Let's report on the Tractor simulator (cabin tractor interface with Farming Simulator 17): Part 1 to see the global sketch.

 

All the PC, except Master, start at boot. The following launch.bat are linked in C:\Users\{USER}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup directory (accessible with Startup:shell in execute menu).

 

launch.bat____________________________________________________________________________

@echo off

color 71

set PROC=ExeToLaunchWithoutExeExtension

set EXE=%PROC%.exe

title %PROC%

:RESTART

taskkill /F /IM %EXE% >NUL 2>&1

%EXE%

if %ERRORLEVEL% LSS 40 goto STOP

if %ERRORLEVEL% EQU 40 echo RESTART ... & goto RESTART

if %ERRORLEVEL% EQU 41 goto REBOOT

if %ERRORLEVEL% EQU 42 goto SHTDWN

exit

 

:REBOOT

echo REBOOT...

start shutdown /r /t 2

exit

 

:SHTDWN

echo SHUTDOWN ...

start shutdown /s /t 2

exit

 

:STOP

pause

____________________________________________________________________________________

 

This allows controlling the behavior of the PC depending on the program exit status (<40=pause, 40=relaunch program, 41=reboot PC, 42=shutdown PC).

 

And all the python scripts are compiled (minGW64) with nuitka with this batch file (just drag and drop the py file on this batch file):

 

compile.bat___________________________________________________________________________

@echo off

color 71

set PROCpy=%1

set PROC=%~n1

set PPTH=%~p1

cd %PPTH%

title Compiling %PROC% ...

echo Compiling %PROCpy% ...

set PATH=C:\minGW64\bin;%PATH%

nuitka --recurse-all --mingw --icon=%PROC%.ico --remove-output --plugin-enable=multiprocessing  %PROCpy%

pause

____________________________________________________________________________________

 

 

1/ FS_Hat

 

It's named "FS_HAT" as it will use the info coming from a, b, c described below to monitor the Farming Simulator 17 Software.

 

This PC is connected to:

a/ Arduino DUE native port, considered as a keyboard by the PC

b/ Arduino DUE USB (serial) link to exchange data between PC ↔ Arduino

c/ BU0836A that simulates a joystick

 

1.1/ The Serial link with Arduino DUE and UNO

 

This function uses 2 queues, one to send the orders from the PC to the Arduino DUE, one to receive the data from the Arduino DUE to the PC.

 

defSerialLink.py_______________________________________________________________________

from defErrPrint import errPrint

from queue import Empty

 

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

#

def serialLink(serportspeed, eols, getData, putData, verbose):

    """ Manage a serial link:

            serportspeed : serial [Port, speed]

                            ex.: ['COM3', 115200], ['ttyUSB0', 115200]

            eols : End Of Line of the System to communicate with

                            ex.: '\r\n' for arduino, '\n' for others

            getData : get the data from the father process to send through link

                        a queue1.get_nowait in the father process

            putData : send the received data from the link to the father process

                        a queue2.put_nowait in the father process

    """

   

    from serial import Serial, SerialException

 

    procName = 'Serial Link'

 

    # ----- adapt the format for the father process

    def putFmtData(msg):

        putData(['SER', msg, 'serialLink'])

   

    # ----- open link

    serialOn = True

    try:

        ser = Serial(*serportspeed)

    except:

        errPrint ('Serial port {} not found'.format(serportspeed[0]))

        serialOn = False

               

    else:

        print(' >> {} {} ({}): On'.format(procName, ser.name, ser.baudrate))

   

    rems = ''   # ---- remainning strings from the link (not ended with eols)

 

    # ----------------------------------------------------------------- Loop

    while serialOn:

 

        try:

            # ---- get (or not if Empty) an entry from father process

            msg = getData()

 

        except Empty:

 

            # ---- if Empty, try to read if there is an entry from link

            try:

                if ser.in_waiting: # ---- incoming bytes waiting to be read

                    rcvs =  rems + ser.read(ser.in_waiting).decode()

                    # ----- split with eols

                    r = rcvs.split(eols)

                    # ---- pop the last element (should be '' otherwise

                    #      the beginning of another order)

                    rems = r.pop()

                    for s in r:

                        # ----- send the received data to the father process

                        if s: putFmtData(s)

                        if verbose: print('\treceived : ' + s)

                

            except SerialException: serialOn = False

 

        else:

            # ----- if 'Exit' from the father process -> exit the function

            if 'SerExit' in msg:

                ser.close()

                break

            else:

                # ----- otherwise, send the data through the link

                try:

                    # ----- add eols to the string to send

                    ser.write((msg + eols).encode())

                    if verbose: print('\tsent : ' + msg)

                   

                except SerialException: serialOn = False

                   

    # ----- special info for the father process to warn Pb on serial link

    if not serialOn: putFmtData('0serial')

   

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

#                       

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

____________________________________________________________________________________

 

   

defTCPIP and defErrPrint are already described in a previous post.

 

 

Fs_Hat.py____________________________________________________________________________

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

 

""" PROCESS Fs_Hat

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

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '2.0'

__date__ = 'nov 2017'

 

from queue import Empty

from time import sleep, strftime, time

from multiprocessing import Process

from defErrPrint import errPrint

     

# ----- Lookup table to translate info coming from DUE to TCP

# ----- DUE orders coded with 3 letters to speed up the communication

outLU = { 'ACC' : 'Acc=' ,         \

          'STW' : 'STW=' ,         \

          'BRK' : 'Brake=',        \

          'RRL' : 'ReRedLght=',    \

          'BCN' : 'Beacon=',       \

          'LFD' : 'LghtFrDown=',   \

          'LRD' : 'LghtReDown=',   \

          'RBL' : 'RightBlinker=', \

          'LBL' : 'LeftBlinker=',  \

          'WRN' : 'Warning=',      \

          'LFU' : 'LghtFrUp=',     \

          'LRU' : 'LghtReUp='      }

         

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

#                                                         Farming Simulator Hat

def gameManager(user, qData, sendSerial, putData):

    """ Manage the Farming Simulator software:

            user : user's name using the software (which use 'My Games' dir)

            getData : get the data from the father process to send to FS

                        a queue1.get_nowait in the father process

            sendSerial : send orders to the serial link

            putData : send the received data to the father process

                        a queue2.put_nowait in the father process

    """

   

    from PIL import Image

    from os import path, listdir, remove, kill, chdir

    from glob import glob as dirlist

    from subprocess import call, check_output

    from win32gui import FindWindow, ShowWindow, BringWindowToTop, \

                         SetForegroundWindow

    from threading import Timer

 

    procName = 'gameManager'

 

    getData = qData.get

    localPutData = qData.put_nowait

 

    # ---------------------------------------------------- FS images management

    scrShotDir = r'C:\Users\{}\Documents\My Games\FarmingSimulator2017\screenshots'.format(user)

    scrShotFile = path.join(scrShotDir, 'fsScreen_20{}_*.png')

 

    # ----- to speed the analyse, just pick a 2x14 pixels block from the image

    imgBox = [[750, 370, 752, 384], [300, 950, 302, 964]]

 

    outMsg = 'FSReboot'

 

    def waitForImg(n):

        """ wait for the image <n>;

                n = 0 : enter game image

                n = 1 : career image

        """

        ok = True

        # ----- purge directory

        for file in listdir(scrShotDir): remove(path.join(scrShotDir, file))

        cont = True

        sleep(1 + 2*n) # longer for image 1

        T0 = time()

        while cont:

            # ----- send Print Screen

            sendSerial('kPSC')

            # ----- time for FS to write the file

            sleep(.1)

            # ----- format of the file generated by FS when Print Screen

            imgfile = sorted(dirlist(scrShotFile.format(strftime('%y_%m_%d'))))

            if imgfile:

                try:

                    # ----- read the screenshot image and crop a block

                    img = Image.open(imgfile.pop()).crop(imgBox[n])

                except: pass

                else:

                    # ----- verify if all the cropped area is green

                    cont = bool(img.getcolors()[0] != (28, (127, 192, 50)))

                    # ----- delete the exceeded screenshots files

                    for file in imgfile: remove(file)

            if cont: sleep(.05)

            # ----- if too long: FS is lost -> kill then reboot the game

            if time() - T0 > 10:

                sendSerial('sOk0')

                out = check_output('tasklist /NH /FI "STATUS eq RUNNING" ' + \

                           '/FI "IMAGENAME eq FarmingSimulator2017Game.exe"')

                for line in out.splitlines():

                    if b'FarmingSimulator2017Game' in line:

                        kill(int(line.split()[1]), 15)

                        print('\t! game lost')

                        ok = False

                break

        return ok

 

    def getFSWindow():

        """ Try during 5s to find the FS window

        """

        for i in range(50):

            h = FindWindow(0, "Farming Simulator 17")

            if h: break

            else: sleep(0.1)

        return h

 

    def setFSForeground(h):

        """ put the window foreground

        """

        try:

            ShowWindow(h, 5)

            BringWindowToTop(h)

            SetForegroundWindow(h)

        except: pass

 

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

 

    # ================================================================ launch Farming Simulator

    # ----- go in the right directory to launch the software

    chdir(r'C:\Program Files (x86)\Farming Simulator 2017')

   

    # ----- call Farming Simulator

    call(['FarmingSimulator2017.exe'])

   

    # ----- wait installed (2 x 5 sec)

    for i in range(2):

        h = getFSWindow()

        if h: break

 

    tt = None

 

    # ----- put it foreground to receive the corresponding orders

    if h:

        setFSForeground(h)

   

        # ----- wait first image ...

        if waitForImg(0):

       

            # ----- enter game (career)

            sendSerial('kRET')

 

            # ----- game not loaded

            gameOn = False

 

            # ----- every 2.5 seconds, verify FS alive

            tt = Timer(2.5, localPutData, args=(['FSAlive','?'], ))

            tt.start()

                

            # ======================================================================= manage the orders

            while "the FS window is present":

 

                try:

                    # ----- wait for the orders to control FS

                    cmd, val = getData(True)

                except Empty:

                    # ----- should not arrive ... but verify FS active

                    if not getFSWindow(): break

                else:

 

                    # ----- get FS window handle

                    h = getFSWindow()

 

                    if h: # if active (else break loop)

 

                        # ----- put the window foreground

                        setFSForeground(h)

                   

                        if 'FSAlive' in cmd:

                            # ----- test every 2.5 sec if FS alive

                            tt = Timer(2.5, localPutData, args=([cmd, val], ))

                            tt.start()

 

                        elif 'SKB' in cmd:

                            # ----- send to keyboard, SKB=

                            # keyA, keyE, keyH, key{letter},

                            # kTAB, kRET, kBKS, kESC ...

                            # relb, rstd

                            sendSerial(val)

                           

                        elif 'Cmp' in cmd:

                            # ----- launch comparison

                            sendSerial('kBKS')

                            sendSerial('kTAB')

                            sendSerial('keyH')

                            sendSerial('kTAB')

                            sendSerial('keyH')

 

                        elif 'Reload' in cmd:

                            gameOn = False

                            sendSerial('sOk0')

                            localPutData(['Load_R', val])

 

                        else:

 

                            # ---- game to close if open

                            if gameOn:

                                # ----- send the quit game code

                                sendSerial('sOk0')

                                sendSerial('qtGm')

                                sleep(3)

                                ok = waitForImg(0)

                                if ok:

                                    sendSerial('kRET')

                                    sleep(.1)

                            else: ok = True

 

                            if ok:

 

                                if 'Load' in cmd:

                                    n = int(val)

                                    # print('\t· load game #' + sn)

                                    # ----- send ld+num code of game to load

                                    sendSerial('ld{:02d}'.format(n))

                                    sleep(2 + n/10)

                                    # ----- wait for the image of game loaded

                                    ok = waitForImg(1)

                                    if ok:

                                        # ----- send Enter if ok

                                        sleep(.1)

                                        sendSerial('kRET')

                                        gameOn = True

                                        sleep(0.1)

                                        if   '_N' in cmd:

                                            sendSerial('SiGa')

                                        elif '_H' in cmd:

                                            sendSerial('HaGa')

                                        elif '_R' in cmd:

                                            localPutData(['Cmp', '1'])

                                        sendSerial('sOk1')

                                    else: break

 

                                elif 'Quit' in cmd:

                                    gameOn = False

 

                                elif 'Exit' in cmd:

                                    # ----- send quit FS code

                                    sendSerial('qtFS')

                                    outMsg = 'FSExit'

                                    break

 

                    else:

                        print('    - Farming Simulator exited -')

                        break

               

    # ---- warn FS Exit or Reboot

    if tt is not None:

        try: tt.cancel()

        except: pass

    putData(['SUB', outMsg, procName])

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

#                       

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

   

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

#                                MAIN

if __name__ == "__main__":

 

    from configparser import ConfigParser

    from multiprocessing import Queue

    from defTCPIP import TCPLink

    from defSerialLink import serialLink

   

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

    config = ConfigParser()

    config.filename = 'FS_Hat.ini'

    config.savfilename = config.filename.replace('.','_sav.')

 

    Ok = False

    for tst in range(2):

        # ----- verify config ini ok

        try:

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

            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)

            serial = bool(int(settings['serial']))

            DUE = config['DUE']

            DUEcom = ['COM{}'.format(int(DUE['cmd_port'])), \

                       int(DUE['cmd_speed'])]

            user = config['USER']['user'].strip()

            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'])

            sLang = config['LANG']['lang']

            Ok = True

            break

        except:

            # ---- take the save ini

            print('\t! Restore ini file')

            with open(config.filename, 'w', encoding='utf-16') as ini:

                with open(config.savfilename, 'r', encoding='utf-16') as _ini:

                    ini.write(_ini.read())

 

    if not Ok:

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

        exit(44)

                   

    # ----- Proc Name from ini = Fs_Hat

    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)

    vrbs = bool(verbose & 4)

   

    qIn = Queue()

    putData = qIn.put_nowait

    getData = qIn.get

 

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

    eth = TCPLink(IPtoNnode, IPtoNlinks, portBase, putData)

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

 

    # ----------------------------------------------------- process serial link

    qSerial = Queue()

    def launchSerial():

        pSerial = Process(target=serialLink, \

                          args=(DUEcom, '\r\n', \

                                qSerial.get_nowait, putData, vrbs))

        pSerial.start()

        return pSerial

    if serial:

        sendSerial = qSerial.put_nowait

        pSerial = launchSerial()

    else:

        sendSerial = print # ----- for debug

 

    # -------------------------------------------------------- game management

    qFS = Queue()

    sendFS = qFS.put_nowait

    def launchFS():

        pFS = Process(target=gameManager, args=(user, qFS, sendSerial, putData))

        pFS.start()

        return pFS

    pFS = launchFS()

       

    # ----------------------------------------------------------- begin program   

    exitVal = 0

    exitFlag = False

    T0 = time()

    Tend = 15

    ackn = False

 

    while True:

 

        try:

            ret = getData(True)

        except Empty:

            ret = []

        except KeyboardInterrupt:

            # ----- simulate an exit order

            ret = ['TCP:rcv0', 'Exit=0', 'Ctrl+C']

 

        if ret: typ, msgs, proc = ret

        else: typ = ''

 

        if 'TCP' in typ:

            # ------------------------------------------------------- TCP entry

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

           

            if 'out' in typ2:

                # ---- have to relaunch ethernet link

                eth.relaunch(proc)

               

            elif 'snt1' in typ2:

                # ---- info of what was sent

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

               

            elif 'snt0' in typ2:

                # ---- info of fail sending

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

               

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

                # ----- received info

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

                if exitFlag:

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

                    if typ2 == 'rcv1':

                        if all(eth.stop(proc)): break

 

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

                       

                    # ----- analyse received order ('cmd=val')

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

 

                    # ----- send aknowledgment if really comming from TCP

                    ackn = bool(proc != 'Ctrl+C')

 

                    if 'Exit' in cmd:

                        # stop requested by TCP

                        exitVal = int(val)

                        if pFS.is_alive(): sendFS([cmd, val])

                        exitFlag = True

                        T0 = time()

 

                    else:

                        # ----- send to FS

                        sendFS([cmd, val])

                  

            elif 'rcv?' in typ2:

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

                ackn = False

               

 

        # -------------------------------------------------------- Serial entry

        elif 'SER' in typ:

            char = msgs[0]

           

            if   char == '0':

                # ----- have to relaunch serial process

                pSerial = launchSerial()

 

            elif char == '1':

                # ----- to send only one host

                eth.sendTo(outLU[msgs[1:4]]+ msgs[4:], 'Rpi_Disp')

 

            elif char == '2':

                # ----- send to many hosts

                eth.sendTos(outLU[msgs[1:4]]+ msgs[4:], ['Rpi_Out', 'Rpi_Disp'])

               

        # --------------------------------------------------- Sub process entry

        elif 'SUB' in typ:

 

            if   'FSExit'   in msgs: break

            elif 'FSReboot' in msgs:

                print('\t· FS Reboot')

                pFS.join()

                pFS = launchFS()

           

        # ------------------------------------------------------------ unknown

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

 

        if exitFlag:

            # ------ force an exit after timeout Tend

            if time() - T0 > Tend or not pFS.is_alive(): break

 

    # ----- free the queue

    qIn.cancel_join_thread()

   

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

    eth.close()

   

    # -------------------------------------------------- wait serial link close

    if serial:

        if pSerial.is_alive(): sendSerial('SerExit')

        pSerial.join()

 

    # --------------------------------------------------- wait process FS close

    if pFS.is_alive(): pFS.terminate() # ----- should not be

    pFS.join()

 

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

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

       

    exit(39 + exitVal)

#

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

____________________________________________________________________________________

 

To know if it's the good moment to enter a key of a sequence of keys to continue a FS (Farming Simulator) session when loading a game, I simulate the 'PrintScreen' key (sendSerial('kPSC')) and retreive the image in 'C:\Users\{USER}\Documents\My Games\FarmingSimulator2017\screenshots' directory. Then, thanks to PIL, I compare some image areas (green pixels) to verify if the game is loaded (def waitForImg).

As Farming Simulator forks at each new session, I have to control if it's still alive (getFSWindow()) and in this case place it foreground to be sure the simulated keyboard send the keys to it (setFSForeground(h)).

 

All info coming from TCP/IP are sent to the FS manager (sendFS([cmd, val])). All the info coming from Arduino, depending on the first digit, are sent on the Raspberry Dashboard (Rpi_Disp) and the Raspberry Outputs (Rpi_Out).

To gain time in the serial communication, I use a Lookup Table (outLU) to translate a few chars command (3 based chars) into a "full" command for the TCP/IP link.

 

The graphic output (HDMI) is split to be displayed on the tractor screen or the main screen and captured through a video grabber (StarTech) to be used by the PC show.

 

 

2/ "Show"

 

This PC is used for the SHOW.

When the animator is not presenting the tractor simulator, it plays some movies in loop (in movies directory) on the main screen. In the meantime, the tractor could be used as a simple simulator playing Farming Simulator.

When the animator switches to the presentation mode, it retreives the images from Farming Simulator (through the video grabber on USB3) and overlay texts, videos, messages on the main screen.

It overlays also the Zen@Terra app (same as the one on the Rasperry "Zen@Terra", Rpi_Pressure).

One can see the demo mode (with a speaker, see video1 ) and the play mode (guests can play with FS17, see video2, video3,).

 

 

 

2.1/ the scrolling banner

 

This function makes a message scroll at the bottom of the screen, overlaying the videos in loop, announcing the next presentation (ex. "Next presentation in 10 mn").

A final countdown for the last 10 sec with big numbers covering the screen.

 

defBanner.py_________________________________________________________________________

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

 

""" Banner

    ======

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.0'

