Added 2016-2017 rover code, and a ROB assignment

This commit is contained in:
2017-11-29 12:46:14 -08:00
parent 4566d98b5f
commit cb3ce5dafc
1414 changed files with 5874 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
"""
This file contains the data view page sub-class
"""
#####################################
# Imports
#####################################
# Python native imports
from PyQt5 import QtCore, QtWidgets, QtGui
import logging
from Framework.MiniBoardIOCore import write_drive_motor_power, read_drive_motor_power, write_pause, write_arm_motors, write_soil_measure, write_soil_sensor_recv, read_soil_measurements, read_gps_position, read_gps_track
from datetime import datetime
from time import sleep
#####################################
# Global Variables
#####################################
JOYSTICK_AXIS_MIN = -144
JOYSTICK_AXIS_MAX = 144
#####################################
# About Class Definition
#####################################
class DataView(QtCore.QObject):
send_miniboard_control_packet = QtCore.pyqtSignal(list)
get_data_from_probe_signal = QtCore.pyqtSignal(int)
def __init__(self, main_window):
super(DataView, self).__init__()
# ########## Reference to top level window ##########
self.main_window = main_window # type: QtWidgets.QMainWindow
self.miniboard_class = main_window.miniboard_class
# ########## Get the settings instance ##########
self.settings = QtCore.QSettings()
# ########## Get the Pick And Plate instance of the logger ##########
self.logger = logging.getLogger("RoverBaseStation")
# ########## References to GUI Elements ##########
self.left_y_lb = self.main_window.left_y_lb
self.right_y_lb = self.main_window.right_y_lb
self.pause_mode = self.main_window.pause_mode
self.drive_mode = self.main_window.drive_mode
self.left_motor_power = self.main_window.left_motor
self.right_motor_power = self.main_window.right_motor
self.battery_voltage = self.main_window.battery_voltage
self.base_power = self.main_window.base
self.bicep_power = self.main_window.bicep
self.forearm_power = self.main_window.forearm
self.pitch_power = self.main_window.pitch
self.wrist_power = self.main_window.wrist
self.temp_label = self.main_window.temp_label
self.moisture_label = self.main_window.moisture_label
self.salinity_label = self.main_window.salinity_label
self.get_data_button = self.main_window.science_data_button
self.get_gps_button = self.main_window.gps_data_button
self.last_contact_label = self.main_window.last_contact_label
self.lat_label = self.main_window.gps_lat_label
self.lon_label = self.main_window.gps_lon_label
self.alt_label = self.main_window.gps_altitude_label
self.gps_heading_label = self.main_window.gps_heading_label
self.gps_speed_label = self.main_window.gps_speed_label
# self.time_label = self.main_window.time_label
# self.voltage_label = self.main_window.battery_voltage_label
# ########## Class Variables ##########
self.controller_states = None
self.sensor_read_timer = QtCore.QTimer()
self.sensor_read_timer.setSingleShot(True)
self.gps_valid = None
self.lat = None
self.lon = None
self.alt = None
self.gps_track_valid = None
self.gps_speed = None
self.gps_heading = None
# ########## Make signal/slot connections ##########
self.__connect_signals_to_slots()
# ########## Set flags ##########
self.xbox_connected = False
self.frysky_connected = False
def __connect_signals_to_slots(self):
# These are incorrect as they don't show the scaled versions
# self.main_window.frsky_controller_class.controller_update_ready_signal.connect(self.on_controller_update_ready__slot)
# self.main_window.frsky_controller_class.controller_connection_aquired.connect(self.frysky_connected__slot)
# self.main_window.xbox_controller_class.controller_connection_aquired.connect(self.xbox_connected__slot)
# the data signals pass dictionaries to our slots after read requested
self.main_window.miniboard_class.data_drive_motor_power.connect(self.__update_drive_power)
self.main_window.miniboard_class.data_battery_voltage.connect(self.__update_battery_voltage)
self.main_window.miniboard_class.data_arm_motors.connect(self.__update_arm_power)
self.get_data_button.clicked.connect(self.on_get_science_data_button_pressed__slot)
self.get_data_from_probe_signal.connect(self.sensor_read_timer.start)
self.sensor_read_timer.timeout.connect(self.on_science_measure_response_received__slot)
self.main_window.miniboard_class.data_soil_measurements.connect(self.on_science_data_received__slot)
self.main_window.miniboard_class.data_gps_position.connect(self.on_gps_coordinates_updated__slot)
self.main_window.miniboard_class.data_gps_track.connect(self.on_gps_track_updated__slot)
self.send_miniboard_control_packet.connect(self.main_window.miniboard_class.append)
self.main_window.miniboard_class.message_received_signal.connect(self.on_message_received__slot)
self.get_gps_button.clicked.connect(self.on_get_gps_button_pressed__slot)
def __update_drive_percentages(self):
if self.frysky_connected:
left_percentage = round((self.controller_states["left_stick_y_axis"] / JOYSTICK_AXIS_MAX * 100), 2)
right_percentage = round((self.controller_states["right_stick_y_axis"] / JOYSTICK_AXIS_MAX * 100), 2)
self.left_y_lb.setText(str(left_percentage) + "%")
self.right_y_lb.setText(str(right_percentage) + "%")
def __update_modes(self):
pause_mode = self.controller_states["sf_state"]
drive_mode = self.controller_states["se_state"]
self.pause_mode.setText(str(pause_mode))
self.drive_mode.setText(str(drive_mode))
def __update_drive_power(self, power_dict):
self.left_motor_power.setText("F:%3d M:%3d B:%3d" % (
power_dict["l_f_drive"],
power_dict["l_m_drive"],
power_dict["l_b_drive"],
))
self.right_motor_power.setText("F:%3d M:%3d B:%3d" % (
power_dict["r_f_drive"],
power_dict["r_m_drive"],
power_dict["r_b_drive"],
))
def __update_battery_voltage(self, battery_dict):
battery_visually_accurate = int(battery_dict["battery_voltage"]) / 1000
self.battery_voltage.setText("%0.2f V" % battery_visually_accurate)
def __update_arm_power(self, power_dict):
self.base_power.setText("%d" % power_dict["arm_motor_1"])
self.bicep_power.setText("%d" % power_dict["arm_motor_2"])
self.forearm_power.setText("%d" % power_dict["arm_motor_3"])
self.pitch_power.setText("%d" % power_dict["arm_motor_4"])
self.wrist_power.setText("%d" % power_dict["arm_motor_5"])
def on_update_other_gui_elements__slot(self, voltage, time):
# self.time_label.setText(time)
# self.voltage_label.setText(voltage)
pass
def on_controller_update_ready__slot(self, controller_states):
self.controller_states = controller_states
self.__update_drive_percentages()
self.__update_modes()
def xbox_connected__slot(self, connected):
self.xbox_connected = connected
def frysky_connected__slot(self, connected):
self.frysky_connected = connected
def on_get_science_data_button_pressed__slot(self):
self.temp_label.setText("Reading From Sensor")
self.moisture_label.setText("Reading From Sensor")
self.salinity_label.setText("Reading From Sensor")
write_soil_measure(self.send_miniboard_control_packet, 1) # Request data
self.get_data_from_probe_signal.emit(5000)
def on_science_measure_response_received__slot(self):
read_soil_measurements(self.send_miniboard_control_packet)
def on_science_data_received__slot(self, sdict):
self.temp_label.setText(str(sdict["temperature"]/1000) + " °C")
self.moisture_label.setText(str(sdict["moisture"]/10) + " %")
self.salinity_label.setText(str(sdict["salinity"]/1000) + " g/L")
# self.logger.debug(sdict)
def on_gps_coordinates_updated__slot(self, sdict):
self.gps_valid = sdict["gps_pos_valid"]
self.lat = "{0:.6f}".format((sdict["latitude"] * 10 ** -5) / 60)
self.lon = "{0:.6f}".format((sdict["longitude"] * 10 ** -5) / 60)
self.alt = "{0:.6f}".format((sdict["altitude"] / 10))
if self.gps_valid:
self.lat_label.setText(str(self.lat))
self.lon_label.setText(str(self.lon))
self.alt_label.setText(str(self.alt))
else:
self.lat_label.setText("No GPS Lock")
self.lon_label.setText("No GPS Lock")
self.alt_label.setText("No GPS Lock")
def on_gps_track_updated__slot(self, sdict):
self.gps_track_valid = sdict["gps_track_valid"]
self.gps_speed = sdict["gps_speed"]
self.gps_heading = sdict["gps_heading"]
if self.gps_track_valid:
self.gps_speed_label.setText(str(self.gps_speed))
self.gps_heading_label(str(self.gps_heading))
else:
self.gps_speed_label.setText("No Movement")
self.gps_heading_label.setText("No Movement")
def on_get_gps_button_pressed__slot(self):
read_gps_position(self.send_miniboard_control_packet)
read_gps_track(self.send_miniboard_control_packet)
def on_message_received__slot(self):
self.last_contact_label.setText(datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
)

