From 1824a4b65c89c596641fbce6947c131e3ecd971f Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 26 Oct 2024 10:33:48 -0600 Subject: [PATCH 01/14] Upload FSM for ack nak command and control msg --- backend/src/CommControlMsg.puml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 backend/src/CommControlMsg.puml diff --git a/backend/src/CommControlMsg.puml b/backend/src/CommControlMsg.puml new file mode 100644 index 0000000..35d9b77 --- /dev/null +++ b/backend/src/CommControlMsg.puml @@ -0,0 +1,14 @@ +@startuml +[*] -right-> RCUInitialized : start +[*] -right-> RocketListening : start +RCUInitialized : RCU is on +RCUInitialized -right-> RCUWait : send first msg +RCUWait -up-> RCUTimeout : does not receive ack from rocket +RCUWait -up-> RCURetransmit : received nak from rocket +RCUWait -down-> RCUSendNextCMD : received ack from rocket +RCUWait -down-> RCUSendNextCMD : retransmit twice +RCUTimeout --> RCURetransmit +RCURetransmit --> RCUWait +RocketListening --> RocketInitialized : receive initialization sequence +RocketInitialized --> RocketReadyForCMD : send ack to rcu +@enduml \ No newline at end of file From ba7172926a78c7201681eebd0beb371ce86cbd98 Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 26 Oct 2024 13:59:14 -0600 Subject: [PATCH 02/14] Add StateMachine States/Events enum for RCU/rocket comm. Add BaseStateMachine API to make StateMachine more reusable --- backend/src/SerialHandler.py | 9 +++ backend/src/StateMachine/BaseStateMachine.py | 38 ++++++++++++ backend/src/StateMachine/StateMachine.py | 64 ++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 backend/src/StateMachine/BaseStateMachine.py create mode 100644 backend/src/StateMachine/StateMachine.py diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index 00f1869..05d524e 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -18,6 +18,7 @@ from src.support.ProtobufParser import ProtobufParser from src.support.CommonLogger import logger +from backend.src.StateMachine.StateMachine import State, Event, StateMachineManager from src.ThreadManager import THREAD_MESSAGE_DB_WRITE, THREAD_MESSAGE_HEARTBEAT_SERIAL, THREAD_MESSAGE_KILL, THREAD_MESSAGE_LOAD_CELL_VOLTAGE, THREAD_MESSAGE_SERIAL_WRITE, WorkQ_Message from src.Utils import Utils as utl @@ -258,6 +259,14 @@ def serial_rx_thread(ser_han: SerialHandler): ser_han.handle_serial_message() pass +def serial_command_control_message_state_machine (self, command: str, target: str, command_param: int, source_sequence_number: int): + #Send command/control message + + #Wait for response from DMB + + #Handle response + return + def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) -> bool: """ Process the message from the workq. diff --git a/backend/src/StateMachine/BaseStateMachine.py b/backend/src/StateMachine/BaseStateMachine.py new file mode 100644 index 0000000..c55f050 --- /dev/null +++ b/backend/src/StateMachine/BaseStateMachine.py @@ -0,0 +1,38 @@ +""" +BaseStateMachine.py + +Define a common Application Program Interface(API) for a set of State Machine subclasses. +Enable code reuse and maintainability when the code base grows in the future. The implementation of the methods +are left for the subclasses that inherit from this class. + +Since the BaseStateMachine is an abstract class. Do NOT instantiate this because there is no actual implementation. + +""" + +# General imports ================================================================================= +from abc import ABC, abstractmethod + +# Class Definitions =============================================================================== +class BaseStateMachine(ABC): + + @abstractmethod + def enter_state(self): + pass + @abstractmethod + def exit_state(self): + pass + @abstractmethod + def update_state(self): + pass + @abstractmethod + def get_next_state(self): + pass + @abstractmethod + def on_trigger_enter_state(self): + pass + @abstractmethod + def on_trigger_stay_state(self): + pass + @abstractmethod + def on_trigger_exit_state(self): + pass diff --git a/backend/src/StateMachine/StateMachine.py b/backend/src/StateMachine/StateMachine.py new file mode 100644 index 0000000..2fef30f --- /dev/null +++ b/backend/src/StateMachine/StateMachine.py @@ -0,0 +1,64 @@ +""" +StateMachine.py + +This file will contain the class State and class Event to make things more modular and easier to add/remove +states and events in the future. This design pattern is selected to make it simple for a direct +translation between the FSM (Finite State Machine) and code. This aims to make it easier to understand +and work with for the future developers, who will be mostly students Both classes will utilize Enum +because it is a common way being used in our code base currently. + +The FSM diagram is CommControlMSG.puml. + +""" + +# General imports ================================================================================= +from enum import Enum, unique +# Project specific imports ======================================================================== +from backend.src.StateMachine.BaseStateMachine import BaseStateMachine + +# Class Definitions =============================================================================== +@unique +class State(Enum): + """ + Represents the states of the rocket and the rcu. If there is a use case to make the state class more general, + and the RocketState and RCUState to be a subclass, we can consider that option at that time. Right now, we don't have + enough states to justify using inheritance and subclasses. + + """ + RCU_INITIALIZED = 0 + RCU_WAIT = 1 + RCU_RETRANSMIT = 2 + RCU_TIMEOUT = 3 + RCU_SEND_NEXT_CMD = 4 + ROCKET_LISTENING = 5 + ROCKET_INITIALIZED = 6 + ROCKET_READY_FOR_CMD = 7 + +@unique +class Event(Enum): + """ + Represents the event that triggers the transition from one state to another. The events that trigger state transition + for the RCU and Rocket are both grouped in here. If there is a case to make RocketEvent and RCUEvent subclass to + inherit from the Event enum, we can explore that option at the time. For now, because state machine isn't being used + too much in our code base, we will stick with this for now. + + """ + RCU_SEND_MSG = 0 + RCU_RCVD_ACK = 1 + RCU_RCVD_NAK = 2 + RCU_NO_RCVD_ACK = 3 + RCU_RETRANSMIT_TWICE = 4 + ROCKET_LISTENING = 5 + ROCKET_RCVD_CMD = 6 + ROCKET_SEND_ACK = 7 + +class StateMachineManager(BaseStateMachine): + """ + Manage state transitions for the communication between the RCU and the rocket. + """ + pass + + + + + From bda0ec7358d5fa774e780d976a6f35f3bc3ccef2 Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 26 Oct 2024 15:52:12 -0600 Subject: [PATCH 03/14] Adjustment to StateMachineManager and BaseStateMachine to make sure the code runs so I can test as I go --- backend/src/SerialHandler.py | 2 +- backend/src/StateMachine/BaseStateMachine.py | 48 ++++--- backend/src/StateMachine/StateMachine.py | 64 --------- .../src/StateMachine/StateMachineManager.py | 128 ++++++++++++++++++ backend/src/StateMachine/test.py | 42 ++++++ 5 files changed, 202 insertions(+), 82 deletions(-) delete mode 100644 backend/src/StateMachine/StateMachine.py create mode 100644 backend/src/StateMachine/StateMachineManager.py create mode 100644 backend/src/StateMachine/test.py diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index 05d524e..55f1147 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -18,7 +18,7 @@ from src.support.ProtobufParser import ProtobufParser from src.support.CommonLogger import logger -from backend.src.StateMachine.StateMachine import State, Event, StateMachineManager +from backend.src.StateMachine.StateMachineManager import State, Event, StateMachineManager from src.ThreadManager import THREAD_MESSAGE_DB_WRITE, THREAD_MESSAGE_HEARTBEAT_SERIAL, THREAD_MESSAGE_KILL, THREAD_MESSAGE_LOAD_CELL_VOLTAGE, THREAD_MESSAGE_SERIAL_WRITE, WorkQ_Message from src.Utils import Utils as utl diff --git a/backend/src/StateMachine/BaseStateMachine.py b/backend/src/StateMachine/BaseStateMachine.py index c55f050..ec81ae3 100644 --- a/backend/src/StateMachine/BaseStateMachine.py +++ b/backend/src/StateMachine/BaseStateMachine.py @@ -16,23 +16,37 @@ class BaseStateMachine(ABC): @abstractmethod - def enter_state(self): - pass - @abstractmethod - def exit_state(self): - pass - @abstractmethod - def update_state(self): - pass - @abstractmethod - def get_next_state(self): - pass - @abstractmethod - def on_trigger_enter_state(self): - pass - @abstractmethod - def on_trigger_stay_state(self): + def start(self): pass + @abstractmethod - def on_trigger_exit_state(self): + def exit(self): pass + + # @abstractmethod + # def enter_state(self): + # pass + + # @abstractmethod + # def exit_state(self): + # pass + + # @abstractmethod + # def update_state(self): + # pass + + # @abstractmethod + # def get_next_state(self): + # pass + + # @abstractmethod + # def on_trigger_enter_state(self): + # pass + + # @abstractmethod + # def on_trigger_stay_state(self): + # pass + + # @abstractmethod + # def on_trigger_exit_state(self): + # pass diff --git a/backend/src/StateMachine/StateMachine.py b/backend/src/StateMachine/StateMachine.py deleted file mode 100644 index 2fef30f..0000000 --- a/backend/src/StateMachine/StateMachine.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -StateMachine.py - -This file will contain the class State and class Event to make things more modular and easier to add/remove -states and events in the future. This design pattern is selected to make it simple for a direct -translation between the FSM (Finite State Machine) and code. This aims to make it easier to understand -and work with for the future developers, who will be mostly students Both classes will utilize Enum -because it is a common way being used in our code base currently. - -The FSM diagram is CommControlMSG.puml. - -""" - -# General imports ================================================================================= -from enum import Enum, unique -# Project specific imports ======================================================================== -from backend.src.StateMachine.BaseStateMachine import BaseStateMachine - -# Class Definitions =============================================================================== -@unique -class State(Enum): - """ - Represents the states of the rocket and the rcu. If there is a use case to make the state class more general, - and the RocketState and RCUState to be a subclass, we can consider that option at that time. Right now, we don't have - enough states to justify using inheritance and subclasses. - - """ - RCU_INITIALIZED = 0 - RCU_WAIT = 1 - RCU_RETRANSMIT = 2 - RCU_TIMEOUT = 3 - RCU_SEND_NEXT_CMD = 4 - ROCKET_LISTENING = 5 - ROCKET_INITIALIZED = 6 - ROCKET_READY_FOR_CMD = 7 - -@unique -class Event(Enum): - """ - Represents the event that triggers the transition from one state to another. The events that trigger state transition - for the RCU and Rocket are both grouped in here. If there is a case to make RocketEvent and RCUEvent subclass to - inherit from the Event enum, we can explore that option at the time. For now, because state machine isn't being used - too much in our code base, we will stick with this for now. - - """ - RCU_SEND_MSG = 0 - RCU_RCVD_ACK = 1 - RCU_RCVD_NAK = 2 - RCU_NO_RCVD_ACK = 3 - RCU_RETRANSMIT_TWICE = 4 - ROCKET_LISTENING = 5 - ROCKET_RCVD_CMD = 6 - ROCKET_SEND_ACK = 7 - -class StateMachineManager(BaseStateMachine): - """ - Manage state transitions for the communication between the RCU and the rocket. - """ - pass - - - - - diff --git a/backend/src/StateMachine/StateMachineManager.py b/backend/src/StateMachine/StateMachineManager.py new file mode 100644 index 0000000..734c1d4 --- /dev/null +++ b/backend/src/StateMachine/StateMachineManager.py @@ -0,0 +1,128 @@ +""" +StateMachine.py + +This file will contain the class State and class Event to make things more modular and easier to add/remove +states and events in the future. This design pattern is selected to make it simple for a direct +translation between the FSM (Finite State Machine) and code. This aims to make it easier to understand +and work with for the future developers, who will be mostly students Both classes will utilize Enum +because it is a common way being used in our code base currently. + +The FSM diagram is CommControlMSG.puml. + +""" + +# General imports ================================================================================= +from enum import Enum, unique +import os, sys + +# Project specific imports ======================================================================== +dirname, _ = os.path.split(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(dirname.split("backend", 1)[0], 'backend')) +sys.path.insert(0, os.path.join(dirname.split("backend", 1)[0], 'src/StateMachine')) +from src.StateMachine.BaseStateMachine import BaseStateMachine +# import proto.Python.CoreProto_pb2 as ProtoCore +# import src.StateMachine.BaseStateMachine as BaseStateMachine + + + +# Class Definitions =============================================================================== +@unique +class State(Enum): + """ + Represents the states of the rocket and the rcu. If there is a use case to make the state class more general, + and the RocketState and RCUState to be a subclass, we can consider that option at that time. Right now, we don't have + enough states to justify using inheritance and subclasses. + + """ + RCU_UNINITIALIZED = 0 + RCU_INITIALIZED = 1 + RCU_WAIT = 2 + RCU_RETRANSMIT = 3 + RCU_TIMEOUT = 4 + RCU_SEND_NEXT_CMD = 5 + ROCKET_UNINITIALIZED = 6 + ROCKET_LISTENING = 7 + ROCKET_INITIALIZED = 8 + ROCKET_READY_FOR_CMD = 9 + +@unique +class Event(Enum): + """ + Represents the event that triggers the transition from one state to another. The events that trigger state transition + for the RCU and Rocket are both grouped in here. If there is a case to make RocketEvent and RCUEvent subclass to + inherit from the Event enum, we can explore that option at the time. For now, because state machine isn't being used + too much in our code base, we will stick with this for now. + + """ + RCU_SEND_MSG = 0 + RCU_RCVD_ACK = 1 + RCU_RCVD_NAK = 2 + RCU_NO_RCVD_ACK = 3 + RCU_RETRANSMIT_TWICE = 4 + RCU_EXIT = 5 + RCU_START = 6 + ROCKET_RCVD_CMD = 7 + ROCKET_SEND_ACK = 8 + +class StateMachineManager(BaseStateMachine): + """ + Manage state transitions for the communication between the RCU and the rocket. + """ + def start(self): + """ + Start the state machine for RCU and Rocket serial communication by setting the RCU state to be + initialized and event to RCU Start. + """ + self.rcu_state = State.RCU_INITIALIZED + self.rocket_state = State.ROCKET_LISTENING + self.event = Event.RCU_START + + def exit(self): + """ + Exit the state machine for RCU and Rocket serial communication by by setting RCU state to be uninitialized + and event to RCU Exit. + """ + self.rcu_state = State.RCU_UNINITIALIZED + self.rocket_state = State.ROCKET_UNINITIALIZED + self.event = Event.RCU_EXIT + + def get_rcu_state(self): + print("\nThe current RCU state:") + return self.rcu_state + + def get_rocket_state(self): + print("\nThe current rocket state:") + return self.rocket_state + + def get_event(self): + print("\nThe current event:") + return self.event + + def transition(self, event, from_state, to_state): + #Check to make sure inputs are part of the enum list + + #Check if event is a valid transition from from_state + + #Check if event is a valid transition to to_state + + #if valid, then set the current event and current state + + #Idk if I want to code the update step separately from the transition step yet - prob to break it up tbh + return + + + +#Test - Do NOT commit +test = StateMachineManager() +print(test.start()) +print(test.get_event()) +print(test.get_rcu_state()) +print(test.get_rocket_state()) + + + + + + + + diff --git a/backend/src/StateMachine/test.py b/backend/src/StateMachine/test.py new file mode 100644 index 0000000..b11db34 --- /dev/null +++ b/backend/src/StateMachine/test.py @@ -0,0 +1,42 @@ +# Python program showing +# abstract base class work +from abc import ABC, abstractmethod + + +class Polygon(ABC): + + @abstractmethod + def noofsides(self): + pass + + @abstractmethod + def start(self): + pass + + +class StateMachineManager(Polygon): + """ + Manage state transitions for the communication between the RCU and the rocket. + """ + # def __init__(self): + # self.state = State(6) + # self.event = Event(3) + def start(self): + print("Im here") + +#Test - Do NOT commit +test = StateMachineManager() +test.start() + +class Triangle(Polygon): + + # overriding abstract method + def noofsides(self): + print("I have 3 sides") + + def start(self): + print("Starting") + + +test = StateMachineManager() +test.start() \ No newline at end of file From 12c7a2c181df4f1dfceb54908c9e81590ca5ce60 Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:11:11 -0600 Subject: [PATCH 04/14] Move all the states from StateMachineManager for system and rocket state into CoreProto.proto and ControlMessage.proto. Generate new .py and pyi files --- backend/proto/ControlMessage.proto | 4 ++ backend/proto/CoreProto.proto | 2 + backend/proto/Python/CommandMessage_pb2.py | 16 +++++-- backend/proto/Python/ControlMessage_pb2.py | 38 +++++++++------ backend/proto/Python/ControlMessage_pb2.pyi | 8 ++++ backend/proto/Python/CoreProto_pb2.py | 20 ++++++-- backend/proto/Python/CoreProto_pb2.pyi | 4 ++ backend/proto/Python/TelemetryMessage_pb2.py | 16 +++++-- backend/src/StateMachine/BaseStateMachine.py | 6 +-- .../src/StateMachine/StateMachineManager.py | 47 +++++++++---------- 10 files changed, 108 insertions(+), 53 deletions(-) diff --git a/backend/proto/ControlMessage.proto b/backend/proto/ControlMessage.proto index adad85b..b287334 100644 --- a/backend/proto/ControlMessage.proto +++ b/backend/proto/ControlMessage.proto @@ -87,6 +87,10 @@ message SystemState { // SoarProto v1.3.2 SYS_HEARTBEAT_LOSS_HALF_WARNING = 5; // The system has lost heartbeat and 1/2 of the time to abort has passed SYS_HEARTBEAT_LOST_ABORTING = 6; // The system has lost heartbeat and is aborting + SYS_WAIT = 7; // System has sent commands to DMB + SYS_RETRANSMIT = 8; // System timeout or receive nak from DMB + SYS_TIMEOUT = 9; // System does not receive ack in a fixed time window + SYS_SEND_NEXT_CMD = 10; // System receives ack or retransmit twice } State sys_state = 1; // The current state of the system diff --git a/backend/proto/CoreProto.proto b/backend/proto/CoreProto.proto index 570edac..1889755 100644 --- a/backend/proto/CoreProto.proto +++ b/backend/proto/CoreProto.proto @@ -51,4 +51,6 @@ enum RocketState { RS_ABORT = 10; RS_TEST = 11; RS_NONE = 12; + RS_LISTENING = 13; + RS_READY_FOR_CMD = 14; } \ No newline at end of file diff --git a/backend/proto/Python/CommandMessage_pb2.py b/backend/proto/Python/CommandMessage_pb2.py index 0a3f2df..08d2980 100644 --- a/backend/proto/Python/CommandMessage_pb2.py +++ b/backend/proto/Python/CommandMessage_pb2.py @@ -1,12 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: CommandMessage.proto -# Protobuf Python Version: 4.25.3 +# Protobuf Python Version: 5.28.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 28, + 3, + '', + 'CommandMessage.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -20,8 +30,8 @@ _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'CommandMessage_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None _globals['_COMMANDMESSAGE']._serialized_start=49 _globals['_COMMANDMESSAGE']._serialized_end=331 _globals['_DMBCOMMAND']._serialized_start=334 diff --git a/backend/proto/Python/ControlMessage_pb2.py b/backend/proto/Python/ControlMessage_pb2.py index 03c3fa4..37304c2 100644 --- a/backend/proto/Python/ControlMessage_pb2.py +++ b/backend/proto/Python/ControlMessage_pb2.py @@ -1,12 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: ControlMessage.proto -# Protobuf Python Version: 4.25.3 +# Protobuf Python Version: 5.28.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 28, + 3, + '', + 'ControlMessage.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -15,13 +25,13 @@ import CoreProto_pb2 as CoreProto__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x43ontrolMessage.proto\x12\x05Proto\x1a\x0f\x43oreProto.proto\"\xec\x02\n\x0e\x43ontrolMessage\x12\x1b\n\x06source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x06target\x18\x02 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x13source_sequence_num\x18\x04 \x01(\r\x12\x1d\n\x03\x61\x63k\x18\x05 \x01(\x0b\x32\x0e.Proto.AckNackH\x00\x12\x1e\n\x04nack\x18\x06 \x01(\x0b\x32\x0e.Proto.AckNackH\x00\x12\x1b\n\x04ping\x18\x07 \x01(\x0b\x32\x0b.Proto.PingH\x00\x12\x1e\n\x02hb\x18\x08 \x01(\x0b\x32\x10.Proto.HeartbeatH\x00\x12\'\n\tsys_state\x18\t \x01(\x0b\x32\x12.Proto.SystemStateH\x00\x12(\n\x08sys_ctrl\x18\n \x01(\x0b\x32\x14.Proto.SystemControlH\x00\x12)\n\x08hb_state\x18\x0b \x01(\x0b\x32\x15.Proto.HeartbeatStateH\x00\x42\t\n\x07message\"w\n\x07\x41\x63kNack\x12&\n\x11\x61\x63king_msg_source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\'\n\racking_msg_id\x18\x02 \x01(\x0e\x32\x10.Proto.MessageID\x12\x1b\n\x13\x61\x63king_sequence_num\x18\x03 \x01(\r\"d\n\x04Ping\x12\x13\n\x0bping_ack_id\x18\x01 \x01(\r\x12\"\n\x1aping_response_sequence_num\x18\x02 \x01(\r\x12#\n\x1bsys_state_response_required\x18\x03 \x01(\x08\"-\n\tHeartbeat\x12 \n\x18hb_response_sequence_num\x18\x01 \x01(\r\"\xc4\x02\n\x0bSystemState\x12+\n\tsys_state\x18\x01 \x01(\x0e\x32\x18.Proto.SystemState.State\x12-\n\x0crocket_state\x18\x02 \x01(\x0e\x32\x12.Proto.RocketStateH\x00\x88\x01\x01\"\xc7\x01\n\x05State\x12\x0f\n\x0bSYS_INVALID\x10\x00\x12\x17\n\x13SYS_BOOTUP_COMPLETE\x10\x01\x12\x1c\n\x18SYS_ASSERT_FAILURE_RESET\x10\x02\x12\x16\n\x12SYS_UNCAUGHT_RESET\x10\x03\x12\x18\n\x14SYS_NORMAL_OPERATION\x10\x04\x12#\n\x1fSYS_HEARTBEAT_LOSS_HALF_WARNING\x10\x05\x12\x1f\n\x1bSYS_HEARTBEAT_LOST_ABORTING\x10\x06\x42\x0f\n\r_rocket_state\"\xb2\x02\n\rSystemControl\x12-\n\x07sys_cmd\x18\x01 \x01(\x0e\x32\x1c.Proto.SystemControl.Command\x12\x11\n\tcmd_param\x18\x02 \x01(\r\"\xde\x01\n\x07\x43ommand\x12\x0f\n\x0bSYS_INVALID\x10\x00\x12\r\n\tSYS_RESET\x10\x01\x12\x13\n\x0fSYS_FLASH_ERASE\x10\x02\x12\x19\n\x15SYS_LOG_PERIOD_CHANGE\x10\x03\x12\x14\n\x10HEARTBEAT_ENABLE\x10\x04\x12\x15\n\x11HEARTBEAT_DISABLE\x10\x05\x12\x18\n\x14SYS_FLASH_LOG_ENABLE\x10\x06\x12\x19\n\x15SYS_FLASH_LOG_DISABLE\x10\x07\x12!\n\x1dSYS_CRITICAL_FLASH_FULL_ERASE\x10\x08\"\xbf\x01\n\x0eHeartbeatState\x12\x35\n\x0btimer_state\x18\x01 \x01(\x0e\x32 .Proto.HeartbeatState.TimerState\x12\x14\n\x0ctimer_period\x18\x02 \x01(\r\x12\x17\n\x0ftimer_remaining\x18\x03 \x01(\r\"G\n\nTimerState\x12\x11\n\rUNINITIALIZED\x10\x00\x12\x0c\n\x08\x43OUNTING\x10\x01\x12\n\n\x06PAUSED\x10\x02\x12\x0c\n\x08\x43OMPLETE\x10\x03\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x43ontrolMessage.proto\x12\x05Proto\x1a\x0f\x43oreProto.proto\"\xec\x02\n\x0e\x43ontrolMessage\x12\x1b\n\x06source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x06target\x18\x02 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x13source_sequence_num\x18\x04 \x01(\r\x12\x1d\n\x03\x61\x63k\x18\x05 \x01(\x0b\x32\x0e.Proto.AckNackH\x00\x12\x1e\n\x04nack\x18\x06 \x01(\x0b\x32\x0e.Proto.AckNackH\x00\x12\x1b\n\x04ping\x18\x07 \x01(\x0b\x32\x0b.Proto.PingH\x00\x12\x1e\n\x02hb\x18\x08 \x01(\x0b\x32\x10.Proto.HeartbeatH\x00\x12\'\n\tsys_state\x18\t \x01(\x0b\x32\x12.Proto.SystemStateH\x00\x12(\n\x08sys_ctrl\x18\n \x01(\x0b\x32\x14.Proto.SystemControlH\x00\x12)\n\x08hb_state\x18\x0b \x01(\x0b\x32\x15.Proto.HeartbeatStateH\x00\x42\t\n\x07message\"w\n\x07\x41\x63kNack\x12&\n\x11\x61\x63king_msg_source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\'\n\racking_msg_id\x18\x02 \x01(\x0e\x32\x10.Proto.MessageID\x12\x1b\n\x13\x61\x63king_sequence_num\x18\x03 \x01(\r\"d\n\x04Ping\x12\x13\n\x0bping_ack_id\x18\x01 \x01(\r\x12\"\n\x1aping_response_sequence_num\x18\x02 \x01(\r\x12#\n\x1bsys_state_response_required\x18\x03 \x01(\x08\"-\n\tHeartbeat\x12 \n\x18hb_response_sequence_num\x18\x01 \x01(\r\"\x8e\x03\n\x0bSystemState\x12+\n\tsys_state\x18\x01 \x01(\x0e\x32\x18.Proto.SystemState.State\x12-\n\x0crocket_state\x18\x02 \x01(\x0e\x32\x12.Proto.RocketStateH\x00\x88\x01\x01\"\x91\x02\n\x05State\x12\x0f\n\x0bSYS_INVALID\x10\x00\x12\x17\n\x13SYS_BOOTUP_COMPLETE\x10\x01\x12\x1c\n\x18SYS_ASSERT_FAILURE_RESET\x10\x02\x12\x16\n\x12SYS_UNCAUGHT_RESET\x10\x03\x12\x18\n\x14SYS_NORMAL_OPERATION\x10\x04\x12#\n\x1fSYS_HEARTBEAT_LOSS_HALF_WARNING\x10\x05\x12\x1f\n\x1bSYS_HEARTBEAT_LOST_ABORTING\x10\x06\x12\x0c\n\x08SYS_WAIT\x10\x07\x12\x12\n\x0eSYS_RETRANSMIT\x10\x08\x12\x0f\n\x0bSYS_TIMEOUT\x10\t\x12\x15\n\x11SYS_SEND_NEXT_CMD\x10\nB\x0f\n\r_rocket_state\"\xb2\x02\n\rSystemControl\x12-\n\x07sys_cmd\x18\x01 \x01(\x0e\x32\x1c.Proto.SystemControl.Command\x12\x11\n\tcmd_param\x18\x02 \x01(\r\"\xde\x01\n\x07\x43ommand\x12\x0f\n\x0bSYS_INVALID\x10\x00\x12\r\n\tSYS_RESET\x10\x01\x12\x13\n\x0fSYS_FLASH_ERASE\x10\x02\x12\x19\n\x15SYS_LOG_PERIOD_CHANGE\x10\x03\x12\x14\n\x10HEARTBEAT_ENABLE\x10\x04\x12\x15\n\x11HEARTBEAT_DISABLE\x10\x05\x12\x18\n\x14SYS_FLASH_LOG_ENABLE\x10\x06\x12\x19\n\x15SYS_FLASH_LOG_DISABLE\x10\x07\x12!\n\x1dSYS_CRITICAL_FLASH_FULL_ERASE\x10\x08\"\xbf\x01\n\x0eHeartbeatState\x12\x35\n\x0btimer_state\x18\x01 \x01(\x0e\x32 .Proto.HeartbeatState.TimerState\x12\x14\n\x0ctimer_period\x18\x02 \x01(\r\x12\x17\n\x0ftimer_remaining\x18\x03 \x01(\r\"G\n\nTimerState\x12\x11\n\rUNINITIALIZED\x10\x00\x12\x0c\n\x08\x43OUNTING\x10\x01\x12\n\n\x06PAUSED\x10\x02\x12\x0c\n\x08\x43OMPLETE\x10\x03\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ControlMessage_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None _globals['_CONTROLMESSAGE']._serialized_start=49 _globals['_CONTROLMESSAGE']._serialized_end=413 _globals['_ACKNACK']._serialized_start=415 @@ -31,15 +41,15 @@ _globals['_HEARTBEAT']._serialized_start=638 _globals['_HEARTBEAT']._serialized_end=683 _globals['_SYSTEMSTATE']._serialized_start=686 - _globals['_SYSTEMSTATE']._serialized_end=1010 + _globals['_SYSTEMSTATE']._serialized_end=1084 _globals['_SYSTEMSTATE_STATE']._serialized_start=794 - _globals['_SYSTEMSTATE_STATE']._serialized_end=993 - _globals['_SYSTEMCONTROL']._serialized_start=1013 - _globals['_SYSTEMCONTROL']._serialized_end=1319 - _globals['_SYSTEMCONTROL_COMMAND']._serialized_start=1097 - _globals['_SYSTEMCONTROL_COMMAND']._serialized_end=1319 - _globals['_HEARTBEATSTATE']._serialized_start=1322 - _globals['_HEARTBEATSTATE']._serialized_end=1513 - _globals['_HEARTBEATSTATE_TIMERSTATE']._serialized_start=1442 - _globals['_HEARTBEATSTATE_TIMERSTATE']._serialized_end=1513 + _globals['_SYSTEMSTATE_STATE']._serialized_end=1067 + _globals['_SYSTEMCONTROL']._serialized_start=1087 + _globals['_SYSTEMCONTROL']._serialized_end=1393 + _globals['_SYSTEMCONTROL_COMMAND']._serialized_start=1171 + _globals['_SYSTEMCONTROL_COMMAND']._serialized_end=1393 + _globals['_HEARTBEATSTATE']._serialized_start=1396 + _globals['_HEARTBEATSTATE']._serialized_end=1587 + _globals['_HEARTBEATSTATE_TIMERSTATE']._serialized_start=1516 + _globals['_HEARTBEATSTATE_TIMERSTATE']._serialized_end=1587 # @@protoc_insertion_point(module_scope) diff --git a/backend/proto/Python/ControlMessage_pb2.pyi b/backend/proto/Python/ControlMessage_pb2.pyi index 5a9fc7c..2e74b15 100644 --- a/backend/proto/Python/ControlMessage_pb2.pyi +++ b/backend/proto/Python/ControlMessage_pb2.pyi @@ -67,6 +67,10 @@ class SystemState(_message.Message): SYS_NORMAL_OPERATION: _ClassVar[SystemState.State] SYS_HEARTBEAT_LOSS_HALF_WARNING: _ClassVar[SystemState.State] SYS_HEARTBEAT_LOST_ABORTING: _ClassVar[SystemState.State] + SYS_WAIT: _ClassVar[SystemState.State] + SYS_RETRANSMIT: _ClassVar[SystemState.State] + SYS_TIMEOUT: _ClassVar[SystemState.State] + SYS_SEND_NEXT_CMD: _ClassVar[SystemState.State] SYS_INVALID: SystemState.State SYS_BOOTUP_COMPLETE: SystemState.State SYS_ASSERT_FAILURE_RESET: SystemState.State @@ -74,6 +78,10 @@ class SystemState(_message.Message): SYS_NORMAL_OPERATION: SystemState.State SYS_HEARTBEAT_LOSS_HALF_WARNING: SystemState.State SYS_HEARTBEAT_LOST_ABORTING: SystemState.State + SYS_WAIT: SystemState.State + SYS_RETRANSMIT: SystemState.State + SYS_TIMEOUT: SystemState.State + SYS_SEND_NEXT_CMD: SystemState.State SYS_STATE_FIELD_NUMBER: _ClassVar[int] ROCKET_STATE_FIELD_NUMBER: _ClassVar[int] sys_state: SystemState.State diff --git a/backend/proto/Python/CoreProto_pb2.py b/backend/proto/Python/CoreProto_pb2.py index 3ca719a..1ffcae1 100644 --- a/backend/proto/Python/CoreProto_pb2.py +++ b/backend/proto/Python/CoreProto_pb2.py @@ -1,12 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: CoreProto.proto -# Protobuf Python Version: 4.25.3 +# Protobuf Python Version: 5.28.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 28, + 3, + '', + 'CoreProto.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -14,17 +24,17 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x43oreProto.proto\x12\x05Proto*p\n\x04Node\x12\x10\n\x0cNODE_INVALID\x10\x00\x12\x10\n\x0cNODE_UNKNOWN\x10\x01\x12\x0c\n\x08NODE_ANY\x10\x02\x12\x0c\n\x08NODE_RCU\x10\x03\x12\x0c\n\x08NODE_DMB\x10\x04\x12\x0c\n\x08NODE_PBB\x10\x05\x12\x0c\n\x08NODE_SOB\x10\x06*w\n\tMessageID\x12\x0f\n\x0bMSG_INVALID\x10\x00\x12\x0f\n\x0bMSG_UNKNOWN\x10\x01\x12\x0f\n\x0bMSG_CONTROL\x10\x02\x12\x0f\n\x0bMSG_COMMAND\x10\x03\x12\x11\n\rMSG_TELEMETRY\x10\x04\x12\x13\n\x0fMSG_MAX_INVALID\x10\x05*\xcd\x01\n\x0bRocketState\x12\x0f\n\x0b\x44MB_INVALID\x10\x00\x12\x10\n\x0cRS_PRELAUNCH\x10\x01\x12\x0b\n\x07RS_FILL\x10\x02\x12\n\n\x06RS_ARM\x10\x03\x12\x0f\n\x0bRS_IGNITION\x10\x04\x12\r\n\tRS_LAUNCH\x10\x05\x12\x0b\n\x07RS_BURN\x10\x06\x12\x0c\n\x08RS_COAST\x10\x07\x12\x0e\n\nRS_DESCENT\x10\x08\x12\x0f\n\x0bRS_RECOVERY\x10\t\x12\x0c\n\x08RS_ABORT\x10\n\x12\x0b\n\x07RS_TEST\x10\x0b\x12\x0b\n\x07RS_NONE\x10\x0c\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x43oreProto.proto\x12\x05Proto*p\n\x04Node\x12\x10\n\x0cNODE_INVALID\x10\x00\x12\x10\n\x0cNODE_UNKNOWN\x10\x01\x12\x0c\n\x08NODE_ANY\x10\x02\x12\x0c\n\x08NODE_RCU\x10\x03\x12\x0c\n\x08NODE_DMB\x10\x04\x12\x0c\n\x08NODE_PBB\x10\x05\x12\x0c\n\x08NODE_SOB\x10\x06*w\n\tMessageID\x12\x0f\n\x0bMSG_INVALID\x10\x00\x12\x0f\n\x0bMSG_UNKNOWN\x10\x01\x12\x0f\n\x0bMSG_CONTROL\x10\x02\x12\x0f\n\x0bMSG_COMMAND\x10\x03\x12\x11\n\rMSG_TELEMETRY\x10\x04\x12\x13\n\x0fMSG_MAX_INVALID\x10\x05*\xf5\x01\n\x0bRocketState\x12\x0f\n\x0b\x44MB_INVALID\x10\x00\x12\x10\n\x0cRS_PRELAUNCH\x10\x01\x12\x0b\n\x07RS_FILL\x10\x02\x12\n\n\x06RS_ARM\x10\x03\x12\x0f\n\x0bRS_IGNITION\x10\x04\x12\r\n\tRS_LAUNCH\x10\x05\x12\x0b\n\x07RS_BURN\x10\x06\x12\x0c\n\x08RS_COAST\x10\x07\x12\x0e\n\nRS_DESCENT\x10\x08\x12\x0f\n\x0bRS_RECOVERY\x10\t\x12\x0c\n\x08RS_ABORT\x10\n\x12\x0b\n\x07RS_TEST\x10\x0b\x12\x0b\n\x07RS_NONE\x10\x0c\x12\x10\n\x0cRS_LISTENING\x10\r\x12\x14\n\x10RS_READY_FOR_CMD\x10\x0e\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'CoreProto_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None _globals['_NODE']._serialized_start=26 _globals['_NODE']._serialized_end=138 _globals['_MESSAGEID']._serialized_start=140 _globals['_MESSAGEID']._serialized_end=259 _globals['_ROCKETSTATE']._serialized_start=262 - _globals['_ROCKETSTATE']._serialized_end=467 + _globals['_ROCKETSTATE']._serialized_end=507 # @@protoc_insertion_point(module_scope) diff --git a/backend/proto/Python/CoreProto_pb2.pyi b/backend/proto/Python/CoreProto_pb2.pyi index f3e32f5..2b2160f 100644 --- a/backend/proto/Python/CoreProto_pb2.pyi +++ b/backend/proto/Python/CoreProto_pb2.pyi @@ -38,6 +38,8 @@ class RocketState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): RS_ABORT: _ClassVar[RocketState] RS_TEST: _ClassVar[RocketState] RS_NONE: _ClassVar[RocketState] + RS_LISTENING: _ClassVar[RocketState] + RS_READY_FOR_CMD: _ClassVar[RocketState] NODE_INVALID: Node NODE_UNKNOWN: Node NODE_ANY: Node @@ -64,3 +66,5 @@ RS_RECOVERY: RocketState RS_ABORT: RocketState RS_TEST: RocketState RS_NONE: RocketState +RS_LISTENING: RocketState +RS_READY_FOR_CMD: RocketState diff --git a/backend/proto/Python/TelemetryMessage_pb2.py b/backend/proto/Python/TelemetryMessage_pb2.py index 7f216db..da65274 100644 --- a/backend/proto/Python/TelemetryMessage_pb2.py +++ b/backend/proto/Python/TelemetryMessage_pb2.py @@ -1,12 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: TelemetryMessage.proto -# Protobuf Python Version: 4.25.3 +# Protobuf Python Version: 5.28.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 28, + 3, + '', + 'TelemetryMessage.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -20,8 +30,8 @@ _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'TelemetryMessage_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None _globals['_TELEMETRYMESSAGE']._serialized_start=51 _globals['_TELEMETRYMESSAGE']._serialized_end=823 _globals['_GPS']._serialized_start=826 diff --git a/backend/src/StateMachine/BaseStateMachine.py b/backend/src/StateMachine/BaseStateMachine.py index ec81ae3..494af8c 100644 --- a/backend/src/StateMachine/BaseStateMachine.py +++ b/backend/src/StateMachine/BaseStateMachine.py @@ -31,9 +31,9 @@ def exit(self): # def exit_state(self): # pass - # @abstractmethod - # def update_state(self): - # pass + @abstractmethod + def update_state(self): + pass # @abstractmethod # def get_next_state(self): diff --git a/backend/src/StateMachine/StateMachineManager.py b/backend/src/StateMachine/StateMachineManager.py index 734c1d4..a1e213c 100644 --- a/backend/src/StateMachine/StateMachineManager.py +++ b/backend/src/StateMachine/StateMachineManager.py @@ -26,25 +26,6 @@ # Class Definitions =============================================================================== -@unique -class State(Enum): - """ - Represents the states of the rocket and the rcu. If there is a use case to make the state class more general, - and the RocketState and RCUState to be a subclass, we can consider that option at that time. Right now, we don't have - enough states to justify using inheritance and subclasses. - - """ - RCU_UNINITIALIZED = 0 - RCU_INITIALIZED = 1 - RCU_WAIT = 2 - RCU_RETRANSMIT = 3 - RCU_TIMEOUT = 4 - RCU_SEND_NEXT_CMD = 5 - ROCKET_UNINITIALIZED = 6 - ROCKET_LISTENING = 7 - ROCKET_INITIALIZED = 8 - ROCKET_READY_FOR_CMD = 9 - @unique class Event(Enum): """ @@ -67,11 +48,13 @@ class Event(Enum): class StateMachineManager(BaseStateMachine): """ Manage state transitions for the communication between the RCU and the rocket. + """ def start(self): """ Start the state machine for RCU and Rocket serial communication by setting the RCU state to be initialized and event to RCU Start. + """ self.rcu_state = State.RCU_INITIALIZED self.rocket_state = State.ROCKET_LISTENING @@ -79,35 +62,49 @@ def start(self): def exit(self): """ - Exit the state machine for RCU and Rocket serial communication by by setting RCU state to be uninitialized + Exit the state machine for RCU and Rocket serial communication by setting RCU state to be uninitialized and event to RCU Exit. + """ self.rcu_state = State.RCU_UNINITIALIZED self.rocket_state = State.ROCKET_UNINITIALIZED self.event = Event.RCU_EXIT def get_rcu_state(self): + """ + Return the current state of the RCU. + + """ print("\nThe current RCU state:") return self.rcu_state def get_rocket_state(self): + """" + Return the current state of the rocket. + + """ print("\nThe current rocket state:") return self.rocket_state def get_event(self): + """ + Return the current event. + + """ print("\nThe current event:") return self.event - def transition(self, event, from_state, to_state): + def update_state(self, event, from_state, to_state): + """ + Update from current state to another state. + + """ + #Check to make sure inputs are part of the enum list #Check if event is a valid transition from from_state #Check if event is a valid transition to to_state - - #if valid, then set the current event and current state - - #Idk if I want to code the update step separately from the transition step yet - prob to break it up tbh return From 801165e7ee6dbfdd488da4e5507335988d8366fd Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:41:51 -0600 Subject: [PATCH 05/14] Start the file for StateMachineManager.py. It is untested logic-wise and function-wise. Please do NOT use yet. --- backend/proto/Python/CommandMessage_pb2.py | 2 +- backend/proto/Python/ControlMessage_pb2.py | 2 +- backend/proto/Python/CoreProto_pb2.pyi | 3 +- backend/src/SerialHandler.py | 32 ++++- .../src/StateMachine/StateMachineManager.py | 109 ++++++++++++------ 5 files changed, 110 insertions(+), 38 deletions(-) diff --git a/backend/proto/Python/CommandMessage_pb2.py b/backend/proto/Python/CommandMessage_pb2.py index 08d2980..cf2f05e 100644 --- a/backend/proto/Python/CommandMessage_pb2.py +++ b/backend/proto/Python/CommandMessage_pb2.py @@ -22,7 +22,7 @@ _sym_db = _symbol_database.Default() -import CoreProto_pb2 as CoreProto__pb2 +# import CoreProto_pb2 as CoreProto__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x43ommandMessage.proto\x12\x05Proto\x1a\x0f\x43oreProto.proto\"\x9a\x02\n\x0e\x43ommandMessage\x12\x1b\n\x06source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x06target\x18\x02 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x13source_sequence_num\x18\x03 \x01(\r\x12(\n\x0b\x64mb_command\x18\x04 \x01(\x0b\x32\x11.Proto.DmbCommandH\x00\x12(\n\x0bpbb_command\x18\x05 \x01(\x0b\x32\x11.Proto.PbbCommandH\x00\x12(\n\x0brcu_command\x18\x06 \x01(\x0b\x32\x11.Proto.RcuCommandH\x00\x12(\n\x0bsob_command\x18\x07 \x01(\x0b\x32\x11.Proto.SobCommandH\x00\x42\t\n\x07message\"\x8d\x05\n\nDmbCommand\x12/\n\x0c\x63ommand_enum\x18\x01 \x01(\x0e\x32\x19.Proto.DmbCommand.Command\"\xcd\x04\n\x07\x43ommand\x12\x15\n\x11RSC_FIRST_INVALID\x10\x00\x12\x14\n\x10RSC_ANY_TO_ABORT\x10\x01\x12\x11\n\rRSC_OPEN_VENT\x10\x02\x12\x12\n\x0eRSC_CLOSE_VENT\x10\x03\x12\x12\n\x0eRSC_OPEN_DRAIN\x10\x04\x12\x13\n\x0fRSC_CLOSE_DRAIN\x10\x05\x12\x11\n\rRSC_MEV_CLOSE\x10\x06\x12\x11\n\rRSC_GOTO_FILL\x10\x07\x12\x15\n\x11RSC_ARM_CONFIRM_1\x10\x08\x12\x15\n\x11RSC_ARM_CONFIRM_2\x10\t\x12\x10\n\x0cRSC_GOTO_ARM\x10\n\x12\x16\n\x12RSC_GOTO_PRELAUNCH\x10\x0b\x12 \n\x1cRSC_POWER_TRANSITION_ONBOARD\x10\x0c\x12!\n\x1dRSC_POWER_TRANSITION_EXTERNAL\x10\r\x12\x15\n\x11RSC_GOTO_IGNITION\x10\x0e\x12\x1a\n\x16RSC_IGNITION_TO_LAUNCH\x10\x0f\x12\x16\n\x12RSC_LAUNCH_TO_BURN\x10\x10\x12\x15\n\x11RSC_BURN_TO_COAST\x10\x11\x12\x18\n\x14RSC_COAST_TO_DESCENT\x10\x12\x12\x1b\n\x17RSC_DESCENT_TO_RECOVERY\x10\x13\x12\x11\n\rRSC_GOTO_TEST\x10\x14\x12\x15\n\x11RSC_TEST_MEV_OPEN\x10\x15\x12\x17\n\x13RSC_TEST_MEV_ENABLE\x10\x16\x12\x18\n\x14RSC_TEST_MEV_DISABLE\x10\x17\x12\x0c\n\x08RSC_NONE\x10\x18\"\x89\x01\n\nPbbCommand\x12/\n\x0c\x63ommand_enum\x18\x01 \x01(\x0e\x32\x19.Proto.PbbCommand.Command\"J\n\x07\x43ommand\x12\x0c\n\x08PBB_NONE\x10\x00\x12\x10\n\x0cPBB_OPEN_MEV\x10\x01\x12\x11\n\rPBB_CLOSE_MEV\x10\x02\x12\x0c\n\x08PMB_LAST\x10\x05\"\xe1\x01\n\nSobCommand\x12/\n\x0c\x63ommand_enum\x18\x01 \x01(\x0e\x32\x19.Proto.SobCommand.Command\x12\x15\n\rcommand_param\x18\x02 \x01(\x05\"\x8a\x01\n\x07\x43ommand\x12\x0c\n\x08SOB_NONE\x10\x00\x12\x16\n\x12SOB_SLOW_SAMPLE_IR\x10\x01\x12\x16\n\x12SOB_FAST_SAMPLE_IR\x10\x02\x12\x16\n\x12SOB_TARE_LOAD_CELL\x10\x03\x12\x1b\n\x17SOB_CALIBRATE_LOAD_CELL\x10\x04\x12\x0c\n\x08SOB_LAST\x10\x05\"\x87\x06\n\nRcuCommand\x12/\n\x0c\x63ommand_enum\x18\x01 \x01(\x0e\x32\x19.Proto.RcuCommand.Command\x12\x15\n\rcommand_param\x18\x02 \x01(\x05\"\xb0\x05\n\x07\x43ommand\x12\x0c\n\x08RCU_NONE\x10\x00\x12\x10\n\x0cRCU_OPEN_AC1\x10\x03\x12\x11\n\rRCU_CLOSE_AC1\x10\x04\x12\x10\n\x0cRCU_OPEN_AC2\x10\x05\x12\x11\n\rRCU_CLOSE_AC2\x10\x06\x12\x11\n\rRCU_OPEN_PBV1\x10\x07\x12\x12\n\x0eRCU_CLOSE_PBV1\x10\x08\x12\x11\n\rRCU_OPEN_PBV2\x10\t\x12\x12\n\x0eRCU_CLOSE_PBV2\x10\n\x12\x11\n\rRCU_OPEN_PBV3\x10\x0b\x12\x12\n\x0eRCU_CLOSE_PBV3\x10\x0c\x12\x11\n\rRCU_OPEN_PBV4\x10\r\x12\x12\n\x0eRCU_CLOSE_PBV4\x10\x0e\x12\x11\n\rRCU_OPEN_SOL5\x10\x15\x12\x12\n\x0eRCU_CLOSE_SOL5\x10\x16\x12\x11\n\rRCU_OPEN_SOL6\x10\x17\x12\x12\n\x0eRCU_CLOSE_SOL6\x10\x18\x12\x11\n\rRCU_OPEN_SOL7\x10\x19\x12\x12\n\x0eRCU_CLOSE_SOL7\x10\x1a\x12\x12\n\x0eRCU_OPEN_SOL8A\x10\x1b\x12\x13\n\x0fRCU_CLOSE_SOL8A\x10\x1c\x12\x12\n\x0eRCU_OPEN_SOL8B\x10\x1d\x12\x13\n\x0fRCU_CLOSE_SOL8B\x10\x1e\x12\x1b\n\x17RCU_TARE_NOS1_LOAD_CELL\x10!\x12\x1b\n\x17RCU_TARE_NOS2_LOAD_CELL\x10\"\x12 \n\x1cRCU_CALIBRATE_NOS1_LOAD_CELL\x10#\x12 \n\x1cRCU_CALIBRATE_NOS2_LOAD_CELL\x10$\x12\x17\n\x13RCU_IGNITE_PAD_BOX1\x10\x1f\x12\x17\n\x13RCU_IGNITE_PAD_BOX2\x10 \x12\x15\n\x11RCU_KILL_PAD_BOX1\x10%\x12\x15\n\x11RCU_KILL_PAD_BOX2\x10&\x12\x0c\n\x08RCU_LAST\x10\'b\x06proto3') diff --git a/backend/proto/Python/ControlMessage_pb2.py b/backend/proto/Python/ControlMessage_pb2.py index 37304c2..710584c 100644 --- a/backend/proto/Python/ControlMessage_pb2.py +++ b/backend/proto/Python/ControlMessage_pb2.py @@ -22,7 +22,7 @@ _sym_db = _symbol_database.Default() -import CoreProto_pb2 as CoreProto__pb2 +# import CoreProto_pb2 as CoreProto__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x43ontrolMessage.proto\x12\x05Proto\x1a\x0f\x43oreProto.proto\"\xec\x02\n\x0e\x43ontrolMessage\x12\x1b\n\x06source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x06target\x18\x02 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x13source_sequence_num\x18\x04 \x01(\r\x12\x1d\n\x03\x61\x63k\x18\x05 \x01(\x0b\x32\x0e.Proto.AckNackH\x00\x12\x1e\n\x04nack\x18\x06 \x01(\x0b\x32\x0e.Proto.AckNackH\x00\x12\x1b\n\x04ping\x18\x07 \x01(\x0b\x32\x0b.Proto.PingH\x00\x12\x1e\n\x02hb\x18\x08 \x01(\x0b\x32\x10.Proto.HeartbeatH\x00\x12\'\n\tsys_state\x18\t \x01(\x0b\x32\x12.Proto.SystemStateH\x00\x12(\n\x08sys_ctrl\x18\n \x01(\x0b\x32\x14.Proto.SystemControlH\x00\x12)\n\x08hb_state\x18\x0b \x01(\x0b\x32\x15.Proto.HeartbeatStateH\x00\x42\t\n\x07message\"w\n\x07\x41\x63kNack\x12&\n\x11\x61\x63king_msg_source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\'\n\racking_msg_id\x18\x02 \x01(\x0e\x32\x10.Proto.MessageID\x12\x1b\n\x13\x61\x63king_sequence_num\x18\x03 \x01(\r\"d\n\x04Ping\x12\x13\n\x0bping_ack_id\x18\x01 \x01(\r\x12\"\n\x1aping_response_sequence_num\x18\x02 \x01(\r\x12#\n\x1bsys_state_response_required\x18\x03 \x01(\x08\"-\n\tHeartbeat\x12 \n\x18hb_response_sequence_num\x18\x01 \x01(\r\"\x8e\x03\n\x0bSystemState\x12+\n\tsys_state\x18\x01 \x01(\x0e\x32\x18.Proto.SystemState.State\x12-\n\x0crocket_state\x18\x02 \x01(\x0e\x32\x12.Proto.RocketStateH\x00\x88\x01\x01\"\x91\x02\n\x05State\x12\x0f\n\x0bSYS_INVALID\x10\x00\x12\x17\n\x13SYS_BOOTUP_COMPLETE\x10\x01\x12\x1c\n\x18SYS_ASSERT_FAILURE_RESET\x10\x02\x12\x16\n\x12SYS_UNCAUGHT_RESET\x10\x03\x12\x18\n\x14SYS_NORMAL_OPERATION\x10\x04\x12#\n\x1fSYS_HEARTBEAT_LOSS_HALF_WARNING\x10\x05\x12\x1f\n\x1bSYS_HEARTBEAT_LOST_ABORTING\x10\x06\x12\x0c\n\x08SYS_WAIT\x10\x07\x12\x12\n\x0eSYS_RETRANSMIT\x10\x08\x12\x0f\n\x0bSYS_TIMEOUT\x10\t\x12\x15\n\x11SYS_SEND_NEXT_CMD\x10\nB\x0f\n\r_rocket_state\"\xb2\x02\n\rSystemControl\x12-\n\x07sys_cmd\x18\x01 \x01(\x0e\x32\x1c.Proto.SystemControl.Command\x12\x11\n\tcmd_param\x18\x02 \x01(\r\"\xde\x01\n\x07\x43ommand\x12\x0f\n\x0bSYS_INVALID\x10\x00\x12\r\n\tSYS_RESET\x10\x01\x12\x13\n\x0fSYS_FLASH_ERASE\x10\x02\x12\x19\n\x15SYS_LOG_PERIOD_CHANGE\x10\x03\x12\x14\n\x10HEARTBEAT_ENABLE\x10\x04\x12\x15\n\x11HEARTBEAT_DISABLE\x10\x05\x12\x18\n\x14SYS_FLASH_LOG_ENABLE\x10\x06\x12\x19\n\x15SYS_FLASH_LOG_DISABLE\x10\x07\x12!\n\x1dSYS_CRITICAL_FLASH_FULL_ERASE\x10\x08\"\xbf\x01\n\x0eHeartbeatState\x12\x35\n\x0btimer_state\x18\x01 \x01(\x0e\x32 .Proto.HeartbeatState.TimerState\x12\x14\n\x0ctimer_period\x18\x02 \x01(\r\x12\x17\n\x0ftimer_remaining\x18\x03 \x01(\r\"G\n\nTimerState\x12\x11\n\rUNINITIALIZED\x10\x00\x12\x0c\n\x08\x43OUNTING\x10\x01\x12\n\n\x06PAUSED\x10\x02\x12\x0c\n\x08\x43OMPLETE\x10\x03\x62\x06proto3') diff --git a/backend/proto/Python/CoreProto_pb2.pyi b/backend/proto/Python/CoreProto_pb2.pyi index 2b2160f..11f73bb 100644 --- a/backend/proto/Python/CoreProto_pb2.pyi +++ b/backend/proto/Python/CoreProto_pb2.pyi @@ -38,7 +38,8 @@ class RocketState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): RS_ABORT: _ClassVar[RocketState] RS_TEST: _ClassVar[RocketState] RS_NONE: _ClassVar[RocketState] - RS_LISTENING: _ClassVar[RocketState] + + : _ClassVar[RocketState] RS_READY_FOR_CMD: _ClassVar[RocketState] NODE_INVALID: Node NODE_UNKNOWN: Node diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index 55f1147..ffff5e8 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -267,6 +267,30 @@ def serial_command_control_message_state_machine (self, command: str, target: st #Handle response return +def start_state_machine(message: WorkQ_Message, ser_han: SerialHandler): + """ + Start the state machine for RCU <-> Rocket command/control message + + Args: + message (str): + The message from the workq + ser_han + + The serial thread handler + """ + logger.debug(f"Processing serial workq message: {message.message_type}") + messageID = message.message_type + + if messageID == THREAD_MESSAGE_SERIAL_WRITE: + command = message.message[0] + target = message.message[1] + command_param = message.message[2] + source_sequence_number = message.message[3] + ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) + elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: + ser_han.send_serial_control_message(message.message[0]) + + def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) -> bool: """ Process the message from the workq. @@ -323,7 +347,13 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread rx_thread.start() while (1): # then once the queue is empty read the serial port - if not process_serial_workq_message(serial_workq.get(), ser_han): + message = serial_workq.get() + print("Message", message.message_type) + if message.message_type == THREAD_MESSAGE_KILL: + logger.debug(f"Killing {ser_han.thread_name} thread") ser_han.kill_rx = True rx_thread.join(10) return + #If valid messages received, start the state machine + StateMachineManager.start_sending_msg(message, ser_han) + diff --git a/backend/src/StateMachine/StateMachineManager.py b/backend/src/StateMachine/StateMachineManager.py index a1e213c..4262eb6 100644 --- a/backend/src/StateMachine/StateMachineManager.py +++ b/backend/src/StateMachine/StateMachineManager.py @@ -20,9 +20,10 @@ sys.path.insert(0, os.path.join(dirname.split("backend", 1)[0], 'backend')) sys.path.insert(0, os.path.join(dirname.split("backend", 1)[0], 'src/StateMachine')) from src.StateMachine.BaseStateMachine import BaseStateMachine -# import proto.Python.CoreProto_pb2 as ProtoCore -# import src.StateMachine.BaseStateMachine as BaseStateMachine - +from src.ThreadManager import * +from src.SerialHandler import SerialHandler +from proto.Python.CoreProto_pb2 import RocketState +from proto.Python.ControlMessage_pb2 import SystemState # Class Definitions =============================================================================== @@ -50,24 +51,86 @@ class StateMachineManager(BaseStateMachine): Manage state transitions for the communication between the RCU and the rocket. """ - def start(self): + def __init__(self): + self.sys_state = SystemState.SYS_NORMAL_OPERATION + self.rocket_state = RocketState.RS_LISTENING + self.event = Event.RCU_START + self.message = None + self.retransmit_counter = 0 + + def start_sending_msg(self, message: WorkQ_Message, ser_han: SerialHandler): """ - Start the state machine for RCU and Rocket serial communication by setting the RCU state to be - initialized and event to RCU Start. + Send control/command message from RCU (radio) to Rocket (DMB) by calling + the appropriate send functions that corresponds to the message type. + Set the RCU and rocket state to the appropriate state and call the next function + + Args: + message: message from the workq to identify whether to send control + or command message + ser_han: Serial Handler class to call the functions to send the messages """ - self.rcu_state = State.RCU_INITIALIZED - self.rocket_state = State.ROCKET_LISTENING - self.event = Event.RCU_START + messageID = message.message_type + if messageID == THREAD_MESSAGE_SERIAL_WRITE: + command = message.message[0] + target = message.message[1] + command_param = message.message[2] + source_sequence_number = message.message[3] + ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) + elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: + ser_han.send_serial_control_message(message.message[0]) + + #Update the state and call next step in the state machine + self.sys_state = SystemState.SYS_WAIT + self.send_message(Event.RCU_SEND_MSG) + + def send_message(self, message: WorkQ_Message, ser_han: SerialHandler,): + """ + Handle the logic of calling other functions depending on rocket responses. + """ + response = "NAK" + response1 = "ACK" + response2 = "TIMEOUT" + + if self.sys_state == SystemState.SYS_WAIT: + #If received NAK from Rocket: + if response == "NAK": + self.handle_retransmit(message, ser_han) + if response1 == "ACK": + self.handle_send_next_cmd() + if response2 == "TIMEOUT": + self.sys_state = SystemState.SYS_TIMEOUT + self.handle_timeout() + + def handle_retransmit(self, message: WorkQ_Message, ser_han: SerialHandler): + #If retransmit counter is within range, retransmit the same message + if self.retransmit_counter < 2: + self.start_sending_msg(message, ser_han) + self.retransmit_counter += 1 + else: + self.sys_state = SystemState.SYS_SEND_NEXT_CMD + self.handle_send_next_cmd() + + def handle_send_next_cmd(self): + #Send next cmd + self.sys_state = SystemState.SYS_WAIT + #reset retransmit counter + self.retransmit_counter = 0 + def handle_timeout(self, message: WorkQ_Message, ser_han: SerialHandler): + + #Call retransmit + self.sys_state = SystemState.SYS_RETRANSMIT + self.handle_retransmit(message, ser_han) + def exit(self): """ Exit the state machine for RCU and Rocket serial communication by setting RCU state to be uninitialized and event to RCU Exit. """ - self.rcu_state = State.RCU_UNINITIALIZED - self.rocket_state = State.ROCKET_UNINITIALIZED + self.sys_state = SystemState.SYS_INVALID + self.rocket_state = RocketState.RS_NONE self.event = Event.RCU_EXIT def get_rcu_state(self): @@ -76,7 +139,7 @@ def get_rcu_state(self): """ print("\nThe current RCU state:") - return self.rcu_state + return self.sys_state def get_rocket_state(self): """" @@ -86,33 +149,11 @@ def get_rocket_state(self): print("\nThe current rocket state:") return self.rocket_state - def get_event(self): - """ - Return the current event. - - """ - print("\nThe current event:") - return self.event - - def update_state(self, event, from_state, to_state): - """ - Update from current state to another state. - - """ - - #Check to make sure inputs are part of the enum list - - #Check if event is a valid transition from from_state - - #Check if event is a valid transition to to_state - return - #Test - Do NOT commit test = StateMachineManager() print(test.start()) -print(test.get_event()) print(test.get_rcu_state()) print(test.get_rocket_state()) From c5bebe1c400023fe44c4b2e714c9ea58ca257d48 Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:42:47 -0600 Subject: [PATCH 06/14] Accidentally delete something. Readd --- backend/proto/Python/CoreProto_pb2.pyi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/proto/Python/CoreProto_pb2.pyi b/backend/proto/Python/CoreProto_pb2.pyi index 11f73bb..2b2160f 100644 --- a/backend/proto/Python/CoreProto_pb2.pyi +++ b/backend/proto/Python/CoreProto_pb2.pyi @@ -38,8 +38,7 @@ class RocketState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): RS_ABORT: _ClassVar[RocketState] RS_TEST: _ClassVar[RocketState] RS_NONE: _ClassVar[RocketState] - - : _ClassVar[RocketState] + RS_LISTENING: _ClassVar[RocketState] RS_READY_FOR_CMD: _ClassVar[RocketState] NODE_INVALID: Node NODE_UNKNOWN: Node From 1ca2ed5ed2d5de239bf082481ab332e0554a3feb Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:53:42 -0600 Subject: [PATCH 07/14] Add basic code. Need to test logic --- backend/src/SerialHandler.py | 81 ++++++------------- .../src/StateMachine/StateMachineManager.py | 25 +++--- 2 files changed, 41 insertions(+), 65 deletions(-) diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index ffff5e8..1bb51d0 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -259,61 +259,29 @@ def serial_rx_thread(ser_han: SerialHandler): ser_han.handle_serial_message() pass -def serial_command_control_message_state_machine (self, command: str, target: str, command_param: int, source_sequence_number: int): - #Send command/control message - - #Wait for response from DMB - - #Handle response - return - -def start_state_machine(message: WorkQ_Message, ser_han: SerialHandler): - """ - Start the state machine for RCU <-> Rocket command/control message - - Args: - message (str): - The message from the workq - ser_han - - The serial thread handler - """ - logger.debug(f"Processing serial workq message: {message.message_type}") - messageID = message.message_type - - if messageID == THREAD_MESSAGE_SERIAL_WRITE: - command = message.message[0] - target = message.message[1] - command_param = message.message[2] - source_sequence_number = message.message[3] - ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) - elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: - ser_han.send_serial_control_message(message.message[0]) - - -def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) -> bool: - """ - Process the message from the workq. - - Args: - message (str): - The message from the workq. - """ - logger.debug(f"Processing serial workq message: {message.message_type}") - messageID = message.message_type - - if messageID == THREAD_MESSAGE_KILL: - logger.debug(f"Killing {ser_han.thread_name} thread") - return False - elif messageID == THREAD_MESSAGE_SERIAL_WRITE: - command = message.message[0] - target = message.message[1] - command_param = message.message[2] - source_sequence_number = message.message[3] - ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) - elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: - ser_han.send_serial_control_message(message.message[0]) - return True +# def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) -> bool: +# """ +# Process the message from the workq. + +# Args: +# message (str): +# The message from the workq. +# """ +# logger.debug(f"Processing serial workq message: {message.message_type}") +# messageID = message.message_type + +# if messageID == THREAD_MESSAGE_KILL: +# logger.debug(f"Killing {ser_han.thread_name} thread") +# return False +# elif messageID == THREAD_MESSAGE_SERIAL_WRITE: +# command = message.message[0] +# target = message.message[1] +# command_param = message.message[2] +# source_sequence_number = message.message[3] +# ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) +# elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: +# ser_han.send_serial_control_message(message.message[0]) +# return True def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread_workq: mp.Queue, message_handler_workq: mp.Queue): """ @@ -354,6 +322,7 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread ser_han.kill_rx = True rx_thread.join(10) return - #If valid messages received, start the state machine + #If valid messages received, initialze the state machine and start sending messages + StateMachineManager() StateMachineManager.start_sending_msg(message, ser_han) diff --git a/backend/src/StateMachine/StateMachineManager.py b/backend/src/StateMachine/StateMachineManager.py index 4262eb6..bc49749 100644 --- a/backend/src/StateMachine/StateMachineManager.py +++ b/backend/src/StateMachine/StateMachineManager.py @@ -13,7 +13,7 @@ # General imports ================================================================================= from enum import Enum, unique -import os, sys +import os, sys, time, random # Project specific imports ======================================================================== dirname, _ = os.path.split(os.path.abspath(__file__)) @@ -82,32 +82,34 @@ def start_sending_msg(self, message: WorkQ_Message, ser_han: SerialHandler): #Update the state and call next step in the state machine self.sys_state = SystemState.SYS_WAIT - self.send_message(Event.RCU_SEND_MSG) + self.handle_wait(message, ser_han) - def send_message(self, message: WorkQ_Message, ser_han: SerialHandler,): + def handle_wait(self, message: WorkQ_Message, ser_han: SerialHandler,): """ Handle the logic of calling other functions depending on rocket responses. """ - response = "NAK" - response1 = "ACK" - response2 = "TIMEOUT" + #Ideally, should have another function to parse the response received from DMB + # response = parse_rocket_response(), change the if blocks below to if, elif,else, etc + response = self.simulate_rocket_response() if self.sys_state == SystemState.SYS_WAIT: #If received NAK from Rocket: if response == "NAK": self.handle_retransmit(message, ser_han) - if response1 == "ACK": + elif response == "ACK": self.handle_send_next_cmd() - if response2 == "TIMEOUT": + elif response == "TIMEOUT": self.sys_state = SystemState.SYS_TIMEOUT self.handle_timeout() + else: + print("Bro, you messed up\n\r") def handle_retransmit(self, message: WorkQ_Message, ser_han: SerialHandler): #If retransmit counter is within range, retransmit the same message if self.retransmit_counter < 2: - self.start_sending_msg(message, ser_han) self.retransmit_counter += 1 + self.start_sending_msg(message, ser_han) else: self.sys_state = SystemState.SYS_SEND_NEXT_CMD self.handle_send_next_cmd() @@ -122,6 +124,11 @@ def handle_timeout(self, message: WorkQ_Message, ser_han: SerialHandler): #Call retransmit self.sys_state = SystemState.SYS_RETRANSMIT self.handle_retransmit(message, ser_han) + + def simulate_rocket_response(self): + # Simulate different responses from Rocket + responses = ["ACK", "NAK", "TIMEOUT"] + return random.choice(responses) def exit(self): """ From 4d7649550d9d818c7c0229fa66e6fda0a2604444 Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 16 Nov 2024 11:35:45 -0700 Subject: [PATCH 08/14] Adjust serial handler to call state machine --- backend/proto/Python/CommandMessage_pb2.py | 2 +- backend/proto/Python/ControlMessage_pb2.py | 2 +- backend/src/SerialHandler.py | 55 +++++++++---------- backend/src/StateMachine/BaseStateMachine.py | 52 ------------------ backend/src/StateMachine/test.py | 42 -------------- .../{StateMachine => }/StateMachineManager.py | 44 ++++----------- backend/src/main.py | 24 ++++---- 7 files changed, 50 insertions(+), 171 deletions(-) delete mode 100644 backend/src/StateMachine/BaseStateMachine.py delete mode 100644 backend/src/StateMachine/test.py rename backend/src/{StateMachine => }/StateMachineManager.py (69%) diff --git a/backend/proto/Python/CommandMessage_pb2.py b/backend/proto/Python/CommandMessage_pb2.py index cf2f05e..08d2980 100644 --- a/backend/proto/Python/CommandMessage_pb2.py +++ b/backend/proto/Python/CommandMessage_pb2.py @@ -22,7 +22,7 @@ _sym_db = _symbol_database.Default() -# import CoreProto_pb2 as CoreProto__pb2 +import CoreProto_pb2 as CoreProto__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x43ommandMessage.proto\x12\x05Proto\x1a\x0f\x43oreProto.proto\"\x9a\x02\n\x0e\x43ommandMessage\x12\x1b\n\x06source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x06target\x18\x02 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x13source_sequence_num\x18\x03 \x01(\r\x12(\n\x0b\x64mb_command\x18\x04 \x01(\x0b\x32\x11.Proto.DmbCommandH\x00\x12(\n\x0bpbb_command\x18\x05 \x01(\x0b\x32\x11.Proto.PbbCommandH\x00\x12(\n\x0brcu_command\x18\x06 \x01(\x0b\x32\x11.Proto.RcuCommandH\x00\x12(\n\x0bsob_command\x18\x07 \x01(\x0b\x32\x11.Proto.SobCommandH\x00\x42\t\n\x07message\"\x8d\x05\n\nDmbCommand\x12/\n\x0c\x63ommand_enum\x18\x01 \x01(\x0e\x32\x19.Proto.DmbCommand.Command\"\xcd\x04\n\x07\x43ommand\x12\x15\n\x11RSC_FIRST_INVALID\x10\x00\x12\x14\n\x10RSC_ANY_TO_ABORT\x10\x01\x12\x11\n\rRSC_OPEN_VENT\x10\x02\x12\x12\n\x0eRSC_CLOSE_VENT\x10\x03\x12\x12\n\x0eRSC_OPEN_DRAIN\x10\x04\x12\x13\n\x0fRSC_CLOSE_DRAIN\x10\x05\x12\x11\n\rRSC_MEV_CLOSE\x10\x06\x12\x11\n\rRSC_GOTO_FILL\x10\x07\x12\x15\n\x11RSC_ARM_CONFIRM_1\x10\x08\x12\x15\n\x11RSC_ARM_CONFIRM_2\x10\t\x12\x10\n\x0cRSC_GOTO_ARM\x10\n\x12\x16\n\x12RSC_GOTO_PRELAUNCH\x10\x0b\x12 \n\x1cRSC_POWER_TRANSITION_ONBOARD\x10\x0c\x12!\n\x1dRSC_POWER_TRANSITION_EXTERNAL\x10\r\x12\x15\n\x11RSC_GOTO_IGNITION\x10\x0e\x12\x1a\n\x16RSC_IGNITION_TO_LAUNCH\x10\x0f\x12\x16\n\x12RSC_LAUNCH_TO_BURN\x10\x10\x12\x15\n\x11RSC_BURN_TO_COAST\x10\x11\x12\x18\n\x14RSC_COAST_TO_DESCENT\x10\x12\x12\x1b\n\x17RSC_DESCENT_TO_RECOVERY\x10\x13\x12\x11\n\rRSC_GOTO_TEST\x10\x14\x12\x15\n\x11RSC_TEST_MEV_OPEN\x10\x15\x12\x17\n\x13RSC_TEST_MEV_ENABLE\x10\x16\x12\x18\n\x14RSC_TEST_MEV_DISABLE\x10\x17\x12\x0c\n\x08RSC_NONE\x10\x18\"\x89\x01\n\nPbbCommand\x12/\n\x0c\x63ommand_enum\x18\x01 \x01(\x0e\x32\x19.Proto.PbbCommand.Command\"J\n\x07\x43ommand\x12\x0c\n\x08PBB_NONE\x10\x00\x12\x10\n\x0cPBB_OPEN_MEV\x10\x01\x12\x11\n\rPBB_CLOSE_MEV\x10\x02\x12\x0c\n\x08PMB_LAST\x10\x05\"\xe1\x01\n\nSobCommand\x12/\n\x0c\x63ommand_enum\x18\x01 \x01(\x0e\x32\x19.Proto.SobCommand.Command\x12\x15\n\rcommand_param\x18\x02 \x01(\x05\"\x8a\x01\n\x07\x43ommand\x12\x0c\n\x08SOB_NONE\x10\x00\x12\x16\n\x12SOB_SLOW_SAMPLE_IR\x10\x01\x12\x16\n\x12SOB_FAST_SAMPLE_IR\x10\x02\x12\x16\n\x12SOB_TARE_LOAD_CELL\x10\x03\x12\x1b\n\x17SOB_CALIBRATE_LOAD_CELL\x10\x04\x12\x0c\n\x08SOB_LAST\x10\x05\"\x87\x06\n\nRcuCommand\x12/\n\x0c\x63ommand_enum\x18\x01 \x01(\x0e\x32\x19.Proto.RcuCommand.Command\x12\x15\n\rcommand_param\x18\x02 \x01(\x05\"\xb0\x05\n\x07\x43ommand\x12\x0c\n\x08RCU_NONE\x10\x00\x12\x10\n\x0cRCU_OPEN_AC1\x10\x03\x12\x11\n\rRCU_CLOSE_AC1\x10\x04\x12\x10\n\x0cRCU_OPEN_AC2\x10\x05\x12\x11\n\rRCU_CLOSE_AC2\x10\x06\x12\x11\n\rRCU_OPEN_PBV1\x10\x07\x12\x12\n\x0eRCU_CLOSE_PBV1\x10\x08\x12\x11\n\rRCU_OPEN_PBV2\x10\t\x12\x12\n\x0eRCU_CLOSE_PBV2\x10\n\x12\x11\n\rRCU_OPEN_PBV3\x10\x0b\x12\x12\n\x0eRCU_CLOSE_PBV3\x10\x0c\x12\x11\n\rRCU_OPEN_PBV4\x10\r\x12\x12\n\x0eRCU_CLOSE_PBV4\x10\x0e\x12\x11\n\rRCU_OPEN_SOL5\x10\x15\x12\x12\n\x0eRCU_CLOSE_SOL5\x10\x16\x12\x11\n\rRCU_OPEN_SOL6\x10\x17\x12\x12\n\x0eRCU_CLOSE_SOL6\x10\x18\x12\x11\n\rRCU_OPEN_SOL7\x10\x19\x12\x12\n\x0eRCU_CLOSE_SOL7\x10\x1a\x12\x12\n\x0eRCU_OPEN_SOL8A\x10\x1b\x12\x13\n\x0fRCU_CLOSE_SOL8A\x10\x1c\x12\x12\n\x0eRCU_OPEN_SOL8B\x10\x1d\x12\x13\n\x0fRCU_CLOSE_SOL8B\x10\x1e\x12\x1b\n\x17RCU_TARE_NOS1_LOAD_CELL\x10!\x12\x1b\n\x17RCU_TARE_NOS2_LOAD_CELL\x10\"\x12 \n\x1cRCU_CALIBRATE_NOS1_LOAD_CELL\x10#\x12 \n\x1cRCU_CALIBRATE_NOS2_LOAD_CELL\x10$\x12\x17\n\x13RCU_IGNITE_PAD_BOX1\x10\x1f\x12\x17\n\x13RCU_IGNITE_PAD_BOX2\x10 \x12\x15\n\x11RCU_KILL_PAD_BOX1\x10%\x12\x15\n\x11RCU_KILL_PAD_BOX2\x10&\x12\x0c\n\x08RCU_LAST\x10\'b\x06proto3') diff --git a/backend/proto/Python/ControlMessage_pb2.py b/backend/proto/Python/ControlMessage_pb2.py index 710584c..37304c2 100644 --- a/backend/proto/Python/ControlMessage_pb2.py +++ b/backend/proto/Python/ControlMessage_pb2.py @@ -22,7 +22,7 @@ _sym_db = _symbol_database.Default() -# import CoreProto_pb2 as CoreProto__pb2 +import CoreProto_pb2 as CoreProto__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x43ontrolMessage.proto\x12\x05Proto\x1a\x0f\x43oreProto.proto\"\xec\x02\n\x0e\x43ontrolMessage\x12\x1b\n\x06source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x06target\x18\x02 \x01(\x0e\x32\x0b.Proto.Node\x12\x1b\n\x13source_sequence_num\x18\x04 \x01(\r\x12\x1d\n\x03\x61\x63k\x18\x05 \x01(\x0b\x32\x0e.Proto.AckNackH\x00\x12\x1e\n\x04nack\x18\x06 \x01(\x0b\x32\x0e.Proto.AckNackH\x00\x12\x1b\n\x04ping\x18\x07 \x01(\x0b\x32\x0b.Proto.PingH\x00\x12\x1e\n\x02hb\x18\x08 \x01(\x0b\x32\x10.Proto.HeartbeatH\x00\x12\'\n\tsys_state\x18\t \x01(\x0b\x32\x12.Proto.SystemStateH\x00\x12(\n\x08sys_ctrl\x18\n \x01(\x0b\x32\x14.Proto.SystemControlH\x00\x12)\n\x08hb_state\x18\x0b \x01(\x0b\x32\x15.Proto.HeartbeatStateH\x00\x42\t\n\x07message\"w\n\x07\x41\x63kNack\x12&\n\x11\x61\x63king_msg_source\x18\x01 \x01(\x0e\x32\x0b.Proto.Node\x12\'\n\racking_msg_id\x18\x02 \x01(\x0e\x32\x10.Proto.MessageID\x12\x1b\n\x13\x61\x63king_sequence_num\x18\x03 \x01(\r\"d\n\x04Ping\x12\x13\n\x0bping_ack_id\x18\x01 \x01(\r\x12\"\n\x1aping_response_sequence_num\x18\x02 \x01(\r\x12#\n\x1bsys_state_response_required\x18\x03 \x01(\x08\"-\n\tHeartbeat\x12 \n\x18hb_response_sequence_num\x18\x01 \x01(\r\"\x8e\x03\n\x0bSystemState\x12+\n\tsys_state\x18\x01 \x01(\x0e\x32\x18.Proto.SystemState.State\x12-\n\x0crocket_state\x18\x02 \x01(\x0e\x32\x12.Proto.RocketStateH\x00\x88\x01\x01\"\x91\x02\n\x05State\x12\x0f\n\x0bSYS_INVALID\x10\x00\x12\x17\n\x13SYS_BOOTUP_COMPLETE\x10\x01\x12\x1c\n\x18SYS_ASSERT_FAILURE_RESET\x10\x02\x12\x16\n\x12SYS_UNCAUGHT_RESET\x10\x03\x12\x18\n\x14SYS_NORMAL_OPERATION\x10\x04\x12#\n\x1fSYS_HEARTBEAT_LOSS_HALF_WARNING\x10\x05\x12\x1f\n\x1bSYS_HEARTBEAT_LOST_ABORTING\x10\x06\x12\x0c\n\x08SYS_WAIT\x10\x07\x12\x12\n\x0eSYS_RETRANSMIT\x10\x08\x12\x0f\n\x0bSYS_TIMEOUT\x10\t\x12\x15\n\x11SYS_SEND_NEXT_CMD\x10\nB\x0f\n\r_rocket_state\"\xb2\x02\n\rSystemControl\x12-\n\x07sys_cmd\x18\x01 \x01(\x0e\x32\x1c.Proto.SystemControl.Command\x12\x11\n\tcmd_param\x18\x02 \x01(\r\"\xde\x01\n\x07\x43ommand\x12\x0f\n\x0bSYS_INVALID\x10\x00\x12\r\n\tSYS_RESET\x10\x01\x12\x13\n\x0fSYS_FLASH_ERASE\x10\x02\x12\x19\n\x15SYS_LOG_PERIOD_CHANGE\x10\x03\x12\x14\n\x10HEARTBEAT_ENABLE\x10\x04\x12\x15\n\x11HEARTBEAT_DISABLE\x10\x05\x12\x18\n\x14SYS_FLASH_LOG_ENABLE\x10\x06\x12\x19\n\x15SYS_FLASH_LOG_DISABLE\x10\x07\x12!\n\x1dSYS_CRITICAL_FLASH_FULL_ERASE\x10\x08\"\xbf\x01\n\x0eHeartbeatState\x12\x35\n\x0btimer_state\x18\x01 \x01(\x0e\x32 .Proto.HeartbeatState.TimerState\x12\x14\n\x0ctimer_period\x18\x02 \x01(\r\x12\x17\n\x0ftimer_remaining\x18\x03 \x01(\r\"G\n\nTimerState\x12\x11\n\rUNINITIALIZED\x10\x00\x12\x0c\n\x08\x43OUNTING\x10\x01\x12\n\n\x06PAUSED\x10\x02\x12\x0c\n\x08\x43OMPLETE\x10\x03\x62\x06proto3') diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index 1bb51d0..76a6f28 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -18,7 +18,7 @@ from src.support.ProtobufParser import ProtobufParser from src.support.CommonLogger import logger -from backend.src.StateMachine.StateMachineManager import State, Event, StateMachineManager +from src.StateMachineManager import StateMachineManager from src.ThreadManager import THREAD_MESSAGE_DB_WRITE, THREAD_MESSAGE_HEARTBEAT_SERIAL, THREAD_MESSAGE_KILL, THREAD_MESSAGE_LOAD_CELL_VOLTAGE, THREAD_MESSAGE_SERIAL_WRITE, WorkQ_Message from src.Utils import Utils as utl @@ -259,29 +259,29 @@ def serial_rx_thread(ser_han: SerialHandler): ser_han.handle_serial_message() pass -# def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) -> bool: -# """ -# Process the message from the workq. - -# Args: -# message (str): -# The message from the workq. -# """ -# logger.debug(f"Processing serial workq message: {message.message_type}") -# messageID = message.message_type - -# if messageID == THREAD_MESSAGE_KILL: -# logger.debug(f"Killing {ser_han.thread_name} thread") -# return False -# elif messageID == THREAD_MESSAGE_SERIAL_WRITE: -# command = message.message[0] -# target = message.message[1] -# command_param = message.message[2] -# source_sequence_number = message.message[3] -# ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) -# elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: -# ser_han.send_serial_control_message(message.message[0]) -# return True +def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) -> bool: + """ + Process the message from the workq. + + Args: + message (str): + The message from the workq. + """ + logger.debug(f"Processing serial workq message: {message.message_type}") + messageID = message.message_type + + if messageID == THREAD_MESSAGE_KILL: + logger.debug(f"Killing {ser_han.thread_name} thread") + return False + elif messageID == THREAD_MESSAGE_SERIAL_WRITE: + command = message.message[0] + target = message.message[1] + command_param = message.message[2] + source_sequence_number = message.message[3] + ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) + elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: + ser_han.send_serial_control_message(message.message[0]) + return True def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread_workq: mp.Queue, message_handler_workq: mp.Queue): """ @@ -315,14 +315,11 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread rx_thread.start() while (1): # then once the queue is empty read the serial port - message = serial_workq.get() - print("Message", message.message_type) - if message.message_type == THREAD_MESSAGE_KILL: - logger.debug(f"Killing {ser_han.thread_name} thread") + if not process_serial_workq_message(serial_workq.get(), ser_han): ser_han.kill_rx = True rx_thread.join(10) return #If valid messages received, initialze the state machine and start sending messages StateMachineManager() - StateMachineManager.start_sending_msg(message, ser_han) + StateMachineManager.start_sending_msg(ser_han) diff --git a/backend/src/StateMachine/BaseStateMachine.py b/backend/src/StateMachine/BaseStateMachine.py deleted file mode 100644 index 494af8c..0000000 --- a/backend/src/StateMachine/BaseStateMachine.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -BaseStateMachine.py - -Define a common Application Program Interface(API) for a set of State Machine subclasses. -Enable code reuse and maintainability when the code base grows in the future. The implementation of the methods -are left for the subclasses that inherit from this class. - -Since the BaseStateMachine is an abstract class. Do NOT instantiate this because there is no actual implementation. - -""" - -# General imports ================================================================================= -from abc import ABC, abstractmethod - -# Class Definitions =============================================================================== -class BaseStateMachine(ABC): - - @abstractmethod - def start(self): - pass - - @abstractmethod - def exit(self): - pass - - # @abstractmethod - # def enter_state(self): - # pass - - # @abstractmethod - # def exit_state(self): - # pass - - @abstractmethod - def update_state(self): - pass - - # @abstractmethod - # def get_next_state(self): - # pass - - # @abstractmethod - # def on_trigger_enter_state(self): - # pass - - # @abstractmethod - # def on_trigger_stay_state(self): - # pass - - # @abstractmethod - # def on_trigger_exit_state(self): - # pass diff --git a/backend/src/StateMachine/test.py b/backend/src/StateMachine/test.py deleted file mode 100644 index b11db34..0000000 --- a/backend/src/StateMachine/test.py +++ /dev/null @@ -1,42 +0,0 @@ -# Python program showing -# abstract base class work -from abc import ABC, abstractmethod - - -class Polygon(ABC): - - @abstractmethod - def noofsides(self): - pass - - @abstractmethod - def start(self): - pass - - -class StateMachineManager(Polygon): - """ - Manage state transitions for the communication between the RCU and the rocket. - """ - # def __init__(self): - # self.state = State(6) - # self.event = Event(3) - def start(self): - print("Im here") - -#Test - Do NOT commit -test = StateMachineManager() -test.start() - -class Triangle(Polygon): - - # overriding abstract method - def noofsides(self): - print("I have 3 sides") - - def start(self): - print("Starting") - - -test = StateMachineManager() -test.start() \ No newline at end of file diff --git a/backend/src/StateMachine/StateMachineManager.py b/backend/src/StateMachineManager.py similarity index 69% rename from backend/src/StateMachine/StateMachineManager.py rename to backend/src/StateMachineManager.py index bc49749..0cab228 100644 --- a/backend/src/StateMachine/StateMachineManager.py +++ b/backend/src/StateMachineManager.py @@ -14,18 +14,14 @@ # General imports ================================================================================= from enum import Enum, unique import os, sys, time, random +import google.protobuf.message as Message # Project specific imports ======================================================================== -dirname, _ = os.path.split(os.path.abspath(__file__)) -sys.path.insert(0, os.path.join(dirname.split("backend", 1)[0], 'backend')) -sys.path.insert(0, os.path.join(dirname.split("backend", 1)[0], 'src/StateMachine')) -from src.StateMachine.BaseStateMachine import BaseStateMachine from src.ThreadManager import * from src.SerialHandler import SerialHandler from proto.Python.CoreProto_pb2 import RocketState from proto.Python.ControlMessage_pb2 import SystemState - # Class Definitions =============================================================================== @unique class Event(Enum): @@ -46,7 +42,7 @@ class Event(Enum): ROCKET_RCVD_CMD = 7 ROCKET_SEND_ACK = 8 -class StateMachineManager(BaseStateMachine): +class StateMachineManager(): """ Manage state transitions for the communication between the RCU and the rocket. @@ -58,33 +54,13 @@ def __init__(self): self.message = None self.retransmit_counter = 0 - def start_sending_msg(self, message: WorkQ_Message, ser_han: SerialHandler): - """ - Send control/command message from RCU (radio) to Rocket (DMB) by calling - the appropriate send functions that corresponds to the message type. - Set the RCU and rocket state to the appropriate state and call the next function - Args: - message: message from the workq to identify whether to send control - or command message - ser_han: Serial Handler class to call the functions to send the messages + def start_sending_msg(self): - """ - messageID = message.message_type - if messageID == THREAD_MESSAGE_SERIAL_WRITE: - command = message.message[0] - target = message.message[1] - command_param = message.message[2] - source_sequence_number = message.message[3] - ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) - elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: - ser_han.send_serial_control_message(message.message[0]) - - #Update the state and call next step in the state machine self.sys_state = SystemState.SYS_WAIT - self.handle_wait(message, ser_han) + self.handle_wait() - def handle_wait(self, message: WorkQ_Message, ser_han: SerialHandler,): + def handle_wait(self): """ Handle the logic of calling other functions depending on rocket responses. @@ -96,7 +72,7 @@ def handle_wait(self, message: WorkQ_Message, ser_han: SerialHandler,): if self.sys_state == SystemState.SYS_WAIT: #If received NAK from Rocket: if response == "NAK": - self.handle_retransmit(message, ser_han) + self.handle_retransmit() elif response == "ACK": self.handle_send_next_cmd() elif response == "TIMEOUT": @@ -105,11 +81,11 @@ def handle_wait(self, message: WorkQ_Message, ser_han: SerialHandler,): else: print("Bro, you messed up\n\r") - def handle_retransmit(self, message: WorkQ_Message, ser_han: SerialHandler): + def handle_retransmit(self): #If retransmit counter is within range, retransmit the same message if self.retransmit_counter < 2: self.retransmit_counter += 1 - self.start_sending_msg(message, ser_han) + self.start_sending_msg() else: self.sys_state = SystemState.SYS_SEND_NEXT_CMD self.handle_send_next_cmd() @@ -119,11 +95,11 @@ def handle_send_next_cmd(self): self.sys_state = SystemState.SYS_WAIT #reset retransmit counter self.retransmit_counter = 0 - def handle_timeout(self, message: WorkQ_Message, ser_han: SerialHandler): + def handle_timeout(self): #Call retransmit self.sys_state = SystemState.SYS_RETRANSMIT - self.handle_retransmit(message, ser_han) + self.handle_retransmit() def simulate_rocket_response(self): # Simulate different responses from Rocket diff --git a/backend/src/main.py b/backend/src/main.py index f50062b..bbe0bd0 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -26,29 +26,29 @@ def initialize_threads(): ''' logger.info('Initializing threads') thread_pool = {} - uart_workq = mp.Queue() + # uart_workq = mp.Queue() radio_workq = mp.Queue() - db_workq = mp.Queue() - loadcell_workq = mp.Queue() + # db_workq = mp.Queue() + # loadcell_workq = mp.Queue() message_handler_workq = mp.Queue() - heartbeat_workq = mp.Queue() + # heartbeat_workq = mp.Queue() # Create a main thread for handling thread messages thread_pool['message_handler'] = {'thread': None, 'workq': message_handler_workq} # Initialize the threads - uart_thread = mp.Process(target=serial_thread, args=('uart', sd.UART, UART_BAUDRATE, uart_workq, message_handler_workq)) + # uart_thread = mp.Process(target=serial_thread, args=('uart', sd.UART, UART_BAUDRATE, uart_workq, message_handler_workq)) radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq)) - db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) - lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) - hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) + # db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) + # lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) + # hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) # Add the threads to the thread pool - thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} + # thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} thread_pool['radio'] = {'thread': radio_thread, 'workq': radio_workq} - thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} - thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} - thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} + # thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} + # thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} + # thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} tm.thread_pool = thread_pool return From 912910aef6a43552605376ea17b78fc869b6093b Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:22:47 -0700 Subject: [PATCH 09/14] Passing in the shared radio_event_queue and uart_event_queue from the respective serialhandler thread (radio/uart) with the state machine manager. Adjust main function to only call the radio_thread and the state_machine manager thread to ensure they are using the same queue. --- backend/src/SerialHandler.py | 18 +-- backend/src/StateMachineManager.py | 178 +++++++++++++++-------------- backend/src/main.py | 39 +++++-- 3 files changed, 131 insertions(+), 104 deletions(-) diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index 76a6f28..4646b9a 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -8,6 +8,7 @@ import serial # You'll need to run `pip install pyserial` from cobs import cobs # pip install cobs import google.protobuf.message as Message +from queue import Queue # Project specific imports ======================================================================== import proto.Python.CoreProto_pb2 as ProtoCore @@ -22,6 +23,7 @@ from src.ThreadManager import THREAD_MESSAGE_DB_WRITE, THREAD_MESSAGE_HEARTBEAT_SERIAL, THREAD_MESSAGE_KILL, THREAD_MESSAGE_LOAD_CELL_VOLTAGE, THREAD_MESSAGE_SERIAL_WRITE, WorkQ_Message from src.Utils import Utils as utl + # Constants ======================================================================================== MIN_SERIAL_MESSAGE_LENGTH = 6 @@ -36,7 +38,7 @@ class SerialDevices(enum.Enum): # Class Definitions =============================================================================== class SerialHandler(): - def __init__(self, thread_name: str, port: str, baudrate: int, message_handler_workq: mp.Queue): + def __init__(self, thread_name: str, port: str, baudrate: int, message_handler_workq: mp.Queue, event_queue : mp.Queue): """ This thread class creates threads to handle incoming and outgoing serial messages over @@ -58,7 +60,8 @@ def __init__(self, thread_name: str, port: str, baudrate: int, message_handler_w self.thread_name = thread_name self.send_message_workq = message_handler_workq self.kill_rx = False - + self.event_queue = event_queue + self.event_queue.put("Hello") # Open serial serial port try: self.serial_port = serial.Serial(port=port, baudrate=baudrate, bytesize=8, parity=serial.PARITY_NONE, timeout=None, stopbits=serial.STOPBITS_ONE) @@ -283,7 +286,7 @@ def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) ser_han.send_serial_control_message(message.message[0]) return True -def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread_workq: mp.Queue, message_handler_workq: mp.Queue): +def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread_workq: mp.Queue, message_handler_workq: mp.Queue, event_queue: mp.Queue): """ Thread function for the incoming serial data listening. @@ -307,7 +310,7 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread # This log line should be removed once the pi core issue is solved logger.info(f"{device.name} process: {os.getpid()}") serial_workq = thread_workq - ser_han = SerialHandler(thread_name, port, baudrate, message_handler_workq) + ser_han = SerialHandler(thread_name, port, baudrate, message_handler_workq, event_queue) if ser_han.serial_port == None: return @@ -319,7 +322,6 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread ser_han.kill_rx = True rx_thread.join(10) return - #If valid messages received, initialze the state machine and start sending messages - StateMachineManager() - StateMachineManager.start_sending_msg(ser_han) - +def radio_thread_test (radio_event_queue: mp.Queue): + radio_event_queue.put("ACKACKHELPME") + time.sleep(1) diff --git a/backend/src/StateMachineManager.py b/backend/src/StateMachineManager.py index 0cab228..9e7f853 100644 --- a/backend/src/StateMachineManager.py +++ b/backend/src/StateMachineManager.py @@ -14,11 +14,12 @@ # General imports ================================================================================= from enum import Enum, unique import os, sys, time, random +from queue import Queue +import threading import google.protobuf.message as Message # Project specific imports ======================================================================== from src.ThreadManager import * -from src.SerialHandler import SerialHandler from proto.Python.CoreProto_pb2 import RocketState from proto.Python.ControlMessage_pb2 import SystemState @@ -47,99 +48,102 @@ class StateMachineManager(): Manage state transitions for the communication between the RCU and the rocket. """ - def __init__(self): + def __init__(self, uart_queue: Queue, radio_queue: Queue): self.sys_state = SystemState.SYS_NORMAL_OPERATION self.rocket_state = RocketState.RS_LISTENING self.event = Event.RCU_START - self.message = None + self.last_message = None self.retransmit_counter = 0 - - - def start_sending_msg(self): - - self.sys_state = SystemState.SYS_WAIT - self.handle_wait() - - def handle_wait(self): - - """ - Handle the logic of calling other functions depending on rocket responses. - """ - #Ideally, should have another function to parse the response received from DMB - # response = parse_rocket_response(), change the if blocks below to if, elif,else, etc - response = self.simulate_rocket_response() - - if self.sys_state == SystemState.SYS_WAIT: - #If received NAK from Rocket: - if response == "NAK": - self.handle_retransmit() - elif response == "ACK": - self.handle_send_next_cmd() - elif response == "TIMEOUT": - self.sys_state = SystemState.SYS_TIMEOUT - self.handle_timeout() - else: - print("Bro, you messed up\n\r") - - def handle_retransmit(self): - #If retransmit counter is within range, retransmit the same message - if self.retransmit_counter < 2: - self.retransmit_counter += 1 - self.start_sending_msg() - else: - self.sys_state = SystemState.SYS_SEND_NEXT_CMD - self.handle_send_next_cmd() - - def handle_send_next_cmd(self): - #Send next cmd - self.sys_state = SystemState.SYS_WAIT - #reset retransmit counter - self.retransmit_counter = 0 - def handle_timeout(self): - - #Call retransmit - self.sys_state = SystemState.SYS_RETRANSMIT - self.handle_retransmit() - - def simulate_rocket_response(self): - # Simulate different responses from Rocket - responses = ["ACK", "NAK", "TIMEOUT"] - return random.choice(responses) + self.uart_queue = uart_queue + self.radio_queue = radio_queue + + while not self.radio_queue.empty(): + queue_item = self.radio_queue.get() + print("queue_item", queue_item) + + # def start_sending_msg(self): + + # self.sys_state = SystemState.SYS_WAIT + # self.handle_wait() + + # def handle_wait(self): + + # """ + # Handle the logic of calling other functions depending on rocket responses. + # """ + # #Ideally, should have another function to parse the response received from DMB + # # response = parse_rocket_response(), change the if blocks below to if, elif,else, etc + # response = self.simulate_rocket_response() + + # if self.sys_state == SystemState.SYS_WAIT: + # #If received NAK from Rocket: + # if response == "NAK": + # self.handle_retransmit() + # elif response == "ACK": + # self.handle_send_next_cmd() + # elif response == "TIMEOUT": + # self.sys_state = SystemState.SYS_TIMEOUT + # self.handle_timeout() + # else: + # print("Bro, you messed up\n\r") + + # def handle_retransmit(self): + # #If retransmit counter is within range, retransmit the same message + # if self.retransmit_counter < 2: + # self.retransmit_counter += 1 + # self.start_sending_msg() + # else: + # self.sys_state = SystemState.SYS_SEND_NEXT_CMD + # self.handle_send_next_cmd() + + # def handle_send_next_cmd(self): + # #Send next cmd + # self.sys_state = SystemState.SYS_WAIT + # #reset retransmit counter + # self.retransmit_counter = 0 + # def handle_timeout(self): + + # #Call retransmit + # self.sys_state = SystemState.SYS_RETRANSMIT + # self.handle_retransmit() + + # def simulate_rocket_response(self): + # # Simulate different responses from Rocket + # responses = ["ACK", "NAK", "TIMEOUT"] + # return random.choice(responses) - def exit(self): - """ - Exit the state machine for RCU and Rocket serial communication by setting RCU state to be uninitialized - and event to RCU Exit. - - """ - self.sys_state = SystemState.SYS_INVALID - self.rocket_state = RocketState.RS_NONE - self.event = Event.RCU_EXIT - - def get_rcu_state(self): - """ - Return the current state of the RCU. - - """ - print("\nThe current RCU state:") - return self.sys_state - - def get_rocket_state(self): - """" - Return the current state of the rocket. - - """ - print("\nThe current rocket state:") - return self.rocket_state + # def exit(self): + # """ + # Exit the state machine for RCU and Rocket serial communication by setting RCU state to be uninitialized + # and event to RCU Exit. + + # """ + # self.sys_state = SystemState.SYS_INVALID + # self.rocket_state = RocketState.RS_NONE + # self.event = Event.RCU_EXIT + + # def get_rcu_state(self): + # """ + # Return the current state of the RCU. + + # """ + # print("\nThe current RCU state:") + # return self.sys_state + + # def get_rocket_state(self): + # """" + # Return the current state of the rocket. + + # """ + # print("\nThe current rocket state:") + # return self.rocket_state - -#Test - Do NOT commit -test = StateMachineManager() -print(test.start()) -print(test.get_rcu_state()) -print(test.get_rocket_state()) - +def state_machine_manager_thread (uart_queue: Queue, radio_queue: Queue): + """ + State Machine manager function to start the state machine thread + """ + StateMachineManager(uart_queue, radio_queue) diff --git a/backend/src/main.py b/backend/src/main.py index bbe0bd0..65afe7f 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -1,6 +1,6 @@ # General imports ================================================================================= -import os, sys +import os, sys, time import multiprocessing as mp # Project specific imports ======================================================================== @@ -11,9 +11,11 @@ from src.support.CommonLogger import logger from src.DatabaseHandler import database_thread from src.HeartbeatHandler import heartbeat_thread -from src.SerialHandler import SerialDevices as sd, serial_thread +from src.SerialHandler import SerialDevices as sd, serial_thread, radio_thread_test from src.ThreadManager import ThreadManager as tm from src.LoadCellHandler import load_cell_thread +from src.StateMachineManager import state_machine_manager_thread +from queue import Queue # Constants ======================================================================================== UART_BAUDRATE = 115200 @@ -31,17 +33,21 @@ def initialize_threads(): # db_workq = mp.Queue() # loadcell_workq = mp.Queue() message_handler_workq = mp.Queue() + uart_event_queue = mp.Queue() + radio_event_queue = mp.Queue() # heartbeat_workq = mp.Queue() # Create a main thread for handling thread messages thread_pool['message_handler'] = {'thread': None, 'workq': message_handler_workq} # Initialize the threads - # uart_thread = mp.Process(target=serial_thread, args=('uart', sd.UART, UART_BAUDRATE, uart_workq, message_handler_workq)) - radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq)) + # uart_thread = mp.Process(target=serial_thread, args=('uart', sd.UART, UART_BAUDRATE, uart_workq, message_handler_workq, uart_event_queue)) + # radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq, radio_event_queue)) + radio_thread = mp.Process(target=radio_thread_test, args=(radio_event_queue, )) # db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) # lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) # hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) + serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(uart_event_queue, radio_event_queue)) # Add the threads to the thread pool # thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} @@ -49,14 +55,29 @@ def initialize_threads(): # thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} # thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} # thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} + thread_pool['state_machine'] = {'thread': serial_state_machine_thread, 'workq': None} tm.thread_pool = thread_pool return if __name__ == "__main__": - tm() - initialize_threads() - tm.start_threads() + # tm() + # initialize_threads() + # tm.start_threads() + radio_event_queue = mp.Queue() + uart_event_queue = mp.Queue() + radio_thread = mp.Process(target=radio_thread_test, args=(radio_event_queue, )) + serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(uart_event_queue, radio_event_queue)) - while 1: - tm.handle_thread_messages() \ No newline at end of file + radio_thread.start() + serial_state_machine_thread.start() + + time.sleep(5) + + radio_thread.terminate() + serial_state_machine_thread.terminate() + + radio_thread.join() + serial_state_machine_thread.join() + # while 1: + # tm.handle_thread_messages() \ No newline at end of file From c94a2a4e75b03a3c31fdb4d427488e75395600f9 Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 23 Nov 2024 11:33:19 -0700 Subject: [PATCH 10/14] Proof of concept. Uart and state machine thread are able to communicate via shared queue and flag --- backend/src/SerialHandler.py | 21 +++++++++-- backend/src/StateMachineManager.py | 60 +++++++++++++++++++++++------- backend/src/main.py | 29 ++++++++------- 3 files changed, 80 insertions(+), 30 deletions(-) diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index 4646b9a..a67c16b 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -15,6 +15,7 @@ import proto.Python.TelemetryMessage_pb2 as TelemetryProto import proto.Python.ControlMessage_pb2 as ControlProto + from src.support.Codec import Codec from src.support.ProtobufParser import ProtobufParser from src.support.CommonLogger import logger @@ -322,6 +323,20 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread ser_han.kill_rx = True rx_thread.join(10) return -def radio_thread_test (radio_event_queue: mp.Queue): - radio_event_queue.put("ACKACKHELPME") - time.sleep(1) +def uart_thread_test (uart_event_queue: mp.Queue, uart_event: mp.Event): + + while True: + uart_event.wait() + while not uart_event_queue.empty(): + sys_state = uart_event_queue.get() + print("Sys_state: ", sys_state) + if sys_state == ControlProto.SystemState.SYS_SEND_NEXT_CMD: + print("Sending next command") + elif sys_state == ControlProto.SystemState.SYS_RETRANSMIT or sys_state == ControlProto.SystemState.SYS_TIMEOUT: + print("Resending the same message") + uart_event.clear() + return + # time.sleep(1) +def radio_thread_test (radio_event_queue: mp.Queue, radio_event: mp.Event): + pass + diff --git a/backend/src/StateMachineManager.py b/backend/src/StateMachineManager.py index 9e7f853..c432fb5 100644 --- a/backend/src/StateMachineManager.py +++ b/backend/src/StateMachineManager.py @@ -16,6 +16,7 @@ import os, sys, time, random from queue import Queue import threading +import multiprocessing as mp import google.protobuf.message as Message # Project specific imports ======================================================================== @@ -48,23 +49,52 @@ class StateMachineManager(): Manage state transitions for the communication between the RCU and the rocket. """ - def __init__(self, uart_queue: Queue, radio_queue: Queue): + def __init__(self, uart_queue: Queue, radio_queue: Queue, uart_event: mp.Event, radio_event: mp.Event): self.sys_state = SystemState.SYS_NORMAL_OPERATION - self.rocket_state = RocketState.RS_LISTENING - self.event = Event.RCU_START self.last_message = None self.retransmit_counter = 0 self.uart_queue = uart_queue self.radio_queue = radio_queue - - while not self.radio_queue.empty(): - queue_item = self.radio_queue.get() - print("queue_item", queue_item) - - # def start_sending_msg(self): - - # self.sys_state = SystemState.SYS_WAIT - # self.handle_wait() + self.uart_event = uart_event + self.radio_event = radio_event + + def start(self): + + #Randomnize ACK/NAK/TIMEOUT + choice = self.mock_receive_msg() + print("Choice: ", choice) + if choice == "ACK": + self.sys_state = SystemState.SYS_SEND_NEXT_CMD + self.uart_queue.put(self.sys_state) + elif choice == "NAK": + #If retransmit counter is less than 2, then resend + if self.retransmit_counter < 2: + self.sys_state = SystemState.SYS_RETRANSMIT + self.uart_queue.put(self.sys_state) + self.retransmit_counter += 1 + #If retransmit twice already, then send the next message and reset the retransmit flag + else: + self.sys_state = SystemState.SYS_SEND_NEXT_CMD + self.uart_queue.put(self.sys_state) + self.retransmit_counter = 0 + elif choice == "TIMEOUT": + if self.retransmit_counter < 2: + #Send time out event and let the uart thread call retransmit function + self.sys_state = SystemState.SYS_TIMEOUT + self.uart_queue.put(self.sys_state) + self.retransmit_counter += 1 + #If retransmit twice already, then send the next message and reset the retransmit flag + else: + self.sys_state = SystemState.SYS_SEND_NEXT_CMD + self.uart_queue.put(self.sys_state) + self.retransmit_counter = 0 + self.uart_event.set() + + def mock_receive_msg(self): + mock_msg = ["ACK"] + choice = random.choice(mock_msg) + print(choice) + return choice # def handle_wait(self): @@ -139,11 +169,13 @@ def __init__(self, uart_queue: Queue, radio_queue: Queue): # return self.rocket_state -def state_machine_manager_thread (uart_queue: Queue, radio_queue: Queue): +def state_machine_manager_thread (uart_queue: Queue, radio_queue: Queue, uart_event: mp.Event, radio_event: mp.Event): """ State Machine manager function to start the state machine thread """ - StateMachineManager(uart_queue, radio_queue) + new_state_machine = StateMachineManager(uart_queue, radio_queue, uart_event, radio_event) + new_state_machine.start() + diff --git a/backend/src/main.py b/backend/src/main.py index 65afe7f..6f0a23b 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -11,7 +11,7 @@ from src.support.CommonLogger import logger from src.DatabaseHandler import database_thread from src.HeartbeatHandler import heartbeat_thread -from src.SerialHandler import SerialDevices as sd, serial_thread, radio_thread_test +from src.SerialHandler import SerialDevices as sd, serial_thread, radio_thread_test, uart_thread_test from src.ThreadManager import ThreadManager as tm from src.LoadCellHandler import load_cell_thread from src.StateMachineManager import state_machine_manager_thread @@ -28,7 +28,7 @@ def initialize_threads(): ''' logger.info('Initializing threads') thread_pool = {} - # uart_workq = mp.Queue() + uart_workq = mp.Queue() radio_workq = mp.Queue() # db_workq = mp.Queue() # loadcell_workq = mp.Queue() @@ -43,15 +43,16 @@ def initialize_threads(): # Initialize the threads # uart_thread = mp.Process(target=serial_thread, args=('uart', sd.UART, UART_BAUDRATE, uart_workq, message_handler_workq, uart_event_queue)) # radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq, radio_event_queue)) - radio_thread = mp.Process(target=radio_thread_test, args=(radio_event_queue, )) + # radio_thread = mp.Process(target=radio_thread_test, args=(radio_event_queue, )) + uart_thread = mp.Process(target=uart_thread_test, args=(uart_event_queue,)) # db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) # lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) # hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(uart_event_queue, radio_event_queue)) # Add the threads to the thread pool - # thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} - thread_pool['radio'] = {'thread': radio_thread, 'workq': radio_workq} + thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} + # thread_pool['radio'] = {'thread': radio_thread, 'workq': radio_workq} # thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} # thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} # thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} @@ -66,18 +67,20 @@ def initialize_threads(): # tm.start_threads() radio_event_queue = mp.Queue() uart_event_queue = mp.Queue() - radio_thread = mp.Process(target=radio_thread_test, args=(radio_event_queue, )) - serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(uart_event_queue, radio_event_queue)) + uart_event = mp.Event() + radio_event = mp.Event() + # radio_thread = mp.Process(target=radio_thread_test, args=(radio_event_queue, )) + uart_thread = mp.Process(target=uart_thread_test, args=(uart_event_queue, uart_event)) + serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(uart_event_queue, radio_event_queue, uart_event, radio_event)) - radio_thread.start() + # radio_thread.start() + uart_thread.start() serial_state_machine_thread.start() - time.sleep(5) + # time.sleep(5) - radio_thread.terminate() - serial_state_machine_thread.terminate() - - radio_thread.join() + # radio_thread.join() + uart_thread.join() serial_state_machine_thread.join() # while 1: # tm.handle_thread_messages() \ No newline at end of file From b26814f1e15c2f95f5567603ee2225cc91b3321b Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 7 Dec 2024 14:12:02 -0700 Subject: [PATCH 11/14] Implement State machine logic to account for ACK/NAK/TIMEOUT/NAK. Have not include logic for TIMEOUT. Have not tested. --- backend/src/SerialHandler.py | 55 ++++---- backend/src/StateMachineManager.py | 196 ++++++++++++----------------- backend/src/main.py | 58 ++++----- 3 files changed, 129 insertions(+), 180 deletions(-) diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index a67c16b..be6bb92 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -39,7 +39,7 @@ class SerialDevices(enum.Enum): # Class Definitions =============================================================================== class SerialHandler(): - def __init__(self, thread_name: str, port: str, baudrate: int, message_handler_workq: mp.Queue, event_queue : mp.Queue): + def __init__(self, thread_name: str, port: str, baudrate: int, message_handler_workq: mp.Queue, serial_event_queue : mp.Queue, state_change_event_queue: mp.Queue, serial_event: mp.Event, state_change_event: mp.Event): """ This thread class creates threads to handle incoming and outgoing serial messages over @@ -61,8 +61,11 @@ def __init__(self, thread_name: str, port: str, baudrate: int, message_handler_w self.thread_name = thread_name self.send_message_workq = message_handler_workq self.kill_rx = False - self.event_queue = event_queue - self.event_queue.put("Hello") + self.serial_event_queue = serial_event_queue + self.state_change_event_queue = state_change_event_queue + self.serial_event = serial_event + self.state_change_event = state_change_event + self.current_ser_workq_msg = None # Open serial serial port try: self.serial_port = serial.Serial(port=port, baudrate=baudrate, bytesize=8, parity=serial.PARITY_NONE, timeout=None, stopbits=serial.STOPBITS_ONE) @@ -187,6 +190,13 @@ def process_control_message(self, data): logger.debug(f"Received message intended for {utl.get_node_from_enum(received_message.target)}") return + #Check to see if it's nak or ack - Might be a better way to check for ACK vs NAK + if received_message.ack: + self.serial_event_queue.put("ACK") + elif received_message.nack: + self.serial_event_queue.put("NAK") + self.serial_event.set() + json_str = ProtobufParser.parse_serial_to_json(data, ProtoCore.MessageID.MSG_CONTROL) self.send_message_workq.put(WorkQ_Message(self.thread_name, 'database', THREAD_MESSAGE_DB_WRITE, (ProtoCore.MessageID.MSG_CONTROL, json_str)))\ @@ -285,9 +295,12 @@ def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: ser_han.send_serial_control_message(message.message[0]) + ser_han.current_ser_workq_msg = message + ser_han.serial_event_queue.put("WAIT") + ser_han.serial_event.set() return True -def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread_workq: mp.Queue, message_handler_workq: mp.Queue, event_queue: mp.Queue): +def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread_workq: mp.Queue, message_handler_workq: mp.Queue, serial_event_queue: mp.Queue, state_change_event_queue: mp.Queue, serial_event: mp.Event, state_change_event: mp.Event): """ Thread function for the incoming serial data listening. @@ -311,7 +324,7 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread # This log line should be removed once the pi core issue is solved logger.info(f"{device.name} process: {os.getpid()}") serial_workq = thread_workq - ser_han = SerialHandler(thread_name, port, baudrate, message_handler_workq, event_queue) + ser_han = SerialHandler(thread_name, port, baudrate, message_handler_workq, serial_event_queue, state_change_event_queue, serial_event, state_change_event) if ser_han.serial_port == None: return @@ -319,24 +332,14 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread rx_thread.start() while (1): # then once the queue is empty read the serial port - if not process_serial_workq_message(serial_workq.get(), ser_han): - ser_han.kill_rx = True - rx_thread.join(10) - return -def uart_thread_test (uart_event_queue: mp.Queue, uart_event: mp.Event): - - while True: - uart_event.wait() - while not uart_event_queue.empty(): - sys_state = uart_event_queue.get() - print("Sys_state: ", sys_state) - if sys_state == ControlProto.SystemState.SYS_SEND_NEXT_CMD: - print("Sending next command") - elif sys_state == ControlProto.SystemState.SYS_RETRANSMIT or sys_state == ControlProto.SystemState.SYS_TIMEOUT: - print("Resending the same message") - uart_event.clear() - return - # time.sleep(1) -def radio_thread_test (radio_event_queue: mp.Queue, radio_event: mp.Event): - pass - + ser_han.state_change_event.wait() + while not ser_han.state_change_event_queue.empty(): + state_event = ser_han.state_change_event_queue.get() + if state_event == ControlProto.SystemState.SYS_SEND_NEXT_CMD: + if not process_serial_workq_message(serial_workq.get(), ser_han): + ser_han.kill_rx = True + rx_thread.join(10) + return + if state_event == ControlProto.SystemState.SYS_RETRANSMIT: + process_serial_workq_message(ser_han.current_ser_workq_msg, ser_han) + ser_han.state_change_event.clear() diff --git a/backend/src/StateMachineManager.py b/backend/src/StateMachineManager.py index c432fb5..8376eaf 100644 --- a/backend/src/StateMachineManager.py +++ b/backend/src/StateMachineManager.py @@ -49,131 +49,91 @@ class StateMachineManager(): Manage state transitions for the communication between the RCU and the rocket. """ - def __init__(self, uart_queue: Queue, radio_queue: Queue, uart_event: mp.Event, radio_event: mp.Event): - self.sys_state = SystemState.SYS_NORMAL_OPERATION - self.last_message = None + def __init__(self, serial_event_queue: Queue, system_state_queue: Queue, serial_event: mp.Event, state_change_event: mp.Event): self.retransmit_counter = 0 - self.uart_queue = uart_queue - self.radio_queue = radio_queue - self.uart_event = uart_event - self.radio_event = radio_event + self.serial_event_queue = serial_event_queue + self.system_state_queue = system_state_queue + self.serial_event = serial_event + self.state_change_event = state_change_event + + self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) + self.state_change_event.set() def start(self): - #Randomnize ACK/NAK/TIMEOUT - choice = self.mock_receive_msg() - print("Choice: ", choice) - if choice == "ACK": - self.sys_state = SystemState.SYS_SEND_NEXT_CMD - self.uart_queue.put(self.sys_state) - elif choice == "NAK": - #If retransmit counter is less than 2, then resend - if self.retransmit_counter < 2: - self.sys_state = SystemState.SYS_RETRANSMIT - self.uart_queue.put(self.sys_state) - self.retransmit_counter += 1 - #If retransmit twice already, then send the next message and reset the retransmit flag - else: - self.sys_state = SystemState.SYS_SEND_NEXT_CMD - self.uart_queue.put(self.sys_state) - self.retransmit_counter = 0 - elif choice == "TIMEOUT": - if self.retransmit_counter < 2: - #Send time out event and let the uart thread call retransmit function - self.sys_state = SystemState.SYS_TIMEOUT - self.uart_queue.put(self.sys_state) - self.retransmit_counter += 1 - #If retransmit twice already, then send the next message and reset the retransmit flag - else: - self.sys_state = SystemState.SYS_SEND_NEXT_CMD - self.uart_queue.put(self.sys_state) - self.retransmit_counter = 0 - self.uart_event.set() - - def mock_receive_msg(self): - mock_msg = ["ACK"] - choice = random.choice(mock_msg) - print(choice) - return choice - - # def handle_wait(self): - - # """ - # Handle the logic of calling other functions depending on rocket responses. - # """ - # #Ideally, should have another function to parse the response received from DMB - # # response = parse_rocket_response(), change the if blocks below to if, elif,else, etc - # response = self.simulate_rocket_response() - - # if self.sys_state == SystemState.SYS_WAIT: - # #If received NAK from Rocket: - # if response == "NAK": - # self.handle_retransmit() - # elif response == "ACK": - # self.handle_send_next_cmd() - # elif response == "TIMEOUT": - # self.sys_state = SystemState.SYS_TIMEOUT - # self.handle_timeout() - # else: - # print("Bro, you messed up\n\r") - - # def handle_retransmit(self): - # #If retransmit counter is within range, retransmit the same message - # if self.retransmit_counter < 2: - # self.retransmit_counter += 1 - # self.start_sending_msg() - # else: - # self.sys_state = SystemState.SYS_SEND_NEXT_CMD - # self.handle_send_next_cmd() - - # def handle_send_next_cmd(self): - # #Send next cmd - # self.sys_state = SystemState.SYS_WAIT - # #reset retransmit counter - # self.retransmit_counter = 0 - # def handle_timeout(self): - - # #Call retransmit - # self.sys_state = SystemState.SYS_RETRANSMIT - # self.handle_retransmit() - - # def simulate_rocket_response(self): - # # Simulate different responses from Rocket - # responses = ["ACK", "NAK", "TIMEOUT"] - # return random.choice(responses) - - # def exit(self): - # """ - # Exit the state machine for RCU and Rocket serial communication by setting RCU state to be uninitialized - # and event to RCU Exit. - - # """ - # self.sys_state = SystemState.SYS_INVALID - # self.rocket_state = RocketState.RS_NONE - # self.event = Event.RCU_EXIT - - # def get_rcu_state(self): - # """ - # Return the current state of the RCU. - - # """ - # print("\nThe current RCU state:") - # return self.sys_state - - # def get_rocket_state(self): - # """" - # Return the current state of the rocket. - - # """ - # print("\nThe current rocket state:") - # return self.rocket_state - - -def state_machine_manager_thread (uart_queue: Queue, radio_queue: Queue, uart_event: mp.Event, radio_event: mp.Event): + while True: + self.serial_event.wait() + while not self.serial_event_queue.empty(): + queue_serial_event = self.serial_event_queue.get() + #Need this so we can start the timer to handle TIMEOUT event + if queue_serial_event == "WAIT": + self.system_state_queue.put(SystemState.SYS_WAIT) + elif queue_serial_event == "ACK": + self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) + elif queue_serial_event == "NAK": + if self.retransmit_counter < 2: + self.system_state_queue.put(SystemState.SYS_RETRANSMIT) + self.retransmit_counter += 1 + else: + self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) + self.retransmit_counter = 0 + elif queue_serial_event == "TIMEOUT": + if self.retransmit_counter < 2: + self.system_state_queue.put(SystemState.SYS_RETRANSMIT) + self.retransmit_counter += 1 + else: + self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) + self.retransmit_counter = 0 + self.state_change_event.set() + self.serial_event.clear() + + + # mock_msg = ["ACK", "NAK", "TIMEOUT", "TIMEOUT", "ACK", "NAK", "WAIT"] + # for choice in mock_msg: + # print("Choice: ", choice) + # if choice == "ACK": + # self.sys_state = SystemState.SYS_SEND_NEXT_CMD + # self.serial_event_queue.put(self.sys_state) + # elif choice == "NAK": + # #If retransmit counter is less than 2, then resend + # if self.retransmit_counter < 2: + # self.sys_state = SystemState.SYS_RETRANSMIT + # self.serial_event_queue.put(self.sys_state) + # self.retransmit_counter += 1 + # #If retransmit twice already, then send the next message and reset the retransmit flag + # else: + # self.sys_state = SystemState.SYS_SEND_NEXT_CMD + # self.serial_event_queue.put(self.sys_state) + # self.retransmit_counter = 0 + # elif choice == "TIMEOUT": + # if self.retransmit_counter < 2: + # #Send time out event and let the uart thread call retransmit function + # self.sys_state = SystemState.SYS_TIMEOUT + # self.serial_event_queue.put(self.sys_state) + # self.retransmit_counter += 1 + # #If retransmit twice already, then send the next message and reset the retransmit flag + # else: + # self.sys_state = SystemState.SYS_SEND_NEXT_CMD + # # self.uart_queue.put(self.sys_state) + # self.serial_event_queue.put(self.sys_state) + # self.retransmit_counter = 0 + # elif choice == "WAIT": + # self.sys_state = SystemState.SYS_WAIT + # self.serial_event_queue.put(self.sys_state) + # self.serial_event.set() + + # def mock_receive_msg(self): + # mock_msg = ["ACK"] + # choice = random.choice(mock_msg) + # print(choice) + # return choice + +# def state_machine_manager_thread (uart_queue: Queue, radio_queue: Queue, uart_event: mp.Event, radio_event: mp.Event): +def state_machine_manager_thread (serial_event_queue: Queue, system_state_queue: Queue, serial_event: mp.Event, system_state_event: mp.Event): """ State Machine manager function to start the state machine thread """ - new_state_machine = StateMachineManager(uart_queue, radio_queue, uart_event, radio_event) + new_state_machine = StateMachineManager(serial_event_queue, system_state_queue, serial_event, system_state_event) new_state_machine.start() diff --git a/backend/src/main.py b/backend/src/main.py index 6f0a23b..08136bd 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -30,57 +30,43 @@ def initialize_threads(): thread_pool = {} uart_workq = mp.Queue() radio_workq = mp.Queue() - # db_workq = mp.Queue() - # loadcell_workq = mp.Queue() + db_workq = mp.Queue() + loadcell_workq = mp.Queue() message_handler_workq = mp.Queue() uart_event_queue = mp.Queue() radio_event_queue = mp.Queue() - # heartbeat_workq = mp.Queue() + radio_state_queue = mp.Queue() + heartbeat_workq = mp.Queue() + uart_event = mp.Event() + radio_event = mp.Event() + radio_state_change_event = mp.Event() # Create a main thread for handling thread messages thread_pool['message_handler'] = {'thread': None, 'workq': message_handler_workq} # Initialize the threads # uart_thread = mp.Process(target=serial_thread, args=('uart', sd.UART, UART_BAUDRATE, uart_workq, message_handler_workq, uart_event_queue)) - # radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq, radio_event_queue)) - # radio_thread = mp.Process(target=radio_thread_test, args=(radio_event_queue, )) - uart_thread = mp.Process(target=uart_thread_test, args=(uart_event_queue,)) - # db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) - # lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) - # hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) - serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(uart_event_queue, radio_event_queue)) + radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq, radio_event_queue, radio_state_queue, radio_event, radio_state_change_event)) + db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) + lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) + hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) + serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(radio_event_queue, radio_state_queue, radio_event, radio_state_change_event)) # Add the threads to the thread pool - thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} - # thread_pool['radio'] = {'thread': radio_thread, 'workq': radio_workq} - # thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} - # thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} - # thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} + # thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} + thread_pool['radio'] = {'thread': radio_thread, 'workq': radio_workq} + thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} + thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} + thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} thread_pool['state_machine'] = {'thread': serial_state_machine_thread, 'workq': None} tm.thread_pool = thread_pool return if __name__ == "__main__": - # tm() - # initialize_threads() - # tm.start_threads() - radio_event_queue = mp.Queue() - uart_event_queue = mp.Queue() - uart_event = mp.Event() - radio_event = mp.Event() - # radio_thread = mp.Process(target=radio_thread_test, args=(radio_event_queue, )) - uart_thread = mp.Process(target=uart_thread_test, args=(uart_event_queue, uart_event)) - serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(uart_event_queue, radio_event_queue, uart_event, radio_event)) + tm() + initialize_threads() + tm.start_threads() - # radio_thread.start() - uart_thread.start() - serial_state_machine_thread.start() - - # time.sleep(5) - - # radio_thread.join() - uart_thread.join() - serial_state_machine_thread.join() - # while 1: - # tm.handle_thread_messages() \ No newline at end of file + while 1: + tm.handle_thread_messages() \ No newline at end of file From 5856163fdbb24536a98eb9d9722561a451e02d87 Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:48:27 -0700 Subject: [PATCH 12/14] Testing the workflow from receiving the msg from the serial workq, then mock sending out the msg to the DMB, then put the system in WAIT state. The Wait state will resend the same msg for now, until TIMEOUT logic is figured out next week. --- backend/src/SerialHandler.py | 101 +++++++++++++++++++++-------- backend/src/StateMachineManager.py | 19 +++++- backend/src/main.py | 45 +++++++++---- 3 files changed, 124 insertions(+), 41 deletions(-) diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index be6bb92..c7c8e9b 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -4,7 +4,7 @@ import enum import multiprocessing as mp import threading -import time +import time, random import serial # You'll need to run `pip install pyserial` from cobs import cobs # pip install cobs import google.protobuf.message as Message @@ -14,6 +14,7 @@ import proto.Python.CoreProto_pb2 as ProtoCore import proto.Python.TelemetryMessage_pb2 as TelemetryProto import proto.Python.ControlMessage_pb2 as ControlProto +from proto.Python.ControlMessage_pb2 import AckNack from src.support.Codec import Codec @@ -167,6 +168,22 @@ def process_telemetry_message(self, data): self.send_message_workq.put(WorkQ_Message(self.thread_name, 'database', THREAD_MESSAGE_DB_WRITE, (ProtoCore.MessageID.MSG_TELEMETRY, json_str))) + def mock_received_message(self): + """ + Mock receiving message from the DMB. + """ + msg = ControlProto.ControlMessage() + mock_msg = [0, 1 ] + choice = random.choice(mock_msg) + if choice == 0: + msg.nack = AckNack() + msg.nack.acking_msg_id = 0 + else: + msg.ack = AckNack() + msg.ack.acking_msg_id = 1 + + return msg + def process_control_message(self, data): """ Process the incoming control message. @@ -175,20 +192,22 @@ def process_control_message(self, data): data (bytes): The data that was received. """ - received_message = ControlProto.ControlMessage() + received_message = self.mock_received_message() + #-------------UNCOMMENT THE TWO LINES BELOW ONCE THE BOARD IS OBTAINED-------------- + # received_message = ControlProto.ControlMessage() # Ensure we received a valid message - try: - received_message.ParseFromString(data) - except Message.DecodeError: - logger.warning(f"Unable to decode control message: {data}") - return - # Ensure the message is intended for us - if received_message.target == ProtoCore.NODE_RCU or received_message.target == ProtoCore.NODE_ANY: - control_message_type = received_message.WhichOneof('message') - logger.debug(f"Received {control_message_type} from {utl.get_node_from_enum(received_message.source)}") - else: - logger.debug(f"Received message intended for {utl.get_node_from_enum(received_message.target)}") - return + # try: + # received_message.ParseFromString(data) + # except Message.DecodeError: + # logger.warning(f"Unable to decode control message: {data}") + # return + # # Ensure the message is intended for us + # if received_message.target == ProtoCore.NODE_RCU or received_message.target == ProtoCore.NODE_ANY: + # control_message_type = received_message.WhichOneof('message') + # logger.debug(f"Received {control_message_type} from {utl.get_node_from_enum(received_message.source)}") + # else: + # logger.debug(f"Received message intended for {utl.get_node_from_enum(received_message.target)}") + # return #Check to see if it's nak or ack - Might be a better way to check for ACK vs NAK if received_message.ack: @@ -197,9 +216,12 @@ def process_control_message(self, data): self.serial_event_queue.put("NAK") self.serial_event.set() - json_str = ProtobufParser.parse_serial_to_json(data, ProtoCore.MessageID.MSG_CONTROL) + logger.info(f"Mock sending to database thread") + #-------------UNCOMMENT THE TWO LINES BELOW ONCE THE BOARD IS OBTAINED-------------- + + # json_str = ProtobufParser.parse_serial_to_json(data, ProtoCore.MessageID.MSG_CONTROL) - self.send_message_workq.put(WorkQ_Message(self.thread_name, 'database', THREAD_MESSAGE_DB_WRITE, (ProtoCore.MessageID.MSG_CONTROL, json_str)))\ + # self.send_message_workq.put(WorkQ_Message(self.thread_name, 'database', THREAD_MESSAGE_DB_WRITE, (ProtoCore.MessageID.MSG_CONTROL, json_str)))\ def send_serial_command_message(self, command: str, target: str, command_param: int, source_sequence_number: int) -> bool: """ @@ -288,13 +310,17 @@ def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) logger.debug(f"Killing {ser_han.thread_name} thread") return False elif messageID == THREAD_MESSAGE_SERIAL_WRITE: - command = message.message[0] - target = message.message[1] - command_param = message.message[2] - source_sequence_number = message.message[3] - ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) + #-------------UNCOMMENT THE TWO LINES BELOW ONCE THE BOARD IS OBTAINED-------------- + # command = message.message[0] + # target = message.message[1] + # command_param = message.message[2] + # source_sequence_number = message.message[3] + print(f"Mock sending serial command message") + # ser_han.send_serial_command_message(command, target, command_param, source_sequence_number) elif messageID == THREAD_MESSAGE_HEARTBEAT_SERIAL: - ser_han.send_serial_control_message(message.message[0]) + print(f"Mock sending serial control message") + #-------------UNCOMMENT THE TWO LINES BELOW ONCE THE BOARD IS OBTAINED-------------- + # ser_han.send_serial_control_message(message.message[0]) ser_han.current_ser_workq_msg = message ser_han.serial_event_queue.put("WAIT") ser_han.serial_event.set() @@ -324,22 +350,43 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread # This log line should be removed once the pi core issue is solved logger.info(f"{device.name} process: {os.getpid()}") serial_workq = thread_workq + + #--------TO BE REMOVED----------- + #Add below to test the workflow from receiving msg from the serial workq -> send msg -> wait + #Need to test if the flags and the corresponding queues are updated appropriately + test_msg = WorkQ_Message('test1', 'test2', THREAD_MESSAGE_SERIAL_WRITE, ("Hello", "There")) + serial_workq.put(test_msg) + #---------END COMMENTS TO BE REMOVED----------- + ser_han = SerialHandler(thread_name, port, baudrate, message_handler_workq, serial_event_queue, state_change_event_queue, serial_event, state_change_event) - if ser_han.serial_port == None: - return + #-------------UNCOMMENT THE TWO LINES BELOW ONCE THE BOARD IS OBTAINED-------------- + # if ser_han.serial_port == None: + # return rx_thread = threading.Thread(target=serial_rx_thread, args=(ser_han,)) rx_thread.start() - while (1): + + #Using the counter to control how many time the workflow will loop through + #Making sure workflow is setting/resetting the flags and update the appropriate queues accordingly. + #Would need to replace while counter for testing < 3: with while True + # counter_for_testing = 0 + # while counter_for_testing < 3: + while True: # then once the queue is empty read the serial port ser_han.state_change_event.wait() while not ser_han.state_change_event_queue.empty(): state_event = ser_han.state_change_event_queue.get() if state_event == ControlProto.SystemState.SYS_SEND_NEXT_CMD: + print("System in SYS_SEND_NEXT_CMD state") if not process_serial_workq_message(serial_workq.get(), ser_han): ser_han.kill_rx = True rx_thread.join(10) return - if state_event == ControlProto.SystemState.SYS_RETRANSMIT: + elif state_event == ControlProto.SystemState.SYS_RETRANSMIT: + print("System in SYS_RETRANSMIT state") + process_serial_workq_message(ser_han.current_ser_workq_msg, ser_han) + elif state_event == ControlProto.SystemState.SYS_WAIT: + print("System in SYS_WAIT state. Sending the same msg (testing the workflow. Skipping timeout for now. )") process_serial_workq_message(ser_han.current_ser_workq_msg, ser_han) - ser_han.state_change_event.clear() + ser_han.state_change_event.clear() + # counter_for_testing += 1 diff --git a/backend/src/StateMachineManager.py b/backend/src/StateMachineManager.py index 8376eaf..337da98 100644 --- a/backend/src/StateMachineManager.py +++ b/backend/src/StateMachineManager.py @@ -56,36 +56,51 @@ def __init__(self, serial_event_queue: Queue, system_state_queue: Queue, serial_ self.serial_event = serial_event self.state_change_event = state_change_event - self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) - self.state_change_event.set() + def start(self): + self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) + self.state_change_event.set() + #Using the counter to control how many time the workflow will loop through + #Making sure workflow is setting/resetting the flags and update the appropriate queues accordingly. + #Would need to replace while counter for testing < 3: with while True + # counter_for_testing = 0 + # while counter_for_testing < 3: while True: self.serial_event.wait() while not self.serial_event_queue.empty(): queue_serial_event = self.serial_event_queue.get() #Need this so we can start the timer to handle TIMEOUT event if queue_serial_event == "WAIT": + print(f"Setting system state to: {SystemState.SYS_WAIT}") self.system_state_queue.put(SystemState.SYS_WAIT) elif queue_serial_event == "ACK": + print(f"Setting system state to: {SystemState.SYS_SEND_NEXT_CMD}") self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) elif queue_serial_event == "NAK": if self.retransmit_counter < 2: + print(f"Setting system state to: {SystemState.SYS_RETRANSMIT}") self.system_state_queue.put(SystemState.SYS_RETRANSMIT) self.retransmit_counter += 1 else: + print(f"Setting system state to: {SystemState.SYS_SEND_NEXT_CMD}") self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) self.retransmit_counter = 0 elif queue_serial_event == "TIMEOUT": if self.retransmit_counter < 2: + print(f"Setting system state to: {SystemState.SYS_RETRANSMIT}") self.system_state_queue.put(SystemState.SYS_RETRANSMIT) self.retransmit_counter += 1 else: + print(f"Setting system state to: {SystemState.SYS_SEND_NEXT_CMD}") self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) self.retransmit_counter = 0 self.state_change_event.set() + print(f"Set self.state_change_event flag to {self.state_change_event.is_set()}") self.serial_event.clear() + print(f"Cleared self.serial_event flag to {self.serial_event.is_set()}") + # counter_for_testing += 1 # mock_msg = ["ACK", "NAK", "TIMEOUT", "TIMEOUT", "ACK", "NAK", "WAIT"] diff --git a/backend/src/main.py b/backend/src/main.py index 08136bd..8a31a24 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -11,7 +11,7 @@ from src.support.CommonLogger import logger from src.DatabaseHandler import database_thread from src.HeartbeatHandler import heartbeat_thread -from src.SerialHandler import SerialDevices as sd, serial_thread, radio_thread_test, uart_thread_test +from src.SerialHandler import SerialDevices as sd, serial_thread from src.ThreadManager import ThreadManager as tm from src.LoadCellHandler import load_cell_thread from src.StateMachineManager import state_machine_manager_thread @@ -47,26 +47,47 @@ def initialize_threads(): # Initialize the threads # uart_thread = mp.Process(target=serial_thread, args=('uart', sd.UART, UART_BAUDRATE, uart_workq, message_handler_workq, uart_event_queue)) radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq, radio_event_queue, radio_state_queue, radio_event, radio_state_change_event)) - db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) - lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) - hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) + # db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) + # lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) + # hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(radio_event_queue, radio_state_queue, radio_event, radio_state_change_event)) # Add the threads to the thread pool # thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} thread_pool['radio'] = {'thread': radio_thread, 'workq': radio_workq} - thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} - thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} - thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} + # thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} + # thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} + # thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} thread_pool['state_machine'] = {'thread': serial_state_machine_thread, 'workq': None} tm.thread_pool = thread_pool return if __name__ == "__main__": - tm() - initialize_threads() - tm.start_threads() + # tm() + # initialize_threads() + # tm.start_threads() + + # while 1: + # tm.handle_thread_messages() + + #Testing the work flow with the state machine integration into serial handler. + #Only testing the radio serial rx thread right now to keep things simple + #If need to do testing/debugging for longer, increment the time.sleep(X) to the desired number + #Without it, the threads will terminate immediately, thus, unable to do any debugging + radio_workq = mp.Queue() + message_handler_workq = mp.Queue() + radio_event_queue = mp.Queue() + radio_state_queue = mp.Queue() + radio_event = mp.Event() + radio_state_change_event = mp.Event() + radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq, radio_event_queue, radio_state_queue, radio_event, radio_state_change_event)) + serial_state_machine_thread = mp.Process(target=state_machine_manager_thread, args=(radio_event_queue, radio_state_queue, radio_event, radio_state_change_event)) + radio_thread.start() + serial_state_machine_thread.start() + + time.sleep(60) + + radio_thread.terminate() + serial_state_machine_thread.terminate() - while 1: - tm.handle_thread_messages() \ No newline at end of file From 6c88bb7e4360ae5733b8bc45df794d10ba7877ba Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Wed, 8 Jan 2025 19:11:27 -0700 Subject: [PATCH 13/14] Added time.sleep in the receiving thread so the state machine has time to switch state and notify serial outgoing thread. Added self.serial_event.wait() after checking if the queue is not empty so we can notify the outgoing thread. The workflow goes as follows: serial_rx_thread receives a message (ACK/NAK) -> set a flag and notify the state machine -> state machine change state and notify the outgoing thread -> outgoing thread process the state change --- backend/src/SerialHandler.py | 117 ++++++++++++++++++----------- backend/src/StateMachineManager.py | 31 ++++---- backend/src/main.py | 2 +- 3 files changed, 91 insertions(+), 59 deletions(-) diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index c7c8e9b..e62d149 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -28,6 +28,7 @@ # Constants ======================================================================================== MIN_SERIAL_MESSAGE_LENGTH = 6 +TIME_OUT_PERIOD = 5 UART_SERIAL_PORT = "/dev/ttyAMA0" RADIO_SERIAL_PORT = "/dev/ttyUSB0" @@ -67,6 +68,9 @@ def __init__(self, thread_name: str, port: str, baudrate: int, message_handler_w self.serial_event = serial_event self.state_change_event = state_change_event self.current_ser_workq_msg = None + self.start_time = 0 + self.end_time = 0 + self.timer_start = False # Open serial serial port try: self.serial_port = serial.Serial(port=port, baudrate=baudrate, bytesize=8, parity=serial.PARITY_NONE, timeout=None, stopbits=serial.STOPBITS_ONE) @@ -94,30 +98,36 @@ def handle_serial_message(self): the data that was received. """ # Read the serial port - message = self._get_serial_message() - - if message == None: - return + # message = self._get_serial_message() + message = self.mock_received_message() - # Check message length - if len(message) < MIN_SERIAL_MESSAGE_LENGTH: - logger.warning(f"Message from {self.port} too short: {message}") + if message == None: + #Simulate not receiving any messages + print("No message during this time") return - # Decode, remove 0x00 byte - try: - msgId, data = Codec.Decode(message[:-1], len(message) - 1) - except cobs.DecodeError: - logger.warning(f"Invalid cobs message from {self.port}") - return + #Comment out to debug workflow with the state machine + # # Check message length + # if len(message) < MIN_SERIAL_MESSAGE_LENGTH: + # logger.warning(f"Message from {self.port} too short: {message}") + # return - # Process message according to ID - if msgId == ProtoCore.MessageID.MSG_TELEMETRY: - self.process_telemetry_message(data) - elif msgId == ProtoCore.MessageID.MSG_CONTROL: - self.process_control_message(data) - else: - logger.warning(f"Received invalid MessageID from {self.port}") + # # Decode, remove 0x00 byte + # try: + # msgId, data = Codec.Decode(message[:-1], len(message) - 1) + # except cobs.DecodeError: + # logger.warning(f"Invalid cobs message from {self.port}") + # return + + # # Process message according to ID + # if msgId == ProtoCore.MessageID.MSG_TELEMETRY: + # self.process_telemetry_message(data) + # elif msgId == ProtoCore.MessageID.MSG_CONTROL: + # self.process_control_message(data) + # else: + # logger.warning(f"Received invalid MessageID from {self.port}") + self.process_control_message(message) + def process_telemetry_message(self, data): """ @@ -172,16 +182,20 @@ def mock_received_message(self): """ Mock receiving message from the DMB. """ - msg = ControlProto.ControlMessage() - mock_msg = [0, 1 ] + msg = None + mock_msg = [ProtoCore.MessageID.MSG_CONTROL] choice = random.choice(mock_msg) - if choice == 0: - msg.nack = AckNack() - msg.nack.acking_msg_id = 0 - else: - msg.ack = AckNack() - msg.ack.acking_msg_id = 1 - + if choice == ProtoCore.MessageID.MSG_INVALID: + print("No message") + msg = None + elif choice == ProtoCore.MessageID.MSG_CONTROL: + msg = ControlProto.ControlMessage() + mock_control_msg = [0,1] + control_msg_choice = random.choice(mock_control_msg) + if control_msg_choice == 0: + msg.nack.acking_msg_id = 0 + elif control_msg_choice == 1: + msg.ack.acking_msg_id = 1 return msg def process_control_message(self, data): @@ -192,7 +206,6 @@ def process_control_message(self, data): data (bytes): The data that was received. """ - received_message = self.mock_received_message() #-------------UNCOMMENT THE TWO LINES BELOW ONCE THE BOARD IS OBTAINED-------------- # received_message = ControlProto.ControlMessage() # Ensure we received a valid message @@ -210,9 +223,9 @@ def process_control_message(self, data): # return #Check to see if it's nak or ack - Might be a better way to check for ACK vs NAK - if received_message.ack: + if data.ack.acking_msg_id == 1: self.serial_event_queue.put("ACK") - elif received_message.nack: + elif data.nack.acking_msg_id == 0: self.serial_event_queue.put("NAK") self.serial_event.set() @@ -291,9 +304,23 @@ def serial_rx_thread(ser_han: SerialHandler): """ Thread function for the incoming serial data listening. """ - while not (ser_han.kill_rx): + counter_for_testing = 0 + # while not ser_han.kill_rx: + while counter_for_testing < 3: ser_han.handle_serial_message() - pass + #Making sure the system is in the waiting state, before checking for timeout + if ser_han.timer_start: + ser_han.end_time = time.time() + time_elapsed = int(ser_han.end_time - ser_han.start_time) + print(f"Time elapsed: {time_elapsed}") + if time_elapsed > TIME_OUT_PERIOD: + print("System Timeout") + ser_han.serial_event_queue.put("TIMEOUT") + ser_han.serial_event.set() + ser_han.timer_start = False + counter_for_testing += 1 + time.sleep(10) + # pass def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) -> bool: """ @@ -324,6 +351,9 @@ def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) ser_han.current_ser_workq_msg = message ser_han.serial_event_queue.put("WAIT") ser_han.serial_event.set() + #Start the timer + ser_han.start_time = time.time() + ser_han.timer_start = True return True def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread_workq: mp.Queue, message_handler_workq: mp.Queue, serial_event_queue: mp.Queue, state_change_event_queue: mp.Queue, serial_event: mp.Event, state_change_event: mp.Event): @@ -368,12 +398,13 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread #Using the counter to control how many time the workflow will loop through #Making sure workflow is setting/resetting the flags and update the appropriate queues accordingly. - #Would need to replace while counter for testing < 3: with while True - # counter_for_testing = 0 - # while counter_for_testing < 3: - while True: + #Would need to replace "while counter_for_testing < 3" with "while True" + counter_for_testing = 0 + while counter_for_testing < 3: + # while True: # then once the queue is empty read the serial port ser_han.state_change_event.wait() + ser_han.state_change_event.clear() while not ser_han.state_change_event_queue.empty(): state_event = ser_han.state_change_event_queue.get() if state_event == ControlProto.SystemState.SYS_SEND_NEXT_CMD: @@ -384,9 +415,7 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread return elif state_event == ControlProto.SystemState.SYS_RETRANSMIT: print("System in SYS_RETRANSMIT state") - process_serial_workq_message(ser_han.current_ser_workq_msg, ser_han) - elif state_event == ControlProto.SystemState.SYS_WAIT: - print("System in SYS_WAIT state. Sending the same msg (testing the workflow. Skipping timeout for now. )") - process_serial_workq_message(ser_han.current_ser_workq_msg, ser_han) - ser_han.state_change_event.clear() - # counter_for_testing += 1 + if ser_han.current_ser_workq_msg: + process_serial_workq_message(ser_han.current_ser_workq_msg, ser_han) + counter_for_testing += 1 + print(f"counter_for_testing: {counter_for_testing}") diff --git a/backend/src/StateMachineManager.py b/backend/src/StateMachineManager.py index 337da98..ff09c3a 100644 --- a/backend/src/StateMachineManager.py +++ b/backend/src/StateMachineManager.py @@ -60,47 +60,50 @@ def __init__(self, serial_event_queue: Queue, system_state_queue: Queue, serial_ def start(self): - self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) - self.state_change_event.set() + # self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) + # self.state_change_event.set() #Using the counter to control how many time the workflow will loop through #Making sure workflow is setting/resetting the flags and update the appropriate queues accordingly. #Would need to replace while counter for testing < 3: with while True - # counter_for_testing = 0 - # while counter_for_testing < 3: - while True: + counter_for_testing = 0 + while counter_for_testing < 3: + # while True: self.serial_event.wait() + self.serial_event.clear() + print(f"Cleared self.serial_event flag to {self.serial_event.is_set()}") while not self.serial_event_queue.empty(): + self.serial_event.wait() + self.serial_event.clear() + print(f"Cleared self.serial_event flag to {self.serial_event.is_set()}") queue_serial_event = self.serial_event_queue.get() #Need this so we can start the timer to handle TIMEOUT event if queue_serial_event == "WAIT": - print(f"Setting system state to: {SystemState.SYS_WAIT}") + print("SM setting state to: SYS_WAIT") self.system_state_queue.put(SystemState.SYS_WAIT) elif queue_serial_event == "ACK": - print(f"Setting system state to: {SystemState.SYS_SEND_NEXT_CMD}") + print("SM setting state to: SYS_SEND_NEXT_CMD") self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) elif queue_serial_event == "NAK": if self.retransmit_counter < 2: - print(f"Setting system state to: {SystemState.SYS_RETRANSMIT}") + print("SM setting state to: SYS_RETRANSMIT") self.system_state_queue.put(SystemState.SYS_RETRANSMIT) self.retransmit_counter += 1 else: - print(f"Setting system state to: {SystemState.SYS_SEND_NEXT_CMD}") + print("SM setting state to: SYS_SEND_NEXT_CMD") self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) self.retransmit_counter = 0 elif queue_serial_event == "TIMEOUT": if self.retransmit_counter < 2: - print(f"Setting system state to: {SystemState.SYS_RETRANSMIT}") + print("SM setting state to: SYS_RETRANSMIT after TIMEOUT") self.system_state_queue.put(SystemState.SYS_RETRANSMIT) self.retransmit_counter += 1 else: - print(f"Setting system state to: {SystemState.SYS_SEND_NEXT_CMD}") + print("SM setting state to: SYS_SEND_NEXT_CMD") self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) self.retransmit_counter = 0 self.state_change_event.set() print(f"Set self.state_change_event flag to {self.state_change_event.is_set()}") - self.serial_event.clear() - print(f"Cleared self.serial_event flag to {self.serial_event.is_set()}") - # counter_for_testing += 1 + counter_for_testing += 1 # mock_msg = ["ACK", "NAK", "TIMEOUT", "TIMEOUT", "ACK", "NAK", "WAIT"] diff --git a/backend/src/main.py b/backend/src/main.py index 8a31a24..e3c8eae 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -86,7 +86,7 @@ def initialize_threads(): radio_thread.start() serial_state_machine_thread.start() - time.sleep(60) + time.sleep(120) radio_thread.terminate() serial_state_machine_thread.terminate() From 17991300f6efa9fc84fd5b50da0f7da93d80198d Mon Sep 17 00:00:00 2001 From: Cocorico101 <83224644+Cocorico101@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:46:19 -0700 Subject: [PATCH 14/14] Update the flags on both the state machine and the serial handler side. It should go serial handler incoming thread -> state machine -> serial handler outgoing thread -> state machine. The order of operation is shown here when running it on debug view. However, there is a race condition when the serial handler incoming thread and the serial handler outgoing thread are updating the serial event queue at the same time, causing the state machine to only see the event in the queue before the serial handler outgoing thread gets a chance to update the queue --- backend/src/SerialHandler.py | 53 ++++++------- backend/src/StateMachineManager.py | 118 ++++++++--------------------- backend/src/main(original).py | 62 +++++++++++++++ backend/src/main.py | 5 +- 4 files changed, 122 insertions(+), 116 deletions(-) create mode 100644 backend/src/main(original).py diff --git a/backend/src/SerialHandler.py b/backend/src/SerialHandler.py index e62d149..0ab8fd1 100644 --- a/backend/src/SerialHandler.py +++ b/backend/src/SerialHandler.py @@ -190,7 +190,7 @@ def mock_received_message(self): msg = None elif choice == ProtoCore.MessageID.MSG_CONTROL: msg = ControlProto.ControlMessage() - mock_control_msg = [0,1] + mock_control_msg = [1] control_msg_choice = random.choice(mock_control_msg) if control_msg_choice == 0: msg.nack.acking_msg_id = 0 @@ -221,12 +221,14 @@ def process_control_message(self, data): # else: # logger.debug(f"Received message intended for {utl.get_node_from_enum(received_message.target)}") # return - + response = None #Check to see if it's nak or ack - Might be a better way to check for ACK vs NAK if data.ack.acking_msg_id == 1: - self.serial_event_queue.put("ACK") + response = "ACK" elif data.nack.acking_msg_id == 0: - self.serial_event_queue.put("NAK") + response = "NAK" + logger.info(f"Incoming thread received: {response}") + self.serial_event_queue.put(response) self.serial_event.set() logger.info(f"Mock sending to database thread") @@ -306,20 +308,10 @@ def serial_rx_thread(ser_han: SerialHandler): """ counter_for_testing = 0 # while not ser_han.kill_rx: - while counter_for_testing < 3: + while counter_for_testing < 10: ser_han.handle_serial_message() - #Making sure the system is in the waiting state, before checking for timeout - if ser_han.timer_start: - ser_han.end_time = time.time() - time_elapsed = int(ser_han.end_time - ser_han.start_time) - print(f"Time elapsed: {time_elapsed}") - if time_elapsed > TIME_OUT_PERIOD: - print("System Timeout") - ser_han.serial_event_queue.put("TIMEOUT") - ser_han.serial_event.set() - ser_han.timer_start = False counter_for_testing += 1 - time.sleep(10) + time.sleep(4) # pass def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) -> bool: @@ -351,9 +343,6 @@ def process_serial_workq_message(message: WorkQ_Message, ser_han: SerialHandler) ser_han.current_ser_workq_msg = message ser_han.serial_event_queue.put("WAIT") ser_han.serial_event.set() - #Start the timer - ser_han.start_time = time.time() - ser_han.timer_start = True return True def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread_workq: mp.Queue, message_handler_workq: mp.Queue, serial_event_queue: mp.Queue, state_change_event_queue: mp.Queue, serial_event: mp.Event, state_change_event: mp.Event): @@ -382,10 +371,11 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread serial_workq = thread_workq #--------TO BE REMOVED----------- - #Add below to test the workflow from receiving msg from the serial workq -> send msg -> wait - #Need to test if the flags and the corresponding queues are updated appropriately - test_msg = WorkQ_Message('test1', 'test2', THREAD_MESSAGE_SERIAL_WRITE, ("Hello", "There")) - serial_workq.put(test_msg) + #Mock putting messages in the workq + for i in range(10): + test_msg = WorkQ_Message('test1', 'test2', THREAD_MESSAGE_SERIAL_WRITE, (f"Outgoing command: {i}")) + serial_workq.put(test_msg) + logger.info(f"serial_workq size: {serial_workq.qsize()}") #---------END COMMENTS TO BE REMOVED----------- ser_han = SerialHandler(thread_name, port, baudrate, message_handler_workq, serial_event_queue, state_change_event_queue, serial_event, state_change_event) @@ -396,17 +386,16 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread rx_thread = threading.Thread(target=serial_rx_thread, args=(ser_han,)) rx_thread.start() - #Using the counter to control how many time the workflow will loop through - #Making sure workflow is setting/resetting the flags and update the appropriate queues accordingly. - #Would need to replace "while counter_for_testing < 3" with "while True" counter_for_testing = 0 - while counter_for_testing < 3: + while counter_for_testing < 10: # while True: # then once the queue is empty read the serial port ser_han.state_change_event.wait() + # if ser_han.state_change_event.is_set(): ser_han.state_change_event.clear() - while not ser_han.state_change_event_queue.empty(): + if not ser_han.state_change_event_queue.empty(): state_event = ser_han.state_change_event_queue.get() + print(f"Outgoing thread sending: {state_event}") if state_event == ControlProto.SystemState.SYS_SEND_NEXT_CMD: print("System in SYS_SEND_NEXT_CMD state") if not process_serial_workq_message(serial_workq.get(), ser_han): @@ -417,5 +406,9 @@ def serial_thread(thread_name: str, device: SerialDevices, baudrate: int, thread print("System in SYS_RETRANSMIT state") if ser_han.current_ser_workq_msg: process_serial_workq_message(ser_han.current_ser_workq_msg, ser_han) - counter_for_testing += 1 - print(f"counter_for_testing: {counter_for_testing}") + elif state_event == ControlProto.SystemState.SYS_WAIT: + print("System is in SYS_WAIT state") + counter_for_testing += 1 + print(f"counter_for_testing: {counter_for_testing}") + time.sleep(0.5) + diff --git a/backend/src/StateMachineManager.py b/backend/src/StateMachineManager.py index ff09c3a..942ccdc 100644 --- a/backend/src/StateMachineManager.py +++ b/backend/src/StateMachineManager.py @@ -56,95 +56,43 @@ def __init__(self, serial_event_queue: Queue, system_state_queue: Queue, serial_ self.serial_event = serial_event self.state_change_event = state_change_event - - def start(self): - - # self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) - # self.state_change_event.set() - #Using the counter to control how many time the workflow will loop through - #Making sure workflow is setting/resetting the flags and update the appropriate queues accordingly. - #Would need to replace while counter for testing < 3: with while True - counter_for_testing = 0 - while counter_for_testing < 3: - # while True: + while True: self.serial_event.wait() self.serial_event.clear() - print(f"Cleared self.serial_event flag to {self.serial_event.is_set()}") - while not self.serial_event_queue.empty(): - self.serial_event.wait() - self.serial_event.clear() - print(f"Cleared self.serial_event flag to {self.serial_event.is_set()}") + if not self.serial_event_queue.empty(): queue_serial_event = self.serial_event_queue.get() - #Need this so we can start the timer to handle TIMEOUT event - if queue_serial_event == "WAIT": - print("SM setting state to: SYS_WAIT") - self.system_state_queue.put(SystemState.SYS_WAIT) - elif queue_serial_event == "ACK": - print("SM setting state to: SYS_SEND_NEXT_CMD") - self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) - elif queue_serial_event == "NAK": - if self.retransmit_counter < 2: - print("SM setting state to: SYS_RETRANSMIT") - self.system_state_queue.put(SystemState.SYS_RETRANSMIT) - self.retransmit_counter += 1 - else: - print("SM setting state to: SYS_SEND_NEXT_CMD") - self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) - self.retransmit_counter = 0 - elif queue_serial_event == "TIMEOUT": - if self.retransmit_counter < 2: - print("SM setting state to: SYS_RETRANSMIT after TIMEOUT") - self.system_state_queue.put(SystemState.SYS_RETRANSMIT) - self.retransmit_counter += 1 - else: - print("SM setting state to: SYS_SEND_NEXT_CMD") - self.system_state_queue.put(SystemState.SYS_SEND_NEXT_CMD) - self.retransmit_counter = 0 - self.state_change_event.set() - print(f"Set self.state_change_event flag to {self.state_change_event.is_set()}") - counter_for_testing += 1 - - - # mock_msg = ["ACK", "NAK", "TIMEOUT", "TIMEOUT", "ACK", "NAK", "WAIT"] - # for choice in mock_msg: - # print("Choice: ", choice) - # if choice == "ACK": - # self.sys_state = SystemState.SYS_SEND_NEXT_CMD - # self.serial_event_queue.put(self.sys_state) - # elif choice == "NAK": - # #If retransmit counter is less than 2, then resend - # if self.retransmit_counter < 2: - # self.sys_state = SystemState.SYS_RETRANSMIT - # self.serial_event_queue.put(self.sys_state) - # self.retransmit_counter += 1 - # #If retransmit twice already, then send the next message and reset the retransmit flag - # else: - # self.sys_state = SystemState.SYS_SEND_NEXT_CMD - # self.serial_event_queue.put(self.sys_state) - # self.retransmit_counter = 0 - # elif choice == "TIMEOUT": - # if self.retransmit_counter < 2: - # #Send time out event and let the uart thread call retransmit function - # self.sys_state = SystemState.SYS_TIMEOUT - # self.serial_event_queue.put(self.sys_state) - # self.retransmit_counter += 1 - # #If retransmit twice already, then send the next message and reset the retransmit flag - # else: - # self.sys_state = SystemState.SYS_SEND_NEXT_CMD - # # self.uart_queue.put(self.sys_state) - # self.serial_event_queue.put(self.sys_state) - # self.retransmit_counter = 0 - # elif choice == "WAIT": - # self.sys_state = SystemState.SYS_WAIT - # self.serial_event_queue.put(self.sys_state) - # self.serial_event.set() - - # def mock_receive_msg(self): - # mock_msg = ["ACK"] - # choice = random.choice(mock_msg) - # print(choice) - # return choice + print(f"State machine process: {queue_serial_event}") + #Need this so we can start the timer to handle TIMEOUT event + if queue_serial_event == "WAIT": + print("State machine sets flag: SYS_WAIT") + response = SystemState.SYS_WAIT + elif queue_serial_event == "ACK": + print("State machine sets flag: SYS_SEND_NEXT_CMD") + response = SystemState.SYS_SEND_NEXT_CMD + elif queue_serial_event == "NAK": + if self.retransmit_counter < 2: + print("State machine sets flag: SYS_RETRANSMIT") + response = SystemState.SYS_RETRANSMIT + self.retransmit_counter += 1 + else: + print("State machine sets flag: SYS_SEND_NEXT_CMD") + response = SystemState.SYS_SEND_NEXT_CMD + self.retransmit_counter = 0 + elif queue_serial_event == "TIMEOUT": + if self.retransmit_counter < 2: + print("State machine sets flag: SYS_RETRANSMIT after TIMEOUT") + response = SystemState.SYS_RETRANSMIT + self.retransmit_counter += 1 + else: + print("State machine sets flag: SYS_SEND_NEXT_CMD") + response = SystemState.SYS_SEND_NEXT_CMD + self.retransmit_counter = 0 + #Put the new state in a shared queue + self.system_state_queue.put(response) + #Notify the outgoing serial thread + self.state_change_event.set() + # def state_machine_manager_thread (uart_queue: Queue, radio_queue: Queue, uart_event: mp.Event, radio_event: mp.Event): def state_machine_manager_thread (serial_event_queue: Queue, system_state_queue: Queue, serial_event: mp.Event, system_state_event: mp.Event): diff --git a/backend/src/main(original).py b/backend/src/main(original).py new file mode 100644 index 0000000..f50062b --- /dev/null +++ b/backend/src/main(original).py @@ -0,0 +1,62 @@ + +# General imports ================================================================================= +import os, sys +import multiprocessing as mp + +# Project specific imports ======================================================================== +dirname, _ = os.path.split(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(dirname.split("backend", 1)[0], 'backend', 'proto/Python')) +sys.path.insert(0, os.path.join(dirname.split("backend", 1)[0], 'backend')) + +from src.support.CommonLogger import logger +from src.DatabaseHandler import database_thread +from src.HeartbeatHandler import heartbeat_thread +from src.SerialHandler import SerialDevices as sd, serial_thread +from src.ThreadManager import ThreadManager as tm +from src.LoadCellHandler import load_cell_thread + +# Constants ======================================================================================== +UART_BAUDRATE = 115200 +RADIO_BAUDRATE = 57600 + +# Local Procedures ================================================================================ +def initialize_threads(): + ''' + Create threads for the backend + ''' + logger.info('Initializing threads') + thread_pool = {} + uart_workq = mp.Queue() + radio_workq = mp.Queue() + db_workq = mp.Queue() + loadcell_workq = mp.Queue() + message_handler_workq = mp.Queue() + heartbeat_workq = mp.Queue() + + # Create a main thread for handling thread messages + thread_pool['message_handler'] = {'thread': None, 'workq': message_handler_workq} + + # Initialize the threads + uart_thread = mp.Process(target=serial_thread, args=('uart', sd.UART, UART_BAUDRATE, uart_workq, message_handler_workq)) + radio_thread = mp.Process(target=serial_thread, args=('radio', sd.RADIO, RADIO_BAUDRATE, radio_workq, message_handler_workq)) + db_thread = mp.Process(target=database_thread, args=('database', db_workq, message_handler_workq)) + lc_thread = mp.Process(target=load_cell_thread, args=('loadcell', loadcell_workq, message_handler_workq)) + hb_thread = mp.Process(target=heartbeat_thread, args=('heartbeat', heartbeat_workq, message_handler_workq)) + + # Add the threads to the thread pool + thread_pool['uart'] = {'thread': uart_thread, 'workq': uart_workq} + thread_pool['radio'] = {'thread': radio_thread, 'workq': radio_workq} + thread_pool['database'] = {'thread': db_thread, 'workq': db_workq} + thread_pool['loadcell'] = {'thread': lc_thread, 'workq': loadcell_workq} + thread_pool['heartbeat'] = {'thread': hb_thread, 'workq': heartbeat_workq} + + tm.thread_pool = thread_pool + return + +if __name__ == "__main__": + tm() + initialize_threads() + tm.start_threads() + + while 1: + tm.handle_thread_messages() \ No newline at end of file diff --git a/backend/src/main.py b/backend/src/main.py index e3c8eae..92684af 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -86,8 +86,11 @@ def initialize_threads(): radio_thread.start() serial_state_machine_thread.start() - time.sleep(120) + time.sleep(400) radio_thread.terminate() serial_state_machine_thread.terminate() + radio_thread.join() + serial_state_machine_thread.join() +