__date__ = 'nov 2017'

 

import tkinter as tk

from threading import Timer

from multiprocessing import Process

 

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

#

def passmsg(text1, text2, N):

    """ pass the 2 texts (original lang and English)

            from right to left of the screen

    """

   

    # ----- init fig

    bannerFig = tk.Tk()

    bannerFig.configure(bg='black')

    R = bannerFig.winfo_screenwidth()

    H = bannerFig.winfo_screenheight()

    # ----- height font = 1/12 screen height

    HFnt = int(H/12)

   

    bannerFig.overrideredirect(True)

    bannerFig.lift()

    bannerFig.wm_attributes("-topmost", True, "-disabled", True,\

                            "-transparentcolor", "black")

    # ----- two lines to display

    w1 = tk.Label(bannerFig, text=text1,\

                  fg='white', bg='black', \

                  font=('Arial',HFnt,'bold'))

    w1.grid(row=0, column=0)

    w2 = tk.Label(bannerFig, text=text2 ,\

                  fg='white', bg='black', \

                  font=('Arial',HFnt,'italic'))

    w2.grid(row=1, column=0)

    # ----- poistion in y depending on existing text2

    L = max(len(text1),len(text2))

    nline = int(H-N*HFnt)

    for i in range(-R, R+L*int(HFnt/4.5), 10):

        bannerFig.geometry("+{}+{}".format(-i, nline))

        bannerFig.update()

        bannerFig.after(25)

    bannerFig.destroy()

#

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

 

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

#

def lastsec(nsec, soundCountDown):

    """ display sec coutdown 3/4 screen

    """

    from winsound import PlaySound, SND_FILENAME

    from threading import Thread

    # ----- init fig

    bannerFig = tk.Tk()

    bannerFig.configure(bg='black')

    R = bannerFig.winfo_screenwidth()

    H = bannerFig.winfo_screenheight()

    # ----- height font = 3/4 screen height

    HFnt = int(H*3/4)

   

    bannerFig.overrideredirect(True)

    bannerFig.lift()

    bannerFig.wm_attributes("-topmost", True, "-disabled", True,\

                            "-transparentcolor", "black")

    cntdwn = tk.IntVar()

    cntdwn.set(nsec)

    # ----- digit(s) to display

    w1 = tk.Label(bannerFig, textvariable=cntdwn,\

                  fg='white', bg='black', \

                  font=('Arial',HFnt,'bold'))

    w1.grid(row=0, column=0)

    bannerFig.update()

    # ----- if associated sound, play it

    if soundCountDown:

        Thread(target=PlaySound, args=('BeepTractor.wav', SND_FILENAME)).start()

    for i in range(nsec):

        cntdwn.set(nsec-i)

        bannerFig.update_idletasks()

        # ----- get the new size (change from 10 to 9 for example)

        Hr = bannerFig.winfo_height()

        Rr = bannerFig.winfo_width()

        bannerFig.geometry("+{}+{}".format(int((R-Rr)/2), int((H-Hr)/2)))

        bannerFig.update()   

        bannerFig.after(1000)

    cntdwn.set(0)

    bannerFig.update()

    bannerFig.after(250)

    bannerFig.destroy()

#

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

   

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

#                                    Banner

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

#

def Banner(qIn, qOut, Text, soundCountDown):

 

    print(' >> Banner proc: On')

 

    # ----- init

    banOn = True

    # ----- lang = En by default

    sLang = 'en'

    # ----- step in mn (update every mn)

    stepmn = 1

    # ----- setp in sec

    stepsec = 5

    # ----- beginning of countdown

    endsec = 10

    # ----- begin with countdown in mn

    mode = 'mn'

    ti = 0

 

    # ----- fake process with is_alive false to initiate proc

    class procclass():

        def is_alive(self):  return False

        def terminate(self): pass

        def join(self):      pass

    proc = procclass()

   

    alrdyExit = False

 

    def sndFmt(msg):

        # ----- send info back to the father

        qOut.put_nowait(['SUB', msg, 'Banner'])

 

    getData = qIn.get

    putData = qIn.put_nowait

 

    while banOn:

       

        # ----- get the order

        cmdval = getData()

 

        # ----- split command value

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

 

        # ----- lang

        if cmd == 'Lang':

            sLang = val

 

        # ----- launch countdown

        elif cmd == 'remTime':

            alrdyExit = False

            # ---- slpit remaining time and unit (remTime=20:mn)

            remstr, mode = [v.strip() for v in val.split(':')]

            # ----- remaining time in int

            remTime = int(remstr)

            lnchflag = True

            # ----- mn

            if mode == 'mn':

                N = 3.25 # factor multply font height (see passmsg)

                # ----- text 1 = requested lang

                text1 =' {} '.format(Text[sLang].format(remTime))

                # ----- text 2 = English lang

                text2 =' {} '.format(Text['en'].format(remTime))

                # ----- check remaining time doesn't lead to negative min

                n, rem = divmod(remTime, stepmn+1)

                # ----- compute tsec for the timer to go next value

                if n:

                    # ----- no pb, continue counting in mn

                    tsec = stepmn

                else:

                    # ----- need to count in sec

                    tsec = rem - 1

                    if tsec == 0:

                        # ----- 1 mn

                        putdata('remTime=60:sec')

                        lnchflag = False

                    elif tsec < 0:

                        # ----- 0 mn

                        putData('endTime=1')

                        lnchflag = False

                tsec *= 60

            # ----- sec

            else:

                N = 1.625 # factor multply font height (see passmsg)

                text1 = '{} s ...'.format(remTime)

                # ----- no need text 2

                text2 = ''

                # ----- check remaining time doesn't lead to negative sec

                n, rem = divmod(remTime, stepsec+endsec)

                if n:

                    # ----- no pb, continue counting in sec

                    tsec = stepsec

                else:

                    tsec = rem

                    if tsec <= 0 :

                        putData('endTime=1')

                        lnchflag = False

               

            if lnchflag:

                # ----- if valable tsec

                if ti:

                    # ----- stop timers

                    ti.cancel()

                    ti.join()

                if proc.is_alive():

                    # ----- stop current proc

                    proc.terminate()

                    proc.join()

                   

                # ----- launch new timer

                ti = Timer(tsec, putData, ('next=1',))

                ti.start()

 

                # ----- launch new process

                proc = Process(target=passmsg, \

                               args=(text1, text2, N))

                proc.start()

           

        # ----- next for  countdown

        elif cmd == 'next':

            if mode == 'mn':

                remTime -= stepmn

                if remTime <= 1:

                    putData('remTime=60:sec')

                else:

                    putData('remTime={}:mn'.format(remTime))

            else:

                remTime -= stepsec

                if remTime <= endsec:

                    p = Process(target=lastsec, args=(endsec, soundCountDown))

                    p.start()

                    sndFmt('last10s=1')

                    p.join()

                    putData('endTime=1')

                else:

                    putData('remTime={}:sec'.format(remTime))

                   

        # ----- set mn step

        elif cmd == 'Stepmn':

            stepmn = int(val)

 

        # ----- set sec step

        elif cmd == 'Stepsec':

            stepsec = int(val)

 

        # ----- set end sec

        elif cmd == 'Endsec':

            endsec = int(val)

 

        elif cmd == 'endTime':

            sndFmt('endTime=1')

            putData('BanExit=0')

 

        # ----- BanExit : 0 to stay in loop, 1 to exit the process

        elif cmd == 'BanExit':

            exitVal = int(val)

            if not alrdyExit or exitVal:

                if exitVal:

                    banOn = False

                    print('\tBanner proc: exiting...')

                else:

                    print('\tBanner proc: ready...')

                if ti:

                    ti.cancel()

                    ti.join()

                if proc.is_alive():

                    proc.terminate()

                    proc.join()

                alrdyExit = True

 

    qIn.cancel_join_thread()

   

    # ----- exit process

    print(' << Banner proc: Out')

#

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

____________________________________________________________________________________

 

 

2.2/ Displaying the benefits (of using Zen@Terra solution)

 

This function overlays a png file explaining the benefits depending on the used language.

ex. English

defBenef.py___________________________________________________________________________

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

 

