diff --git a/.gitignore b/.gitignore index 076cf0c..6c2d44d 100755 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,79 @@ Lib/ .idea/ -vessel_info.json \ No newline at end of file +vessel_info.json +share/sdl2/bin/LICENSE.png.txt +share/sdl2/bin/LICENSE.opusfile.txt +share/sdl2/bin/zlib1.dll +share/sdl2/bin/SDL2_ttf.dll +share/sdl2/bin/SDL2_mixer.dll +share/sdl2/bin/SDL2_image.dll +share/sdl2/bin/SDL2.dll +share/sdl2/bin/LICENSE.zlib.txt +share/sdl2/bin/LICENSE.webp.txt +share/sdl2/bin/LICENSE.tiff.txt +share/sdl2/bin/LICENSE.opus.txt +share/sdl2/bin/LICENSE.ogg-vorbis.txt +share/sdl2/bin/LICENSE.mpg123.txt +share/sdl2/bin/LICENSE.modplug.txt +share/sdl2/bin/LICENSE.jpeg.txt +share/sdl2/bin/LICENSE.harfbuzz.txt +share/sdl2/bin/LICENSE.freetype.txt +share/sdl2/bin/LICENSE.FLAC.txt +share/sdl2/bin/libwebp-7.dll +share/sdl2/bin/libvorbisfile-3.dll +share/sdl2/bin/libvorbis-0.dll +share/sdl2/bin/libtiff-5.dll +share/sdl2/bin/libpng16-16.dll +share/sdl2/bin/libopusfile-0.dll +share/sdl2/bin/libopus-0.dll +share/sdl2/bin/libogg-0.dll +share/sdl2/bin/libmpg123-0.dll +share/sdl2/bin/libmodplug-1.dll +share/sdl2/bin/libjpeg-9.dll +share/sdl2/bin/libFLAC-8.dll +share/glew/bin/glew32.dll +share/angle/bin/libGLESv2.dll +share/angle/bin/libEGL.dll +share/angle/bin/d3dcompiler_47.dll +Scripts/wsdump.exe +Scripts/rstpep2html.py +Scripts/rst2xml.py +Scripts/rst2xetex.py +Scripts/rst2s5.py +Scripts/rst2pseudoxml.py +Scripts/rst2odt_prepstyles.py +Scripts/rst2odt.py +Scripts/rst2man.py +Scripts/rst2latex.py +Scripts/rst2html5.py +Scripts/rst2html4.py +Scripts/rst2html.py +Scripts/pywin32_testall.py +Scripts/pywin32_postinstall.py +Scripts/pythonw.exe +Scripts/python.exe +Scripts/pyserial-ports.exe +Scripts/pyserial-miniterm.exe +Scripts/pygmentize.exe +Scripts/pip3.exe +Scripts/pip3.10.exe +Scripts/pip.exe +Scripts/pasteurize.exe +Scripts/pasteurize-script.py +Scripts/normalizer.exe +Scripts/jsonschema.exe +Scripts/httpx.exe +Scripts/garden.bat +Scripts/garden +Scripts/futurize.exe +Scripts/futurize-script.py +Scripts/f2py.exe +Scripts/docutils.exe +Scripts/deactivate.bat +Scripts/Activate.ps1 +Scripts/activate.bat +Scripts/activate +pyvenv.cfg +Include/site/python3.10/greenlet/greenlet.h +submodules/tinyproto diff --git a/app/content/flight_director/flight_director.py b/app/content/flight_director/flight_director.py index b9aa62d..2309bab 100644 --- a/app/content/flight_director/flight_director.py +++ b/app/content/flight_director/flight_director.py @@ -1,26 +1,19 @@ -from asyncio import Future, Task -import asyncio from datetime import datetime, timedelta -import threading -import time -from typing import Collection, Iterable, Tuple, Type, Union, cast +from typing import Iterable, Tuple, Type, Union from uuid import UUID, uuid4 -from dataclasses import dataclass from app.content.flight_director.abort_command import AbortCommand from app.content.flight_director.arm_director_command import ArmDirectorCommand from app.content.flight_director.start_countdown_command import StartCountDownCommand from app.content.general_commands.calibrate import CalibrateZeroCommand -from app.content.general_commands.enable import DisableCommand, EnableCommand from app.content.microcontroller.arduino_serial import ArduinoSerial from app.content.motor_commands.open import IgniteCommand, OpenCommand from app.content.sensors.android_native.acceleration_pyjinius import PyjiniusAccelerationSensor from app.content.sensors.android_native.gyroscope_pyjinius import PyjiniusGyroscopeSensor from app.content.sensors.android_native.inertial_reference_frame import InertialReferenceFrame -from app.content.sensors.arduino.igniter import IgniterSensor -from app.content.sensors.arduino.servo import ServoSensor -from app.logic.commands.command import Command, Command -from app.content.general_commands.enable import DisableCommand, EnableCommand, ResetCommand +from app.content.microcontroller.arduino.parts.igniter import IgniterSensor +from app.content.microcontroller.arduino.parts.servo import ServoSensor +from app.logic.commands.command import Command from app.logic.rocket_definition import Part, Rocket @@ -139,7 +132,7 @@ def run_arm(self, c: ArmDirectorCommand): if not self.calibrated: c.state = 'failed' - c.response_message = 'Sensors not yet calibrated' + c.response_message = 'sensors not yet calibrated' return if not self.arduino.connected: diff --git a/app/content/microcontroller/arduino/messages/messages.py b/app/content/microcontroller/arduino/messages/messages.py new file mode 100644 index 0000000..16192b5 --- /dev/null +++ b/app/content/microcontroller/arduino/messages/messages.py @@ -0,0 +1,73 @@ +from typing import Collection, Iterable, Tuple, Type, Union, cast + +class ResponseMessage: + arr: bytearray() + + def __init__(self, arr): + self.arr = arr + + def getPart(self) -> chr: + mask = 63; + + return self.arr[0] & mask; + + def getCommand(self) -> chr: + mask = 15; + + return (self.arr[2] >> 4) & mask + + def getIndex(self) -> chr: + return self.arr[1] + + def getResponseRequestByte(self) -> chr: + mask = 1 + return self.arr[0] >> 6 & mask + + def getResult(self) -> chr: + mask = 15; + + return self.arr[2] & mask; + + +class SensorData: + arr: bytearray() + + def __init__(self, arr): + self.arr = arr + + def getPart(self) -> chr: + mask = 127; + + return self.arr[0] & mask; + + def getType(self) -> chr: + mask = 15; + + return (self.arr[1] >> 4) & mask; + + def getPayloadLength(self) -> int: + mask = 15; + + return self.arr[1] & mask; + + def getData(self) -> list[int]: + arr = [] + for i in range(self.getPayloadLength()): + arr.append(int.from_bytes(self.arr[2 + i * 2: 4 + i * 2], 'little')) + + return arr + + +messageIndex = 1 +def sendCommand(partID : chr, commandID : chr, pl : chr = 0): + global messageIndex + arr = bytearray(0 for x in range(3)) + + arr[0] |= 1 << 7 + arr[0] |= partID + arr[1] |= messageIndex + arr[2] |= commandID << 4 + arr[2] |= pl + + messageIndex += 1 + return arr, messageIndex - 1 diff --git a/app/content/sensors/arduino/igniter.py b/app/content/microcontroller/arduino/parts/igniter.py similarity index 69% rename from app/content/sensors/arduino/igniter.py rename to app/content/microcontroller/arduino/parts/igniter.py index b293b97..41a2cd6 100644 --- a/app/content/sensors/arduino/igniter.py +++ b/app/content/microcontroller/arduino/parts/igniter.py @@ -1,26 +1,21 @@ -from asyncio import Future, Task +from asyncio import Future from datetime import timedelta -from typing import Collection, Iterable, Tuple, Type, Union, cast +from typing import Iterable, Tuple, Type, Union from uuid import UUID -from dataclasses import dataclass -from app.content.general_commands.enable import DisableCommand, EnableCommand -from app.content.motor_commands.open import OpenCommand, CloseCommand, IgniteCommand -from app.content.sensors.arduino.servo import ServoSensor -from app.logic.commands.command import Command, Command -from app.content.general_commands.enable import DisableCommand, EnableCommand, ResetCommand +from app.content.motor_commands.open import IgniteCommand +from app.content.microcontroller.arduino.parts.servo import ServoSensor +from app.logic.commands.command import Command from app.content.microcontroller.arduino_serial import ArduinoSerial from app.logic.rocket_definition import Part, Rocket -from kivy.utils import platform - class IgniterSensor(Part): type = 'Igniter' enabled: bool = True - min_update_period = timedelta(milliseconds=20) + min_update_period = timedelta(milliseconds=100) min_measurement_period = timedelta(milliseconds=1000) @@ -38,6 +33,10 @@ class IgniterSensor(Part): state: str + commandList : dict() + + partID : chr + def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], arduino_parent: Union[ArduinoSerial, None], parachute: ServoSensor, start_enabled=True): self.arduino = arduino_parent self.enabled = start_enabled @@ -45,6 +44,15 @@ def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], ardu self.parachute = parachute super().__init__(_id, name, parent, list()) # type: ignore + self.partID = 2 + self.commandList = { 'Ignite' : 0 } + + self.arduino.addCallback(self.partID, self.proccessCommand) + + def proccessCommand(self, command : Command): + command.response_message = 'Ignited' + + self.arduino.launchPhase = 'LiftOff' def get_accepted_commands(self) -> list[Type[Command]]: return [IgniteCommand] @@ -60,24 +68,20 @@ def update(self, commands: Iterable[Command], now, iteration): if c.state == 'received': self.last_command = c - self.last_ignite_future = self.arduino.send_message(0x02, 0x01) + self.last_ignite_future = self.arduino.send_message(self.partID, self.commandList['Ignite']) self.last_ignited = now self.parachute_triggered = False + + self.arduino.commandProccessingDict[self.last_ignite_future.result()] = c c.state = 'processing' + if c.state == 'processing' and self.last_command != c: c.state = 'failed' c.response_message = 'Another ignite command was send, this command will no longer be processed' continue - if c.state == 'processing' and self.last_ignite_future is not None and self.last_ignite_future.done(): - exception = self.last_ignite_future.exception() - if exception is not None: - c.state = 'failed' - c.response_message = exception.args[0] - continue - c.state = 'success' - c.response_message = 'Igniter triggered' + else: c.state = 'failed' # Part cannot handle this command @@ -86,7 +90,7 @@ def update(self, commands: Iterable[Command], now, iteration): if self.arduino is not None and self.last_ignited is not None and not self.parachute_triggered and (now - self.last_ignited) >= self.deploy_parachute_delay: self.parachute_triggered = True - self.arduino.send_message(0x01, 0x04) + self.arduino.send_message(self.parachute.partID, self.parachute.commandList['Open']) # def add_command_to_queue(command_code: int, payload): diff --git a/app/content/sensors/arduino/servo.py b/app/content/microcontroller/arduino/parts/servo.py similarity index 74% rename from app/content/sensors/arduino/servo.py rename to app/content/microcontroller/arduino/parts/servo.py index 601dcb8..f1e69f3 100644 --- a/app/content/sensors/arduino/servo.py +++ b/app/content/microcontroller/arduino/parts/servo.py @@ -5,21 +5,18 @@ from dataclasses import dataclass from app.content.general_commands.enable import DisableCommand, EnableCommand -from app.content.motor_commands.open import OpenCommand, CloseCommand, IgniteCommand -from app.logic.commands.command import Command, Command -from app.content.general_commands.enable import DisableCommand, EnableCommand, ResetCommand +from app.content.motor_commands.open import OpenCommand, CloseCommand +from app.logic.commands.command import Command from app.content.microcontroller.arduino_serial import ArduinoSerial from app.logic.rocket_definition import Part, Rocket -from kivy.utils import platform - class ServoSensor(Part): type = 'Servo' enabled: bool = True - min_update_period = timedelta(milliseconds=20) + min_update_period = timedelta(milliseconds=200) min_measurement_period = timedelta(milliseconds=1000) @@ -31,15 +28,27 @@ class ServoSensor(Part): last_command: Union[None, Command] = None + commandList : dict() + + partID : chr + def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], arduino_parent: Union[ArduinoSerial, None],start_enabled=True): self.arduino = arduino_parent self.enabled = start_enabled self.state = 'close' super().__init__(_id, name, parent, list()) # type: ignore + self.partID = 1 + self.commandList = { 'Close' : 0, 'Open' : 1 } + self.arduino.addCallback(self.partID, self.proccessCommand) + + def proccessCommand(self, command : Command): + command.response_message = 'Servo activated' + + print("ssssssssss") def get_accepted_commands(self) -> list[Type[Command]]: - return [OpenCommand, CloseCommand] + return [DisableCommand, EnableCommand, OpenCommand, CloseCommand] def update(self, commands: Iterable[Command], now, iteration): @@ -55,34 +64,35 @@ def update(self, commands: Iterable[Command], now, iteration): c.response_message = 'Another ignite command was send, this command will no longer be processed' continue - if c.state == 'processing' and self.last_ignite_future is not None and self.last_ignite_future.done(): - exception = self.last_ignite_future.exception() - if exception is not None: - c.state = 'failed' - c.response_message = exception.args[0] - continue - c.state = 'success' - c.response_message = 'Servo actuated' + if c.state == 'processing': + print("jas") + continue + + if isinstance(c, CloseCommand): if c.state == 'received': self.last_command = c - self.last_ignite_future = self.arduino.send_message(0x01, 0x03) + self.last_ignite_future = self.arduino.send_message(self.partID, self.commandList["Close"]) + + self.arduino.commandProccessingDict[self.last_ignite_future.result()] = c c.state = 'processing' elif isinstance(c, OpenCommand): if c.state == 'received': self.last_command = c - self.last_ignite_future = self.arduino.send_message(0x01, 0x04) + self.last_ignite_future = self.arduino.send_message(self.partID, self.commandList["Open"]) + + self.arduino.commandProccessingDict[self.last_ignite_future.result()] = c c.state = 'processing' + else: c.state = 'failed' # Part cannot handle this command continue - # def add_command_to_queue(command_code: int, payload): def get_measurement_shape(self) -> Iterable[Tuple[str, Type]]: return [ diff --git a/app/content/microcontroller/arduino/sensors/orientation_arduino.py b/app/content/microcontroller/arduino/sensors/orientation_arduino.py new file mode 100644 index 0000000..c50deda --- /dev/null +++ b/app/content/microcontroller/arduino/sensors/orientation_arduino.py @@ -0,0 +1,66 @@ +from asyncio import Future, Task +from datetime import timedelta +from typing import Collection, Iterable, Tuple, Type, Union, cast +from uuid import UUID + +from dataclasses import dataclass +from app.content.general_commands.enable import DisableCommand, EnableCommand +from app.logic.commands.command import Command +from app.content.microcontroller.arduino_serial import ArduinoSerial +from app.logic.rocket_definition import Part, Rocket + +class OrientationSensor(Part): + type = 'Orientation' + + enabled: bool = True + + min_update_period = timedelta(milliseconds=20) + + min_measurement_period = timedelta(milliseconds=1000) + + arduino: Union[ArduinoSerial, None] + + OrientationX : float + OrientationY : float + OrientationZ : float + + def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], arduino_parent: Union[ArduinoSerial, None],start_enabled=True): + self.arduino = arduino_parent + self.enabled = start_enabled + + super().__init__(_id, name, parent, list()) # type: ignore + + partID = 0x54 + self.OrientationX = self.OrientationY = self.OrientationZ = 0.0 + self.arduino.addCallback(partID, self.set_measurements) + + def set_measurements(self, dataList : list[int]): + self.OrientationX = dataList[0] + self.OrientationY = dataList[1] + self.OrientationZ = dataList[2] + + def get_accepted_commands(self) -> list[Type[Command]]: + return [EnableCommand, DisableCommand] + + def update(self, commands: Iterable[Command], now, iteration): + + for c in commands: + + if isinstance(c, EnableCommand): + self.enabled = True + c.state = "success" + + elif isinstance(c, DisableCommand): + self.enabled = False + c.state = "success" + + def get_measurement_shape(self) -> Iterable[Tuple[str, Type]]: + return [ + ('X', float), + ('Y', float), + ('Z', float), + ] + + def collect_measurements(self, now, iteration) -> Iterable[Iterable[float]]: + return [[self.OrientationX, self.OrientationY, self.OrientationZ]] + diff --git a/app/content/microcontroller/arduino/sensors/pressure/altitude_arduino.py b/app/content/microcontroller/arduino/sensors/pressure/altitude_arduino.py new file mode 100644 index 0000000..895abac --- /dev/null +++ b/app/content/microcontroller/arduino/sensors/pressure/altitude_arduino.py @@ -0,0 +1,52 @@ +from asyncio import Future, Task +from datetime import timedelta +from typing import Collection, Iterable, Tuple, Type, Union, cast +from uuid import UUID + +from dataclasses import dataclass +from app.content.general_commands.enable import DisableCommand, EnableCommand +from app.logic.commands.command import Command +from app.logic.rocket_definition import Part, Rocket + + +class AltitudeSensor(Part): + type = 'Altitude' + + enabled: bool = True + + min_update_period = timedelta(milliseconds=20) + + min_measurement_period = timedelta(milliseconds=1000) + + altitude : float + + def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None],start_enabled=True): + self.enabled = start_enabled + + super().__init__(_id, name, parent, list()) # type: ignore + + self.altitude = 0.0 + + def get_accepted_commands(self) -> list[Type[Command]]: + return [EnableCommand, DisableCommand] + + def update(self, commands: Iterable[Command], now, iteration): + for c in commands: + + if isinstance(c, EnableCommand): + self.enabled = True + c.state = "success" + + elif isinstance(c, DisableCommand): + self.enabled = False + c.state = "success" + + + def get_measurement_shape(self) -> Iterable[Tuple[str, Type]]: + return [ + ('altitude', float), + ] + + def collect_measurements(self, now, iteration) -> Iterable[Iterable[float]]: + return [[self.altitude]] + diff --git a/app/content/microcontroller/arduino/sensors/pressure/pressure_arduino.py b/app/content/microcontroller/arduino/sensors/pressure/pressure_arduino.py new file mode 100644 index 0000000..67bea64 --- /dev/null +++ b/app/content/microcontroller/arduino/sensors/pressure/pressure_arduino.py @@ -0,0 +1,54 @@ +from asyncio import Future, Task +from datetime import timedelta +from typing import Collection, Iterable, Tuple, Type, Union, cast +from uuid import UUID + +from dataclasses import dataclass +from app.content.general_commands.enable import DisableCommand, EnableCommand +from app.logic.commands.command import Command +from app.logic.rocket_definition import Part, Rocket + + +class PressureSensor(Part): + type = 'Pressure' + + enabled: bool = True + + min_update_period = timedelta(milliseconds=20) + + min_measurement_period = timedelta(milliseconds=1000) + + pressure : float + + def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], start_enabled=True): + self.enabled = start_enabled + + super().__init__(_id, name, parent, list()) # type: ignore + + self.pressure = 0.0 + + + def get_accepted_commands(self) -> list[Type[Command]]: + return [EnableCommand, DisableCommand] + + def update(self, commands: Iterable[Command], now, iteration): + + for c in commands: + + if isinstance(c, EnableCommand): + self.enabled = True + c.state = "success" + + elif isinstance(c, DisableCommand): + self.enabled = False + c.state = "success" + + + def get_measurement_shape(self) -> Iterable[Tuple[str, Type]]: + return [ + ('pressure', float), + ] + + def collect_measurements(self, now, iteration) -> Iterable[Iterable[float]]: + return [[self.pressure]] + diff --git a/app/content/microcontroller/arduino/sensors/pressure/pressure_sensor_arduino.py b/app/content/microcontroller/arduino/sensors/pressure/pressure_sensor_arduino.py new file mode 100644 index 0000000..cc24df4 --- /dev/null +++ b/app/content/microcontroller/arduino/sensors/pressure/pressure_sensor_arduino.py @@ -0,0 +1,67 @@ +from datetime import timedelta +from typing import Iterable, Tuple, Type, Union +from uuid import UUID + +from app.content.general_commands.enable import DisableCommand, EnableCommand +from app.logic.commands.command import Command +from app.content.microcontroller.arduino_serial import ArduinoSerial +from app.content.microcontroller.arduino.sensors.pressure.temperature_arduino import TemperatureSensor +from app.content.microcontroller.arduino.sensors.pressure.pressure_arduino import PressureSensor +from app.content.microcontroller.arduino.sensors.pressure.altitude_arduino import AltitudeSensor +from app.logic.rocket_definition import Part, Rocket + +class PressureArduinoSensor(Part): + type = 'PressureArduinoSensor' + + enabled: bool = True + + min_update_period = timedelta(milliseconds=20) + + min_measurement_period = timedelta(milliseconds=1000) + + sensorsList : list() + + def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], arduino : ArduinoSerial, + temperatureSensor : TemperatureSensor, pressureSensor : PressureSensor, + altitudeSensor : AltitudeSensor, start_enabled=True): + + self.enabled = start_enabled + + super().__init__(_id, name, parent, list()) # type: ignore + + partID = 0x53 + + self.sensorsList = [] + self.sensorsList.append(temperatureSensor) + self.sensorsList.append(pressureSensor) + self.sensorsList.append(altitudeSensor) + + arduino.addCallback(partID, self.set_measurements) + + + def set_measurements(self, dataList : list[int]): + self.sensorsList[0].temperature = dataList[0] + self.sensorsList[1].pressure = dataList[1] + self.sensorsList[2].altitude = dataList[2] + + def get_accepted_commands(self) -> list[Type[Command]]: + return [EnableCommand, DisableCommand] + + def update(self, commands: Iterable[Command], now, iteration): + for c in commands: + + if isinstance(c, EnableCommand): + self.enabled = True + c.state = "success" + + elif isinstance(c, DisableCommand): + self.enabled = False + c.state = "success" + + + def get_measurement_shape(self) -> Iterable[Tuple[str, Type]]: + return [] + + def collect_measurements(self, now, iteration) -> Iterable[Iterable[float]]: + return [[]] + diff --git a/app/content/microcontroller/arduino/sensors/pressure/temperature_arduino.py b/app/content/microcontroller/arduino/sensors/pressure/temperature_arduino.py new file mode 100644 index 0000000..11bbb90 --- /dev/null +++ b/app/content/microcontroller/arduino/sensors/pressure/temperature_arduino.py @@ -0,0 +1,53 @@ +from asyncio import Future, Task +from datetime import timedelta +from typing import Collection, Iterable, Tuple, Type, Union, cast +from uuid import UUID + +from dataclasses import dataclass +from app.content.general_commands.enable import DisableCommand, EnableCommand +from app.logic.commands.command import Command +from app.logic.rocket_definition import Part, Rocket + + +class TemperatureSensor(Part): + type = 'Temperature' + + enabled: bool = True + + min_update_period = timedelta(milliseconds=20) + + min_measurement_period = timedelta(milliseconds=1000) + + temperature : float + + def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None],start_enabled=True): + self.enabled = start_enabled + + super().__init__(_id, name, parent, list()) # type: ignore + + self.temperature = 0.0 + + def get_accepted_commands(self) -> list[Type[Command]]: + return [EnableCommand, DisableCommand] + + def update(self, commands: Iterable[Command], now, iteration): + + for c in commands: + + if isinstance(c, EnableCommand): + self.enabled = True + c.state = "success" + + elif isinstance(c, DisableCommand): + self.enabled = False + c.state = "success" + + + def get_measurement_shape(self) -> Iterable[Tuple[str, Type]]: + return [ + ('temperature', float), + ] + + def collect_measurements(self, now, iteration) -> Iterable[Iterable[float]]: + return [[self.temperature]] + diff --git a/app/content/microcontroller/arduino_serial.py b/app/content/microcontroller/arduino_serial.py index b769d7b..a201369 100644 --- a/app/content/microcontroller/arduino_serial.py +++ b/app/content/microcontroller/arduino_serial.py @@ -2,20 +2,22 @@ import asyncio from datetime import timedelta import threading -from typing import Collection, Iterable, Tuple, Type, Union, cast +from typing import Collection, Iterable, Tuple, Type, Union from uuid import UUID from dataclasses import dataclass -from app.content.general_commands.enable import DisableCommand, EnableCommand -from app.content.motor_commands.open import OpenCommand, CloseCommand, IgniteCommand -from app.logic.commands.command import Command, Command +from app.logic.commands.command import Command from app.content.general_commands.enable import DisableCommand, EnableCommand, ResetCommand +from app.content.motor_commands.open import SetIgnitionPhaseCommand +from app.content.motor_commands.open import SetPreparationPhaseCommand from app.logic.rocket_definition import Part, Rocket from kivy.utils import platform import tinyproto +from app.content.microcontroller.arduino.messages.messages import SensorData, ResponseMessage, sendCommand + if platform == 'android': from usb4a import usb @@ -47,7 +49,7 @@ class ArduinoSerial(Part): connected: bool = False - min_update_period = timedelta(milliseconds=1000) + min_update_period = timedelta(milliseconds=50) min_measurement_period = timedelta(milliseconds=1000) @@ -72,8 +74,6 @@ class ArduinoSerial(Part): hdlc: Union[tinyproto.Hdlc, None] - current_message = bytearray([]) - expected_next_response_part: Union[int, None] = None expected_next_response_command: Union[int, None] = None @@ -82,16 +82,19 @@ class ArduinoSerial(Part): logs = [] - part_activated: Union[bytes, None] - part_state: Union[str, None] + launchPhase : str - commands_list = [] + sensorsCallBack : dict() + partID : chr - def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], start_enabled = True): - self.part_state = None - self.part_state = None + commandProccessingDict : dict() + + commandList : dict() + + errorMessageDict : dict() + def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], start_enabled = True): self.enabled = start_enabled super().__init__(_id, name, parent, list()) # type: ignore @@ -99,8 +102,36 @@ def __init__(self, _id: UUID, name: str, parent: Union[Part, Rocket, None], star self.hdlc = None + self.launchPhase = 'Preparation' + self.sensorsCallBack = dict() + + self.partID = 0 + self.commandList = { 'Reset' : 0, 'Preparation' : 1, 'Ignition' : 2, 'LiftOff' : 3 } + + self.commandProccessingDict = dict() + self.addCallback(self.partID, self.proccessCommand) + + self.errorMessageDict = dict() + self.errorMessageDict[0] = "Success" + self.errorMessageDict[1] = "Failed : Incompatible Launch Phase" + self.errorMessageDict[2] = "Failed : Incorrect Part Byte" + self.errorMessageDict[3] = "Failed : Incorrect Command Byte" + self.errorMessageDict[4] = "Failed" + + def proccessCommand(self, command : Command): + command.response_message = 'Command activated' + + if isinstance(command, SetIgnitionPhaseCommand): + self.launchPhase = 'Ignition' + + elif isinstance(command, SetPreparationPhaseCommand): + self.launchPhase = 'Preparation' + + def addCallback(self, key : chr, fun): + self.sensorsCallBack[key] = fun + def try_get_device_list(self): if platform == 'android': usb_device_list = usb.get_usb_device_list() @@ -111,7 +142,6 @@ def try_get_device_list(self): usb_device_list = list_ports.comports() self.device_name_list = [port.device for port in usb_device_list] - def try_connect_device_in_background(self, device_name: str): # Only one connect at a time @@ -139,8 +169,6 @@ def try_connect_last_device_background(self): self.try_connect_device_in_background(self.last_selected_device) - - async def try_connect_device(self, device_name: str) -> str: # Hack to have this running the background @@ -194,50 +222,54 @@ def read_msg_thread(self): self.hdlc = hdlc - def on_read(b: bytes): + def on_read(a): + print(a) + if a[0] >> 7 | 0 == 1: + response = ResponseMessage(a) - if len(b) < 3: - print('skip') - return + if response.getResponseRequestByte() == 1: + index = response.getIndex() + result = response.getResult() - payload_size = int(b[2]) - payload = b[3:-1] if payload_size > 0 else bytes() + if index in self.commandProccessingDict: + print(index, result) - package = RssPacket(int(b[0]), int(b[1]), payload_size, payload) + command = self.commandProccessingDict[index] - #print(package) + if result == 0: + command.state = 'success' + self.sensorsCallBack[response.getPart()](command) - self.last_message = package + else: + command.state = 'failed' + command.response_message = self.errorMessageDict[result] + + self.commandProccessingDict.pop(index) + + + else: + message, _ = sendCommand(self.partID, self.commandList[self.launchPhase]) + self.send_message_hdlc(message) + + else: + sensorData = SensorData(a) + self.sensorsCallBack[sensorData.getPart()](sensorData.getData()) - hdlc.on_read = on_read hdlc.crc = 8 + hdlc.on_read = on_read hdlc.begin() try: while True: - #print(self.logs) with self.port_thread_lock: if not self.serial_port.is_open: break - received_msg = self.serial_port.read( - self.serial_port.in_waiting - ) - if received_msg: - print(received_msg) - # hdlc.rx(received_msg) - - - for i in received_msg: - if len(self.current_message) and self.current_message[-1] != 0x7E and i == 0x7E: - self.current_message.append(i) - self.logs.append(self.current_message) - self.parse() - self.current_message = bytearray([]) - else: - self.current_message.append(i) + received_msg = self.serial_port.read(1) + if received_msg: + hdlc.rx(received_msg) except Exception as ex: self.connected = False @@ -248,27 +280,9 @@ def on_read(b: bytes): print(f'crash read thread {ex.args[0]}') raise ex - def parse(self): - - part = self.current_message[3] - command = self.current_message[4] - success_bit = self.current_message[5] - success = success_bit == 0x01 - - if self.response_future is None or self.response_future.done(): - return - - if part != self.expected_next_response_part: - self.response_future.set_exception(Exception('Received response from arduino for wrong part (commands where send to fast)')) - elif command != self.expected_next_response_command: - self.response_future.set_exception(Exception('Received response from arduino for wrong command (commands where send to fast)')) - elif not success: - self.response_future.set_exception(Exception('The arduino send back that execution of the command was unsuccessful')) - else: - self.response_future.set_result(None) def get_accepted_commands(self) -> list[Type[Command]]: - return [EnableCommand, DisableCommand, ResetCommand] + return [EnableCommand, DisableCommand, ResetCommand, SetPreparationPhaseCommand, SetIgnitionPhaseCommand] def send_message_hdlc(self, message: bytearray): if self.serial_port is None or self.hdlc is None: @@ -277,7 +291,7 @@ def send_message_hdlc(self, message: bytearray): self.hdlc.put(message) self.serial_port.write(self.hdlc.tx()) - def send_message(self, part: int, command: int): + def send_message(self, partID : chr, commandID : chr): '''Sends the given message to the arduino and returns a future that will be completed if the command got processed. If the command did not get processed or the connection dies the future will throw''' @@ -287,32 +301,56 @@ def send_message(self, part: int, command: int): future = asyncio.Future() self.response_future = future - self.expected_next_response_part = part - self.expected_next_response_command = command + + message, index = sendCommand(partID, commandID) + future.set_result(index) try: - self.send_message_hdlc(bytearray([0x7E, 0xFF, 0x4F, part, command, 0x7E])) + self.send_message_hdlc(message) except Exception as e: future.set_exception(e) return future - def reset_arduino(self): - return self.send_message(0x00, 0x01) def update(self, commands: Iterable[Command], now, iteration): for c in commands: + if c.state == 'processing' and self.last_command != c: + c.state = 'failed' + c.response_message = 'Another ignite command was send, this command will no longer be processed' + continue + + if isinstance(c, EnableCommand): self.enabled = True c.state = "success" elif isinstance(c, DisableCommand): self.enabled = False c.state = "success" + elif isinstance(c, ResetCommand): - self.reset_arduino() + self.send_message(self.partID, self.commandList['Reset']) c.state = "success" + + elif isinstance(c, SetPreparationPhaseCommand): + if c.state == 'received': + self.last_command = c + self.last_ignite_future = self.send_message(self.partID, self.commandList['Preparation']) + + self.commandProccessingDict[self.last_ignite_future.result()] = c + c.state = 'processing' + + + elif isinstance(c, SetIgnitionPhaseCommand): + if c.state == 'received': + self.last_command = c + self.last_ignite_future = self.send_message(self.partID, self.commandList['Ignition']) + + self.commandProccessingDict[self.last_ignite_future.result()] = c + c.state = 'processing' + else: c.state = 'failed' # Part cannot handle this command continue diff --git a/app/content/motor_commands/open.py b/app/content/motor_commands/open.py index db7fb71..cdeff01 100644 --- a/app/content/motor_commands/open.py +++ b/app/content/motor_commands/open.py @@ -34,5 +34,49 @@ class IgniteCommand(Command): response_schema = BasicErrorResponseSchema() + def set_payload(self, payload): + pass + +class SetPreparationPhaseCommand(Command): + + command_type = 'Control.SetPreparationPhase' + + payload_schema = None + + response_schema = BasicErrorResponseSchema() + + def set_payload(self, payload): + pass + +class SetIgnitionPhaseCommand(Command): + + command_type = 'Control.SetIgnitionPhasePhase' + + payload_schema = None + + response_schema = BasicErrorResponseSchema() + + def set_payload(self, payload): + pass + +class SetLiftoffPhaseCommand(Command): + + command_type = 'Control.SetLiftoffPhase' + + payload_schema = None + + response_schema = BasicErrorResponseSchema() + + def set_payload(self, payload): + pass + +class SetRecoveryPhaseCommand(Command): + + command_type = 'Control.SetRecoveryPhase' + + payload_schema = None + + response_schema = BasicErrorResponseSchema() + def set_payload(self, payload): pass \ No newline at end of file diff --git a/app/flight_executer.py b/app/flight_executer.py index 7da2788..3750ddc 100755 --- a/app/flight_executer.py +++ b/app/flight_executer.py @@ -74,7 +74,7 @@ def draw_overview(self): self.clear_widgets() - self.add_widget(Label(text='Parts', size_hint=(1, 0.3))) + self.add_widget(Label(text='parts', size_hint=(1, 0.3))) for ui in self.part_uis: diff --git a/app/logic/rocket_definition.py b/app/logic/rocket_definition.py index 648a656..f7dc965 100755 --- a/app/logic/rocket_definition.py +++ b/app/logic/rocket_definition.py @@ -11,7 +11,7 @@ #Maybe # PART_CATEGORY_SENSOR = 'SENSOR' -# ''' Meant for any input data, i.e. sensors. Sensors will always be called first in the update order''' +# ''' Meant for any input data, i.e. sensors. sensors will always be called first in the update order''' # PART_CATEGORY_DIRECTOR = 'DIRECTOR' @@ -79,7 +79,7 @@ class Part(ABC): def __init__(self, _id: UUID, name: str, parent: Union[Self, Rocket, None], dependencies: Iterable[Self]): ''' - :param dependencies: Parts that will be updated before this part + :param dependencies: parts that will be updated before this part ''' self._id = _id self.name = name diff --git a/app/rockets/make_spatula.py b/app/rockets/make_spatula.py index b6fc6ef..8782300 100755 --- a/app/rockets/make_spatula.py +++ b/app/rockets/make_spatula.py @@ -1,30 +1,27 @@ # from app.content.measurement_sinks.api_measurement_sink_ui import ApiMeasurementSinkUI -from app.content.flight_director.flight_director import FlightDirector from app.content.measurement_sinks.api_measurement_sink import ApiMeasurementSink from app.content.microcontroller.arduino_serial import ArduinoSerial from app.content.microcontroller.arduino_serial_monitor_ui import ArduinoSerialMonitorUI from app.content.microcontroller.arduino_serial_select_ui import ArduinoSerialSelectUI from app.content.sensors.android_native.gyroscope_pyjinius import PyjiniusGyroscopeSensor from app.content.sensors.android_native.inertial_reference_frame import InertialReferenceFrame -from app.content.sensors.plyer.acceleration_plyer import PlyerAccelerationSensor from app.content.sensors.android_native.acceleration_pyjinius import PyjiniusAccelerationSensor from app.content.sensors.plyer.framerate import FramerateSensor from app.content.sensors.plyer.gps_plyer import PlyerGPSSensor -from app.content.sensors.plyer.temperature_plyer import PlyerTemperatureSensor -from app.content.sensors.plyer.barometer_plyer import PlyerBarometerSensor -from app.content.sensors.plyer.gyroscope_plyer import PlyerGyroscopeSensor -from app.content.sensors.plyer.light_plyer import PlyerLightSensor -from app.content.sensors.plyer.gravity_plyer import PlyerGravitySensor from app.content.sensors.plyer.battery_plyer import PlyerBatterySensor -from app.content.sensors.arduino.servo import ServoSensor -from app.content.sensors.arduino.igniter import IgniterSensor +from app.content.microcontroller.arduino.parts.servo import ServoSensor +from app.content.microcontroller.arduino.parts.igniter import IgniterSensor -from app.content.sensors.plyer.spatial_orientation_plyer import PlyerSpatialOrientationSensor +from app.content.microcontroller.arduino.sensors.pressure.temperature_arduino import TemperatureSensor +from app.content.microcontroller.arduino.sensors.pressure.pressure_arduino import PressureSensor +from app.content.microcontroller.arduino.sensors.pressure.altitude_arduino import AltitudeSensor +from app.content.microcontroller.arduino.sensors.pressure.pressure_sensor_arduino import PressureArduinoSensor +from app.content.microcontroller.arduino.sensors.orientation_arduino import OrientationSensor -from app.flight_config import FlightConfig +from app.flight_config import FlightConfig from app.logic.rocket_definition import Rocket # from app.ui.part_ui import PartUi @@ -58,10 +55,21 @@ def make_spatula() -> FlightConfig: inertialFrame = InertialReferenceFrame(acc, gyro, UUID('27f5d5e0-5fa9-4ae1-88af-8477d80960d7'), 'Intertial Reference Frame', rocket) # # Serial communication + # Arduino parts arduino_serial = ArduinoSerial(UUID('cd170fff-0138-4820-8e97-969eb3f2f287'), 'Serial Port', rocket) parachute = ServoSensor(UUID('9f86acb1-9795-46fc-b083-e6451f214d1f'), 'Servo', rocket, arduino_serial) igniter = IgniterSensor(UUID('f309669d-6bd7-4ee3-90a5-45a0e1bdd60e'), 'Igniter', rocket, arduino_serial, parachute) + # Arduino sensors + orientation = OrientationSensor(UUID('158314cc-6d1f-11ee-b962-0242ac120002'), 'Orientation', rocket, arduino_serial) + + # Pressure arduino sensor + temperature = TemperatureSensor(UUID('ac93964a-6bb0-11ee-b962-0242ac120002'), 'Temperature', rocket) + pressure = PressureSensor(UUID('eedd649e-78c7-11ee-b962-0242ac120002'), 'Pressure', rocket) + altitude = AltitudeSensor(UUID('f526cb42-78c7-11ee-b962-0242ac120002'), 'Altitude', rocket) + pressureArduino = PressureArduinoSensor(UUID('8ed5e972-8cb3-11ee-b9d1-0242ac120002'), 'Pressure Sensor', rocket, + arduino_serial, temperature, pressure, altitude) + # FlightDirector(UUID('37155a2c-c51d-41b7-9dae-67d640d8c284'), 'Flight Director', rocket, arduino_serial, igniter, parachute, acc, gyro, inertialFrame) return FlightConfig(rocket, [ArduinoSerialSelectUI(arduino_serial), ArduinoSerialMonitorUI(arduino_serial)], True)