Update Files
This commit is contained in:
156
Kinc/Sources/kinc/libs/drivers/button/README.md
Normal file
156
Kinc/Sources/kinc/libs/drivers/button/README.md
Normal file
@ -0,0 +1,156 @@
|
||||
# LWS GPIO Button class drivers
|
||||
|
||||
Lws provides an GPIO button controller class, this centralizes handling a set of
|
||||
up to 31 buttons for resource efficiency. Each controller has two OS timers,
|
||||
one for interrupt to bottom-half event triggering and another that runs at 5ms
|
||||
intervals only when one or more button is down.
|
||||
|
||||
Each button has its own active level control and sophisticated state tracking;
|
||||
each button can apply its own classification regime, to allow for different
|
||||
physical button characteristics, if not overridden a default one is provided.
|
||||
|
||||
Both the controller and individual buttons specify names that are used in the
|
||||
JSON events produced when the buttons perform actions.
|
||||
|
||||
## Button electronic to logical event processing
|
||||
|
||||
Buttons are monitored using GPIO interrupts since this is very cheap in the
|
||||
usual case no interaction is ongoing. There is assumed to be one interrupt
|
||||
per GPIO, but they are pointed at the same ISR, with an opaque pointer to an
|
||||
internal struct passed per-interrupt to differentiate them and bind them to a
|
||||
particular button.
|
||||
|
||||
The interrupt is set for notification of the active-going edge, usually if
|
||||
the button is pulled-up, that's the downgoing edge only. This avoids any
|
||||
ambiguity about the interrupt meaning, although oscillation is common around
|
||||
the transition region when the signal is becoming inactive too.
|
||||
|
||||
An OS timer is used to schedule a bottom-half handler outside of interrupt
|
||||
context.
|
||||
|
||||
To combat commonly-seen partial charging of the actual and parasitic network
|
||||
around the button causing drift and oscillation, the bottom-half briefly drives
|
||||
the button signal to the active level, forcing a more deterministic charge level
|
||||
if it reached the point the interrupt was triggered. This removes much of the
|
||||
unpredictable behaviour in the us range. It would be better done in the ISR
|
||||
but many OS apis cannot perform GPIO operations in interrupt context.
|
||||
|
||||
The bottom-half makes sure a monitoring timer is enabled, by refcount. This
|
||||
is the engine of the rest of the classification while any button is down. The
|
||||
monitoring timer happens per OS tick or 5ms, whichever is longer.
|
||||
|
||||
## Declaring button controllers
|
||||
|
||||
An array of button map elements if provided first mapping at least GPIOs to
|
||||
button names, and also optionally the classification regime for that button.
|
||||
|
||||
Then the button controller definition which points back to the button map.
|
||||
|
||||
```
|
||||
static const lws_button_map_t bcm[] = {
|
||||
{
|
||||
.gpio = GPIO_NUM_0,
|
||||
.smd_interaction_name = "user"
|
||||
},
|
||||
};
|
||||
|
||||
static const lws_button_controller_t bc = {
|
||||
.smd_bc_name = "bc",
|
||||
.gpio_ops = &lws_gpio_plat,
|
||||
.button_map = &bcm[0],
|
||||
.active_state_bitmap = 0,
|
||||
.count_buttons = LWS_ARRAY_SIZE(bcm),
|
||||
};
|
||||
|
||||
struct lws_button_state *bcs;
|
||||
|
||||
bcs = lws_button_controller_create(context, &bc);
|
||||
if (!bcs) {
|
||||
lwsl_err("%s: could not create buttons\n", __func__);
|
||||
goto spin;
|
||||
}
|
||||
```
|
||||
|
||||
That is all that is needed for init, button events will be issued on lws_smd
|
||||
when buttons are pressed.
|
||||
|
||||
### Regime settings
|
||||
|
||||
The classification regime is designed to reflect both the user interaction
|
||||
style and the characteristics of a particular type of button.
|
||||
|
||||
Member|Default|Meaning
|
||||
---|---|---
|
||||
ms_min_down|20ms|Down events shorter than this are ignored
|
||||
ms_min_down_longpress|300ms|Down events longer than this are reported as a long-click
|
||||
ms_up_settle|20ms|After the first indication a button is no longer down, the button is ignored for this interval
|
||||
ms_doubleclick_grace|120ms|The time allowed after a click to see if a second, double-click, is forthcoming
|
||||
ms_repeat_down|0 / disabled|If held down, interval at which to issue `stilldown` events
|
||||
flags|LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK|Control which classifications can apply
|
||||
|
||||
### lws_smd System Message Distribution Events
|
||||
|
||||
The button controller emits system messages of class `LWSSMDCL_INTERACTION`,
|
||||
using a JSON formatted payload
|
||||
|
||||
```
|
||||
{
|
||||
"type": "button",
|
||||
"src": "controller-name/button-name",
|
||||
"event": "event-name"
|
||||
}
|
||||
```
|
||||
|
||||
For example, `{"type":"button","src":"bc/user","event":"doubleclick"}`
|
||||
|
||||
JSON is used because it is maintainable, extensible, self-documenting and does
|
||||
not require a central, fragile-against-versioning specification of mappings.
|
||||
Using button names allows the same code to adapt to different hardware or
|
||||
button mappings. Button events may be synthesized for test or other purposes
|
||||
cleanly and clearly.
|
||||
|
||||
All the events are somewhat filtered, too short glitches from EMI or whatever
|
||||
are not reported. "up" and "down" events are reported for the buttons in case
|
||||
the intention is the duration of the press is meaningful to the user code, but
|
||||
more typically the user code wants to consume a higher-level classification of
|
||||
the interaction, eg, that it can be understood as a single "double-click" event.
|
||||
|
||||
Event name|Meaning
|
||||
---|---
|
||||
down|The button passes a filter for being down, useful for duration-based response
|
||||
stilldown|The regime can be configured to issue "repeat" notifications at intervals
|
||||
up|The button has come up, useful for duration-based response
|
||||
click|The button activity resulted in a classification as a single-click
|
||||
longclick|The button activity resulted in a classification as a long-click
|
||||
doubleclick|The button activity resulted in a classification as a double-click
|
||||
|
||||
Since double-click detection requires delaying click reporting until it becomes
|
||||
clear a second click isn't coming, it is enabled as a possible classification in
|
||||
the regime structure and the regime structure chosen per-button.
|
||||
|
||||
Typically user code is interested in, eg, a high level classification of what
|
||||
the button is doing, eg, a "click" event on a specific button. Rather than
|
||||
perform a JSON parse, these events can be processed as strings cheaply using
|
||||
`lws_json_simple_strcmp()`, it's dumb enough to be cheap but smart enough to
|
||||
understand enough JSON semantics to be accurate, while retaining the ability to
|
||||
change and extend the JSON, eg
|
||||
|
||||
```
|
||||
if (!lws_json_simple_strcmp(buf, len, "\"src\":", "bc/user")) {
|
||||
if (!lws_json_simple_strcmp(buf, len, "\"event\":", "click")) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Relationship between up / down and classification
|
||||
|
||||
Classification|Sequencing
|
||||
---|---
|
||||
click|down-up-click (it's classified when it went up and cannot be a longclick)
|
||||
longclick|down-longclick-up (it's classified while still down)
|
||||
doubleclick|down-up-down-doubleclick-up (classified as soon as second click down long enough)
|
||||
|
||||
If the regime is configured for it, any "down" may be followed by one or more
|
||||
"stilldown" at intervals if the button is down long enough
|
532
Kinc/Sources/kinc/libs/drivers/button/lws-button.c
Normal file
532
Kinc/Sources/kinc/libs/drivers/button/lws-button.c
Normal file
@ -0,0 +1,532 @@
|
||||
/*
|
||||
* Generic GPIO / irq buttons
|
||||
*
|
||||
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#include "private-lib-core.h"
|
||||
|
||||
typedef enum lws_button_classify_states {
|
||||
LBCS_IDLE, /* nothing happening */
|
||||
LBCS_MIN_DOWN_QUALIFY,
|
||||
|
||||
LBCS_ASSESS_DOWN_HOLD,
|
||||
LBCS_UP_SETTLE1,
|
||||
LBCS_WAIT_DOUBLECLICK,
|
||||
LBCS_MIN_DOWN_QUALIFY2,
|
||||
|
||||
LBCS_WAIT_UP,
|
||||
LBCS_UP_SETTLE2,
|
||||
} lws_button_classify_states_t;
|
||||
|
||||
/*
|
||||
* This is the opaque, allocated, non-const, dynamic footprint of the
|
||||
* button controller
|
||||
*/
|
||||
|
||||
typedef struct lws_button_state {
|
||||
#if defined(LWS_PLAT_TIMER_TYPE)
|
||||
LWS_PLAT_TIMER_TYPE timer; /* bh timer */
|
||||
LWS_PLAT_TIMER_TYPE timer_mon; /* monitor timer */
|
||||
#endif
|
||||
const lws_button_controller_t *controller;
|
||||
struct lws_context *ctx;
|
||||
short mon_refcount;
|
||||
lws_button_idx_t enable_bitmap;
|
||||
lws_button_idx_t state_bitmap;
|
||||
|
||||
uint16_t mon_timer_count;
|
||||
/* incremented each time the mon timer cb happens */
|
||||
|
||||
/* lws_button_each_t per button overallocated after this */
|
||||
} lws_button_state_t;
|
||||
|
||||
typedef struct lws_button_each {
|
||||
lws_button_state_t *bcs;
|
||||
uint16_t mon_timer_comp;
|
||||
uint16_t mon_timer_repeat;
|
||||
uint8_t state;
|
||||
/**^ lws_button_classify_states_t */
|
||||
uint8_t isr_pending;
|
||||
} lws_button_each_t;
|
||||
|
||||
#if defined(LWS_PLAT_TIMER_START)
|
||||
static const lws_button_regime_t default_regime = {
|
||||
.ms_min_down = 20,
|
||||
.ms_min_down_longpress = 300,
|
||||
.ms_up_settle = 20,
|
||||
.ms_doubleclick_grace = 120,
|
||||
.flags = LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* This is happening in interrupt context, we have to schedule a bottom half to
|
||||
* do the foreground lws_smd queueing, using, eg, a platform timer.
|
||||
*
|
||||
* All the buttons point here and use one timer per button controller. An
|
||||
* interrupt here means, "something happened to one or more buttons"
|
||||
*/
|
||||
#if defined(LWS_PLAT_TIMER_START)
|
||||
void
|
||||
lws_button_irq_cb_t(void *arg)
|
||||
{
|
||||
lws_button_each_t *each = (lws_button_each_t *)arg;
|
||||
|
||||
each->isr_pending = 1;
|
||||
LWS_PLAT_TIMER_START(each->bcs->timer);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This is the bottom-half scheduled via a timer set in the ISR. From here we
|
||||
* are allowed to hold mutexes etc. We are coming here because any button
|
||||
* interrupt arrived, we have to run another timer that tries to put whatever is
|
||||
* observed on any active button into context and either discard it or arrive at
|
||||
* a definitive event classification.
|
||||
*/
|
||||
|
||||
#if defined(LWS_PLAT_TIMER_CB)
|
||||
static LWS_PLAT_TIMER_CB(lws_button_bh, th)
|
||||
{
|
||||
lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
|
||||
lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
|
||||
const lws_button_controller_t *bc = bcs->controller;
|
||||
size_t n;
|
||||
|
||||
/*
|
||||
* The ISR and bottom-half is shared by all the buttons. Each gpio
|
||||
* IRQ has an individual opaque ptr pointing to the corresponding
|
||||
* button's dynamic lws_button_each_t, the ISR marks the button's
|
||||
* each->isr_pending and schedules this bottom half.
|
||||
*
|
||||
* So now the bh timer has fired and something to do, we need to go
|
||||
* through all the buttons that have isr_pending set and service their
|
||||
* state. Intermediate states should start / bump the refcount on the
|
||||
* mon timer. That's refcounted so it only runs when a button down.
|
||||
*/
|
||||
|
||||
for (n = 0; n < bc->count_buttons; n++) {
|
||||
|
||||
if (!each[n].isr_pending)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Hide what we're about to do from the delicate eyes of the
|
||||
* IRQ controller...
|
||||
*/
|
||||
|
||||
bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
|
||||
LWSGGPIO_IRQ_NONE, NULL, NULL);
|
||||
|
||||
each[n].isr_pending = 0;
|
||||
|
||||
/*
|
||||
* Force the network around the switch to the
|
||||
* active level briefly
|
||||
*/
|
||||
|
||||
bc->gpio_ops->set(bc->button_map[n].gpio,
|
||||
!!(bc->active_state_bitmap & (1 << n)));
|
||||
bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_WRITE);
|
||||
|
||||
if (each[n].state == LBCS_IDLE) {
|
||||
/*
|
||||
* If this is the first sign something happening on this
|
||||
* button, make sure the monitor timer is running to
|
||||
* classify its response over time
|
||||
*/
|
||||
|
||||
each[n].state = LBCS_MIN_DOWN_QUALIFY;
|
||||
each[n].mon_timer_comp = bcs->mon_timer_count;
|
||||
|
||||
if (!bcs->mon_refcount++) {
|
||||
#if defined(LWS_PLAT_TIMER_START)
|
||||
LWS_PLAT_TIMER_START(bcs->timer_mon);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Just for a us or two inbetween here, we're driving it to the
|
||||
* level we were informed by the interrupt it had enetered, to
|
||||
* force to charge on the actual and parasitic network around
|
||||
* the switch to a deterministic-ish state.
|
||||
*
|
||||
* If the switch remains in that state, well, it makes no
|
||||
* difference; if it was a pre-contact and the charge on the
|
||||
* network was left indeterminate, this will dispose it to act
|
||||
* consistently in the short term until the pullup / pulldown
|
||||
* has time to act on it or the switch comes and forces the
|
||||
* network charge state itself.
|
||||
*/
|
||||
bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_READ);
|
||||
|
||||
/*
|
||||
* We could do a better job manipulating the irq mode according
|
||||
* to the switch state. But if an interrupt comes and we have
|
||||
* done that, we can't tell if it's from before or after the
|
||||
* mode change... ie, we don't know what the interrupt was
|
||||
* telling us. We can't trust the gpio state if we read it now
|
||||
* to be related to what the irq from some time before was
|
||||
* trying to tell us. So always set it back to the same mode
|
||||
* and accept the limitation.
|
||||
*/
|
||||
|
||||
bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
|
||||
bc->active_state_bitmap & (1 << n) ?
|
||||
LWSGGPIO_IRQ_RISING :
|
||||
LWSGGPIO_IRQ_FALLING,
|
||||
lws_button_irq_cb_t, &each[n]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(LWS_PLAT_TIMER_CB)
|
||||
static LWS_PLAT_TIMER_CB(lws_button_mon, th)
|
||||
{
|
||||
lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
|
||||
lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
|
||||
const lws_button_controller_t *bc = bcs->controller;
|
||||
const lws_button_regime_t *regime;
|
||||
const char *event_name;
|
||||
int comp_age_ms;
|
||||
char active;
|
||||
size_t n;
|
||||
|
||||
bcs->mon_timer_count++;
|
||||
|
||||
for (n = 0; n < bc->count_buttons; n++) {
|
||||
|
||||
if (each->state == LBCS_IDLE) {
|
||||
each++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bc->button_map[n].regime)
|
||||
regime = bc->button_map[n].regime;
|
||||
else
|
||||
regime = &default_regime;
|
||||
|
||||
comp_age_ms = (bcs->mon_timer_count - each->mon_timer_comp) *
|
||||
LWS_BUTTON_MON_TIMER_MS;
|
||||
|
||||
active = bc->gpio_ops->read(bc->button_map[n].gpio) ^
|
||||
(!(bc->active_state_bitmap & (1 << n)));
|
||||
|
||||
// lwsl_notice("%d\n", each->state);
|
||||
|
||||
switch (each->state) {
|
||||
case LBCS_MIN_DOWN_QUALIFY:
|
||||
/*
|
||||
* We're trying to figure out if the initial down event
|
||||
* is a glitch, or if it meets the criteria for being
|
||||
* treated as the definitive start of some kind of click
|
||||
* action. To get past this, he has to be solidly down
|
||||
* for the time mentioned in the applied regime (at
|
||||
* least when we sample it).
|
||||
*
|
||||
* Significant bounce at the start will abort this try,
|
||||
* but if it's really down there will be a subsequent
|
||||
* solid down period... it will simply restart this flow
|
||||
* from a new interrupt and pass the filter then.
|
||||
*
|
||||
* The "brief drive on edge" strategy considerably
|
||||
* reduces inconsistencies here. But physical bounce
|
||||
* will continue to be observed.
|
||||
*/
|
||||
|
||||
if (!active) {
|
||||
/* We ignore stuff for a bit after discard */
|
||||
each->mon_timer_comp = bcs->mon_timer_count;
|
||||
each->state = LBCS_UP_SETTLE2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (comp_age_ms >= regime->ms_min_down) {
|
||||
|
||||
/* We made it through the initial regime filter,
|
||||
* the next step is wait and see if this down
|
||||
* event evolves into a single/double click or
|
||||
* we can call it as a long-click
|
||||
*/
|
||||
|
||||
each->mon_timer_repeat = bcs->mon_timer_count;
|
||||
each->state = LBCS_ASSESS_DOWN_HOLD;
|
||||
event_name = "down";
|
||||
goto emit;
|
||||
}
|
||||
break;
|
||||
|
||||
case LBCS_ASSESS_DOWN_HOLD:
|
||||
|
||||
/*
|
||||
* How long is he going to hold it? If he holds it
|
||||
* past the long-click threshold, we can call it as a
|
||||
* long-click and do the up processing afterwards.
|
||||
*/
|
||||
if (comp_age_ms >= regime->ms_min_down_longpress) {
|
||||
/* call it as a longclick */
|
||||
event_name = "longclick";
|
||||
each->state = LBCS_WAIT_UP;
|
||||
goto emit;
|
||||
}
|
||||
|
||||
if (!active) {
|
||||
/*
|
||||
* He didn't hold it past the long-click
|
||||
* threshold... we could end up classifying it
|
||||
* as either a click or a double-click then.
|
||||
*
|
||||
* If double-clicks are not allowed to be
|
||||
* classified, then we can already classify it
|
||||
* as a single-click.
|
||||
*/
|
||||
if (!(regime->flags &
|
||||
LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK))
|
||||
goto classify_single;
|
||||
|
||||
/*
|
||||
* Just wait for the up settle time then start
|
||||
* looking for a second down.
|
||||
*/
|
||||
each->mon_timer_comp = bcs->mon_timer_count;
|
||||
each->state = LBCS_UP_SETTLE1;
|
||||
event_name = "up";
|
||||
goto emit;
|
||||
}
|
||||
|
||||
goto stilldown;
|
||||
|
||||
case LBCS_UP_SETTLE1:
|
||||
if (comp_age_ms > regime->ms_up_settle)
|
||||
/*
|
||||
* Just block anything for the up settle time
|
||||
*/
|
||||
each->state = LBCS_WAIT_DOUBLECLICK;
|
||||
break;
|
||||
|
||||
case LBCS_WAIT_DOUBLECLICK:
|
||||
if (active) {
|
||||
/*
|
||||
* He has gone down again inside the regime's
|
||||
* doubleclick grace period... he's going down
|
||||
* the double-click path
|
||||
*/
|
||||
each->mon_timer_comp = bcs->mon_timer_count;
|
||||
each->state = LBCS_MIN_DOWN_QUALIFY2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (comp_age_ms >= regime->ms_doubleclick_grace) {
|
||||
/*
|
||||
* The grace period expired, the second click
|
||||
* was either not forthcoming at all, or coming
|
||||
* quick enough to count: we classify it as a
|
||||
* single-click
|
||||
*/
|
||||
|
||||
goto classify_single;
|
||||
}
|
||||
break;
|
||||
|
||||
case LBCS_MIN_DOWN_QUALIFY2:
|
||||
if (!active) {
|
||||
|
||||
/*
|
||||
* He went up again too quickly, classify it
|
||||
* as a single-click. It could be bounce in
|
||||
* which case you might want to increase the
|
||||
* ms_up_settle in the regime
|
||||
*/
|
||||
classify_single:
|
||||
event_name = "click";
|
||||
each->mon_timer_comp = bcs->mon_timer_count;
|
||||
each->state = LBCS_UP_SETTLE2;
|
||||
goto emit;
|
||||
}
|
||||
|
||||
if (comp_age_ms == regime->ms_min_down) {
|
||||
event_name = "down";
|
||||
goto emit;
|
||||
}
|
||||
|
||||
if (comp_age_ms > regime->ms_min_down) {
|
||||
/*
|
||||
* It's a double-click
|
||||
*/
|
||||
event_name = "doubleclick";
|
||||
each->state = LBCS_WAIT_UP;
|
||||
goto emit;
|
||||
}
|
||||
break;
|
||||
|
||||
case LBCS_WAIT_UP:
|
||||
if (!active) {
|
||||
/*
|
||||
* He has stopped pressing it
|
||||
*/
|
||||
each->mon_timer_comp = bcs->mon_timer_count;
|
||||
each->state = LBCS_UP_SETTLE2;
|
||||
event_name = "up";
|
||||
goto emit;
|
||||
}
|
||||
stilldown:
|
||||
if (regime->ms_repeat_down &&
|
||||
(bcs->mon_timer_count - each->mon_timer_repeat) *
|
||||
LWS_BUTTON_MON_TIMER_MS > regime->ms_repeat_down) {
|
||||
each->mon_timer_repeat = bcs->mon_timer_count;
|
||||
event_name = "stilldown";
|
||||
goto emit;
|
||||
}
|
||||
break;
|
||||
|
||||
case LBCS_UP_SETTLE2:
|
||||
if (comp_age_ms < regime->ms_up_settle)
|
||||
break;
|
||||
|
||||
each->state = LBCS_IDLE;
|
||||
if (!(--bcs->mon_refcount)) {
|
||||
#if defined(LWS_PLAT_TIMER_STOP)
|
||||
LWS_PLAT_TIMER_STOP(bcs->timer_mon);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
each++;
|
||||
continue;
|
||||
|
||||
emit:
|
||||
lws_smd_msg_printf(bcs->ctx, LWSSMDCL_INTERACTION,
|
||||
"{\"type\":\"button\","
|
||||
"\"src\":\"%s/%s\",\"event\":\"%s\"}",
|
||||
bc->smd_bc_name,
|
||||
bc->button_map[n].smd_interaction_name,
|
||||
event_name);
|
||||
|
||||
each++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
struct lws_button_state *
|
||||
lws_button_controller_create(struct lws_context *ctx,
|
||||
const lws_button_controller_t *controller)
|
||||
{
|
||||
lws_button_state_t *bcs = lws_zalloc(sizeof(lws_button_state_t) +
|
||||
(controller->count_buttons * sizeof(lws_button_each_t)),
|
||||
__func__);
|
||||
lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
|
||||
size_t n;
|
||||
|
||||
if (!bcs)
|
||||
return NULL;
|
||||
|
||||
bcs->controller = controller;
|
||||
bcs->ctx = ctx;
|
||||
|
||||
for (n = 0; n < controller->count_buttons; n++)
|
||||
each[n].bcs = bcs;
|
||||
|
||||
#if defined(LWS_PLAT_TIMER_CREATE)
|
||||
/* this only runs inbetween a gpio ISR and the bottom half */
|
||||
bcs->timer = LWS_PLAT_TIMER_CREATE("bcst",
|
||||
1, 0, bcs, (TimerCallbackFunction_t)lws_button_bh);
|
||||
if (!bcs->timer)
|
||||
return NULL;
|
||||
|
||||
/* this only runs when a button activity is being classified */
|
||||
bcs->timer_mon = LWS_PLAT_TIMER_CREATE("bcmon", LWS_BUTTON_MON_TIMER_MS,
|
||||
1, bcs, (TimerCallbackFunction_t)
|
||||
lws_button_mon);
|
||||
if (!bcs->timer_mon)
|
||||
return NULL;
|
||||
#endif
|
||||
|
||||
return bcs;
|
||||
}
|
||||
|
||||
void
|
||||
lws_button_controller_destroy(struct lws_button_state *bcs)
|
||||
{
|
||||
/* disable them all */
|
||||
lws_button_enable(bcs, 0, 0);
|
||||
|
||||
#if defined(LWS_PLAT_TIMER_DELETE)
|
||||
LWS_PLAT_TIMER_DELETE(bcs->timer);
|
||||
LWS_PLAT_TIMER_DELETE(bcs->timer_mon);
|
||||
#endif
|
||||
|
||||
lws_free(bcs);
|
||||
}
|
||||
|
||||
lws_button_idx_t
|
||||
lws_button_get_bit(struct lws_button_state *bcs, const char *name)
|
||||
{
|
||||
const lws_button_controller_t *bc = bcs->controller;
|
||||
int n;
|
||||
|
||||
for (n = 0; n < bc->count_buttons; n++)
|
||||
if (!strcmp(name, bc->button_map[n].smd_interaction_name))
|
||||
return 1 << n;
|
||||
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
||||
void
|
||||
lws_button_enable(lws_button_state_t *bcs,
|
||||
lws_button_idx_t _reset, lws_button_idx_t _set)
|
||||
{
|
||||
lws_button_idx_t u = (bcs->enable_bitmap & (~_reset)) | _set;
|
||||
const lws_button_controller_t *bc = bcs->controller;
|
||||
#if defined(LWS_PLAT_TIMER_START)
|
||||
lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
|
||||
#endif
|
||||
int n;
|
||||
|
||||
for (n = 0; n < bcs->controller->count_buttons; n++) {
|
||||
if (!(bcs->enable_bitmap & (1 << n)) && (u & (1 << n))) {
|
||||
/* set as input with pullup or pulldown appropriately */
|
||||
bc->gpio_ops->mode(bc->button_map[n].gpio,
|
||||
LWSGGPIO_FL_READ |
|
||||
((bc->active_state_bitmap & (1 << n)) ?
|
||||
LWSGGPIO_FL_PULLDOWN : LWSGGPIO_FL_PULLUP));
|
||||
#if defined(LWS_PLAT_TIMER_START)
|
||||
/*
|
||||
* This one is becoming enabled... the opaque for the
|
||||
* ISR is the indvidual lws_button_each_t, they all
|
||||
* point to the same ISR
|
||||
*/
|
||||
bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
|
||||
bc->active_state_bitmap & (1 << n) ?
|
||||
LWSGGPIO_IRQ_RISING :
|
||||
LWSGGPIO_IRQ_FALLING,
|
||||
lws_button_irq_cb_t, &each[n]);
|
||||
#endif
|
||||
}
|
||||
if ((bcs->enable_bitmap & (1 << n)) && !(u & (1 << n)))
|
||||
/* this one is becoming disabled */
|
||||
bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
|
||||
LWSGGPIO_IRQ_NONE, NULL, NULL);
|
||||
}
|
||||
|
||||
bcs->enable_bitmap = u;
|
||||
}
|
Reference in New Issue
Block a user