Implementing an Event Loop for Effective Communication with the Base System

In the development of applications for the ACU6 system, employing an event loop is a highly recommended method for efficient communication with the base system. As applications scale, the complexity of the event loop and its associated functions often increases significantly. To maintain code clarity and manageability, it’s considered best practice to separate the event loop from other components of the application. The upcoming chapter will provide a detailed guide on constructing an effective event loop for your ACU6 application.

Inspecting the API for LED Control in the ACU6 System

Understanding the API for controlling LEDs is a crucial aspect of ACU6 application development. The API provides the necessary interfaces and protocols for interacting with the LED system effectively. The API for the LEDs can be found in System.

Key Points of the LED API

System service
  • Location in the System Service API: The LED service is a part of the broader System Service API. It operates using the IPC ID A_SERVICE_ID_SYSTEM.

  • IPC Call Process: To manipulate the LED state, the process involves an IPC call:

    • A request is sent with the ID A_IPC_MSG_LED_SET_REQ.

    • This request triggers a response from the base system, which carries the ID A_IPC_MSG_LED_SET_RSP.

Creating an new file event_handler.h for Event Handling

In the development of the ACU6 system, creating a new file named event_handler.h in /package/led-example/files/ directory is an essential step. This file will serve as an interface for event handling, designed to be agnostic of the main loop for better portability and flexibility. The event responses are managed through callbacks, while requests are handled using simple wrappers.

Key Components of event_handler.h

Header Guards and Includes: The first part of the file contains guards to prevent multiple inclusions and necessary includes for functionality.

#ifndef EVENT_HANDLER_H_
#define EVENT_HANDLER_H_

#include <actia/actia.h>
#include <stdbool.h>
#include <stdint.h>

#define IPC_ID (A_IPC_GUEST_MIN_ID + 9)

#define IPC_OUTBOX_SIZE 1
#define IPC_INBOX_SIZE 1

#define LED_ON 1
#define LED_OFF 0

typedef bool (*ipc_response_cbk)(a_ipc_msg* message);

int event_loop_start(ipc_response_cbk ipc_rsp_handler);

A_IPC_RESULT ipc_set_led(a_ipc_enum_led_pin_type led, uint8_t state);

#endif  // EVENT_HANDLER_H_

IPC Configuration Parameters: These are tunable parameters. IPC_ID identifies the IPC instance, and the sizes determine the message queue sizes.

LED State Constants: Constants for LED states are defined for ease of code readability.

Callback Response Handler Typedef: This defines the type for the callback response handler. The return value indicates if the event loop should continue.

Function Declarations: Here, the function for starting the event loop and a wrapper function for the LED request are declared.

Closing Header Guard: This ends the conditional preprocessor directive to close the header file.

Source: event_handler.h

Implementing the Event Handler in event_handler.c

For effective event handling in the ACU6 system, we create the event_handler.c file in /package/led-exampl/files/, which includes definitions corresponding to the declarations in event_handler.h. This file uses epoll to efficiently monitor events, enhancing the responsiveness of the system.

Key Elements of event_handler.c

Includes and Initialization: The file starts with necessary includes, similar to the header file, but adds epoll for event handling.

#include <actia/actia.h>
#include <sys/epoll.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

#include "event_handler.h"

A_IPC_STATIC_BUFFERS(ipc_buffers, IPC_INBOX_SIZE, IPC_OUTBOX_SIZE);

static a_ipc_handle *ipc_handle;

int event_loop_start(ipc_response_cbk ipc_handler)
{
    ipc_handle = a_ipc_init(IPC_ID, &ipc_buffers);
    if (ipc_handle == NULL) {
        printf("IPC init failed\n");
        return -1;
    }

    A_IPC_MSG_ON_STACK(msg, 256);
    A_IPC_RESULT res = a_ipc_init_ipc_open_session_req(msg);
    if (res != A_IPC_RET_OK) {
        printf("Couldn't initialize message memory %d\n", res);
        return -2;
    }

    res = a_ipc_send(ipc_handle, A_SERVICE_ID_SYSTEM, msg);
    if (res != A_IPC_RET_OK) {
        printf("Send Open session failed %d\n", res);
        return -3;
    }

    int event_count = 0;
    struct epoll_event events[3];
    int epfd = epoll_create1(0);
    int ipc_fd = a_ipc_get_fd(ipc_handle);
    struct epoll_event ipc_event = {
        .events = EPOLLIN,
        .data.fd = ipc_fd,
    };

    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, ipc_fd, &ipc_event);
    if (ret < 0) {
        printf("Couldn't create epoll");
        return -4;
    }

    bool running = true;
    while(running) {
        event_count = epoll_wait(epfd, events, a_membersof(events), -1);
        for (int i = 0; i < event_count; i++) {
            if ((events[i].data.fd == ipc_fd) && (events[i].events & EPOLLIN)) {
                int from = 0;
                res = a_ipc_recv(ipc_handle, &from, msg);
                if (res == A_IPC_RET_OK) {
                      running = ipc_handler(msg);
                } else {
                      printf("Failed to fetch message");
                       running = false;
                }
            } else {
                running = false;
            }
        }
    }

    close(epfd);
    a_ipc_destroy(ipc_handle);
    return 0;
}

A_IPC_RESULT ipc_set_led(a_ipc_enum_led_pin_type led, uint8_t state)
{
    A_IPC_MSG_ON_STACK(msg, 256);
    A_IPC_RESULT res = a_ipc_init_led_set_req(msg);
    if (res != A_IPC_RET_OK) {
        printf("Could not init A_IPC_LED_SET_REQ message %d\n", res);
        return -1;
    }
    msg->led_set_req.pin = led;
    msg->led_set_req.value = state;

    return a_ipc_send(ipc_handle, A_SERVICE_ID_SYSTEM, msg);
}