""" Benef PROCESS

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

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.0'

__date__ = 'nov 2017'

 

import tkinter as tk

 

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

#

class DispBenef:

 

    def __init__(self, lang):

   

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

        #                          Init Graphic

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

        # ----- Graphic init

        fig = tk.Tk()

        fig.overrideredirect(1)

        fig.title('Benef')

        fig.configure(bg='black')

        fig.wm_attributes("-topmost", True, "-disabled", True,\

                                "-transparentcolor", "black")

        scrSize = [1920, 1080]

        cv = tk.Canvas(fig, width=scrSize[0]-4, height=scrSize[1]-4, bg = 'black')

        cv.grid()

 

        # ----- background image

        self.Img = tk.PhotoImage(file='./Img/{}_Benef.png'.format(lang))

        self.wBkgd = cv.create_image(0, 0, image=self.Img, anchor=tk.NW)

        fig.geometry('1920x1080+0+0')

        self.fig = fig

        self.setConfig = cv.itemconfig

        self.update()

       

    def update(self):

        self.fig.update()

        self.fig.after(500, self.update)

 

    def hide(self):

        self.fig.withdraw()

        self.fig.update_idletasks()

 

    def show(self):

        self.fig.deiconify()

        self.fig.lift()

        self.fig.update()

 

    def lang(self, lang):

        self.Img = tk.PhotoImage(file='./Img/{}_Benef.png'.format(lang))

        self.setConfig(self.wBkgd, image = self.Img)

 

    def exit(self):

        self.fig.destroy()

#

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

____________________________________________________________________________________

 

 

2.3/ Pictures and videos overlay

 

This function merges pictures and videos with the Farming Simulator screens (captured with a video grabber)  thanks to openCV.

It allows zoom in and zoom out effects.

 

defObjOverlay.py_______________________________________________________________________

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

 

import cv2

from numpy import linspace

from os import path

 

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

#

class OvlObj():

    """ class to manage the video (load, stop, pause, zoom, ..)

    """

    def __init__(self, name, offsetloop):

       

        # ----- name of object

        objType, filename = name.split(':')

        offset, loop = offsetloop

        self.name = path.splitext(path.split(filename)[1])[0]

        self.fileName = './Img/' + filename

        if 'img' in objType:

            # ----- image filename

            self.vdo = False

            # ----- open video (cv capture)

            self.firstImage = cv2.imread(self.fileName)

        else:

            # ----- video filename

            self.vdo = True

            # ----- open video (cv capture)

            self.capt = cv2.VideoCapture(self.fileName)

            # ----- read first frame ...

            self.firstImage = self.capt.read()[1]

        # ----- save the last image

        self.lastImage = self.firstImage

        # ----- relative position of the video (between 0 and 1)

        self.offsetx, self.offsety = offset

        # ---- frame per sec

        self.fps = 25

        # ----- ... to know the size of it

        self.rows, self.cols, _ = self.firstImage.shape

        # ----- then compute where to place in pixels

        self.offsetx = min(max(0, round(1920 * self.offsetx - self.cols/2)), 1920-self.cols)

        self.offsety = min(max(0, round(1080 * self.offsety - self.rows/2)), 1080-self.rows)

        # ----- compute the slice for the frame (inverted x, y)

        self.X = slice(self.offsety, self.offsety + self.rows, 1)

        self.Y = slice(self.offsetx, self.offsetx + self.cols, 1)

        # ----- mode loop

        self.loop = bool(loop)

        # ----- flag mode zoom (in or out)

        self.zoomFlag = False

        # ----- index of zoom range

        self.zi = 0

        # ----- final index

        self.ze = 0

        # ----- range (from 0 to 1)

        self.zr = []

        # ----- pause at frame

        self.pauseFrame = 0

        self.nbFrames = 0

        # ----- pause flag

        self.pause = False

 

    # ------------------------------------------------------------- get frames

    def getNextFrame(self):

        # ----- copy the slices for the frame

        X, Y = self.X, self.Y

        # ----- flag wether to remove the object or not (for zoomOut)

        rmvObj = False

        if not self.pause:

            # ----- get the next frame

            if self.vdo: frame = self.capt.read()[1]

            else:        frame = self.lastImage

            if frame is None:

                # ----- if end of video

                if self.loop:

                    # ----- if loop, copy fisrt image in last image

                    self.lastImage = self.firstImage

                    # ----- rewind the video

                    self.rewind()

                else:

                    # ----- if not loop, pause

                    self.pause = True

            else:  

                self.nbFrames += 1

                if self.nbFrames == self.pauseFrame: self.pause = True

                if self.zoomFlag:

                    # ----- if zoom, take the next increment

                    if self.zi < self.ze:

                        self.zi += 1

                        if self.zi == self.ze:

                            # ----- if end of increment, zoom flag false

                            self.zoomFlag = False

                            if self.zoomOutFlag:

                                # ----- if zoomOut, rewind the file

                                self.rewind()

                                # ----- and set the flag to remove it

                                rmvObj = True

                    # ----- take the ratio of image

                    r = self.zr[self.zi-1]

                    # ----- reduce the frame following the ratio

                    frame = cv2.resize(frame, (0,0), fx=r, fy=r)

                    # ----- get the size of the reduced video

                    rows, cols, _ = frame.shape

                    # ----- compute the new position (centered) and inverted x, y

                    x = self.offsety + round((self.rows-rows)/2)

                    y = self.offsetx + round((self.cols-cols)/2)

                    # ----- compute the slices

                    X, Y = slice(x, x + rows, 1), slice(y, y + cols, 1)

                # ----- put the frame in lastimage

                self.lastImage = frame

        return X, Y, self.lastImage, rmvObj

 

    # --------------------------------------------------------- initiate zoom In

    def zoomIn(self):

        # ---- init zoom from 0 to 1 in 1s

        self.zoom([0, 1, 1])

       

    # -------------------------------------------------------- initiate zoom Out

    def zoomOut(self):

        # ---- init zoom from 1 to 0 in 1s

        self.zoom([1, 0, 1])

 

    # ----------------------------------------------------------- initiate zoom

    def zoom(self, valz):

        # ----- zomm flag true

        self.zoomFlag = True

        # ----- zoom Out if begin > end

        self.zoomOutFlag = bool(valz[0] > valz[1])

        # ----- convert sec in nb of frames

        valz[2] = int(valz[2] * self.fps)

        # ----- compute the range (remove 0 and 1)

        zr = linspace(*valz)[1:-1]

        # ---- set the range

        self.zr = zr

        # ----- init the index

        self.zi = 0

        # ----- set the final index

        self.ze = len(zr)

   

    # ----------------------------------------------------------------- rewind

    def rewind(self):

        if self.vdo:

            # ----- set the video at the fisrt frame

            self.capt.set(cv2.CAP_PROP_POS_FRAMES, 0)

            self.nbFrames = 0

           

    # ------------------------------------------------------- pause at frame nb

    def pauseAtFrame(self, noFrame):

        if self.vdo:

            # ----- set the video at frame #nbFrame

            self.pauseFrame = noFrame

           

    # ------------------------------------------------------- pause at frame nb

    def goToFrame(self, nbFrame):

        if self.vdo:

            # ----- set the video at frame #nbFrame

            self.capt.set(cv2.CAP_PROP_POS_FRAMES, nbFrame)

            self.pause = True

            self.nbFrames = nbFrame

           

#

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

 

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

#

def objOverlay(getData, vdoChannel, ovlsProp):

   

    from queue import Empty

    import numpy as np

    from win32api import SetCursorPos

    from win32gui import FindWindow, ShowWindow, BringWindowToTop

   

    # ----- transparent image for the background

    transpImg = cv2.imread('./Img/TranspImg.png', cv2.IMREAD_UNCHANGED)

   

    # ----- Black image for the background

    blkImg = cv2.imread('./Img/BlckBckgrnd.png', cv2.IMREAD_UNCHANGED)

 

    # ----- set video window, fullscreen

    cv2.namedWindow('objOverlay', cv2.WND_PROP_FULLSCREEN)

    cv2.setWindowProperty('objOverlay', cv2.WND_PROP_FULLSCREEN, \

                                     cv2.WINDOW_FULLSCREEN)

 

    # ----- capture FS mainFrames

    vdoCapt0 = cv2.VideoCapture(vdoChannel)

    # vdoCapt0 = cv2.VideoCapture('./Movies/00_cubtotal1920.mov') ## debug

    # ----- set the video size

    vdoCapt0.set(3, 1920)

    vdoCapt0.set(4, 1080)

 

    # ----- load the videos to overlay

    ovlObjs = [OvlObj(*vdoProp) for vdoProp in ovlsProp.items()]

 

    wait_ms = int(1000/60) # 60 Hz of refresh

    wait4data = False

    # ----- display flag

    dispFlag = True

 

    # ----- seletion of video to display

    vdoSel = []

 

    # ----- hide the cursor

    SetCursorPos([-1, -1])

 

    winId = FindWindow(0, "objOverlay")

 

    while(True):

 

        try:

            # ----- wait for incoming order

            data = getData(wait4data)

        except Empty:

            data =''

 

        if 'Hide' in data:

            # ----- hide the display

            ShowWindow(winId, 0)

            dispFlag = False

            wait4data = True

            

        elif 'Show' in data:

            # ----- show the display

            ShowWindow(winId, 5)

            BringWindowToTop(winId)

            # SetForegroundWindow(winId)

            dispFlag = True

            wait4data = False

 

        elif 'hide' in data:

            # ----- hide the video object (remove from Selection)

            ovlobj = data.split(':')[0]

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    if obj in vdoSel : vdoSel.remove(obj)

                    break

 

        elif 'show' in data:

            # ----- show the video object (insert in Selection)

            ovlobj = data.split(':')[0]

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    if obj not in vdoSel : vdoSel.append(obj)

                    break

 

        elif 'pauseAtFrame' in data:

            # ----- pause at frame #n

            ovlobj, val = data.split(':')

            n = int(val[val.find('(')+1:val.find(')')])

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    if obj not in vdoSel : vdoSel.append(obj)

                    obj.pauseAtFrame(n)

                    break               

 

        elif 'pauseAtSec' in data:

            # ----- pause at frame #n

            ovlobj, val = data.split(':')

            n = float(val[val.find('(')+1:val.find(')')])

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    if obj not in vdoSel : vdoSel.append(obj)

                    obj.pauseAtFrame(int(n*obj.fps))

                    break               

 

        elif 'pause' in data:

            # ----- set pause mode

            ovlobj = data.split(':')[0]

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    obj.pause = True

                    break

 

        elif 'goToFrame' in data:

            # ----- pause at frame #n

            ovlobj, val = data.split(':')

            n = int(val[val.find('(')+1:val.find(')')])

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    if obj not in vdoSel : vdoSel.append(obj)

                    obj.goToFrame(n)

                    break

           

        elif 'goToSec' in data:

            # ----- pause at sec #n

            ovlobj, val = data.split(':')

            n = float(val[val.find('(')+1:val.find(')')])

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    if obj not in vdoSel : vdoSel.append(obj)

                    obj.goToFrame(int(n*obj.fps))

                    break

 

        elif 'restart' in data:

            # ----- unset the pause mode

            ovlobj = data.split(':')[0]

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    obj.pause = False

                    break

           

        elif 'rewind' in data:

            # ----- rewind the movie

            ovlobj = data.split(':')[0]

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    obj.rewind()

                    break

 

        elif 'play' in data:

            # ----- play from the beginning

            ovlobj = data.split(':')[0]

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    if obj not in vdoSel : vdoSel.append(obj)

                    obj.rewind()

                    break

 

        elif 'zoomIn' in data:

            # ----- insert the video in selection and activate the zoom

            ovlobj = data.split(':')[0]

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    if obj not in vdoSel : vdoSel.append(obj)

                    obj.zoomIn()

                    break

           

        elif 'zoomOut' in data:

            # ----- activate the zoom

            # obj will be removed from selection with rmvObj flag set with getNextFrame

            ovlobj = data.split(':')[0]

            for obj in ovlObjs:

                if ovlobj in obj.name:

                    obj.zoomOut()

                    break

               

        elif 'Exit' in data:

            # ----- exit process

            break

       

        # ----- Display the current mainFrame

        if dispFlag:

            # ----- get the main frame

            mainFrame = vdoCapt0.read()[1]

            if mainFrame is None: mainFrame = blkImg.copy()

           

            # ---- get the objects to remove to false

            obj2rmv = []

            for obj in vdoSel:

                # ----- get the slices, the frame and the remove object flag

                X, Y, frame, rmvObj = obj.getNextFrame()

                # ----- merge it in the main frame

                mainFrame[X, Y] = frame

                # ----- add in the list to remove

                if rmvObj: obj2rmv.append(obj)

            # ----- remove in the selcection the video to remove

            for obj in obj2rmv: vdoSel.remove(obj)

            # ----- display it

            cv2.imshow('objOverlay', mainFrame)

        else:

            # ---- if display disabled, display a transparent frame

            cv2.imshow('objOverlay', transpImg)

 

        # ----- update display

        if cv2.waitKey(wait_ms) & 0xFF == ord('q'):

            break

           

    # ----- When everything done, release the vdoCapture

    for obj in ovlObjs:

        if obj.vdo: obj.capt.release()

    # ----- destroy the window

    cv2.destroyAllWindows()

#

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

____________________________________________________________________________________

 

 

2.4/ Displaying videos with VLC player (loop)

 

defVLCPlayer.py______________________________________________________________________

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

 

""" VLC Player

    ==========

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.0'

__date__ = 'nov 2017'

 

from vlc import MediaPlayer, EventType

from os import listdir

from multiprocessing import Process

from win32api import SetCursorPos

 

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

# VLC Player

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

def VLC_player(qIn, qOut):

    """

    see example in main

    """

 

    print(' >> VLC player: On')

    vlcp = MediaPlayer()

    vlcp.set_fullscreen(True)

 

    def fnext(event, put): put('next')

   

    evmngr = vlcp.event_manager()

    evmngr.event_attach(EventType.MediaPlayerEndReached, \

                                     fnext, qIn.put_nowait)

    VLCOn = True

    lst = []

    ilst = -1

    mtype = ''

    loop = False

 

    def sndFmt(msg):

        return qOut.put_nowait(['SUB', msg, 'VLC'])

 

    # ----- hide the cursor

    SetCursorPos([-1, -1])

 

    while VLCOn:

 

        cmd = qIn.get()

 

        if '>' in cmd:

            mtype, nameOpt = cmd.split('>')

            if ';' in nameOpt:

                name, opts = nameOpt.split(';', maxsplit=1)

                for cmdval in opts.split(';'):

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

                    if 'fullscreen' in cmd_:

                        vlcp.set_fullscreen(bool(int(val)))

                    elif 'aspect-ratio' in cmd_:

                        vlcp.video_set_aspect_ratio(val)

                    elif 'loop' in cmd_:

                        loop = bool(int(val))

            else:

                name = nameOpt

            if mtype == 'File':

                lst = [name]

            elif mtype == 'Dir':

                lst = ['{}\\{}'.format(name,fn) for fn in listdir(name)]

            elif mtype == 'Opt':

                # fullscreen, loop

                cmd_, val = nameOpt.split('=')

                if 'fullscreen' in cmd_:

                    vlcp.set_fullscreen(bool(int(val)))

                elif 'aspect-ratio' in cmd_:

                    vlcp.video_set_aspect_ratio(val)

                elif 'loop' in cmd_:

                    loop = bool(int(val))

            ilst = 0

            qIn.put_nowait('play')

 

        elif cmd == 'play':

            vlcp.stop()

            vlcp.set_mrl(lst[ilst])

            vlcp.play()

 

        elif cmd == 'next':

            if mtype =='File':

                if loop:

                    qIn.put_nowait('play')

                else:

                    sndFmt('endPlay=1')

                    vlcp.stop()

            else:

                ilst += 1

                if ilst >= len(lst): ilst = 0

                if loop:

                    qIn.put_nowait('play')

                else:

                    sndFmt('endPlay=1')

                    vlcp.stop()

           

        elif cmd == 'Pause':

            vlcp.pause()

           

        elif cmd == 'Stop':

            print('\tVLCPlayer: ready...')

            vlcp.stop()

 

        elif cmd == 'getHWND':

            qOut.put_nowait(vlcp.get_hwnd())

               

        elif cmd == 'VLCExit':

            print('\tVLCPlayer: exiting...')

            vlcp.release()

               

            VLCOn = False

           

    qIn.cancel_join_thread()

    print(' << VLC player: Out')

#

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

____________________________________________________________________________________

 

 

2.5/ Overlay Zen@Terra app

 

Quite the same as the one on Raspberry Zen@Terra ...

 

defZenAtTerr.py_______________________________________________________________________

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

 

""" Zen@Terra

    =========

"""

 

__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 time import time, sleep

from os import path

 

appRed = '#CC0000'

appBlue = '#006699'

appGreen = '#41B649'

appOrange = '#CC6600'

 

def nullFun(): return

 

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

#

