Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input_SRV: migration to event_loop #3968

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 201 additions & 114 deletions applications/services/input/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,61 @@

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <furi.h>
#include <cli/cli.h>
#include <furi_hal_gpio.h>

#define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2)
#define INPUT_PRESS_TICKS 150
#define INPUT_LONG_PRESS_COUNTS 2
#define INPUT_THREAD_FLAG_ISR 0x00000001
#define TAG "Input"

/** Input pin state */
typedef struct {
const InputPin* pin;
// State
volatile bool state;
volatile uint8_t debounce;
FuriTimer* press_timer;
FuriPubSub* event_pubsub;
volatile uint8_t press_counter;
volatile uint32_t counter;
} InputPinState;
#define INPUT_SRV_DEBOUNCE_TIMER_TICKS 1 //ms

/** Input CLI command handler */
void input_cli(Cli* cli, FuriString* args, void* context);

// #define INPUT_DEBUG
#define INPUT_SRV_INPUT_LONG_PRESS_TICKS 150 //ms
#define INPUT_SRV_LONG_PRESS_COUNTS 2

#define GPIO_Read(input_pin) (furi_hal_gpio_read(input_pin.pin->gpio) ^ (input_pin.pin->inverted))

void input_press_timer_callback(void* arg) {
InputPinState* input_pin = arg;
InputEvent event;
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.sequence_counter = input_pin->counter;
event.key = input_pin->pin->key;
input_pin->press_counter++;
if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) {
event.type = InputTypeLong;
furi_pubsub_publish(input_pin->event_pubsub, &event);
} else if(input_pin->press_counter > INPUT_LONG_PRESS_COUNTS) {
input_pin->press_counter--;
event.type = InputTypeRepeat;
furi_pubsub_publish(input_pin->event_pubsub, &event);
}
}
#ifdef INPUT_DEBUG
#define INPUT_LOG(...) FURI_LOG_D(TAG, __VA_ARGS__)
#else
#define INPUT_LOG(...)
#endif
typedef struct {
FuriEventLoopTimer* timer;
FuriPubSub* event_pubsub;
uint32_t sequence_counter;
uint32_t press_counter;
InputType type;
InputKey key;
} InputSrvKeySequence;

void input_isr(void* _ctx) {
FuriThreadId thread_id = (FuriThreadId)_ctx;
furi_thread_flags_set(thread_id, INPUT_THREAD_FLAG_ISR);
}
typedef struct {
const InputPin* pin;
uint16_t debounce_count;
bool state;
} InputSrvKeyState;

typedef struct {
FuriEventLoop* event_loop;
FuriPubSub* event_pubsub;
FuriSemaphore* input_semaphore;
FuriEventLoopTimer* debounce_timer;
InputSrvKeyState* key_state;
InputSrvKeySequence* key_sequence;
uint32_t sequence_counter;
} InputSrv;

static void input_key_sequence_run(
InputSrvKeySequence* key_sequence,
InputType type,
uint32_t sequence_counter);

const char* input_get_key_name(InputKey key) {
for(size_t i = 0; i < input_pins_count; i++) {
if(input_pins[i].key == key) {
return input_pins[i].name;
}
}
return "Unknown";
furi_crash();
}

const char* input_get_type_name(InputType type) {
Expand All @@ -75,95 +72,185 @@ const char* input_get_type_name(InputType type) {
case InputTypeRepeat:
return "Repeat";
default:
return "Unknown";
furi_crash();
}
}

