mirror of
https://github.com/paparazzi/paparazzi.git
synced 2026-05-09 22:49:53 +08:00
483 lines
14 KiB
C
483 lines
14 KiB
C
/*
|
|
* Copyright (C) 2011 Andreas Gaeb, Max Chtangeev
|
|
*
|
|
* This file is part of paparazzi.
|
|
*
|
|
* paparazzi is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* paparazzi is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with paparazzi; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
*/
|
|
|
|
/** \file kestrel2ivy.c
|
|
* \brief Connect a Kestrel 4500 Bluetooth weather station to the Paparazzi system
|
|
*
|
|
* The program communicates with a Kestrel 4500 weather station connected
|
|
* to a serial port via bluethooth. It asks for new data (Kestrel S command) in the
|
|
* specified intervals, extracts the relevant data (ambient pressure and
|
|
* temperature, wind speed and direction) and broadcasts this via the Ivy bus.
|
|
*
|
|
* At the moment, the Ivy messages should be sent with the ID of the actually
|
|
* flying aircraft, which integrates them into the log file, as long as the
|
|
* aircraft sends its alive message.
|
|
*
|
|
* Useful links:
|
|
* - <a href="http://www.nkhome.com/kestrel/kestrel-4500</a>
|
|
*
|
|
* A few notes:
|
|
* On OS X, the bluetooth pairing is a little bit shaky. The serial port availability
|
|
* seems to time out and give a resource busy error, and the device must be woken up
|
|
* again. Easiest way to do this is to open the Bluetooth Preferences, select the
|
|
* Kestrel device, and in the settings dropdown, select the Edit Serial Ports... option
|
|
* (OS X 10.7 Lion tested). This seems to wake up the device and allow a new connection.
|
|
*
|
|
* Also noted (at least on OS X) if the update period is set at faster than five seconds
|
|
* (-s 5), then the link is not fast enough and data is occasionally missed, leading to
|
|
* missed samples. The five second max was recommended by NKHome tech support.
|
|
*
|
|
* Circular buffer implementation from http://en.wikipedia.org/wiki/Circular_buffer
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <termios.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <Ivy/ivy.h>
|
|
#include <Ivy/ivyloop.h>
|
|
#include <Ivy/timer.h>
|
|
#include <Ivy/version.h>
|
|
|
|
|
|
typedef enum { FALSE = 0, TRUE } BOOL;
|
|
enum {DT = 0,MG,TR,WS,CW,HW,TP,WC,RH,HI,DP,WB,BP,AL,DA};
|
|
enum {UNINIT = 0,GOT_START,GOT_END};
|
|
|
|
/* Opaque buffer element type. This would be defined by the application. */
|
|
typedef struct { char value; } ElemType;
|
|
|
|
/* Circular buffer object */
|
|
typedef struct {
|
|
int size; /* maximum number of elements */
|
|
int start; /* index of oldest element */
|
|
int end; /* index at which to write new element */
|
|
ElemType *elems; /* vector of elements */
|
|
} CircularBuffer;
|
|
|
|
#define PACKET_LENGTH 255
|
|
#define BUF_LENGTH (PACKET_LENGTH*4)
|
|
#define MIN_PACKET_LENGTH (103-3) // using the > character as the start
|
|
#define NUM_PARAMS DA+1
|
|
#define CMD_LENGTH 8
|
|
|
|
// global variables
|
|
int fd;
|
|
int ac_id = 1;
|
|
const char *device;
|
|
char packet[PACKET_LENGTH];
|
|
char tempBuf[BUF_LENGTH];
|
|
int packetidx = 0;
|
|
CircularBuffer cb;
|
|
ElemType elem = {0};
|
|
int msgState = UNINIT;
|
|
|
|
TimerId tid;
|
|
BOOL want_alive_msg = FALSE;
|
|
BOOL metric_input = FALSE;
|
|
|
|
|
|
/**
|
|
* Circular buffer initialization.
|
|
*/
|
|
void cbInit(CircularBuffer *cb, int size) {
|
|
cb->size = size + 1; /* include empty elem */
|
|
cb->start = 0;
|
|
cb->end = 0;
|
|
cb->elems = (ElemType *)calloc(cb->size, sizeof(ElemType));
|
|
}
|
|
|
|
/**
|
|
* Free buffer memory, call before exit.
|
|
*/
|
|
void cbFree(CircularBuffer *cb) {
|
|
free(cb->elems); /* OK if null */ }
|
|
|
|
/**
|
|
* @returns True if buffer completely full.
|
|
*/
|
|
int cbIsFull(CircularBuffer *cb) {
|
|
return (cb->end + 1) % cb->size == cb->start; }
|
|
|
|
/**
|
|
* @returns True if nothing in buffer.
|
|
*/
|
|
int cbIsEmpty(CircularBuffer *cb) {
|
|
return cb->end == cb->start; }
|
|
|
|
/**
|
|
* @returns The number of free slots in the buffer.
|
|
*/
|
|
int cbAvailable(CircularBuffer *cb) {
|
|
if (cb->end >= cb->start)
|
|
return (cb->size - abs(cb->end - cb->start) - 1);
|
|
else
|
|
return (abs(cb->end - cb->start) - 1);
|
|
}
|
|
|
|
/**
|
|
* Write an element, overwriting oldest element if buffer is full. App can
|
|
* choose to avoid the overwrite by checking cbIsFull().
|
|
*/
|
|
void cbWrite(CircularBuffer *cb, ElemType *elem) {
|
|
cb->elems[cb->end] = *elem;
|
|
cb->end = (cb->end + 1) % cb->size;
|
|
if (cb->end == cb->start)
|
|
cb->start = (cb->start + 1) % cb->size; /* full, overwrite */
|
|
}
|
|
|
|
/**
|
|
* Read oldest element, app must ensure !cbIsEmpty() first.
|
|
*/
|
|
void cbRead(CircularBuffer *cb, ElemType *elem) {
|
|
*elem = cb->elems[cb->start];
|
|
cb->start = (cb->start + 1) % cb->size;
|
|
}
|
|
|
|
/**
|
|
* Handler for Ctrl-C, exits the main loop.
|
|
*/
|
|
void sigint_handler(int sig) {
|
|
cbFree(&cb);
|
|
IvyStop();
|
|
TimerRemove(tid);
|
|
close(fd);
|
|
}
|
|
|
|
/**
|
|
* Open the serial port with the appropiate settings.
|
|
*/
|
|
void open_port(const char* device) {
|
|
fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
|
|
if (fd == -1) {
|
|
fprintf(stderr, "kestrel2ivy: open_port: unable to open device %s - ", device);
|
|
perror(NULL);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
// setup connection options
|
|
struct termios options;
|
|
|
|
// get the current options
|
|
tcgetattr(fd, &options);
|
|
|
|
// set local mode, enable receiver, set comm. options:
|
|
// 8 data bits, 1 stop bit, no parity, 9600 Baud
|
|
options.c_cflag = CLOCAL | CREAD | CS8 | B9600;
|
|
|
|
// write options back to port
|
|
tcsetattr(fd, TCSANOW, &options);
|
|
}
|
|
|
|
/**
|
|
* Send an 'S' command (read sensor data) to the station and get the packet back.
|
|
*/
|
|
BOOL send_S() {
|
|
|
|
char cmd[CMD_LENGTH] = "S\r";
|
|
|
|
int bytes = write(fd, cmd, strlen(cmd));
|
|
if (bytes == -1) {
|
|
perror("kestrel2ivy: Error writing command bytes");
|
|
fprintf(stderr,"You may need to reset the connection.\n");
|
|
}
|
|
|
|
bzero(tempBuf, BUF_LENGTH);
|
|
|
|
// repeat a few times within time limit, just return if done
|
|
int result = 0;
|
|
int j = 0;
|
|
for(j = 0; j < 3; j++) {
|
|
// delay to allow kestrel to give some data
|
|
usleep(250000); // 0.25sec
|
|
// read in one chunk, skip reads for the period if there is a read error
|
|
if (result >= 0) {
|
|
result = read(fd, tempBuf, cbAvailable(&cb));
|
|
if (result < 0) {
|
|
//perror("kestrel2ivy: Got a read error on the port");
|
|
} else {
|
|
int i = 0;
|
|
for (i = 0; i < result; i++) {
|
|
ElemType elemBuf;
|
|
elemBuf.value = tempBuf[i];
|
|
cbWrite(&cb, &elemBuf);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (!cbIsEmpty(&cb)) {
|
|
// First get the (approximate) start indicator
|
|
// Then copy in the rest of the data
|
|
// Stop copying in data at the (approximate) end indicator
|
|
// Return if it looks like the last packet in the buffer, otherwise keep processing buffer
|
|
ElemType tempElem;
|
|
cbRead(&cb,&tempElem);
|
|
char tempChar = tempElem.value;
|
|
|
|
switch (msgState) {
|
|
case UNINIT:
|
|
if (tempChar == '>') {
|
|
bzero(packet, PACKET_LENGTH); // clear the packet after getting values
|
|
packetidx = 0;
|
|
packet[packetidx++] = tempChar;
|
|
msgState = GOT_START;
|
|
}
|
|
break;
|
|
case GOT_START:
|
|
if (tempChar == '>') {
|
|
if (packetidx < MIN_PACKET_LENGTH) {
|
|
// False alarm, not actually the start
|
|
bzero(packet, PACKET_LENGTH);
|
|
packetidx = 0;
|
|
packet[packetidx++] = tempChar;
|
|
} else {
|
|
msgState = UNINIT;
|
|
// if we think we have another full packet available, don't return
|
|
if (cbAvailable(&cb) > (BUF_LENGTH - (MIN_PACKET_LENGTH + 80)))
|
|
return TRUE;
|
|
}
|
|
}
|
|
else {
|
|
packet[packetidx++] = tempChar;
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr, "kestrel2ivy: Unknown parser state!\n");
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Populate values[] array with floats extracted from the packet string.
|
|
*/
|
|
int getValues(char* msg, float* values){
|
|
int i;
|
|
char* tok;
|
|
const char delim = ',';
|
|
|
|
tok = strtok(msg, &delim);
|
|
|
|
for(i=0; i<NUM_PARAMS; i++) {
|
|
if (tok == NULL) return 0;
|
|
|
|
values[i] = strtof(tok, NULL);
|
|
tok = strtok(NULL, &delim);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Get the relevant data from the packet and send it as Ivy message
|
|
*/
|
|
void decode_and_send_to_ivy() {
|
|
// get relevant data and convert to SI units
|
|
|
|
// contents of the kestrel S message response in imperial units:
|
|
// DT, MG, TR, WS, CW, HW, TP, WC, RH, HI, DP, WB, BP, AL, DA
|
|
// s, Mag, True, mph, mph, mph, F, F, %, F, F, F, inHg, ft, ft
|
|
// in metric units:
|
|
// s,Mag,True,km/h,km/h,km/h,�C,�C,%,�C,�C,�C,hPa,m,m
|
|
//
|
|
// Sample packet in metric units:
|
|
// S
|
|
// > DT,MG,TR,WS,CW,HW,TP,WC,RH,HI,DP,WB,BP,AL,DA
|
|
// s,Mag,True,km/h,km/h,km/h,�C,�C,%,�C,�C,�C,hPa,m,m
|
|
// 399124138,000,000,0.0,0.0,0.0,22.5,22.5,37.6,21.4,7.3,13.5,931.5,701,1171
|
|
// >
|
|
//
|
|
// where:
|
|
//
|
|
// DT is the date and time in seconds since 1st January 2000,
|
|
// AV is air velocity
|
|
// AF is air flow
|
|
// MG is the compass magnetic direction
|
|
// TR is the compass true direction
|
|
// WS is the wind speed
|
|
// CW is the crosswind
|
|
// HW is the headwind
|
|
// TP is the temperature,
|
|
// WC is the wind chill,
|
|
// RH is the relative humidity,
|
|
// EV is the evaporation rate
|
|
// CT is the concrete temperature
|
|
// HR, MO (moisture) are the humidity ratio
|
|
// HI is the heat index,
|
|
// DP is the dew point,
|
|
// WB is the wet bulb
|
|
// AP is absolute pressure
|
|
// BP is the pressure,
|
|
// AL is the altitude,
|
|
// DA is the density altitude
|
|
// AD is air density
|
|
// RA is relative air density
|
|
|
|
float values[NUM_PARAMS];
|
|
float pstatic_Pa, temp_degC, windspeed_mps, winddir_deg, rel_humidity;
|
|
|
|
if (want_alive_msg)
|
|
IvySendMsg("%d ALIVE 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n", ac_id);
|
|
|
|
if (getValues(packet + MIN_PACKET_LENGTH, values)){
|
|
if (metric_input) {
|
|
//If the unit outputs are set for SI (or closest match), use these conversions
|
|
pstatic_Pa = values[BP] * 100.0; // original is hPa
|
|
temp_degC = values[TP]; // original is deg C
|
|
windspeed_mps = values[WS] * 0.2778; // original is km/h
|
|
winddir_deg = values[MG];
|
|
rel_humidity = values[RH];
|
|
} else {
|
|
//Otherwise use the default imperial units and convert to SI
|
|
pstatic_Pa = values[BP] * 3386.4; // original is inches Hg
|
|
temp_degC = (values[TP] - 32.0) * 5.0/9.0; // original is deg F
|
|
windspeed_mps = values[WS] * 0.44704; // original is miles per hour
|
|
winddir_deg = values[MG];
|
|
rel_humidity = values[RH];
|
|
}
|
|
|
|
// format has to match declaration in conf/messages.xml
|
|
IvySendMsg("%d WEATHER %f %f %f %f %f\n",
|
|
ac_id, pstatic_Pa, temp_degC, windspeed_mps, winddir_deg, rel_humidity);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get data from the station and send it via Ivy.
|
|
* This function is executed by the timer.
|
|
*/
|
|
void handle_timer (TimerId id, void *data, unsigned long delta) {
|
|
if (send_S()) decode_and_send_to_ivy();
|
|
}
|
|
|
|
void print_usage(int argc, char ** argv) {
|
|
static const char* usage =
|
|
"Usage: %s [-a] [-b <bus>] [-d <device>] [-i <aircraft_id>] [-m] [-s <delay time in seconds>]\n"
|
|
" Options:\n"
|
|
" -h Display this help\n"
|
|
" -a Send aircraft ALIVE message (default is no)\n"
|
|
" -b <bus> Ivy bus (default is 127.255.255.255:2010)\n"
|
|
" -d <device> Weatherstation Device (default is /dev/ttyUSB1)\n"
|
|
" -i <number> Specify aircraft ID (default is 1)\n"
|
|
" -s <number> Specify station polling period in seconds (default is 5)\n"
|
|
"\n"
|
|
" Note: kestrel2ivy may send messages with different aircraft IDs. The simplest way is to use\n"
|
|
" the ID of the actually flying aircraft, thus the messages will show up in the messages window\n"
|
|
" and in the log file as if they were sent from that aircraft. If you use another aircraft ID,\n"
|
|
" you have to add the -a option to get the messages into the log file.\n"
|
|
"\n"
|
|
" The shortest recommended sampling period is 5 seconds. Less than this may result in poor\n"
|
|
" weather meter performance, missed packets, etc. (It can sometimes work down to 1 second,\n"
|
|
" but it may stop working unexpectedly\n";
|
|
|
|
fprintf(stderr, usage, argv[0]);
|
|
}
|
|
|
|
/** Main function.
|
|
*/
|
|
int main(int argc, char **argv) {
|
|
// default values for options
|
|
const char
|
|
*defaultbus = "127.255.255.255:2010",
|
|
*bus = defaultbus,
|
|
*defaultdevice = "/dev/rfcomm0";
|
|
device = defaultdevice;
|
|
long delay = 5000;
|
|
|
|
// parse options
|
|
char c;
|
|
while ((c = getopt (argc, argv, "hamb:d:i:s:")) != EOF) {
|
|
switch (c) {
|
|
case 'h':
|
|
print_usage(argc, argv);
|
|
exit(EXIT_SUCCESS);
|
|
break;
|
|
case 'a':
|
|
want_alive_msg = TRUE;
|
|
break;
|
|
case 'b':
|
|
bus = optarg;
|
|
break;
|
|
case 'd':
|
|
device = optarg;
|
|
break;
|
|
case 'i':
|
|
ac_id = atoi(optarg);
|
|
break;
|
|
case 'm':
|
|
metric_input = TRUE;
|
|
break;
|
|
case 's':
|
|
delay = atoi(optarg)*1000;
|
|
if (delay < 5000) {
|
|
fprintf(stderr,"kestrel2ivy: Warning: Setting the sampling period less than 5 seconds\n"
|
|
"may result in poor sampling performance!\n");
|
|
}
|
|
break;
|
|
case '?':
|
|
if (optopt == 'a' || optopt == 'b' || optopt == 'd' || optopt == 's')
|
|
fprintf (stderr, "Option -%c requires an argument.\n", optopt);
|
|
else if (isprint (optopt))
|
|
fprintf (stderr, "Unknown option `-%c'.\n", optopt);
|
|
else
|
|
fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
|
|
print_usage(argc, argv);
|
|
exit(EXIT_FAILURE);
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
// make Ctrl-C stop the main loop and clean up properly
|
|
signal(SIGINT, sigint_handler);
|
|
|
|
// zero out and initialize buffers
|
|
int bufferSize = BUF_LENGTH;
|
|
cbInit(&cb, bufferSize);
|
|
|
|
bzero(packet, PACKET_LENGTH);
|
|
msgState = UNINIT;
|
|
open_port(device);
|
|
|
|
// setup Ivy communication
|
|
IvyInit("kestrel2ivy", "READY", 0, 0, 0, 0);
|
|
IvyStart(bus);
|
|
|
|
// create timer (Ivy)
|
|
tid = TimerRepeatAfter (0, delay, handle_timer, 0);
|
|
|
|
/* main loop */
|
|
#if IVYMINOR_VERSION == 8
|
|
IvyMainLoop (NULL,NULL);
|
|
#else
|
|
IvyMainLoop ();
|
|
#endif
|
|
return 0;
|
|
}
|