gpiod 0.0.2+1 gpiod: ^0.0.2+1 copied to clipboard
FFI for libgpiod
import 'dart:convert';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:gpiod/gpiod.dart';
import 'package:logging/logging.dart';
final Logger _log = new Logger('main');
var gpiod = GPIOD();
// https://github.com/ardera/flutter-pi/blob/master/src/plugins/gpiod.c
class GPIO_plugin {
bool initialized = false;
bool line_event_listener_should_run = false;
List<Pointer<gpiod_chip>> chips = <Pointer<gpiod_chip>>[];
int n_chips = 0;
// complete list of GPIO lines
List<Pointer<gpiod_line>> lines = <Pointer<gpiod_line>>[];
int n_lines = 0;
// GPIO lines that flutter is currently listening to
int epollfd;
// pthread_mutex_t listening_lines_mutex;
// struct gpiod_line_bulk listening_lines;
// pthread_t line_event_listener_thread;
bool should_emit_events = false;
}
void main() async {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((LogRecord rec) {
StringBuffer message = new StringBuffer();
message.write('${rec.level.name}:${rec.loggerName} ${rec.time}: ${rec.message}');
if (rec.error != null) {
message.write(' ${rec.error}');
}
print(message);
});
try {
_log.info('GPIOD version: ${Utf8.fromUtf8(gpiod.version_string())}');
// iterate through the GPIO chips
var chipiter = gpiod.chip_iter_new();
if (chipiter.address == 0) {
_log.severe("[gpiod] could not create GPIO chip iterator. gpiod_chip_iter_new");
return;//errno;
}
Map<String,Pointer<gpiod_chip>> chips = <String,Pointer<gpiod_chip>>{};
_log.info('get chips');
for (var n_chips = 0, n_lines = 0, chip = gpiod.chip_iter_next_noclose(chipiter); chip.address != 0;
n_chips++, chip = gpiod.chip_iter_next_noclose(chipiter))
{
_log.info('${n_chips} ${chip.ref.name} ${chip.ref.label}');
_log.info('chip.ref.num_lines ${chip.ref.num_lines}');
chips[chip.ref.name] = chip;
}
gpiod.chip_iter_free_noclose(chipiter);
gpiodp_ensure_gpiod_initialized();
} catch(e, st){
_log.severe("Error",e, st);
}
}
final gpio_plugin = new GPIO_plugin();
bool gpiodp_ensure_gpiod_initialized(){
if (gpio_plugin.initialized) return true;
// iterate through the GPIO chips
var chipiter = gpiod.chip_iter_new();
if (chipiter.address == 0) {
_log.severe("[flutter_gpiod] could not create GPIO chip iterator. gpiod_chip_iter_new");
return false;
}
gpio_plugin.n_chips = 0;
gpio_plugin.n_lines = 0;
for (var chip = gpiod.chip_iter_next_noclose(chipiter); chip.address != 0;
gpio_plugin.n_chips++, chip = gpiod.chip_iter_next_noclose(chipiter))
{
gpio_plugin.chips.add(chip);//[gpio_plugin.n_chips] = chip;
gpio_plugin.n_lines += gpiod.chip_num_lines(chip);
}
gpiod.chip_iter_free_noclose(chipiter);
// prepare the GPIO line list
//gpio_plugin.lines = calloc(gpio_plugin.n_lines, sizeof(struct gpiod_line*));
// if (gpio_plugin.lines.isEmpty) {
// _log.severe("could not allocate memory for GPIO line list");
// return;
// }
// iterate through the chips and put all lines into the list
for (var i = 0, j = 0; i < gpio_plugin.n_chips; i++) {
var lineiter = gpiod.line_iter_new(gpio_plugin.chips[i]);
if (lineiter.address == 0) {
_log.severe("could not create new GPIO line iterator");
return false;
}
for (var line = gpiod.line_iter_next(lineiter); line.address != 0; line = gpiod.line_iter_next(lineiter), j++) {
gpio_plugin.lines.add(line);//[j] = line;
_log.info('${j}: name: "${line.ref.name}", consumer: "${line.ref.consumer}" state: ${line.ref.state}');
}
gpiod.line_iter_free(lineiter);
}
/*fd = epoll_create1(0);
if (fd == -1) {
perror("[flutter_gpiod] Could not create line event listen epoll");
return errno;
}
gpio_plugin.epollfd = fd;
gpio_plugin.listening_lines = (struct gpiod_line_bulk) GPIOD_LINE_BULK_INITIALIZER;
gpio_plugin.listening_lines_mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER;
gpio_plugin.line_event_listener_should_run = true;
ok = pthread_create(
&gpio_plugin.line_event_listener_thread,
NULL,
gpiodp_io_loop,
NULL
);
if (ok == -1) {
perror("[flutter_gpiod] could not create line event listener thread");
return errno;
}*/
gpio_plugin.initialized = true;
return true;
}
class LineConfig {
Pointer<gpiod_line> line;
LineDirection direction;
LineRequestType request_type;
int initial_value;
int flags;
}
/// The way high voltage / low voltage should be written
/// to the line.
enum OutputMode {
pushPull, openDrain, openSource
}
line_config gpiodp_get_config({int line_handle = 0,
LineDirection direction = LineDirection.input,
OutputMode outputMode}
/*struct std_value *value,
struct line_config *conf_out,
FlutterPlatformMessageResponseHandle *responsehandle*/) {
// struct std_value *temp;
// unsigned int line_handle;
// bool has_bias;
// int ok;
var conf_out = new LineConfig();
conf_out.direction = 0;
conf_out.request_type = 0;
conf_out.flags = 0;
// if ((!value) || (value->type != kStdMap)) {
// ok = platch_respond_illegal_arg_std(
// responsehandle,
// "Expected `arg` to be a `Map<String, dynamic>`"
// );
// if (ok != 0) return ok;
//
// return EINVAL;
// }
// get the line handle from the argument map
// temp = stdmap_get_str(value, "lineHandle");
// if (temp && STDVALUE_IS_INT(*temp)) {
// line_handle = STDVALUE_AS_INT(*temp);
// } else {
// ok = platch_respond_illegal_arg_std(
// responsehandle,
// "Expected `arg['lineHandle']` to be an integer."
// );
// if (ok != 0) return ok;
//
// return EINVAL;
// }
// get the corresponding gpiod line
// if (line_handle < gpio_plugin.n_lines) {
conf_out.line = gpio_plugin.lines[line_handle];
// } else {
// ok = gpiodp_respond_illegal_line_handle(responsehandle);
// if (ok != 0) return ok;
//
// return EINVAL;
// }
// get the direction
// temp = stdmap_get_str(value, "direction");
// if (temp && (temp->type == kStdString)) {
if (direction == LineDirection.input) {
conf_out.direction = direction;
conf_out.request_type = LineRequestType.GPIOD_LINE_REQUEST_DIRECTION_INPUT;
} else if (direction == LineDirection.output){
conf_out.direction = direction;
conf_out.request_type = LineRequestType.GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
}
// else {
// goto invalid_direction;
// }
// } else {
// invalid_direction:
//
// ok = platch_respond_illegal_arg_std(
// responsehandle,
// "Expected `arg['direction']` to be a string-ification of `LineDirection`."
// );
// if (ok != 0) return ok;
//
// return EINVAL;
// }
// get the output mode
temp = stdmap_get_str(value, "outputMode");
if ((!temp) || STDVALUE_IS_NULL(*temp)) {
if (conf_out.direction == GPIOD_LINE_DIRECTION_OUTPUT) {
//goto invalid_output_mode;
throw new Exception('invalid_output_mode');
}
} else if (temp && temp->type == kStdString) {
if (conf_out->direction == GPIOD_LINE_DIRECTION_INPUT) {
goto invalid_output_mode;
}
if STREQ("OutputMode.pushPull", temp->string_value) {
// do nothing
} else if STREQ("OutputMode.openDrain", temp->string_value) {
conf_out->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
} else if STREQ("OutputMode.openSource", temp->string_value) {
conf_out->flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
} else {
goto invalid_output_mode;
}
} else {
invalid_output_mode:
ok = platch_respond_illegal_arg_std(
responsehandle,
"Expected `arg['outputMode']` to be a string-ification "
"of [OutputMode] when direction is output, "
"null when direction is input."
);
if (ok != 0) return ok;
return EINVAL;
}
// get the bias
has_bias = false;
temp = stdmap_get_str(value, "bias");
if ((!temp) || STDVALUE_IS_NULL(*temp)) {
// don't need to set any flags
} else if (temp && temp->type == kStdString) {
if STREQ("Bias.disable", temp->string_value) {
conf_out->flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE;
has_bias = true;
} else if STREQ("Bias.pullUp", temp->string_value) {
conf_out->flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
has_bias = true;
} else if STREQ("Bias.pullDown", temp->string_value) {
conf_out->flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
has_bias = true;
} else {
goto invalid_bias;
}
} else {
invalid_bias:
ok = platch_respond_illegal_arg_std(
responsehandle,
"Expected `arg['bias']` to be a stringification of [Bias] or null."
);
if (ok != 0) return ok;
return EINVAL;
}
if (has_bias && !libgpiod.line_bias) {
ok = gpiodp_respond_not_supported(
responsehandle,
"Setting line bias is not supported on this platform. "
"Expected `arg['bias']` to be null."
);
if (ok != 0) return ok;
return ENOTSUP;
}
// get the initial value
conf_out->initial_value = 0;
temp = stdmap_get_str(value, "initialValue");
if ((!temp) || STDVALUE_IS_NULL(*temp)) {
if (conf_out->direction == GPIOD_LINE_DIRECTION_INPUT) {
// do nothing.
} else if (conf_out->direction == GPIOD_LINE_DIRECTION_OUTPUT) {
goto invalid_initial_value;
}
} else if (temp && STDVALUE_IS_BOOL(*temp)) {
if (conf_out->direction == GPIOD_LINE_DIRECTION_INPUT) {
goto invalid_initial_value;
} else if (conf_out->direction == GPIOD_LINE_DIRECTION_OUTPUT) {
conf_out->initial_value = STDVALUE_AS_BOOL(*temp) ? 1 : 0;
}
} else {
invalid_initial_value:
ok = platch_respond_illegal_arg_std(
responsehandle,
"Expected `arg['initialValue']` to be null if direction is input, "
"a bool if direction is output."
);
if (ok != 0) return ok;
return EINVAL;
}
// get the active state
temp = stdmap_get_str(value, "activeState");
if (temp && (temp->type == kStdString)) {
if STREQ("ActiveState.low", temp->string_value) {
conf_out->flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
} else if STREQ("ActiveState.high", temp->string_value) {
// do nothing
} else {
goto invalid_active_state;
}
} else {
invalid_active_state:
ok = platch_respond_illegal_arg_std(
responsehandle,
"Expected `arg['activeState']` to be a stringification of [ActiveState]."
);
if (ok != 0) return ok;
return EINVAL;
}
return 0;
}
int gpiodp_request_line(
//lineHandle: _lineHandle,
String consumer,
//direction: LineDirection.output,
//outputMode: outputMode,
//bias: bias,
//activeState: activeState,
//initialValue: initialValue
) {
// struct line_config config;
// struct std_value *temp;
bool is_event_line = false;
// String consumer;
bool ok;
int fd;
// check that the arg is a map
// if (object->std_arg.type != kStdMap) {
// return platch_respond_illegal_arg_std(
// responsehandle,
// "Expected `arg` to be a `Map<String, dynamic>`"
// );
// }
// ensure GPIO is initialized
ok = gpiodp_ensure_gpiod_initialized();
if (!ok) {
return gpiodp_respond_init_failed(responsehandle);
}
// temp = stdmap_get_str(&object->std_arg, "consumer");
// if (!temp || STDVALUE_IS_NULL(*temp)) {
// consumer = NULL;
// } else if (temp && (temp->type == kStdString)) {
// consumer = temp->string_value;
// } else {
// return platch_respond_illegal_arg_std(
// responsehandle,
// "Expected `arg['consumer']` to be a string or null."
// );
// }
// get the line config
ok = gpiodp_get_config(&object->std_arg, &config, responsehandle);
if (ok != 0) return ok;
// get the triggers
temp = stdmap_get_str(&object->std_arg, "triggers");
if ((!temp) || STDVALUE_IS_NULL(*temp)) {
if (config.direction == GPIOD_LINE_DIRECTION_INPUT) {
goto invalid_triggers;
}
} else if (temp && STDVALUE_IS_LIST(*temp)) {
if (config.direction == GPIOD_LINE_DIRECTION_OUTPUT) {
goto invalid_triggers;
}
// iterate through elements in the trigger list.
for (int i = 0; i < temp->size; i++) {
if (temp->list[i].type != kStdString) {
goto invalid_triggers;
}
// now update config.request_type accordingly.
if STREQ("SignalEdge.falling", temp->list[i].string_value) {
is_event_line = true;
switch (config.request_type) {
case GPIOD_LINE_REQUEST_DIRECTION_INPUT:
config.request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
break;
case GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE:
break;
case GPIOD_LINE_REQUEST_EVENT_RISING_EDGE:
case GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES:
config.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
break;
default: break;
}
} else if STREQ("SignalEdge.rising", temp->list[i].string_value) {
is_event_line = true;
switch (config.request_type) {
case GPIOD_LINE_REQUEST_DIRECTION_INPUT:
config.request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
break;
case GPIOD_LINE_REQUEST_EVENT_RISING_EDGE:
break;
case GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE:
case GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES:
config.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
break;
default: break;
}
} else {
goto invalid_triggers;
}
}
} else {
invalid_triggers:
return platch_respond_illegal_arg_std(
responsehandle,
"Expected `arg['triggers']` to be a `List<String>` of "
"string-ifications of [SignalEdge] when direction is input "
"(no null values in the list), null when direction is output."
);
}
// finally request the line
ok = libgpiod.line_request(
config.line,
&(struct gpiod_line_request_config) {
.consumer = consumer,
.request_type = config.request_type,
.flags = config.flags
},
config.initial_value
);
if (ok == -1) {
return platch_respond_native_error_std(responsehandle, errno);
}
if (is_event_line) {
pthread_mutex_lock(&gpio_plugin.listening_lines_mutex);
fd = libgpiod.line_event_get_fd(config.line);
ok = epoll_ctl(gpio_plugin.epollfd,
EPOLL_CTL_ADD,
fd,
&(struct epoll_event) {.events = EPOLLPRI | EPOLLIN, .data.fd = fd}
);
if (ok == -1) {
perror("[flutter_gpiod] Could not add GPIO line to epollfd. epoll_ctl");
libgpiod.line_release(config.line);
return platch_respond_native_error_std(responsehandle, errno);
}
gpiod_line_bulk_add(&gpio_plugin.listening_lines, config.line);
pthread_mutex_unlock(&gpio_plugin.listening_lines_mutex);
}
return platch_respond_success_std(responsehandle, NULL);
}