-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into bach/feature/config_parser
- Loading branch information
Showing
18 changed files
with
1,087 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -161,3 +161,5 @@ cython_debug/ | |
|
||
# MacOS | ||
.DS_Store | ||
|
||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.un~ | ||
*.swp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# ADCS Simulated Subsystem | ||
|
||
## Overview | ||
|
||
The ADCS, also known as the Altitude Determination and Control System, is a | ||
system that controls the orientation of ex3. | ||
|
||
The ADCS is used to help steer the satellite. | ||
|
||
## Commands | ||
All currently implemented commands are listed in the following table. [Here](https://docs.google.com/spreadsheets/d/1rWde3jjrgyzO2fsg2rrVAKxkPa2hy-DDaqlfQTDaNxg/edit?gid=0#gid=0) is a spreadsheet on the commands. | ||
|COMMAND NAME| PARAMETERS | PARAMETER TYPE | RETURN | RETURN TYPE | DESCRIPTION | | ||
|-|-|-|-|-|-| | ||
| `HELP` | N/A | N/A | list of all commands | string | Returns all working commands | ||
| `GS` | N/A | N/A | State of the ADCS ("WORKING" or "OFF") | string | Gets the current state of the ADCS | ||
| `ON` | N/A | N/A | N/A | N/A | Sets the ADCS state to "WORKING" | ||
| `OFF` | N/A | N/A | N/A | N/A | Sets the ADCS state to "OFF" | ||
| `GWS` | N/A | N/A | Wheel speed x, y, & z | tuple of floats | Records and sends the wheel speed of the ADCS | ||
| `SWS` | Wheel speed x, y, & z | floats in RPM | N/A | N/A | Sets the ADCS wheel speed on all three axes in RPM | ||
| `SC` | N/A | N/A | House keeping data | string | Generates random number for voltage, current, and temperature of the adcs. Also generates an "OK" or "BAD" overall status | ||
| `SMC` | Magnetorquer current x, y, & z | floats in mA | N/A | N/A | Sets the ADCS's magnetorquer current on all three axes in mA | ||
| `GMC` | N/A | N/A | Magnetorquer current x, y, & z | tuple of floats | Records and sends the magnetorquer currents of the ADCS | ||
| `GTM` | N/A | N/A | Time | float in s | Sends the time recorded by the ADCS | ||
| `STM` | Time | float in s | N/A | N/A | Sets the time currently recorded by the ADCS | ||
| `GOR` | N/A | N/A | ADCS angles x, y, z | tuple of floats | Returns the current orientation of the ADCS in degrees | ||
| `RESET` | N/A | N/A | N/A | N/A | Sets the wheel speeds to 0 RPM, and the magnetorquer currents to 0 mA | ||
| `EXIT` | N/A | N/A | N/A | N/A | Disconnect the client from the server, and closes the server. This is the only command that is not stored with the `adcs_subsystem.py` file | ||
|
||
## Usage | ||
|
||
Start the server by running `adcs_server.py`, and connect to the server using some program/utility such as `nc` | ||
|
||
```bash | ||
$ python3 .ADCS/adcs_server.py 8000 | ||
Starting ADCS subsystem on port 8000 | ||
``` | ||
Then in a separate terminal process you can connect to the server on localhost:8000. Note if no host or port is specified the program defaults to localhost:42123 | ||
|
||
## Example usage | ||
Here is an example of connecting to the server sending the `HELP`, `GWS`, and `SWS` command. Exit by sending `^C` from the client side. | ||
``` | ||
$ nc localhost 8000 | ||
HELP | ||
HELP | help (string) | ||
GS | Get State (OFF or WORKING) | ||
ON | Set state WORKING | ||
OFF | Set state OFF | ||
GWS | Get wheel speed (tuple) | ||
SWS:x:y:z | set wheel speed | ||
GMC | get magnetorquer current (tuple) | ||
SMC:x:y:z | set magnetorquer current | ||
SC | Status Check (string) | ||
GTM | Get time (float) | ||
STM:float | Set time | ||
GOR | Get orientation (tuple) | ||
RESET | Resets wheels and magnetorquer | ||
GWS | ||
(0, 0, 0) | ||
SWS:10.3:-32.4:13.536 | ||
GWS | ||
(10.3, -32.4, 13.536) | ||
EXIT | ||
``` | ||
|
||
## TODO | ||
|
||
- [ ] Convert to a more realistic packet format using Bach's branch | ||
- [ ] Utilize Bach's unittest structure | ||
- [ ] Make a status report transaction code | ||
- [ ] Implement an echo command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
"""Holds the abstract class ConnectionProtocol""" | ||
|
||
|
||
class ConnectionProtocol: | ||
"""TODO: We should put the packet serialization and so on be the | ||
responsibility of the connection protocol, since it will be | ||
changing as we get further into the project.""" | ||
|
||
def send(self, data: bytes): | ||
""" | ||
This method will send data. Force the input to be bytes so that the | ||
caller must format the data to be bytes already. | ||
""" | ||
raise NotImplementedError | ||
|
||
def recv(self, timeout: float) -> bytes: | ||
""" | ||
This method will recieve data. It should return it. | ||
timeout specifies in seconds how long the recv function | ||
should block before raising an exception | ||
Fundamental assumption is that this will work with bytes | ||
""" | ||
raise NotImplementedError | ||
|
||
def connect(self): | ||
""" | ||
This method will start the connection | ||
""" | ||
raise NotImplementedError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
""" | ||
Holds classes for each type of measurements on the ADCS | ||
""" | ||
# pylint: disable=too-few-public-methods | ||
# pylint: disable=useless-parent-delegation | ||
|
||
|
||
class ThreeDimensionalMeasurements: | ||
""" | ||
Abstract class that deals with having multiple three | ||
dimensional stored values | ||
""" | ||
|
||
@staticmethod | ||
def new(): | ||
"""Factory method test""" | ||
return ThreeDimensionalMeasurements(0, 0, 0) | ||
|
||
def __init__(self, x, y, z): | ||
self.x = x | ||
self.y = y | ||
self.z = z | ||
|
||
def __repr__(self): | ||
"""This function is for debugging by printing the object""" | ||
return f"({self.x!r}, {self.y!r}, {self.z!r})" | ||
|
||
|
||
class AngularMeasurement(ThreeDimensionalMeasurements): | ||
"""Stores the angle in 3D""" | ||
|
||
def __init__(self, x: float, y: float, z: float): | ||
super().__init__(x, y, z) | ||
|
||
|
||
class AngularSpeed(ThreeDimensionalMeasurements): | ||
"""Stores the angular speed in 3D""" | ||
|
||
def __init__(self, x: float, y: float, z: float): | ||
super().__init__(x, y, z) | ||
|
||
|
||
class MagneticMeasurements(ThreeDimensionalMeasurements): | ||
"""Stores the magnetic measurements in 3D""" | ||
|
||
def __init__(self, x, y, z): | ||
super().__init__(x, y, z) | ||
|
||
|
||
class MagneticCurrent(ThreeDimensionalMeasurements): | ||
"""Stores the current in 3D""" | ||
|
||
def __init__(self, x, y, z): | ||
super().__init__(x, y, z) | ||
|
||
|
||
class WheelSpeed(ThreeDimensionalMeasurements): | ||
"""Stores the wheels speeds in 3D""" | ||
|
||
def __init__(self, x, y, z): | ||
super().__init__(x, y, z) | ||
|
||
|
||
class SystemClock: | ||
"""Stores the time""" | ||
|
||
def __init__(self, time: float): | ||
self.time = time |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
""" | ||
This will be the main file for the simulated ADCS subsystem. | ||
""" | ||
|
||
import sys | ||
import threading | ||
import time | ||
from queue import Empty # Import is needed from use of queue timeouts | ||
|
||
from tcp_server import TcpListener | ||
from adcs_subsystem import ADCSSubsystem | ||
|
||
SLEEP_TIME = 5 # in seconds | ||
EXIT_FLAG = b"EXIT" | ||
DEFAULT_HOST = "127.0.0.1" | ||
DEFAULT_PORT = 42123 | ||
|
||
|
||
def command_line_handler(argv) -> tuple[int, str]: | ||
""" | ||
Control flow for what to return depending on the commandline arg. | ||
**Change here if you need to change the port and address values** | ||
Returns: | ||
(PORT, HOST) | ||
""" | ||
|
||
ret_port = int(argv[1]) if len(argv) > 1 else DEFAULT_PORT | ||
ret_host = argv[2] if len(argv) > 2 else DEFAULT_HOST | ||
|
||
return ret_port, ret_host | ||
|
||
|
||
def command_parser(data: str): | ||
"""Splits the command from its parameters using colons (:)""" | ||
parsed = data.rstrip().split(sep=":") | ||
return parsed | ||
|
||
|
||
def listen(adcs: ADCSSubsystem, stop: threading.Event): | ||
""" | ||
function that will continuously listen for client messages | ||
when a message is received it will be put onto the rx buffer | ||
""" | ||
while not stop.is_set(): | ||
try: | ||
received = adcs.read_bytes(timeout=SLEEP_TIME) | ||
print(received) | ||
if not received or received.rstrip() == EXIT_FLAG: | ||
stop.set() # begin closing server | ||
return | ||
adcs.rx_buffer.put(received) | ||
except TimeoutError: | ||
continue | ||
|
||
|
||
def handle_input(adcs: ADCSSubsystem, stop: threading.Event): | ||
""" | ||
function that will continuously check the rx buffer for any | ||
data if there is it will take the data from the buffer and | ||
handle it putting any output onto the tx buffer for transmitting | ||
""" | ||
while not stop.is_set(): | ||
try: | ||
received = adcs.rx_buffer.get(timeout=SLEEP_TIME) | ||
|
||
decoded = received.decode("utf-8") | ||
data_list = command_parser(decoded) | ||
|
||
handle_output_thread = threading.Thread( | ||
target=handle_output, args=(adcs, data_list), daemon=True) | ||
handle_output_thread.start() | ||
except Empty: | ||
continue | ||
|
||
|
||
def handle_output(adcs: ADCSSubsystem, data_list: list): | ||
""" | ||
function that will only run when a handle_output_thread is spawned | ||
from the input handler. Once spawned, it will run its command and | ||
push to tx_buffer. | ||
This prevents tasks with short run times from being blocked by | ||
longer tasks | ||
""" | ||
transmit = run_command(data_list, adcs) | ||
|
||
if transmit is not None: | ||
encoded = str(transmit).encode("utf-8") | ||
adcs.tx_buffer.put(encoded) | ||
|
||
|
||
def send(adcs: ADCSSubsystem, stop: threading.Event): | ||
""" | ||
function that will continuously check the tx buffer for | ||
any data if there is it will send it back to the server. | ||
It is assumed that all data in the tx buffer are bytes object. | ||
""" | ||
while not stop.is_set(): | ||
try: | ||
transmitting = adcs.tx_buffer.get(timeout=SLEEP_TIME) | ||
adcs.send_bytes(transmitting) | ||
except Empty: | ||
continue | ||
|
||
|
||
def run_command(data: list, adcs: ADCSSubsystem): | ||
""" | ||
Given a list of stings this will run an adcs command with | ||
some given arguments. Returns anything that the adcs command | ||
wishes to send back to the OBC | ||
""" | ||
command = data[0] | ||
try: | ||
func = adcs.commands[command][0] | ||
num_params = adcs.commands[command][1] | ||
|
||
if len(data) != num_params + 1: | ||
return f"INVALID COMMAND: command {command} requires {num_params} arguments\n" | ||
|
||
if len(data) == 1: # No args | ||
return func() | ||
|
||
arg_list = data[1:] | ||
return func(*arg_list) | ||
except KeyError: | ||
return "INVALID COMMAND: run \"HELP\" for list of commands\n" | ||
|
||
|
||
if __name__ == "__main__": | ||
port, host = command_line_handler(sys.argv) | ||
addr = (host, port) | ||
|
||
print(f"Starting ADCS subsystem on port {port}") | ||
|
||
server = TcpListener(port, host) | ||
server.set_debug(True) | ||
|
||
adcs_subsystem = ADCSSubsystem(server) | ||
adcs_subsystem.init_link() # opens the server | ||
|
||
stop_event = threading.Event() # used to stop child threads | ||
|
||
rx_thread = threading.Thread(target=listen, args=( | ||
adcs_subsystem, stop_event), daemon=True) | ||
tx_thread = threading.Thread(target=send, args=( | ||
adcs_subsystem, stop_event), daemon=True) | ||
handle_input_thread = threading.Thread(target=handle_input, args=( | ||
adcs_subsystem, stop_event), daemon=True) | ||
|
||
rx_thread.start() | ||
tx_thread.start() | ||
handle_input_thread.start() | ||
|
||
while not stop_event.is_set(): | ||
try: | ||
time.sleep(SLEEP_TIME) | ||
except KeyboardInterrupt: | ||
stop_event.set() | ||
|
||
handle_input_thread.join() | ||
tx_thread.join() | ||
rx_thread.join() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
""" | ||
Stores the enum states of the ADCS | ||
""" | ||
|
||
from enum import Enum | ||
from enum import auto | ||
|
||
|
||
class ADCSState(Enum): | ||
""" | ||
Enum to represent the states of the ADCS | ||
""" | ||
|
||
OFF = auto() | ||
WORKING = auto() |
Oops, something went wrong.