albertaloop / Firmware

0 stars 0 forks source link

Create initial state machine firmware #4

Open Iyury1 opened 4 months ago

Iyury1 commented 4 months ago

Summary

The state machine will be a C program similar to the MessageTransfer_RPI program from last year. https://github.com/albertaloop/T_SWE_2022_2023/tree/master/Firmware/MessageTransfer_RPi/src

The program will be divided up into modular tasks and data structures to maximize portability between microcontrollers.

diagram of the proposed software architecture

state_machine drawio

State Machine Task

This should have its own file with a thread function containing a switch statement. Each case of the switch statement should implement the logic for the current state using a series of function calls. By keeping a small amount of control statements and moving all logic to the individual functions, the state machine should be much more readable.

Before the switch statement is called, we may want to perform common operations such as reading incoming CAN messages and UDP commands. Don't worry too much about implementing every detail yet, just create the state machine switch statement and state variables.

UDP Comms Task

Make a new source file based on udp.c from MessageTransfer_RPI. All UNIX dependencies should exist in this file only. This task will not be portable between operating systems. If we make the state machine using an RTOS, then we likely will not be able to use sockets.

Dont worry about communicating with the state machine task yet. Just make a test program and can send commands and receive telemetry. Just have the server and test client both print the messages they receive.

Main.c

Launch state machine and udp threads using pthreads. This file will also not be portable between operating systems.

Acceptance Criteria

Iyury1 commented 3 months ago

I added a CMakeLists.txt, and a script to invoke CMake commands.

When you build the project, it prints out "Invalid state" and exits immediately. This is because we didnt write in a case for STATE_NORMAL in the switch statement.

The CMakeLists.txt file only builds statemachine.c. I had to add a header file for statemachine.c, and I move the thread function declaration into the header.

To add the UDP threads, we have a couple options.

We could change the main() function in udp.c to "udpcomms_thread()". Create a header udp.h, and move the declaration there. (You don't actually need to make the function declaration extern, that is only needed for global variables defined in other source files). In this way, we would run both the telemetry and the command task in the same thread. You would need add a timeout to "recvfrom()" in the command task, check if a message was received or if it timed out without receiving a message. If there is a message, process it. If there was a timeout, then dont process any message. In both cases, return from the command function at this point. After that the telemetry task can run and broadcast a message. You might want to add a delay in the upd thread function too.

The other option is to keep the telemetry and command tasks in separate threads.

You should also create some kind of "init" or "setup" function for the sockets, and call that function once from the main thread before entering a loop. Then the udp thread function(s) will only worry about sending and receiving messages.

Iyury1 commented 3 months ago

I said before we could use 1 thread or 2 threads for the UDP comms. For now, lets keep it as 2 threads.

I see that udp_functionality.c client program is good progress. I suggest you save these UDP examples somewhere so you can refer to them in the future.

We need to make the udp.c server more complex now though.

(1) Move socket setup code into a single function like "init_udp_sockets()". Call this function before starting the udp threads. For the telemetry function, this is everything up to "sendto". For the command function, this is everything up to bind() for sure. I am not certain if bind() can be called from a separate thread or not.

(2) Add a sleep function at the end of the telemetry function. It could be something like this:

#include <time.h>
#include <errno.h>

void sleep_ms(int milliseconds) {
    struct timespec ts;
    ts.tv_sec = milliseconds / 1000;
    ts.tv_nsec = (milliseconds % 1000) * 1000000;
    while (nanosleep(&ts, &ts) && errno == EINTR);
}

Please read to the man page for whichever sleep function you use. eg. https://man7.org/linux/man-pages/man2/nanosleep.2.html

(3) Include udp.h from main.c, and create two more threads to run the udp_cmd_thread_fn() and udp_tlm_thread_fn(). Modify the CMakeLists.txt to compile udp.c by adding it to the STATE_MACHINE_SRCS variable. We want main.c to run both UDP threads and the state machine thread.

(4) Use an infinite while loop inside udp_cmd_thread_fn() and udp_tlm_thread_fn(), so they run continuously. The command thread will block on sendto(), while the telemetry thread will block on sleep().