Defining Static Variables: ipc_buffers initializes the message queues using values specified in the header file. ipc_handle is a static pointer to the IPC instance, serving as the main handler for IPC communications.

The event_loop_start function in event_handler.c is essential for initiating communication with the base system using IPC (Inter-Process Communication). This function lays the groundwork for establishing a session and handling responses.

IPC Initialization: The function begins with the initialization of IPC using a_ipc_init, tying together the IPC_ID with the message queue.

Opening Session Request: The function then proceeds to send an open session request. The macro A_IPC_MSG_ON_STACK is used to create a message, and a_ipc_init_ipc_open_session_req sets its parameters. The message is sent to the system service using a_ipc_send.

Epoll Setup: Initialize epoll and add the IPC file descriptor to it.

Running the Event Loop: The loop runs continuously, waiting for events and processing them.

Function Setup: Initialize an IPC message and check for successful initialization.

Setting LED State: Assign the LED pin and its desired state to the message.

Sending the Message: Send the configured message to the system service.

Source: event_handler.c

Enhanced Response Handler in LED Control

Delve into the practical implementation of response handling in led_example.c, focusing on robust and efficient message processing. Update led_example.c with the following content:

#include <actia/actia.h>
#include <stdio.h>
#include <unistd.h>
#include "event_handler.h"

static bool ipc_handler(a_ipc_msg *msg)
{
    static bool led_on = false;
    bool keep_going = true;
    A_IPC_RESULT res;
    switch (msg->info.type) {
    case A_IPC_MSG_IPC_OPEN_SESSION_RSP:
        if (msg->ipc_open_session_rsp.result != A_IPC_ENUM_IPC_OPEN_SESSION_RESULT_OK) {
            printf("Error Opening Session: %d", msg->ipc_open_session_rsp.result);
            keep_going = false;
        } else {
            res = ipc_set_led(A_IPC_ENUM_LED_PIN_TYPE_GREEN, LED_ON);
            if (res != A_IPC_RET_OK) {
                printf("Couldn't send message");
                keep_going = false;
            }
            led_on = true;
        }
        break;
    case A_IPC_MSG_LED_SET_RSP:
        if (msg->led_set_rsp.result != A_IPC_ENUM_LED_RESULT_OK) {
            printf("Error lighting LED: %d", msg->led_set_rsp.result);
            keep_going = false;
        } else if (led_on) {
            sleep(1);
            res = ipc_set_led(A_IPC_ENUM_LED_PIN_TYPE_GREEN, LED_OFF);
            if (res != A_IPC_RET_OK) {
                printf("Couldn't send message");
                keep_going = false;
            }
            led_on = false;
        } else {
            keep_going = false;
        }
        break;
    default:
        printf("Unrecognized response recieved");
        keep_going = false;
        break;
    }
    return keep_going;
}

int main(void)
{
    printf("LEDs are fun\n");
    event_loop_start(ipc_handler);

    return 0;
}

Essential headers: Start by including the essential headers, enabling response processing and LED control functionalities.

ipc_handler: The ipc_handler function is crucial for managing different types of messages and ensuring appropriate actions are taken.

Ensure open sessions: Ensure the session opens correctly before proceeding. Successful sessions trigger the green LED, indicating operational status.

LED’s behavior: Managing the LED’s behavior is key. Here, the LED is turned off after a short duration, showcasing a basic response handling scenario.

Fallback: The default case is a fallback for unanticipated message types, ensuring the program’s stability.

Event loop: The revised main function illustrates the start of the event loop, emphasizing the interaction with the response handler for LED control.

Source: led_example.c

Adjust Makefile and Verify

Adjustments to the Makefile are essential for integrating new components into the build process:

Add led_example.c and event_handler.c to the compilation sources, ensuring they are compiled with the rest of the project.

TARGET_BIN  = led-example

CFLAGS  += -MMD -MP -Wall -Wextra -pedantic -Werror -Wno-error=deprecated-declarations

LIBS    += -lactia

C_SRCS   = led_example.c
C_SRCS   += event_handler.c

OBJS     += $(patsubst %.c, $(BUILD_DIR)/%.o, $(C_SRCS))
OBJS     += $(patsubst %.S, $(BUILD_DIR)/%.o, $(ASM_SRCS))

DEPS = $(OBJS:%.o=%.d)

BUILD_DIR = build

.PHONY: all

all: $(BUILD_DIR)/$(TARGET_BIN)

$(BUILD_DIR)/$(TARGET_BIN): $(OBJS)
	@echo LINK $@ $(LDFLAGS)
	@$(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@

$(BUILD_DIR)/%.o: %.c
	@echo CC $<
	@mkdir -p $(@D)
	@$(CC) -c $(CFLAGS) $< -o $@

install: all
	@install -m 755 $(BUILD_DIR)/$(TARGET_BIN) $(DESTDIR)/$(PREFIX)/bin

clean:
	@-rm -rf $(BUILD_DIR)/

-include $(DEPS)

Verification Steps:

  1. make buildroot-led-example-dirclean: Run this command in the project directory. It cleans the directory, ensuring a fresh build environment.

  2. make: Compiles the project with the new source files included.

  3. make flash: Flashes the compiled program onto the target device.

  4. ssh root@198.18.1.3: Connects to the device via SSH for direct interaction.

  5. led-example: Executes the program on the device.

Post-execution, verify that the program exits without complications. Observe the behavior of the green LED; when the program starts the message “LEDs are fun” should be printed and the middle, green LED will be lit for a couple of seconds, indicating successful operation.

Source: Makefile