int32_t input_srv(void* p) {
UNUSED(p);

const FuriThreadId thread_id = furi_thread_get_current_id();
FuriPubSub* event_pubsub = furi_pubsub_alloc();
uint32_t counter = 1;
furi_record_create(RECORD_INPUT_EVENTS, event_pubsub);
static void input_isr_key(void* context) {
InputSrv* instance = context;
furi_semaphore_release(instance->input_semaphore);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interrupts may be missed in some edge cases

}

#ifdef INPUT_DEBUG
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
#endif
static bool input_semaphore_callback(FuriEventLoopObject* object, void* context) {
furi_assert(context);
InputSrv* instance = context;
furi_assert(object == instance->input_semaphore);

#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub);
#endif
furi_check(furi_semaphore_acquire(instance->input_semaphore, 0) == FuriStatusOk);

InputPinState pin_states[input_pins_count];
if(!furi_event_loop_timer_is_running(instance->debounce_timer)) {
furi_event_loop_timer_start(instance->debounce_timer, INPUT_SRV_DEBOUNCE_TIMER_TICKS);
}
return true;
}

static void input_debounce_timer_callback(void* context) {
furi_assert(context);
InputSrv* instance = context;
bool is_changing = false;
for(size_t i = 0; i < input_pins_count; i++) {
furi_hal_gpio_add_int_callback(input_pins[i].gpio, input_isr, thread_id);
pin_states[i].pin = &input_pins[i];
pin_states[i].state = GPIO_Read(pin_states[i]);
pin_states[i].debounce = INPUT_DEBOUNCE_TICKS_HALF;
pin_states[i].press_timer =
furi_timer_alloc(input_press_timer_callback, FuriTimerTypePeriodic, &pin_states[i]);
pin_states[i].event_pubsub = event_pubsub;
pin_states[i].press_counter = 0;
}
bool state = GPIO_Read(instance->key_state[i]);

while(1) {
bool is_changing = false;
for(size_t i = 0; i < input_pins_count; i++) {
bool state = GPIO_Read(pin_states[i]);
if(state) {
if(pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) pin_states[i].debounce += 1;
} else {
if(pin_states[i].debounce > 0) pin_states[i].debounce -= 1;
if(state) {
if(instance->key_state[i].debounce_count < INPUT_DEBOUNCE_TICKS) {
instance->key_state[i].debounce_count++;
is_changing = true;
}
} else if(instance->key_state[i].debounce_count > 0) {
instance->key_state[i].debounce_count--;
is_changing = true;
}

if(pin_states[i].debounce > 0 && pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) {
is_changing = true;
} else if(pin_states[i].state != state) {
pin_states[i].state = state;

// Common state info
InputEvent event;
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.key = pin_states[i].pin->key;

// Short / Long / Repeat timer routine
if(state) {
pin_states[i].counter = counter++;
event.sequence_counter = pin_states[i].counter;
furi_timer_start(pin_states[i].press_timer, INPUT_PRESS_TICKS);
} else {
event.sequence_counter = pin_states[i].counter;
furi_timer_stop(pin_states[i].press_timer);
while(furi_timer_is_running(pin_states[i].press_timer))
furi_delay_tick(1);
if(pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) {
event.type = InputTypeShort;
furi_pubsub_publish(event_pubsub, &event);
}
pin_states[i].press_counter = 0;
}

// Send Press/Release event
event.type = pin_states[i].state ? InputTypePress : InputTypeRelease;
furi_pubsub_publish(event_pubsub, &event);
if(!is_changing && instance->key_state[i].state != state) {
instance->key_state[i].state = state;

if(state) {
input_key_sequence_run(
&instance->key_sequence[i], InputTypePress, ++instance->sequence_counter);
} else {
input_key_sequence_run(
&instance->key_sequence[i], InputTypeRelease, instance->sequence_counter);
}
}
}

if(is_changing) {
#ifdef INPUT_DEBUG
furi_hal_gpio_write(&gpio_ext_pa4, 1);
#endif
furi_delay_tick(1);
} else {
#ifdef INPUT_DEBUG
furi_hal_gpio_write(&gpio_ext_pa4, 0);
#endif
furi_thread_flags_wait(INPUT_THREAD_FLAG_ISR, FuriFlagWaitAny, FuriWaitForever);
if(!is_changing) {
furi_event_loop_timer_stop(instance->debounce_timer);
}
}

static inline void input_send(FuriPubSub* pubsub, InputEvent* event) {
furi_pubsub_publish(pubsub, event);
INPUT_LOG(
"input_send: %s %s %x",
input_get_key_name(event->key),
input_get_type_name(event->type),
event->sequence_counter);
}

static void input_key_sequence_run(
InputSrvKeySequence* key_sequence,
InputType type,
uint32_t sequence_counter) {
InputEvent event;

switch(type) {
case InputTypePress:
key_sequence->sequence_counter = sequence_counter;
key_sequence->press_counter = 0;
key_sequence->type = InputTypePress;

event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.sequence_counter = key_sequence->sequence_counter;
event.key = key_sequence->key;
event.type = InputTypePress;
input_send(key_sequence->event_pubsub, &event);

furi_check(!furi_event_loop_timer_is_running(key_sequence->timer));
furi_event_loop_timer_start(key_sequence->timer, INPUT_SRV_INPUT_LONG_PRESS_TICKS);

key_sequence->type = InputTypeRepeat;
break;
case InputTypeRelease:
if(key_sequence->press_counter < INPUT_SRV_LONG_PRESS_COUNTS) {
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.sequence_counter = key_sequence->sequence_counter;
event.key = key_sequence->key;
event.type = InputTypeShort;
input_send(key_sequence->event_pubsub, &event);
}
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.sequence_counter = key_sequence->sequence_counter;
event.key = key_sequence->key;
event.type = InputTypeRelease;
input_send(key_sequence->event_pubsub, &event);

furi_event_loop_timer_stop(key_sequence->timer);

key_sequence->type = InputTypeRelease;
break;

default:
break;
}
}
static void input_sequence_timer_callback(void* context) {
furi_assert(context);
InputSrvKeySequence* key_sequence = context;
InputEvent event;

if(key_sequence->press_counter == INPUT_SRV_LONG_PRESS_COUNTS) {
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.sequence_counter = key_sequence->sequence_counter;
event.key = key_sequence->key;
event.type = InputTypeLong;
input_send(key_sequence->event_pubsub, &event);
} else if(key_sequence->press_counter > INPUT_SRV_LONG_PRESS_COUNTS) {
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.sequence_counter = key_sequence->sequence_counter;
event.key = key_sequence->key;
event.type = InputTypeRepeat;
input_send(key_sequence->event_pubsub, &event);
}

key_sequence->press_counter++;
}

int32_t input_srv(void* p) {
UNUSED(p);
InputSrv* instance = malloc(sizeof(InputSrv));
instance->event_pubsub = furi_pubsub_alloc();
furi_record_create(RECORD_INPUT_EVENTS, instance->event_pubsub);

instance->input_semaphore = furi_semaphore_alloc(1, 0);
instance->event_loop = furi_event_loop_alloc();
instance->debounce_timer = furi_event_loop_timer_alloc(
instance->event_loop,
input_debounce_timer_callback,
FuriEventLoopTimerTypePeriodic,
instance);

instance->key_state = malloc(sizeof(InputSrvKeyState) * input_pins_count);
for(size_t i = 0; i < input_pins_count; i++) {
furi_hal_gpio_add_int_callback(input_pins[i].gpio, input_isr_key, instance);
instance->key_state[i].pin = &input_pins[i];
instance->key_state[i].state = GPIO_Read(instance->key_state[i]);
instance->sequence_counter = 0;
}

furi_event_loop_subscribe_semaphore(
instance->event_loop,
instance->input_semaphore,
FuriEventLoopEventIn,
input_semaphore_callback,
instance);

instance->key_sequence = malloc(sizeof(InputSrvKeySequence) * input_pins_count);
Copy link
Contributor

@CookiePLMonster CookiePLMonster Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All those allocations could easily land on the stack, VLAs come in handy here (copying my remark from the previous PR).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, if you put everything on the stack and turn on debugging, then the 1 KB stack allocated to the process is sometimes not enough and everything crashes with special effects

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those structures are tiny - so wouldn't this mean that even without them on the stack, 1KB is pushing the limit very close?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all structures on the stack will take about 220 bytes.

provided that there are only 6 buttons, when the number of buttons changes, the size of the structure will increase and if the structure is placed on the stack, the implementation ceases to be universal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the expense of 1kb on the stack it is enough even with debugging enabled, without it about 500 bytes are used per process

Copy link
Contributor

@CookiePLMonster CookiePLMonster Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case the keys could stay on the heap (although maybe put them on the service heap?) while instance goes to the stack? It does not have to be all or nothing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well then what's the point of chopping it into pieces?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it's chopped into pieces already, and occupies 3 heap allocations - so by moving you save one allocation, which is better than nothing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep in mind that services are allocated in special memory region and it's quite small. Increasing stack size may bring another set of problems

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, in this case the key arrays could stay on the user heap, but the inputsrv is so small there's no reason not to put it on the stack. Before this PR it was just a bunch of local variables.

for(size_t i = 0; i < input_pins_count; i++) {
instance->key_sequence[i].sequence_counter = 0;
instance->key_sequence[i].press_counter = 0;
instance->key_sequence[i].type = InputTypeRelease;
instance->key_sequence[i].key = input_pins[i].key;
instance->key_sequence[i].timer = furi_event_loop_timer_alloc(
instance->event_loop,
input_sequence_timer_callback,
FuriEventLoopTimerTypePeriodic,
&instance->key_sequence[i]);
instance->key_sequence[i].event_pubsub = instance->event_pubsub;
}

// Start Input Service
furi_event_loop_run(instance->event_loop);

return 0;
}
Loading