-
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.
Transferred and fixed, modernised the code
- Loading branch information
1 parent
f7dbc1c
commit 94fce59
Showing
19 changed files
with
793 additions
and
0 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 |
---|---|---|
@@ -0,0 +1 @@ | ||
sudo python3.8 -m ncl_rovers & |
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,37 @@ | ||
name: Python package testing | ||
|
||
on: | ||
pull_request: | ||
branches: [ master ] | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
python-version: [3.6, 3.7, 3.8] | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: Install Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
|
||
- name: Install the package | ||
run : | | ||
python -m pip install --upgrade pip setuptools wheel | ||
python -m pip install . | ||
- name: Static analysis (pylint + pydocstyle) | ||
run: | | ||
python -m pip install pylint pydocstyle | ||
pylint --rcfile .pylintrc raspberry_pi tests setup.py | ||
pydocstyle --config .pydocstylerc raspberry_pi tests setup.py | ||
- name: Tests (pytest) | ||
run: | | ||
python -m pip install pytest pytest-cov | ||
pytest --cov=raspberry_pi |
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 |
---|---|---|
|
@@ -127,3 +127,6 @@ dmypy.json | |
|
||
# Pyre type checker | ||
.pyre/ | ||
|
||
# JetBrains | ||
.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,3 @@ | ||
[pydocstyle] | ||
add_ignore = D200, D105, D107 | ||
add_select = D213, D214, D215, D404 |
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,11 @@ | ||
[FORMAT] | ||
max-line-length=120 | ||
|
||
[MESSAGES CONTROL] | ||
disable = logging-fstring-interpolation, too-few-public-methods, too-many-instance-attributes | ||
|
||
[SIMILARITIES] | ||
min-similarity-lines=4 | ||
ignore-comments=yes | ||
ignore-docstrings=yes | ||
ignore-imports=yes |
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,3 @@ | ||
""" | ||
Raspberry Pi package. | ||
""" |
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,16 @@ | ||
""" | ||
Raspberry Pi execution file. | ||
""" | ||
import os | ||
import sys | ||
|
||
# Make sure a local raspberry-pi package can be found and overrides any installed versions | ||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) | ||
|
||
from raspberry_pi.data_manager import DataManager # pylint: disable = wrong-import-position | ||
from raspberry_pi.server import Server # pylint: disable = wrong-import-position | ||
|
||
if __name__ == '__main__': | ||
dm = DataManager() | ||
server = Server(dm) | ||
server.start() |
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,170 @@ | ||
""" | ||
Representation of an Arduino, including relevant networking functionalities. | ||
""" | ||
from threading import Thread | ||
import msgpack | ||
from msgpack import UnpackException | ||
from serial import Serial, SerialException | ||
from .data_manager import DataManager | ||
from .constants import SERIAL_BAUDRATE, SERIAL_READ_TIMEOUT, SERIAL_WRITE_TIMEOUT | ||
from .enums import Device | ||
from .utils import logger | ||
|
||
|
||
class Arduino: | ||
""" | ||
Handle serial communication between the ROV and an Arduino. | ||
The Arduino-s are created via server, and can be accessed in the following way: | ||
arduinos = server.arduinos | ||
While working, the code should check if the communication is happening, to detect when it stops: | ||
if not arduino.connected(): | ||
arduino.reconnect() | ||
You can check __main__.py to see how the surface-rov and rov-arduino(s) connections are kept alive. | ||
""" | ||
|
||
def __init__(self, dm: DataManager, port: str): | ||
""" | ||
Initialise the serial object and the thread. | ||
At first, the device is not identified (it's unassigned), and will be known after the pre-communication process. | ||
""" | ||
self._dm = dm | ||
self._port = port | ||
self._device = Device.UNASSIGNED | ||
self._communication_thread = self._new_thread() | ||
self._serial = Serial(baudrate=SERIAL_BAUDRATE) | ||
self._serial.port = self._port | ||
self._serial.write_timeout = SERIAL_WRITE_TIMEOUT | ||
self._serial.timeout = SERIAL_READ_TIMEOUT | ||
|
||
def __str__(self): | ||
return self._port | ||
|
||
@property | ||
def connected(self) -> bool: | ||
""" | ||
Check if the communication is happening. | ||
""" | ||
return self._communication_thread.is_alive() | ||
|
||
def _new_thread(self) -> Thread: | ||
""" | ||
Create the communication thread. | ||
""" | ||
return Thread(target=self._communicate) | ||
|
||
def connect(self): | ||
""" | ||
Connect to the Arduino and start exchanging the data. | ||
Opens a serial connection and starts the communication thread. | ||
""" | ||
if self.connected: | ||
logger.error(f"Can't connect - already connected to {self._port}") | ||
return | ||
|
||
logger.info(f"Connecting to {self._port}") | ||
while True: | ||
try: | ||
if not self._serial.is_open: | ||
self._serial.open() | ||
break | ||
except SerialException as ex: | ||
logger.debug(f"Failed to connect to {self._port} - {ex}") | ||
|
||
logger.info(f"Connected to {self._port}") | ||
self._communication_thread.start() | ||
|
||
def _communicate(self): | ||
""" | ||
Exchange the data with the Arduino. | ||
Pre-communicates at first, to identify the device and set relevant ID, while ignoring empty data. Starts | ||
exchanging information properly immediately after. | ||
""" | ||
while True: | ||
try: | ||
data = self._serial.read_until().strip() | ||
if not data: | ||
continue | ||
|
||
try: | ||
self._device = Device(msgpack.unpackb(data.decode("utf-8"))["ID"]) | ||
except (UnicodeError, UnpackException): | ||
logger.exception(f"Failed to decode the following data in pre-communication: {data}") | ||
return | ||
|
||
# Knowing the id, set the connection status to connected (True) and exit the pre-communication step | ||
logger.info(f"Detected a valid device at {self._port} - {self._device.name}") | ||
self._dm.set(self._device, **{self._device.value: True}) | ||
break | ||
|
||
except SerialException: | ||
logger.exception(f"Lost connection to {self._port}") | ||
return | ||
|
||
except (KeyError, ValueError): | ||
logger.error(f"Invalid device ID received from {self._port}") | ||
return | ||
|
||
while True: | ||
try: | ||
if data: | ||
logger.debug(f"Received data from {self._port} - {data}") | ||
|
||
try: | ||
data = msgpack.unpackb(data.decode("utf-8").strip()) | ||
except (UnicodeError, UnpackException): | ||
logger.exception(f"Failed to decode following data: {data}") | ||
|
||
# Remove ID from the data to avoid setting it upstream, disconnect in case of errors | ||
if "ID" not in data or data["ID"] != self._device.value: | ||
logger.error(f"ID key not in {data} or key doesn't match {self._device.value}") | ||
break | ||
|
||
del data["ID"] | ||
self._dm.set(self._device, **data) | ||
|
||
else: | ||
logger.debug(f"Timed out reading from {self._port}, clearing the buffer") | ||
self._serial.reset_output_buffer() | ||
|
||
# Send data and wait for a response from Arduino (next set of data to process) | ||
self._serial.write(bytes(msgpack.packb(self._dm.get(self._device)) + "\n")) | ||
data = self._serial.read_until().strip() | ||
|
||
except SerialException: | ||
logger.error(f"Lost connection to {self._port}") | ||
break | ||
|
||
def disconnect(self): | ||
""" | ||
Disconnect from the Arduino and stop exchanging the data. | ||
""" | ||
try: | ||
if self._serial.is_open: | ||
self._serial.close() | ||
except SerialException: | ||
logger.exception(f"Failed to safely disconnect from {self._port}") | ||
|
||
# Clean up the communication thread | ||
self._communication_thread = self._new_thread() | ||
|
||
# Set the connection status to disconnected, if the id was known | ||
if self._device != Device.UNASSIGNED: | ||
self._dm.set(self._device, **{self._device.value: False}) | ||
|
||
# Forget the device id | ||
self._device = Device.UNASSIGNED | ||
|
||
def reconnect(self): | ||
""" | ||
Reconnect to the Arduino. | ||
""" | ||
self.disconnect() | ||
self.connect() |
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,76 @@ | ||
""" | ||
Constants and other static values. | ||
""" | ||
import os | ||
import dotenv | ||
from .enums import Device | ||
|
||
# Declare paths to relevant folders - tests folder shouldn't be known here | ||
ROOT_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) | ||
RASPBERRY_PI_DIR = os.path.join(ROOT_DIR, "raspberry_pi") | ||
RES_DIR = os.path.join(RASPBERRY_PI_DIR, "res") | ||
LOG_DIR = os.path.join(RASPBERRY_PI_DIR, "log") | ||
|
||
# Load the environment variables from the root folder and/or the resources folder | ||
dotenv.load_dotenv(dotenv_path=os.path.join(ROOT_DIR, ".env")) | ||
dotenv.load_dotenv(dotenv_path=os.path.join(RES_DIR, ".env")) | ||
|
||
# Declare logging config | ||
LOG_CONFIG_PATH = os.getenv("LOG_CONFIG_PATH", os.path.join(RES_DIR, "log-config.json")) | ||
LOGGER_NAME = os.getenv("LOGGER_NAME", "raspberry-pi") | ||
|
||
# Declare surface connection information | ||
CONNECTION_IP = os.getenv("CONNECTION_IP", "0.0.0.0") | ||
CONNECTION_PORT = int(os.getenv("CONNECTION_PORT", "50000")) | ||
CONNECTION_DATA_SIZE = int(os.getenv("CONNECTION_DATA_SIZE", "4096")) | ||
|
||
# Declare Arduino-related constants (timeouts in seconds) | ||
ARDUINO_PORTS = tuple(port for port in os.getenv("ARDUINO_PORTS", "").split(",") if port) | ||
SERIAL_WRITE_TIMEOUT = int(os.getenv("SERIAL_WRITE_TIMEOUT", "1")) | ||
SERIAL_READ_TIMEOUT = int(os.getenv("SERIAL_READ_TIMEOUT", "1")) | ||
SERIAL_BAUDRATE = int(os.getenv("SERIAL_BAUDRATE", "115200")) | ||
|
||
# Declare the constant to determine how often should the connection statuses be checked (in seconds) | ||
CONNECTION_CHECK_DELAY = int(os.getenv("CONNECTION_CHECK_DELAY", "1")) | ||
|
||
# Declare constant for slowly changing up all values and keys affected | ||
RAMP_RATE = 2 | ||
RAMP_KEYS = { | ||
"T_HFP", | ||
"T_HFS", | ||
"T_HAP", | ||
"T_HAS", | ||
"T_VFP", | ||
"T_VFS", | ||
"T_VAP", | ||
"T_VAS", | ||
"T_M" | ||
} | ||
|
||
|
||
# Declare the transmission sets with the default values as initial values | ||
THRUSTER_IDLE = 1500 | ||
GRIPPER_IDLE = 1500 | ||
CORD_IDLE = 1500 | ||
DEFAULTS = { | ||
Device.SURFACE: { | ||
"A_O": False, | ||
"A_I": False, | ||
"S_O": 0, | ||
"S_I": 0 | ||
}, | ||
Device.ARDUINO_O: { | ||
"T_HFP": THRUSTER_IDLE, | ||
"T_HFS": THRUSTER_IDLE, | ||
"T_HAP": THRUSTER_IDLE, | ||
"T_HAS": THRUSTER_IDLE, | ||
"T_VFP": THRUSTER_IDLE, | ||
"T_VFS": THRUSTER_IDLE, | ||
"T_VAP": THRUSTER_IDLE, | ||
"T_VAS": THRUSTER_IDLE, | ||
"T_M": THRUSTER_IDLE, | ||
"M_G": GRIPPER_IDLE, | ||
"M_C": CORD_IDLE | ||
}, | ||
Device.ARDUINO_I: {} | ||
} |
Oops, something went wrong.