View File

@@ -0,0 +1,46 @@
"""
This file contains the interface core sub-class
This instantiates all the high level sub-classes for the interface
"""
#####################################
# Imports
#####################################
# Python native imports
from PyQt5 import QtCore, QtWidgets, QtWebEngine
# Custom imports
from Interface.LiveLogs.LiveLogsCore import LiveLogs
from PyQt5.QtQuick import QQuickView, QQuickItem
from PyQt5.QtCore import QUrl, QObject
from Interface.Map.MapCore import Map
from Interface.DataView.DataViewCore import DataView
#####################################
# Interface Class Definition
#####################################
class Interface(QtCore.QObject):
def __init__(self, main_window):
super(Interface, self).__init__()
# ########## Reference to top level window ##########
self.main_window = main_window # type: QtWidgets.QMainWindow
QtWebEngine.QtWebEngine.initialize()
# ########## Instantiations of sub-classes ##########
self.live_logs_class = LiveLogs(self.main_window)
# self.maps_class = Map(self.main_window)
self.data_view_class = DataView(self.main_window)
# ########## References to GUI Elements ##########
self.tab_widget = self.main_window.tab_widget # type: QtWidgets.QTabWidget
# ########## Set default interface parameters ##########
# Always open to first tab on launch
self.tab_widget.setCurrentIndex(0)
def qml_clicked__slot(self):
print("UI CLICKED")

