Add files via upload
|
@ -0,0 +1,416 @@
|
|||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <stdbool.h>
|
||||
#include <termios.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/util.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#define MAX_MTU 9000
|
||||
|
||||
bool verbose = false;
|
||||
|
||||
const char *default_master = "/dev/ttyAMA0";
|
||||
const int default_baudrate = 115200;
|
||||
const char *defualt_out_addr = "127.0.0.1:14600";
|
||||
const char *default_in_addr = "127.0.0.1:14601";
|
||||
|
||||
uint8_t ch_count = 0;
|
||||
uint16_t ch[14];
|
||||
|
||||
struct bufferevent *serial_bev;
|
||||
struct sockaddr_in sin_out = {
|
||||
.sin_family = AF_INET,
|
||||
};
|
||||
int out_sock;
|
||||
|
||||
static void print_usage()
|
||||
{
|
||||
printf("Usage: mavfwd [OPTIONS]\n"
|
||||
"Where:\n"
|
||||
" --master Local MAVLink master port (%s by default)\n"
|
||||
" --baudrate Serial port baudrate (%d by default)\n"
|
||||
" --out Remote output port (%s by default)\n"
|
||||
" --in Remote input port (%s by default)\n"
|
||||
" --channels RC override channels to parse after first 4 and call /root/channels.sh $ch $val, default 0\n"
|
||||
" --verbose display each packet, default not\n"
|
||||
" --help Display this help\n",
|
||||
default_master, default_baudrate, defualt_out_addr,
|
||||
default_in_addr);
|
||||
}
|
||||
|
||||
static speed_t speed_by_value(int baudrate)
|
||||
{
|
||||
switch (baudrate) {
|
||||
case 9600:
|
||||
return B9600;
|
||||
case 19200:
|
||||
return B19200;
|
||||
case 38400:
|
||||
return B38400;
|
||||
case 57600:
|
||||
return B57600;
|
||||
case 115200:
|
||||
return B115200;
|
||||
case 230400:
|
||||
return B230400;
|
||||
case 460800:
|
||||
return B460800;
|
||||
case 500000:
|
||||
return B500000;
|
||||
case 921600:
|
||||
return B921600;
|
||||
case 1500000:
|
||||
return B1500000;
|
||||
default:
|
||||
printf("Not implemented baudrate %d\n", baudrate);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
static bool parse_host_port(const char *s, struct in_addr *out_addr,
|
||||
in_port_t *out_port)
|
||||
{
|
||||
char host_and_port[32] = { 0 };
|
||||
strncpy(host_and_port, s, sizeof(host_and_port) - 1);
|
||||
|
||||
char *colon = strchr(host_and_port, ':');
|
||||
if (NULL == colon) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*colon = '\0';
|
||||
const char *host = host_and_port, *port_ptr = colon + 1;
|
||||
|
||||
const bool is_valid_addr = inet_aton(host, out_addr) != 0;
|
||||
if (!is_valid_addr) {
|
||||
printf("Cannot parse host `%s'.\n", host);
|
||||
return false;
|
||||
}
|
||||
|
||||
int port;
|
||||
if (sscanf(port_ptr, "%d", &port) != 1) {
|
||||
printf("Cannot parse port `%s'.\n", port_ptr);
|
||||
return false;
|
||||
}
|
||||
*out_port = htons(port);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void signal_cb(evutil_socket_t fd, short event, void *arg)
|
||||
{
|
||||
struct event_base *base = arg;
|
||||
(void)event;
|
||||
|
||||
printf("%s signal received\n", strsignal(fd));
|
||||
event_base_loopbreak(base);
|
||||
}
|
||||
|
||||
static void dump_mavlink_packet(unsigned char *data, const char *direction)
|
||||
{
|
||||
uint8_t seq;
|
||||
uint8_t sys_id;
|
||||
uint8_t comp_id;
|
||||
uint32_t msg_id;
|
||||
|
||||
if(data[0] == 0xFE) { //mavlink 1
|
||||
seq = data[2];
|
||||
sys_id = data[3];
|
||||
comp_id = data[4];
|
||||
msg_id = data[5];
|
||||
} else { //mavlink 2
|
||||
seq = data[4];
|
||||
sys_id = data[5];
|
||||
comp_id = data[6];
|
||||
msg_id = data[7];
|
||||
}
|
||||
|
||||
if (verbose) printf("%s %#02x sender %d/%d\t%d\t%d\n", direction, data[0], sys_id, comp_id, seq, msg_id);
|
||||
|
||||
uint16_t val;
|
||||
|
||||
//RC_CHANNELS ( #65 ) hook
|
||||
if(msg_id == 65 && ch_count > 0) {
|
||||
uint8_t offset = 18; //15 = 1ch;
|
||||
for(uint8_t i=0; i < ch_count; i++) {
|
||||
val = data[offset] | (data[offset+1] << 8);
|
||||
if(ch[i] != val) {
|
||||
ch[i] = val;
|
||||
char buff[44];
|
||||
sprintf(buff, "/root/channels.sh %d %d &", i+5, val);
|
||||
system(buff);
|
||||
if (verbose) printf("called /root/channels.sh %d %d\n", i+5, val);
|
||||
}
|
||||
offset = offset + 2;
|
||||
} //for
|
||||
} //msg_id
|
||||
}
|
||||
|
||||
/* https://discuss.ardupilot.org/uploads/short-url/vS0JJd3BQfN9uF4DkY7bAeb6Svd.pdf
|
||||
* 0. Message header, always 0xFE
|
||||
* 1. Message length
|
||||
* 2. Sequence number -- rolls around from 255 to 0 (0x4e, previous was 0x4d)
|
||||
* 3. System ID - what system is sending this message
|
||||
* 4. Component ID- what component of the system is sending the message
|
||||
* 5. Message ID (e.g. 0 = heartbeat and many more! Don’t be shy, you can add too..)
|
||||
*/
|
||||
static bool get_mavlink_packet(unsigned char *in_buffer, int buf_len,
|
||||
int *packet_len)
|
||||
{
|
||||
if (buf_len < 6 /* header */) {
|
||||
return false;
|
||||
}
|
||||
assert(in_buffer[0] == 0xFE || in_buffer[0] == 0xFD); //mavlink 1 or 2
|
||||
|
||||
uint8_t msg_len = in_buffer[1];
|
||||
if (in_buffer[0] == 0xFE)
|
||||
*packet_len = 6 /* header */ + msg_len + 2 /* crc */; //mavlink 1
|
||||
else
|
||||
*packet_len = 10 /* header */ + msg_len + 2 /* crc */; //mavlink 2
|
||||
if (buf_len < *packet_len)
|
||||
return false;
|
||||
|
||||
dump_mavlink_packet(in_buffer, ">>");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns num bytes before first occurrence of 0xFE or full data length
|
||||
static size_t until_first_fe(unsigned char *data, size_t len)
|
||||
{
|
||||
for (size_t i = 1; i < len; i++) {
|
||||
if (data[i] == 0xFE || data[i] == 0xFD) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void serial_read_cb(struct bufferevent *bev, void *arg)
|
||||
{
|
||||
struct evbuffer *input = bufferevent_get_input(bev);
|
||||
int packet_len, in_len;
|
||||
struct event_base *base = arg;
|
||||
|
||||
while ((in_len = evbuffer_get_length(input))) {
|
||||
unsigned char *data = evbuffer_pullup(input, in_len);
|
||||
if (data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find first 0xFE and skip everything before it
|
||||
if (*data != 0xFE && *data != 0xFD) {
|
||||
int bad_len = until_first_fe(data, in_len);
|
||||
if (verbose) printf(">> Skipping %d bytes of unknown data\n",
|
||||
bad_len);
|
||||
evbuffer_drain(input, bad_len);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!get_mavlink_packet(data, in_len, &packet_len))
|
||||
return;
|
||||
|
||||
// TODO: check CRC correctness and skip bad packets
|
||||
|
||||
if (sendto(out_sock, data, packet_len, 0,
|
||||
(struct sockaddr *)&sin_out,
|
||||
sizeof(sin_out)) == -1) {
|
||||
perror("sendto()");
|
||||
event_base_loopbreak(base);
|
||||
}
|
||||
|
||||
evbuffer_drain(input, packet_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void serial_event_cb(struct bufferevent *bev, short events, void *arg)
|
||||
{
|
||||
(void)bev;
|
||||
struct event_base *base = arg;
|
||||
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
|
||||
printf("Serial connection closed\n");
|
||||
event_base_loopbreak(base);
|
||||
}
|
||||
}
|
||||
|
||||
static void in_read(evutil_socket_t sock, short event, void *arg)
|
||||
{
|
||||
(void)event;
|
||||
unsigned char buf[MAX_MTU];
|
||||
struct event_base *base = arg;
|
||||
ssize_t nread;
|
||||
|
||||
nread = recvfrom(sock, &buf, sizeof(buf) - 1, 0, NULL, NULL);
|
||||
if (nread == -1) {
|
||||
perror("recvfrom()");
|
||||
event_base_loopbreak(base);
|
||||
}
|
||||
|
||||
assert(nread > 6);
|
||||
|
||||
dump_mavlink_packet(buf, "<<");
|
||||
|
||||
bufferevent_write(serial_bev, buf, nread);
|
||||
}
|
||||
|
||||
static int handle_data(const char *port_name, int baudrate,
|
||||
const char *out_addr, const char *in_addr)
|
||||
{
|
||||
struct event_base *base = NULL;
|
||||
struct event *sig_int = NULL, *in_ev = NULL;
|
||||
int ret = EXIT_SUCCESS;
|
||||
|
||||
int serial_fd = open(port_name, O_RDWR | O_NOCTTY);
|
||||
if (serial_fd < 0) {
|
||||
printf("Error while openning port %s: %s\n", port_name,
|
||||
strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
};
|
||||
evutil_make_socket_nonblocking(serial_fd);
|
||||
|
||||
struct termios options;
|
||||
tcgetattr(serial_fd, &options);
|
||||
cfsetspeed(&options, speed_by_value(baudrate));
|
||||
|
||||
options.c_cflag &= ~CSIZE; // Mask the character size bits
|
||||
options.c_cflag |= CS8; // 8 bit data
|
||||
options.c_cflag &= ~PARENB; // set parity to no
|
||||
options.c_cflag &= ~PARODD; // set parity to no
|
||||
options.c_cflag &= ~CSTOPB; // set one stop bit
|
||||
|
||||
options.c_cflag |= (CLOCAL | CREAD);
|
||||
|
||||
options.c_oflag &= ~OPOST;
|
||||
|
||||
options.c_lflag &= 0;
|
||||
options.c_iflag &= 0; // disable software flow controll
|
||||
options.c_oflag &= 0;
|
||||
|
||||
cfmakeraw(&options);
|
||||
tcsetattr(serial_fd, TCSANOW, &options);
|
||||
|
||||
out_sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
int in_sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
struct sockaddr_in sin_in = {
|
||||
.sin_family = AF_INET,
|
||||
};
|
||||
if (!parse_host_port(in_addr, (struct in_addr *)&sin_in.sin_addr.s_addr,
|
||||
&sin_in.sin_port))
|
||||
goto err;
|
||||
if (!parse_host_port(out_addr,
|
||||
(struct in_addr *)&sin_out.sin_addr.s_addr,
|
||||
&sin_out.sin_port))
|
||||
goto err;
|
||||
|
||||
if (bind(in_sock, (struct sockaddr *)&sin_in, sizeof(sin_in))) {
|
||||
perror("bind()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
printf("Listening on %s...\n", in_addr);
|
||||
|
||||
base = event_base_new();
|
||||
|
||||
sig_int = evsignal_new(base, SIGINT, signal_cb, base);
|
||||
event_add(sig_int, NULL);
|
||||
// it's recommended by libevent authors to ignore SIGPIPE
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
serial_bev = bufferevent_socket_new(base, serial_fd, 0);
|
||||
bufferevent_setcb(serial_bev, serial_read_cb, NULL, serial_event_cb,
|
||||
base);
|
||||
bufferevent_enable(serial_bev, EV_READ);
|
||||
|
||||
in_ev = event_new(base, in_sock, EV_READ | EV_PERSIST, in_read, NULL);
|
||||
event_add(in_ev, NULL);
|
||||
|
||||
event_base_dispatch(base);
|
||||
|
||||
err:
|
||||
if (serial_fd >= 0)
|
||||
close(serial_fd);
|
||||
|
||||
if (serial_bev)
|
||||
bufferevent_free(serial_bev);
|
||||
|
||||
if (in_ev) {
|
||||
event_del(in_ev);
|
||||
event_free(in_ev);
|
||||
}
|
||||
|
||||
if (sig_int)
|
||||
event_free(sig_int);
|
||||
|
||||
if (base)
|
||||
event_base_free(base);
|
||||
|
||||
libevent_global_shutdown();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const struct option long_options[] = {
|
||||
{ "master", required_argument, NULL, 'm' },
|
||||
{ "baudrate", required_argument, NULL, 'b' },
|
||||
{ "out", required_argument, NULL, 'o' },
|
||||
{ "in", required_argument, NULL, 'i' },
|
||||
{ "channels", required_argument, NULL, 'c' },
|
||||
{ "verbose", no_argument, NULL, 'v' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
const char *port_name = default_master;
|
||||
int baudrate = default_baudrate;
|
||||
const char *out_addr = defualt_out_addr;
|
||||
const char *in_addr = default_in_addr;
|
||||
|
||||
int opt;
|
||||
int long_index = 0;
|
||||
while ((opt = getopt_long_only(argc, argv, "", long_options,
|
||||
&long_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'm':
|
||||
port_name = optarg;
|
||||
break;
|
||||
case 'b':
|
||||
baudrate = atoi(optarg);
|
||||
break;
|
||||
case 'o':
|
||||
out_addr = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
in_addr = optarg;
|
||||
break;
|
||||
case 'c':
|
||||
ch_count = atoi(optarg);
|
||||
if(ch_count == 0) printf("rc_channels_override monitoring disabled\n");
|
||||
else printf("rc_channels_override monitoring %d channels after first 4\n", ch_count);
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'h':
|
||||
default:
|
||||
print_usage();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return handle_data(port_name, baudrate, out_addr, in_addr);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
### Зачем нужен mavfwd
|
||||
`mavfwd` в первую очередь необходим для связи телеметрийного потока wifibroadcast,
|
||||
разделенного на входящий и исходящий на разных udp-портах, с uart камеры, который
|
||||
подключен к uart полетного контроллера UAV, настроенного на обмен телеметрией.
|
||||
Поддерживается mavlink 1 и 2 версий. Подробности о параметрах доступны по `mavfwd --help`.
|
||||
|
||||
Во вторую очередь, mavfwd способен мониторить передаваемые в mavlink-пакете [RC_CHANNELS #65](https://mavlink.io/en/messages/common.html#RC_CHANNELS)
|
||||
значения каналов с 4-го и выше, указанное в параметре --channels числом. По изменению значений каналов вызывается bash-скрипт /root/channels.sh,
|
||||
передавая ему параметрами номер канала и его значение. Это нужно, чтобы организовать какое-то управление хост-системой (камерой), например ее перезагрузку
|
||||
или настройку каких-то параметров стримера. В приложенном примере производятся:
|
||||
* переключение разрешений 1080p / 720p;
|
||||
* включение и отключение ircut камеры;
|
||||
* пороговое изменение яркости, три режима, для подбора нужного под текущие условия освещённости (яркий день, обычный день, ночь).
|
|
@ -0,0 +1,13 @@
|
|||
### Why you need mavfwd
|
||||
`mavfwd` is primarily necessary for the connection of the telemetry stream wifibroadcast,
|
||||
divided into incoming and outgoing on different udp ports, from a uart camera, which
|
||||
connected to the uart flight controller UAV, configured to exchange telemetry.
|
||||
maxlink 1 and 2 versions are supported. Details of the parameters are available for `mavfwd --help`.
|
||||
|
||||
In the second place, mavfwd is able to monitor transmitted in the mavlink-pack [RC_CHANNELS #65](htts://mavlink.io/en/messages/common.html#RC_CHANNELS)
|
||||
the channel value with 4 and above, specified in the --channels number parameter. By changing the values of the channels, the bash-script /root/channels.sh is called,
|
||||
passing the channel number and its value. This is necessary to organize some kind of control of the host system (camera), for example, its reboot
|
||||
or setting up some streamer parameters. The attached example makes:
|
||||
* 180p / 720p resolution switching;
|
||||
* Turning on and off the ircut camera;
|
||||
* threshold change of brightness, three modes, for selecting the necessary under current conditions of illumination (strong day, ordinary day, night).
|
After Width: | Height: | Size: 153 KiB |
|
@ -0,0 +1,27 @@
|
|||
text,type,typeId,Description
|
||||
,Rectangle,creately.basic.rectangle,
|
||||
GROUND (NVR),Text,creately.basic.text,
|
||||
WIFI,WiFi,creately.material-icons.wifi_twotone_2,
|
||||
rtl8812au,Rectangle,creately.basic.rectangle,
|
||||
wifi adapter driver in monitor mode,Rectangle,creately.basic.rectangle,
|
||||
wfb-rx,Rectangle,creately.basic.rectangle,
|
||||
LAN / WLAN or usb network,Ellipse,creately.basic.ellipse,
|
||||
,Rectangle,creately.basic.rectangle,
|
||||
PC or Pocket,Text,creately.basic.text,
|
||||
GS telemetry program,Computer,creately.material-icons.computer_twotone_2,
|
||||
udp:14551,Text,creately.basic.text,
|
||||
udp:14550,Text,creately.basic.text,
|
||||
,Rectangle,creately.basic.rectangle,
|
||||
CAM,Text,creately.basic.text,
|
||||
mavfwd,Rectangle,creately.basic.rectangle,
|
||||
wfb-tx,Rectangle,creately.basic.rectangle,
|
||||
wfb-rx,Rectangle,creately.basic.rectangle,
|
||||
udp:14550,Text,creately.basic.text,
|
||||
udp:14551,Text,creately.basic.text,
|
||||
FC > cam uart,Rectangle,creately.basic.rectangle,
|
||||
wifi adapter driver in monitor mode,Rectangle,creately.basic.rectangle,
|
||||
rtl8812au,Rectangle,creately.basic.rectangle,
|
||||
WIFI,WiFi,creately.material-icons.wifi_twotone_2,
|
||||
wfb-rx,Rectangle,creately.basic.rectangle,
|
||||
mavlink-routerd,Rectangle,creately.basic.rectangle,
|
||||
udp:14550,Text,creately.basic.text,
|
|
|
@ -0,0 +1,29 @@
|
|||
text,type,typeId,Description
|
||||
,Rectangle,creately.basic.rectangle,
|
||||
sensor,Rectangle,creately.basic.rectangle,
|
||||
CAM,Text,creately.basic.text,
|
||||
majestic or vencoder,Rectangle,creately.basic.rectangle,
|
||||
wfb-tx,Rectangle,creately.basic.rectangle,
|
||||
wifi adapter driver in monitor mode,Rectangle,creately.basic.rectangle,
|
||||
isp,Text,creately.basic.text,
|
||||
rtp:5600,Text,creately.basic.text,
|
||||
rtl8812au,Rectangle,creately.basic.rectangle,
|
||||
WIFI,WiFi,creately.material-icons.wifi_twotone_2,
|
||||
,Rectangle,creately.basic.rectangle,
|
||||
GROUND (NVR),Text,creately.basic.text,
|
||||
WIFI,WiFi,creately.material-icons.wifi_twotone_2,
|
||||
rtl8812au,Rectangle,creately.basic.rectangle,
|
||||
wifi adapter driver in monitor mode,Rectangle,creately.basic.rectangle,
|
||||
wfb-rx,Rectangle,creately.basic.rectangle,
|
||||
LAN / WLAN or usb network,Ellipse,creately.basic.ellipse,
|
||||
,Rectangle,creately.basic.rectangle,
|
||||
PC or Pocket,Text,creately.basic.text,
|
||||
videoplayer or GS program,Computer,creately.material-icons.computer_twotone_2,
|
||||
,To Right Arrow,creately.arrows.torightarrow,
|
||||
,To Right Arrow,creately.arrows.torightarrow,
|
||||
rtp:5600,Text,creately.basic.text,
|
||||
rtp:5600,Text,creately.basic.text,
|
||||
wfb-rx,Rectangle,creately.basic.rectangle,
|
||||
vdecoder,Rectangle,creately.basic.rectangle,
|
||||
HDMI,Rectangle,creately.basic.rectangle,
|
||||
udp:5000,Text,creately.basic.text,
|
|
After Width: | Height: | Size: 755 KiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 333 KiB |
After Width: | Height: | Size: 451 KiB |
After Width: | Height: | Size: 323 KiB |
After Width: | Height: | Size: 490 KiB |
After Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 135 KiB |
After Width: | Height: | Size: 47 KiB |