Add a Timer to the Event Loop
One of the versatile features of epoll is its ability to handle various file descriptors, including timers. This functionality enhances the event loop by allowing timed events.
Expanding Interface
To utilize the timer effectively, the interface must support arming and disarming the timer. Additionally, a callback mechanism is required to handle the expiration of the timer. The following modifications in event_handler.h implement these functionalities:
#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);
typedef bool (*timer_cbk)(void);
A_IPC_RESULT ipc_set_led(a_ipc_enum_led_pin_type led, uint8_t state);
int timer_arm(uint32_t start_time, uint32_t interval);
int event_loop_start(ipc_response_cbk ipc_handler, timer_cbk timeout_handler);
#endif // EVENT_HANDLER_H_
The timer_arm function is introduced to set up the timer, and the event_loop_start function is expanded to include a new callback type for timer events. These enhancements allow for more sophisticated event handling in the system.
Adding Timer to Epoll
Start by modifying event_handler.c to integrate a timer with epoll, including adding necessary headers and initializing a timer file descriptor.
Initial Setup
#include <sys/timerfd.h>
static int timer_fd;
Include the sys/timerfd.h header and define a static variable timer_fd for the timer file descriptor at the beginning of the file.
Configuring the Timer
timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
if (timer_fd < 0) {
printf("Couldn't create timer");
return -5;
}
struct epoll_event timer_event = {
.events = EPOLLIN,
.data.fd = timer_fd,
};
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, timer_fd, &timer_event);
if (ret < 0) {
perror("epoll_add_fd for timer_fd");
return -6;
}
This code creates a non-blocking timer and adds it to the epoll file descriptor set. The timer is initially disarmed and will be monitored alongside IPC events.
Modifying the Event Loop
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 if ((events[i].data.fd == timer_fd) && (events[i].events & EPOLLIN)) {
uint64_t timers = 0;
// ! to suppress warning
(void)!read(events[i].data.fd, &timers, 8);
running = timeout_handler();
} else {
running = false;
}
In the while loop of the event loop, include a condition to check for timer_fd. If the timer event is triggered, read the file descriptor to acknowledge the alarm and then call the timeout handler.
Arming and Disarming the Timer
int timer_arm(uint32_t start_time, uint32_t interval)
{
struct itimerspec ts = {0};
ts.it_value.tv_sec = (time_t)start_time;
ts.it_interval.tv_sec = (time_t)interval;
return timerfd_settime(timer_fd, 0, &ts, NULL);
}
The timer_arm function is used for setting the start time and interval for the timer, enabling activation and deactivation as needed.
Adding Logic to the Main Program
The goal is to create a program that sequences the lighting of LEDs in a rolling pattern. This functionality is implemented in led_example.c. Start by defining the LED order and setting up iteration variables.
Defining LED Order and Iteration Variables
#define NUM_LEDS 3
static a_ipc_enum_led_pin_type leds[NUM_LEDS] = {
A_IPC_ENUM_LED_PIN_TYPE_RED,
A_IPC_ENUM_LED_PIN_TYPE_GREEN,
A_IPC_ENUM_LED_PIN_TYPE_BLUE
};
static int ticks = 0;
static int max_ticks;
The max_ticks variable will determine the number of iterations as provided by the user, and ticks tracks the current iteration.
Implementing the Timer Callback
static bool timeout_handler(void)
{
bool keep_going = true;
A_IPC_RESULT res = ipc_set_led(leds[ticks % NUM_LEDS], LED_OFF);
if (res != A_IPC_RET_OK) {
printf("Couldn't send message");
keep_going = false;
}
++ticks;
if ((ticks < max_ticks) || (max_ticks == 0)) {
res = ipc_set_led(leds[ticks % NUM_LEDS], LED_ON);
if (res != A_IPC_RET_OK) {
printf("Couldn't send message");
keep_going = false;
}
} else {
keep_going = false;
}
return keep_going;
}
This callback function controls the LED sequence, turning off the current LED and lighting the next one in order.
Updating the IPC Handler
static bool ipc_handler(a_ipc_msg *msg)
{
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(leds[0], LED_ON);
if (res != A_IPC_RET_OK) {
printf("Couldn't send message");
keep_going = false;
}
int ret = timer_arm(1, 1);
if (ret < 0) {
printf("Couldn't arm timer");
keep_going = false;
}
}
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;
}
break;
default:
printf("Unrecognized response recieved");
keep_going = false;
break;
}
return keep_going;
}
The IPC handler now primarily focuses on session management and arming the timer, with the rolling LED logic shifted to the timer callback.
Argument Handling in Main Function
int main(int argc, char **argv)
{
if (argc < 2) {
max_ticks = 0;
} else if (argc == 2) {
max_ticks = atoi(argv[1]);
} else {
printf("Usage: led-example <n>, leave blank to run indefinitly");
return 0;
}
printf("LEDs are fun\n");
event_loop_start(ipc_handler, timeout_handler);
return 0;
}
This main function allows for an optional command-line argument to specify the number of LED sequence iterations, with indefinite running as the default behavior if no argument is provided.
Verification Process
To verify the proper functioning of the updated application, follow the standard rebuild and execution steps. Additionally, run the program with an argument to ensure it stops as expected.
Rebuilding the Application
make buildroot-led-example-dirclean: Clean the build directory to ensure a fresh start.
make: Compile the application with the new changes.
make flash: Flash the compiled program onto the target device.
ssh root@198.18.1.3: Connect to the device via SSH for direct interaction.
led-example 3: Run the program with the argument ‘3’ to test the LED sequence.
Counting LED Pulses
After executing the program, observe the behavior of the LEDs. Count the number of times the LEDs “pulse” or change state. Verify that this count matches the provided argument, which in this case is three. Ensure that the program stops after completing three pulses.