class ZenAtTerra():

    """

    Main animation

    """

 

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

    def __init__(self, qIn, qOut):

           

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

        fig = tk.Tk()

        fig.overrideredirect(True)

        fig.lift()

        WxH = [960, 600]

        fig.wm_attributes('-topmost', True)

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

        fig.title('zen@Terra')

 

        langs = ConfigParser()

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

   

        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, \

                        cursor='hand2', \

                       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', 9, 'bold')

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

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

        fontTimer = ('Michelin', 16)

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

        fontSpeed = ('CARACTERES L1', 30)

 

        # ----- Directory containing the pictures

        imgDir = 'ZaTimg'

 

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

 

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

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

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

 

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

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

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

                  anchor=tk.CENTER, \

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

                       .place(x=960-2, y=50/2, anchor=tk.E)

       

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

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

        self.tractorAlone = setImage(371, 321, image=self.tractorAloneTk)

 

 

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

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

        self.tractorTrailer = setImage(384, 321, 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 = 114

       

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

        midfx = 280

        midfy = 409

        midrx = 491

        midry = 400

        midtx = 576 + 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,[37, 44, 37])]

 

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

        ptfx = 303

        ptfy = 492

        ptrx = 515

        ptry = ptfy

        pttx = 597 + self.wshift

        ptty = ptfy

        # ----- 'real' pressures

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

        # ----- target pressures

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

        # ----- manometer

        xym = [[x-15, y+11] 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(145+5, 61, font=fontLabel, \

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

                                              anchor=tk.NW)

        # ----- shadow

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

        but1s = setImage(145+3, 85+4, 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(145, 85, 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(145+166/2, 85+73-20, 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(145+166/2-59+2, 85+73/2-2, \

                                 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(145+166/2, 85+73-22, \

                                 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(145+166/2, 85+70, 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 = 63*i           

            # ----- shadow

            subbut1s = setImage(145+3+j, 169+4, anchor=tk.NW, \

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

           

            # ----- grey background

            subbut1b = setImage(145+j, 169, anchor=tk.NW, tag=taga, \

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

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

           

            # ----- yellow background

            self.subbut1y.append(\

                setImage(145+j, 169, 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(145+j+40/2, 169+39/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(457+4, 61, font=fontLabel, \

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

                                           anchor=tk.NW)

        # ----- shadow

        but2s = setImage(457+3, 85+4, image=self.but1sTk, anchor=tk.NW)

       

        # ----- button

        but2 = setImage(457, 85, 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(457+166/2, 85+7, 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(457+166/2, 85+7, image=self.roadButTk, \

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

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

 

        # ----- text in button: field

        txtBut2f = setText(457+166/2, 85+69, 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(457+166/2, 85+69, 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 = 68*i

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

            taga = (tag, tag2)

 

            # ----- shadow

            subbut2s = setImage(487+3+j, 169+4, anchor=tk.NW, state=tk.HIDDEN, \

                             tag=tag, image=self.subbut1Tks )

           

            # ----- grey background

            subbut2b = setImage(487+j, 169, anchor=tk.NW, state=tk.HIDDEN, \

                             tag=taga, image=self.subbut1Tkg )

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

           

            # ----- yellow background

            self.subbut2y.append(\

                setImage(487+j, 169, 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(487+j+40/2, 169+39/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(823+3, 66+4, image=self.rightButtTks, \

                                     anchor=tk.NW)

        # ----- normal button

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

        self.manualBut = setImage(823, 66, 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(823, 66, image=self.manualButtTkb, \

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

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

 

        # ----- text in button

        txtButManual = setText(823+81/2, 66+58-3, 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(176+3, 556+4, image=self.plusminusButtTks, \

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

        # ----- normal button

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

        minusBut = setImage(176, 556, 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(385, 556, image=self.axleButtTk, \

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

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

       

        # ----- text in button

        txtButAxle = setText(385, 556+46/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(592+3, 556+4, image=self.plusminusButtTks, \

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

        # ----- normal button

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

        plusBut = setImage(592, 556, 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(823+3, 142+5, image=self.rightButtTks, \

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

        # ----- normal button

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

        self.boostBut = setImage(823, 142, 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(823, 142, image=self.boostButtTkr, \

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

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

 

        # ----- text in button

        txtButBoost = setText(823+79/2, 142+58-5, 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 = 805, 248, 33

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

        for i in range(3):

            j = i*43

            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 = 805, 312, 120

        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 = 11

        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 = 805, 261, 106

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

        tag = 'Speed'

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

        dr = 2

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

                          fill=appRed, width=2)

        dr = 20

        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, 220, anchor=tk.NW, state=tk.HIDDEN, \

                             image=self.topview1Tk )]

       

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

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

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

                             image=self.topview2Tk ))

        tag = 'topview'

        ptx = [142, 355, 554]

        pty = [y+220 for y in [36, 343]]

       

        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+4*j, y+1, 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+4*(j==0), y+1, 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-15, y-5+16*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.update = fig.update

        self.setTimer(10, self.wait4action)

        self.setPresSpeed()

        print(' >> Zen@Terra: On')

        self.setTimer(5, self.Hide)

        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, 3, 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, -3, -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 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

       

    # ------------------------------------------------------------------ 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].copy()

        self.setSpeed(self.toolSpeed[self.curTool][self.mode][self.load])

        if saved is None:

            self.targetPres = self.oriTargetPres.copy()

        else:

            self.targetPres = saved.copy()

        if refreshPres: self.manPress()

 

    # ------------------------------------------------------------------ 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'

            # ----- warn Master

            self.putData(['SUB', 'SurfSet=' + self.mode, 'Master'])

            # ----- 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, -200)

            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, 200)

            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', 121, 0)

                self.topViewMoved = True

            else:

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

                if self.topViewMoved: self.move('topview', -121, 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.split('=')

 

            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].lower())

 

            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 'Zoom' in cmd:

                if   'in'  in val: self.zoomIn()

                elif 'out' in val: self.zoomOut()

 

            elif 'Exit' in cmd:

                loop = False

                break

 

            self.fig.update()

                   

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

        else:    self.Quit()

      

    def Hide(self, event=None):

        self.fig.overrideredirect(False)

        self.fig.iconify()

 

    def Show(self, event=None):

        self.fig.deiconify()

        self.fig.overrideredirect(True)

        self.fig.lift()

 

    def zoomIn(self):

        self.Show()

        for h in range(2, 602, 5):

            w = round(h * 1.6)

            self.fig.geometry('{}x{}-0-0'.format(w, h))

            self.update()

        self.fig.geometry('960x600-0-0')

 

    def zoomOut(self):

        for h in range(600, 2, -5):

            w = round(h * 1.6)

            self.fig.geometry('{}x{}-0-0'.format(w, h))

            self.update()

        self.Hide()

 

    def Quit(self, *param):

        print(' << Zen@Terra: Out')

        self.fig.destroy()          # quit the program

 

#

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

____________________________________________________________________________________

 

 

2.6/ Finally, the main program

 

Show_Proc.py________________________________________________________________________

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

 

""" PROCESS Show_Proc

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

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.0'

__date__ = 'nov 2017'

 

from defErrPrint import errPrint

 

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

#                                MAIN

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

 

if __name__ == "__main__":

 

    from configparser import ConfigParser

    from defBanner import Banner

    from defObjOverlay import objOverlay

    from defVLCplayer import VLC_player

    from defBenef import DispBenef

    from defZenAtTerra import ZenAtTerra

    from queue import Empty

    from multiprocessing import Process, Queue

    from defTCPIP import TCPLink

    from os import path

    from time import strftime, time, sleep

   

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

    config = ConfigParser()

    config.filename = 'Show_Proc.ini'

    config.savfilename = config.filename.replace('.','_sav.')

 

    Ok = False

    for tst in range(2):

       # ----- verify config ini ok

        try:

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

            config.langList = [lg.strip().title() for lg in config['LANG']['list'].split(',')]

            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)

            countDownSound = bool(int(settings['countdown_sound']))

            nextshtime = config['NEXT_SHOW']['nextshtime']

            sLang = config['LANG']['lang']

            cvtst = [config['CONVERT']['euro2' + lg[:2].lower()] \

                                     for lg in config.langList]

            # saves = [config['SAVED'][s.strip() for s in ['wa', 'py', 'sp']]]

            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'])

            FSvdoChan = int(config['FS']['vdo_chan'])

            Ok = True

            break

        except:

            # ---- take the save ini

            print('\t! Restore ini file')

            with open(config.filename, 'w', encoding='utf-16') as ini:

                with open(config.savfilename, 'r', encoding='utf-16') as _ini:

                    ini.write(_ini.read())

    if not Ok:

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

        exit(44)

 

    # ----- Proc Name from ini = Show_Proc

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

 

    #

    # verbose : 1 = main, 2 = ethernet

    #

    vrbm = bool(verbose & 1)

    vrbe = bool(verbose & 2)

   

    MovieDir = path.join(path.abspath('.'),'Movies')

    ImgDir = path.join(path.abspath('.'),'Img')

 

    # ----- videos to overlay and offsets

    ovlsProp = {'vdo:CP_Defl.avi':   [[.75, .25], 1], \

                'vdo:CP_Infl.avi':   [[.75, .25], 1], \

                'vdo:tire_Defl.avi': [[.25, .25], 1], \

                'vdo:tire_Infl.avi': [[.25, .25], 1], \

                'vdo:rut_Defl.avi':  [[.50, .75], 1], \

                'vdo:rut_Infl.avi':  [[.50, .75], 1]}

 

    # ------------------------------------------------ launch background video

    qOvlObj = Queue()

    def launchOvlObj():

        pOvlObj = Process(target=objOverlay, \

                              args=(qOvlObj.get, FSvdoChan, ovlsProp))

        pOvlObj.start()

        return pOvlObj

    pOvlObj = launchOvlObj()

    setOvlObj = qOvlObj.put_nowait

 

    qIn = Queue()

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

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

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

 

    # -------------------------------------------------------- Launch VLCPlayer

    qVLC = Queue()

    def launchVLC():

        pVLC = Process(target=VLC_player, args=(qVLC, qIn))

        pVLC.start()

        return pVLC

    pVLC = launchVLC()

    setVLC = qVLC.put_nowait

 

    # ----------------------------------------------------------- Launch banner

    qBanner = Queue()

    def launchBanner():

        pBanner = Process(target=Banner, \

                       args=(qBanner, qIn, config['WAITING_TEXT'], \

                                 countDownSound))

        pBanner.start()

        return pBanner

    pBanner = launchBanner()

    setBanner = qBanner.put_nowait

 

    # ------------------------------------------------------------ Launch benef

    benef = DispBenef(sLang[:2])

    benef.hide()

 

    # -------------------------------------------------------- Launch Zen@Terra

    qZaT = Queue()

    def launchZaT():

        pZaT = Process(target=ZenAtTerra, args=(qZaT, qIn))

        pZaT.start()

        return pZaT

    pZaT = launchZaT()

    setZaT = qZaT.put_nowait

 

    exitVal = 0

    MainOn = True

    wait4data = True

    exitFlag = False

    getdata = qIn.get

    putdata = qIn.put_nowait

   

    T0 = time()

    Tend = 5

    ackn = False

 

    # ----- Status

    MOVIES = 1

    SIMUON = 2

    HADISP = 3

 

    curState = MOVIES

    putdata(['TCP:rcv0', 'ShowOn=0', 'local'])

    VLCFileOn = 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))

 

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

                       

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

 

                    ackn = True

 

                    if 'remTime' in cmd:

                        if curState != MOVIES:

                            if curState == SIMUON:

                                # hide FS window

                                setOvlObj('Hide')

                            elif curState == HADISP:

                                benef.hide()

                            # ----- launch movies

                            setVLC('Dir>{};loop=1'.format(MovieDir))

                            curState = MOVIES

                        # launch chrono

                        setBanner(cmdval)

                       

                    elif 'Benef' in cmd:

                        if 'Show'   in val: benef.show()

                        elif 'Hide' in val: benef.hide()

 

                    elif 'ShowOn' in cmd:

                        # ShowOn = 0, 1

                        setBanner('BanExit=0')

                        benef.hide()

                        if int(val):

                            setVLC('Stop')

                            setOvlObj('Show')

                            curState = SIMUON

                        else:

                            setOvlObj('Hide')

                            setVLC('Dir>{};loop=1'.format(MovieDir))

                            curState = MOVIES

 

                    elif 'OvlOn' in cmd:

                        setVLC('Stop')

                        setOvlObj('Show')

                                         

                    elif 'Ovl' in cmd:

                        """

                        Ovl=obj:fun(arg)

                        obj ='CP_Defl', 'CP_Infl', 'tire_Defl', \

                             'tire_Infl', 'rut', ...

                        fun = zoomIn, zoomOut, hide, show, pause, restart,

                            pauseAtFrame(n), pauseAtSec(n), rewind, play*

                        arg = n for pauseAtFrame & pauseAtSec

                        *from the beginning of the movie

                        """

                        setOvlObj(val)

 

                    elif 'ZaT' in cmd:

                        setZaT(val.replace(':', '='))

 

                    elif 'VLCPlay' in cmd:

                        setOvlObj('Hide')

                        setVLC('File>{};loop=0'.format(path.join(ImgDir, val)))

                        VLCFileOn = True

                           

                    elif 'Lang' in cmd:

                        # Lang = Fr, Ge, ...

                        # external field (internal in MOVIES)

                        sLang = val[:2]

                        config['LANG']['lang'] = sLang

                        setBanner(cmdval)

                        setZaT(cmdval)

                        benef.lang(sLang)

 

                    elif 'Exit' in cmd:

                        # Exit = 0, 1, 2 ..

                        exitVal = int(val)

                        exitFlag = True

                        setOvlObj('Exit')

                        setVLC('VLCExit')

                        setBanner('BanExit=1')

                        benef.exit()

                        setZaT('Exit=1')

                        wait4data = False

                        T0 = time()

                       

            elif 'rcv?' in typ2:

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

                ackn = False

 

        elif 'SUB' in typ:

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

           

            if cmd in ['endTime', 'last10s', 'SurfSet']:

                eth.sendTo(msgs, 'Master')

           

            elif 'waitTime' in cmd:

                # internal command (main)

                curState = MOVIES

                # ----- launch movies

                setVLC('Dir>{};loop=1'.format(MovieDir))

                # avoid sending Ok to internal cmd

 

            elif 'endPlay' in cmd:

                if VLCFileOn:

                    VLCFileOn = False

                    setOvlObj('Show')

 

        elif typ:

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

 

        # or Texit > 5 s

        if exitFlag:

            # ===== Exit Loop if all procs exited

            MainOn = (pOvlObj.is_alive() or pVLC.is_alive() or \

                     pBanner.is_alive() or pZaT.is_alive()) and \

                     not (time() - T0 > Tend)

 

    qIn.cancel_join_thread()

    # ------------------------------------------------ wait sub processes ended

    pOvlObj.join()

    pBanner.join()

    pVLC.join()

    pZaT.join()

   

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

    eth.close()

   

    # ------------------------------------------------------------- save config

    config['LANG']['lang'] = sLang

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

        config.write(cfgfile)

             

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

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

   

    exit(39 + exitVal)

#

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

____________________________________________________________________________________

 

 

 

3/ "Master"

 

This PC monitors all the simulator, even the raspberry pis' supplies and the sound between PC FS_Hat  and PC_Show (closed when in presentating mode to reproduce the sound of Farming Simulator during the show, opened otherwise).

 

A first program, TCP2Ser.exe,  is launched at the PC boot, to monitor the link with the Arduino Uno. So, the main "Master" program could be used independently by the speaker without any impact on the whole system (as the Arduino Uno manages the Raspberry's supplies, for maintenance purposes).

The goal of this script, is to "translate" commands coming from the Master (local host) to Arduino ones.

 

 

Tcp2Ser.py_____________________________________________________________________________

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

 

""" tcp2ser

    =======

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.0'

__date__ = 'Sept 2017'

 

from queue import Empty

from defErrPrint import errPrint

 

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

#

if __name__ == '__main__':

 

    from multiprocessing import Process, Queue

    from queue import Empty

    from defTCPIP import TCPLink

    from defSerialLink import serialLink

    from configparser import ConfigParser

    from time import strftime

    from threading import Timer

 

    # ----- read params from ini file

    config = ConfigParser()

    config.filename = 'tcp2ser.ini'

    config.savfilename = config.filename.replace('.','_sav.')

       

    Ok = False

    for tst in range(2):

       # ----- verify config ini ok

        try:

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

            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)

            serial = bool(int(settings['serial']))

            UNO = config['UNO']

            UNOcom = ['COM{}'.format(int(UNO['com_port'])), \

                       int(UNO['com_speed'])]

            netCfg = config['NET']

            addrCfg = config['ADDRESS']

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

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

            IPtoNnode = {'127.0.0.1':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

            break

        except:

            # ---- take the save ini

            print(r'\t/!\ Restore ini file')

            with open(config.filename, 'w', encoding='utf-16') as ini:

                with open(config.savfilename, 'r', encoding='utf-16') as _ini:

                    ini.write(_ini.read())

    if not Ok:

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

        exit(44)

       

    #

    # verbose : 1 = main, 2 = ethernet, 4 = serial

    #

    vrbm = bool(verbose & 1)

    vrbe = bool(verbose & 2)

    vrbs = bool(verbose & 4)

   

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

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

   

    qIn = Queue()

    putData = qIn.put_nowait

    getData = qIn.get

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

    eth = TCPLink(IPtoNnode, IPtoNlinks, portBase, putData)

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

 

    # ---------------------------------------------------------- process serial

    qSerial = Queue()       

    def launchSerial():

        pSerial = Process(target=serialLink, \

                          args=(UNOcom,  '\r\n', \

                                qSerial.get, putData, vrbs))

        pSerial.start()

        return pSerial

    if serial:

        sendSerial = qSerial.put_nowait

        pSerial = launchSerial()

    else:

        sendSerial = print

 

    exitFlag = False

    ackn = False

 

    Timer(2, putData, args=(['TCP:rcv0', 'ResetUno=1', 'init'], )).start()

 

    while True:

 

        try:

            ret = getData(True)

        except Empty:

            ret = []

        except KeyboardInterrupt:

            # ----- simulate an exit order

            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:

                # ---- have to relaunch ethernet link

                eth.relaunch(proc)

               

            elif 'snt1' in typ2:

                # ---- info of what was sent

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

               

            elif 'snt0' in typ2:

                # ---- info of fail sending

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

               

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

                # ----- aknowledge

                # ----- received info

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

                if exitFlag:

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

                    if typ2 == 'rcv1':

                        if all(eth.stop(proc)): break

 

                # ----- send aknowledgment if really comming from TCP

                fromTCP = bool(proc != 'Ctrl+C')

                ackn = fromTCP

 

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

 

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

 

                    if 'Sound' in cmd:

                        # ----- snd{0|1}

                        sendSerial('snd' + val)

                           

                    elif 'Rpi#' in cmd:

                        # rpi{0|1|2|a}{0|1}

                        sendSerial('rpi' + cmd[4] + val)

 

                    elif 'Supply15' in cmd:

                        # sup{0|1}

                        sendSerial('sup' + val)

 

                    elif 'ResetDue' in cmd:

                        # rstd

                        sendSerial('rstd')

 

                    elif 'ResetUno' in cmd:

                        # rstd

                        sendSerial('rstu')

 

                    elif 'TirePos' in cmd:

                        #    tirePos = 0, 1, 2, 3, 4

                        # ord{n} n the relay to close

                        sendSerial('ord'+ val)

 

                    elif 'Shutdown' in cmd:

                        # shtd

                        sendSerial('shtd')

                       

                    elif 'Test' in cmd:

                        pass

 

                    elif 'Exit' in cmd:

                        # stop requested by TCP --- JUST FOR MAINTENANCE

                        sendSerial('shtd')

                        exitFlag = True

                        exitVal = int(val)

                        break

                   

            elif 'rcv?' in typ2:

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

                ackn = False

               

 

        # -------------------------------------------------------- Serial entry

        elif 'SER' in typ:

            if   msgs[0] == '0': pSerial = launchSerial()

           

        # ------------------------------------------------------------ unknown

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

 

    # ----- free the queue

    qIn.cancel_join_thread()

   

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

    eth.close()

   

    # -------------------------------------------------- wait serial link close

    if serial:

        if pSerial.is_alive(): sendSerial('SerExit')

        pSerial.join()

   

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

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

   

    exit(0)

#

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

____________________________________________________________________________________

 

And it's .ini file:

 

Tcp2Ser.ini_________________________________________________________________________

[SETTINGS]

verbose = b011

serial = 0

 

[UNO]

com_port = 19

com_speed = 115200

 

[NET]

base = 192.168.0.

node = tcp2ser

links =

port_base = 17700

 

[ADDRESS]

fs_hat = 60

rpi_pres = 61

rpi_out = 62

rpi_disp = 63

master= 64

show = 65

tcp2ser = 0

____________________________________________________________________________________

 

 

 

The main "Master" program, now, uses a numpad function to enter the time between presentations,

 

defNumpad.py_________________________________________________________________________

"""

NUMPAD ===============================================================

"""

class npProcess():

    def __init__(self,wVal,wOk):

        self.current = str(wVal.get())

        self.dispVal = wVal

        self.Ok = wOk

        self.first = True

 

    def key_press(self, num):

        if self.first:

            self.first = False

            self.current = '0'

        currt = self.current

        toAd = str(num)     

        if toAd == '.':

            if toAd in currt: return

        if currt == '0':

            self.current = toAd

        else:

            self.current = currt + toAd

        self.display()

       

    def man_key(self, event):

        chr = event.char

        if self.first:

            self.first = False

            if chr != '\x08': self.current = '0'

        if chr in '.0123456789':

            self.key_press(chr)

        elif chr == '\r':

            self.OkReturn()

        elif chr == '\x08':

            self.cancel()

 

    def display(self):

        self.dispVal.set(self.current)

 

    def cancel(self):

        if self.first: self.first = False

        currt = self.current

        if len(currt)>1:

            self.current = currt[0:-1]

        else:

            self.current = '0'

        self.display()

 

    def OkReturn(self):

        self.Ok.set('')

 

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

def numPad(inival=0, OutType='float'):

 

    import tkinter as tk

 

    # --- prepare the figure

    fig = tk.Toplevel()

    fig.title('NumPad {}'.format(OutType))

    fig.rowconfigure(0, weight=1)

    fig.columnconfigure(0, weight=1)

    fig.attributes('-topmost', 1)

    fig.focus()

    npFr = tk.Frame(fig)

    npFr.grid()

    npFr.columnconfigure(0, weight=1, uniform=1)

    npFr.columnconfigure(1, weight=1, uniform=1)

    npFr.columnconfigure(2, weight=1, uniform=1)

   

    # --- use font,  color and button width

    Fmt = dict(font = ("Arial", 23), fg='darkblue')

    butWidth = 3

 

    # --- value to display

    val = tk.StringVar()

    val.set(str(inival))

   

    # --- value Ok

    vOk = tk.StringVar()

    vOk.set('Ok')

   

    # --- init core process

    core = npProcess(val,vOk)

   

    # --- display box

    tk.Label(npFr, justify=tk.RIGHT, bg='white', textvariable=val, \

             relief=tk.RIDGE, bd = 3, **Fmt)\

        .grid(row=0, column=0, columnspan=2, padx=5, pady=5, \

              sticky=tk.E+tk.W+tk.S+tk.N)

   

    # --- key '<-' ################ 

    tk.Button(npFr, text='<-', command=core.cancel, width=butWidth,**Fmt)\

        .grid(row=0, column=2, pady=5)

   

    # --- make the buttons

    keybrd = '789456123'

    i = 0

    for r in range(1,4):

        for c in range(3):

            tk.Button(npFr, text= keybrd[i], width=butWidth,\

                      command=lambda x=keybrd[i]: core.key_press(x), **Fmt)\

                .grid(row=r, column=c, padx=5, pady=5)

            i += 1

           

    # --- key '.'

    p = tk.Button(npFr, text = '.', width=butWidth,\

                  command = lambda : core.key_press('.'), **Fmt)

    p.grid(row = 4, column = 0, padx = 5, pady = 5)

    # --- key '.': if int, key not used

    if OutType.upper()=='INT': p.configure(command='')

   

    # --- key '0'

    tk.Button(npFr, text = '0', width=butWidth,\

              command = lambda : core.key_press('0'), **Fmt)\

        .grid(row = 4, column = 1, pady = 5)

   

    # --- remaining 'Ok' button

    tk.Button(npFr, textvariable = vOk, width=butWidth, \

              command = core.OkReturn, **Fmt)\

        .grid(row = 4, column = 2, pady = 5)

           

    # --- bind keyboard to numpad

    fig.bind('<Key>', core.man_key)

 

    fig.attributes('-topmost', True)

    fig.focus_set()

    fig.grab_set()

    fig.protocol('WM_DELETE_WINDOW',lambda:vOk.set(' '))

   

    # --- Waiting Ok accessed

    npFr.wait_variable(vOk)

 

    ress = val.get().strip()

    if ress:

        if OutType.upper() == 'INT':

            res = int(ress)

        else:

            res = float(ress)

    else:

        res = inival

           

    fig.destroy()

   

    return res

"""

END NUMPAD ===========================================================

"""

____________________________________________________________________________________

 

 

it also needs a menu  dialog (to choose the presenting language):

 

defDlg.py____________________________________________________________________________

import tkinter as tk

 

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

# Class selection in a list

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

class menuDlg(tk.Toplevel):

    def __init__(self, title, choose_in_list, \

                 butFmt=dict(font = ("Arial", 12), fg='darkblue')):

 

        tk.Toplevel.__init__(self)

       

        gridFmt = dict(padx=5, pady=2, sticky=tk.E+tk.W)

       

        self.title(title)

        self.lift()

        self.protocol('WM_DELETE_WINDOW', self.Quit)

        self.attributes('-topmost', True)

        self.scrSize = [self.winfo_screenwidth(), \

                        self.winfo_screenheight()]

       

        self.cv = tk.Canvas(self)

        self.frame = tk.Frame(self.cv)

        self.vsb = tk.Scrollbar(self, orient="vertical", \

                                command=self.cv.yview)

        self.cv.configure(yscrollcommand=self.vsb.set)

       

        self.vsb.grid(row=0, column=1, sticky=tk.N+tk.S)

        self.cv.grid(row=0, column=0, sticky='news')

        self.cv.create_window((5,5), window=self.frame, anchor='nw')

 

        self.grid_rowconfigure(0, weight=1)

        self.grid_columnconfigure(0, weight=1)

        nw = max([len(txt) for txt in choose_in_list]) + 1

        self.out = -1

       

        # ----- Buts : files

        for i, fname in enumerate(choose_in_list):

            tk.Button(self.frame, text = fname, width=nw, \

                      command = lambda x=i: self.Quit(x), **butFmt)\

                .grid(row=i, **gridFmt)

        self.cv.update_idletasks()

        size = (self.frame.winfo_reqwidth(), \

                min(self.frame.winfo_reqheight()+5,int(self.scrSize[1]*.8)))

        self.cv.config(scrollregion="0 0 %s %s" % size, height=size[1])

        self.cv.itemconfigure(self.frame, height=size[1])

        self.frame.bind("<Configure>", self._configure_interior)

        self.cv.bind("<Configure>", self._configure_canvas)

        self.wait_window(self)

 

    def _configure_interior(self, event):

        # update the scrollbars to match the size of the inner frame

        size = (self.frame.winfo_reqwidth(), self.frame.winfo_reqheight())

        self.cv.config(scrollregion="0 0 %s %s" % size)

        if self.frame.winfo_reqwidth() != self.cv.winfo_width():

            # update the canvas's width to fit the inner frame

            self.cv.config(width=self.frame.winfo_reqwidth())

 

    def _configure_canvas(self, event):

        if self.frame.winfo_reqwidth() != self.cv.winfo_width():

            # update the inner frame's width to fill the canvas

            self.cv.itemconfigure(self.frame, width=self.cv.winfo_width())

 

    def Quit(self, out=-1):

        # ----- Quit (index = -1)

        self.out = out

        self.destroy()

 

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

# inpudDlg

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

class inputDlg:

    def __init__(self, parent, title, txt, inival, dicts={}):

 

        self.top = tk.Toplevel(parent)

        self.top.title(title)

        # ----- Label

        tk.Label(self.top, text=txt, **dicts).pack()

        # ----- Entrée

        self.e = tk.Entry(self.top, **dicts)

        self.e.pack(padx=5)

        self.e.insert(0, str(inival))

        self.out = inival

        # ----- Bouton

        b = tk.Button(self.top, text='OK', command=self.ok, **dicts)

        b.pack(pady=5)

 

    def ok(self):

        self.out = self.e.get()

        self.top.destroy()

____________________________________________________________________________________

 

 

 

Finally, the main program:

 

Master.py_______________________________________________________________________________________

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

 

""" Master

    ======

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.1'

__date__ = 'nov 2017'

 

import tkinter as tk

from configparser import ConfigParser

from multiprocessing import Queue

from subprocess import Popen

from queue import Empty

from defNumpad import numPad

from defDlg import menuDlg

from defErrPrint import errPrint

from textwrap import shorten

 

import tkinter.messagebox as messagebox

QuestBox = messagebox.askyesno

MsgBox = messagebox.showinfo

 

from time import time, sleep, strftime

from os import path

from shutil import copyfile

 

MiBlue = '#01019A'

MiWht1 = '#FFFFFF'

MiWht2 = '#E5E5E5'

 

# 0:wheat,      1;corn,   2:rape      x 3

# 0: Soil prep, 1:sowing, 2:harvest

# wheat / soil prep

# wheat / sowing

# wheat / harvest

# corn  / soil prep

# corn  / sowing

# corn  / harvest

# rape  / soil prep

# rape  / sowing

# rape  / harvest

 

tools = [['CULTIVATOR', 'DIRECTDRILL', 'TRAILER'], \

         ['PLOUGH', 'DRILL', 'TRAILER'], \

         ['CULTIVATOR', 'DRILL', 'TRAILER']]

 

ShowSteps = ['Show Zen@Terra on road', \

             'Start Tractor', \

             'Go directly to field' , \

             'Show Zen@Terra on field', \

             'Launch comparison', \

             '-']

 

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

#                        configuration ligne/colonne

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

#

def colrowconfig(frames):

    for fri in frames:

        c, r = fri.grid_size()

        for ic in range(c):

            fri.columnconfigure(ic, weight=1)

        for ir in range(r):

            fri.rowconfigure(ir, weight=1)

        colrowconfig(fri.grid_slaves())

#

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

       

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

#                              Language Selection

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

#

class LangAPP(tk.Toplevel):

    def __init__(self, icofile, LangList, func, dx):

        tk.Toplevel.__init__(self)

        self.resizable(width = False, height = False)

        self.title('Lang.')

        self.iconbitmap(icofile)

        self.protocol('WM_DELETE_WINDOW', self.Quit)

        butFmt = dict(font = ('Arial', 16), fg=MiBlue)

        butFmtC = dict(font = ('Arial', 14, 'italic'), fg=MiBlue)

        gridFmt = dict(padx=10, pady=10, sticky=tk.E+tk.W)

        self.transient()

        self.geometry('+{}+0'.format(dx))

       

        # ----- Buts : langages

        for i, lang in enumerate(LangList):

            tk.Button(self, text = lang, width = 10, height = 2,\

                      command = lambda lg = lang : func(lg,self), **butFmt)\

                .grid(row=i, **gridFmt)

           

        tk.Button(self, text = '◄ back', width = 10, height = 2,\

                      command = self.Quit, **butFmtC)\

                .grid(row=i+1, **gridFmt)

 

        self.func = func

 

    def Quit(self):

        self.func('', self)

#

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

 

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

#                               Time manager

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

#

class TimeMan(tk.Toplevel):

    def __init__(self, icofile, func1, func2, nt1, nt2, dx, dy, fFmtn, gwdFmt):

        tk.Toplevel.__init__(self)

        self.resizable(width = False, height = False)

        self.title('Time')

        self.iconbitmap(icofile)

        self.protocol('WM_DELETE_WINDOW', self.Quit)

        self.fFmt1 = dict(font = ("Arial", 50, 'bold'), fg=MiBlue)

        fFmtC = dict(font = ("Arial", 16, 'italic'), fg=MiBlue)

        self.fFmt1i = dict(font = ("Arial", 50, 'bold', 'italic'), fg='darkgrey')

        fFmt2 = fFmtn

        fFmt3 = dict(font = ("Arial", 12), fg=MiBlue)

        gridFmt1 = gwdFmt

        gridFmt2 = dict(padx=10, pady=5, sticky='news')

       

        self.transient()

        self.geometry('+{}+{}'.format(dx, round(dy/3)))

 

        # ----- digits

        self.time = tk.IntVar()

        self.time.set(nt1)

        self.digit= tk.Button(self, textvariable=self.time, width=4, bg='white', \

                  command = self.getNum, **self.fFmt1)

        self.digit.grid(row=0, column=0, rowspan=3, **gridFmt1)

        # ---- add

        tk.Button(self, text='▲', command=self.add, **fFmt2)\

                .grid(row=0, column=1, **gridFmt2)

        self.ttype = tk.StringVar()

        self.ttype.set('mn')

        tk.Label(self, textvariable=self.ttype, bg=MiWht2, **fFmt3)\

                .grid(row=1, column=1, **gridFmt2)

        # ---- soustract       

        tk.Button(self, text='▼', command=self.sous, **fFmt2)\

                .grid(row=2, column=1, **gridFmt2)

        # ----- set

        tk.Button(self, text='Set', width=5, command=self.dsetTime, **fFmt2)\

                .grid(row=3, column=0, **gridFmt1)

        # ----- display set

        self.vsetTime = tk.StringVar()

        self.vsetTime.set(str(nt2)+' mn')

        tk.Label(self, textvariable=self.vsetTime, width=7, bg=MiWht2, **fFmt2)\

                .grid(row=3, column=1, **gridFmt1)

        # ----- Refresh

        tk.Button(self, text='Refresh', command=self.refresh, **fFmt2)\

                .grid(row=4, column=0, columnspan=2, **gridFmt1)

        # ----- Cancel

        tk.Button(self, text='◄ back', command=self.Quit, **fFmtC)\

                .grid(row=5, column=0, columnspan=2, **gridFmt1)

 

        colrowconfig(self.winfo_children())

       

        self.setTime = func1

        self.setWait = func2

 

    def getNum(self):

        n = numPad(self.time.get(), 'int')

        if n == 0:

            n = 59

            self.ttype.set('sec')

            self.digit.config(**self.fFmt1i)

        self.time.set(n)

 

    def add(self):

        n = self.time.get() + 1

        if n > 59:

            n = 1

            self.ttype.set('mn')

            self.digit.config(**self.fFmt1)

        self.time.set(n)

       

    def sous(self):

        n = self.time.get() - 1

        if n < 1 :

            n = 59

            self.ttype.set('sec')

            self.digit.config(**self.fFmt1i)

        self.time.set(n)

 

    def dsetTime(self):

        n = self.time.get()

        self.vsetTime.set(str(n) + ' mn')

        self.setTime(n)

 

    def refresh(self):

        self.setWait(self.time.get(), self.ttype.get(), self)

       

    def Quit(self):

        self.setWait(None, 'mn', self)

#

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

 

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

#                        Farming Simulator Show manager

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

#

class FSimuShowMan(tk.Toplevel):

    def __init__(self, icofile, putData, dx, dy, LangList, \

                 vLang, fFmtn, gwdFmt, retfun):       

        tk.Toplevel.__init__(self)

        self.resizable(width = False, height = False)

        self.title('FS Show')

        self.iconbitmap(icofile)

        self.protocol('WM_DELETE_WINDOW', self.Quit)

       

        self.transient()

        self.geometry('+{}+{}'.format(dx, round(dy/3)))

 

        self.putData = putData

        self.icofile = icofile

        self.LangList = LangList

        self.vLang = vLang

        self.fFmtn = fFmtn

        self.gwdFmt = gwdFmt

        self.retfun = retfun

        self.pannelOn = False

        self.curCrop = None

        self.gameOn = False

        self.T0l = time()

        self.T0n = self.T0l

        self.setTimer = self.after

       

        iRow = 0

        # ------- Crop

        fr = tk.LabelFrame(self, text='Crop', fg=MiBlue)

        fr.grid(row=iRow, column=0, **gwdFmt)

        self.crop = tk.IntVar()

        self.crop.set(1)

        self.crops = ['Wheat', 'Corn', 'Rape']

        for i, t in enumerate(self.crops):

            tk.Radiobutton(fr, text=t, variable=self.crop, \

                           value=i,  **fFmtn).grid(row=0, column=i,**gwdFmt)

        self.selRadio = [fr]

 

        iRow += 1

        # ------- Work

        fr = tk.LabelFrame(self, text='Work', fg=MiBlue)

        fr.grid(row=iRow, column=0, **gwdFmt)

        self.prep = tk.IntVar()

        self.prep.set(0)

        self.works = ['Soil Prep.', 'Sowing']

        for i, t in enumerate(self.works):

            tk.Radiobutton(fr, text=t, variable=self.prep, \

                       value=i,  **fFmtn).grid(row=0, column=i,**gwdFmt)

        self.selRadio.append(fr)

           

        iRow += 1

        # ------- Load

        self.butLoad = tk.Button(self, text='Load', command=self.load, **fFmtn)

        self.butLoad.grid(row=iRow, column=0, **gwdFmt)

       

        # ------- reload

        self.fFmtr = fFmtn.copy()

        self.fFmtr.update(fg='darkred', \

                          font=tuple([int(3*v/4) if isinstance(v, int) else v \

                              for v in self.fFmtr['font']]+['italic']))

        iRow += 1

        # ------- curGame

        self.curGame = tk.StringVar()

        self.curGame.set('Current game =')

        tk.Label(self, textvariable=self.curGame, anchor=tk.W, fg=MiBlue)\

                 .grid(row=iRow, column=0, **gwdFmt)

       

        iRow += 1

        # ------- next step

        self.stepTxt = tk.StringVar()

        self.stepTxt.set(ShowSteps[0] + '(1/5)')

        self.nextBut = tk.Button(self, textvariable=self.stepTxt, \

                                 state = tk.DISABLED, command=self.nextStep, **fFmtn)

        self.nextBut.grid(row=iRow, column=0, **gwdFmt)

        self.step=0

       

        iRow += 1

        # ------- Anim

        self.wAnim = tk.Button(self, text='Animations  \t►', state=tk.DISABLED, \

                                   command=self.dlgAnim, **fFmtn)

        self.wAnim.grid(row=iRow, column=0, columnspan=2,  **gwdFmt)

 

        iRow += 1

        # ------- Benef

        self.wBenef = tk.Button(self, text='Benefits', state=tk.DISABLED, \

                                command=self.dlgBenef, **fFmtn)

        self.wBenef.grid(row=iRow, column=0, columnspan=2,  **gwdFmt)

 

        iRow += 1

        # ------- Back

        tk.Button(self, text='◄ End', command=self.Quit, **fFmtn)\

                 .grid(row=iRow, column=0, **gwdFmt)

       

        colrowconfig(self.winfo_children())

       

        self.update()

        self.dx, self.dy = [int(x.split('+')[0]) \

                            for x in self.geometry().split('x')]

        self.dx += dx

        self.curNGame = 0

        self.putData(['SUB', 'TirePos=2', 'Tcp2Ser'])

           

    def load(self):

        tCur = time()

        if tCur - self.T0l > 1: # avoid button "bouncing"

            self.T0l = tCur

            iPrep = self.prep.get()

            iCrop = self.crop.get()

            idx = iPrep + iCrop * 3

            ngame = 12 + idx

            self.curNGame = ngame

            if self.gameOn: self.end()

            self.putData(['SUB', 'Load={}&SKB=iOk0'.format(ngame), 'Fs_Hat'])

            self.curGame.set('Current game = {} / {} (#{})'\

                             .format(self.crops[iCrop], self.works[iPrep], \

                                        ngame))

            curTool = tools[iCrop][iPrep]

            self.putData(['SUB', 'Tool=' + curTool + '&Surf=road', 'Rpi_Pres'])

            self.putData(['SUB', 'ZaT=Tool:' + curTool + \

                             '&ZaT=Surf:road',  'Show'])

            self.gameOn = True

            self.nextBut.config(state=tk.NORMAL)

            self.curCrop = iCrop

            self.curPrep = iPrep

            self.butLoad.config(state=tk.DISABLED)

            for ws in self.selRadio:

                for w in ws.winfo_children(): w.config(state=tk.DISABLED)

            # vvvv - to delete when synchronized wint Fs_Hat - vvvvv

            self.setTimer(5000+ngame*10, self.enterGame)

 

    def enterGame(self):

        self.putData(['SUB', 'Sound=0',  'Tcp2Ser'])

        self.putData(['SUB', 'VLCPlay=begin_{}_{}.mp4'\

                            .format(self.curCrop+1, self.curPrep+1), 'Show'])

   

    def end(self):

        self.putData(['SUB', 'Tool=TRACTOR&Surf=road', 'Rpi_Pres'])

        self.putData(['SUB', 'Quit=1', 'Fs_Hat'])

        self.curGame.set('Current game = ')

        self.gameOn = False

       

    def nextStep(self, ncrop=0):

        """

        ShowSteps = ['Show Zen@Terra on road', \

                     'Start Tractor', \

                     'Go directly to field' , \

                     'Show Zen@Terra on field', \

                     'Launch comparison', \

                     '-']

        """

        tCur = time()

        if tCur - self.T0n > 1: # avoid button "bouncing"

            self.T0n = tCur

            self.step += 1

            lST = len(ShowSteps)

            if self.step < lST:

                self.stepTxt.set(ShowSteps[self.step]+' ({}/{})'\

                                                      .format(self.step+1, lST-1))

                step = self.step

                iCrop = self.curCrop

                iPrep = self.curPrep

               

                if step == 1:

                    self.putData(['SUB', 'ZaT=Zoom:in', 'Show'])

                   

                elif step == 2:

                    self.putData(['SUB', 'ZaT=Zoom:out' + \

                                         '&VLCPlay=start_{}_{}.mp4'\

                                  .format(iCrop+1, iPrep+1), 'Show'])

                   

                elif step == 3:

                    self.putData(['SUB', 'VLCPlay=stop_{}_{}.mp4'\

                                  .format(iCrop+1, iPrep+1), 'Show'])

                elif step == 4:

                    self.putData(['SUB', 'ZaT=Zoom:in', 'Show'])

                   

                elif step == 5:

                    self.putData(['SUB', 'ZaT=Zoom:out&OvlOn=1', 'Show'])

                    self.putData(['SUB', 'Sound=1',  'Tcp2Ser'])

                    self.putData(['SUB', 'Cmp=1',  'Fs_Hat'])

                    self.stepTxt.set('(Send again comparison if needed)')

                    self.wAnim.config(state=tk.NORMAL)

                    self.wBenef.config(state=tk.NORMAL)

                    self.nextBut.config(**self.fFmtr)

            else:

                if self.curNGame and QuestBox(title = 'Reload?', \

                            message = 'Are you sure to reload:\n{}?'\

                                              .format(self.curGame.get())):

                    self.putData(['SUB', 'Reload={}'.format(self.curNGame), \

                                  'Fs_Hat'])

                    self.nextBut.config(state=tk.DISABLED)

          

    # ===== ANIM   

    def dlgAnim(self):

        if self.wAnim['relief'] == tk.RAISED:

            if not self.pannelOn:

                print('\t\t\t» Anims : {}'.format(strftime('%d/%m/%y %H:%M:%S')))

                self.wAnim.config(relief=tk.SUNKEN, fg='black')

                self.pannelOn = True

                AnimMan(self.icofile, self.putData, self.dx, int(self.dy*.3),  \

                                       self.fFmtn, self.gwdFmt, self.endAnim)

 

    def endAnim(self, adef):

        self.wAnim.config(relief=tk.RAISED, fg=MiBlue)

        adef.destroy()

        self.pannelOn = False

        print('\t\t\t« Anims : {}'.format(strftime('%d/%m/%y %H:%M:%S')))

       

    # ===== BENEF

    def dlgBenef(self):

        if self.wBenef['relief'] == tk.RAISED:

            if not self.pannelOn:

                print('\t\t\t» Benef : {}'.format(strftime('%d/%m/%y %H:%M:%S')))

                self.wBenef.config(relief=tk.SUNKEN, fg='black')

                self.pannelOn = True

                self.putData(['SUB', 'Benef=Show',  'Show'])

                self.wBenef.config(command=self.endBenef)

               

    def endBenef(self):

        self.wBenef.config(relief=tk.RAISED, fg=MiBlue)

        self.pannelOn = False

        self.putData(['SUB', 'Benef=Hide',  'Show'])

        self.wBenef.config(command=self.dlgBenef)

        print('\t\t\t« Benef : {}'.format(strftime('%d/%m/%y %H:%M:%S')))

        

    def Quit(self):

        if not self.pannelOn:

            if self.gameOn: self.end()

            self.retfun(self)

            self.putData(['SUB', 'TirePos=2', 'Tcp2Ser'])

#                       

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

 

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

#                        Farming Simulator Play manager

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

#

class FSimuPlayMan(tk.Toplevel):

    def __init__(self, icofile, putData, dx, dy, fFmtn, gwdFmt, retfun):

        tk.Toplevel.__init__(self)

        self.resizable(width = False, height = False)

        self.title('FS Play')

        self.iconbitmap(icofile)

        self.protocol('WM_DELETE_WINDOW', self.Quit)

       

        self.transient()

        self.geometry('+{}+{}'.format(dx, round(dy/3)))

 

        self.putData = putData

        self.retfun = retfun

        self.gameOn = False

       

        butFmtC = dict(font = ('Arial', 14, 'italic'), fg=MiBlue)

       

        iRow = 0

        # ------- Crop

        fr = tk.LabelFrame(self, text='Crop', fg=MiBlue)

        fr.grid(row=iRow, column=0, **gwdFmt)

        self.crop = tk.IntVar()

        self.crop.set(0)

        self.crops = ['Wheat', 'Corn', 'Rape']

        for i, t in enumerate(self.crops):

            tk.Radiobutton(fr, text=t, variable=self.crop, \

                           value=i+1,  **fFmtn).grid(row=0, column=i, **gwdFmt)

 

        iRow += 1

        # ------- Work

        fr = tk.LabelFrame(self, text='Work', fg=MiBlue)

        fr.grid(row=iRow, column=0, **gwdFmt)

        self.prep = tk.IntVar()

        self.prep.set(0)

        self.works = ['Soil Preparation', 'Sowing', 'Harvest']

        for i, t in enumerate(self.works):

            tk.Radiobutton(fr, text=t, variable=self.prep, \

                       value=i+1,  **fFmtn).grid(row=0, column=i, **gwdFmt)

 

        iRow += 1

        # ------- Load

        tk.Button(self, text='Load', command=self.load, **fFmtn)\

                 .grid(row=iRow, column=0, **gwdFmt)

 

        iRow += 1

        # ------- curGame

        self.curGame = tk.StringVar()

        self.curGame.set('Current game =')

        tk.Label(self, textvariable=self.curGame, anchor=tk.W, fg=MiBlue)\

                 .grid(row=iRow, column=0, **gwdFmt)

       

        iRow += 1

        # ------- extra buttons

        fr = tk.LabelFrame(self, text='Extra commands', fg=MiBlue)

        fr.grid(row=iRow, column=0, **gwdFmt)

        self.buts = [tk.Button(fr, text='in/out/change vehicle [Tab]', command=self.vehChg, \

                               state=tk.DISABLED, **fFmtn)]

        self.buts[-1].grid(row=0, column=0, **gwdFmt)

        self.buts.append(tk.Button(fr, text='couple/uncouple tool [A]', \

                                   command=self.couple, \

                                   state=tk.DISABLED, **fFmtn))

        self.buts[-1].grid(row=0, column=1, **gwdFmt)

        self.buts.append(tk.Button(fr, text='in/out cabin [E]', \

                                   command=self.inOutCabin, \

                                   state=tk.DISABLED, **fFmtn))

        self.buts[-1].grid(row=1, column=0, **gwdFmt)

        self.buts.append(tk.Button(fr, text='camera in/out [C]', \

                                   command=self.camera, \

                                   state=tk.DISABLED, **fFmtn))

        self.buts[-1].grid(row=1, column=1, **gwdFmt)

        self.buts.append(tk.Button(fr, text='worker [H]', \

                                   command=self.worker, \

                                   state=tk.DISABLED, **fFmtn))

        self.buts[-1].grid(row=2, column=0, **gwdFmt)

        self.buts.append(tk.Button(fr, text='[Enter] key', command=self.enterKey,

                                   state=tk.DISABLED, **fFmtn))

        self.buts[-1].grid(row=2, column=1, **gwdFmt)

       

        iRow += 1

        # ------- Quit game

        self.buts.append(tk.Button(self, text='Quit the current game', \

                                   state=tk.DISABLED, command=self.end, **fFmtn))

        self.buts[-1].grid(row=iRow, column=0, **gwdFmt)

       

        iRow += 1

        # ------- Back

        tk.Button(self, text='◄ back', command=self.Quit, **butFmtC)\

                 .grid(row=iRow, column=0, **gwdFmt)

       

        colrowconfig(self.winfo_children())

 

    def _resetSel(self):

        self.crop.set(0)

        self.prep.set(0)

                 

    def load(self):

        iPrep = self.prep.get() - 1

        iCrop = self.crop.get() - 1

        if iPrep >= 0 and iCrop >= 0:

            idx = iPrep + iCrop * 3

            ngame = 3 + idx

            if self.gameOn: self.end()

            self.putData(['SUB', 'Load_{}={}&SKB=iOk1'\

                                 .format(['N', 'H'][iPrep==2], ngame), 'Fs_Hat'])

            self.curGame.set('Current game = {} / {} (#{})'\

                             .format(self.crops[iCrop], self.works[iPrep], \

                                     ngame))

            self.putData(['SUB', 'Tool=' + tools[iCrop][iPrep] + \

                                 '&Surf='+['field', 'road'][iPrep==2], \

                              'Rpi_Pres'])

            for b in self.buts: b.config(state=tk.NORMAL)

            self._resetSel()

            self.gameOn = True

   

    def vehChg(self):

        self.putData(['SUB', 'SKB=kTAB', 'Fs_Hat'])

   

    def couple(self):

        self.putData(['SUB', 'SKB=keyA', 'Fs_Hat'])

 

    def inOutCabin(self):

        self.putData(['SUB', 'SKB=keyE', 'Fs_Hat'])

 

    def camera(self):

        self.putData(['SUB', 'SKB=keyC', 'Fs_Hat'])

 

    def worker(self):

        self.putData(['SUB', 'SKB=keyH', 'Fs_Hat'])

 

    def enterKey(self):

        self.putData(['SUB', 'SKB=kRET', 'Fs_Hat'])

 

    def end(self):

        self.putData(['SUB', 'Boost=0&Manual=0&Tool=TRACTOR&Surf=road', \

                              'Rpi_Pres'])

        self.putData(['SUB', 'Quit=1', 'Fs_Hat'])

        self.curGame.set('Current game = ')

        self.gameOn = False

        for b in self.buts: b.config(state=tk.DISABLED)

       

    def Quit(self):

        if self.gameOn: self.end()

        self.retfun(self)

#                       

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

 

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

#                           Animation manager

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

class AnimMan(tk.Toplevel):

    def __init__(self, icofile, putData, dx, dy, fFmtn, gwdFmt, retfun):

        tk.Toplevel.__init__(self)

        self.resizable(width = False, height = False)

        self.title('Animation')

        self.iconbitmap(icofile)

        self.protocol('WM_DELETE_WINDOW', self.Quit)

       

        self.transient()

        self.geometry('+{}+{}'.format(dx, round(dy/3)))

 

        self.putData = putData

        self.retfun = retfun

 

        # ------- INFL /DEFL

        xflNames = ['Inflating', 'Deflating']

        fr = tk.LabelFrame(self, text=' / '.join(xflNames), fg=MiBlue)

        fr.grid(row=0, column=0, **gwdFmt)

        self.wxfl = []

        for i, label in enumerate(xflNames):

            self.wxfl.append(tk.Button(fr, text=xflNames[i], \

                                command=lambda n=i+1:self.xflSel(n), **fFmtn))

            self.wxfl[-1].grid(row=0, column=i, **gwdFmt)

        self.nXfl = ['', 'Infl', 'Defl' ]

        self.iXfl = 0

       

        # ------- VIEW

        fr = tk.LabelFrame(self, text='Animations', fg=MiBlue)

 

        fr.grid(row=1, column=0, **gwdFmt)

        self.views = tk.IntVar()

        self.views.set(0)

        viewNames = ['Front View', 'Contact Patch', 'Rut Difference']

        self.rb = []

        for i, label in enumerate(viewNames):

            self.rb.append(tk.Button(fr, text=viewNames[i], \

                                      command=lambda n=i+1: self.viewSel(n), \

                                        **fFmtn))

            self.rb[-1].grid(row=0, column=i, **gwdFmt)

        self.nView = [ '', 'tire', 'CP', 'rut']

        self.viewOn = [False]*4

 

        # ----- END

        tk.Button(self, text='◄ END', command=self.Quit, **fFmtn)\

                      .grid(row=3, column=0, **gwdFmt)

       

        colrowconfig(self.winfo_children())

        self.T0n = time() - 1.1

        self.wxfl[-1].invoke()

 

    def unselectView(self):

        tosend = ''

        for i in range(3):

            if self.viewOn[i]:

                tosend += 'Ovl={}_{}:zoomOut&'\

                              .format(self.nView[i+1], self.nXfl[self.iXfl])

                self.rb[i].config(relief=tk.RAISED)

                self.viewOn[i] = False

        if tosend: self.putData(['SUB', tosend[:-1], 'Show'])

 

    def xflSel(self, n):

        tCur = time()

        if tCur - self.T0n > 1: # avoid button "bouncing"

            self.T0n = tCur

            self.iXfl = n

            n -= 1

            for i in range(2): self.wxfl[i].config(relief=tk.RAISED)

            self.wxfl[n].config(relief=tk.SUNKEN)

            self.unselectView()

 

    def viewSel(self, n):

        tCur = time()

        if tCur - self.T0n > 1: # avoid button "bouncing"

            self.T0n = tCur

            n -= 1

            if self.viewOn[n]:

                self.rb[n].config(relief=tk.RAISED)

                self.putData(['SUB', 'Ovl={}_{}:zoomOut'\

                              .format(self.nView[n+1], self.nXfl[self.iXfl]), \

                              'Show'])

                self.viewOn[n] = False

            else:

                self.rb[n].config(relief=tk.SUNKEN)

                self.putData(['SUB', 'Ovl={}_{}:zoomIn'\

                              .format(self.nView[n+1], self.nXfl[self.iXfl]) , \

                              'Show'])

                self.viewOn[n] = True

 

    def Quit(self):

        self.unselectView()

        self.retfun(self)

#

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

 

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

#                           Maintenance  manager

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

class MntMan(tk.Toplevel):

    def __init__(self, icofile, putData, RpiOn, dx, dy, fFmtn, gwdFmt, retfun):

        tk.Toplevel.__init__(self)

        self.resizable(width = False, height = False)

        self.title('Maintenance')

        self.iconbitmap(icofile)

        self.protocol('WM_DELETE_WINDOW', self.Quit)

       

        self.transient()

        self.geometry('+{}+{}'.format(dx, round(dy/3)))

 

        butFmtC = dict(font = ('Arial', 14, 'italic'), fg=MiBlue)

       

        # ----- log

        fr = tk.LabelFrame(self, text='Log', fg=MiBlue)

        fr.grid(row=0, column=0, **gwdFmt)

        self.wti = fr

        tk.Button(fr, text='View', command=self.viewlog, **fFmtn)\

                      .grid(row=0, column=0, **gwdFmt)

        tk.Button(fr, text='Delete', command=self.dellog, **fFmtn)\

                      .grid(row=0, column=1, **gwdFmt)

 

        # ----- procs

        self.Arduinos = ['DUE', 'UNO']

        self.vnArduinos = []

        self.RpiOn = RpiOn

        self.Rpis = ['Rpi_Disp', 'Rpi_Pres', 'Rpi_Out']

        self.nProcs = ['Fs_Hat', 'Show', 'Tcp2Ser'] + self.Rpis

        self.vnProcs = []

 

        # ----- Arduinos

        lbf = tk.LabelFrame(self, text='Arduino\'s', fg=MiBlue)

        lbf.grid(row=1, column=0, **gwdFmt)

        for i in range(2):

            self.vnArduinos.append(tk.IntVar())

            self.vnArduinos[i].set(0)

            tk.Checkbutton(lbf, text=self.Arduinos[i], \

                           variable=self.vnArduinos[i], \

                           anchor=tk.W, **fFmtn)\

                          .grid(row=0, column=i, **gwdFmt)

        tk.Button(lbf, text='Reset', \

                  command=self.resetArduino, **fFmtn)\

                      .grid(row=1, column=0, columnspan=2, **gwdFmt)

 

        fr = tk.LabelFrame(self, text='Procs', fg=MiBlue)

        fr.grid(row=2, column=0, **gwdFmt)

        self.allFlag = tk.IntVar()

        self.allFlag.set(0)

        tk.Checkbutton(fr, text='all', variable=self.allFlag, \

                       command=self.selectAll, \

                       anchor=tk.W, **fFmtn)\

                      .grid(row=1, column=0, **gwdFmt)

       

        # ----- PC

        lbf = tk.LabelFrame(fr, text='PC\'s', fg=MiBlue)

        lbf.grid(row=3, column=0, **gwdFmt)

        for i in range(3):

            self.vnProcs.append(tk.IntVar())

            self.vnProcs[i].set(0)

            tk.Checkbutton(lbf, text=self.nProcs[i], variable=self.vnProcs[i], \

                           anchor=tk.W, **fFmtn)\

                          .grid(row=0, column=i, **gwdFmt)

           

        # ----- Raspberry

        lbf = tk.LabelFrame(fr, text='Raspberry Pi\'s', fg=MiBlue)

        lbf.grid(row=4, column=0, **gwdFmt)

        for k in range(3):

            j = k + 3

            self.vnProcs.append(tk.IntVar())

            self.vnProcs[j].set(0)

            tk.Checkbutton(lbf, text=self.nProcs[j], \

                           variable=self.vnProcs[j], \

                           anchor=tk.W, **fFmtn)\

                          .grid(row=0, column=k, **gwdFmt)

        tk.Button(lbf, text='Replug', command=self.replugRpis, **fFmtn)\

                      .grid(row=1, column=0, columnspan=3, **gwdFmt)

 

        fr2 = tk.LabelFrame(fr, text='Command', fg=MiBlue)

        fr2.grid(row=5, column=0, **gwdFmt)

        self.editTxt = tk.StringVar()

        self.editTxt.set('')

        tk.Entry(fr2, exportselection=0, textvariable=self.editTxt, **fFmtn)\

               .grid(row=0, column=0, columnspan=3, **gwdFmt)                                  

        tk.Button(fr2, text='send', command=self.sendTxt, **fFmtn)\

               .grid(row=0, column=4, columnspan=3, **gwdFmt)                                  

       

        """

          Exit :  0 : RESTART loop

          1 : RESTART prog

          2 : REBOOT device

          3 : SHUTDOWN device

        """

        fr2 = tk.LabelFrame(fr, text='Prog', fg=MiBlue)

        fr2.grid(row=6, column=0, **gwdFmt)

        tk.Button(fr2, text='Quit', \

                  command=lambda n=0: self.exitn(n), **fFmtn)\

                      .grid(row=0, column=0, **gwdFmt)

        tk.Button(fr2, text='Restart', \

                  command=lambda n=1: self.exitn(n), **fFmtn)\

                      .grid(row=0, column=1, **gwdFmt)

       

        fr2 = tk.LabelFrame(fr, text='Device', fg=MiBlue)

        fr2.grid(row=7, column=0, **gwdFmt)

        tk.Button(fr2, text='Reboot', \

                  command=lambda n=2: self.exitn(n), **fFmtn)\

                      .grid(row=0, column=0, **gwdFmt)       

        tk.Button(fr2, text='Shutdown', \

                  command=lambda n=3: self.exitn(n), **fFmtn)\

                      .grid(row=0, column=1, **gwdFmt)       

        # ----- END

        tk.Button(self, text='◄ back', command=self.Quit, **butFmtC)\

                 .grid(row=8, column=0, **gwdFmt)

 

        colrowconfig(self.winfo_children())

        self.retfun = retfun

        self.putData = putData

        self.setTimer = self.after

        self.tiOn = False

 

    def sendTxt(self):

        txt = self.editTxt.get()

        if '=' in txt:

            anySent = False

            for i, p in enumerate(self.nProcs):

                if self.vnProcs[i].get():

                        self.putData(['SUB', txt, p])

                        anySent = True

            for v in self.vnProcs: v.set(0)

            if anySent: self.editTxt.set('')

            self.allFlag.set(0)

 

    def selectAll(self):

        val = self.allFlag.get()

        for v in self.vnProcs: v.set(val)

       

    def viewlog(self):

        with open('__Master.log', 'w', encoding='utf-16') as log:

            with open('Master.log', 'r', encoding='utf-16') as _log:

               log.write(_log.read())

            with open('_Master.log', 'r', encoding='utf-16') as _log:

               log.write(_log.read())

        Popen('notepad __Master.log')

 

    def dellog(self):

        copyfile('Master.log', './log/M{}.log'.format(strftime('%y%m%d%H%M%S')))

        with open('Master.log', 'w', encoding='utf-16'): pass

 

    def resetArduino(self):

        if self.vnArduinos[0].get():

            self.putData(['SUB', 'SKB=rstd', 'Fs_Hat'])

        if self.vnArduinos[1].get():

            self.putData(['SUB', 'ResetUno=1', 'Tcp2Ser'])

        for v in self.vnArduinos: v.set(0)

 

    def replugRpis(self):

        for i in range(3):

            if self.vnProcs[i+3].get():

                self.unplugRpiN(i)

                sleep(.1)

 

    def unplugRpiN(self, n):

        self.putData(['SUB', 'Rpi#{}=0'.format(n), 'Tcp2Ser'])

        self.tiOn = True

        self.RpiOn[i] = False

        self.setTimer(5000, self.replugRpiN, n)

 

    def replugRpiN(self, n):

        self.putData(['SUB', 'Rpi#{}=1'.format(n), 'Tcp2Ser'])

        self.RpiOn[n] = True

        self.vnProcs[n+3].set(0)

        self.tiOn = not all(self.RpiOn)

 

    def exitn(self, n):

        for i, p in enumerate(self.nProcs):

            if self.vnProcs[i].get() and i != 2:

                    self.putData(['SUB', 'Exit={}'.format(n), p])

                    sleep(.1)

        for v in self.vnProcs: v.set(0)

        self.allFlag.set(0)

 

    def Quit(self):

        if self.tiOn:

            MsgBox('Action in progress',\

                   'Cannot close now: action in progress....\n' + \

                   'Try again in a few sec ...')

        else:

            self.retfun(self.RpiOn, self)

#

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

 

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

#

class mainBoard():

    """

    Main animation

    """

 

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

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

           

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

        fig = tk.Tk()

        #fig.overrideredirect(True)

        #fig.lift()

        fig.wm_attributes('-topmost', True)

        fig.geometry('+0+0')

        fig.iconbitmap(config.iconfile)

        fig.title('Master')

        fig.protocol('WM_DELETE_WINDOW', self.dlgEnd)

 

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

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

       

        self.config = config

        self.getData = qIn.get_nowait

        # self.putData = print                       # <<<<< DEBUG

        self.putData = qOut.put_nowait

 

        # ------------------------------------------------------------ formats

        fs = 16

        self.fFmtn = dict(font=("Arial", fs), fg=MiBlue)

        self.fFmtb = dict(font=("Arial", fs, 'bold'), fg=MiBlue)

        self.fFmti = dict(font=("Arial", fs-2, 'italic'), fg=MiBlue)

        fs2 = 50

        self.fFmtc1 = dict(font=("Arial", fs2, 'bold'), fg=MiBlue)

        self.fFmtc2 = dict(font=("Arial", fs2, 'italic'), fg='blue')

        self.fFmtc3 = dict(font=("Arial", fs2), fg='darkgrey')

        self.fFmtc4 = dict(font=("Arial", fs2, 'italic'), fg='darkgrey')

        self.gwdFmt = dict(padx=5, pady=5, sticky='news')

 

        # ----------------------------------------------------------- variable

        self.nextShowTime = int(config['NEXT_SHOW']['nextshtime'])

       

        sLang = config['LANG']['lang']

        lglst = [lg[:2].lower() for lg in config.langList]

 

 

        iRow = 0

        # ---- line 1 : lang

        self.vLang = tk.StringVar()

        self.vLang.set(sLang)

        tk.Label(textvariable=self.vLang, bg=MiWht2, **self.fFmtn)\

                .grid(row=iRow, column=0, columnspan=2, **self.gwdFmt)

        self.wLang = tk.Button(text='Lang\t►', \

                               command=self.askLang, **self.fFmtn)

        self.wLang.grid(row=iRow, column=2, **self.gwdFmt)

 

        iRow += 1

        # ----- Counter                                             (STEP#0)

        self.wStep = [tk.Label(text='►', disabledforeground=MiWht2 , \

                                   **self.fFmtn)]

        self.wStep[-1].grid(row=iRow+1, column=0, **self.gwdFmt)

        self.vCnt = tk.StringVar()

        self.vCnt.set('_ _')

        self.wCnt = [tk.Label(textvariable=self.vCnt, width = 3, \

                              bg=MiWht1, **self.fFmtc1)]

        self.wCnt[-1].grid(row=iRow, column=1, rowspan=3, **self.gwdFmt)

        self.wCnt.append(tk.Button(text='Wait\t►', \

                              command=self.askTime, **self.fFmtn))

        iRow += 1

        self.wCnt[-1].grid(row=iRow, column=2, **self.gwdFmt)

       

        iRow += 2

        # ----- Farming Simulator Show                              (STEP#1)

        self.wStep.append(tk.Label(text='►', disabledforeground=MiWht2 , \

                                   **self.fFmtn))

        self.wStep[-1].grid(row=iRow, column=0, **self.gwdFmt)

        self.wFSimuS = tk.Button(text='Farming Simulator Show\t►', \

                               command=self.dlgFSimuShow, **self.fFmtn)

        self.wFSimuS.grid(row=iRow, column=1, columnspan=2,  **self.gwdFmt)

       

        iRow += 1

        # ----- Farming Simulator Play                              (STEP#2)

        self.wStep.append(tk.Label(text='►', disabledforeground=MiWht2 , \

                                   **self.fFmtn))

        self.wStep[-1].grid(row=iRow, column=0, **self.gwdFmt)

        self.wFSimuP = tk.Button(text='Farming Simulator Play\t►', \

                               command=self.dlgFSimuPlay, **self.fFmtn)

        self.wFSimuP.grid(row=iRow, column=1, columnspan=2,  **self.gwdFmt)

       

        iRow += 1

        # ----- end                                                 (STEP#3)

        self.wStep.append(tk.Label(text='►', disabledforeground=MiWht2 , \

                                   **self.fFmtn))

        self.wStep[-1].grid(row=iRow, column=0, **self.gwdFmt)

        self.wEnd = tk.Button(text='End', command=self.dlgEnd, **self.fFmtn)

        self.wEnd.grid(row=iRow, column=1, columnspan=2, **self.gwdFmt)

 

        iRow += 1

        # ----- status

        self.vStatus = tk.StringVar()

        self.vStatus.set('Ok')

        self.wStatus = tk.Label(textvariable=self.vStatus, \

                                bg='green', fg='white')

        self.wStatus.grid(row=iRow, column=0, columnspan=3, **self.gwdFmt)

 

        iRow += 1

        # ----- maintenance                                         (STEP#4)

        self.wStep.append(tk.Label(text='►', disabledforeground=MiWht2 , \

                                   **self.fFmtn))

        self.wStep[-1].grid(row=iRow, column=0, **self.gwdFmt)

        self.wMnt = tk.Button(text='Maintenance\t\t►', \

                              command=self.dlgMnt, **self.fFmti)

        self.wMnt.grid(row=iRow, column=1, columnspan=2, **self.gwdFmt)

 

        fig.update()

        self.dx, self.dy = [int(x.split('+')[0]) \

                            for x in fig.geometry().split('x')]

        # ----- pannels

        self.panLang = None

        self.panTime = None

        self.tiCnt = None

        self.CountFlag = 0

        self.mnsec = 'mn'

        self.mnsec2 = 'sec'

        self.cntTime = 0

        self.step = 0

        self.setState(0)

        self.panFSimuShow = None

        self.panFSimuPlay = None

        self.panMnt = None

        self.pannelOn = False

        self.RpiOn = [True]*4

        self.enterGameFlag = tk.IntVar()

 

        self.icofile = config.iconfile

        self.mainloop = fig.mainloop

        self.destroy = fig.destroy

        self.setTimer = fig.after

        self.fig = fig

        colrowconfig(self.fig.winfo_children())

        self.fig.after(10, self.wait4action)

        self.mainloop()

       

    # === ask for language

    def askLang(self):

        if self.wLang['relief'] == tk.RAISED:

            self.wLang.config(relief=tk.SUNKEN, fg='black')

            self.panLang = LangAPP(self.icofile, self.config.langList, \

                                   self.setLang, self.dx)

            self.panLang.mainloop()

 

    # ----- Set language

    def setLang(self, lang, adef):

        if lang:

            self.vLang.set(lang)

            self.putData(['SUB', 'Lang=' + lang[:2], 'Show'])

            self.putData(['SUB', 'Lang=' + lang[:2], 'Rpi_Pres'])

        self.wLang.config(relief=tk.RAISED,  fg=MiBlue)

        self.panLang = None

        adef.destroy()

 

    # ===== ask for time

    def askTime(self):

        if self.wCnt[1]['relief'] == tk.RAISED:

            if self.step == 0 and not self.pannelOn:

                self.pannelOn = True

                self.mnsec = 'mn'

                self.wCnt[1].config(relief=tk.SUNKEN, fg='black')

                if self.mnsec2 == 'mn':

                    n = self.nextShowTime - self.cntTime

                else:

                    n = self.nextShowTime

                if n < 0: n = self.nextShowTime

                self.panTime = TimeMan(self.icofile, \

                                       self.setTime, self.setWait, \

                        n, self.nextShowTime, self.dx, int(self.dy*.05),\

                                       self.fFmtn, self.gwdFmt)

                self.panTime.mainloop()

               

    # ----- Set next show time

    def setTime(self, waittime):

        if waittime:

            self.nextShowTime = waittime

       

    # ----- Set remTime

    def setWait(self, nexttime, mnsec, adef):

        if nexttime is not None:

            if mnsec == 'mn':

                self.wCnt[0].config(**self.fFmtc1)

            else:

                if nexttime >= 60:

                    self.wCnt[0].config(**self.fFmtc1)

                    mnsec = 'mn'

                    nexttime = round(nexttime / 60)

                else:

                    self.wCnt[0].config(**self.fFmtc2)

            self.mnsec = mnsec

            if self.tiCnt is not None:

                self.wCnt[0].after_cancel(self.tiCnt)

                self.tiCnt = None

            self.putData(['SUB', 'remTime={}:{}'.format(nexttime, mnsec), 'Show'])

            self.wCnt[1].config(relief=tk.RAISED,  fg=MiBlue)

            adef.destroy()

            self.CountFlag = -1

            self.countDown(nexttime)

        else:

            adef.destroy()

            self.wCnt[1].config(relief=tk.RAISED,  fg=MiBlue)

        self.panTime = None

        self.pannelOn = False

 

    # ----- countDown

    def countDown(self, n, endTime=0):

        if n > 1:

            self.vCnt.set(str(n))

            nw = 1

            if self.mnsec == 'mn' : nw *= 60

            self.tiCnt = self.wCnt[0].after(nw*1000, self.countDown, n-1)

        else:

            if self.mnsec == 'mn':

                self.mnsec = 'sec'

                n = 60

                self.wCnt[0].config(**self.fFmtc2)

                self.countDown(n)

            else:

                self.CountFlag = 0

                self.setState(endTime)

 

    # ----- prepare to count Up

    def startCntUp(self):

        self.wCnt[0].config(**self.fFmtc4)

        self.mnsec2 = 'sec'

        if self.tiCnt is not None:

            self.wCnt[0].after_cancel(self.tiCnt)

            self.tiCnt = None

        self.CountFlag = 1

        self.countUp(0)

       

    # ----- countUp

    def countUp(self, n):

        self.cntTime = n

        self.vCnt.set(str(n))

        if self.mnsec2 == 'sec':

            if n < 60:

                self.tiCnt = self.wCnt[0].after(1000, self.countUp, n+1)

            else:

                self.wCnt[0].config(**self.fFmtc3)

                self.mnsec2 = 'mn'

                self.countUp(1)

        else:

            self.tiCnt = self.wCnt[0].after(60000, self.countUp, n+1)

                   

    # ----- iddle Time

    def iddleTime(self):

        self.wCnt[0].config(**self.fFmtc1)

        self.vCnt.set('_ _')

        self.CountFlag = 0

        if self.tiCnt is not None:

            self.wCnt[0].after_cancel(self.tiCnt)

            self.tiCnt = None

       

    # ===== set state

    def setState(self, n, iddle=True):

        for w in self.wStep:

            w.config(state=tk.DISABLED)

        self.wStep[n].config(state=tk.NORMAL)

        prev = self.step

        self.step = n

        if n == 0:

            if iddle: self.iddleTime()

        elif n == 1:

            if self.wFSimuS['relief'] == tk.RAISED:

                self.wFSimuS.invoke()

        elif n == 2:

            if self.wFSimuP['relief'] == tk.RAISED:

                self.wFSimuP.invoke()

        elif n == 3:

            self.step = prev

            if self.wEnd['relief'] == tk.RAISED:

                self.wEnd.invoke()

        elif n == 4:

            self.step = prev

            if self.wMnt['relief'] == tk.RAISED:

                self.wMnt.invoke()

 

    # ===== FARMNG SIMULATOR - SHOW - 1

    def dlgFSimuShow(self):

        if self.wFSimuS['relief'] == tk.RAISED and not self.pannelOn:

            Ok = True

            if self.CountFlag == -1:

                Ok = bool(QuestBox(title = 'Launch Farming Simulator Show?', \

                        message = 'Do you want to launch the FS show right now?'))

            if Ok:

                if self.CountFlag != 1: self.startCntUp()

                self.pannelOn = True

                self.wFSimuS.config(relief=tk.SUNKEN, fg='black')

                self.setState(1)

                print('\t\t» FSimuShow : {}'.format(strftime('%d/%m/%y %H:%M:%S')))

                self.putData(['SUB','ShowOn=1', 'Show'])

                self.putData(['SUB','Sound=1', 'Tcp2Ser'])

                self.panFSimuShow = FSimuShowMan(self.icofile, self.putData, self.dx, \

                                                 int(self.dy*.1), \

                                                 self.config.langList, \

                                                 self.vLang,  \

                                                 self.fFmtn, self.gwdFmt,\

                                                 self.endFSimuShow)

                self.panFSimuShow.mainloop()

               

    def endFSimuShow(self, adef):

        self.wFSimuS.config(relief=tk.RAISED, fg=MiBlue)

        adef.destroy()

        self.pannelOn = False

        self.panFSimuShow = None

        self.putData(['SUB','ShowOn=0', 'Show'])

        self.putData(['SUB','Sound=0', 'Tcp2Ser'])

        print('\t\t« FSimuShow : {}'.format(strftime('%d/%m/%y %H:%M:%S')))

        self.setState(0)

       

    # ===== FARMNG SIMULATOR - PLAY - 2

    def dlgFSimuPlay(self):

        if self.wFSimuP['relief'] == tk.RAISED and not self.pannelOn:

            self.wFSimuP.config(relief=tk.SUNKEN, fg='black')

            self.setState(2)

            self.pannelOn = True

            print('\t\t» FSimuPlay : {}'.format(strftime('%d/%m/%y %H:%M:%S')))

            self.panFSimuPlay = FSimuPlayMan(self.icofile, self.putData, self.dx, \

                                   int(self.dy*.2), self.fFmtn, self.gwdFmt,\

                                              self.endFSimuPlay)

            self.panFSimuPlay.mainloop()

 

    def endFSimuPlay(self, adef):

        self.wFSimuP.config(relief=tk.RAISED, fg=MiBlue)

        adef.destroy()

        self.pannelOn = False

        self.panFSimuPlay = None

        print('\t\t« FSimuPlay : {}'.format(strftime('%d/%m/%y %H:%M:%S')))

        self.setState(0, False)

        

    # ===== quit current process - 3

    def dlgEnd(self):

        if not self.pannelOn:

            self.wEnd.config(relief=tk.SUNKEN, fg='black')

            self.setState(3)

            if QuestBox(title = 'Quit?', \

                        message = 'Are you sure to quit right now?'):

                self.Quit()

            else:  

                self.wEnd.config(relief=tk.RAISED, fg=MiBlue)

                self.setState(self.step, False)

                self.fig.update_idletasks()

 

    # ===== MAINTENANCE          - 4

    def dlgMnt(self):

        if self.wMnt['relief'] == tk.RAISED:

            self.wMnt.config(relief=tk.SUNKEN, fg='black')

            self.setState(4)

            self.panMnt = MntMan(self.icofile, self.putData, self.RpiOn, \

                                 self.dx, int(self.dy*.075), \

                                       self.fFmtn, self.gwdFmt, self.endMnt)

            self.panMnt.mainloop()

 

    def endMnt(self, RpiOn, adef):

        self.RpiOn = RpiOn

        self.wMnt.config(relief=tk.RAISED, fg=MiBlue)

        adef.destroy()

        self.setState(self.step, False)

        self.panMnt = None

       

    def wait4action(self):

        loop = True

        try:

            ret = self.getData()

        except Empty:

            ret = []

 

        if ret:

            cmd, val = ret

 

            if 'endTime' in cmd:

                if self.step == 0:

                    self.mnsec = 'sec'

                    self.countDown(0, endTime=1)

 

            elif 'EnterGame' in cmd:

                # self.enterGame()

                #self.enterGameFlag.set(1)

                pass

 

            elif 'note' in cmd:

                n = int(cmd[-1])

                if n == 0:

                    self.wStatus.config(bg='green')

                elif n == 1:

                    self.wStatus.config(bg='orange')

                elif n == 2:

                    self.wStatus.config(bg='red')

                self.vStatus.set(shorten(val.strip(), width=60, \

                                         placeholder='…'))

 

            elif cmd == 'Exit':

                loop = False

               

        if loop:

            self.setTimer(10, self.wait4action)

        else:

            self.destroy()

           

    def Quit(self, *event):

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

        self.destroy()          # quit the program

#

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

 

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

#                               Create log file

import sys

class std_dup(object):

    def __init__(self, files):

        self._files = files

    def __getattr__(self, attr, *args):

        return self._wrap(attr, *args)

    def _wrap(self, attr, *args):

        def g(*a, **kw):

            for f in self._files:

                res = getattr(f, attr, *args)(*a, **kw)

            return res

        return g

fidlog = open('_Master.log', 'a', 1, encoding='utf-16')

sys.stdout = std_dup([sys.stdout, fidlog])

sys.stderr = std_dup([sys.stderr, fidlog])

#

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

   

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

#

if __name__ == '__main__':

 

    from multiprocessing import Process

    from defTCPIP import TCPLink

    from os import path, remove

 

    if not path.exists('Master.log'):

        with open('Master.log', 'w', encoding='utf-16'): pass

 

    # ----- read params from ini file

    config = ConfigParser()

    config.filename = 'Master.ini'

    config.savfilename = config.filename.replace('.','_sav.')

    config.iconfile = config.filename.replace('.ini', '.ico')

   

    Ok = False

    for tst in range(2):

       # ----- verify config ini ok

        try:

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

            vrbstr = config['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)

            LNG = config['LANG']

            config.langList = [lg.strip().title() for lg in LNG['list'].split(',')]

            sLang = LNG['lang']

            nextshtime = config['NEXT_SHOW']['nextshtime']

            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'])

            animLights = 'Anim={}'\

                          .format(int(config['NEXT_SHOW']['animlights']))

            Ok = True

            break

        except:

            # ---- take the save ini

            print('\t! Restore ini file')

            with open(config.filename, 'w', encoding='utf-16') as ini:

                with open(config.savfilename, 'r', encoding='utf-16') as _ini:

                    ini.write(_ini.read())

    if not Ok:

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

        exit(44)

       

    #

    # verbose : 1 = main, 2 = ethernet, 4 = Serial

    #

    vrbm = bool(verbose & 1)

    vrbe = bool(verbose & 2)

    vrbs = bool(verbose & 4)

   

    # ----- 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{}: {}\n'.format(procName, eth.myAddr))

 

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

    qCmd = Queue()

    setBoard = qCmd.put_nowait

 

    # ----- launch main program and loop

    pB = Process(target=mainBoard, args=(qCmd, qIn, config))

    pB.start()

 

    exitVal = 0

    MainOn = True

    exitFlag = False

    getdata = qIn.get

    wait4data = True

    T0 = time()

    Tend = 5

    ackn = False

    exitFromBoard = False

    tireUpFlag = False

 

    # ----- test if tcp2ser launched

    eth.sendTo('Test=1', 'Tcp2Ser')

   

    while MainOn:

 

        try:

            ret = getdata(wait4data)

        except Empty:

            ret = []

        except KeyboardInterrupt:

            ret = []

            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:

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

                if vrbe: print(msg)

                setBoard(['note0', msg])

               

            elif 'snt0' in typ2:

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

                print(msg)

                if 'Tcp2Ser' in proc: Popen('./tcp2ser.exe')

                else: setBoard(['note1', msg])

               

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

                # ----- aknowledge

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

                if vrbe: print(msg)

                setBoard(['note0', msg])

                if exitFlag:

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

                    if typ2 == 'rcv1':

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

 

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

 

                ackn = True

 

                if cmd in ['endTime', 'endPlay', 'EnterGame']:

                    setBoard([cmd, val])

 

                elif 'SurfSet' in cmd:

                    eth.sendTo('Surf='+val, 'Rpi_Pres')

                    if 'road' in val:

                        eth.sendTo('TirePos=3', 'Tcp2Ser')

                    elif 'field' in val:

                        eth.sendTo('TirePos=4', 'Tcp2Ser')

                       

                elif cmd == 'last10s':

                    eth.sendTo(animLights, 'Rpi_Out')

 

                elif cmd == 'Exit':

                    # stop Rpi_Inputs

                    exitFlag = True

                    exitVal = int(val)

                    wait4data = False

                    T0 = time()

                   

            elif 'rcv?' in typ2:

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

                setBoard(['note1', msg])

                ackn = False

               

        elif 'SUB' in typ:

            if msgs == 'Exit':

                MainOn = False

                exitFromBoard = True

               

            else:

                # ----- send msg to proc

                eth.sendTo(msgs, proc)

 

        elif 'SER' in typ:

            if 'TiUp' in msgs:

                tireUpFlag = True

               

        elif typ:

            msg = '\t/!\ unknown type : ', typ

            print(msg)

            setBoard(['note1', msg])

 

        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')))

   

    # ----- close log

    sys.stderr.flush()

    sys.stdout.flush()

    fidlog.close()

 

    # ---- update (append)  Master.log

    with open('Master.log', 'a', encoding='utf-16') as log:

        with open('_Master.log', 'r', encoding='utf-16') as _log:

            log.write(_log.read())

    remove('_Master.log')

    if path.exists('__Master.log'): remove('__Master.log')

 

    eth.sendTo('TirePos=1', 'Tcp2Ser')

   

    exit(0)

#

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

____________________________________________________________________________________

 

 

 

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 hardware

 

Arduinos

Tractor simulator (cabin tractor interface with Farming Simulator 17): Part 3 - the software, 1/ Arduinos

 

Raspberry Pis

Tractor simulator (cabin tractor interface with Farming Simulator 17): Part 3 - the software, 2/ Raspberrys

 

Actual doc