View File

@@ -0,0 +1,95 @@
"""
This file contains the live logs page sub-class
"""
#####################################
# Imports
#####################################
# Python native imports
from PyQt5 import QtCore, QtWidgets, QtGui
import logging
#####################################
# Live Logs Class Definition
#####################################
class LiveLogs(QtCore.QThread):
text_ready_signal = QtCore.pyqtSignal(str)
send_packet = QtCore.pyqtSignal(list)
def __init__(self, main_window):
super(LiveLogs, self).__init__()
# ########## Reference to top level window ##########
self.main_window = main_window # type: QtWidgets.QMainWindow
# ########## Get the settings instance ##########
self.settings = QtCore.QSettings()
# ########## Get the Pick And Plate instance of the logger ##########
self.logger = logging.getLogger("RoverBaseStation")
# ########## Thread Flags ##########
self.run_thread_flag = True
self.open_log_file_flag = True
self.show_log_file_flag = True
# ########## References to GUI Elements ##########
self.live_log_tb = self.main_window.live_log_text_browser # type: QtWidgets.QTextBrowser
# ########## Class Variables ##########
self.log_file_path = None
self.log_file_reader = None
self.log_file_prev_mtime = 0
def run(self):
self.logger.debug("Live Logs Thread Starting...")
while self.run_thread_flag:
if self.open_log_file_flag:
self.__open_log_file()
self.open_log_file_flag = False
elif self.show_log_file_flag:
self.__show_updated_log_file()
self.msleep(250)
self.logger.debug("Live Logs Thread Stopping...")
# noinspection PyUnresolvedReferences
def connect_signals_to_slots__slot(self):
self.text_ready_signal.connect(self.live_log_tb.setText)
self.live_log_tb.textChanged.connect(self.__on_move_cursor_needed__slot)
self.main_window.kill_threads_signal.connect(self.on_kill_threads__slot)
def __open_log_file(self):
# Get the log file path
appdata_base_directory = self.settings.value("appdata_directory", type=str)
log_directory = appdata_base_directory + "/logs"
self.log_file_path = log_directory + "/log.txt"
# Open the class' reader for the file
self.log_file_reader = open(self.log_file_path, 'r')
def __show_updated_log_file(self):
log_browser_string = ""
# Seek back to the beginning of the file and read in the lines
self.log_file_reader.seek(0)
log_lines = self.log_file_reader.readlines()
# Go through line by line and only add lines that are selected to be shown via the checkboxes
for line in log_lines:
log_browser_string += line
# Display the text
self.text_ready_signal.emit(log_browser_string)
def __on_move_cursor_needed__slot(self):
# Move the cursor to the end when the text browser text updates. This essentially scrolls constantly.
self.live_log_tb.moveCursor(QtGui.QTextCursor.End)
def on_kill_threads__slot(self):
self.run_thread_flag = False

View File

@@ -0,0 +1,47 @@
"""
This file contains the map page sub-class
"""
#####################################
# Imports
#####################################
# Python native imports
from PyQt5 import QtCore, QtWidgets, QtGui, QtWebEngine
from PyQt5.QtCore import QUrl
import logging
#####################################
# About Class Definition
#####################################
class Map(QtCore.QObject):
def __init__(self, main_window):
super(Map, self).__init__()
# ########## Reference to top level window ##########
self.main_window = main_window # type: QtWidgets.QMainWindow
# ########## Get the settings instance ##########
self.settings = QtCore.QSettings()
# ########## Get the Pick And Plate instance of the logger ##########
self.logger = logging.getLogger("RoverBaseStation")
# ########## References to GUI Elements ##########
self.maps_view = self.main_window.map_label # type: QtWidgets.QLabel
# ########## Class Variables ##########
# self.map_view_pixmap = QtGui.QPixmap("Resources/Maps/mars_testing_site.png")
#
# self.maps_view.setPixmap(self.map_view_pixmap)
QtWebEngine.QtWebEngine.initialize()
self.map_view = self.main_window.leaflet_map # type: QtWidgets.QQuickView
self.map_view.setSource(QUrl("Resources/UI/map_view.qml"))
# ########## Set defaults on GUI Elements ##########
self.__load_settings()
def __load_settings(self):
self.logger.info("Map Interface Configured")