Skip to content

Commit 29fe9ea

Browse files
Imported code from X-17 Surface repo
1 parent 9f4c5fb commit 29fe9ea

File tree

23 files changed

+917
-564
lines changed

23 files changed

+917
-564
lines changed

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ For more current and past information view the links to the left or contact us a
3838

3939
## Project Description
4040

41-
X18 Surface contains the frontend and backend processes for the ROV. For the frontend, the surface (computer) connects the user interface with the cameras and gamepad, allowing us to display the camera steams, controls, and readouts needed to successfully pilot the ROV. For the backend, the surface sends and receives information to and from the Raspberry Pi inside of the ROV, allows us to send outputs to the thrusters and tools in order to fully operate the ROV.
41+
X17 Surface contains the frontend and backend processes for the ROV. For the frontend, the surface (computer) connects the user interface with the cameras and gamepad, allowing us to display the camera steams, controls, and readouts needed to successfully pilot the ROV. For the backend, the surface sends and receives information to and from the Raspberry Pi inside of the ROV, allows us to send outputs to the thrusters and tools in order to fully operate the ROV.
4242

4343
### System Architecture
4444

45-
Shown below is a diagram showing the X18 system architecture. Each white box represents a ROS node and each arrow represents a topic which is published and subscribed to.
45+
Shown below is a diagram showing the X17 system architecture. Each white box represents a ROS node and each arrow represents a topic which is published and subscribed to.
4646

4747
<div align="center">
4848
<img src="https://github.com/purduerov/X16-Surface/assets/115110018/0f7b27da-1e25-4b1f-817c-2d3890259eed" alt="x16_system_architecture" width="650" height="450" /><br>
49-
<i>X18 System Architecture</i>
49+
<i>X16 System Architecture</i>
5050
</div>
5151

5252
### Dependencies
@@ -73,7 +73,7 @@ To see all of the dependenices used for this system, navigate to **X17-Surface >
7373

7474
This file is generated by running the following command while in the directory:
7575
```bash
76-
pipreqs X18-Surface
76+
pipreqs X17-Surface
7777
```
7878

