summaryrefslogtreecommitdiffstats
path: root/Widgets
diff options
context:
space:
mode:
Diffstat (limited to 'Widgets')
-rw-r--r--Widgets/HVACPage.py112
-rw-r--r--Widgets/ICPage.py353
-rw-r--r--Widgets/NavPage.py179
-rw-r--r--Widgets/SteeringCtrlPage.py166
-rw-r--r--Widgets/settings.py116
5 files changed, 926 insertions, 0 deletions
diff --git a/Widgets/HVACPage.py b/Widgets/HVACPage.py
new file mode 100644
index 0000000..99ee798
--- /dev/null
+++ b/Widgets/HVACPage.py
@@ -0,0 +1,112 @@
+"""
+ Copyright 2023 Suchinton Chakravarty
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import os
+import sys
+from PyQt5 import uic
+from PyQt5.QtWidgets import QApplication, QListWidget, QSlider, QPushButton
+
+current_dir = os.path.dirname(os.path.abspath(__file__))
+
+# ========================================
+
+sys.path.append(os.path.dirname(current_dir))
+
+from extras.FeedKuksa import FeedKuksa
+
+Form, Base = uic.loadUiType(os.path.join(current_dir, "../ui/HVAC.ui"))
+
+# ========================================
+
+class HVAC_Paths():
+ def __init__(self):
+ self.leftTemp = "Vehicle.Cabin.HVAC.Station.Row1.Left.Temperature"
+ self.leftFanSpeed = "Vehicle.Cabin.HVAC.Station.Row1.Left.FanSpeed"
+ self.rightTemp = "Vehicle.Cabin.HVAC.Station.Row1.Right.Temperature"
+ self.rightFanSpeed = "Vehicle.Cabin.HVAC.Station.Row1.Right.FanSpeed"
+
+ # temperatureList contains values from 32 to 16
+ self.temperatureList = [str(i) + "°C" for i in range(32, 15, -1)]
+
+class HVACWidget(Base, Form):
+ def __init__(self, parent=None):
+ super(self.__class__, self).__init__(parent)
+ self.setupUi(self)
+
+ self.HVAC = HVAC_Paths()
+
+ self.feed_kuksa = FeedKuksa()
+
+ self.leftTempList = self.findChild(QListWidget, "leftTempList")
+ self.leftTempList.addItems(self.HVAC.temperatureList)
+ self.leftTempList.setCurrentRow(0)
+ self.leftTempList.itemClicked.connect(self.leftTempListClicked)
+ self.leftTempList.itemSelectionChanged.connect(self.leftTempListClicked)
+ self.leftTempList.wheelEvent = lambda event: None
+
+ self.rightTempList = self.findChild(QListWidget, "rightTempList")
+ self.rightTempList.addItems(self.HVAC.temperatureList)
+ self.rightTempList.setCurrentRow(0)
+ self.rightTempList.itemClicked.connect(self.rightTempListClicked)
+ self.rightTempList.itemSelectionChanged.connect(self.rightTempListClicked)
+ self.rightTempList.wheelEvent = lambda event: None
+
+ self.leftTempUp = self.findChild(QPushButton, "leftTempUp")
+ self.leftTempUp.clicked.connect(lambda: self.leftTempList.setCurrentRow(self.leftTempList.currentRow() - 1))
+
+ self.leftTempDown = self.findChild(QPushButton, "leftTempDown")
+ self.leftTempDown.clicked.connect(lambda: self.leftTempList.setCurrentRow(self.leftTempList.currentRow() + 1))
+
+ self.rightTempUp = self.findChild(QPushButton, "rightTempUp")
+ self.rightTempUp.clicked.connect(lambda: self.rightTempList.setCurrentRow(self.rightTempList.currentRow() - 1))
+
+ self.rightTempDown = self.findChild(QPushButton, "rightTempDown")
+ self.rightTempDown.clicked.connect(lambda: self.rightTempList.setCurrentRow(self.rightTempList.currentRow() + 1))
+
+ self.leftFanSpeed_slider = self.findChild(QSlider, "leftFanSpeed_slider")
+ self.leftFanSpeed_slider.valueChanged.connect(self.leftFanSpeed_sliderChanged)
+
+ self.rightFanSpeed_slider = self.findChild(QSlider, "rightFanSpeed_slider")
+ self.rightFanSpeed_slider.valueChanged.connect(self.rightFanSpeed_sliderChanged)
+
+ def leftTempListClicked(self):
+ item = self.leftTempList.currentItem()
+ self.leftTempList.scrollToItem(item, 1)
+ self.feed_kuksa.send_values(self.HVAC.leftTemp, item.text()[:-2])
+ print(item.text())
+
+ def rightTempListClicked(self):
+ item = self.rightTempList.currentItem()
+ self.rightTempList.scrollToItem(item, 1)
+ self.feed_kuksa.send_values(self.HVAC.rightTemp, item.text()[:-2])
+ print(item.text())
+
+ def leftFanSpeed_sliderChanged(self):
+ value = self.leftFanSpeed_slider.value()
+ self.feed_kuksa.send_values(self.HVAC.leftFanSpeed, str(value))
+ print(value)
+
+ def rightFanSpeed_sliderChanged(self):
+ value = self.rightFanSpeed_slider.value()
+ self.feed_kuksa.send_values(self.HVAC.rightFanSpeed, str(value))
+ print(value)
+
+if __name__ == '__main__':
+ import sys
+ app = QApplication(sys.argv)
+ w = HVACWidget()
+ w.show()
+ sys.exit(app.exec_())
diff --git a/Widgets/ICPage.py b/Widgets/ICPage.py
new file mode 100644
index 0000000..64fa2d3
--- /dev/null
+++ b/Widgets/ICPage.py
@@ -0,0 +1,353 @@
+"""
+ Copyright 2023 Suchinton Chakravarty
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import os
+import sys
+from PyQt5 import uic, QtCore, QtWidgets
+from PyQt5.QtWidgets import QApplication
+from PyQt5.QtWidgets import QSlider, QLCDNumber, QPushButton
+from PyQt5.QtGui import QIcon, QPixmap, QPainter
+import time
+from PyQt5.QtCore import QThread
+
+current_dir = os.path.dirname(os.path.abspath(__file__))
+
+# ========================================
+
+sys.path.append(os.path.dirname(current_dir))
+
+from extras.FeedKuksa import FeedKuksa
+
+Form, Base = uic.loadUiType(os.path.join(current_dir, "../ui/IC.ui"))
+
+# ========================================
+
+
+class IC_Paths():
+ def __init__(self):
+ self.speed = "Vehicle.Speed"
+ self.engineRPM = "Vehicle.Powertrain.CombustionEngine.Speed"
+ self.leftIndicator = "Vehicle.Body.Lights.DirectionIndicator.Left.IsSignaling"
+ self.rightIndicator = "Vehicle.Body.Lights.DirectionIndicator.Right.IsSignaling"
+ self.hazard = "Vehicle.Body.Lights.Hazard.IsSignaling"
+ self.fuelLevel = "Vehicle.Powertrain.FuelSystem.Level"
+ self.coolantTemp = "Vehicle.Powertrain.CombustionEngine.ECT"
+ self.selectedGear = "Vehicle.Powertrain.Transmission.SelectedGear"
+
+
+class ICWidget(Base, Form):
+ def __init__(self, parent=None):
+ super(self.__class__, self).__init__(parent)
+ self.setupUi(self)
+
+ self.IC = IC_Paths()
+
+ self.feed_kuksa = FeedKuksa()
+ self.feed_kuksa.start()
+
+ # # load the stylesheet
+ # theme = open(os.path.join(current_dir, "../ui/styles/Tron/ICPage.qss"), 'r')
+ # self.setStyleSheet(theme.read())
+ # theme.close()
+
+ self.scriptBtn = self.findChild(QPushButton, "scriptBtn")
+
+ self.Speed_slider = self.findChild(QSlider, "Speed_slider")
+ self.Speed_monitor = self.findChild(QLCDNumber, "Speed_monitor")
+ self.RPM_slider = self.findChild(QSlider, "RPM_slider")
+ self.RPM_monitor = self.findChild(QLCDNumber, "RPM_monitor")
+
+ self.coolantTemp_slider = self.findChild(QSlider, "coolantTemp_slider")
+ self.fuelLevel_slider = self.findChild(QSlider, "fuelLevel_slider")
+
+ self.accelerationBtn = self.findChild(QPushButton, "accelerationBtn")
+
+ self.leftIndicatorBtn = self.findChild(QPushButton, "leftIndicatorBtn")
+ self.rightIndicatorBtn = self.findChild(QPushButton, "rightIndicatorBtn")
+ self.hazardBtn = self.findChild(QPushButton, "hazardBtn")
+
+ buttons = [self.parkBtn,
+ self.reverseBtn,
+ self.neutralBtn,
+ self.driveBtn]
+
+ # group for the buttons for mutual exclusion
+ self.driveGroupBtns = QtWidgets.QButtonGroup(self)
+ self.driveGroupBtns.setExclusive(True)
+
+ for button in buttons:
+ self.driveGroupBtns.addButton(button)
+
+ self.driveGroupBtns.buttonClicked.connect(self.driveBtnClicked)
+
+ self.scriptBtn.clicked.connect(self.scriptBtnClicked)
+
+ self.Speed_slider.valueChanged.connect(self.update_Speed_monitor)
+ self.Speed_slider.setMinimum(0)
+ self.Speed_slider.setMaximum(240)
+
+ self.RPM_slider.valueChanged.connect(self.update_RPM_monitor)
+ self.RPM_slider.setMinimum(0)
+ self.RPM_slider.setMaximum(8000)
+
+ self.coolantTemp_slider.valueChanged.connect(
+ self.update_coolantTemp_monitor)
+ self.fuelLevel_slider.valueChanged.connect(
+ self.update_fuelLevel_monitor)
+
+ self.accelerationBtn.pressed.connect(self.accelerationBtnPressed)
+ self.accelerationBtn.released.connect(self.accelerationBtnReleased)
+
+ # make both buttons checkable
+ self.leftIndicatorBtn.setCheckable(True)
+ self.rightIndicatorBtn.setCheckable(True)
+ self.hazardBtn.setCheckable(True)
+
+ self.leftIndicatorBtn.clicked.connect(self.leftIndicatorBtnClicked)
+ self.rightIndicatorBtn.clicked.connect(self.rightIndicatorBtnClicked)
+ self.hazardBtn.clicked.connect(self.hazardBtnClicked)
+
+ def scriptBtnClicked(self):
+ if self.scriptBtn.isChecked():
+ ICScript.start_script()
+
+ if not self.ScriptBtn.isChecked():
+ ICScript.stop_script()
+
+ def update_Speed_monitor(self):
+ speed = int(self.Speed_slider.value())
+ self.Speed_monitor.display(speed)
+ self.feed_kuksa.send_values(self.IC.speed, str(speed), 'value')
+
+ def update_RPM_monitor(self):
+ rpm = int(self.RPM_slider.value())
+ self.RPM_monitor.display(rpm)
+ self.feed_kuksa.send_values(self.IC.engineRPM, str(rpm), 'value')
+
+ def update_coolantTemp_monitor(self):
+ coolantTemp = int(self.coolantTemp_slider.value())
+ self.feed_kuksa.send_values(self.IC.coolantTemp, str(coolantTemp), 'value')
+
+ def update_fuelLevel_monitor(self):
+ fuelLevel = int(self.fuelLevel_slider.value())
+ self.feed_kuksa.send_values(self.IC.fuelLevel, str(fuelLevel))
+
+ def hazardBtnClicked(self):
+ hazardIcon = QPixmap(":/Images/Images/hazard.png")
+ if self.hazardBtn.isChecked():
+ painter = QPainter(hazardIcon)
+ painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
+ painter.fillRect(hazardIcon.rect(), QtCore.Qt.yellow)
+ painter.end()
+ self.hazardBtn.setIcon(QIcon(hazardIcon))
+
+ self.leftIndicatorBtn.setChecked(True)
+ self.rightIndicatorBtn.setChecked(True)
+ self.feed_kuksa.send_values(self.IC.leftIndicator, "true")
+ self.feed_kuksa.send_values(self.IC.rightIndicator, "true")
+ self.feed_kuksa.send_values(self.IC.hazard, "true")
+ else:
+ painter = QPainter(hazardIcon)
+ painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
+ painter.fillRect(hazardIcon.rect(), QtCore.Qt.black)
+ painter.end()
+ self.hazardBtn.setIcon(QIcon(hazardIcon))
+
+ self.leftIndicatorBtn.setChecked(False)
+ self.rightIndicatorBtn.setChecked(False)
+ self.feed_kuksa.send_values(self.IC.leftIndicator, "false")
+ self.feed_kuksa.send_values(self.IC.rightIndicator, "false")
+ self.feed_kuksa.send_values(self.IC.hazard, "false")
+
+ def leftIndicatorBtnClicked(self):
+ leftIndicatorIcon = QPixmap(":/Images/Images/left.png")
+ if self.leftIndicatorBtn.isChecked():
+
+ painter = QPainter(leftIndicatorIcon)
+ painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
+ painter.fillRect(leftIndicatorIcon.rect(), QtCore.Qt.green)
+ painter.end()
+
+ self.leftIndicatorBtn.setIcon(QIcon(leftIndicatorIcon))
+ self.feed_kuksa.send_values(self.IC.leftIndicator, "true")
+ else:
+
+ painter = QPainter(leftIndicatorIcon)
+ painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
+ painter.fillRect(leftIndicatorIcon.rect(), QtCore.Qt.black)
+ painter.end()
+
+ self.leftIndicatorBtn.setIcon(QIcon(leftIndicatorIcon))
+ self.feed_kuksa.send_values(self.IC.leftIndicator, "false")
+
+ def rightIndicatorBtnClicked(self):
+ rightIndicatorIcon = QPixmap(":/Images/Images/right.png")
+ if self.rightIndicatorBtn.isChecked():
+
+ painter = QPainter(rightIndicatorIcon)
+ painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
+ painter.fillRect(rightIndicatorIcon.rect(), QtCore.Qt.green)
+ painter.end()
+ self.rightIndicatorBtn.setIcon(QIcon(rightIndicatorIcon))
+ self.feed_kuksa.send_values(self.IC.rightIndicator, "true")
+ else:
+
+ painter = QPainter(rightIndicatorIcon)
+ painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
+ painter.fillRect(rightIndicatorIcon.rect(), QtCore.Qt.black)
+ painter.end()
+ self.rightIndicatorBtn.setIcon(QIcon(rightIndicatorIcon))
+ self.feed_kuksa.send_values(self.IC.rightIndicator, "false")
+
+ def accelerationBtnPressed(self):
+ self.startTime = QtCore.QTime.currentTime()
+ self.acceleration_timer = QtCore.QTimer()
+ self.acceleration_timer.timeout.connect(
+ lambda: self.updateSpeedAndEngineRpm("Accelerate"))
+ self.acceleration_timer.start(100)
+
+ def accelerationBtnReleased(self):
+ if self.Speed_slider.value() <= 0:
+ self.acceleration_timer.stop()
+ else:
+ self.acceleration_timer.timeout.connect(
+ lambda: self.updateSpeedAndEngineRpm("Decelerate"))
+ self.acceleration_timer.start(100)
+
+ def updateSpeedAndEngineRpm(self, action, acceleration=(60/5)):
+ if action == "Accelerate":
+ pass
+ elif action == "Decelerate":
+ acceleration = -acceleration
+
+ currentTime = QtCore.QTime.currentTime()
+ duration = self.startTime.msecsTo(currentTime)
+ self.current_speed = AccelerationFns.calculate_speed(
+ duration, acceleration)
+ self.current_rpm = AccelerationFns.calculate_engine_rpm(
+ self.current_speed)
+
+ if self.current_speed <= 0:
+ self.current_speed = 0
+ self.current_rpm = 0
+ self.acceleration_timer.stop()
+
+ if self.current_speed >= 240:
+ self.current_speed = 240
+ self.current_rpm = 0
+ self.acceleration_timer.stop()
+
+ self.Speed_slider.setValue(self.current_speed)
+ self.RPM_slider.setValue(self.current_rpm)
+
+ self.update_Speed_monitor()
+ self.update_RPM_monitor()
+
+ def driveBtnClicked(self):
+ # // Selected Gear output = > 0 = Neutral, 1/2/.. = Forward, -1/.. = Reverse, 126 = Park, 127 = Drive
+ # #859287 ; /* light green */
+
+ if self.driveGroupBtns.checkedButton() == self.driveBtn:
+ self.accelerationBtn.setEnabled(True)
+ self.Speed_slider.setEnabled(True)
+ self.RPM_slider.setEnabled(True)
+ self.feed_kuksa.send_values(self.IC.selectedGear, "127")
+
+ if self.driveGroupBtns.checkedButton() == self.parkBtn:
+ self.accelerationBtn.setEnabled(False)
+ self.Speed_slider.setEnabled(False)
+ self.RPM_slider.setEnabled(False)
+ self.feed_kuksa.send_values(self.IC.selectedGear, "126")
+
+ if self.driveGroupBtns.checkedButton() == self.reverseBtn:
+ self.accelerationBtn.setEnabled(True)
+ self.Speed_slider.setEnabled(True)
+ self.RPM_slider.setEnabled(True)
+ self.feed_kuksa.send_values(self.IC.selectedGear, "-1")
+
+ if self.driveGroupBtns.checkedButton() == self.neutralBtn:
+ self.accelerationBtn.setEnabled(False)
+ self.Speed_slider.setEnabled(False)
+ self.RPM_slider.setEnabled(True)
+ self.feed_kuksa.send_values(self.IC.selectedGear, "0")
+
+
+class AccelerationFns():
+ def calculate_speed(time, acceleration) -> int:
+ # acceleration = 60 / 5 # acceleration from 0 to 60 in 5 seconds
+ time = time / 1000 # convert milliseconds to seconds
+ speed = acceleration * time # calculate speed
+ return int(speed)
+
+ def calculate_engine_rpm(speed) -> int:
+ wheel_diameter = 0.48 # in meters
+ wheel_circumference = wheel_diameter * 3.14 # in meters
+
+ # Adjust the gear ratios to match the desired speed and rpm
+ gear_ratios = [3.36, 2.10, 1.48, 1.16, 0.95, 0.75]
+ speed = speed * 1000 / 3600 # Convert speed from km/h to m/s
+ wheel_rps = speed / wheel_circumference
+
+ current_gear = None
+ for i in range(len(gear_ratios)):
+ if wheel_rps * gear_ratios[i] < 8000 / 60:
+ current_gear = i + 1
+ break
+
+ # If no gear is found, use the highest gear
+ if current_gear is None:
+ current_gear = len(gear_ratios)
+
+ engine_rpm = wheel_rps * gear_ratios[current_gear - 1] * 60
+
+ return int(engine_rpm)
+
+
+class ICScript(ICWidget):
+ def start_script(self):
+ ICWidget.reset()
+
+ # disable all widgets in the scroll area
+ for widget in ICWidget.scrollAreaWidgetContents.children():
+ widget.setEnabled(False)
+
+ rates = [(60/5), (60/4), (60/3)]
+
+ # start assigning values to the speed and rpm sliders and send them to the IC do this in a loop for each rate
+ for rate in rates:
+ ICWidget.accelerationBtnPressed()
+ ICWidget.acceleration_timer.timeout.connect(
+ lambda: ICWidget.updateSpeedAndEngineRpm("Accelerate"), rate)
+ ICWidget.acceleration_timer.start(100)
+ time.sleep(5)
+ ICWidget.accelerationBtnReleased()
+ ICWidget.acceleration_timer.timeout.connect(
+ lambda: ICWidget.updateSpeedAndEngineRpm("Decelerate"), rate)
+ ICWidget.acceleration_timer.start(100)
+ time.sleep(5)
+
+ def stop_script(self):
+ ICWidget.reset()
+
+ # enable all widgets in the scroll area
+ for widget in ICWidget.scrollAreaWidgetContents.children():
+ widget.setEnabled(True)
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ w = ICWidget()
+ w.show()
+ sys.exit(app.exec_()) \ No newline at end of file
diff --git a/Widgets/NavPage.py b/Widgets/NavPage.py
new file mode 100644
index 0000000..61d63aa
--- /dev/null
+++ b/Widgets/NavPage.py
@@ -0,0 +1,179 @@
+"""
+ Copyright 2023 Suchinton Chakravarty
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import extras.Kuksa_Instance as kuksa_instance
+import os
+import sys
+import requests
+import folium
+from PyQt5 import uic, QtCore
+
+from PyQt5.QtCore import QUrl, QObject
+from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot
+from PyQt5.QtWebEngineWidgets import QWebEngineView
+from PyQt5.QtWidgets import QApplication, QListWidget, QLineEdit, QCompleter, QListView
+from PyQt5.QtGui import QPainterPath, QRegion, QStandardItemModel, QStandardItem
+from PyQt5.QtCore import QRectF
+from PyQt5.QtCore import QTimer
+from PyQt5.QtCore import Qt
+
+current_dir = os.path.dirname(os.path.abspath(__file__))
+
+# ========================================
+
+sys.path.append(os.path.dirname(current_dir))
+
+
+Form, Base = uic.loadUiType(os.path.join(current_dir, "../ui/Nav.ui"))
+
+# ========================================
+
+
+class Nav_Paths():
+ def __init__(self):
+ self.currLat = "Vehicle.CurrentLocation.Latitude"
+ self.currLng = "Vehicle.CurrentLocation.Longitude"
+ self.desLat = "Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Latitude"
+ self.desLng = "Vehicle.Cabin.Infotainment.Navigation.DestinationSet.Longitude"
+
+
+class NavWidget(Base, Form):
+ suggestionsUpdated = pyqtSignal(list)
+
+ def __init__(self, parent=None):
+ super(self.__class__, self).__init__(parent)
+ self.setupUi(self)
+
+ self.From_address = self.findChild(QLineEdit, "From_address")
+ self.To_address = self.findChild(QLineEdit, "To_address")
+ self.map_view = self.findChild(QWebEngineView, "map_view")
+
+ path = QPainterPath()
+ path.addRoundedRect(QRectF(self.map_view.rect()), 10, 10)
+ mask = QRegion(path.toFillPolygon().toPolygon())
+ self.map_view.setMask(mask)
+
+ self.searching_thread = QThread()
+
+ self.suggested_addresses = QStandardItemModel()
+ completer = CustomCompleter(self.suggested_addresses)
+ self.From_address.setCompleter(completer)
+
+ self.From_address.textChanged.connect(self.delayed_search)
+ # self.To_address.textChanged.connect(lambda: self.start_search(self.To_address.text()))
+
+ self.suggestionsUpdated.connect(self.update_suggestions)
+
+ self.timer = QTimer()
+ self.timer.setInterval(500) # Adjust delay as needed
+ self.timer.setSingleShot(True)
+ self.timer.timeout.connect(self.start_search)
+
+ def delayed_search(self):
+ self.timer.start()
+
+ def start_search(self):
+ query = self.From_address.text().strip()
+ if query:
+ self.searching_thread.run = lambda: self.search_address(query)
+ self.searching_thread.start()
+
+ def search_address(self, query):
+ options = self.fetch_address_suggestions(query)
+ self.suggestionsUpdated.emit(options)
+
+ def fetch_address_suggestions(self, query):
+ url = f"https://nominatim.openstreetmap.org/search?format=json&limit=5&q={requests.utils.quote(query)}"
+ response = requests.get(url)
+ if response.status_code == 200:
+ return response.json()
+ else:
+ return []
+
+ def show_suggestions(self, options):
+ current_query = self.From_address.text().strip()
+ if current_query:
+ self.suggested_addresses.clear()
+ for suggestion in options:
+ address = suggestion.get("display_name", "")
+ self.suggested_addresses.appendRow(QStandardItem(address))
+
+ @pyqtSlot(list)
+ def update_suggestions(self, options):
+ self.show_suggestions(options)
+
+ def select_suggestion(self, item):
+ address = item.text()
+ # self.address_input.setText(address)
+ coordinates = self.show_location(address)
+
+ def show_location(self, query):
+ url = f"https://nominatim.openstreetmap.org/search?format=json&limit=1&q={requests.utils.quote(query)}"
+ response = requests.get(url)
+ if response.status_code == 200:
+ data = response.json()
+ if data:
+ lat = data[0]["lat"]
+ lon = data[0]["lon"]
+ location = [float(lat), float(lon)]
+
+ self.update_map(location)
+ return location
+
+ def update_map(self, location):
+ map_html = self.create_map_html(location)
+ file_path = os.path.abspath("map.html")
+ with open(file_path, "w") as f:
+ f.write(map_html)
+ self.map_view.load(QUrl.fromLocalFile(file_path))
+
+ def create_map_html(self, location):
+ map = folium.Map(location=location, zoom_start=15)
+ marker = folium.Marker(location=location)
+ marker.add_to(map)
+ return map._repr_html_()
+
+
+class CustomCompleter(QCompleter):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setPopup(QListView())
+ self.popup().setStyleSheet("""
+ QListView {
+ background-color: #131313 ; /* black */
+ color: #fff;
+ border: 1px solid #4BD7D6 ; /* light blue */
+ border-radius: 2px;
+ padding: 10px;
+ margin: 2px;
+ }
+ """)
+
+ self.popup().setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
+ self.popup().setUniformItemSizes(True)
+ self.popup().setWordWrap(True)
+ self.popup().setSpacing(1)
+ self.popup().setFrameShape(QListView.NoFrame)
+ self.popup().setFrameShadow(QListView.Plain)
+ self.popup().setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+
+
+if __name__ == '__main__':
+ import sys
+ app = QApplication(sys.argv)
+ w = NavWidget()
+ w.show()
+ sys.exit(app.exec_())
diff --git a/Widgets/SteeringCtrlPage.py b/Widgets/SteeringCtrlPage.py
new file mode 100644
index 0000000..61271c2
--- /dev/null
+++ b/Widgets/SteeringCtrlPage.py
@@ -0,0 +1,166 @@
+"""
+ Copyright 2023 Suchinton Chakravarty
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+"""
+
+import os
+import sys
+from PyQt5 import uic
+from PyQt5.QtWidgets import QApplication, QButtonGroup
+from PyQt5.QtCore import QThread
+
+import time
+
+current_dir = os.path.dirname(os.path.abspath(__file__))
+
+# ========================================
+
+sys.path.append(os.path.dirname(current_dir))
+
+import extras.Kuksa_Instance as kuksa_instance
+
+Form, Base = uic.loadUiType(os.path.join(current_dir, "../ui/SteeringControls.ui"))
+
+# ========================================
+
+class Steering_Paths():
+ def __init__(self):
+ self.VolumeUp = "Vehicle.Cabin.SteeringWheel.Switches.VolumeUp"
+ self.VolumeDown = "Vehicle.Cabin.SteeringWheel.Switches.VolumeDown"
+ self.VolumeMute = "Vehicle.Cabin.SteeringWheel.Switches.VolumeMute"
+
+ self.Mode = "Vehicle.Cabin.SteeringWheel.Switches.Mode"
+
+ self.NextTrack = "Vehicle.Cabin.SteeringWheel.Switches.Next"
+ self.PreviousTrack = "Vehicle.Cabin.SteeringWheel.Switches.Previous"
+
+ self.Info = "Vehicle.Cabin.SteeringWheel.Switches.Info"
+
+ self.PhoneCall = "Vehicle.Cabin.SteeringWheel.Switches.PhoneCall"
+ self.PhoneHangup = "Vehicle.Cabin.SteeringWheel.Switches.PhoneHangup"
+ self.Voice = "Vehicle.Cabin.SteeringWheel.Switches.Voice"
+ self.LaneDeparture = "Vehicle.Cabin.SteeringWheel.Switches.LaneDepartureWarning"
+
+ self.Horn = "Vehicle.Cabin.SteeringWheel.Switches.Horn"
+
+ self.CruiseEnable = "Vehicle.Cabin.SteeringWheel.Switches.CruiseEnable"
+ self.CruseSet = "Vehicle.Cabin.SteeringWheel.Switches.CruiseSet"
+ self.CruiseResume = "Vehicle.Cabin.SteeringWheel.Switches.CruiseResume"
+ self.CruiseCancel = "Vehicle.Cabin.SteeringWheel.Switches.CruiseCancel"
+
+ self.CruiseLimit = "Vehicle.Cabin.SteeringWheel.Switches.CruiseLimit"
+ self.CruiseDistance = "Vehicle.Cabin.SteeringWheel.Switches.CruiseDistance"
+
+class SteeringCtrlWidget(Base, Form):
+ def __init__(self, parent=None):
+ super(self.__class__, self).__init__(parent)
+ self.setupUi(self)
+
+ self.Steering = Steering_Paths()
+ self.feed_kuksa = FeedKuksa()
+ self.add_buttons()
+
+ def add_buttons(self):
+
+ # Define button groups and actions
+ LeftControlsBtns = [self.VolUpBtn,
+ self.VolDownBtn,
+ self.ModeBtn,
+ self.VolMuteBtn,
+ self.NextTrackBtn,
+ self.PrevTrackBtn,
+ self.InfoBtn]
+
+
+ PhoneBtns = [self.PhoneCallBtn, self.PhoneHangupBtn]
+ ExtraContolsBtns = [self.VoiceBtn, self.LaneDepartureBtn]
+
+ RightControlsBtns = [self.CruiseEnableBtn,
+ self.CruiseSetBtn,
+ self.CruiseResumeBtn,
+ self.CruiseCancelBtn,
+ self.CruiseLimitBtn,
+ self.CruiseDistanceBtn]
+
+ self.LeftControlsBtnsGroup = QButtonGroup()
+ self.PhoneBtnsGroup = QButtonGroup()
+ self.ExtraContolsBtnsGroup = QButtonGroup()
+ self.RiqhtControlsBtnsGroup = QButtonGroup()
+
+ for btn in LeftControlsBtns:
+ self.LeftControlsBtnsGroup.addButton(btn)
+
+ for btn in PhoneBtns:
+ self.PhoneBtnsGroup.addButton(btn)
+
+
+ for btn in RightControlsBtns:
+ self.RiqhtControlsBtnsGroup.addButton(btn)
+
+ self.LeftControlsBtnsGroup.buttonClicked.connect(self.left_controls_clicked)
+ self.RiqhtControlsBtnsGroup.buttonClicked.connect(self.right_controls_clicked)
+
+ self.HornBtn.clicked.connect(self.horn_clicked)
+
+ def left_controls_clicked(self):
+ print("Left controls clicked")
+
+ def right_controls_clicked(self):
+ print("Right controls clicked")
+
+ def horn_clicked(self):
+ print("Horn clicked")
+
+class FeedKuksa(QThread):
+ def __init__(self, parent=None):
+ QThread.__init__(self,parent)
+ self.stop_flag = False
+ self.set_instance()
+
+ def run(self):
+ print("Starting thread")
+ self.set_instance()
+ while not self.stop_flag:
+ self.send_values()
+
+ def stop(self):
+ self.stop_flag = True
+ print("Stopping thread")
+
+ def set_instance(self):
+ self.kuksa = kuksa_instance.KuksaClientSingleton.get_instance()
+ self.client = self.kuksa.get_client()
+
+ def send_values(self, Path=None, Value=None, Attribute=None):
+ if self.client is not None:
+ if self.client.checkConnection() is True:
+
+ if Attribute is not None:
+ self.client.setValue(Path, Value, Attribute)
+ else:
+ self.client.setValue(Path, Value)
+ else:
+ print("Could not connect to Kuksa")
+ self.set_instance()
+ else:
+ print("Kuksa client is None, try reconnecting")
+ time.sleep(2)
+ self.set_instance()
+
+if __name__ == '__main__':
+ import sys
+ app = QApplication(sys.argv)
+ w = SteeringCtrlWidget()
+ w.show()
+ sys.exit(app.exec_()) \ No newline at end of file
diff --git a/Widgets/settings.py b/Widgets/settings.py
new file mode 100644
index 0000000..11800a2
--- /dev/null
+++ b/Widgets/settings.py
@@ -0,0 +1,116 @@
+# Copyright 2023 Suchinton Chakravarty
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import extras.Kuksa_Instance as kuksa_instance
+import os
+import sys
+import time
+from PyQt5 import uic
+from PyQt5.QtWidgets import QApplication, QLineEdit, QPushButton, QLabel, QCheckBox
+
+current_dir = os.path.dirname(os.path.abspath(__file__))
+
+# ========================================
+
+sys.path.append(os.path.dirname(current_dir))
+
+
+Form, Base = uic.loadUiType(os.path.join(
+ current_dir, "../ui/Settings_Window.ui"))
+
+# ========================================
+
+
+class settings(Base, Form):
+ def __init__(self, parent=None):
+ super(self.__class__, self).__init__(parent)
+ self.setupUi(self)
+
+ self.SSLToggle = self.findChild(QCheckBox, "SSLToggle")
+
+ self.connectionStatus = self.findChild(QLabel, "connectionStatus")
+ self.connectionLogo = self.findChild(QLabel, "connectionLogo")
+
+ self.IPAddrInput = self.findChild(QLineEdit, "IPAddrInput")
+ self.tokenPathInput = self.findChild(QLineEdit, "tokenPathInput")
+
+ self.reconnectBtn = self.findChild(QPushButton, "reconnectBtn")
+ self.refreshBtn = self.findChild(QPushButton, "refreshBtn")
+ self.startClientBtn = self.findChild(QPushButton, "startClientBtn")
+
+ self.startClientBtn.clicked.connect(self.set_instance)
+ self.reconnectBtn.clicked.connect(self.reconnectClient)
+ self.refreshBtn.clicked.connect(self.refreshStatus)
+
+ self.refreshStatus()
+ self.show()
+
+ def set_instance(self):
+ self.kuksa = kuksa_instance.KuksaClientSingleton.get_instance()
+ self.client = self.kuksa.get_client()
+
+ self.config = self.kuksa.get_config()
+ self.token = self.kuksa.get_token()
+
+ self.IPAddrInput.setText(self.config["ip"])
+ self.SSLToggle.setChecked(self.config["insecure"])
+ self.tokenPathInput.setText(self.token)
+
+ time.sleep(2)
+
+ if (self.client is None):
+ self.connectionStatus.setText('Not Connected')
+ self.connectionLogo.setStyleSheet("background-color: red")
+
+ self.refreshStatus()
+
+ def refreshStatus(self):
+ try:
+ if (self.client is None):
+ self.connectionStatus.setText('Not Connected')
+ self.connectionLogo.setStyleSheet("background-color: red")
+ return None
+
+ if (self.client.checkConnection() == True):
+ self.connectionStatus.setText('Connected')
+ self.connectionLogo.setPixmap(":/icons/feather/check-circle.svg")
+ self.connectionLogo.setStyleSheet("background-color: green")
+ self.client.start()
+ return True
+
+ if (self.client.checkConnection() == False):
+ self.client.stop()
+ self.connectionStatus.setText('Disconnected')
+ self.connectionLogo.setStyleSheet("background-color: yellow")
+ return False
+ except:
+ pass
+
+ def reconnectClient(self):
+ try:
+ self.config["ip"] = self.IPAddrInput.text()
+ self.config["insecure"] = self.SSLToggle.isChecked()
+ self.token = self.tokenPathInput.text()
+ self.client = self.kuksa.reconnect_client(self.config, self.token)
+ self.client.start()
+ self.refreshStatus()
+ except Exception as e:
+ print(e)
+
+if __name__ == '__main__':
+ import sys
+ app = QApplication(sys.argv)
+ w = settings()
+ w.show()
+ sys.exit(app.exec_())