1#!/usr/bin/env python
2#
3# Copyright (C) 2017 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18# A simple GUI to remotely actuate the Vehicle HAL via the eumalator
19
20import argparse
21import sys
22from threading import Thread
23from PyQt5 import QtCore
24from PyQt5 import QtWidgets
25
26import VehicleHalProto_pb2
27from vhal_emulator import Vhal
28import vhal_consts_2_0 as c
29
30
31# Define a simple thread that receives messages from a vhal object (v) and prints them
32def rxThread(v):
33    while(1):
34        msg = v.rxMsg()
35        if (msg.msg_type == VehicleHalProto_pb2.SET_PROPERTY_RESP):
36            if msg.status == 0:
37                print('Success (%s)' % msg.status)
38            else:
39                print('Error (%s)' % msg.status)
40        else:
41            print(msg)
42
43
44# Main window setup
45def window():
46    app = QtWidgets.QApplication(sys.argv)
47    dialog = QtWidgets.QDialog()
48    dialog.setWindowTitle('VHal Driver')
49    dialog.setGeometry(100, 100, 200, 50)
50    topLevelLayout = QtWidgets.QHBoxLayout()
51    dialog.setLayout(topLevelLayout)
52
53    shiftLayout = QtWidgets.QVBoxLayout()
54    topLevelLayout.addLayout(shiftLayout)
55
56    gearTitle = QtWidgets.QLabel(dialog)
57    gearTitle.setText('Gear Shift')
58    shiftLayout.addWidget(gearTitle)
59
60    gearDisplay = QtWidgets.QLabel(dialog)
61    shiftLayout.addWidget(gearDisplay)
62
63    slider = QtWidgets.QSlider(QtCore.Qt.Vertical)
64    slider.setMinimum(0)
65    slider.setMaximum(2)
66    slider.setInvertedAppearance(True)
67    slider.valueChanged.connect(lambda:sliderMove(slider, gearDisplay))
68    shiftLayout.addWidget(slider)
69    sliderMove(slider, gearDisplay)
70
71
72    buttonLayout = QtWidgets.QVBoxLayout()
73    topLevelLayout.addLayout(buttonLayout)
74
75    signalButtonGroup = QtWidgets.QButtonGroup()
76
77    bNoSignal = QtWidgets.QPushButton('None')
78    bNoSignal.setCheckable(True)
79    bNoSignal.setChecked(True)
80    buttonLayout.addWidget(bNoSignal)
81    signalButtonGroup.addButton(bNoSignal)
82
83    bHazards = QtWidgets.QPushButton('Hazards')
84    bHazards.setCheckable(True)
85    buttonLayout.addWidget(bHazards)
86    signalButtonGroup.addButton(bHazards)
87
88    bLeft = QtWidgets.QPushButton('Left')
89    bLeft.setCheckable(True)
90    buttonLayout.addWidget(bLeft)
91    signalButtonGroup.addButton(bLeft)
92
93    bRight = QtWidgets.QPushButton('Right')
94    bRight.setCheckable(True)
95    buttonLayout.addWidget(bRight)
96    signalButtonGroup.addButton(bRight)
97
98    signalButtonGroup.buttonClicked.connect(lambda: onSignalClicked(signalButtonGroup))
99
100    dialog.show()
101    sys.exit(app.exec_())
102
103
104def onSignalClicked(group):
105    print('signal ' + group.checkedButton().text() + ' is active')
106    try:
107        vhal.setProperty(c.VEHICLEPROPERTY_TURN_SIGNAL_STATE, 0, group.checkedId())
108    except:
109        print('Ignoring error setting property 0x{:08X}'.format(c.VEHICLEPROPERTY_TURN_SIGNAL_STATE))
110
111
112def sliderMove(slider, gearDisplay):
113    if slider.value() == 0:
114        gearName = 'park'
115        vhal.setProperty(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_PARK)
116    elif slider.value() == 1:
117        gearName = 'reverse'
118        vhal.setProperty(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_REVERSE)
119    elif slider.value() == 2:
120        gearName = 'drive'
121        vhal.setProperty(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_DRIVE)
122    else:
123        gearName = 'UNK'
124    print('slider %s requested %s = %s'  % (slider.objectName(), slider.value(), gearName))
125    gearDisplay.setText(gearName)
126
127
128if __name__ == '__main__':
129    parser = argparse.ArgumentParser(description='Vehicle HAL Driver UI')
130    parser.add_argument('--serial', '-s', action='store', dest='serial',
131        default=None, required=False, help='Select which device to connect to')
132    args = parser.parse_args()
133    print('Starting VHal driver GUI')
134    vhal = Vhal(c.vhal_types_2_0, device=args.serial)
135
136    # Start a receive thread to consume any replies from the vhal
137    print('Starting receiver thread')
138    rx = Thread(target=rxThread, args=(vhal,))
139    rx.setDaemon(True)
140    rx.start()
141
142    # Put the car in park so we start in a known state (consistent with the GUI default state)
143    vhal.setProperty(c.VEHICLEPROPERTY_GEAR_SELECTION, 0, c.VEHICLEGEAR_GEAR_PARK)
144
145    # Start the main UI -- never returns
146    window()
147