[hamradio-commits] [dump1090] 32/389: Initial HTTP support with planes animated using google map.
Matthew Ernisse
mernisse-guest at moszumanska.debian.org
Wed Nov 5 00:19:37 UTC 2014
This is an automated email from the git hooks/post-receive script.
mernisse-guest pushed a commit to branch master
in repository dump1090.
commit a583615b669e665cf4e45329ada437a4c4f6918a
Author: antirez <antirez at gmail.com>
Date: Sun Jan 13 01:39:29 2013 +0100
Initial HTTP support with planes animated using google map.
---
README.md | 16 ++-
dump1090.c | 358 ++++++++++++++++++++++++++++++++++++++++++++++---------------
gmap.html | 88 +++++++++++++++
3 files changed, 371 insertions(+), 91 deletions(-)
diff --git a/README.md b/README.md
index e7b502c..ae88057 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,10 @@ The main features are:
* Interactive mode where aircrafts currently detected are shown
as a list refreshing as more data arrives.
* CPR coordinates decoding and track calculation from velocity.
-* TCP server streaming and recceiving raw data to/from connected clients (using --net).
+* TCP server streaming and recceiving raw data to/from connected clients
+ (using --net).
+* Embedded HTTP server that displays the currently detected aircrafts on
+ Google Map.
Installation
---
@@ -39,6 +42,11 @@ To run the program in interactive mode:
./dump1090 --interactive
+To run the program in interactive mode, with networking support, and connect
+with your browser to http://localhost:8080 to see live traffic:
+
+ ./dump1090 --interactive --net
+
In iteractive mode it is possible to have a less information dense but more
"arcade style" output, where the screen is refreshed every second displaying
all the recently seen aircrafts with some additional information such as
@@ -136,7 +144,11 @@ normal traffic from RTL devices or from file when --ifile is used.
It is possible to use Dump1090 just as an hub using --ifile with /dev/zero
as argument as in the following example:
- ./dump1090 --ifile /dev/zero --net --interactive
+ ./dump1090 --net-only
+
+Or alternatively to see what's happening on the screen:
+
+ ./dump1090 --net-only --interactive
Then you can feed it from different data sources from the internet.
diff --git a/dump1090.c b/dump1090.c
index 0d523a7..3323515 100644
--- a/dump1090.c
+++ b/dump1090.c
@@ -40,6 +40,7 @@
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
+#include <sys/stat.h>
#include "rtl-sdr.h"
#include "anet.h"
@@ -81,7 +82,8 @@
#define MODES_NET_MAX_FD 1024
#define MODES_NET_OUTPUT_RAW_PORT 30002
#define MODES_NET_INPUT_RAW_PORT 30001
-#define MODES_CLIENT_BUF_SIZE 256
+#define MODES_NET_HTTP_PORT 8080
+#define MODES_CLIENT_BUF_SIZE 1024
#define MODES_NOTUSED(V) ((void) V)
@@ -142,6 +144,7 @@ struct {
int maxfd; /* Greatest fd currently active. */
int ros; /* Raw output listening socket. */
int ris; /* Raw input listening socket. */
+ int https; /* HTTP listening socket. */
/* Configuration */
char *filename; /* Input form file, --ifile option. */
@@ -150,8 +153,10 @@ struct {
int raw; /* Raw output format. */
int debug; /* Debugging mode. */
int net; /* Enable networking. */
+ int net_only; /* Enable just networking. */
int net_output_raw_port; /* Raw output TCP port. */
int net_input_raw_port; /* Raw input TCP port. */
+ int net_http_port; /* HTTP port. */
int interactive; /* Interactive mode */
int interactive_rows; /* Interactive mode: max number of rows. */
int interactive_ttl; /* Interactive mode: TTL before deletion. */
@@ -169,6 +174,7 @@ struct {
long long stat_goodcrc;
long long stat_badcrc;
long long stat_fixed;
+ long long stat_http_requests;
} Modes;
/* The struct we use to store information about a decoded message. */
@@ -244,8 +250,10 @@ void modesInitConfig(void) {
Modes.check_crc = 1;
Modes.raw = 0;
Modes.net = 0;
+ Modes.net_only = 0;
Modes.net_output_raw_port = MODES_NET_OUTPUT_RAW_PORT;
Modes.net_input_raw_port = MODES_NET_INPUT_RAW_PORT;
+ Modes.net_http_port = MODES_NET_HTTP_PORT;
Modes.onlyaddr = 0;
Modes.debug = 0;
Modes.interactive = 0;
@@ -292,6 +300,7 @@ void modesInit(void) {
Modes.stat_goodcrc = 0;
Modes.stat_badcrc = 0;
Modes.stat_fixed = 0;
+ Modes.stat_http_requests = 0;
Modes.exit = 0;
}
@@ -1257,9 +1266,13 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
* further processing and visualization. */
void useModesMessage(struct modesMessage *mm) {
if (!Modes.stats && (Modes.check_crc == 0 || mm->crcok)) {
- if (Modes.interactive) {
+ /* Track aircrafts in interactive mode or if the HTTP
+ * interface is enabled. */
+ if (Modes.interactive || Modes.stat_http_requests > 0) {
interactiveReceiveData(mm);
- } else {
+ }
+ /* In non-interactive way, display messages on standard output. */
+ if (!Modes.interactive) {
displayModesMessage(mm);
if (!Modes.raw && !Modes.onlyaddr) printf("\n");
}
@@ -1596,27 +1609,31 @@ void snipMode(int level) {
/* Networking "stack" initialization. */
void modesInitNet(void) {
+ struct {
+ char *descr;
+ int *socket;
+ int port;
+ } services[3] = {
+ {"Raw TCP output", &Modes.ros, Modes.net_output_raw_port},
+ {"Raw TCP input", &Modes.ris, Modes.net_input_raw_port},
+ {"HTTP server", &Modes.https, Modes.net_http_port}
+ };
+ int j;
+
memset(Modes.clients,0,sizeof(Modes.clients));
Modes.maxfd = -1;
- /* Raw output port */
- Modes.ros = anetTcpServer(Modes.aneterr, Modes.net_output_raw_port, NULL);
- if (Modes.ros == -1) {
- fprintf(stderr, "Error opening raw TCP output port %d: %s\n",
- Modes.net_output_raw_port, Modes.aneterr);
- exit(1);
- }
-
- /* Raw input port */
- Modes.ris = anetTcpServer(Modes.aneterr, Modes.net_input_raw_port, NULL);
- if (Modes.ris == -1) {
- fprintf(stderr, "Error opening raw TCP input port %d: %s\n",
- Modes.net_input_raw_port, Modes.aneterr);
- exit(1);
+ for (j = 0; j < 3; j++) {
+ int s = anetTcpServer(Modes.aneterr, services[j].port, NULL);
+ if (s == -1) {
+ fprintf(stderr, "Error opening the listening port %d (%s): %s\n",
+ services[j].port, services[j].descr, strerror(errno));
+ exit(1);
+ }
+ anetNonBlock(Modes.aneterr, s);
+ *services[j].socket = s;
}
- anetNonBlock(Modes.aneterr, Modes.ros);
- anetNonBlock(Modes.aneterr, Modes.ris);
signal(SIGPIPE, SIG_IGN);
}
@@ -1627,10 +1644,11 @@ void modesAcceptClients(void) {
int fd, port;
unsigned int j;
struct client *c;
- int services[2];
+ int services[3];
services[0] = Modes.ros;
services[1] = Modes.ris;
+ services[2] = Modes.https;
for (j = 0; j < sizeof(services)/sizeof(int); j++) {
fd = anetTcpAccept(Modes.aneterr, services[j], NULL, &port);
@@ -1712,16 +1730,30 @@ int hexDigitVal(int c) {
/* This function decodes a string representing a Mode S message in
* raw hex format like: *8D4B969699155600E87406F5B69F;
+ * The string is supposed to be at the start of the client buffer
+ * and null-terminated.
*
* The message is passed to the higher level layers, so it feeds
* the selected screen output, the network output and so forth.
*
* If the message looks invalid is silently discarded. */
-void decodeHexMessage(char *hex) {
+void decodeHexMessage(struct client *c) {
+ char *hex = c->buf;
int l = strlen(hex), j;
unsigned char msg[MODES_LONG_MSG_BYTES];
struct modesMessage mm;
+ /* Remove spaces on the left and on the right. */
+ while(l && isspace(hex[l-1])) {
+ hex[l-1] = '\0';
+ l--;
+ }
+ while(isspace(*hex)) {
+ hex++;
+ l--;
+ }
+
+ /* Turn the message into binary. */
if (l < 2 || hex[0] != '*' || hex[l-1] != ';') return;
hex++; l-=2; /* Skip * and ; */
if (l > MODES_LONG_MSG_BYTES*2) return; /* Too long message... broken. */
@@ -1736,65 +1768,190 @@ void decodeHexMessage(char *hex) {
useModesMessage(&mm);
}
-/* This function polls all the clients using read() in order to receive new
+/* Return a description of planes in json. */
+char *aircraftsToJson(int *len) {
+ struct aircraft *a = Modes.aircrafts;
+ int buflen = 1024; /* The initial buffer is incremented as needed. */
+ char *buf = malloc(buflen), *p = buf;
+ int l;
+
+ l = snprintf(p,buflen,"[\n");
+ p += l; buflen -= l;
+ while(a) {
+ int altitude = a->altitude, speed = a->speed;
+
+ /* Convert units to metric if --metric was specified. */
+ if (Modes.metric) {
+ altitude /= 3.2828;
+ speed *= 1.852;
+ }
+
+ if (a->lat != 0 && a->lon != 0) {
+ l = snprintf(p,buflen,
+ "{\"hex\":\"%s\", \"lat\":%f, \"lon\":%f, \"track\":%d},\n",
+ a->hexaddr, a->lat, a->lon, a->track);
+ p += l; buflen -= l;
+ /* Resize if needed. */
+ if (buflen < 256) {
+ int used = p-buf;
+ buflen += 1024; /* Our increment. */
+ buf = realloc(buf,used+buflen);
+ p = buf+used;
+ }
+ }
+ a = a->next;
+ }
+ /* Remove the final comma if any, and closes the json array. */
+ if (*(p-2) == ',') {
+ *(p-2) = '\n';
+ p--;
+ buflen++;
+ }
+ l = snprintf(p,buflen,"]\n");
+ p += l; buflen -= l;
+
+ *len = p-buf;
+ return buf;
+}
+
+#define MODES_CONTENT_TYPE_HTML "text/html;charset=utf-8"
+#define MODES_CONTENT_TYPE_JSON "application/json;charset=utf-8"
+
+/* Get an HTTP request header and write the response to the client.
+ * Again here we assume that the socket buffer is enough without doing
+ * any kind of userspace buffering. */
+void handleHTTPRequest(struct client *c) {
+ char hdr[512];
+ int clen, hdrlen;
+ int keepalive;
+ char *p, *url, *content;
+ char *ctype;
+
+ /* printf("HTTP request: %s\n", c->buf); */
+
+ /* Minimally parse the request. */
+ keepalive = strstr(c->buf, "keep-alive") != NULL;
+ p = strchr(c->buf,' ');
+ if (!p) return; /* There should be the method and a space... */
+ url = ++p; /* Now this should point to the requested URL. */
+ p = strchr(p, ' ');
+ if (!p) return; /* There should be a space before HTTP/... */
+ *p = '\0';
+ /* printf("URL: %s\n", url); */
+
+ /* Select the content to send, we have just two so far:
+ * "/" -> Our google map application.
+ * "/data.json" -> Our ajax request to update planes. */
+ if (strstr(url, "/data.json")) {
+ content = aircraftsToJson(&clen);
+ ctype = MODES_CONTENT_TYPE_JSON;
+ } else {
+ struct stat sbuf;
+ int fd = -1;
+
+ if (stat("gmap.html",&sbuf) != -1 &&
+ (fd = open("gmap.html",O_RDONLY)) != -1)
+ {
+ content = malloc(sbuf.st_size);
+ read(fd,content,sbuf.st_size);
+ } else {
+ char buf[128];
+
+ clen = snprintf(buf,sizeof(buf),"Error opening HTML file: %s",
+ strerror(errno));
+ content = strdup(buf);
+ }
+ if (fd != -1) close(fd);
+ ctype = MODES_CONTENT_TYPE_HTML;
+ }
+
+ /* Create the header and send the reply. */
+ hdrlen = snprintf(hdr, sizeof(hdr),
+ "HTTP/1.1 200 OK\r\n"
+ "Server: Dump1090\r\n"
+ "Content-Type: %s\r\n"
+ "Connection: %s\r\n"
+ "Content-Length: %d\r\n"
+ "\r\n",
+ ctype,
+ keepalive ? "keep-alive" : "close",
+ clen);
+ write(c->fd, hdr, hdrlen);
+
+ /* Send the actual content. */
+ write(c->fd, content, clen);
+ free(content);
+ Modes.stat_http_requests++;
+}
+
+/* This function polls the clients using read() in order to receive new
* messages from the net.
*
- * Every full message received is decoded and passed to the higher layers. */
-void modesReceiveRawInput(void) {
+ * The message is supposed to be separated by the next message by the
+ * separator 'sep', that is a null-terminated C string.
+ *
+ * Every full message received is decoded and passed to the higher layers
+ * calling the function 'handler'. */
+void modesReadFromClient(struct client *c, char *sep,
+ void(*handler)(struct client *))
+{
+ while(1) {
+ int left = sizeof(c->buf) - c->buflen;
+ int nread = read(c->fd, c->buf+c->buflen, left);
+ int fullmsg = 0;
+ int i;
+ char *p;
+
+ if (nread < 0) {
+ if (nread == 0 || errno != EAGAIN) {
+ /* Error, or end of file. */
+ modesFreeClient(c->fd);
+ }
+ break; /* Serve next client. */
+ }
+ c->buflen += nread;
+
+ /* If there is a complete message there must be the separator 'sep'
+ * in the buffer, note that we full-scan the buffer at every read
+ * for simplicity. */
+ if ((p = strstr(c->buf, sep)) != NULL) {
+ i = p - c->buf; /* Turn it as an index inside the buffer. */
+ c->buf[i] = '\0'; /* Te handler expects null terminated strings. */
+ handler(c); /* Call the function to process the message. */
+ /* Move what's left at the start of the buffer. */
+ i += strlen(sep); /* The separator is part of the previous msg. */
+ memmove(c->buf,c->buf+i,c->buflen-i);
+ c->buflen -= i;
+ /* Maybe there are more messages inside the buffer.
+ * Start looping from the start again. */
+ i = -1;
+ fullmsg = 1;
+ }
+ /* If our buffer is full discard it, this is some badly
+ * formatted shit. */
+ if (c->buflen == sizeof(c->buf)) {
+ c->buflen = 0;
+ /* If there is garbage, read more to discard it ASAP. */
+ continue;
+ }
+ /* If no message was decoded process the next client, otherwise
+ * read more data from the same client. */
+ if (!fullmsg) break;
+ }
+}
+
+/* Read data from clients. This function actually delegates a lower-level
+ * function that depends on the kind of service (raw, http, ...). */
+void modesReadFromClients(void) {
int j;
struct client *c;
for (j = 0; j <= Modes.maxfd; j++) {
- c = Modes.clients[j];
- if (c && c->service == Modes.ris) {
- while(1) {
- int left = sizeof(c->buf) - c->buflen;
- int nread = read(j, c->buf+c->buflen, left);
- int decoded = 0;
- int oldpos = c->buflen;
- int i;
-
- if (nread < 0) {
- if (nread == 0 || errno != EAGAIN) {
- /* Error, or end of file. */
- modesFreeClient(j);
- }
- break; /* Serve next client. */
- }
- c->buflen += nread;
-
- /* If there is a complete message there must be a newline
- * in the buffer. The iteration starts from 'oldpos' as
- * we need to check only the chars we read in this interaction
- * as we are sure there is no newline in the pre-existing
- * buffer. */
- for (i = oldpos; i < c->buflen; i++) {
- if (c->buf[i] == '\n') {
- c->buf[i] = '\0';
- if (i && c->buf[i-1] == '\r') c->buf[i-1] = '\0';
- decodeHexMessage(c->buf);
- /* Move what's left at the start of the buffer. */
- i++;
- memmove(c->buf,c->buf+i,c->buflen-i);
- c->buflen -= i;
- /* Maybe there are more messages inside the buffer.
- * Start looping from the start again. */
- i = -1;
- decoded = 1;
- }
- }
- /* If our buffer is full discard it, this is some badly
- * formatted shit. */
- if (c->buflen == sizeof(c->buf)) {
- c->buflen = 0;
- /* If there is garbage, read more to discard it ASAP. */
- continue;
- }
- /* If no message was decoded process the next client, otherwise
- * read more data from the same client. */
- if (!decoded) break;
- }
- }
+ if ((c = Modes.clients[j]) == NULL) continue;
+ if (c->service == Modes.ris)
+ modesReadFromClient(c,"\n",decodeHexMessage);
+ else if (c->service == Modes.https)
+ modesReadFromClient(c,"\r\n\r\n",handleHTTPRequest);
}
}
@@ -1812,8 +1969,10 @@ void showHelp(void) {
"--interactive-ttl <sec> Remove from list if idle for <sec> (default: 60).\n"
"--raw Show only messages hex values.\n"
"--net Enable networking.\n"
+"--net-only Enable just networking, no RTL device or file used.\n"
"--net-ro-port <port> TCP listening port for raw output (default: 30002).\n"
"--net-ri-port <port> TCP listening port for raw input (default: 30001).\n"
+"--net-http-port <port> HTTP server port (default: 8080).\n"
"--no-fix Disable single-bits error correction using CRC.\n"
"--no-crc-check Disable messages with broken CRC (discouraged).\n"
"--stats With --ifile print stats at exit. No other output.\n"
@@ -1825,6 +1984,26 @@ void showHelp(void) {
);
}
+/* This function is called a few times every second by main in order to
+ * perform tasks we need to do continuously, like accepting new clients
+ * from the net, refreshing the screen in interactive mode, and so forth. */
+void backgroundTasks(void) {
+ if (Modes.net) {
+ modesAcceptClients();
+ modesReadFromClients();
+ }
+
+ /* Refresh screen when in interactive mode. */
+ if (Modes.interactive &&
+ (mstime() - Modes.interactive_last_update) >
+ MODES_INTERACTIVE_REFRESH_TIME)
+ {
+ interactiveRemoveStaleAircrafts();
+ interactiveShowData();
+ Modes.interactive_last_update = mstime();
+ }
+}
+
int main(int argc, char **argv) {
int j;
@@ -1853,10 +2032,15 @@ int main(int argc, char **argv) {
Modes.raw = 1;
} else if (!strcmp(argv[j],"--net")) {
Modes.net = 1;
+ } else if (!strcmp(argv[j],"--net-only")) {
+ Modes.net = 1;
+ Modes.net_only = 1;
} else if (!strcmp(argv[j],"--net-ro-port") && more) {
Modes.net_output_raw_port = atoi(argv[++j]);
} else if (!strcmp(argv[j],"--net-ri-port") && more) {
Modes.net_input_raw_port = atoi(argv[++j]);
+ } else if (!strcmp(argv[j],"--net-http-port") && more) {
+ Modes.net_http_port = atoi(argv[++j]);
} else if (!strcmp(argv[j],"--onlyaddr")) {
Modes.onlyaddr = 1;
} else if (!strcmp(argv[j],"--metric")) {
@@ -1888,7 +2072,9 @@ int main(int argc, char **argv) {
/* Initialization */
modesInit();
- if (Modes.filename == NULL) {
+ if (Modes.net_only) {
+ fprintf(stderr,"Net-only mode, no RTL device or file open.\n");
+ } else if (Modes.filename == NULL) {
modesInitRTLSDR();
} else {
if (Modes.filename[0] == '-' && Modes.filename[1] == '\0') {
@@ -1900,6 +2086,13 @@ int main(int argc, char **argv) {
}
if (Modes.net) modesInitNet();
+ /* If the user specifies --net-only, just run in order to serve network
+ * clients without reading data from the RTL device. */
+ while (Modes.net_only) {
+ backgroundTasks();
+ usleep(100000);
+ }
+
/* Create the thread that will read the data from the device. */
pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL);
@@ -1922,20 +2115,7 @@ int main(int argc, char **argv) {
* slow processors). */
pthread_mutex_unlock(&Modes.data_mutex);
detectModeS(Modes.magnitude, Modes.data_len/2);
- if (Modes.net) {
- modesAcceptClients();
- modesReceiveRawInput();
- }
-
- /* Refresh screen when in interactive mode. */
- if (Modes.interactive &&
- (mstime() - Modes.interactive_last_update) >
- MODES_INTERACTIVE_REFRESH_TIME)
- {
- interactiveRemoveStaleAircrafts();
- interactiveShowData();
- Modes.interactive_last_update = mstime();
- }
+ backgroundTasks();
pthread_mutex_lock(&Modes.data_mutex);
if (Modes.exit) break;
}
diff --git a/gmap.html b/gmap.html
new file mode 100644
index 0000000..a5b4a01
--- /dev/null
+++ b/gmap.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
+ <style type="text/css">
+ html { height: 100% }
+ body { height: 100%; margin: 0; padding: 0 }
+ #map_canvas { height: 100% }
+ </style>
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js">
+ </script>
+ <script type="text/javascript"
+ src="https://maps.googleapis.com/maps/api/js?sensor=true">
+ </script>
+ <script type="text/javascript">
+ Map=null;
+ CenterLat=50.0;
+ CenterLon=9.0;
+ Planes={};
+
+ function getIconForPlane(plane) {
+ return {
+ strokeWeight: 2,
+ path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
+ scale: 5,
+ fillColor: 'yellow',
+ fillOpacity: 0.8,
+ rotation: plane.track
+ };
+ }
+
+ function fetchData() {
+ $.getJSON('/data.json', function(data) {
+ var stillhere = {}
+ for (var j=0; j < data.length; j++) {
+ var plane = data[j];
+ stillhere[plane.hex] = true;
+
+ if (Planes[plane.hex]) {
+ var myplane = Planes[plane.hex];
+ var marker = myplane.marker;
+ var icon = marker.getIcon();
+ var newpos = new google.maps.LatLng(plane.lat, plane.lon);
+ marker.setPosition(newpos);
+ marker.setIcon(getIconForPlane(plane));
+ } else {
+ var marker = new google.maps.Marker({
+ position: new google.maps.LatLng(plane.lat, plane.lon),
+ map: Map,
+ title: plane.hex,
+ icon: getIconForPlane(plane)
+ });
+ plane.marker = marker;
+ Planes[plane.hex] = plane;
+ }
+ }
+
+ /* Remove idle planes. */
+ for (var p in Planes) {
+ if (!stillhere[p]) {
+ Planes[p].marker.setMap(null);
+ delete Planes[p];
+ }
+ }
+
+ });
+ }
+
+ function initialize() {
+ var mapOptions = {
+ center: new google.maps.LatLng(CenterLat, CenterLon),
+ zoom: 8,
+ mapTypeId: google.maps.MapTypeId.ROADMAP
+ };
+ Map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
+
+ /* Setup our timer to poll from the server. */
+ window.setInterval(function() {
+ fetchData();
+ }, 1000);
+ }
+ </script>
+ </head>
+ <body onload="initialize()">
+ <div id="map_canvas" style="width:100%; height:100%"></div>
+ </body>
+</html>
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/dump1090.git
More information about the pkg-hamradio-commits
mailing list