mirror of
https://github.com/ArduPilot/ardupilot
synced 2025-01-03 14:38:30 -04:00
2bab0efc41
this allows Linux boards to trigger an external script on GPIO writes. This can be hooked up to relays with either RC options or mavlink or mission items to trigger actions that can be scripted
295 lines
7.0 KiB
C++
295 lines
7.0 KiB
C++
#include "GPIO_Sysfs.h"
|
|
|
|
#include <AP_HAL/AP_HAL.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "Util.h"
|
|
|
|
#define LOW 0
|
|
#define HIGH 1
|
|
#define assert_vpin(v_, max_, ...) do { \
|
|
if (v_ >= max_) { \
|
|
hal.console->printf("warning (%s): vpin %u out of range [0, %u)\n",\
|
|
__PRETTY_FUNCTION__, v_, max_); \
|
|
return __VA_ARGS__; \
|
|
} \
|
|
} while (0)
|
|
|
|
using namespace Linux;
|
|
|
|
static const AP_HAL::HAL& hal = AP_HAL::get_HAL();
|
|
|
|
#define UINT32_MAX_STR "4294967295"
|
|
|
|
/* Trick to use minimum stack space for each of the params */
|
|
union gpio_params {
|
|
char export_gpio[sizeof("export")];
|
|
char direction[sizeof("gpio" UINT32_MAX_STR "/direction")];
|
|
char value[sizeof("gpio" UINT32_MAX_STR "/value")];
|
|
};
|
|
|
|
#define GPIO_BASE_PATH "/sys/class/gpio/"
|
|
#define GPIO_PATH_MAX (sizeof(GPIO_BASE_PATH) + sizeof(gpio_params) - 1)
|
|
|
|
DigitalSource_Sysfs::DigitalSource_Sysfs(unsigned pin, int value_fd)
|
|
: _pin(pin)
|
|
, _value_fd(value_fd)
|
|
{
|
|
}
|
|
|
|
DigitalSource_Sysfs::~DigitalSource_Sysfs()
|
|
{
|
|
if (_value_fd >= 0) {
|
|
::close(_value_fd);
|
|
}
|
|
}
|
|
|
|
uint8_t DigitalSource_Sysfs::read()
|
|
{
|
|
char char_value;
|
|
if (::pread(_value_fd, &char_value, 1, 0) < 0) {
|
|
hal.console->printf("DigitalSource_Sysfs: Unable to read pin %u value.\n",
|
|
_pin);
|
|
return LOW;
|
|
}
|
|
return char_value - '0';
|
|
}
|
|
|
|
void DigitalSource_Sysfs::write(uint8_t value)
|
|
{
|
|
if (::pwrite(_value_fd, value == HIGH ? "1" : "0", 1, 0) < 0) {
|
|
hal.console->printf("DigitalSource_Sysfs: Unable to write pin %u value.\n",
|
|
_pin);
|
|
}
|
|
}
|
|
|
|
void DigitalSource_Sysfs::mode(uint8_t output)
|
|
{
|
|
auto gpio_sysfs = GPIO_Sysfs::from(hal.gpio);
|
|
gpio_sysfs->_pinMode(_pin, output);
|
|
}
|
|
|
|
void DigitalSource_Sysfs::toggle()
|
|
{
|
|
write(!read());
|
|
}
|
|
|
|
void GPIO_Sysfs::init()
|
|
{
|
|
#ifdef HAL_GPIO_SCRIPT
|
|
if (!_script.thread_created) {
|
|
_script.thread_created = true;
|
|
if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&GPIO_Sysfs::_gpio_script_thread, void),
|
|
"GPIO_Script", 4096, AP_HAL::Scheduler::PRIORITY_IO, -1)) {
|
|
AP_HAL::panic("Unable to create GPIO_Script thread");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void GPIO_Sysfs::pinMode(uint8_t vpin, uint8_t output)
|
|
{
|
|
#ifdef HAL_GPIO_SCRIPT
|
|
if (vpin >= n_pins && output) {
|
|
return;
|
|
}
|
|
#endif
|
|
assert_vpin(vpin, n_pins);
|
|
|
|
_export_pin(vpin);
|
|
_pinMode(pin_table[vpin], output);
|
|
}
|
|
|
|
void GPIO_Sysfs::_pinMode(unsigned int pin, uint8_t output)
|
|
{
|
|
const char *dir = output ? "out" : "in";
|
|
char path[GPIO_PATH_MAX];
|
|
|
|
int r = snprintf(path, GPIO_PATH_MAX, GPIO_BASE_PATH "gpio%u/direction",
|
|
pin);
|
|
if (r < 0 || r >= (int)GPIO_PATH_MAX
|
|
|| Util::from(hal.util)->write_file(path, "%s", dir) < 0) {
|
|
hal.console->printf("GPIO_Sysfs: Unable to set pin %u mode.\n", pin);
|
|
}
|
|
}
|
|
|
|
int GPIO_Sysfs::_open_pin_value(unsigned int pin, int flags)
|
|
{
|
|
char path[GPIO_PATH_MAX];
|
|
int fd;
|
|
|
|
int r = snprintf(path, GPIO_PATH_MAX, GPIO_BASE_PATH "gpio%u/value", pin);
|
|
if (r < 0 || r >= (int)GPIO_PATH_MAX
|
|
|| (fd = open(path, flags | O_CLOEXEC)) < 0) {
|
|
hal.console->printf("GPIO_Sysfs: Unable to get value file descriptor for pin %u.\n",
|
|
pin);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
uint8_t GPIO_Sysfs::read(uint8_t vpin)
|
|
{
|
|
assert_vpin(vpin, n_pins, LOW);
|
|
|
|
const unsigned pin = pin_table[vpin];
|
|
int fd = _open_pin_value(pin, O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
goto error;
|
|
}
|
|
|
|
char char_value;
|
|
if (::pread(fd, &char_value, 1, 0) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
close(fd);
|
|
|
|
return char_value - '0';
|
|
|
|
error:
|
|
hal.console->printf("GPIO_Sysfs: Unable to read pin %u value.\n", vpin);
|
|
return LOW;
|
|
}
|
|
|
|
void GPIO_Sysfs::write(uint8_t vpin, uint8_t value)
|
|
{
|
|
#ifdef HAL_GPIO_SCRIPT
|
|
if (vpin >= n_pins) {
|
|
_gpio_script_write(vpin, value);
|
|
return;
|
|
}
|
|
#endif
|
|
assert_vpin(vpin, n_pins);
|
|
|
|
const unsigned pin = pin_table[vpin];
|
|
int fd = _open_pin_value(pin, O_WRONLY);
|
|
|
|
if (fd < 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (::pwrite(fd, value == HIGH ? "1" : "0", 1, 0) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
close(fd);
|
|
return;
|
|
|
|
error:
|
|
hal.console->printf("GPIO_Sysfs: Unable to write pin %u value.\n", vpin);
|
|
}
|
|
|
|
void GPIO_Sysfs::toggle(uint8_t vpin)
|
|
{
|
|
write(vpin, !read(vpin));
|
|
}
|
|
|
|
AP_HAL::DigitalSource* GPIO_Sysfs::channel(uint16_t vpin)
|
|
{
|
|
assert_vpin(vpin, n_pins, nullptr);
|
|
|
|
const unsigned pin = pin_table[vpin];
|
|
int value_fd = -1;
|
|
|
|
if (_export_pin(vpin)) {
|
|
value_fd = _open_pin_value(pin, O_RDWR);
|
|
}
|
|
|
|
/* Even if we couldn't open the fd, return a new DigitalSource and let
|
|
* reads and writes fail later due to invalid. Otherwise we
|
|
* could crash in undesired places */
|
|
return new DigitalSource_Sysfs(pin, value_fd);
|
|
}
|
|
|
|
bool GPIO_Sysfs::usb_connected(void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool GPIO_Sysfs::_export_pin(uint8_t vpin)
|
|
{
|
|
#ifdef HAL_GPIO_SCRIPT
|
|
if (vpin >= n_pins) {
|
|
return false;
|
|
}
|
|
#endif
|
|
assert_vpin(vpin, n_pins, false);
|
|
|
|
const unsigned int pin = pin_table[vpin];
|
|
char gpio_path[GPIO_PATH_MAX];
|
|
int fd;
|
|
|
|
int r = snprintf(gpio_path, GPIO_PATH_MAX, GPIO_BASE_PATH "gpio%u", pin);
|
|
if (r < 0 || r >= (int) GPIO_PATH_MAX) {
|
|
goto fail_snprintf;
|
|
}
|
|
|
|
if (access(gpio_path, F_OK) == 0) {
|
|
// Already exported
|
|
return true;
|
|
}
|
|
|
|
fd = open(GPIO_BASE_PATH "export", O_WRONLY | O_CLOEXEC);
|
|
if (fd < 0) {
|
|
goto fail_open;
|
|
}
|
|
|
|
// Try to export
|
|
if (dprintf(fd, "%u", pin) < 0) {
|
|
goto fail_export;
|
|
}
|
|
|
|
close(fd);
|
|
return true;
|
|
|
|
fail_export:
|
|
close(fd);
|
|
fail_open:
|
|
fail_snprintf:
|
|
hal.console->printf("GPIO_Sysfs: Unable to export pin %u.\n", pin);
|
|
return false;
|
|
}
|
|
|
|
#ifdef HAL_GPIO_SCRIPT
|
|
/*
|
|
support using an external script triggered by a write to a GPIO
|
|
value. This is called whenever a GPIO request is made that is for an
|
|
unknown pin value. The script is called by a separate thread, and
|
|
only one script can be run at a time. This prevents the scripts
|
|
using too many resources
|
|
*/
|
|
void GPIO_Sysfs::_gpio_script_write(uint8_t vpin, uint8_t value)
|
|
{
|
|
pin_value_t pv;
|
|
pv.pin = vpin;
|
|
pv.value = value;
|
|
_script.pending.push(pv);
|
|
|
|
}
|
|
|
|
/*
|
|
thread for running GPIO scripts
|
|
*/
|
|
void GPIO_Sysfs::_gpio_script_thread(void)
|
|
{
|
|
while (true) {
|
|
// don't run more than 20/sec
|
|
hal.scheduler->delay(50);
|
|
pin_value_t pv;
|
|
if (_script.pending.pop(pv)) {
|
|
char cmd[100];
|
|
snprintf(cmd, sizeof(cmd)-1, "/bin/sh %s %u %u", HAL_GPIO_SCRIPT, pv.pin, pv.value);
|
|
hal.console->printf("Running: %s\n", cmd);
|
|
system(cmd);
|
|
}
|
|
}
|
|
}
|
|
#endif // HAL_GPIO_SCRIPT
|