7979
To install all of the dependancies for the frontend, you can run the following command:
@@ -104,7 +104,7 @@ All of the information needed for use of the X17 ROV can be found below.
104104
```
105105
#### Launching the User Interface
106106

107-
- The user interface can be launched by running the main file in **X18Surface > ui > src**
107+
- The user interface can be launched by running the main file in **X17Surface > ui > src**
108108
- This will automatically launch and bring up the camera streams
109109
- Upon closing the user interface, these processes will be killed
110110

@@ -210,15 +210,17 @@ https://chat.openai.com/
210210

211211
### Documentation
212212

213-
For more documentation on using X18 Surface, visit the Purdue ROV BookStack.
213+
For more documentation on using X17 Surface, visit the Purdue ROV BookStack.
214214

215215
## Credits
216216

217-
X18 Software has been a collaborative effort involving the dedication and contributions of many individuals, including:
217+
X17 Software has been a collaborative effort involving the dedication and contributions of many individuals, including:
218218

219219
### Project Team
220-
- **Software Lead:** Aditya Nawandhar, Kira Yang
221-
- **CV Lead:** Aran P
220+
- **Software Lead:** Xavier Callait
221+
- **Frontend Leads:** Ethan Burmane, Caden Brennan
222+
- **Sensors Lead:** Adam Kahl
223+
- **CV Lead:** Anna Arnaudova
222224

223225
### Special Thanks
224226
- **Purdue IEEE:** For their ongoing support and collaboration.

frontend/launch/surface_launch.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from launch_ros.actions import Node
33
from launch.actions import TimerAction
44

5+
56
def generate_launch_description():
67
return LaunchDescription(
78
[
@@ -10,16 +11,16 @@ def generate_launch_description():
1011
executable="app.py",
1112
namespace="rov",
1213
),
13-
# Node(
14-
# package='controller',
15-
# executable='sender.py',
16-
# namespace='rov',
17-
# ),
18-
# Node(
19-
# package="mediamtx_node",
20-
# executable="mediamtx_node.py",
21-
# namespace="rov",
22-
# ),
14+
Node(
15+
package="controller",
16+
executable="sender.py",
17+
namespace="rov",
18+
),
19+
Node(
20+
package="mediamtx_node",
21+
executable="mediamtx_node.py",
22+
namespace="rov",
23+
),
2324
TimerAction(
2425
period=5.0, # Delay for n seconds
2526
actions=[
@@ -29,10 +30,10 @@ def generate_launch_description():
2930
namespace="rov",
3031
),
3132
Node(
32-
package='ui_subscriber',
33-
executable='ui_subscriber.py',
34-
namespace='rov',
35-
)
33+
package="ui_subscriber",
34+
executable="ui_subscriber.py",
35+
namespace="rov",
36+
),
3637
],
3738
),
3839
]

frontend/launch/surface_launch.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ launch:
55
exec: app.py
66
namespace: rov
77

8-
# - node:
9-
# pkg: controller
10-
# exec: sender.py
11-
# namespace: rov
8+
- node:
9+
pkg: controller
10+
exec: sender.py
11+
namespace: rov

frontend/src/app.py

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from frontend_utils.recording_api import RecordingRoutes # Import the new module
2222
from frontend_utils.controller_api import ControllerRoutes # Import the new module
2323

24+
2425
class Frontend(Node):
2526
def __init__(self):
2627
super().__init__("frontend")
@@ -42,10 +43,10 @@ def __init__(self):
4243

4344
# Get camera URLs from environment variables or use defaults
4445
self.camera_urls = {
45-
'camera1': os.getenv('CAMERA1_URL', 'http://localhost:8889/camera_1'),
46-
'camera2': os.getenv('CAMERA2_URL', 'http://localhost:8889/camera_2'),
47-
'camera3': os.getenv('CAMERA3_URL', 'http://localhost:8889/camera_3'),
48-
'camera4': os.getenv('CAMERA4_URL', 'http://localhost:8889/camera_4')
46+
"camera1": os.getenv("CAMERA1_URL", "http://localhost:8889/camera_1"),
47+
"camera2": os.getenv("CAMERA2_URL", "http://localhost:8889/camera_2"),
48+
"camera3": os.getenv("CAMERA3_URL", "http://localhost:8889/camera_3"),
49+
"camera4": os.getenv("CAMERA4_URL", "http://localhost:8889/camera_4"),
4950
}
5051

5152
self.get_logger().info(f"Camera URLs: {self.camera_urls}")
@@ -79,56 +80,73 @@ def setup_routes(self):
7980
# Default Route
8081
@self.app.route("/")
8182
def index():
82-
return redirect(url_for("new_ui"), code=302)
83-
83+
return redirect(url_for("home"), code=302)
84+
8485
# Main UI
85-
@self.app.route("/ui")
86-
def new_ui():
87-
return render_template("innovative_ui.html", camera_urls=self.camera_urls)
88-
86+
@self.app.route("/home")
87+
def home():
88+
return render_template(
89+
"innovative_ui.html", active_page="home", camera_urls=self.camera_urls
90+
)
91+
8992
### -------- CAMERA PAGES -------- ###
9093

9194
@self.app.route("/all-cameras")
9295
def all_cameras():
93-
return render_template("all_cameras.html", active_page='all-cameras', camera_urls=self.camera_urls)
94-
96+
return render_template(
97+
"all_cameras.html",
98+
active_page="all-cameras",
99+
camera_urls=self.camera_urls,
100+
)
101+
95102
@self.app.route("/fullscreen-camera")
96103
def fullscreen_camera():
97-
camera = request.args.get('camera', '1') # Default to camera 1 if not specified
98-
return render_template("fullscreen_camera.html", active_page='fullscreen-camera', camera=camera, camera_urls=self.camera_urls)
99-
104+
camera = request.args.get(
105+
"camera", "1"
106+
) # Default to camera 1 if not specified
107+
return render_template(
108+
"fullscreen_camera.html",
109+
active_page="fullscreen-camera",
110+
camera=camera,
111+
camera_urls=self.camera_urls,
112+
)
113+
100114
### -------- UTILITY PAGES -------- ###
101115

102116
# The thrust testing page
103117
@self.app.route("/thruster-testing")
104118
def thrust_testing():
105-
return render_template("thruster_testing.html", active_page='thruster-testing')
106-
119+
return render_template(
120+
"thruster_testing.html", active_page="thruster-testing"
121+
)
122+
107123
# Node status page
108124
@self.app.route("/node-status")
109125
def node_status():
110-
return render_template("node_status.html", active_page='node-status')
111-
126+
return render_template("node_status.html", active_page="node-status")
127+
112128
# Logs page
113129
@self.app.route("/logs")
114130
def logs():
115-
return render_template("logs.html", active_page='logs')
116-
131+
return render_template("logs.html", active_page="logs")
132+
117133
# Recordings page
118134
@self.app.route("/recordings")
119135
def recordings_page():
120-
return render_template("recordings.html", active_page='recordings')
121-
136+
return render_template("recordings.html", active_page="recordings")
137+
122138
@self.app.route("/controller-mapping")
123139
def controller_mapping():
124-
return render_template("controller_mapping.html", active_page='controller-mapping')
140+
return render_template(
141+
"controller_mapping.html", active_page="controller-mapping"
142+
)
125143

126144
# Function to setup the socketio events
127145
def setup_socketio_events(self):
128146
@self.socketio.on("connect")
129147
def connect():
130148
# Only log first connection in a session, not reconnects
131-
if not hasattr(self, '_session_started'):
149+
if not hasattr(self, "_session_started"):
132150
self.get_logger().info("SocketIO client connected")
133151
self._session_started = True
134152

@@ -147,29 +165,30 @@ def handle_all_events(event, data=None):
147165
self.get_logger().debug(f"Frontend event: {event}")
148166
# Handle the event
149167
handle_frontend_event(self, event, data)
150-
168+
151169
# Silently handle log-related events
152170
elif event.startswith("request_logs") or event.startswith("clear_logs"):
153171
pass
154-
172+
155173
# Forward all other events
156174
else:
157175
self.socketio.emit(event, data)
158176

177+
159178
def main():
160179
rclpy.init()
161180
frontend = Frontend()
162-
181+
163182
# Silent exit on SIGINT - just terminate immediately
164183
def silent_exit(sig, frame):
165184
# Exit without any logging or cleanup
166185
frontend.log_helper.stop_log_monitor()
167186
os._exit(0) # exit immediately without cleanup
168-
187+
169188
# Register the signal handler
170189
signal.signal(signal.SIGINT, silent_exit)
171190
signal.signal(signal.SIGTERM, silent_exit)
172-
191+
173192
try:
174193
rclpy.spin(frontend)
175194
except KeyboardInterrupt:
@@ -180,5 +199,6 @@ def silent_exit(sig, frame):
180199
frontend.log_helper.stop_log_monitor()
181200
sys.exit(0)
182201

202+
183203
if __name__ == "__main__":
184204
main()

frontend/src/frontend_utils/controller_api.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,30 @@
22
import json
33
from flask import jsonify, request
44

5+
56
class ControllerRoutes:
67
def __init__(self, app, logger):
78
self.app = app
89
self.logger = logger
9-
self.config_file = os.path.join(os.getcwd(), 'ros', 'controller', 'src', 'mappings.json')
10+
self.config_file = os.path.join(
11+
os.getcwd(), "ros", "controller", "src", "mappings.json"
12+
)
1013
self.register_routes()
1114

1215
def load_configs(self):
1316
"""Load controller configurations from file"""
1417
try:
1518
# Only log at debug level for routine operations
16-
self.logger.debug(f"Loading controller configurations from {self.config_file}")
17-
with open(self.config_file, 'r') as f:
19+
self.logger.debug(
20+
f"Loading controller configurations from {self.config_file}"
21+
)
22+
with open(self.config_file, "r") as f:
1823
configs = json.load(f)
1924
# Log config names at debug level
2025
config_names = list(configs.keys())
21-
self.logger.debug(f"Loaded {len(config_names)} configurations: {config_names}")
26+
self.logger.debug(
27+
f"Loaded {len(config_names)} configurations: {config_names}"
28+
)
2229
return configs
2330
except FileNotFoundError:
2431
self.logger.warning(f"Configuration file not found: {self.config_file}")
@@ -32,22 +39,22 @@ def save_config(self, mapping):
3239
pass
3340
self.logger.info(f"Saving configs: {mapping}")
3441
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
35-
with open(self.config_file, 'w') as f:
42+
with open(self.config_file, "w") as f:
3643
json.dump(mapping, f, indent=2)
3744

3845
def register_routes(self):
3946
"""Register all controller-related routes with the Flask app"""
4047

41-
@self.app.route('/api/controller/configs', methods=['GET'])
48+
@self.app.route("/api/controller/configs", methods=["GET"])
4249
def get_configs():
4350
"""Get current controller button configuration"""
4451
configs = self.load_configs()
4552
# Only return a list of the keys in the mapping
4653
configs = list(configs.keys())
4754
self.logger.info(f"Current configs: {configs}")
4855
return jsonify(configs)
49-
50-
@self.app.route('/api/controller/configs/<config_name>', methods=['GET'])
56+
57+
@self.app.route("/api/controller/configs/<config_name>", methods=["GET"])
5158
def get_config(config_name):
5259
"""Get a specific controller button configuration"""
5360
configs = self.load_configs()
@@ -56,26 +63,26 @@ def get_config(config_name):
5663
# self.logger.info(f"Config for {config_name}: {config}")
5764
return jsonify(config)
5865
else:
59-
return jsonify({'error': 'Config not found'}), 404
66+
return jsonify({"error": "Config not found"}), 404
6067

61-
@self.app.route('/api/controller/configs/<config_name>', methods=['PUT'])
68+
@self.app.route("/api/controller/configs/<config_name>", methods=["PUT"])
6269
def update_config(config_name):
6370
"""Update controller button configurations"""
6471
configs = self.load_configs()
6572
new_config = request.json
6673
configs[config_name] = new_config
6774
self.save_config(configs)
6875
self.logger.info(f"Updated config for {config_name}: {new_config}")
69-
return jsonify({'message': 'Config updated successfully'}), 200
70-
71-
@self.app.route('/api/controller/configs/<config_name>', methods=['DELETE'])
76+
return jsonify({"message": "Config updated successfully"}), 200
77+
78+
@self.app.route("/api/controller/configs/<config_name>", methods=["DELETE"])
7279
def delete_config(config_name):
7380
"""Delete a specific controller button configuration"""
7481
configs = self.load_configs()
7582
if config_name in configs:
7683
del configs[config_name]
7784
self.save_config(configs)
7885
self.logger.info(f"Deleted config for {config_name}")
79-
return jsonify({'message': 'Config deleted successfully'}), 200
86+
return jsonify({"message": "Config deleted successfully"}), 200
8087
else:
81-
return jsonify({'error': 'Config not found'}), 404
88+
return jsonify({"error": "Config not found"}), 404

0 commit comments

Comments
 (0)