Skip to content

Commit 6118f20

Browse files
Add ground station back to pysquared repo (#311)
Co-authored-by: ineskhou <[email protected]>
1 parent 38dbf81 commit 6118f20

File tree

7 files changed

+208
-14
lines changed

7 files changed

+208
-14
lines changed

circuitpython-workspaces/flight-software/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies = [
1313
"adafruit-circuitpython-neopixel==6.3.12",
1414
"adafruit-circuitpython-register==1.10.4",
1515
"adafruit-circuitpython-rfm==1.0.6",
16-
"adafruit-circuitpython-tca9548a @ git+https://github.com/proveskit/Adafruit_CircuitPython_TCA9548A@1.0.0",
16+
"adafruit-circuitpython-tca9548a @ git+https://github.com/proveskit/Adafruit_CircuitPython_TCA9548A@1.1.0",
1717
"adafruit-circuitpython-ticks==1.1.1",
1818
"adafruit-circuitpython-veml7700==2.1.4",
1919
"adafruit-circuitpython-hashlib==1.4.19",

circuitpython-workspaces/flight-software/src/pysquared/logger.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def __init__(
9595
"""
9696
self._error_counter: Counter = error_counter
9797
self._log_level: int = log_level
98-
self._colorized: bool = colorized
98+
self.colorized: bool = colorized
9999

100100
def _can_print_this_level(self, level_value: int) -> bool:
101101
"""
@@ -162,7 +162,7 @@ def _log(self, level: str, level_value: int, message: str, **kwargs) -> None:
162162
with open(file, "a") as f:
163163
f.write(json_output + "\n")
164164

165-
if self._colorized:
165+
if self.colorized:
166166
json_output = json_output.replace(
167167
f'"level": "{level}"', f'"level": "{LogColors[level]}"'
168168
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[project]
2+
name = "pysquared-ground-station"
3+
version = "2.0.0"
4+
description = "Ground Station Software for the PROVES Kit"
5+
requires-python = ">=3.13"
6+
dependencies = [
7+
"pysquared-flight-software",
8+
]
9+
10+
[tool.setuptools]
11+
package-dir = {"" = "src"}
12+
13+
[tool.setuptools.packages.find]
14+
where = ["src"]
15+
include = ["ground_station*"]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
PySquared Ground Station
3+
"""
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
PySquared Ground Station
3+
"""
4+
5+
import json
6+
import time
7+
8+
import supervisor
9+
from pysquared.cdh import CommandDataHandler
10+
from pysquared.config.config import Config
11+
from pysquared.hardware.radio.packetizer.packet_manager import PacketManager
12+
from pysquared.logger import Logger
13+
14+
15+
class GroundStation:
16+
"""Ground Station class to manage communication with the satellite."""
17+
18+
def __init__(
19+
self,
20+
logger: Logger,
21+
config: Config,
22+
packet_manager: PacketManager,
23+
cdh: CommandDataHandler,
24+
):
25+
self._log = logger
26+
self._log.colorized = True
27+
self._config = config
28+
self._packet_manager = packet_manager
29+
self._cdh = cdh
30+
31+
def listen(self):
32+
"""Listen for incoming packets from the satellite."""
33+
34+
try:
35+
while True:
36+
if supervisor.runtime.serial_bytes_available:
37+
typed = input().strip()
38+
if typed:
39+
self.handle_input(typed)
40+
41+
b = self._packet_manager.listen(1)
42+
if b is not None:
43+
self._log.info(
44+
message="Received response", response=b.decode("utf-8")
45+
)
46+
47+
except KeyboardInterrupt:
48+
self._log.debug("Keyboard interrupt received, exiting listen mode.")
49+
50+
def send_receive(self):
51+
"""Send commands to the satellite and wait for responses."""
52+
53+
try:
54+
cmd_selection = input(
55+
"""
56+
===============================
57+
| Select command to send |
58+
| 1: Reset |
59+
| 2: Change radio modulation |
60+
| 3: Send joke |
61+
===============================
62+
"""
63+
)
64+
65+
self.handle_input(cmd_selection)
66+
67+
except KeyboardInterrupt:
68+
self._log.debug("Keyboard interrupt received, exiting send mode.")
69+
70+
def handle_input(self, cmd_selection):
71+
"""
72+
Handle user input commands.
73+
74+
Args:
75+
cmd_selection: The command selection input by the user.
76+
"""
77+
if cmd_selection not in ["1", "2", "3"]:
78+
self._log.warning("Invalid command selection. Please try again.")
79+
return
80+
81+
message: dict[str, object] = {
82+
"name": self._config.cubesat_name,
83+
"password": self._config.super_secret_code,
84+
}
85+
86+
if cmd_selection == "1":
87+
message["command"] = self._cdh.command_reset
88+
elif cmd_selection == "2":
89+
message["command"] = self._cdh.command_change_radio_modulation
90+
modulation = input("Enter new radio modulation [FSK | LoRa]: ")
91+
message["args"] = [modulation]
92+
elif cmd_selection == "3":
93+
message["command"] = self._cdh.command_send_joke
94+
95+
while True:
96+
# Turn on the radio so that it captures any received packets to buffer
97+
self._packet_manager.listen(1)
98+
99+
# Send the message
100+
self._log.info(
101+
"Sending command",
102+
cmd=message["command"],
103+
args=message.get("args", []),
104+
)
105+
self._packet_manager.send(json.dumps(message).encode("utf-8"))
106+
107+
# Listen for ACK response
108+
b = self._packet_manager.listen(1)
109+
if b is None:
110+
self._log.info("No response received, retrying...")
111+
continue
112+
113+
if b != b"ACK":
114+
self._log.info(
115+
"No ACK response received, retrying...",
116+
response=b.decode("utf-8"),
117+
)
118+
continue
119+
120+
self._log.info("Received ACK")
121+
122+
# Now listen for the actual response
123+
b = self._packet_manager.listen(1)
124+
if b is None:
125+
self._log.info("No response received, retrying...")
126+
continue
127+
128+
self._log.info("Received response", response=b.decode("utf-8"))
129+
break
130+
131+
def run(self):
132+
"""Run the ground station interface."""
133+
while True:
134+
print(
135+
"""
136+
=============================
137+
| |
138+
| WELCOME! |
139+
| PROVESKIT Ground Station |
140+
| |
141+
=============================
142+
| Please Select Your Mode |
143+
| 'A': Listen |
144+
| 'B': Send |
145+
=============================
146+
"""
147+
)
148+
149+
device_selection = input().lower()
150+
151+
if device_selection not in ["a", "b"]:
152+
self._log.warning("Invalid Selection. Please try again.")
153+
continue
154+
155+
if device_selection == "a":
156+
self.listen()
157+
elif device_selection == "b":
158+
self.send_receive()
159+
160+
time.sleep(1)

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ dependencies = [
66
"pysquared-flight-software",
77
"pysquared-flight-software-mocks",
88
"pysquared-flight-software-unit-tests",
9+
"pysquared-ground-station",
910
]
1011

1112
[dependency-groups]
@@ -22,6 +23,7 @@ docs = [
2223
pysquared-flight-software = { workspace = true }
2324
pysquared-flight-software-mocks = { workspace = true }
2425
pysquared-flight-software-unit-tests = { workspace = true }
26+
pysquared-ground-station = { workspace = true }
2527

2628
[tool.uv.workspace]
2729
members = [

uv.lock

Lines changed: 25 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)