From b98315e2b65d7479cbc9062cd38a11f85aff2968 Mon Sep 17 00:00:00 2001 From: "M. Wasif Kamran" <115906305+muwasifk@users.noreply.github.com> Date: Tue, 31 Dec 2024 14:04:36 -0500 Subject: [PATCH] X86 interrupts (#14) * started working on nvic * finish interrupt_nvic_enable * exti enable and small fixes * finish exti functions * Finish NVIC and fix EXTI * Remove .idea * Comment NVIC related * small fix * lint and format * Simulation infra and can stuff * Formatting --------- Co-authored-by: d3kanesa --- SConstruct | 6 +- ....c.jinja => test_{{project_name}}.c.jinja} | 0 .../{can_codegen.h => can_codegen.h.jinja} | 4 + .../src/{{project_name}}_rx_all.c.jinja | 95 ++++- .../src/{{project_name}}_tx_all.c.jinja | 45 ++- .../templates/system_can/system_can.h.jinja | 2 +- build_system.md | 7 + can/boards/bms_carrier.yaml | 42 +-- can/boards/can_communication.yaml | 44 +++ can/boards/centre_console.yaml | 4 +- can/inc/can.h | 66 ++-- can/inc/can_hw.h | 6 +- can/inc/can_msg.h | 3 + can/inc/can_watchdog.h | 42 +++ can/src/can.c | 132 ++++++- can/tools/system_dbc.dbc | 36 +- libraries/ms-common/inc/flash.h | 6 +- libraries/ms-common/inc/i2c.h | 4 +- libraries/ms-common/inc/interrupts.h | 16 +- libraries/ms-common/inc/notify.h | 4 +- libraries/ms-common/inc/spi.h | 4 +- libraries/ms-common/inc/tasks.h | 6 +- libraries/ms-common/src/x86/interrupts.c | 84 ++++- projects/bms_carrier/README.md | 15 + projects/bms_carrier/config.json | 8 + projects/bms_carrier/inc/bms_carrier.h | 24 ++ projects/bms_carrier/src/main.c | 50 +++ projects/bms_carrier/test/test_bms_carrier.c | 26 ++ projects/can_communication/src/main.c | 3 +- scons/build.scons | 15 +- scons/vehicle_sim.scons | 57 +++ simulation/.placeholder | 0 simulation/README.md | 5 + simulation/Simulation_JSON/README.md | 1 + simulation/client/app/inc/app.h | 14 + simulation/client/app/inc/app_callback.h | 10 + simulation/client/app/inc/gpio_manager.h | 31 ++ simulation/client/app/inc/i2c_manager.h | 11 + simulation/client/app/inc/spi_manager.h | 11 + simulation/client/app/src/app_callback.cpp | 50 +++ simulation/client/app/src/gpio_manager.cpp | 170 +++++++++ simulation/client/app/src/i2c_manager.cpp | 2 + simulation/client/app/src/main.cpp | 36 ++ simulation/client/app/src/spi_manager.cpp | 2 + simulation/client/utils/inc/ntp_client.h | 34 ++ simulation/client/utils/inc/tcp_client.h | 56 +++ simulation/client/utils/src/ntp_client.cpp | 92 +++++ simulation/client/utils/src/tcp_client.cpp | 180 +++++++++ simulation/command.md | 17 + simulation/common/inc/command_code.h | 34 ++ simulation/common/inc/gpio_datagram.h | 100 +++++ simulation/common/inc/i2c_datagram.h | 41 ++ simulation/common/inc/json_manager.h | 105 ++++++ simulation/common/inc/metadata.h | 34 ++ simulation/common/inc/network_time_protocol.h | 55 +++ simulation/common/inc/serialization.h | 40 ++ simulation/common/inc/spi_datagram.h | 41 ++ simulation/common/inc/thread_helpers.h | 7 + simulation/common/src/command_code.cpp | 27 ++ simulation/common/src/gpio_datagram.cpp | 79 ++++ simulation/common/src/i2c_datagram.cpp | 61 +++ simulation/common/src/json_manager.cpp | 104 ++++++ simulation/common/src/metadata.cpp | 57 +++ .../common/src/network_time_protocol.cpp | 86 +++++ simulation/common/src/serialization.cpp | 15 + simulation/common/src/spi_datagram.cpp | 61 +++ simulation/common/src/thread_helpers.cpp | 17 + simulation/scripts/main.py | 22 ++ simulation/server/app/inc/app.h | 16 + simulation/server/app/inc/app_callback.h | 11 + simulation/server/app/inc/app_terminal.h | 22 ++ simulation/server/app/inc/can_manager.h | 45 +++ simulation/server/app/inc/gpio_manager.h | 40 ++ simulation/server/app/inc/i2c_manager.h | 10 + simulation/server/app/inc/spi_manager.h | 10 + simulation/server/app/src/app_callback.cpp | 60 +++ simulation/server/app/src/app_terminal.cpp | 126 +++++++ simulation/server/app/src/can_manager.cpp | 66 ++++ simulation/server/app/src/gpio_manager.cpp | 350 ++++++++++++++++++ simulation/server/app/src/i2c_manager.cpp | 2 + simulation/server/app/src/main.cpp | 32 ++ simulation/server/app/src/spi_manager.cpp | 2 + .../server/utils/inc/client_connection.h | 46 +++ simulation/server/utils/inc/ntp_server.h | 37 ++ simulation/server/utils/inc/tcp_server.h | 48 +++ .../server/utils/src/client_connection.cpp | 122 ++++++ simulation/server/utils/src/ntp_server.cpp | 188 ++++++++++ simulation/server/utils/src/tcp_server.cpp | 141 +++++++ 88 files changed, 3816 insertions(+), 122 deletions(-) rename autogen/templates/new_project/test/{test.c.jinja => test_{{project_name}}.c.jinja} (100%) rename autogen/templates/project_can/inc/{can_codegen.h => can_codegen.h.jinja} (87%) create mode 100644 can/boards/can_communication.yaml create mode 100644 projects/bms_carrier/README.md create mode 100644 projects/bms_carrier/config.json create mode 100644 projects/bms_carrier/inc/bms_carrier.h create mode 100644 projects/bms_carrier/src/main.c create mode 100644 projects/bms_carrier/test/test_bms_carrier.c create mode 100644 scons/vehicle_sim.scons delete mode 100644 simulation/.placeholder create mode 100644 simulation/README.md create mode 100644 simulation/Simulation_JSON/README.md create mode 100644 simulation/client/app/inc/app.h create mode 100644 simulation/client/app/inc/app_callback.h create mode 100644 simulation/client/app/inc/gpio_manager.h create mode 100644 simulation/client/app/inc/i2c_manager.h create mode 100644 simulation/client/app/inc/spi_manager.h create mode 100644 simulation/client/app/src/app_callback.cpp create mode 100644 simulation/client/app/src/gpio_manager.cpp create mode 100644 simulation/client/app/src/i2c_manager.cpp create mode 100644 simulation/client/app/src/main.cpp create mode 100644 simulation/client/app/src/spi_manager.cpp create mode 100644 simulation/client/utils/inc/ntp_client.h create mode 100644 simulation/client/utils/inc/tcp_client.h create mode 100644 simulation/client/utils/src/ntp_client.cpp create mode 100644 simulation/client/utils/src/tcp_client.cpp create mode 100644 simulation/command.md create mode 100644 simulation/common/inc/command_code.h create mode 100644 simulation/common/inc/gpio_datagram.h create mode 100644 simulation/common/inc/i2c_datagram.h create mode 100644 simulation/common/inc/json_manager.h create mode 100644 simulation/common/inc/metadata.h create mode 100644 simulation/common/inc/network_time_protocol.h create mode 100644 simulation/common/inc/serialization.h create mode 100644 simulation/common/inc/spi_datagram.h create mode 100644 simulation/common/inc/thread_helpers.h create mode 100644 simulation/common/src/command_code.cpp create mode 100644 simulation/common/src/gpio_datagram.cpp create mode 100644 simulation/common/src/i2c_datagram.cpp create mode 100644 simulation/common/src/json_manager.cpp create mode 100644 simulation/common/src/metadata.cpp create mode 100644 simulation/common/src/network_time_protocol.cpp create mode 100644 simulation/common/src/serialization.cpp create mode 100644 simulation/common/src/spi_datagram.cpp create mode 100644 simulation/common/src/thread_helpers.cpp create mode 100644 simulation/scripts/main.py create mode 100644 simulation/server/app/inc/app.h create mode 100644 simulation/server/app/inc/app_callback.h create mode 100644 simulation/server/app/inc/app_terminal.h create mode 100644 simulation/server/app/inc/can_manager.h create mode 100644 simulation/server/app/inc/gpio_manager.h create mode 100644 simulation/server/app/inc/i2c_manager.h create mode 100644 simulation/server/app/inc/spi_manager.h create mode 100644 simulation/server/app/src/app_callback.cpp create mode 100644 simulation/server/app/src/app_terminal.cpp create mode 100644 simulation/server/app/src/can_manager.cpp create mode 100644 simulation/server/app/src/gpio_manager.cpp create mode 100644 simulation/server/app/src/i2c_manager.cpp create mode 100644 simulation/server/app/src/main.cpp create mode 100644 simulation/server/app/src/spi_manager.cpp create mode 100644 simulation/server/utils/inc/client_connection.h create mode 100644 simulation/server/utils/inc/ntp_server.h create mode 100644 simulation/server/utils/inc/tcp_server.h create mode 100644 simulation/server/utils/src/client_connection.cpp create mode 100644 simulation/server/utils/src/ntp_server.cpp create mode 100644 simulation/server/utils/src/tcp_server.cpp diff --git a/SConstruct b/SConstruct index 044abec..e0283e5 100644 --- a/SConstruct +++ b/SConstruct @@ -163,17 +163,13 @@ elif COMMAND == "new": SConscript('scons/new_target.scons', exports='VARS') ########################################################### -# hil command +# HIL command ########################################################### elif COMMAND == "hil": - print(TEST_FILE) if not TEST_FILE: - #Error handling pass - SConscript('scons/pytest.scons', exports='VARS') - ########################################################### # Clean ########################################################### diff --git a/autogen/templates/new_project/test/test.c.jinja b/autogen/templates/new_project/test/test_{{project_name}}.c.jinja similarity index 100% rename from autogen/templates/new_project/test/test.c.jinja rename to autogen/templates/new_project/test/test_{{project_name}}.c.jinja diff --git a/autogen/templates/project_can/inc/can_codegen.h b/autogen/templates/project_can/inc/can_codegen.h.jinja similarity index 87% rename from autogen/templates/project_can/inc/can_codegen.h rename to autogen/templates/project_can/inc/can_codegen.h.jinja index da1205b..7864ceb 100644 --- a/autogen/templates/project_can/inc/can_codegen.h +++ b/autogen/templates/project_can/inc/can_codegen.h.jinja @@ -31,6 +31,10 @@ extern rx_struct g_rx_struct; extern tx_struct g_tx_struct; void can_tx_all(); +void can_tx_fast_cycle(); +void can_tx_medium_cycle(); +void can_tx_slow_cycle(); + void can_rx_all(); /** @} */ diff --git a/autogen/templates/project_can/src/{{project_name}}_rx_all.c.jinja b/autogen/templates/project_can/src/{{project_name}}_rx_all.c.jinja index 5d8959f..af8994e 100644 --- a/autogen/templates/project_can/src/{{project_name}}_rx_all.c.jinja +++ b/autogen/templates/project_can/src/{{project_name}}_rx_all.c.jinja @@ -53,13 +53,37 @@ void can_rx_all() { } } -void clear_rx_received() { +void clear_all_rx_received() { {%- for message in messages %} g_rx_struct.received_{{message.name}} = false; {%- endfor %} } -StatusCode check_can_watchdogs() { +void clear_fast_rx_received() { +{%- for message in messages %} +{%- if message.cycle == "fast" %} + g_rx_struct.received_{{message.name}} = false; +{%- endif %} +{%- endfor %} +} + +void clear_medium_rx_received() { +{%- for message in messages %} +{%- if message.cycle == "medium" %} + g_rx_struct.received_{{message.name}} = false; +{%- endif %} +{%- endfor %} +} + +void clear_slow_rx_received() { +{%- for message in messages %} +{%- if message.cycle == "slow" %} + g_rx_struct.received_{{message.name}} = false; +{%- endif %} +{%- endfor %} +} + +StatusCode check_all_can_watchdogs() { {%- for message in messages %} {%- if message.receiver[project_name].watchdog != 0 %} if (!g_rx_struct.received_{{message.name}}) { @@ -67,12 +91,73 @@ StatusCode check_can_watchdogs() { if (s_{{message.name}}_msg_watchdog.cycles_over >= s_{{message.name}}_msg_watchdog.max_cycles) { LOG_CRITICAL("DID NOT RECEIVE CAN MESSAGE: %u IN MAX CYCLES : %u\n", SYSTEM_CAN_MESSAGE_{{message.sender | upper}}_{{message.name | upper}}, s_{{message.name}}_msg_watchdog.max_cycles); - s_{{message.name}}_msg_watchdog.missed = 1; + s_{{message.name}}_msg_watchdog.missed = true; } else { - s_{{message.name}}_msg_watchdog.missed = 0; + s_{{message.name}}_msg_watchdog.missed = false; } } {%- endif %} {%- endfor %} return STATUS_CODE_OK; -} \ No newline at end of file +} + +StatusCode check_fast_can_watchdogs() { +{%- for message in messages %} +{%- if message.cycle == "fast" %} + {%- if message.receiver[project_name].watchdog != 0 %} + if (!g_rx_struct.received_{{message.name}}) { + ++s_{{message.name}}_msg_watchdog.cycles_over; + if (s_{{message.name}}_msg_watchdog.cycles_over >= s_{{message.name}}_msg_watchdog.max_cycles) { + LOG_CRITICAL("DID NOT RECEIVE FAST CYCLE CAN MESSAGE: %u IN MAX CYCLES : %u\n", SYSTEM_CAN_MESSAGE_{{message.sender | upper}}_{{message.name | upper}}, + s_{{message.name}}_msg_watchdog.max_cycles); + s_{{message.name}}_msg_watchdog.missed = true; + } else { + s_{{message.name}}_msg_watchdog.missed = false; + } + } + {%- endif %} +{%- endif %} +{%- endfor %} + return STATUS_CODE_OK; +} + +StatusCode check_medium_can_watchdogs() { +{%- for message in messages %} +{%- if message.cycle == "medium" %} + {%- if message.receiver[project_name].watchdog != 0 %} + if (!g_rx_struct.received_{{message.name}}) { + ++s_{{message.name}}_msg_watchdog.cycles_over; + if (s_{{message.name}}_msg_watchdog.cycles_over >= s_{{message.name}}_msg_watchdog.max_cycles) { + LOG_CRITICAL("DID NOT RECEIVE MEDIUM CYCLE CAN MESSAGE: %u IN MAX CYCLES : %u\n", SYSTEM_CAN_MESSAGE_{{message.sender | upper}}_{{message.name | upper}}, + s_{{message.name}}_msg_watchdog.max_cycles); + s_{{message.name}}_msg_watchdog.missed = true; + } else { + s_{{message.name}}_msg_watchdog.missed = false; + } + } + {%- endif %} +{%- endif %} +{%- endfor %} + return STATUS_CODE_OK; +} + +StatusCode check_slow_can_watchdogs() { +{%- for message in messages %} +{%- if message.cycle == "slow" %} + {%- if message.receiver[project_name].watchdog != 0 %} + if (!g_rx_struct.received_{{message.name}}) { + ++s_{{message.name}}_msg_watchdog.cycles_over; + if (s_{{message.name}}_msg_watchdog.cycles_over >= s_{{message.name}}_msg_watchdog.max_cycles) { + LOG_CRITICAL("DID NOT RECEIVE SLOW CAN MESSAGE: %u IN MAX CYCLES : %u\n", SYSTEM_CAN_MESSAGE_{{message.sender | upper}}_{{message.name | upper}}, + s_{{message.name}}_msg_watchdog.max_cycles); + s_{{message.name}}_msg_watchdog.missed = true; + } else { + s_{{message.name}}_msg_watchdog.missed = false; + } + } + {%- endif %} +{%- endif %} +{%- endfor %} + return STATUS_CODE_OK; +} + diff --git a/autogen/templates/project_can/src/{{project_name}}_tx_all.c.jinja b/autogen/templates/project_can/src/{{project_name}}_tx_all.c.jinja index 448617f..4c75c74 100644 --- a/autogen/templates/project_can/src/{{project_name}}_tx_all.c.jinja +++ b/autogen/templates/project_can/src/{{project_name}}_tx_all.c.jinja @@ -17,6 +17,7 @@ /* Intra-component Headers */ #include "can_codegen.h" {% set messages = messages | selectattr("sender", "eq", project_name) | list -%} + static CanMessage s_msg = { 0U }; static void prv_tx_can_message(CanMessageId id, uint8_t num_bytes, uint64_t data) { @@ -37,4 +38,46 @@ void can_tx_all() { {%- endfor -%} ); {%- endfor %} -} \ No newline at end of file +} + +void can_tx_fast_cycle() { +{%- for message in messages %} +{%- if message.cycle == "fast" %} + prv_tx_can_message( + SYSTEM_CAN_MESSAGE_{{message.sender | upper}}_{{message.name | upper}}, + {{- (message.signals | sum(attribute='length') / 8) | int }}, + {%- for signal in message.signals %} + (uint64_t) g_tx_struct.{{message.name}}_{{signal.name}} << {{signal.start_bit}}{{ " |" if not loop.last }} + {%- endfor -%} + ); +{%- endif %} +{%- endfor %} +} + +void can_tx_medium_cycle() { +{%- for message in messages %} +{%- if message.cycle == "medium" %} + prv_tx_can_message( + SYSTEM_CAN_MESSAGE_{{message.sender | upper}}_{{message.name | upper}}, + {{- (message.signals | sum(attribute='length') / 8) | int }}, + {%- for signal in message.signals %} + (uint64_t) g_tx_struct.{{message.name}}_{{signal.name}} << {{signal.start_bit}}{{ " |" if not loop.last }} + {%- endfor -%} + ); +{%- endif %} +{%- endfor %} +} + +void can_tx_slow_cycle() { +{%- for message in messages %} +{%- if message.cycle == "slow" %} + prv_tx_can_message( + SYSTEM_CAN_MESSAGE_{{message.sender | upper}}_{{message.name | upper}}, + {{- (message.signals | sum(attribute='length') / 8) | int }}, + {%- for signal in message.signals %} + (uint64_t) g_tx_struct.{{message.name}}_{{signal.name}} << {{signal.start_bit}}{{ " |" if not loop.last }} + {%- endfor -%} + ); +{%- endif %} +{%- endfor %} +} diff --git a/autogen/templates/system_can/system_can.h.jinja b/autogen/templates/system_can/system_can.h.jinja index ae9442d..c3945b4 100644 --- a/autogen/templates/system_can/system_can.h.jinja +++ b/autogen/templates/system_can/system_can.h.jinja @@ -35,6 +35,6 @@ typedef enum { {% for message in messages %} #define SYSTEM_CAN_MESSAGE_{{ message.sender | upper }}_{{ message.name | upper }} \ - {% if not message.critical %} SYSTEM_CAN_MESSAGE_PRIORITY_BIT + {% endif %}({{ message.id }} << CAN_MESSAGE_ID_OFFSET) + SYSTEM_CAN_DEVICE_{{ message.sender | upper }} + {% if not message.critical %} SYSTEM_CAN_MESSAGE_PRIORITY_BIT + {% endif %}({{ message.id }} << SYSTEM_CAN_MESSAGE_ID_OFFSET) + SYSTEM_CAN_DEVICE_{{ message.sender | upper }} {% endfor %} /** @} */ diff --git a/build_system.md b/build_system.md index 715f0ae..7838624 100644 --- a/build_system.md +++ b/build_system.md @@ -63,6 +63,10 @@ Commands: (x86) Run the project's binary. - e.g. `scons sim --platform=x86 ` (`scons sim --platform=x86 --project=new_led`) + vehicle_sim + (x86) Runs a full vehicle simulation + - e.g. 'scons vehicle_sim --platform=x86' + gdb (x86) Run the project's binary with gdb. - e.g. `scons gdb ` (`scons gdb --project=new_led`) @@ -84,6 +88,9 @@ Commands: clean Delete the `build` directory. + hil + Runs a HIL test. This is not fully implemented yet + doxygen Build a local copy of the doxygen HTML diff --git a/can/boards/bms_carrier.yaml b/can/boards/bms_carrier.yaml index 69ed757..a8854bb 100644 --- a/can/boards/bms_carrier.yaml +++ b/can/boards/bms_carrier.yaml @@ -25,25 +25,8 @@ afe_status: length: 8 - battery_info: - id: 14 - critical: true - cycle: "slow" - target: - centre_console: - watchdog: 0 - signals: - fan1: - length: 8 - fan2: - length: 8 - max_cell_v: - length: 16 - min_cell_v: - length: 16 - battery_vt: - id: 15 + id: 2 critical: true cycle: "fast" target: @@ -58,11 +41,28 @@ length: 16 batt_perc: length: 16 + + battery_info: + id: 3 + critical: true + cycle: "slow" + target: + centre_console: + watchdog: 0 + signals: + fan1: + length: 8 + fan2: + length: 8 + max_cell_v: + length: 16 + min_cell_v: + length: 16 AFE1_status: id: 61 critical: false - cycle: "medium" + cycle: "slow" target: centre_console: watchdog: 0 @@ -83,7 +83,7 @@ AFE2_status: id: 62 critical: false - cycle: "medium" + cycle: "slow" target: centre_console: watchdog: 0 @@ -104,7 +104,7 @@ AFE3_status: id: 63 critical: false - cycle: "medium" + cycle: "slow" target: centre_console: watchdog: 0 diff --git a/can/boards/can_communication.yaml b/can/boards/can_communication.yaml new file mode 100644 index 0000000..6d29a5b --- /dev/null +++ b/can/boards/can_communication.yaml @@ -0,0 +1,44 @@ +# If you need to add a new message use a reasonable +# reserved ID. The higher ID the lower the priority. Generally +# - 0-13: Critical messages +# - 14-30: Actionable messages (trigger a change in another system) +# - 30-63: Data messages (usually not actionable by an onboard device) + +--- + Messages: + fast_one_shot_msg: + id: 58 + critical: false + cycle: "fast" + target: + can_communication: + watchdog: 5 + signals: + sig1: + length: 16 + sig2: + length: 16 + medium_one_shot_msg: + id: 59 + critical: false + cycle: "medium" + target: + can_communication: + watchdog: 5 + signals: + sig1: + length: 16 + sig2: + length: 16 + slow_one_shot_msg: + id: 60 + critical: false + cycle: "slow" + target: + can_communication: + watchdog: 5 + signals: + sig1: + length: 16 + sig2: + length: 16 diff --git a/can/boards/centre_console.yaml b/can/boards/centre_console.yaml index aa0a91f..5f4c570 100644 --- a/can/boards/centre_console.yaml +++ b/can/boards/centre_console.yaml @@ -7,7 +7,7 @@ --- Messages: cc_pedal: - id: 2 + id: 4 critical: true cycle: "fast" target: @@ -58,7 +58,7 @@ Messages: length: 8 cc_regen_percentage: - id: 8 + id: 7 critical: true cycle: "medium" target: diff --git a/can/inc/can.h b/can/inc/can.h index 4a61718..885eead 100644 --- a/can/inc/can.h +++ b/can/inc/can.h @@ -27,12 +27,15 @@ * @{ */ +/** @brief Maximum time permitted for a CAN transaction */ +#define CAN_TIMEOUT_MS 5U + /** * @brief Storage class for the device ID and RX data */ typedef struct CanStorage { - volatile CanQueue rx_queue; - uint16_t device_id; + volatile CanQueue rx_queue; /**< Global RX queue to store CAN messages */ + uint16_t device_id; /**< Device ID of the running device */ } CanStorage; /** @@ -48,62 +51,79 @@ StatusCode can_init(CanStorage *storage, const CanSettings *settings); /** * @brief Sets a filter on the CAN interface * @param msg_id Message ID of the message to filter - * @return STATUS_CODE_OK if adding the filter succeeded + * @return STATUS_CODE_OK if the filter is added successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails */ StatusCode can_add_filter_in(CanMessageId msg_id); /** - * @brief Initialize the CAN interface + * @brief Transmits CAN data on the bus * @param msg Pointer to the message to transmit - * @return STATUS_CODE_OK if data is transmitted succesfuully + * @return STATUS_CODE_OK if data is transmitted successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect - * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails + * STATUS_CODE_UNINITIALIZED if the CAN bus is not initialized */ StatusCode can_transmit(const CanMessage *msg); /** - * @brief Initialize the CAN interface + * @brief Receives CAN data from the bus * @param msg Pointer to the message to update on receive - * @return STATUS_CODE_OK if data is retrieved succesfuully + * @return STATUS_CODE_OK if data is retrieved successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect - * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails + * STATUS_CODE_UNINITIALIZED if the CAN bus is not initialized */ StatusCode can_receive(const CanMessage *msg); /** - * @brief Run the CAN TX cache to transmit all messages - * @param rx_queue Pointer to the CAN RX queue - * @param settings Pointer to the CAN settings - * @return STATUS_CODE_OK if the cache is cleared succesfuully + * @brief Transmit all CAN data + * @return STATUS_CODE_OK if the cache is cleared successfully * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails */ -StatusCode run_can_tx_cache(); +StatusCode run_can_tx_all(); /** - * @brief Run the CAN RX cache to receive all messages - * @param rx_queue Pointer to the CAN RX queue - * @param settings Pointer to the CAN settings - * @return STATUS_CODE_OK if the cache is cleared succesfuully + * @brief Transmit all fast-cycle CAN data + * @return STATUS_CODE_OK if the cache is cleared successfully + * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails + */ +StatusCode run_can_tx_fast(); + +/** + * @brief Transmit all medium-cycle CAN data + * @return STATUS_CODE_OK if the cache is cleared successfully + * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails + */ +StatusCode run_can_tx_medium(); + +/** + * @brief Transmit all slow-cycle CAN data + * @return STATUS_CODE_OK if the cache is cleared successfully + * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails + */ +StatusCode run_can_tx_slow(); + +/** + * @brief Receive all CAN data + * @return STATUS_CODE_OK if the cache is cleared successfully * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails */ -StatusCode run_can_rx_cache(); +StatusCode run_can_rx_all(); /** - * @brief Clear the RX cache + * @brief Clear the RX data struct * @return STATUS_CODE_OK if initialization succeeded * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails */ -StatusCode clear_rx_cache(); +StatusCode clear_rx_struct(); /** - * @brief Clear the TX cache + * @brief Clear the TX data struct * @return STATUS_CODE_OK if initialization succeeded * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect * STATUS_CODE_INTERNAL_ERROR if HAL initialization fails */ -StatusCode clear_tx_cache(); +StatusCode clear_tx_struct(); /** @} */ diff --git a/can/inc/can_hw.h b/can/inc/can_hw.h index 339e308..dbaa099 100644 --- a/can/inc/can_hw.h +++ b/can/inc/can_hw.h @@ -89,6 +89,8 @@ StatusCode can_hw_init(const CanQueue* rx_queue, const CanSettings *settings); /** * @brief Sets a filter on the CAN interface + * @details The filter works as such: + * if INCOMING_MSG_ID & mask == filter & mask, the message is handled * @param mask Determines which bits in the received ID are considered during filtering * @param filter Specifies the pattern the CAN ID must adhere to * @param extended Boolean to use CAN extended ID feature @@ -111,7 +113,7 @@ CanHwBusStatus can_hw_bus_status(void); * @param extended Boolean to use CAN extended ID feature * @param data Pointer to the data to transmit * @param len Size of the data to transfer - * @return STATUS_CODE_OK if data is transmitted succesfully + * @return STATUS_CODE_OK if data is transmitted successfully * STATUS_CODE_RESOURCE_EXHAUSTED if CAN mailbox is full * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ @@ -123,7 +125,7 @@ StatusCode can_hw_transmit(uint32_t id, bool extended, const uint8_t *data, size * @param extended Pointer to a flag to indicate CAN extended ID feature * @param data Pointer to a buffer to store data * @param len Pointer to the number of CAN messages received - * @return true if data is retrieved succesfully + * @return true if data is retrieved successfully * false if one of the parameters are incorrect or internal error occurred */ bool can_hw_receive(uint32_t *id, bool *extended, uint64_t *data, size_t *len); diff --git a/can/inc/can_msg.h b/can/inc/can_msg.h index b4fa661..b37bb5e 100644 --- a/can/inc/can_msg.h +++ b/can/inc/can_msg.h @@ -23,6 +23,9 @@ * @{ */ +/** @brief Maximum permitted CAN ID for 11-bit identifiers */ +#define CAN_MSG_MAX_STD_IDS (1 << 11) + /** * @brief CAN message ID * @details 11 Bits in standard mode diff --git a/can/inc/can_watchdog.h b/can/inc/can_watchdog.h index 5be5578..909caa2 100644 --- a/can/inc/can_watchdog.h +++ b/can/inc/can_watchdog.h @@ -23,4 +23,46 @@ * @{ */ +/** + * @brief Software defined CAN watchdog + */ +typedef struct CanWatchDog { + uint16_t cycles_over; /**< Tracks the number of cycles over max_cycles */ + uint16_t max_cycles; /**< Maximum number of cycles open for data to be received */ + bool missed; /**< Flag to track if the message has been missed */ +} CanWatchDog; + +/** + * @brief Checks all CAN watchdogs + * @details This will throw a LOG_WARN if a message has missed its watchdog + * @return STATUS_CODE_OK if all watchdogs are checked succesfully + */ +StatusCode check_all_can_watchdogs(); + +/** + * @brief Checks CAN watchdogs for messages in fast-cycle + * @details This will throw a LOG_WARN if a message has missed its watchdog + * @return STATUS_CODE_OK if all watchdogs are checked succesfully + */ +StatusCode check_fast_can_watchdogs(); + +/** + * @brief Checks CAN watchdogs for messages in medium-cycle + * @details This will throw a LOG_WARN if a message has missed its watchdog + * @return STATUS_CODE_OK if all watchdogs are checked succesfully + */ +StatusCode check_medium_can_watchdogs(); + +/** + * @brief Checks CAN watchdogs for messages in slow-cycle + * @details This will throw a LOG_WARN if a message has missed its watchdog + * @return STATUS_CODE_OK if all watchdogs are checked succesfully + */ +StatusCode check_slow_can_watchdogs(); + +void clear_all_rx_received(); +void clear_fast_rx_received(); +void clear_medium_rx_received(); +void clear_slow_rx_received(); + /** @} */ diff --git a/can/src/can.c b/can/src/can.c index 57d9b80..9c08be1 100644 --- a/can/src/can.c +++ b/can/src/can.c @@ -15,13 +15,16 @@ #include "FreeRTOS.h" #include "semphr.h" #include "log.h" +#include "system_can.h" +#include "can_codegen.h" /* Intra-component Headers */ #include "can_hw.h" #include "can.h" +#include "can_watchdog.h" -// rx_struct g_rx_struct; -// tx_struct g_tx_struct; +rx_struct g_rx_struct; +tx_struct g_tx_struct; static CanStorage *s_can_storage; @@ -33,17 +36,17 @@ static SemaphoreHandle_t s_can_rx_handle; StatusCode can_init(CanStorage *storage, const CanSettings *settings) { - // if (settings->device_id >= CAN_MSG_MAX_DEVICES) { - // return STATUS_CODE_INVALID_ARGS; - // } + if (settings->device_id >= NUM_SYSTEM_CAN_DEVICES) { + return STATUS_CODE_INVALID_ARGS; + } s_can_storage = storage; memset(s_can_storage, 0, sizeof(*s_can_storage)); s_can_storage->device_id = settings->device_id; - // memset(&g_tx_struct, 0, sizeof(g_tx_struct)); - // memset(&g_rx_struct, 0, sizeof(g_rx_struct)); + memset(&g_tx_struct, 0, sizeof(g_tx_struct)); + memset(&g_rx_struct, 0, sizeof(g_rx_struct)); status_ok_or_return(can_queue_init(&s_can_storage->rx_queue)); @@ -58,4 +61,117 @@ StatusCode can_init(CanStorage *storage, const CanSettings *settings) } return STATUS_CODE_OK; -} \ No newline at end of file +} + +StatusCode can_add_filter_in(CanMessageId msg_id) { + + if (s_can_storage == NULL) { + return STATUS_CODE_UNINITIALIZED; + } else if (msg_id >= CAN_MSG_MAX_STD_IDS) { + return STATUS_CODE_INVALID_ARGS; + } + + /* Set the filter as the message ID */ + CanId can_id = { .raw = msg_id }; + + /* Mask everything, to check all bits of the incoming ID */ + CanId mask = { 0U }; + mask.raw = (uint32_t)~mask.msg_id; + + return can_hw_add_filter_in(mask.raw, can_id.raw, false); +} + +StatusCode can_transmit(const CanMessage *msg) { + if (s_can_storage == NULL) { + return STATUS_CODE_UNINITIALIZED; + } + + return can_hw_transmit(msg->id.raw, msg->extended, msg->data_u8, msg->dlc); +} + +StatusCode can_receive(const CanMessage *msg) { + if (s_can_storage == NULL) { + return STATUS_CODE_UNINITIALIZED; + } + + StatusCode ret = can_queue_pop(&s_can_storage->rx_queue, msg); + + // if (ret == STATUS_CODE_OK) + // { + // LOG_DEBUG("Source Id: %d\n", msg->id); + // LOG_DEBUG("Data: %lx\n", msg->data); + // LOG_DEBUG("DLC: %ld\n", msg->dlc); + // LOG_DEBUG("ret: %d\n", ret); + // } + + return ret; +} + +StatusCode run_can_tx_all() { + xSemaphoreTake(s_can_tx_handle, pdMS_TO_TICKS(CAN_TIMEOUT_MS)); + + can_tx_all(); + + /* Check all cycles watchdogs, and update internal states */ + check_all_can_watchdogs(); + clear_all_rx_received(); + + xSemaphoreGive(s_can_tx_handle); + return STATUS_CODE_OK; +} + +StatusCode run_can_tx_fast() { + xSemaphoreTake(s_can_tx_handle, pdMS_TO_TICKS(CAN_TIMEOUT_MS)); + + can_tx_fast_cycle(); + + /* Check fast cycle watchdogs, and update internal states */ + check_fast_can_watchdogs(); + clear_fast_rx_received(); + + xSemaphoreGive(s_can_tx_handle); + return STATUS_CODE_OK; +} + +StatusCode run_can_tx_medium() { + xSemaphoreTake(s_can_tx_handle, pdMS_TO_TICKS(CAN_TIMEOUT_MS)); + + can_tx_medium_cycle(); + + /* Check medium cycle watchdogs, and update internal states */ + check_medium_can_watchdogs(); + clear_medium_rx_received(); + + xSemaphoreGive(s_can_tx_handle); + return STATUS_CODE_OK; +} + +StatusCode run_can_tx_slow() { + xSemaphoreTake(s_can_tx_handle, pdMS_TO_TICKS(CAN_TIMEOUT_MS)); + + can_tx_slow_cycle(); + + /* Check slow cycle watchdogs, and update internal states */ + check_slow_can_watchdogs(); + clear_slow_rx_received(); + + xSemaphoreGive(s_can_tx_handle); + return STATUS_CODE_OK; +} + +StatusCode run_can_rx_all() { + xSemaphoreTake(s_can_rx_handle, pdMS_TO_TICKS(CAN_TIMEOUT_MS)); + can_rx_all(); + xSemaphoreGive(s_can_rx_handle); + return STATUS_CODE_OK; +} + +StatusCode clear_rx_struct() { + memset(&g_rx_struct, 0, sizeof(g_rx_struct)); + return STATUS_CODE_OK; +} + +StatusCode clear_tx_struct() { + memset(&g_rx_struct, 0, sizeof(g_rx_struct)); + return STATUS_CODE_OK; +} diff --git a/can/tools/system_dbc.dbc b/can/tools/system_dbc.dbc index 24b9237..1376fdc 100644 --- a/can/tools/system_dbc.dbc +++ b/can/tools/system_dbc.dbc @@ -30,7 +30,7 @@ NS_ : BS_: -BU_: BMS_CARRIER CENTRE_CONSOLE +BU_: BMS_CARRIER CAN_COMMUNICATION CENTRE_CONSOLE BO_ 16 battery_status: 7 bms_carrier SG_ fault : 0|16@1+ (1,0) [0|65535] "" centre_console, power_distribution @@ -38,18 +38,18 @@ BO_ 16 battery_status: 7 bms_carrier SG_ aux_batt_v : 32|16@1+ (1,0) [0|65535] "" centre_console, power_distribution SG_ afe_status : 48|8@1+ (1,0) [0|255] "" centre_console, power_distribution -BO_ 224 battery_info: 6 bms_carrier - SG_ fan1 : 0|8@1+ (1,0) [0|255] "" centre_console - SG_ fan2 : 8|8@1+ (1,0) [0|255] "" centre_console - SG_ max_cell_v : 16|16@1+ (1,0) [0|65535] "" centre_console - SG_ min_cell_v : 32|16@1+ (1,0) [0|65535] "" centre_console - -BO_ 240 battery_vt: 8 bms_carrier +BO_ 32 battery_vt: 8 bms_carrier SG_ voltage : 0|16@1+ (1,0) [0|65535] "" centre_console SG_ current : 16|16@1+ (1,0) [0|65535] "" centre_console SG_ temperature : 32|16@1+ (1,0) [0|65535] "" centre_console SG_ batt_perc : 48|16@1+ (1,0) [0|65535] "" centre_console +BO_ 48 battery_info: 6 bms_carrier + SG_ fan1 : 0|8@1+ (1,0) [0|255] "" centre_console + SG_ fan2 : 8|8@1+ (1,0) [0|255] "" centre_console + SG_ max_cell_v : 16|16@1+ (1,0) [0|65535] "" centre_console + SG_ min_cell_v : 32|16@1+ (1,0) [0|65535] "" centre_console + BO_ 2000 AFE1_status: 8 bms_carrier SG_ id : 0|8@1+ (1,0) [0|255] "" centre_console, power_distribution SG_ temp : 8|8@1+ (1,0) [0|255] "" centre_console, power_distribution @@ -71,21 +71,33 @@ BO_ 2032 AFE3_status: 8 bms_carrier SG_ v2 : 32|16@1+ (1,0) [0|65535] "" centre_console, power_distribution SG_ v3 : 48|16@1+ (1,0) [0|65535] "" centre_console, power_distribution -BO_ 33 cc_pedal: 5 centre_console +BO_ 1953 fast_one_shot_msg: 4 can_communication + SG_ sig1 : 0|16@1+ (1,0) [0|65535] "" can_communication + SG_ sig2 : 16|16@1+ (1,0) [0|65535] "" can_communication + +BO_ 1969 medium_one_shot_msg: 4 can_communication + SG_ sig1 : 0|16@1+ (1,0) [0|65535] "" can_communication + SG_ sig2 : 16|16@1+ (1,0) [0|65535] "" can_communication + +BO_ 1985 slow_one_shot_msg: 4 can_communication + SG_ sig1 : 0|16@1+ (1,0) [0|65535] "" can_communication + SG_ sig2 : 16|16@1+ (1,0) [0|65535] "" can_communication + +BO_ 66 cc_pedal: 5 centre_console SG_ throttle_output : 0|32@1+ (1,0) [0|4294967295] "" motor_controller, power_distribution SG_ brake_output : 32|8@1+ (1,0) [0|255] "" motor_controller, power_distribution -BO_ 81 cc_info: 8 centre_console +BO_ 82 cc_info: 8 centre_console SG_ target_velocity : 0|32@1+ (1,0) [0|4294967295] "" motor_controller, power_distribution SG_ drive_state : 32|8@1+ (1,0) [0|255] "" motor_controller, power_distribution SG_ cruise_control : 40|8@1+ (1,0) [0|255] "" motor_controller, power_distribution SG_ regen_braking : 48|8@1+ (1,0) [0|255] "" motor_controller, power_distribution SG_ hazard_enabled : 56|8@1+ (1,0) [0|255] "" motor_controller, power_distribution -BO_ 97 cc_steering: 2 centre_console +BO_ 98 cc_steering: 2 centre_console SG_ input_cc : 0|8@1+ (1,0) [0|255] "" power_distribution, motor_controller SG_ input_lights : 8|8@1+ (1,0) [0|255] "" power_distribution, motor_controller -BO_ 129 cc_regen_percentage: 4 centre_console +BO_ 114 cc_regen_percentage: 4 centre_console SG_ percent : 0|32@1+ (1,0) [0|4294967295] "" motor_controller diff --git a/libraries/ms-common/inc/flash.h b/libraries/ms-common/inc/flash.h index c34b306..eeadaf3 100644 --- a/libraries/ms-common/inc/flash.h +++ b/libraries/ms-common/inc/flash.h @@ -57,7 +57,7 @@ StatusCode flash_init(); * @param address Memory address to read from. This value MUST be 4-byte aligned * @param buffer Pointer to the buffer to store data * @param buffer_len Length of the buffer. This value MUST be 4-byte aligned - * @return STATUS_CODE_OK if flash memory was read succesfully + * @return STATUS_CODE_OK if flash memory was read successfully * STATUS_CODE_OUT_OF_RANGE if address is out of range * STATUS_CODE_INVALID_ARGS if address or read bytes is not aligned */ @@ -70,7 +70,7 @@ StatusCode flash_read(uintptr_t address, uint8_t *buffer, size_t buffer_len); * @param address Memory address to store the buffer. This value MUST be 4-byte aligned * @param buffer Pointer to the buffer of data to store * @param buffer_len Length of the buffer to write. This MUST also be 4-byte aligned - * @return STATUS_CODE_OK if flash memory was written succesfully + * @return STATUS_CODE_OK if flash memory was written successfully * STATUS_CODE_OUT_OF_RANGE if address is out of range * STATUS_CODE_INTERNAL_ERROR if flash write failed */ @@ -80,7 +80,7 @@ StatusCode flash_write(uintptr_t address, uint8_t *buffer, size_t buffer_len); * @brief Erase pages of flash memory * @param start_page First page to erase * @param num_pages Number of pages to erase - * @return STATUS_CODE_OK if flash memory was erased succesfully + * @return STATUS_CODE_OK if flash memory was erased successfully * STATUS_CODE_INVALID_ARGS if incorrect arguments were passed in * STATUS_CODE_INTERNAL_ERROR if flash erase failed */ diff --git a/libraries/ms-common/inc/i2c.h b/libraries/ms-common/inc/i2c.h index 601bc13..8ce735f 100644 --- a/libraries/ms-common/inc/i2c.h +++ b/libraries/ms-common/inc/i2c.h @@ -130,7 +130,7 @@ StatusCode i2c_write_reg(I2CPort i2c, I2CAddress addr, uint8_t reg, uint8_t *tx_ * @param i2c Specifies which I2C port to update * @param data Pointer to a buffer of data to set * @param len Length of the data to set - * @return STATUS_CODE_OK if data is set succesfully + * @return STATUS_CODE_OK if data is set successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode i2c_set_rx_data(I2CPort i2c, uint8_t *data, size_t len); @@ -140,7 +140,7 @@ StatusCode i2c_set_rx_data(I2CPort i2c, uint8_t *data, size_t len); * @param i2c Specifies which I2C port to read from * @param data Pointer to a buffer of data to fill * @param len Length of the data to retrieve - * @return STATUS_CODE_OK if data is retrieved succesfully + * @return STATUS_CODE_OK if data is retrieved successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode i2c_get_tx_data(I2CPort i2c, uint8_t *data, size_t len); diff --git a/libraries/ms-common/inc/interrupts.h b/libraries/ms-common/inc/interrupts.h index c5a47f5..c94413d 100644 --- a/libraries/ms-common/inc/interrupts.h +++ b/libraries/ms-common/inc/interrupts.h @@ -88,7 +88,7 @@ void interrupt_init(void); * @brief Enables the nested interrupt vector controller for a given channel * @param irq_channel Numeric ID of the interrupt channel from the NVIC * @param priority Priority level of the interrupt - * @return STATUS_CODE_OK if the channel is succesfully initialized + * @return STATUS_CODE_OK if the channel is successfully initialized * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode interrupt_nvic_enable(uint8_t irq_channel, InterruptPriority priority); @@ -97,7 +97,7 @@ StatusCode interrupt_nvic_enable(uint8_t irq_channel, InterruptPriority priority * @brief Enables an external interrupt line with the given settings * @param address Pointer to the GPIO address * @param settings Pointer to the interrupt settings - * @return STATUS_CODE_OK if the channel is succesfully initialized + * @return STATUS_CODE_OK if the channel is successfully initialized * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode interrupt_exti_enable(GpioAddress *address, const InterruptSettings *settings); @@ -105,7 +105,7 @@ StatusCode interrupt_exti_enable(GpioAddress *address, const InterruptSettings * /** * @brief Triggers a software interrupt on a given external interrupt channel * @param line Numeric ID of the EXTI line (GPIO Pin number) - * @return STATUS_CODE_OK if the channel is succesfully initialized + * @return STATUS_CODE_OK if the channel is successfully initialized * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode interrupt_exti_trigger(uint8_t line); @@ -114,7 +114,7 @@ StatusCode interrupt_exti_trigger(uint8_t line); * @brief Get the pending flag for an external interrupt * @param line Numeric ID of the EXTI line (GPIO Pin number) * @param pending_bit Pointer to a variable that is updated with the pending bit - * @return STATUS_CODE_OK if the channel is succesfully initialized + * @return STATUS_CODE_OK if the channel is successfully initialized * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode interrupt_exti_get_pending(uint8_t line, uint8_t *pending_bit); @@ -122,7 +122,7 @@ StatusCode interrupt_exti_get_pending(uint8_t line, uint8_t *pending_bit); /** * @brief Clears the pending flag for an external interrupt * @param line Numeric ID of the EXTI line (GPIO Pin number) - * @return STATUS_CODE_OK if the channel is succesfully initialized + * @return STATUS_CODE_OK if the channel is successfully initialized * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode interrupt_exti_clear_pending(uint8_t line); @@ -131,7 +131,7 @@ StatusCode interrupt_exti_clear_pending(uint8_t line); * @brief Masks or clears the external interrupt on a given line * @param line Numeric ID of the EXTI line (GPIO Pin number) * @param masked 0: Clears the interrupt 1: Mask the lines interrupt - * @return STATUS_CODE_OK if the channel is succesfully initialized + * @return STATUS_CODE_OK if the channel is successfully initialized * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode interrupt_exti_set_mask(uint8_t line, bool masked); @@ -147,7 +147,7 @@ typedef void (*x86InterruptHandler)(uint8_t interrupt_id); * @param irq_channel Numeric ID of the interrupt channel from the NVIC * @param handler Function pointer to the interrupt handler. Can be left as NULL * @param settings Pointer to the interrupt settings - * @return STATUS_CODE_OK if the interrupt handler is registered succesfully + * @return STATUS_CODE_OK if the interrupt handler is registered successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode interrupt_nvic_register_handler(uint8_t irq_channel, x86InterruptHandler handler, const InterruptSettings *settings); @@ -158,7 +158,7 @@ StatusCode interrupt_nvic_register_handler(uint8_t irq_channel, x86InterruptHand * @param line Numeric ID of the EXTI line (GPIO Pin number) * @param handler Function pointer to the interrupt handler. Can be left as NULL * @param settings Pointer to the interrupt settings - * @return STATUS_CODE_OK if the interrupt handler is registered succesfully + * @return STATUS_CODE_OK if the interrupt handler is registered successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode interrupt_exti_register_handler(uint8_t line, x86InterruptHandler handler, const InterruptSettings *settings); diff --git a/libraries/ms-common/inc/notify.h b/libraries/ms-common/inc/notify.h index 5cfa2c4..e3b0b10 100644 --- a/libraries/ms-common/inc/notify.h +++ b/libraries/ms-common/inc/notify.h @@ -53,7 +53,7 @@ bool notify_check_event(uint32_t *notification, Event event); /** * @brief Get the current notification value for the calilng task without a timeout * @param notification Pointer to a notification value that is updated upon success - * @return STATUS_CODE_OK if the value is retrieved succesfully + * @return STATUS_CODE_OK if the value is retrieved successfully * STATUS_CODE_TIMEOUT if the value cannot be retrieved */ StatusCode notify_get(uint32_t *notification); @@ -62,7 +62,7 @@ StatusCode notify_get(uint32_t *notification); * @brief Get the current notification value for the calilng task with a maximum timeout * @param notification Pointer to a notification value that is polled * @param ms_to_wait Time in milliseconds to wait for a notification before timing out - * @return STATUS_CODE_OK if notification is received succesfully + * @return STATUS_CODE_OK if notification is received successfully * STATUS_CODE_TIMEOUT if a timeout has occurred */ StatusCode notify_wait(uint32_t *notification, uint32_t ms_to_wait); diff --git a/libraries/ms-common/inc/spi.h b/libraries/ms-common/inc/spi.h index dc0512b..7f567dc 100644 --- a/libraries/ms-common/inc/spi.h +++ b/libraries/ms-common/inc/spi.h @@ -119,7 +119,7 @@ StatusCode spi_exchange(SpiPort spi, uint8_t *tx_data, size_t tx_len, uint8_t *r * @param i2c Specifies which SPI port to read from * @param data Pointer to a buffer of data to fill * @param len Length of the data to retrieve - * @return STATUS_CODE_OK if data is retrieved succesfully + * @return STATUS_CODE_OK if data is retrieved successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode spi_get_tx_data(SpiPort spi, uint8_t *data, uint8_t len); @@ -129,7 +129,7 @@ StatusCode spi_get_tx_data(SpiPort spi, uint8_t *data, uint8_t len); * @param spi Specifies which SPI port to update * @param data Pointer to a buffer of data to set * @param len Length of the data to set - * @return STATUS_CODE_OK if data is set succesfully + * @return STATUS_CODE_OK if data is set successfully * STATUS_CODE_INVALID_ARGS if one of the parameters are incorrect */ StatusCode spi_set_rx(SpiPort spi, uint8_t *data, uint8_t len); diff --git a/libraries/ms-common/inc/tasks.h b/libraries/ms-common/inc/tasks.h index 0d36ee1..229c040 100644 --- a/libraries/ms-common/inc/tasks.h +++ b/libraries/ms-common/inc/tasks.h @@ -96,7 +96,7 @@ typedef struct Task { * @param priority The task priority: higher number is higher priority, and the maximum * @param context Pointer to arguments that are passed to the task * is configNUM_TASK_PRIORITIES - 1. - * @return STATUS_CODE_OK if succesfully initialize task. + * @return STATUS_CODE_OK if successfully initialize task. */ StatusCode tasks_init_task(Task *task, TaskPriority priority, void *context); @@ -109,7 +109,7 @@ void tasks_start(void); /** * @brief Initialize the task module * @details Must be called before tasks are initialized or the scheduler is started. - * @return STATUS_CODE_OK if succesfully initialize the module. + * @return STATUS_CODE_OK if successfully initialize the module. */ StatusCode tasks_init(void); @@ -124,7 +124,7 @@ StatusCode wait_tasks(uint16_t num_tasks); /** * @brief A wrapper to give to the semaphore, to be called by tasks when they complete - * @return STATUS_CODE_OK if succesfully release sempahore + * @return STATUS_CODE_OK if successfully release sempahore */ StatusCode send_task_end(void); diff --git a/libraries/ms-common/src/x86/interrupts.c b/libraries/ms-common/src/x86/interrupts.c index c0140c9..93c35bb 100644 --- a/libraries/ms-common/src/x86/interrupts.c +++ b/libraries/ms-common/src/x86/interrupts.c @@ -39,7 +39,9 @@ typedef enum { typedef struct { bool enabled; /**< Interrupt Channel enabled state */ bool masked; /**< Interrupt Channel masked state */ + bool pending; /**< Interrupt Channel pending state */ x86InterruptHandler handler; /**< Interrupt Channel interrupt handler */ + InterruptPriority priority; /**< Interrupt Channel interrupt priority */ } X86Interrupt; /** @brief Mock NVIC table that stores all function pointers */ @@ -59,8 +61,11 @@ static void s_nvic_handler(int signum, siginfo_t *info, void *context) { int interrupt_id = info->si_value.sival_int; if (interrupt_id >= 0 && (uint32_t)interrupt_id < NUM_STM32L433X_INTERRUPT_CHANNELS) { - if ((s_nvic_handlers[interrupt_id].handler != NULL) && !s_nvic_handlers[interrupt_id].masked && s_nvic_handlers[interrupt_id].enabled) { + if ((s_nvic_handlers[interrupt_id].handler != NULL) && !s_nvic_handlers[interrupt_id].masked && s_nvic_handlers[interrupt_id].enabled && s_nvic_handlers[interrupt_id].pending) { s_nvic_handlers[interrupt_id].handler(interrupt_id); + + /* Turn off pending after handler function has run */ + s_nvic_handlers[interrupt_id].pending = false; } } } @@ -69,8 +74,9 @@ static void s_exti_handler(int signum, siginfo_t *info, void *context) { int interrupt_id = info->si_value.sival_int; if (interrupt_id >= 0 && (uint32_t)interrupt_id < NUM_STM32L433X_EXTI_LINES) { - if ((s_exti_interrupts[interrupt_id].handler != NULL) && !s_exti_interrupts[interrupt_id].masked && s_exti_interrupts[interrupt_id].enabled) { + if ((s_exti_interrupts[interrupt_id].handler != NULL) && !s_exti_interrupts[interrupt_id].masked && s_exti_interrupts[interrupt_id].enabled && s_exti_interrupts[interrupt_id].pending) { s_exti_interrupts[interrupt_id].handler(interrupt_id); + s_exti_interrupts[interrupt_id].pending = false; } } } @@ -150,26 +156,44 @@ void interrupt_init(void) { for (uint8_t i = 0U; i < NUM_STM32L433X_INTERRUPT_CHANNELS; i++) { s_nvic_handlers[i].enabled = false; s_nvic_handlers[i].masked = true; + s_nvic_handlers[i].pending = false; s_nvic_handlers[i].handler = s_default_handler; } for (uint8_t i = 0U; i < NUM_STM32L433X_EXTI_LINES; i++) { s_exti_interrupts[i].enabled = false; s_exti_interrupts[i].masked = true; + s_exti_interrupts[i].pending = false; s_exti_interrupts[i].handler = s_default_handler; } } StatusCode interrupt_nvic_enable(uint8_t irq_channel, InterruptPriority priority) { - /* This function is for future expansion, in the situation we want to simulate NVIC interrupts */ - return STATUS_CODE_UNIMPLEMENTED; + /* Validate priority and irq_channel */ + if ((priority >= NUM_INTERRUPT_PRIORITIES && priority < configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY) || irq_channel >= NUM_STM32L433X_INTERRUPT_CHANNELS) { + return STATUS_CODE_INVALID_ARGS; + } + + /* Check if channel is already enabled */ + if (s_nvic_handlers[irq_channel].enabled) { + return STATUS_CODE_RESOURCE_EXHAUSTED; + } + + /* Enable the interrupt, unmask, and set priority */ + s_nvic_handlers[irq_channel].enabled = true; + s_nvic_handlers[irq_channel].masked = false; + s_nvic_handlers[irq_channel].priority = priority; + + return STATUS_CODE_OK; } StatusCode interrupt_nvic_register_handler(uint8_t irq_channel, x86InterruptHandler handler, const InterruptSettings *settings) { + /* Validate settings and channel */ if (settings == NULL || settings->class >= NUM_INTERRUPT_CLASSES || settings->edge >= NUM_INTERRUPT_EDGES || irq_channel > NUM_STM32L433X_INTERRUPT_CHANNELS) { return STATUS_CODE_INVALID_ARGS; } + /* If handler is not provided, use the default handler defined earlier */ if (handler == NULL) { s_nvic_handlers[irq_channel].handler = s_default_handler; } else { @@ -180,8 +204,21 @@ StatusCode interrupt_nvic_register_handler(uint8_t irq_channel, x86InterruptHand } StatusCode interrupt_nvic_trigger(uint8_t irq_channel) { - /* This function is for future expansion, in the situation we want to simulate NVIC interrupts */ - return STATUS_CODE_UNIMPLEMENTED; + /* Validate channel */ + + if (irq_channel >= NUM_STM32L433X_INTERRUPT_CHANNELS) { + return STATUS_CODE_INVALID_ARGS; + } + + s_nvic_handlers[irq_channel].pending = true; + + /* Add the interrupt to the signal queue + https://man7.org/linux/man-pages/man3/sigqueue.3.html */ + siginfo_t value_store; + value_store.si_value.sival_int = irq_channel; + sigqueue(s_pid, SIGRTMIN + (int)s_nvic_handlers[irq_channel].priority, value_store.si_value); + + return STATUS_CODE_OK; } StatusCode interrupt_exti_enable(GpioAddress *address, const InterruptSettings *settings) { @@ -195,6 +232,7 @@ StatusCode interrupt_exti_enable(GpioAddress *address, const InterruptSettings * s_exti_interrupts[address->pin].enabled = true; s_exti_interrupts[address->pin].masked = false; + s_exti_interrupts[address->pin].priority = settings->priority; return STATUS_CODE_OK; } @@ -214,17 +252,43 @@ StatusCode interrupt_exti_register_handler(uint8_t line, x86InterruptHandler han } StatusCode interrupt_exti_trigger(uint8_t line) { - return STATUS_CODE_UNIMPLEMENTED; + if (line > NUM_STM32L433X_EXTI_LINES) { + return STATUS_CODE_INVALID_ARGS; + } + + s_exti_interrupts[line].pending = true; + + siginfo_t value_store; + value_store.si_value.sival_int = line; + sigqueue(s_pid, SIGRTMIN + (int)s_exti_interrupts[line].priority, value_store.si_value); + + return STATUS_CODE_OK; } StatusCode interrupt_exti_get_pending(uint8_t line, uint8_t *pending_bit) { - return STATUS_CODE_UNIMPLEMENTED; + if (line > NUM_STM32L433X_EXTI_LINES) { + return STATUS_CODE_INVALID_ARGS; + } + + *pending_bit = (uint8_t)s_exti_interrupts[line].pending; + return STATUS_CODE_OK; } StatusCode interrupt_exti_clear_pending(uint8_t line) { - return STATUS_CODE_UNIMPLEMENTED; + if (line > NUM_STM32L433X_EXTI_LINES) { + return STATUS_CODE_INVALID_ARGS; + } + + s_exti_interrupts[line].pending = false; + return STATUS_CODE_OK; } StatusCode interrupt_exti_set_mask(uint8_t line, bool masked) { - return STATUS_CODE_UNIMPLEMENTED; + if (line > NUM_STM32L433X_EXTI_LINES) { + return STATUS_CODE_INVALID_ARGS; + } + + s_exti_interrupts[line].masked = masked; + + return STATUS_CODE_OK; } diff --git a/projects/bms_carrier/README.md b/projects/bms_carrier/README.md new file mode 100644 index 0000000..d45daab --- /dev/null +++ b/projects/bms_carrier/README.md @@ -0,0 +1,15 @@ + +# bms_carrier \ No newline at end of file diff --git a/projects/bms_carrier/config.json b/projects/bms_carrier/config.json new file mode 100644 index 0000000..efab08f --- /dev/null +++ b/projects/bms_carrier/config.json @@ -0,0 +1,8 @@ +{ + "libs": [ + "FreeRTOS", + "ms-common", + "master" + ], + "can": true +} \ No newline at end of file diff --git a/projects/bms_carrier/inc/bms_carrier.h b/projects/bms_carrier/inc/bms_carrier.h new file mode 100644 index 0000000..9cfe3a3 --- /dev/null +++ b/projects/bms_carrier/inc/bms_carrier.h @@ -0,0 +1,24 @@ +#pragma once + +/************************************************************************************************ + * @file bms_carrier.h + * + * @brief Header file for bms_carrier + * + * @date 2024-12-25 + * @author Midnight Sun Team #24 - MSXVI + ************************************************************************************************/ + +/* Standard library Headers */ + +/* Inter-component Headers */ + +/* Intra-component Headers */ + +/** + * @defgroup bms_carrier + * @brief bms_carrier Firmware + * @{ + */ + +/** @} */ diff --git a/projects/bms_carrier/src/main.c b/projects/bms_carrier/src/main.c new file mode 100644 index 0000000..3dc6d5a --- /dev/null +++ b/projects/bms_carrier/src/main.c @@ -0,0 +1,50 @@ +/************************************************************************************************ + * @file main.c + * + * @brief Main file for bms_carrier + * + * @date 2024-12-25 + * @author Midnight Sun Team #24 - MSXVI + ************************************************************************************************/ + +/* Standard library Headers */ + +/* Inter-component Headers */ +#include "can.h" +#include "gpio.h" +#include "log.h" +#include "master_tasks.h" +#include "mcu.h" +#include "tasks.h" + +/* Intra-component Headers */ +#include "bms_carrier.h" + +void pre_loop_init() {} + +void run_1000hz_cycle() { + run_can_rx_all(); + + run_can_tx_fast(); +} + +void run_10hz_cycle() { + run_can_tx_medium(); +} + +void run_1hz_cycle() { + run_can_tx_slow(); +} + +int main() { + mcu_init(); + tasks_init(); + log_init(); + + init_master_tasks(); + + tasks_start(); + + LOG_DEBUG("exiting main?"); + return 0; +} diff --git a/projects/bms_carrier/test/test_bms_carrier.c b/projects/bms_carrier/test/test_bms_carrier.c new file mode 100644 index 0000000..a21738a --- /dev/null +++ b/projects/bms_carrier/test/test_bms_carrier.c @@ -0,0 +1,26 @@ +/************************************************************************************************ + * @file test_bms_carrier.c + * + * @brief Test file for bms_carrier + * + * @date 2024-12-25 + * @author Midnight Sun Team #24 - MSXVI + ************************************************************************************************/ + +/* Standard library Headers */ +#include +#include + +/* Inter-component Headers */ +#include "test_helpers.h" +#include "unity.h" + +/* Intra-component Headers */ + +void setup_test(void) {} + +void teardown_test(void) {} + +void test_example(void) { + TEST_ASSERT_TRUE(true); +} diff --git a/projects/can_communication/src/main.c b/projects/can_communication/src/main.c index c834b61..1680c51 100644 --- a/projects/can_communication/src/main.c +++ b/projects/can_communication/src/main.c @@ -14,6 +14,7 @@ #include "gpio.h" #include "log.h" #include "mcu.h" +#include "system_can.h" #include "tasks.h" /* Intra-component Headers */ @@ -21,7 +22,7 @@ static CanStorage s_can_storage = { 0 }; const CanSettings can_settings = { - .device_id = 0U, + .device_id = SYSTEM_CAN_DEVICE_CAN_COMMUNICATION, .bitrate = CAN_HW_BITRATE_500KBPS, .tx = { GPIO_PORT_A, 12 }, .rx = { GPIO_PORT_A, 11 }, diff --git a/scons/build.scons b/scons/build.scons index 6d58f7f..39d5355 100644 --- a/scons/build.scons +++ b/scons/build.scons @@ -22,7 +22,7 @@ SMOKE_DIR = ROOT.Dir('smoke') LIB_BIN_DIR = BIN_DIR.Dir('libraries') LIBRARIES_INC_DIR = LIB_DIR.Dir("ms-common").Dir("inc") -AUTOGEN_DIR = LIB_DIR.Dir("autogen") +AUTOGEN_DIR = ROOT.Dir("autogen") def src(path): # return all source files within a path @@ -55,12 +55,6 @@ def get_lib_deps(entry): autogen_sources = list(Path(AUTOGEN_DIR.abspath).rglob("*")) autogen_sources += list(Path(ROOT.abspath).glob("can/boards/*")) -env.Command( - PY_DIR.File("can/message.py"), - autogen_sources, - f"python3 -m autogen python_can -o py/can" -) - env.Command( LIBRARIES_INC_DIR.File("system_can.h"), autogen_sources, @@ -73,18 +67,17 @@ def generate_can_files(env, project): output_files = [] project_name = Path(project).stem - can_template_dir = Path(str(AUTOGEN_DIR), "templates/can") + can_template_dir = Path(str(AUTOGEN_DIR), "templates/project_can") for template in can_template_dir.glob('*/*.jinja'): template_path = template.relative_to(can_template_dir) output_name = str(template_path) \ - .replace(r"{{board}}", project_name) \ + .replace(r"{{project_name}}", project_name) \ .replace(".jinja", "") output_files.append(project_can_dir.File(output_name)) - # print([f.path for f in output_files]) env.Command(output_files, autogen_sources, - f"python3 -m autogen can -o {project_can_dir.path}") + f"python3 -m autogen project_can -o {project_can_dir.path}") # Add a VariantDir that point to can folder. Create the can target specific for the project VariantDir(project_can_dir, ROOT.Dir('can'), duplicate=0) diff --git a/scons/vehicle_sim.scons b/scons/vehicle_sim.scons new file mode 100644 index 0000000..3c5b9e1 --- /dev/null +++ b/scons/vehicle_sim.scons @@ -0,0 +1,57 @@ +from scons.common import parse_config +from pathlib import Path + +Import("VARS") + +TARGET = VARS.get("TARGET") +PLATFORM = VARS.get("PLATFORM") +env = VARS.get("env") + +ROOT = Dir('#') + +BUILD_DIR = ROOT.Dir('build').Dir(PLATFORM) +BIN_DIR = BUILD_DIR.Dir('bin') +OBJ_DIR = BUILD_DIR.Dir('obj') +TEST_DIR = BUILD_DIR.Dir('test') + +AUTOGEN_DIR = ROOT.Dir("autogen") + +########################################################### +# Create appropriate targets for all projects and smoke projects +########################################################### +for entry in PROJ_DIR.glob('*') + SMOKE_DIR.glob('*'): + config = parse_config(entry) + + incs += map(ROOT.Dir, config.get("include", [])) + srcs += map(OBJ_DIR.File, config.get("sources", [])) + + lib_deps = get_lib_deps(entry) + # SCons automagically handles object creation and linking + target = env.Program( + target=BIN_DIR.File(entry.path), + source=srcs, + CPPPATH=env['CPPPATH'] + incs + lib_incs, + # link each library twice so that dependency cycles are resolved + # See: https://stackoverflow.com/questions/45135 + LIBS=env['LIBS'], + LIBPATH=[LIB_BIN_DIR], + CCFLAGS=env['CCFLAGS'] + config['cflags'], + ) + + Alias(entry.path, target) + Alias(entry.name, target) + +########################################################### +# Python +########################################################### +for entry in PY_DIR.glob("*", exclude=["*.*", "__pycache__"]): + target = env.Command(entry.path, [], + f"PYTHONPATH={PY_DIR.path} python3 {entry.path}/main.py") + config = parse_config(entry) + if config["can"]: + # Depends(target, PY_DIR.File("can/message.py")) + pass + Alias(entry.path, target) + Alias(entry.name, target) + +Default(TARGET or [e.path for e in PROJ_DIR.glob('*')]) diff --git a/simulation/.placeholder b/simulation/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/simulation/README.md b/simulation/README.md new file mode 100644 index 0000000..3db39a5 --- /dev/null +++ b/simulation/README.md @@ -0,0 +1,5 @@ +Supports: +1. JSON Manager: Logs project data in a human-readable format for easy tracking and analysis. +2. Network Time Protocol (NTP): Prepares for future expansion by offloading client simulations to external PCs for more scalable testing. +3. Project Metadata: Stores key project details such as project name, vehicle version, and project status. +4. Callback Functions: Provides application-layer support with vehicle system-specific callbacks (e.g., BMS, Power Distribution, Driver Controls) to handle various command codes. diff --git a/simulation/Simulation_JSON/README.md b/simulation/Simulation_JSON/README.md new file mode 100644 index 0000000..243846e --- /dev/null +++ b/simulation/Simulation_JSON/README.md @@ -0,0 +1 @@ +# This folder contains autogenerated JSON files \ No newline at end of file diff --git a/simulation/client/app/inc/app.h b/simulation/client/app/inc/app.h new file mode 100644 index 0000000..83dd46f --- /dev/null +++ b/simulation/client/app/inc/app.h @@ -0,0 +1,14 @@ +#ifndef APP_H +#define APP_H + +#include "gpio_manager.h" + +#ifndef USE_NETWORK_TIME_PROTOCOL + #define USE_NETWORK_TIME_PROTOCOL 0U +#endif + +#define DEFAULT_PROJECT_NAME "Default Project" + +extern GpioManager clientGpioManager; + +#endif diff --git a/simulation/client/app/inc/app_callback.h b/simulation/client/app/inc/app_callback.h new file mode 100644 index 0000000..8fe30c4 --- /dev/null +++ b/simulation/client/app/inc/app_callback.h @@ -0,0 +1,10 @@ +#ifndef APP_CALLBACK_H +#define APP_CALLBACK_H + +#include + +#include "tcp_client.h" + +void applicationMessageCallback(TCPClient *client, std::string &message); + +#endif diff --git a/simulation/client/app/inc/gpio_manager.h b/simulation/client/app/inc/gpio_manager.h new file mode 100644 index 0000000..a1abcca --- /dev/null +++ b/simulation/client/app/inc/gpio_manager.h @@ -0,0 +1,31 @@ +#ifndef GPIO_MANAGER_H +#define GPIO_MANAGER_H + +#include +#include + +#include + +#include "gpio_datagram.h" + +class GpioManager { + private: + Datagram::Gpio m_gpioDatagram; + + public: + GpioManager() = default; + + void setGpioPinState(std::string &payload); + void setGpioAllStates(std::string &payload); + + std::string processGpioPinState(std::string &payload); + std::string processGpioAllStates(); + + std::string processGpioPinMode(std::string &payload); + std::string processGpioAllModes(); + + std::string processGpioPinAltFunction(std::string &payload); + std::string processGpioAllAltFunctions(); +}; + +#endif diff --git a/simulation/client/app/inc/i2c_manager.h b/simulation/client/app/inc/i2c_manager.h new file mode 100644 index 0000000..79ba0d5 --- /dev/null +++ b/simulation/client/app/inc/i2c_manager.h @@ -0,0 +1,11 @@ +#ifndef I2C_MANAGER_H +#define I2C_MANAGER_H + +class I2CManager { + private: + + public: + I2CManager() = default; +}; + +#endif diff --git a/simulation/client/app/inc/spi_manager.h b/simulation/client/app/inc/spi_manager.h new file mode 100644 index 0000000..688ac76 --- /dev/null +++ b/simulation/client/app/inc/spi_manager.h @@ -0,0 +1,11 @@ +#ifndef SPI_MANAGER_H +#define SPI_MANAGER_H + +class SPIManager { + private: + + public: + SPIManager() = default; +}; + +#endif diff --git a/simulation/client/app/src/app_callback.cpp b/simulation/client/app/src/app_callback.cpp new file mode 100644 index 0000000..11d555a --- /dev/null +++ b/simulation/client/app/src/app_callback.cpp @@ -0,0 +1,50 @@ +#include "app_callback.h" + +#include "app.h" +#include "command_code.h" +#include "json_manager.h" + +void applicationMessageCallback(TCPClient *client, std::string &message) { + auto [commandCode, payload] = decodeCommand(message); + switch (commandCode) { + case CommandCode::METADATA: { + // TBD. Debating if server will send meta data to client? + break; + } + case CommandCode::GPIO_SET_PIN_STATE: { + clientGpioManager.setGpioPinState(payload); + break; + } + case CommandCode::GPIO_SET_ALL_STATES: { + clientGpioManager.setGpioAllStates(payload); + break; + } + case CommandCode::GPIO_GET_PIN_STATE: { + client->sendMessage(clientGpioManager.processGpioPinState(payload)); + break; + } + case CommandCode::GPIO_GET_ALL_STATES: { + client->sendMessage(clientGpioManager.processGpioAllStates()); + break; + } + case CommandCode::GPIO_GET_PIN_MODE: { + client->sendMessage(clientGpioManager.processGpioPinMode(payload)); + break; + } + case CommandCode::GPIO_GET_ALL_MODES: { + client->sendMessage(clientGpioManager.processGpioAllModes()); + break; + } + case CommandCode::GPIO_GET_PIN_ALT_FUNCTION: { + client->sendMessage(clientGpioManager.processGpioPinAltFunction(payload)); + break; + } + case CommandCode::GPIO_GET_ALL_ALT_FUNCTIONS: { + client->sendMessage(clientGpioManager.processGpioAllAltFunctions()); + break; + } + default: { + break; + } + } +} diff --git a/simulation/client/app/src/gpio_manager.cpp b/simulation/client/app/src/gpio_manager.cpp new file mode 100644 index 0000000..f4c7c16 --- /dev/null +++ b/simulation/client/app/src/gpio_manager.cpp @@ -0,0 +1,170 @@ +extern "C" { +#include "gpio.h" +} +#include +#include +#include + +#include "app.h" +#include "command_code.h" +#include "gpio_manager.h" + +void GpioManager::setGpioPinState(std::string &payload) { + m_gpioDatagram.deserialize(payload); + + const uint8_t *receivedData = m_gpioDatagram.getBuffer(); + GpioAddress pinAddress = {.port = static_cast(m_gpioDatagram.getGpioPort()), .pin = m_gpioDatagram.getGpioPin()}; + + gpio_set_state(&pinAddress, static_cast(receivedData[0U])); +} + +void GpioManager::setGpioAllStates(std::string &payload) { + m_gpioDatagram.deserialize(payload); + + const uint8_t *receivedData = m_gpioDatagram.getBuffer(); + + for (uint8_t i = 0U; i < static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) * Datagram::Gpio::PINS_PER_PORT; i++) { + GpioAddress pinAddress = {.port = static_cast(i / 16U), .pin = i % 16U}; + gpio_set_state(&pinAddress, static_cast(receivedData[0U])); + } +} + +std::string GpioManager::processGpioPinState(std::string &payload) { + m_gpioDatagram.deserialize(payload); + + GpioAddress pinAddress = {.port = static_cast(m_gpioDatagram.getGpioPort()), .pin = m_gpioDatagram.getGpioPin()}; + uint8_t pinState = static_cast(gpio_peek_state(&pinAddress)); + + m_gpioDatagram.clearBuffer(); + m_gpioDatagram.setBuffer(&pinState, sizeof(pinState)); + + return m_gpioDatagram.serialize(CommandCode::GPIO_GET_PIN_STATE); +} + +std::string GpioManager::processGpioAllStates() { + std::vector gpioStateBitsetArray; + + /* Round up the number of blocks by adding the divisor - 1 */ + constexpr uint8_t numBlocks = ((Datagram::Gpio::PINS_PER_PORT * static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS)) + 32U - 1U) / 32U; + gpioStateBitsetArray.resize(numBlocks, 0U); + + /* Simulation only supports reading port A and B */ + for (uint8_t i = 0U; i < static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) * Datagram::Gpio::PINS_PER_PORT; i++) { + size_t blockIndex = i / 32U; + size_t bitPosition = i % 32U; + GpioAddress pinAddress = {.port = static_cast(i / 16U), .pin = i % 16U}; + GpioState pinState = gpio_peek_state(&pinAddress); + gpioStateBitsetArray[blockIndex] |= (static_cast(pinState << bitPosition)); + } + m_gpioDatagram.setGpioPort(Datagram::Gpio::Port::NUM_GPIO_PORTS); + m_gpioDatagram.setGpioPin(Datagram::Gpio::PINS_PER_PORT); + + std::vector byteArray; + for (uint32_t value : gpioStateBitsetArray) { + byteArray.push_back(static_cast(value & 0xFF)); + byteArray.push_back(static_cast((value >> 8) & 0xFF)); + byteArray.push_back(static_cast((value >> 16) & 0xFF)); + byteArray.push_back(static_cast((value >> 24) & 0xFF)); + } + + m_gpioDatagram.clearBuffer(); + m_gpioDatagram.setBuffer(byteArray.data(), byteArray.size()); + + return m_gpioDatagram.serialize(CommandCode::GPIO_GET_ALL_STATES); +} + +std::string GpioManager::processGpioPinMode(std::string &payload) { + m_gpioDatagram.deserialize(payload); + + GpioAddress pinAddress = {.port = static_cast(m_gpioDatagram.getGpioPort()), .pin = m_gpioDatagram.getGpioPin()}; + uint8_t pinMode = static_cast(gpio_peek_mode(&pinAddress)); + + m_gpioDatagram.clearBuffer(); + m_gpioDatagram.setBuffer(&pinMode, sizeof(pinMode)); + + return m_gpioDatagram.serialize(CommandCode::GPIO_GET_PIN_MODE); +} + +std::string GpioManager::processGpioAllModes() { + std::vector gpioStateBitsetArray; + + /* Calculate number of 32-bit blocks needed (8 pins per 32-bit block with 4 bits per pin) */ + constexpr uint8_t numBlocks = (Datagram::Gpio::PINS_PER_PORT * static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) + 8U - 1U) / 8U; + gpioStateBitsetArray.resize(numBlocks, 0U); + + /* Simulation only supports reading port A and B */ + for (uint8_t i = 0U; i < static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) * Datagram::Gpio::PINS_PER_PORT; i++) { + size_t blockIndex = (i / 8U); /* 4 bits per pin so there is only 8 pins per block */ + size_t bitPosition = (i % 8U) * 4U; /* Multiply by 4 to account for 4 bits per pin */ + + GpioAddress pinAddress = {.port = static_cast(i / 16U), .pin = i % 16U}; + GpioMode pinMode = gpio_peek_mode(&pinAddress); + + gpioStateBitsetArray[blockIndex] &= ~(0xFU << bitPosition); + gpioStateBitsetArray[blockIndex] |= (static_cast(pinMode) << bitPosition); + } + + m_gpioDatagram.setGpioPort(Datagram::Gpio::Port::NUM_GPIO_PORTS); + m_gpioDatagram.setGpioPin(Datagram::Gpio::PINS_PER_PORT); + + std::vector byteArray; + for (uint32_t value : gpioStateBitsetArray) { + byteArray.push_back(static_cast(value & 0xFF)); + byteArray.push_back(static_cast((value >> 8) & 0xFF)); + byteArray.push_back(static_cast((value >> 16) & 0xFF)); + byteArray.push_back(static_cast((value >> 24) & 0xFF)); + } + + m_gpioDatagram.clearBuffer(); + m_gpioDatagram.setBuffer(byteArray.data(), byteArray.size()); + + return m_gpioDatagram.serialize(CommandCode::GPIO_GET_ALL_MODES); +} + +std::string GpioManager::processGpioPinAltFunction(std::string &payload) { + m_gpioDatagram.deserialize(payload); + + GpioAddress pinAddress = {.port = static_cast(m_gpioDatagram.getGpioPort()), .pin = m_gpioDatagram.getGpioPin()}; + uint8_t pinAltFunction = static_cast(gpio_peek_alt_function(&pinAddress)); + + m_gpioDatagram.clearBuffer(); + m_gpioDatagram.setBuffer(&pinAltFunction, sizeof(pinAltFunction)); + + return m_gpioDatagram.serialize(CommandCode::GPIO_GET_PIN_ALT_FUNCTION); +} + +std::string GpioManager::processGpioAllAltFunctions() { + std::vector gpioAltFunctionBitsetArray; + + /* Calculate number of 32-bit blocks needed (8 pins per 32-bit block with 4 bits per pin) */ + constexpr uint8_t numBlocks = (Datagram::Gpio::PINS_PER_PORT * static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) + 8U - 1U) / 8U; + gpioAltFunctionBitsetArray.resize(numBlocks, 0U); + + /* Simulation only supports reading port A and B */ + for (uint8_t i = 0U; i < static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) * Datagram::Gpio::PINS_PER_PORT; i++) { + size_t blockIndex = (i / 8U); /* 4 bits per pin so there is only 8 pins per block */ + size_t bitPosition = (i % 8U) * 4U; /* Multiply by 4 to account for 4 bits per pin */ + + GpioAddress pinAddress = {.port = static_cast(i / 16U), .pin = i % 16U}; + GpioAlternateFunctions pinFunction = gpio_peek_alt_function(&pinAddress); + + gpioAltFunctionBitsetArray[blockIndex] &= ~(0xFU << bitPosition); + gpioAltFunctionBitsetArray[blockIndex] |= (static_cast(pinFunction) << bitPosition); + } + + m_gpioDatagram.setGpioPort(Datagram::Gpio::Port::NUM_GPIO_PORTS); + m_gpioDatagram.setGpioPin(Datagram::Gpio::PINS_PER_PORT); + + std::vector byteArray; + for (uint32_t value : gpioAltFunctionBitsetArray) { + byteArray.push_back(static_cast(value & 0xFF)); + byteArray.push_back(static_cast((value >> 8) & 0xFF)); + byteArray.push_back(static_cast((value >> 16) & 0xFF)); + byteArray.push_back(static_cast((value >> 24) & 0xFF)); + } + + m_gpioDatagram.clearBuffer(); + m_gpioDatagram.setBuffer(byteArray.data(), byteArray.size()); + + return m_gpioDatagram.serialize(CommandCode::GPIO_GET_ALL_ALT_FUNCTIONS); +} diff --git a/simulation/client/app/src/i2c_manager.cpp b/simulation/client/app/src/i2c_manager.cpp new file mode 100644 index 0000000..7829c87 --- /dev/null +++ b/simulation/client/app/src/i2c_manager.cpp @@ -0,0 +1,2 @@ + +#include "i2c_manager.h" diff --git a/simulation/client/app/src/main.cpp b/simulation/client/app/src/main.cpp new file mode 100644 index 0000000..1903aca --- /dev/null +++ b/simulation/client/app/src/main.cpp @@ -0,0 +1,36 @@ +#include + +#include "app.h" +#include "app_callback.h" +#include "gpio_manager.h" +#include "metadata.h" +#include "ntp_client.h" +#include "tcp_client.h" + +GpioManager clientGpioManager; +std::string projectName = DEFAULT_PROJECT_NAME; + +void connectCallback(TCPClient *client) { + Datagram::Metadata::Payload initialData = {.projectName = projectName, .projectStatus = "RUNNING", .hardwareModel = "MS16.0.0"}; + + Datagram::Metadata projectMetadata(initialData); + + client->sendMessage(projectMetadata.serialize()); +} + +int main(int argc, char **argv) { + std::cout << "Running Client" << std::endl; + + TCPClient serverClient("127.0.0.1", 1024, applicationMessageCallback, connectCallback); + serverClient.connectServer(); + +#if USE_NETWORK_TIME_PROTOCOL == 1U + NTPClient ntpClient; + ntpClient.startSynchronization("127.0.0.1"); +#endif + + int n; + std::cin >> n; + + return 0; +} diff --git a/simulation/client/app/src/spi_manager.cpp b/simulation/client/app/src/spi_manager.cpp new file mode 100644 index 0000000..ab1dd77 --- /dev/null +++ b/simulation/client/app/src/spi_manager.cpp @@ -0,0 +1,2 @@ + +#include "spi_manager.h" diff --git a/simulation/client/utils/inc/ntp_client.h b/simulation/client/utils/inc/ntp_client.h new file mode 100644 index 0000000..79b6e22 --- /dev/null +++ b/simulation/client/utils/inc/ntp_client.h @@ -0,0 +1,34 @@ +#ifndef NTP_CLIENT_H +#define NTP_CLIENT_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "network_time_protocol.h" + +#define NTP_SYNC_PERIOD_S 60 + +class NTPClient { + private: + static constexpr int NTP_PORT = 123; + int m_NTPSocket; + std::string m_serverAddress; + NTPPacket m_serverResponse; + pthread_t m_NTPClientThreadId; + std::atomic m_isSynchronizing; + + public: + NTPClient(); + void NTPClientProcedure(); + void startSynchronization(const std::string &serverAddress); +}; + +#endif diff --git a/simulation/client/utils/inc/tcp_client.h b/simulation/client/utils/inc/tcp_client.h new file mode 100644 index 0000000..9d12a38 --- /dev/null +++ b/simulation/client/utils/inc/tcp_client.h @@ -0,0 +1,56 @@ +#ifndef TCP_CLIENT_H +#define TCP_CLIENT_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class TCPClient { + private: + static constexpr size_t MAX_BUFFER_SIZE = 256; + using messageCallback = std::function; + using connectCallback = std::function; + int m_clientSocket; + std::string m_host; + int m_port; + struct sockaddr_in m_serverAddress; + messageCallback m_messageCallback; + connectCallback m_connectCallback; + std::queue m_messageQueue; + + pthread_t m_receiverThreadId; + pthread_t m_processMessageThreadId; + pthread_mutex_t m_mutex; + sem_t m_messageSemaphore; + + std::atomic m_isRunning{false}; + std::atomic m_isConnected{false}; + + bool waitForSocket(int timeout_ms, bool read); + + public: + TCPClient(const std::string &host, int port, messageCallback messageCallback, connectCallback connectCallback); + ~TCPClient(); + + void connectServer(); + void disconnectServer(); + bool isConnected() const; + + void receiverProcedure(); + void processMessages(); + + void sendMessage(const std::string &message); + std::string receiveMessage(); +}; + +#endif diff --git a/simulation/client/utils/src/ntp_client.cpp b/simulation/client/utils/src/ntp_client.cpp new file mode 100644 index 0000000..136b8cc --- /dev/null +++ b/simulation/client/utils/src/ntp_client.cpp @@ -0,0 +1,92 @@ +#include "ntp_client.h" + +#include +#include + +#include "thread_helpers.h" + +void NTPClient::NTPClientProcedure() { + int ntpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (ntpSocket < 0) { + close(ntpSocket); + throw std::runtime_error("Error creating socket"); + } + + struct sockaddr_in serverAddr; + memset((char *)&serverAddr, 0U, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(NTP_PORT); + + if (inet_pton(AF_INET, m_serverAddress.c_str(), &serverAddr.sin_addr) <= 0) { + close(ntpSocket); + throw std::runtime_error("Invalid server address"); + } + + m_isSynchronizing = true; + std::cerr << "Connected NTP Server" << std::endl; + + while (m_isSynchronizing) { + NTPPacket request = {}; + request.flags = (NTP_VERSION << NTP_VERSION_OFFSET) | (NTPLeapIndicator::NTP_LI_NOSYNC << NTP_LEAP_INDICATOR_OFFSET) | + (NTPMode::NTP_CLIENT_MODE << NTP_MODE_OFFSET); + + /* Convert UNIX time in seconds to NTP time */ + request.transmitTime.seconds = htonl(unixToNTPTime(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()))); + + /* Obtain the fractional time */ + request.transmitTime.fraction = htonl(static_cast( + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count() % 1000000)); + + if (sendto(ntpSocket, &request, sizeof(request), 0, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) <= 0) { + close(ntpSocket); + throw std::runtime_error("Error sending NTP request"); + } + + NTPPacket serverResponse = {}; + if (recvfrom(ntpSocket, &serverResponse, sizeof(serverResponse), 0, nullptr, nullptr) <= 0) { + close(ntpSocket); + throw std::runtime_error("Error receiving NTP response"); + } + + convertNTPTimestamp(serverResponse.referenceTime); + convertNTPTimestamp(serverResponse.originTime); + convertNTPTimestamp(serverResponse.receiveTime); + convertNTPTimestamp(serverResponse.transmitTime); + + serverResponse.rootDelay = ntohl(serverResponse.rootDelay); + serverResponse.rootDispersion = ntohl(serverResponse.rootDispersion); + + m_serverResponse = serverResponse; + + dumpNTPPacketData(m_serverResponse); + thread_sleep_s(1); + } + + m_isSynchronizing = false; + close(ntpSocket); +} + +void *NTPClientWrapper(void *param) { + NTPClient *client = static_cast(param); + + try { + client->NTPClientProcedure(); + } catch (std::exception &e) { + std::cerr << "NTP Client Thread Error: " << e.what() << std::endl; + } + + return nullptr; +} + +void NTPClient::startSynchronization(const std::string &serverAddress) { + m_serverAddress = serverAddress; + + if (pthread_create(&m_NTPClientThreadId, nullptr, NTPClientWrapper, this)) { + throw std::runtime_error("Listen Error"); + } +} + +NTPClient::NTPClient() { + m_isSynchronizing = false; + m_NTPSocket = -1; +} diff --git a/simulation/client/utils/src/tcp_client.cpp b/simulation/client/utils/src/tcp_client.cpp new file mode 100644 index 0000000..152513c --- /dev/null +++ b/simulation/client/utils/src/tcp_client.cpp @@ -0,0 +1,180 @@ +#include "tcp_client.h" + +#include +#include + +#define MAX_PACKET_SIZE 256U + +bool TCPClient::waitForSocket(int timeout_ms, bool read) { + struct pollfd fds[1]; + fds[0].fd = m_clientSocket; + fds[0].events = read ? POLLIN : POLLOUT; + + int result = poll(fds, 1, timeout_ms); + + if (result < 0) { + return false; + } + + return result > 0; +} + +void TCPClient::processMessages() { + while (m_isRunning) { + /* Wait for new data */ + sem_wait(&m_messageSemaphore); + + pthread_mutex_lock(&m_mutex); + + if (!m_messageQueue.empty()) { + auto message = m_messageQueue.front(); + m_messageQueue.pop(); + + if (m_messageCallback) { + m_messageCallback(this, message); + } + pthread_mutex_unlock(&m_mutex); + } + } +} + +void TCPClient::receiverProcedure() { + while (m_isRunning) { + /* wait for socket to be notified */ + if (!waitForSocket(1000, true)) { + continue; + } + + std::string message(MAX_PACKET_SIZE, '\0'); + + size_t bytesRead = read(m_clientSocket, &message[0], sizeof(message)); + + if (bytesRead <= 0) { + disconnectServer(); + throw std::runtime_error("Connection lost"); + } + + message[bytesRead] = '\0'; + + pthread_mutex_lock(&m_mutex); + m_messageQueue.push(message); + pthread_mutex_unlock(&m_mutex); + + /* Signal that there is new data */ + sem_post(&m_messageSemaphore); + } +} + +void *processMessagesThread(void *param) { + TCPClient *client = static_cast(param); + + try { + client->processMessages(); + } catch (...) { + std::cerr << "Process Messages Thread Error" << std::endl; + } + + return nullptr; +} + +void *receiverThreadProcedure(void *param) { + TCPClient *client = static_cast(param); + + try { + client->receiverProcedure(); + } catch (...) { + std::cerr << "Receiver Thread Error" << std::endl; + } + + return nullptr; +} + +void TCPClient::connectServer() { + m_clientSocket = socket(AF_INET, SOCK_STREAM, 0); + + if (m_clientSocket < 0) + throw std::runtime_error("Error created socket"); + + m_serverAddress.sin_family = AF_INET; + m_serverAddress.sin_port = htons(m_port); + + if (inet_pton(AF_INET, m_host.c_str(), &m_serverAddress.sin_addr) <= 0) { + close(m_clientSocket); + throw std::runtime_error("Error converting IPv4 host address to binary form"); + } + + if (connect(m_clientSocket, (struct sockaddr *)&m_serverAddress, sizeof(m_serverAddress)) < 0) { + close(m_clientSocket); + throw std::runtime_error("Error connecting socket"); + } + + if (!waitForSocket(5000, false)) { + close(m_clientSocket); + throw std::runtime_error("Connection timeout"); + } + + m_isRunning = true; + m_isConnected = true; + + if (m_connectCallback) { + m_connectCallback(this); + } + + if (pthread_create(&m_receiverThreadId, NULL, receiverThreadProcedure, this)) { + close(m_clientSocket); + throw std::runtime_error("Failed to create receiver thread"); + } + + if (pthread_create(&m_processMessageThreadId, NULL, processMessagesThread, this)) { + close(m_clientSocket); + throw std::runtime_error("Failed to create process messages thread"); + } + + std::cout << "Connected :-)" << std::endl; +} + +void TCPClient::disconnectServer() { + close(m_clientSocket); + m_clientSocket = 0; +} + +void TCPClient::sendMessage(const std::string &message) { + int n = send(m_clientSocket, message.c_str(), message.size(), 0); + if (n < 0) + throw std::runtime_error("Error sending message"); +} + +std::string TCPClient::receiveMessage() { + std::string message(MAX_PACKET_SIZE, '\0'); + + int n = read(m_clientSocket, &message[0], MAX_PACKET_SIZE); + message[n] = '\0'; + + return message; +} + +bool TCPClient::isConnected() const { + return m_isConnected; +} + +TCPClient::TCPClient(const std::string &host, int port, messageCallback messageCallback, connectCallback connectCallback) { + this->m_host = host; + this->m_port = port; + this->m_clientSocket = -1; + this->m_messageCallback = messageCallback; + this->m_connectCallback = connectCallback; + + if (pthread_mutex_init(&m_mutex, NULL) != 0) { + throw std::runtime_error("Error initializing mutex"); + } + + if (sem_init(&m_messageSemaphore, 0, 0) != 0) { + throw std::runtime_error("Error initializing semaphore"); + } +} + +TCPClient::~TCPClient() { + disconnectServer(); + pthread_mutex_destroy(&m_mutex); + sem_destroy(&m_messageSemaphore); +} diff --git a/simulation/command.md b/simulation/command.md new file mode 100644 index 0000000..0597baf --- /dev/null +++ b/simulation/command.md @@ -0,0 +1,17 @@ +### Gpio Commands +1. GPIO GET_PIN_STATE [PORT][PIN] + - Example: GPIO GET_PIN_STATE A12 +2. GPIO GET_ALL_STATES + - Example: GPIO GET_ALL_STATES +3. GPIO GET_PIN_MODE [PORT][PIN] + - Example: GPIO GET_PIN_MODE A12 +4. GPIO GET_ALL_MODES + - Example: GPIO GET_ALL_MODES +5. GPIO GET_PIN_ALT_FUNCTION [PORT][PIN] + - Example: GPIO GET_PIN_ALT_FUNCTION A12 +6. GPIO GET_ALL_ALT_FUNCTIONS + - Example: GPIO GET_ALL_ALT_FUNCTIONS +7. GPIO SET_PIN_STATE [PORT][PIN] [STATE] + - Example: GPIO SET_PIN_STATE A12 HIGH +8. GPIO SET_ALL_STATES [STATE] + - Example: GPIO SET_ALL_STATES HIGH diff --git a/simulation/common/inc/command_code.h b/simulation/common/inc/command_code.h new file mode 100644 index 0000000..befbc1e --- /dev/null +++ b/simulation/common/inc/command_code.h @@ -0,0 +1,34 @@ +#ifndef COMMAND_CODE_H +#define COMMAND_CODE_H + +#include + +enum class CommandCode { + /* MISC Commands */ + METADATA, + + /* GPIO Commands */ + GPIO_SET_PIN_STATE, + GPIO_SET_ALL_STATES, + GPIO_GET_PIN_STATE, + GPIO_GET_ALL_STATES, + GPIO_GET_PIN_MODE, + GPIO_GET_ALL_MODES, + GPIO_GET_PIN_ALT_FUNCTION, + GPIO_GET_ALL_ALT_FUNCTIONS, + + /* I2C Commands */ + + /* SPI Commands */ + + /* UART Commands */ + + /* FLASH Commands */ + + NUM_COMMAND_CODES +}; + +std::string encodeCommand(const CommandCode commandCode, std::string &message); +std::pair decodeCommand(std::string &message); + +#endif diff --git a/simulation/common/inc/gpio_datagram.h b/simulation/common/inc/gpio_datagram.h new file mode 100644 index 0000000..244013b --- /dev/null +++ b/simulation/common/inc/gpio_datagram.h @@ -0,0 +1,100 @@ +#ifndef GPIO_DATAGRAM_H +#define GPIO_DATAGRAM_H + +#include +#include + +#include "command_code.h" + +namespace Datagram { +class Gpio { + public: + static const constexpr uint8_t PINS_PER_PORT = 16U; + + enum class Port { GPIO_PORT_A = 0, GPIO_PORT_B, NUM_GPIO_PORTS }; + + enum class State { + GPIO_STATE_LOW = 0, + GPIO_STATE_HIGH, + }; + + enum class Mode { + GPIO_ANALOG = 0, + GPIO_INPUT_FLOATING, + GPIO_INPUT_PULL_DOWN, + GPIO_INPUT_PULL_UP, + GPIO_OUTPUT_OPEN_DRAIN, + GPIO_OUTPUT_PUSH_PULL, + GPIO_ALFTN_OPEN_DRAIN, + GPIO_ALTFN_PUSH_PULL, + NUM_GPIO_MODES, + }; + + enum class AltFunction { + // No ALT function + GPIO_ALT_NONE = 0x00U, + + // GPIO_ALT0 - System + GPIO_ALT0_SWDIO = 0x00U, + GPIO_ALT0_SWCLK = 0x00U, + + // GPIO_ALT1 - TIM1/TIM2 + GPIO_ALT1_TIM1 = 0x01U, + GPIO_ALT1_TIM2 = 0x01U, + + // GPIO_ALT4 - I2C + GPIO_ALT4_I2C1 = 0x04U, + GPIO_ALT4_I2C2 = 0x04U, + GPIO_ALT4_I2C3 = 0x04U, + + // GPIO_ALT5 - SPI + GPIO_ALT5_SPI1 = 0x05U, + GPIO_ALT5_SPI2 = 0x05U, + + // GPIO_ALT6 - SPI3 + GPIO_ALT6_SPI3 = 0x06U, + + // GPIO_ALT7 - USART + GPIO_ALT7_USART1 = 0x07U, + GPIO_ALT7_USART2 = 0x07U, + GPIO_ALT7_USART3 = 0x07U, + + // GPIO_ALT9 - CAN1 + GPIO_ALT9_CAN1 = 0x09U, + + // GPIO_ALT14 - Timers + GPIO_ALT14_TIM15 = 0x0EU, + GPIO_ALT14_TIM16 = 0x0EU, + }; + + static constexpr size_t GPIO_MAX_BUFFER_SIZE = PINS_PER_PORT * static_cast(Port::NUM_GPIO_PORTS); + + struct Payload { + Port gpioPort; + uint8_t gpioPin; + uint8_t bufferLength; + uint8_t buffer[GPIO_MAX_BUFFER_SIZE]; + }; + + explicit Gpio(Payload &data); + Gpio() = default; + + std::string serialize(const CommandCode &commandCode) const; + void deserialize(std::string &gpioDatagramPayload); + + void setGpioPort(const Port &gpioPort); + void setGpioPin(const uint8_t &gpioPin); + void setBuffer(const uint8_t *data, uint8_t length); + void clearBuffer(); + + Port getGpioPort() const; + uint8_t getGpioPin() const; + uint8_t getBufferLength() const; + const uint8_t *getBuffer() const; + + private: + Payload m_gpioDatagram; +}; + +} // namespace Datagram +#endif diff --git a/simulation/common/inc/i2c_datagram.h b/simulation/common/inc/i2c_datagram.h new file mode 100644 index 0000000..5a1cfb8 --- /dev/null +++ b/simulation/common/inc/i2c_datagram.h @@ -0,0 +1,41 @@ +#ifndef I2C_DATAGRAM_H +#define I2C_DATAGRAM_H + +#include + +#include "command_code.h" + +namespace Datagram { +class I2C { + public: + static constexpr size_t MAX_BUFFER_SIZE = 256; + + enum class Port { I2C_PORT_1, I2C_PORT_2, NUM_I2C_PORTS }; + + struct Payload { + Port i2cPort; + size_t bufferLength; + uint8_t buffer[MAX_BUFFER_SIZE]; + }; + + explicit I2C(Payload &data); + I2C() = default; + + std::string serialize(const CommandCode &commandCode) const; + void deserialize(std::string &i2cDatagramPayload); + + void setI2CPort(const Port &i2cPort); + void setBuffer(const uint8_t *data, size_t length); + + void clearBuffer(); + + Port getI2CPort() const; + size_t getBufferLength() const; + const uint8_t *getBuffer() const; + + private: + Payload m_i2cDatagram; +}; + +} // namespace Datagram +#endif diff --git a/simulation/common/inc/json_manager.h b/simulation/common/inc/json_manager.h new file mode 100644 index 0000000..7062943 --- /dev/null +++ b/simulation/common/inc/json_manager.h @@ -0,0 +1,105 @@ +#ifndef JSON_MANAGER_H +#define JSON_MANAGER_H + +#include +#include +#include +#include +#include +#include + +#define PROJECT_VERSION "1.0.0" + +class JSONManager { + private: + static constexpr const char *DEFAULT_JSON_PATH = "./Simulation_JSON/"; + std::filesystem::path m_projectBasePath; + + void createDefaultProjectJSON(const std::string &projectName); + std::filesystem::path getProjectFilePath(const std::string &projectName); + + nlohmann::json loadProjectJSON(const std::string &projectName); + void saveProjectJSON(const std::string &projectName, const nlohmann::json &projectData); + + public: + JSONManager(); + + bool projectExists(const std::string &projectName); + void deleteProject(const std::string &projectName); + + template + void setProjectValue(const std::string &projectName, const std::string &key, T value) { + try { + nlohmann::json projectJSON = loadProjectJSON(projectName); + + projectJSON[key] = value; + + saveProjectJSON(projectName, projectJSON); + } catch (const std::exception &e) { + std::cerr << "Error setting project value: " << e.what() << std::endl; + } + } + + template + void setProjectNestedValue(const std::string &projectName, const std::vector &keyPath, const T &value) { + try { + nlohmann::json projectJSON = loadProjectJSON(projectName); + + nlohmann::json *current = &projectJSON; + + /* Navigate to the desired key location */ + for (size_t i = 0; i < keyPath.size() - 1; ++i) { + if (!current->contains(keyPath[i])) { + (*current)[keyPath[i]] = nlohmann::json::object(); + } + /* Update the JSON pointer to the nested JSON */ + current = &((*current)[keyPath[i]]); + } + + (*current)[keyPath.back()] = value; + + saveProjectJSON(projectName, projectJSON); + } catch (const std::exception &e) { + std::cerr << "Error setting nested project value: " << e.what() << std::endl; + } + } + + template + T getProjectValue(const std::string &projectName, const std::string &key) { + try { + nlohmann::json projectJSON = loadProjectJSON(projectName); + + if (projectJSON.contains(key)) { + return projectJSON[key].get(); + } + + } catch (const std::exception &e) { + std::cerr << "Error getting project value: " << e.what() << std::endl; + } + return static_cast(0U); + } + + template + T getProjectNestedValue(const std::string &projectName, const std::vector &keyPath, const T &defaultValue = T()) { + try { + nlohmann::json projectJSON = loadProjectJSON(projectName); + + const nlohmann::json *current = &projectJSON; + /* Navigate to the desired key location */ + for (const auto &key : keyPath) { + if (!current->contains(key)) { + return defaultValue; + } + /* Update the JSON pointer to the nested JSON */ + current = &((*current)[key]); + } + + return current->get(); + } catch (const std::exception &e) { + std::cerr << "Error getting nested project value: " << e.what() << std::endl; + return defaultValue; + } + } +}; + +#endif diff --git a/simulation/common/inc/metadata.h b/simulation/common/inc/metadata.h new file mode 100644 index 0000000..45df44d --- /dev/null +++ b/simulation/common/inc/metadata.h @@ -0,0 +1,34 @@ +#ifndef METADATA_H +#define METADATA_H + +#include +#include + +namespace Datagram { +class Metadata { + public: + struct Payload { + std::string projectName; + std::string projectStatus; + std::string hardwareModel; + }; + explicit Metadata(Payload &data); + Metadata() = default; + + std::string serialize() const; + void deserialize(std::string &metadataPayload); + + void setProjectName(const std::string &projectName); + void setProjectStatus(const std::string &projectStatus); + void setHardwareModel(const std::string &hardwareModel); + + std::string getProjectName() const; + std::string getProjectStatus() const; + std::string getHardwareModel() const; + + private: + Payload m_metadata; +}; + +} // namespace Datagram +#endif diff --git a/simulation/common/inc/network_time_protocol.h b/simulation/common/inc/network_time_protocol.h new file mode 100644 index 0000000..dd3a54f --- /dev/null +++ b/simulation/common/inc/network_time_protocol.h @@ -0,0 +1,55 @@ +#ifndef NETWORK_TIME_PROTOCOL_H +#define NETWORK_TIME_PROTOCOL_H + +#include +#include + +#define NTP_VERSION 4U + +#define NTP_LEAP_INDICATOR_OFFSET 6U +#define NTP_VERSION_OFFSET 3U +#define NTP_MODE_OFFSET 0U + +#define NTP_POLL_TO_SECONDS(poll) (1U << poll) + +static constexpr uint32_t NTP_UNIX_EPOCH_DIFF = 2208988800UL; + +struct NTPTime { + uint32_t seconds; + uint32_t fraction; +}; + +struct NTPPacket { + uint8_t flags; + uint8_t stratum; + uint8_t poll; + uint8_t precision; + uint32_t rootDelay; + uint32_t rootDispersion; + uint8_t referenceId[4]; + NTPTime referenceTime; + NTPTime originTime; + NTPTime receiveTime; + NTPTime transmitTime; +}; + +enum NTPMode { + NTP_RESERVED_MODE, + NTP_ACTIVE_MODE, + NTP_PASSIVE_MODE, + NTP_CLIENT_MODE, + NTP_SERVER_MODE, + NTP_BROADCAST_MODE, + NTP_CONTROL_MODE, + NUM_NTP_MODES +}; + +enum NTPLeapIndicator { NTP_LI_NONE, NTP_LI_LAST_MINUTE_OF_THE_DAY_61_S, NTP_LI_LAST_MINUTE_OF_THE_DAY_59_S, NTP_LI_NOSYNC }; + +time_t ntpToUnixTime(NTPTime ntpTime); +uint32_t unixToNTPTime(time_t unixTime); + +void convertNTPTimestamp(NTPTime ×tamp); +void dumpNTPPacketData(const NTPPacket packet); + +#endif diff --git a/simulation/common/inc/serialization.h b/simulation/common/inc/serialization.h new file mode 100644 index 0000000..e31bd10 --- /dev/null +++ b/simulation/common/inc/serialization.h @@ -0,0 +1,40 @@ +#ifndef SERIALIZATION +#define SERIALIZATION + +#include +#include + +/** + * @brief Serialize an integer value + * @details The integer will be converted to a string of size T. + * Size T is dependent on the type of integer. + * The stringified-integer is appended to the target. + * @param target + * @param value + */ +template +void serializeInteger(std::string &target, T value) { + target.append(reinterpret_cast(&value), sizeof(T)); +} + +/** + * @brief Serialize a string value + * @details The string will be pre-fixed with a 16-bit size to indicate the + * size. The string is appended to the target. The final packet follows as such: + * | target | 16-bit length | str | + * @param target + * @param str + */ +void serializeString(std::string &target, const std::string &str); + +template +T deserializeInteger(const std::string &source, size_t &offset) { + T value; + std::memcpy(&value, source.data() + offset, sizeof(T)); + offset += sizeof(T); + return value; +} + +std::string deserializeString(std::string &source, size_t &offset); + +#endif diff --git a/simulation/common/inc/spi_datagram.h b/simulation/common/inc/spi_datagram.h new file mode 100644 index 0000000..a230652 --- /dev/null +++ b/simulation/common/inc/spi_datagram.h @@ -0,0 +1,41 @@ +#ifndef SPI_DATAGRAM_H +#define SPI_DATAGRAM_H + +#include + +#include "command_code.h" + +namespace Datagram { +class SPI { + public: + static constexpr size_t SPI_MAX_BUFFER_SIZE = 256; + + enum class Port { SPI_PORT_1, SPI_PORT_2, NUM_SPI_PORTS }; + + struct Payload { + Port spiPort; + size_t bufferLength; + uint8_t buffer[SPI_MAX_BUFFER_SIZE]; + }; + + explicit SPI(Payload &data); + SPI() = default; + + std::string serialize(const CommandCode &commandCode) const; + void deserialize(std::string &spiDatagramPayload); + + void setSPIPort(const Port &spiPort); + void setBuffer(const uint8_t *data, size_t length); + + void clearBuffer(); + + Port getSPIPort() const; + size_t getBufferLength() const; + const uint8_t *getBuffer() const; + + private: + Payload m_spiDatagram; +}; + +} // namespace Datagram +#endif diff --git a/simulation/common/inc/thread_helpers.h b/simulation/common/inc/thread_helpers.h new file mode 100644 index 0000000..6979293 --- /dev/null +++ b/simulation/common/inc/thread_helpers.h @@ -0,0 +1,7 @@ +#ifndef THREAD_HELPERS_H +#define THREAD_HELPERS_H + +void thread_sleep_s(unsigned int seconds); +void thread_sleep_ms(unsigned int milliseconds); + +#endif diff --git a/simulation/common/src/command_code.cpp b/simulation/common/src/command_code.cpp new file mode 100644 index 0000000..b6af8f3 --- /dev/null +++ b/simulation/common/src/command_code.cpp @@ -0,0 +1,27 @@ +#include "command_code.h" + +#include + +std::string encodeCommand(const CommandCode commandCode, std::string &message) { + return std::to_string(static_cast(commandCode)) + '|' + message; +} + +std::pair decodeCommand(std::string &message) { + size_t delimPosition = message.find('|'); + + if (delimPosition == std::string::npos) { + throw std::runtime_error("Invalid command format"); + } + + uint8_t commandCodeValue = std::stoi(message.substr(0, delimPosition)); + + if (commandCodeValue > static_cast(CommandCode::NUM_COMMAND_CODES)) { + throw std::runtime_error("CommandCode out of valid range"); + } + + CommandCode commandCode = static_cast(commandCodeValue); + + std::string payload = message.substr(delimPosition + 1); + + return {commandCode, payload}; +} diff --git a/simulation/common/src/gpio_datagram.cpp b/simulation/common/src/gpio_datagram.cpp new file mode 100644 index 0000000..2e33299 --- /dev/null +++ b/simulation/common/src/gpio_datagram.cpp @@ -0,0 +1,79 @@ +#include "gpio_datagram.h" + +#include +#include +#include + +#include "serialization.h" + +namespace Datagram { +std::string Gpio::serialize(const CommandCode &commandCode) const { + std::string serializedData; + + serializeInteger(serializedData, static_cast(m_gpioDatagram.gpioPort)); + serializeInteger(serializedData, static_cast(m_gpioDatagram.gpioPin)); + + if (m_gpioDatagram.bufferLength > GPIO_MAX_BUFFER_SIZE) { + throw std::runtime_error("Serialized Gpio buffer length exceeds maximum allowed size"); + } + + serializeInteger(serializedData, m_gpioDatagram.bufferLength); + serializedData.append(reinterpret_cast(m_gpioDatagram.buffer), m_gpioDatagram.bufferLength); + return encodeCommand(commandCode, serializedData); +} + +void Gpio::deserialize(std::string &gpioDatagramPayload) { + if (gpioDatagramPayload.size() < 3U) { + throw std::runtime_error("Invalid GPIO datagram payload"); + } + size_t offset = 0; + + m_gpioDatagram.gpioPort = static_cast(deserializeInteger(gpioDatagramPayload, offset)); + m_gpioDatagram.gpioPin = deserializeInteger(gpioDatagramPayload, offset); + m_gpioDatagram.bufferLength = deserializeInteger(gpioDatagramPayload, offset); + + if (m_gpioDatagram.bufferLength > GPIO_MAX_BUFFER_SIZE) { + throw std::runtime_error("Deserialized Gpio buffer length exceeds maximum allowed size"); + } + + std::memcpy(m_gpioDatagram.buffer, gpioDatagramPayload.data() + offset, m_gpioDatagram.bufferLength); +} + +Gpio::Gpio(Payload &data) { + m_gpioDatagram = data; +} + +void Gpio::setGpioPort(const Port &gpioPort) { + m_gpioDatagram.gpioPort = gpioPort; +} + +void Gpio::setGpioPin(const uint8_t &gpioPin) { + m_gpioDatagram.gpioPin = gpioPin; +} + +void Gpio::setBuffer(const uint8_t *data, uint8_t length) { + std::memcpy(m_gpioDatagram.buffer, data, length); + m_gpioDatagram.bufferLength = length; +} + +void Gpio::clearBuffer() { + std::memset(m_gpioDatagram.buffer, 0U, GPIO_MAX_BUFFER_SIZE); +} + +Gpio::Port Gpio::getGpioPort() const { + return m_gpioDatagram.gpioPort; +} + +uint8_t Gpio::getGpioPin() const { + return m_gpioDatagram.gpioPin; +} + +uint8_t Gpio::getBufferLength() const { + return m_gpioDatagram.bufferLength; +} + +const uint8_t *Gpio::getBuffer() const { + return m_gpioDatagram.buffer; +} + +} // namespace Datagram diff --git a/simulation/common/src/i2c_datagram.cpp b/simulation/common/src/i2c_datagram.cpp new file mode 100644 index 0000000..deaeaad --- /dev/null +++ b/simulation/common/src/i2c_datagram.cpp @@ -0,0 +1,61 @@ +#include "i2c_datagram.h" + +#include +#include +#include + +#include "serialization.h" + +namespace Datagram { + +I2C::I2C(Payload &data) { + m_i2cDatagram = data; +} + +std::string I2C::serialize(const CommandCode &commandCode) const { + std::string serializedData; + + serializeInteger(serializedData, static_cast(m_i2cDatagram.i2cPort)); + serializeInteger(serializedData, static_cast(m_i2cDatagram.bufferLength)); + serializedData.append(reinterpret_cast(m_i2cDatagram.buffer), m_i2cDatagram.bufferLength); +} + +void I2C::deserialize(std::string &i2cDatagramPayload) { + size_t offset = 0; + + m_i2cDatagram.i2cPort = static_cast(deserializeInteger(i2cDatagramPayload, offset)); + m_i2cDatagram.bufferLength = deserializeInteger(i2cDatagramPayload, offset); + + if (m_i2cDatagram.bufferLength > MAX_BUFFER_SIZE) { + throw std::runtime_error("Deserialized I2C buffer length exceeds maximum allowed size"); + } + + std::memcpy(m_i2cDatagram.buffer, i2cDatagramPayload.data() + offset, m_i2cDatagram.bufferLength); +} + +void I2C::setI2CPort(const Port &i2cPort) { + m_i2cDatagram.i2cPort = i2cPort; +} + +void I2C::setBuffer(const uint8_t *data, size_t length) { + std::memcpy(m_i2cDatagram.buffer, data, length); + m_i2cDatagram.bufferLength = length; +} + +void I2C::clearBuffer() { + std::memset(m_i2cDatagram.buffer, 0U, MAX_BUFFER_SIZE); +} + +I2C::Port I2C::getI2CPort() const { + return m_i2cDatagram.i2cPort; +} + +size_t I2C::getBufferLength() const { + return m_i2cDatagram.bufferLength; +} + +const uint8_t *I2C::getBuffer() const { + return m_i2cDatagram.buffer; +} + +} // namespace Datagram diff --git a/simulation/common/src/json_manager.cpp b/simulation/common/src/json_manager.cpp new file mode 100644 index 0000000..70e7b22 --- /dev/null +++ b/simulation/common/src/json_manager.cpp @@ -0,0 +1,104 @@ +#include "json_manager.h" + +#include +#include + +void JSONManager::createDefaultProjectJSON(const std::string &projectName) { + if (projectExists(projectName)) { + std::cerr << "Project '" << projectName << "' already exists." << std::endl; + return; + } + + try { + std::time_t now = std::time(nullptr); + + /* YYYY-MM-DD HH:MM:SS -> 19 chars + null terminator */ + char timeBuffer[20]; + std::strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); + + nlohmann::json defaultJSON = { + {"project_name", projectName}, {"version", "1.0.0"}, {"created_at", std::string(timeBuffer)}, {"settings", nlohmann::json::object()}}; + saveProjectJSON(projectName, defaultJSON); + } catch (const std::exception &e) { + std::cerr << "Error creating project JSON: " << e.what() << std::endl; + } +} + +std::filesystem::path JSONManager::getProjectFilePath(const std::string &projectName) { + std::string sanitizedName = projectName; + + /* Get rid of special characters that aren't _ or - */ + for (char &c : sanitizedName) { + if (!std::isalnum(c) && c != '_' && c != '-') { + c = '_'; + } + } + return m_projectBasePath / (sanitizedName + ".json"); +} + +nlohmann::json JSONManager::loadProjectJSON(const std::string &projectName) { + try { + std::filesystem::path projectPath = getProjectFilePath(projectName); + + if (!std::filesystem::exists(projectPath)) { + createDefaultProjectJSON(projectName); + } + + std::ifstream projectFile(projectPath); + if (!projectFile.is_open()) { + throw std::runtime_error("Could not open project JSON file"); + } + + return nlohmann::json::parse(projectFile); + + } catch (const std::exception &e) { + std::cerr << "Error loading project JSON: " << e.what() << std::endl; + } + return nlohmann::json::object(); +} + +void JSONManager::saveProjectJSON(const std::string &projectName, const nlohmann::json &projectData) { + try { + std::filesystem::path projectPath = getProjectFilePath(projectName); + + std::ofstream projectFile(projectPath); + if (!projectFile.is_open()) { + throw std::runtime_error("Could not open project JSON file for writing"); + } + + projectFile << projectData.dump(2); + } catch (const std::exception &e) { + std::cerr << "Error saving project JSON: " << e.what() << std::endl; + } +} + +JSONManager::JSONManager() { + /* Create the JSON output directory */ + m_projectBasePath = std::filesystem::absolute(DEFAULT_JSON_PATH); + std::filesystem::create_directories(m_projectBasePath); + + /* Clean up the directory by deleting all .json files */ + for (const auto &file : std::filesystem::directory_iterator(m_projectBasePath)) { + if (file.is_regular_file() && file.path().extension() == ".json") { + std::filesystem::remove(file.path()); + } + } +} + +bool JSONManager::projectExists(const std::string &projectName) { + return std::filesystem::exists(getProjectFilePath(projectName)); +} + +void JSONManager::deleteProject(const std::string &projectName) { + try { + std::filesystem::path projectPath = getProjectFilePath(projectName); + + if (std::filesystem::exists(projectPath)) { + std::filesystem::remove(projectPath); + } else { + std::cerr << "Project '" << projectName << "' does not exist." << std::endl; + } + } catch (const std::exception &e) { + std::cerr << "Error deleting project JSON: " << e.what() << std::endl; + } +} diff --git a/simulation/common/src/metadata.cpp b/simulation/common/src/metadata.cpp new file mode 100644 index 0000000..73621b2 --- /dev/null +++ b/simulation/common/src/metadata.cpp @@ -0,0 +1,57 @@ +#include "metadata.h" + +#include +#include + +#include "command_code.h" +#include "serialization.h" + +namespace Datagram { + +std::string Metadata::serialize() const { + std::string serializedData; + + serializeString(serializedData, m_metadata.projectName); + serializeString(serializedData, m_metadata.projectStatus); + serializeString(serializedData, m_metadata.hardwareModel); + + return encodeCommand(CommandCode::METADATA, serializedData); +} + +void Metadata::deserialize(std::string &metadataPayload) { + size_t offset = 0; + + m_metadata.projectName = deserializeString(metadataPayload, offset); + m_metadata.projectStatus = deserializeString(metadataPayload, offset); + m_metadata.hardwareModel = deserializeString(metadataPayload, offset); +} + +Metadata::Metadata(Payload &data) { + m_metadata = data; +} + +void Metadata::setProjectName(const std::string &projectName) { + m_metadata.projectName = projectName; +} + +void Metadata::setProjectStatus(const std::string &projectStatus) { + m_metadata.projectStatus = projectStatus; +} + +void Metadata::setHardwareModel(const std::string &hardwareModel) { + m_metadata.hardwareModel = hardwareModel; +} + +std::string Metadata::getProjectName() const { + return m_metadata.projectName; +} + +std::string Metadata::getProjectStatus() const { + return m_metadata.projectStatus; +} + +std::string Metadata::getHardwareModel() const { + return m_metadata.hardwareModel; +} + +} // namespace Datagram diff --git a/simulation/common/src/network_time_protocol.cpp b/simulation/common/src/network_time_protocol.cpp new file mode 100644 index 0000000..8555f24 --- /dev/null +++ b/simulation/common/src/network_time_protocol.cpp @@ -0,0 +1,86 @@ +#include "network_time_protocol.h" + +#include + +#include +#include + +time_t ntpToUnixTime(uint32_t ntpTime) { + return ntpTime - NTP_UNIX_EPOCH_DIFF; +} + +uint32_t unixToNTPTime(time_t unixTime) { + return unixTime + NTP_UNIX_EPOCH_DIFF; +} + +void convertNTPTimestamp(NTPTime ×tamp) { + timestamp.seconds = ntohl(timestamp.seconds); + timestamp.fraction = ntohl(timestamp.fraction); +} + +void dumpNTPPacketData(const NTPPacket packet) { + uint8_t leapIndicator = (packet.flags >> NTP_LEAP_INDICATOR_OFFSET) & 0x03U; + uint8_t version = (packet.flags >> NTP_VERSION_OFFSET) & 0x07U; + uint8_t mode = packet.flags & 0x07U; + + const char *leapStrings[] = {"No warning", "Last minute has 61 seconds", "Last minute has 59 seconds", "Alarm: clock not synchronized"}; + + const char *modeStrings[] = {"Reserved", "Symmetric Active", "Symmetric Passive", "Client", + "Server", "Broadcast", "NTP Control Message", "Private Use"}; + + std::cout << "NTP Packet Dump:" << std::endl; + std::cout << "----------------" << std::endl; + + std::cout << "Leap Indicator: " << leapStrings[leapIndicator] << " (" << static_cast(leapIndicator) << ")" << std::endl; + std::cout << "NTP Version: " << static_cast(version) << std::endl; + std::cout << "Mode: " << modeStrings[mode] << " (" << static_cast(mode) << ")" << std::endl; + + std::cout << "Stratum: " << static_cast(packet.stratum); + switch (packet.stratum) { + case 0: + std::cout << " (Unspecified)"; + break; + case 1: + std::cout << " (Primary Reference)"; + break; + case 2 ... 15: + std::cout << " (Secondary Reference)"; + break; + case 16 ... 255: + std::cout << " (Reserved)"; + break; + } + std::cout << std::endl; + + auto formatTimestamp = [](NTPTime ts) { + time_t unixTime = ntpToUnixTime(ts.seconds); + char buffer[64]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::gmtime(&unixTime)); + return std::string(buffer); + }; + + std::cout << "Reference Time: " << formatTimestamp(packet.referenceTime) << " (Seconds: " << packet.referenceTime.seconds + << ", Fraction: " << packet.referenceTime.fraction << ")" << std::endl; + + std::cout << "Origin Time: " << formatTimestamp(packet.originTime) << " (Seconds: " << packet.originTime.seconds + << ", Fraction: " << packet.originTime.fraction << ")" << std::endl; + + std::cout << "Receive Time: " << formatTimestamp(packet.receiveTime) << " (Seconds: " << packet.receiveTime.seconds + << ", Fraction: " << packet.receiveTime.fraction << ")" << std::endl; + + std::cout << "Transmit Time: " << formatTimestamp(packet.transmitTime) << " (Seconds: " << packet.transmitTime.seconds + << ", Fraction: " << packet.transmitTime.fraction << ")" << std::endl; + + std::cout << "Poll Interval: " << static_cast(packet.poll) << " (2^" << static_cast(packet.poll) << " seconds)" << std::endl; + + std::cout << "Precision: " << static_cast(packet.precision) << " (2^" << static_cast(packet.precision) << " seconds)" << std::endl; + + std::cout << "Reference ID: "; + char refIdStr[5]; + memcpy(refIdStr, &packet.referenceId, 4); + refIdStr[4] = '\0'; + std::cout << refIdStr << " (Raw: 0x" << std::hex << packet.referenceId << std::dec << ")" << std::endl; + + std::cout << "Root Delay: " << (packet.rootDelay / 65536.0) << " seconds" << std::endl; + std::cout << "Root Dispersion:" << (packet.rootDispersion / 65536.0) << " seconds" << std::endl; +} diff --git a/simulation/common/src/serialization.cpp b/simulation/common/src/serialization.cpp new file mode 100644 index 0000000..6ea0d70 --- /dev/null +++ b/simulation/common/src/serialization.cpp @@ -0,0 +1,15 @@ +#include "serialization.h" + +#include +void serializeString(std::string &target, const std::string &str) { + uint16_t length = static_cast(str.length()); + serializeInteger(target, length); + target.append(str); +} + +std::string deserializeString(std::string &source, size_t &offset) { + uint16_t length = deserializeInteger(source, offset); + std::string str = source.substr(offset, length); + offset += length; + return str; +} diff --git a/simulation/common/src/spi_datagram.cpp b/simulation/common/src/spi_datagram.cpp new file mode 100644 index 0000000..b650789 --- /dev/null +++ b/simulation/common/src/spi_datagram.cpp @@ -0,0 +1,61 @@ +#include "spi_datagram.h" + +#include +#include +#include + +#include "serialization.h" + +namespace Datagram { + +SPI::SPI(Payload &data) { + m_spiDatagram = data; +} + +std::string SPI::serialize(const CommandCode &commandCode) const { + std::string serializedData; + + serializeInteger(serializedData, static_cast(m_spiDatagram.spiPort)); + serializeInteger(serializedData, static_cast(m_spiDatagram.bufferLength)); + serializedData.append(reinterpret_cast(m_spiDatagram.buffer), m_spiDatagram.bufferLength); +} + +void SPI::deserialize(std::string &spiDatagramPayload) { + size_t offset = 0; + + m_spiDatagram.spiPort = static_cast(deserializeInteger(spiDatagramPayload, offset)); + m_spiDatagram.bufferLength = deserializeInteger(spiDatagramPayload, offset); + + if (m_spiDatagram.bufferLength > SPI_MAX_BUFFER_SIZE) { + throw std::runtime_error("Deserialized SPI buffer length exceeds maximum allowed size"); + } + + std::memcpy(m_spiDatagram.buffer, spiDatagramPayload.data() + offset, m_spiDatagram.bufferLength); +} + +void SPI::setSPIPort(const Port &spiPort) { + m_spiDatagram.spiPort = spiPort; +} + +void SPI::setBuffer(const uint8_t *data, size_t length) { + std::memcpy(m_spiDatagram.buffer, data, length); + m_spiDatagram.bufferLength = length; +} + +void SPI::clearBuffer() { + std::memset(m_spiDatagram.buffer, 0U, SPI_MAX_BUFFER_SIZE); +} + +SPI::Port SPI::getSPIPort() const { + return m_spiDatagram.spiPort; +} + +size_t SPI::getBufferLength() const { + return m_spiDatagram.bufferLength; +} + +const uint8_t *SPI::getBuffer() const { + return m_spiDatagram.buffer; +} + +} // namespace Datagram diff --git a/simulation/common/src/thread_helpers.cpp b/simulation/common/src/thread_helpers.cpp new file mode 100644 index 0000000..423bfbf --- /dev/null +++ b/simulation/common/src/thread_helpers.cpp @@ -0,0 +1,17 @@ +#include "thread_helpers.h" + +#include + +void thread_sleep_s(unsigned int seconds) { + struct timespec ts; + ts.tv_sec = seconds; + ts.tv_nsec = 0; + nanosleep(&ts, NULL); +} + +void thread_sleep_ms(unsigned int milliseconds) { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = milliseconds * 1000000; + nanosleep(&ts, NULL); +} diff --git a/simulation/scripts/main.py b/simulation/scripts/main.py new file mode 100644 index 0000000..a00cd41 --- /dev/null +++ b/simulation/scripts/main.py @@ -0,0 +1,22 @@ +# @defgroup VehicleSimulationPy Vehicle Simulation Python Package +# This is a python package that supports running the vehicle simulation +# +# @file main.py +# @date 2024-12-26 +# @author Midnight Sun Team #24 - MSXVI +# @brief Main python module for the Vehicle Simulation +# +# @details This file shall autogenerate the required files, and initiate the client and server +# +# @ingroup VehicleSimulationPy + + +def main(): + """ + @brief Main function for sample + """ + print("Welcome to Vehicle simulation python package!") + + +if __name__ == "__main__": + main() diff --git a/simulation/server/app/inc/app.h b/simulation/server/app/inc/app.h new file mode 100644 index 0000000..77d52d7 --- /dev/null +++ b/simulation/server/app/inc/app.h @@ -0,0 +1,16 @@ +#ifndef APP_H +#define APP_H + +#include + +#include "gpio_manager.h" +#include "json_manager.h" + +#ifndef USE_NETWORK_TIME_PROTOCOL +#define USE_NETWORK_TIME_PROTOCOL 0U +#endif + +extern JSONManager globalJSON; +extern GpioManager serverGpioManager; + +#endif diff --git a/simulation/server/app/inc/app_callback.h b/simulation/server/app/inc/app_callback.h new file mode 100644 index 0000000..44a5d7e --- /dev/null +++ b/simulation/server/app/inc/app_callback.h @@ -0,0 +1,11 @@ +#ifndef APP_CALLBACK_H +#define APP_CALLBACK_H + +#include + +#include "client_connection.h" +#include "tcp_server.h" + +void applicationCallback(TCPServer *srv, ClientConnection *src, std::string &message); + +#endif diff --git a/simulation/server/app/inc/app_terminal.h b/simulation/server/app/inc/app_terminal.h new file mode 100644 index 0000000..8894099 --- /dev/null +++ b/simulation/server/app/inc/app_terminal.h @@ -0,0 +1,22 @@ +#ifndef APP_TERMINAL_H +#define APP_TERMINAL_H + +#include "client_connection.h" +#include "tcp_server.h" +class Terminal { + private: + TCPServer *m_Server; + ClientConnection *m_targetClient; + + std::string toLower(const std::string &input); + + void handleGpioCommands(const std::string &action, std::vector &tokens); + void parseCommand(std::vector &tokens); + + public: + Terminal(TCPServer *server); + + void run(); +}; + +#endif diff --git a/simulation/server/app/inc/can_manager.h b/simulation/server/app/inc/can_manager.h new file mode 100644 index 0000000..48e8a99 --- /dev/null +++ b/simulation/server/app/inc/can_manager.h @@ -0,0 +1,45 @@ +#ifndef CAN_MANAGER_H +#define CAN_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class CanManager { + private: + const std::string CAN_INTERFACE_NAME = "vcan0"; + static const constexpr int UPDATE_CAN_JSON_FREQUENCY_MS = 1000U; + + // std::unordered_map<> m_canInfo; + + pthread_mutex_t m_mutex; + pthread_t m_listenCanBusId; + + int m_listeningSocket; + struct sockaddr_can m_canAddress; + struct ifreq m_interfaceRequest; + + std::atomic m_isListening; + + void canCallback(); + + public: + CanManager() = default; + + void listenCanBus(); + void listenCanBusProcedure(); +}; + +#endif diff --git a/simulation/server/app/inc/gpio_manager.h b/simulation/server/app/inc/gpio_manager.h new file mode 100644 index 0000000..d97b0d5 --- /dev/null +++ b/simulation/server/app/inc/gpio_manager.h @@ -0,0 +1,40 @@ +#ifndef GPIO_MANAGER_H +#define GPIO_MANAGER_H + +#include +#include + +#include + +#include "gpio_datagram.h" + +class GpioManager { + private: + using PinInfo = std::unordered_map; + + std::unordered_map m_gpioInfo; + + Datagram::Gpio m_gpioDatagram; + + std::string stringifyPinMode(Datagram::Gpio::Mode mode); + std::string stringifyPinAltFunction(Datagram::Gpio::AltFunction altFunction); + + void loadGpioInfo(std::string &projectName); + void saveGpioInfo(std::string &projectName); + + public: + GpioManager() = default; + + void updateGpioPinState(std::string &projectName, std::string &payload); + void updateGpioAllStates(std::string &projectName, std::string &payload); + + void updateGpioPinMode(std::string &projectName, std::string &payload); + void updateGpioAllModes(std::string &projectName, std::string &payload); + + void updateGpioPinAltFunction(std::string &projectName, std::string &payload); + void updateGpioAllAltFunctions(std::string &projectName, std::string &payload); + + std::string createGpioCommand(CommandCode commandCode, std::string &gpioPortPin, std::string data); +}; + +#endif diff --git a/simulation/server/app/inc/i2c_manager.h b/simulation/server/app/inc/i2c_manager.h new file mode 100644 index 0000000..e5c2190 --- /dev/null +++ b/simulation/server/app/inc/i2c_manager.h @@ -0,0 +1,10 @@ +#ifndef I2C_MANAGER_H +#define I2C_MANAGER_H + +class I2CManager { + private: + public: + I2CManager() = default; +}; + +#endif diff --git a/simulation/server/app/inc/spi_manager.h b/simulation/server/app/inc/spi_manager.h new file mode 100644 index 0000000..fb20dca --- /dev/null +++ b/simulation/server/app/inc/spi_manager.h @@ -0,0 +1,10 @@ +#ifndef SPI_MANAGER_H +#define SPI_MANAGER_H + +class SPIManager { + private: + public: + SPIManager() = default; +}; + +#endif diff --git a/simulation/server/app/src/app_callback.cpp b/simulation/server/app/src/app_callback.cpp new file mode 100644 index 0000000..232bda5 --- /dev/null +++ b/simulation/server/app/src/app_callback.cpp @@ -0,0 +1,60 @@ +#include "app_callback.h" + +#include + +#include "app.h" +#include "command_code.h" +#include "gpio_datagram.h" +#include "i2c_datagram.h" +#include "json_manager.h" +#include "metadata.h" +#include "spi_datagram.h" + +void applicationCallback(TCPServer *srv, ClientConnection *src, std::string &message) { + std::string clientName = src->getClientName(); + + auto [commandCode, payload] = decodeCommand(message); + switch (commandCode) { + case CommandCode::METADATA: { + Datagram::Metadata clientMetadata; + clientMetadata.deserialize(payload); + + if (src->getClientName() != clientMetadata.getProjectName()) { + srv->updateClientName(src, clientMetadata.getProjectName()); + } + + globalJSON.setProjectValue(src->getClientName(), "project_name", src->getClientName()); /* Get the updated name if there are duplicates */ + globalJSON.setProjectValue(src->getClientName(), "project_status", clientMetadata.getProjectStatus()); + globalJSON.setProjectValue(src->getClientName(), "hardware_model", clientMetadata.getHardwareModel()); + + break; + } + case CommandCode::GPIO_GET_PIN_STATE: { + serverGpioManager.updateGpioPinState(clientName, payload); + break; + } + case CommandCode::GPIO_GET_ALL_STATES: { + serverGpioManager.updateGpioAllStates(clientName, payload); + break; + } + case CommandCode::GPIO_GET_PIN_MODE: { + serverGpioManager.updateGpioPinMode(clientName, payload); + break; + } + case CommandCode::GPIO_GET_ALL_MODES: { + serverGpioManager.updateGpioAllModes(clientName, payload); + break; + } + case CommandCode::GPIO_GET_PIN_ALT_FUNCTION: { + serverGpioManager.updateGpioPinAltFunction(clientName, payload); + break; + } + case CommandCode::GPIO_GET_ALL_ALT_FUNCTIONS: { + serverGpioManager.updateGpioAllAltFunctions(clientName, payload); + break; + } + default: { + break; + } + } +} \ No newline at end of file diff --git a/simulation/server/app/src/app_terminal.cpp b/simulation/server/app/src/app_terminal.cpp new file mode 100644 index 0000000..bcd02fc --- /dev/null +++ b/simulation/server/app/src/app_terminal.cpp @@ -0,0 +1,126 @@ +#include "app_terminal.h" + +#include +#include +#include +#include + +#include "app.h" +#include "command_code.h" + +Terminal::Terminal(TCPServer *server) { + m_Server = server; +} + +std::string Terminal::toLower(const std::string &input) { + std::string lowered = input; + std::transform(lowered.begin(), lowered.end(), lowered.begin(), [](unsigned char c) { return std::tolower(c); }); + return lowered; +} + +void Terminal::handleGpioCommands(const std::string &action, std::vector &tokens) { + std::string message; + if (action == "get_pin_state" && tokens.size() >= 3) { + message = serverGpioManager.createGpioCommand(CommandCode::GPIO_GET_PIN_STATE, tokens[2], ""); + } else if (action == "get_all_states" && tokens.size() >= 2) { + message = serverGpioManager.createGpioCommand(CommandCode::GPIO_GET_ALL_STATES, tokens[0], ""); + } else if (action == "get_pin_mode" && tokens.size() >= 3) { + message = serverGpioManager.createGpioCommand(CommandCode::GPIO_GET_PIN_MODE, tokens[2], ""); + } else if (action == "get_all_modes" && tokens.size() >= 2) { + message = serverGpioManager.createGpioCommand(CommandCode::GPIO_GET_ALL_MODES, tokens[0], ""); + } else if (action == "get_pin_alt_function" && tokens.size() >= 3) { + message = serverGpioManager.createGpioCommand(CommandCode::GPIO_GET_PIN_ALT_FUNCTION, tokens[2], ""); + } else if (action == "get_all_alt_functions" && tokens.size() >= 2) { + message = serverGpioManager.createGpioCommand(CommandCode::GPIO_GET_ALL_ALT_FUNCTIONS, tokens[0], ""); + } else if (action == "set_pin_state" && tokens.size() >= 4) { + message = serverGpioManager.createGpioCommand(CommandCode::GPIO_SET_PIN_STATE, tokens[2], tokens[3]); + } else if (action == "set_all_states" && tokens.size() >= 3) { + message = serverGpioManager.createGpioCommand(CommandCode::GPIO_SET_ALL_STATES, tokens[0], tokens[2]); + } else { + std::cerr << "Unsupported action: " << action << std::endl; + } + + if (!message.empty()) { + m_Server->sendMessage(m_targetClient, message); + } else { + std::cout << "Invalid command. Refer to sim_command.md" << std::endl; + } + m_targetClient = nullptr; +} + +void Terminal::parseCommand(std::vector &tokens) { + if (tokens.size() < 2) { + std::cout << "Invalid command. Format: \n"; + return; + } + + std::string interface = toLower(tokens[0]); + std::string action = toLower(tokens[1]); + + try { + if (interface == "gpio") { + handleGpioCommands(action, tokens); + } else if (interface == "i2c") { + } else if (interface == "spi") { + } else { + std::cerr << "Unsupported interface: " << interface << std::endl; + } + } catch (const std::exception &e) { + std::cerr << "Terminal command error: " << e.what() << std::endl; + } +} + +void Terminal::run() { + std::string input; + + while (true) { + std::cout << "Client List:" << std::endl; + std::cout << "------------" << std::endl; + m_Server->dumpClientList(); + std::cout << "------------" << std::endl; + + std::cout << "Select Client by Name (Enter to refresh) > "; + std::getline(std::cin, input); + + input.erase(0, input.find_first_not_of(" \t")); + input.erase(input.find_last_not_of(" \t") + 1); + + if (toLower(input) == "exit") { + break; + } + + m_targetClient = m_Server->getClientByName(input); + + if (m_targetClient == nullptr) { + std::cout << "Invalid client selection." << std::endl << std::endl; + continue; + } + std::cout << "Selected " << m_targetClient->getClientName() << std::endl << std::endl; + + std::cout << "Enter commmand > "; + std::getline(std::cin, input); + + input.erase(0, input.find_first_not_of(" \t")); + input.erase(input.find_last_not_of(" \t") + 1); + + if (toLower(input) == "exit") { + break; + } + + /** + * Create tokens out of input string + * Input = "GPIO GET_PIN_STATE A9" + * tokens = ["GPIO", "GET_PIN_STATE", "A9"] + */ + std::vector tokens; + std::istringstream iss(input); + std::string token; + while (iss >> token) { + tokens.push_back(token); + } + + if (!tokens.empty()) { + parseCommand(tokens); + } + } +} diff --git a/simulation/server/app/src/can_manager.cpp b/simulation/server/app/src/can_manager.cpp new file mode 100644 index 0000000..debeadd --- /dev/null +++ b/simulation/server/app/src/can_manager.cpp @@ -0,0 +1,66 @@ +#include "can_manager.h" + +#include + +#include +#include + +void CanManager::listenCanBusProcedure() { + m_listeningSocket = socket(PF_CAN, SOCK_DGRAM, CAN_BCM); + + if (m_listeningSocket < 0) { + throw std::runtime_error("Error creating socket for CAN Broadcast Manager"); + } + + strcpy(m_interfaceRequest.ifr_name, CAN_INTERFACE_NAME.c_str()); + if (ioctl(m_listeningSocket, SIOCGIFINDEX, &m_interfaceRequest) < 0) { + throw std::runtime_error("Error writing interface name to socketCAN file descriptor"); + } + + m_canAddress.can_family = AF_CAN; + m_canAddress.can_ifindex = m_interfaceRequest.ifr_ifindex; + + if (connect(m_listeningSocket, (struct sockaddr *)&m_canAddress, sizeof(m_canAddress)) < 0) { + throw std::runtime_error("Error connecting to SocketCAN broadcast manager"); + } + + m_isListening = true; + + struct can_frame canFrame; + int numBytes; + + while (m_isListening) { + numBytes = read(m_listeningSocket, &canFrame, sizeof(struct can_frame)); + + if (numBytes < 0) { + throw std::runtime_error("Error reading CAN data"); + break; + } + } + + close(m_listeningSocket); + m_isListening = false; +} + +void *listenCanBusWrapper(void *param) { + CanManager *canManager = static_cast(param); + + try { + canManager->listenCanBusProcedure(); + } catch (...) { + std::cerr << "Can Manager Thread Error" << std::endl; + } + + return nullptr; +} + +void CanManager::listenCanBus() { + if (m_isListening) + return; + + if (pthread_create(&m_listenCanBusId, nullptr, listenCanBusWrapper, this)) { + throw std::runtime_error("CAN listener thread creation error"); + } +} + +void CanManager::canCallback() {} diff --git a/simulation/server/app/src/gpio_manager.cpp b/simulation/server/app/src/gpio_manager.cpp new file mode 100644 index 0000000..8d4e4c5 --- /dev/null +++ b/simulation/server/app/src/gpio_manager.cpp @@ -0,0 +1,350 @@ +#include "gpio_manager.h" + +#include + +#include "app.h" +#include "command_code.h" + +#define GPIO_KEY "gpio" +#define PIN_STATE_KEY "state" +#define PIN_MODE_KEY "mode" +#define PIN_ALT_FUNC_KEY "alternate_function" + +const char *gpioPortNames[] = { + "A", /* GPIO_PORT_A */ + "B", /* GPIO_PORT_B */ +}; + +std::string GpioManager::stringifyPinMode(Datagram::Gpio::Mode mode) { + std::string result = ""; + + switch (mode) { + case Datagram::Gpio::Mode::GPIO_ANALOG: { + result = "Analog"; + break; + } + case Datagram::Gpio::Mode::GPIO_INPUT_FLOATING: { + result = "Floating Input"; + break; + } + case Datagram::Gpio::Mode::GPIO_INPUT_PULL_DOWN: { + result = "Pull-down Input"; + break; + } + case Datagram::Gpio::Mode::GPIO_INPUT_PULL_UP: { + result = "Pull-up Input"; + break; + } + case Datagram::Gpio::Mode::GPIO_OUTPUT_OPEN_DRAIN: { + result = "Open-drain Output"; + break; + } + case Datagram::Gpio::Mode::GPIO_OUTPUT_PUSH_PULL: { + result = "Push-pull Output"; + break; + } + case Datagram::Gpio::Mode::GPIO_ALFTN_OPEN_DRAIN: { + result = "Open-drain Alternate Function"; + break; + } + case Datagram::Gpio::Mode::GPIO_ALTFN_PUSH_PULL: { + result = "Push-pull Alternate Function"; + break; + } + default: { + result = "Invalid Mode"; + break; + } + } + + return result; +} + +std::string GpioManager::stringifyPinAltFunction(Datagram::Gpio::AltFunction altFunction) { + std::string result = ""; + + switch (altFunction) { + /* Duplicate value is SWCLK/SWDIO */ + case Datagram::Gpio::AltFunction::GPIO_ALT_NONE: { + result = "None/SWDIO/SWCLK ALT0"; + break; + } + /* Duplicate value is Timer 2 */ + case Datagram::Gpio::AltFunction::GPIO_ALT1_TIM1: { + result = "Timer1/2 ALT1"; + break; + } + /* Duplicate values is I2C2/3 */ + case Datagram::Gpio::AltFunction::GPIO_ALT4_I2C1: { + result = "I2C1/2/3 ALT4"; + break; + } + /* Duplicate value is SPI2 */ + case Datagram::Gpio::AltFunction::GPIO_ALT5_SPI1: { + result = "SPI1/2 ALT5"; + break; + } + case Datagram::Gpio::AltFunction::GPIO_ALT6_SPI3: { + result = "SPI3 ALT6"; + break; + } + case Datagram::Gpio::AltFunction::GPIO_ALT7_USART1: { + result = "USART1/2/3 ALT7"; + break; + } + case Datagram::Gpio::AltFunction::GPIO_ALT9_CAN1: { + result = "CAN1 ALT9"; + break; + } + /* Duplicate value is Timer 16 */ + case Datagram::Gpio::AltFunction::GPIO_ALT14_TIM15: { + result = "TIMER15/16 ALT14"; + break; + } + default: { + result = "Invalid Alternate Function"; + } + } + + return result; +} + +void GpioManager::loadGpioInfo(std::string &projectName) { + m_gpioInfo = globalJSON.getProjectValue>(projectName, GPIO_KEY); +} + +void GpioManager::saveGpioInfo(std::string &projectName) { + globalJSON.setProjectValue(projectName, GPIO_KEY, m_gpioInfo); + + /* Upon save, clear the memory */ + m_gpioInfo.clear(); +} + +void GpioManager::updateGpioPinState(std::string &projectName, std::string &payload) { + loadGpioInfo(projectName); + + m_gpioDatagram.deserialize(payload); + + std::string key = gpioPortNames[static_cast(m_gpioDatagram.getGpioPort())]; + key += std::to_string(m_gpioDatagram.getGpioPin()); + const uint8_t *receivedData = m_gpioDatagram.getBuffer(); + + if (static_cast(receivedData[0U]) == Datagram::Gpio::State::GPIO_STATE_HIGH) { + m_gpioInfo[key][PIN_STATE_KEY] = "HIGH"; + } else if (static_cast(receivedData[0U]) == Datagram::Gpio::State::GPIO_STATE_LOW) { + m_gpioInfo[key][PIN_STATE_KEY] = "LOW"; + } else { + m_gpioInfo[key][PIN_STATE_KEY] = "INVALID"; + } + + saveGpioInfo(projectName); +} + +void GpioManager::updateGpioAllStates(std::string &projectName, std::string &payload) { + loadGpioInfo(projectName); + + m_gpioDatagram.deserialize(payload); + + const uint8_t *receivedData = m_gpioDatagram.getBuffer(); + + for (uint8_t i = 0U; i < static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) * static_cast(Datagram::Gpio::PINS_PER_PORT); i++) { + size_t blockIndex = i / 8U; + size_t bitPosition = i % 8U; + bool pinState = (receivedData[blockIndex] & (1U << bitPosition)) != 0; + std::string key = gpioPortNames[i / Datagram::Gpio::PINS_PER_PORT]; + key += std::to_string(i % Datagram::Gpio::PINS_PER_PORT); + + if (pinState) { + m_gpioInfo[key][PIN_STATE_KEY] = "HIGH"; + } else { + m_gpioInfo[key][PIN_STATE_KEY] = "LOW"; + } + } + + saveGpioInfo(projectName); +} + +void GpioManager::updateGpioPinMode(std::string &projectName, std::string &payload) { + loadGpioInfo(projectName); + + m_gpioDatagram.deserialize(payload); + + std::string key = gpioPortNames[static_cast(m_gpioDatagram.getGpioPort())]; + key += std::to_string(m_gpioDatagram.getGpioPin()); + + const uint8_t *receivedData = m_gpioDatagram.getBuffer(); + + m_gpioInfo[key][PIN_MODE_KEY] = stringifyPinMode(static_cast(receivedData[0U])); + + saveGpioInfo(projectName); +} + +void GpioManager::updateGpioAllModes(std::string &projectName, std::string &payload) { + loadGpioInfo(projectName); + + m_gpioDatagram.deserialize(payload); + + const uint32_t *receivedData = reinterpret_cast(m_gpioDatagram.getBuffer()); + + for (uint8_t i = 0U; i < static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) * static_cast(Datagram::Gpio::PINS_PER_PORT); i++) { + size_t blockIndex = (i / 8U); /* 4 bits per pin so there is only 8 pins per block */ + size_t bitOffset = (i % 8U) * 4U; /* Multiply by 4 because each each mode is 4 bit */ + + uint8_t pinMode = (receivedData[blockIndex] >> bitOffset) & 0x0F; + + std::string key = gpioPortNames[i / Datagram::Gpio::PINS_PER_PORT]; + key += std::to_string(i % Datagram::Gpio::PINS_PER_PORT); + + m_gpioInfo[key][PIN_MODE_KEY] = stringifyPinMode(static_cast(pinMode)); + } + + saveGpioInfo(projectName); +} + +void GpioManager::updateGpioPinAltFunction(std::string &projectName, std::string &payload) { + loadGpioInfo(projectName); + + m_gpioDatagram.deserialize(payload); + + std::string key = gpioPortNames[static_cast(m_gpioDatagram.getGpioPort())]; + key += std::to_string(m_gpioDatagram.getGpioPin()); + + const uint8_t *receivedData = m_gpioDatagram.getBuffer(); + + m_gpioInfo[key][PIN_ALT_FUNC_KEY] = stringifyPinAltFunction(static_cast(receivedData[0U])); + + saveGpioInfo(projectName); +} + +void GpioManager::updateGpioAllAltFunctions(std::string &projectName, std::string &payload) { + loadGpioInfo(projectName); + + m_gpioDatagram.deserialize(payload); + + const uint32_t *receivedData = reinterpret_cast(m_gpioDatagram.getBuffer()); + + for (uint8_t i = 0U; i < static_cast(Datagram::Gpio::Port::NUM_GPIO_PORTS) * static_cast(Datagram::Gpio::PINS_PER_PORT); i++) { + size_t blockIndex = (i / 8U); /* 4 bits per pin so there is only 8 pins per block */ + size_t bitOffset = (i % 8U) * 4U; /* Multiply by 4 because each each mode is 4 bit */ + + uint8_t pinAltFunction = (receivedData[blockIndex] >> bitOffset) & 0x0F; + + std::string key = gpioPortNames[i / Datagram::Gpio::PINS_PER_PORT]; + key += std::to_string(i % Datagram::Gpio::PINS_PER_PORT); + + m_gpioInfo[key][PIN_ALT_FUNC_KEY] = stringifyPinAltFunction(static_cast(pinAltFunction)); + } + + saveGpioInfo(projectName); +} + +std::string GpioManager::createGpioCommand(CommandCode commandCode, std::string &gpioPortPin, std::string data) { + try { + switch (commandCode) { + case CommandCode::GPIO_GET_PIN_STATE: + case CommandCode::GPIO_GET_PIN_MODE: + case CommandCode::GPIO_GET_PIN_ALT_FUNCTION: { + if (gpioPortPin.empty() || gpioPortPin.size() < 2) { + throw std::runtime_error( + "Invalid format for port/pin specification. Good examples: 'A9' " + "'A12' 'B13'"); + break; + } + + Datagram::Gpio::Port port = static_cast(gpioPortPin[0] - 'A'); + uint8_t pin = static_cast(std::stoi(gpioPortPin.substr(1))); + + if (port >= Datagram::Gpio::Port::NUM_GPIO_PORTS) { + throw std::runtime_error("Invalid selection for Gpio ports. Expected: A or B"); + break; + } + + if (pin >= Datagram::Gpio::PINS_PER_PORT) { + throw std::runtime_error("Exceeded maximum number of Gpio pins: " + std::to_string(static_cast(Datagram::Gpio::PINS_PER_PORT))); + break; + } + + m_gpioDatagram.setGpioPort(port); + m_gpioDatagram.setGpioPin(pin); + m_gpioDatagram.setBuffer(nullptr, 0U); + break; + } + + case CommandCode::GPIO_GET_ALL_STATES: + case CommandCode::GPIO_GET_ALL_MODES: + case CommandCode::GPIO_GET_ALL_ALT_FUNCTIONS: { + m_gpioDatagram.setGpioPort(Datagram::Gpio::Port::NUM_GPIO_PORTS); + m_gpioDatagram.setGpioPin(Datagram::Gpio::PINS_PER_PORT); + m_gpioDatagram.setBuffer(nullptr, 0U); + break; + } + + case CommandCode::GPIO_SET_PIN_STATE: { + if (gpioPortPin.empty() || gpioPortPin.size() < 2) { + throw std::runtime_error( + "Invalid format for port/pin specification. Good examples: 'A9' " + "'A12' 'B13'"); + break; + } + + Datagram::Gpio::Port port = static_cast(gpioPortPin[0] - 'A'); + uint8_t pin = static_cast(std::stoi(gpioPortPin.substr(1))); + uint8_t pinState; + + if (port >= Datagram::Gpio::Port::NUM_GPIO_PORTS) { + throw std::runtime_error("Invalid selection for Gpio ports. Expected: A or B"); + break; + } + + if (pin >= Datagram::Gpio::PINS_PER_PORT) { + throw std::runtime_error("Exceeded maximum number of Gpio pins: " + std::to_string(static_cast(Datagram::Gpio::PINS_PER_PORT))); + break; + } + + if (data == "HIGH") { + pinState = static_cast(Datagram::Gpio::State::GPIO_STATE_HIGH); + } else if (data == "LOW") { + pinState = static_cast(Datagram::Gpio::State::GPIO_STATE_LOW); + } else { + throw std::runtime_error("Invalid Gpio state: " + data); + break; + } + + m_gpioDatagram.setGpioPort(port); + m_gpioDatagram.setGpioPin(pin); + m_gpioDatagram.setBuffer(&pinState, sizeof(pinState)); + + break; + } + + case CommandCode::GPIO_SET_ALL_STATES: { + uint8_t pinState; + if (data == "HIGH") { + pinState = static_cast(Datagram::Gpio::State::GPIO_STATE_HIGH); + } else if (data == "LOW") { + pinState = static_cast(Datagram::Gpio::State::GPIO_STATE_LOW); + } else { + throw std::runtime_error("Invalid Gpio state: " + data); + break; + } + + m_gpioDatagram.setGpioPort(Datagram::Gpio::Port::NUM_GPIO_PORTS); + m_gpioDatagram.setGpioPin(Datagram::Gpio::PINS_PER_PORT); + m_gpioDatagram.setBuffer(&pinState, sizeof(pinState)); + + break; + } + + default: { + throw std::runtime_error("Invalid command code"); + break; + } + } + + return m_gpioDatagram.serialize(commandCode); + + } catch (std::exception &e) { + std::cerr << "Gpio Manager error: " << e.what() << std::endl; + } + return ""; +} diff --git a/simulation/server/app/src/i2c_manager.cpp b/simulation/server/app/src/i2c_manager.cpp new file mode 100644 index 0000000..7829c87 --- /dev/null +++ b/simulation/server/app/src/i2c_manager.cpp @@ -0,0 +1,2 @@ + +#include "i2c_manager.h" diff --git a/simulation/server/app/src/main.cpp b/simulation/server/app/src/main.cpp new file mode 100644 index 0000000..fbe7de2 --- /dev/null +++ b/simulation/server/app/src/main.cpp @@ -0,0 +1,32 @@ + +#include +#include + +#include "app.h" +#include "app_callback.h" +#include "app_terminal.h" +#include "client_connection.h" +#include "gpio_manager.h" +#include "json_manager.h" +#include "ntp_server.h" +#include "tcp_server.h" + +JSONManager globalJSON; +GpioManager serverGpioManager; + +int main(int argc, char **argv) { + std::cout << "Running Server" << std::endl; + TCPServer tcp_server; + Terminal application_terminal(&tcp_server); + + tcp_server.listenClients(1024, applicationCallback); + +#if USE_NETWORK_TIME_PROTOCOL == 1U + ntp_server.startListening("127.0.0.1", "time.google.com"); + NTPServer ntp_server; +#endif + + application_terminal.run(); + + return 0; +} diff --git a/simulation/server/app/src/spi_manager.cpp b/simulation/server/app/src/spi_manager.cpp new file mode 100644 index 0000000..ab1dd77 --- /dev/null +++ b/simulation/server/app/src/spi_manager.cpp @@ -0,0 +1,2 @@ + +#include "spi_manager.h" diff --git a/simulation/server/utils/inc/client_connection.h b/simulation/server/utils/inc/client_connection.h new file mode 100644 index 0000000..8a9a2d9 --- /dev/null +++ b/simulation/server/utils/inc/client_connection.h @@ -0,0 +1,46 @@ +#ifndef CLIENT_CONNECTION_H +#define CLIENT_CONNECTION_H + +#include +#include + +#include +#include + +/* Forward declaration */ +class TCPServer; + +class ClientConnection { + private: + static const constexpr size_t MAX_CLIENT_BUFFER_SIZE = 512U; + + std::atomic m_isConnected; + int m_clientPort; + int m_clientSocket; + struct sockaddr_in m_clientAddress; + pthread_t m_monitorThreadId; + std::string m_clientName; + + TCPServer *server; + + std::string getClientAddress() const; + + public: + ClientConnection(TCPServer *server); + ~ClientConnection(); + + bool acceptClient(int listeningSocket); + + void monitorThreadProcedure(); + void sendMessage(const std::string &message); + + std::string getClientName() const; + void setClientName(const std::string &name); + + int getClientPort() const; + void setClientPort(int port); + + bool isConnected(); +}; + +#endif diff --git a/simulation/server/utils/inc/ntp_server.h b/simulation/server/utils/inc/ntp_server.h new file mode 100644 index 0000000..6327e7f --- /dev/null +++ b/simulation/server/utils/inc/ntp_server.h @@ -0,0 +1,37 @@ +#ifndef NTP_SERVER_H +#define NTP_SERVER_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "network_time_protocol.h" + +#define NTP_SYNC_PERIOD_S 60 + +class NTPServer { + private: + static constexpr int NTP_PORT = 123; + int m_NTPSocket; + pthread_t m_NTPServerThreadId; + std::atomic m_isListening; + std::string m_bindAddress; + std::string m_NTPServerAddress; + + NTPPacket processNTPRequest(const NTPPacket &request); + bool queryNTPServer(NTPPacket &response); + + public: + NTPServer(); + + void NTPServerProcedure(); + void startListening(const std::string &bindAddress, const std::string &NTPServerAddress); +}; + +#endif diff --git a/simulation/server/utils/inc/tcp_server.h b/simulation/server/utils/inc/tcp_server.h new file mode 100644 index 0000000..044b26a --- /dev/null +++ b/simulation/server/utils/inc/tcp_server.h @@ -0,0 +1,48 @@ +#ifndef TCP_SERVER_H +#define TCP_SERVER_H + +#include +#include + +#include +#include +#include +#include +#include + +#include "client_connection.h" + +class TCPServer { + private: + using serverCallback = std::function; + pthread_mutex_t m_mutex; + serverCallback m_serverCallback; + std::unordered_map m_connections; + + std::atomic m_isListening; + int m_listenPort; + int m_listeningSocket; + + pthread_t m_listenThreadId; + struct sockaddr_in m_serverAddress; + + public: + TCPServer(); + ~TCPServer(); + + void listenThreadProcedure(); + + void stop(); + void listenClients(int port, serverCallback callback); + void messageReceived(ClientConnection *src, std::string &message); + + void sendMessage(ClientConnection *dst, const std::string &message); + void broadcastMessage(const std::string &message); + void updateClientName(ClientConnection *conn, std::string newName); + void removeClient(ClientConnection *conn); + ClientConnection *getClientByName(std::string &clientName); + + void dumpClientList(); +}; + +#endif diff --git a/simulation/server/utils/src/client_connection.cpp b/simulation/server/utils/src/client_connection.cpp new file mode 100644 index 0000000..f0f3bac --- /dev/null +++ b/simulation/server/utils/src/client_connection.cpp @@ -0,0 +1,122 @@ +#include "client_connection.h" + +#include +#include + +#include + +#include "tcp_server.h" + +std::string ClientConnection::getClientAddress() const { + char ip_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(m_clientAddress.sin_addr), ip_str, INET_ADDRSTRLEN); + return std::string(ip_str); +} + +ClientConnection::ClientConnection(TCPServer *server) { + this->server = server; + m_isConnected = false; +} + +ClientConnection::~ClientConnection() {} + +void ClientConnection::monitorThreadProcedure() { + const int bufferSize = MAX_CLIENT_BUFFER_SIZE; + char buffer[bufferSize + 1]; + int numBytes; + + m_isConnected = true; + while (true) { + numBytes = read(m_clientSocket, &buffer, bufferSize); + if (numBytes <= 0) { + break; + } + + buffer[numBytes] = '\0'; + + std::string msg(buffer, numBytes); + server->messageReceived(this, msg); + } + + close(m_clientSocket); + m_isConnected = false; + + server->removeClient(this); +} + +void *monitorThreadWrapper(void *param) { + ClientConnection *conn = static_cast(param); + + try { + conn->monitorThreadProcedure(); + } catch (const std::exception &e) { + std::cerr << "Error in Monitor Thread Procedure: " << e.what() << std::endl; + } catch (...) { + std::cerr << "Unknown error in MonitorThreadProcedure" << std::endl; + } + + delete conn; + return nullptr; +} + +bool ClientConnection::acceptClient(int listeningSocket) { + socklen_t clientLength = sizeof(m_clientAddress); + m_clientSocket = accept(listeningSocket, (struct sockaddr *)&m_clientAddress, &clientLength); + + if (m_clientSocket < 0) { + std::cerr << "Failed to accept client connection" << std::endl; + return false; + } + + try { + if (m_clientName.empty()) { + m_clientName = getClientAddress(); + } + + if (pthread_create(&m_monitorThreadId, nullptr, monitorThreadWrapper, this) < 0) { + throw std::runtime_error("Error creating monitor thread"); + } + + if (pthread_detach(m_monitorThreadId) != 0) { + throw std::runtime_error("Error detatching monitor-thread"); + } + } catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + close(m_clientSocket); + return false; + } + + m_isConnected = true; + return true; +} + +void ClientConnection::sendMessage(const std::string &message) { + if (!m_isConnected) { + throw std::runtime_error("Attempting to send on unconnected socket"); + } + + size_t bytesSent = write(m_clientSocket, message.c_str(), message.length()); + if (bytesSent != message.length()) { + throw std::runtime_error("Failed to send complete message"); + } +} + +std::string ClientConnection::getClientName() const { + return m_clientName; +} + +void ClientConnection::setClientName(const std::string &name) { + m_clientName = name; +} + +int ClientConnection::getClientPort() const { + return m_clientPort; +} + +void ClientConnection::setClientPort(int port) { + m_clientPort = port; +} + +bool ClientConnection::isConnected() { + return m_isConnected; +} diff --git a/simulation/server/utils/src/ntp_server.cpp b/simulation/server/utils/src/ntp_server.cpp new file mode 100644 index 0000000..7189934 --- /dev/null +++ b/simulation/server/utils/src/ntp_server.cpp @@ -0,0 +1,188 @@ +#include "ntp_server.h" + +#include + +#include +#include + +bool NTPServer::queryNTPServer(NTPPacket &response) { + struct addrinfo hints = {}, *addrs; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + if (getaddrinfo(m_NTPServerAddress.c_str(), "123", &hints, &addrs) != 0) { + freeaddrinfo(addrs); + throw std::runtime_error("DNS resolution failed"); + } + + int ntpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (ntpSocket < 0) { + std::cerr << "Error creating socket" << std::endl; + return false; + } + + NTPPacket request = {}; + request.flags = (NTP_VERSION << NTP_VERSION_OFFSET) | (NTPLeapIndicator::NTP_LI_NOSYNC << NTP_LEAP_INDICATOR_OFFSET) | + (NTPMode::NTP_CLIENT_MODE << NTP_MODE_OFFSET); + + auto now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + auto seconds = std::chrono::duration_cast(duration); + auto nanoSeconds = std::chrono::duration_cast(duration); + + request.transmitTime.seconds = seconds.count() + NTP_UNIX_EPOCH_DIFF; + request.transmitTime.fraction = static_cast((nanoSeconds.count() % 1000000000ULL) * 4.294967296); + + if (sendto(ntpSocket, &request, sizeof(request), 0, addrs->ai_addr, addrs->ai_addrlen) < 0) { + close(ntpSocket); + freeaddrinfo(addrs); + throw std::runtime_error("Send failed"); + } + + NTPPacket serverResponse = {}; + sockaddr_storage serverAddr; + socklen_t serverAddrLen = sizeof(serverAddr); + + ssize_t receivedBytes = recvfrom(ntpSocket, &serverResponse, sizeof(serverResponse), 0, reinterpret_cast(&serverAddr), &serverAddrLen); + + freeaddrinfo(addrs); + + if (receivedBytes < 0) { + close(ntpSocket); + throw std::runtime_error("Receive timeout or error"); + } + + convertNTPTimestamp(serverResponse.referenceTime); + convertNTPTimestamp(serverResponse.originTime); + convertNTPTimestamp(serverResponse.receiveTime); + convertNTPTimestamp(serverResponse.transmitTime); + + serverResponse.rootDelay = ntohl(serverResponse.rootDelay); + serverResponse.rootDispersion = ntohl(serverResponse.rootDispersion); + + response = serverResponse; + + close(ntpSocket); + return true; +} + +NTPPacket NTPServer::processNTPRequest(const NTPPacket &request) { + NTPPacket response = {}; + response.flags = (NTP_VERSION << NTP_VERSION_OFFSET) | (NTPLeapIndicator::NTP_LI_NONE << NTP_LEAP_INDICATOR_OFFSET) | + (NTPMode::NTP_SERVER_MODE << NTP_MODE_OFFSET); + + response.stratum = 2U; + response.poll = 4U; /* 2^4 = 16 seconds between succesive messages. */ + response.precision = -6; /* 2^-6 = 15.625 microseconds precision */ + response.rootDelay = htonl(0U); + response.rootDispersion = htonl(0U); + + /* Local reference Id */ + response.referenceId[0] = 'L'; + response.referenceId[1] = 'O'; + response.referenceId[2] = 'C'; + response.referenceId[3] = 'L'; + + auto now = std::chrono::system_clock::now(); + uint32_t currentSeconds = unixToNTPTime(std::chrono::system_clock::to_time_t(now)); + uint32_t currentFraction = static_cast(std::chrono::duration_cast(now.time_since_epoch()).count() % 1000000); + + response.referenceTime.seconds = htonl(currentSeconds); + response.referenceTime.fraction = htonl(currentFraction); + response.originTime.seconds = ntohl(request.transmitTime.seconds); + response.originTime.fraction = ntohl(request.transmitTime.fraction); + response.receiveTime.seconds = htonl(currentSeconds); + response.receiveTime.fraction = htonl(currentFraction); + response.transmitTime.seconds = htonl(currentSeconds); + response.transmitTime.fraction = htonl(currentFraction); + + NTPPacket externalResponse; + if (queryNTPServer(externalResponse)) { + response.referenceTime = externalResponse.referenceTime; + response.originTime = externalResponse.originTime; + response.receiveTime = externalResponse.receiveTime; + response.transmitTime = externalResponse.transmitTime; + } + + return response; +} + +void NTPServer::NTPServerProcedure() { + m_NTPSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (m_NTPSocket < 0) { + throw std::runtime_error("Error creating socket for NTP server: " + m_NTPServerAddress); + } + + int enable = 1; + if (setsockopt(m_NTPSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + throw std::runtime_error("Error setting socket option SO_REUSEADDR"); + } + + struct sockaddr_in serverAddr; + memset((char *)&serverAddr, 0U, sizeof(serverAddr)); + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(NTP_PORT); + + if (inet_pton(AF_INET, m_bindAddress.c_str(), &serverAddr.sin_addr) <= 0) { + close(m_NTPSocket); + throw std::runtime_error("Error converting IPv4 bind address to binary form"); + } + + if (bind(m_NTPSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { + close(m_NTPSocket); + throw std::runtime_error("Error binding NTP socket"); + } + + m_isListening = true; + + while (m_isListening) { + NTPPacket request; + struct sockaddr_in clientAddress; + socklen_t clientLength = sizeof(clientAddress); + + if (recvfrom(m_NTPSocket, &request, sizeof(request), 0, (struct sockaddr *)&clientAddress, &clientLength) <= 0) { + std::cerr << "Error receiving NTP request" << std::endl; + continue; + } + + NTPPacket response = processNTPRequest(request); + dumpNTPPacketData(response); + if (sendto(m_NTPSocket, &response, sizeof(response), 0, (struct sockaddr *)&clientAddress, clientLength) <= 0) { + std::cerr << "Error sending NTP response" << std::endl; + } + } + + m_isListening = false; + close(m_NTPSocket); +} + +void *NTPServerWrapper(void *param) { + NTPServer *server = static_cast(param); + + try { + server->NTPServerProcedure(); + } catch (std::exception &e) { + std::cerr << "NTP Server Thread Error: " << e.what() << std::endl; + } + + return nullptr; +} + +void NTPServer::startListening(const std::string &bindAddress, const std::string &NTPServerAddress) { + if (m_isListening) + return; + + m_NTPServerAddress = NTPServerAddress; + m_bindAddress = bindAddress; + + if (pthread_create(&m_NTPServerThreadId, nullptr, NTPServerWrapper, this)) { + throw std::runtime_error("Listen Error"); + } +} + +NTPServer::NTPServer() { + m_isListening = false; + m_NTPSocket = -1; +} diff --git a/simulation/server/utils/src/tcp_server.cpp b/simulation/server/utils/src/tcp_server.cpp new file mode 100644 index 0000000..9cd740c --- /dev/null +++ b/simulation/server/utils/src/tcp_server.cpp @@ -0,0 +1,141 @@ +#include "tcp_server.h" + +#include +#include + +#include +#include + +TCPServer::TCPServer() { + m_isListening = false; + m_listenPort = -1; + pthread_mutex_init(&m_mutex, nullptr); +} + +TCPServer::~TCPServer() { + pthread_mutex_destroy(&m_mutex); +} + +void TCPServer::listenThreadProcedure() { + m_listeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (m_listeningSocket < 0) { + throw std::runtime_error("Error creating socket for port " + std::to_string(m_listenPort)); + } + + int enable = 1; + if (setsockopt(m_listeningSocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + throw std::runtime_error("Error setting socket option SO_REUSEADDR"); + } + memset((char *)&m_serverAddress, 0U, sizeof(m_serverAddress)); + m_serverAddress.sin_family = AF_INET; + m_serverAddress.sin_port = htons(m_listenPort); + m_serverAddress.sin_addr.s_addr = INADDR_ANY; /* Listen to all addresses */ + + if (bind(m_listeningSocket, (struct sockaddr *)&m_serverAddress, sizeof(m_serverAddress)) < 0) { + throw std::runtime_error("Error binding socket"); + } + + listen(m_listeningSocket, 5); + m_isListening = true; + + while (m_isListening) { + ClientConnection *conn = new ClientConnection(this); + + if (!conn->acceptClient(m_listeningSocket)) { + delete conn; + break; + } + + updateClientName(conn, conn->getClientName()); + } + + m_isListening = false; +} + +void *listenThreadWrapper(void *param) { + TCPServer *server = static_cast(param); + + try { + server->listenThreadProcedure(); + } catch (...) { + std::cerr << "Listen Thread Error" << std::endl; + } + + return nullptr; +} + +void TCPServer::listenClients(int port, serverCallback callback) { + if (m_isListening) + return; + + m_listenPort = port; + m_serverCallback = callback; + + if (pthread_create(&m_listenThreadId, nullptr, listenThreadWrapper, this)) { + throw std::runtime_error("Listen Error"); + } +} + +void TCPServer::removeClient(ClientConnection *conn) { + if (conn) { + std::cerr << "Removed client: " << conn->getClientName() << std::endl; + m_connections.erase(conn->getClientName()); + } +} + +void TCPServer::updateClientName(ClientConnection *conn, std::string newName) { + pthread_mutex_lock(&m_mutex); + std::string oldClientName = conn->getClientName(); + + if (!oldClientName.empty()) { + m_connections.erase(oldClientName); + } + + conn->setClientName(newName); + + /* Handle potential client duplicates */ + std::string clientName = newName; + unsigned int clientDuplicateCount = 1U; + while (m_connections.count(clientName)) { + clientName = newName + " (" + std::to_string(clientDuplicateCount) + ")"; + clientDuplicateCount++; + } + + m_connections[clientName] = conn; + conn->setClientName(clientName); + pthread_mutex_unlock(&m_mutex); +} + +void TCPServer::messageReceived(ClientConnection *src, std::string &message) { + m_serverCallback(this, src, message); +} + +void TCPServer::sendMessage(ClientConnection *dst, const std::string &message) { + dst->sendMessage(message); +} + +void TCPServer::broadcastMessage(const std::string &message) { + for (auto &pair : m_connections) { + if (pair.second->isConnected()) { + pair.second->sendMessage(message); + } + } +} + +ClientConnection *TCPServer::getClientByName(std::string &clientName) { + if (m_connections.count(clientName)) { + return m_connections[clientName]; + } + return nullptr; +} + +void TCPServer::dumpClientList() { + for (auto &pair : m_connections) { + if (pair.second->isConnected()) { + std::cout << pair.first << std::endl; + } + } +} + +void TCPServer::stop() {}