kopia lustrzana https://github.com/cyoung/stratux
commit
c3476f064c
6
Makefile
6
Makefile
|
@ -3,7 +3,11 @@ GOARCH ?= arm
|
|||
GOARM ?= 7
|
||||
|
||||
all:
|
||||
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build gen_gdl90.go traffic.go ry835ai.go network.go
|
||||
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go get -t -d -v ./...
|
||||
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build main/gen_gdl90.go main/traffic.go main/ry835ai.go main/network.go main/managementinterface.go
|
||||
|
||||
test:
|
||||
sh -c true
|
||||
|
||||
install:
|
||||
cp -f gen_gdl90 /usr/bin/gen_gdl90
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
override:
|
||||
- make
|
||||
test:
|
||||
override:
|
||||
- make test
|
|
@ -0,0 +1,15 @@
|
|||
CFLAGS?=-O2 -g -Wall -W $(shell pkg-config --cflags librtlsdr)
|
||||
LDLIBS+=$(shell pkg-config --libs librtlsdr) -lpthread -lm
|
||||
CC?=gcc
|
||||
PROGNAME=dump1090
|
||||
|
||||
all: dump1090
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $<
|
||||
|
||||
dump1090: dump1090.o anet.o
|
||||
$(CC) -g -o dump1090 dump1090.o anet.o $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f *.o dump1090
|
|
@ -0,0 +1,284 @@
|
|||
Dump1090 README
|
||||
===
|
||||
|
||||
Dump 1090 is a Mode S decoder specifically designed for RTLSDR devices.
|
||||
|
||||
The main features are:
|
||||
|
||||
* Robust decoding of weak messages, with mode1090 many users observed
|
||||
improved range compared to other popular decoders.
|
||||
* Network support: TCP30003 stream (MSG5...), Raw packets, HTTP.
|
||||
* Embedded HTTP server that displays the currently detected aircrafts on
|
||||
Google Map.
|
||||
* Single bit errors correction using the 24 bit CRC.
|
||||
* Ability to decode DF11, DF17 messages.
|
||||
* Ability to decode DF formats like DF0, DF4, DF5, DF16, DF20 and DF21
|
||||
where the checksum is xored with the ICAO address by brute forcing the
|
||||
checksum field using recently seen ICAO addresses.
|
||||
* Decode raw IQ samples from file (using --ifile command line switch).
|
||||
* Interactive command-line-interfae 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 receiving raw data to/from connected clients
|
||||
(using --net).
|
||||
|
||||
While from time to time I still add / fix stuff in my fork, I target
|
||||
minimalism of the implementation. However there is a
|
||||
[much more feature complete fork](https://github.com/MalcolmRobb/dump1090)
|
||||
available, developed by MalcolmRobb.
|
||||
|
||||
Installation
|
||||
---
|
||||
|
||||
Type "make".
|
||||
|
||||
Normal usage
|
||||
---
|
||||
|
||||
To capture traffic directly from your RTL device and show the captured traffic
|
||||
on standard output, just run the program without options at all:
|
||||
|
||||
./dump1090
|
||||
|
||||
To just output hexadecimal messages:
|
||||
|
||||
./dump1090 --raw
|
||||
|
||||
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
|
||||
altitude and flight number, extracted from the received Mode S packets.
|
||||
|
||||
Using files as source of data
|
||||
---
|
||||
|
||||
To decode data from file, use:
|
||||
|
||||
./dump1090 --ifile /path/to/binfile
|
||||
|
||||
The binary file should be created using `rtl_sdr` like this (or with any other
|
||||
program that is able to output 8-bit unsigned IQ samples at 2Mhz sample rate).
|
||||
|
||||
rtl_sdr -f 1090000000 -s 2000000 -g 50 output.bin
|
||||
|
||||
In the example `rtl_sdr` a gain of 50 is used, simply you should use the highest
|
||||
gain availabe for your tuner. This is not needed when calling Dump1090 itself
|
||||
as it is able to select the highest gain supported automatically.
|
||||
|
||||
It is possible to feed the program with data via standard input using
|
||||
the --ifile option with "-" as argument.
|
||||
|
||||
Additional options
|
||||
---
|
||||
|
||||
Dump1090 can be called with other command line options to set a different
|
||||
gain, frequency, and so forth. For a list of options use:
|
||||
|
||||
./dump1090 --help
|
||||
|
||||
Everything is not documented here should be obvious, and for most users calling
|
||||
it without arguments at all is the best thing to do.
|
||||
|
||||
Reliability
|
||||
---
|
||||
|
||||
By default Dump1090 tries to fix single bit errors using the checksum.
|
||||
Basically the program will try to flip every bit of the message and check if
|
||||
the checksum of the resulting message matches.
|
||||
|
||||
This is indeed able to fix errors and works reliably in my experience,
|
||||
however if you are interested in very reliable data I suggest to use
|
||||
the --no-fix command line switch in order to disable error fixing.
|
||||
|
||||
Performances and sensibility of detection
|
||||
---
|
||||
|
||||
In my limited experience Dump1090 was able to decode a big number of messages
|
||||
even in conditions where I encountered problems using other programs, however
|
||||
no formal test was performed so I can't really claim that this program is
|
||||
better or worse compared to other similar programs.
|
||||
|
||||
If you can capture traffic that Dump1090 is not able to decode properly, drop
|
||||
me an email with a download link. I may try to improve the detection during
|
||||
my free time (this is just an hobby project).
|
||||
|
||||
Network server features
|
||||
---
|
||||
|
||||
By enabling the networking support with --net Dump1090 starts listening
|
||||
for clients connections on port 30002 and 30001 (you can change both the
|
||||
ports if you want, see --help output).
|
||||
|
||||
Port 30002
|
||||
---
|
||||
|
||||
Connected clients are served with data ASAP as they arrive from the device
|
||||
(or from file if --ifile is used) in the raw format similar to the following:
|
||||
|
||||
*8D451E8B99019699C00B0A81F36E;
|
||||
|
||||
Every entry is separated by a simple newline (LF character, hex 0x0A).
|
||||
|
||||
Port 30001
|
||||
---
|
||||
|
||||
Port 30001 is the raw input port, and can be used to feed Dump1090 with
|
||||
data in the same format as specified above, with hex messages starting with
|
||||
a `*` and ending with a `;` character.
|
||||
|
||||
So for instance if there is another remote Dump1090 instance collecting data
|
||||
it is possible to sum the output to a local Dump1090 instance doing something
|
||||
like this:
|
||||
|
||||
nc remote-dump1090.example.net 30002 | nc localhost 30001
|
||||
|
||||
It is important to note that what is received via port 30001 is also
|
||||
broadcasted to clients listening to port 30002.
|
||||
|
||||
In general everything received from port 30001 is handled exactly like the
|
||||
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 --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.
|
||||
|
||||
Port 30003
|
||||
---
|
||||
|
||||
Connected clients are served with messages in SBS1 (BaseStation) format,
|
||||
similar to:
|
||||
|
||||
MSG,4,,,738065,,,,,,,,420,179,,,0,,0,0,0,0
|
||||
MSG,3,,,738065,,,,,,,35000,,,34.81609,34.07810,,,0,0,0,0
|
||||
|
||||
This can be used to feed data to various sharing sites without the need to use another decoder.
|
||||
|
||||
Antenna
|
||||
---
|
||||
|
||||
Mode S messages are transmitted in the 1090 Mhz frequency. If you have a decent
|
||||
antenna you'll be able to pick up signals from aircrafts pretty far from your
|
||||
position, especially if you are outdoor and in a position with a good sky view.
|
||||
|
||||
You can easily build a very cheap antenna following the istructions at:
|
||||
|
||||
http://antirez.com/news/46
|
||||
|
||||
With this trivial antenna I was able to pick up signals of aircrafts 200+ Km
|
||||
away from me.
|
||||
|
||||
If you are interested in a more serious antenna check the following
|
||||
resources:
|
||||
|
||||
* http://gnuradio.org/redmine/attachments/download/246/06-foster-adsb.pdf
|
||||
* http://www.lll.lu/~edward/edward/adsb/antenna/ADSBantenna.html
|
||||
* http://modesbeast.com/pix/adsb-ant-drawing.gif
|
||||
|
||||
Aggressive mode
|
||||
---
|
||||
|
||||
With --aggressive it is possible to activate the *aggressive mode* that is a
|
||||
modified version of the Mode S packet detection and decoding.
|
||||
THe aggresive mode uses more CPU usually (especially if there are many planes
|
||||
sending DF17 packets), but can detect a few more messages.
|
||||
|
||||
The algorithm in aggressive mode is modified in the following ways:
|
||||
|
||||
* Up to two demodulation errors are tolerated (adjacent entires in the magnitude
|
||||
vector with the same eight). Normally only messages without errors are
|
||||
checked.
|
||||
* It tries to fix DF17 messages trying every two bits combination.
|
||||
|
||||
The use of aggressive mdoe is only advised in places where there is low traffic
|
||||
in order to have a chance to capture some more messages.
|
||||
|
||||
Debug mode
|
||||
---
|
||||
|
||||
The Debug mode is a visual help to improve the detection algorithm or to
|
||||
understand why the program is not working for a given input.
|
||||
|
||||
In this mode messages are displayed in an ASCII-art style graphical
|
||||
representation, where the individial magnitude bars sampled at 2Mhz are
|
||||
displayed.
|
||||
|
||||
An index shows the sample number, where 0 is the sample where the first
|
||||
Mode S peak was found. Some additional background noise is also added
|
||||
before the first peak to provide some context.
|
||||
|
||||
To enable debug mode and check what combinations of packets you can
|
||||
log, use `mode1090 --help` to obtain a list of available debug flags.
|
||||
|
||||
Debug mode includes an optional javascript output that is used to visualize
|
||||
packets using a web browser, you can use the file debug.html under the
|
||||
'tools' directory to load the generated frames.js file.
|
||||
|
||||
How this program works?
|
||||
---
|
||||
|
||||
The code is very documented and written in order to be easy to understand.
|
||||
For the diligent programmer with a Mode S specification on his hands it
|
||||
should be trivial to understand how it works.
|
||||
|
||||
The algorithms I used were obtained basically looking at many messages
|
||||
as displayed using a trow-away SDL program, and trying to model the algorithm
|
||||
based on how the messages look graphically.
|
||||
|
||||
How to test the program?
|
||||
---
|
||||
|
||||
If you have an RTLSDR device and you happen to be in an area where there
|
||||
are aircrafts flying over your head, just run the program and check for signals.
|
||||
|
||||
However if you don't have an RTLSDR device, or if in your area the presence
|
||||
of aircrafts is very limited, you may want to try the sample file distributed
|
||||
with the Dump1090 distribution under the "testfiles" directory.
|
||||
|
||||
Just run it like this:
|
||||
|
||||
./dump1090 --ifile testfiles/modes1.bin
|
||||
|
||||
What is --strip mode?
|
||||
---
|
||||
|
||||
It is just a simple filter that will get raw IQ 8 bit samples in input
|
||||
and will output a file missing all the parts of the file where I and Q
|
||||
are lower than the specified <level> for more than 32 samples.
|
||||
|
||||
Use it like this:
|
||||
|
||||
cat big.bin | ./dump1090 --snip 25 > small.bin
|
||||
|
||||
I used it in order to create a small test file to include inside this
|
||||
program source code distribution.
|
||||
|
||||
Contributing
|
||||
---
|
||||
|
||||
Dump1090 was written during some free time during xmas 2012, it is an hobby
|
||||
project so I'll be able to address issues and improve it only during
|
||||
free time, however you are incouraged to send pull requests in order to
|
||||
improve the program. A good starting point can be the TODO list included in
|
||||
the source distribution.
|
||||
|
||||
Credits
|
||||
---
|
||||
|
||||
Dump1090 was written by Salvatore Sanfilippo <antirez@gmail.com> and is
|
||||
released under the BSD three clause license.
|
|
@ -0,0 +1,5 @@
|
|||
TODO
|
||||
|
||||
* Extract more information from captured Mode S messages.
|
||||
* Improve the web interface gmap.html.
|
||||
* Enhance the algorithm to reliably decode more messages.
|
|
@ -0,0 +1,382 @@
|
|||
/* anet.c -- Basic TCP socket stuff made a bit less boring
|
||||
*
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "anet.h"
|
||||
|
||||
static void anetSetError(char *err, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
if (!err) return;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(err, ANET_ERR_LEN, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
int anetNonBlock(char *err, int fd)
|
||||
{
|
||||
int flags;
|
||||
|
||||
/* Set the socket nonblocking.
|
||||
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
|
||||
* interrupted by a signal. */
|
||||
if ((flags = fcntl(fd, F_GETFL)) == -1) {
|
||||
anetSetError(err, "fcntl(F_GETFL): %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetTcpNoDelay(char *err, int fd)
|
||||
{
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
|
||||
{
|
||||
anetSetError(err, "setsockopt TCP_NODELAY: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetSetSendBuffer(char *err, int fd, int buffsize)
|
||||
{
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
|
||||
{
|
||||
anetSetError(err, "setsockopt SO_SNDBUF: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetTcpKeepAlive(char *err, int fd)
|
||||
{
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_KEEPALIVE: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetResolve(char *err, char *host, char *ipbuf)
|
||||
{
|
||||
struct sockaddr_in sa;
|
||||
|
||||
sa.sin_family = AF_INET;
|
||||
if (inet_aton(host, &sa.sin_addr) == 0) {
|
||||
struct hostent *he;
|
||||
|
||||
he = gethostbyname(host);
|
||||
if (he == NULL) {
|
||||
anetSetError(err, "can't resolve: %s", host);
|
||||
return ANET_ERR;
|
||||
}
|
||||
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
|
||||
}
|
||||
strcpy(ipbuf,inet_ntoa(sa.sin_addr));
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int anetCreateSocket(char *err, int domain) {
|
||||
int s, on = 1;
|
||||
if ((s = socket(domain, SOCK_STREAM, 0)) == -1) {
|
||||
anetSetError(err, "creating socket: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
/* Make sure connection-intensive things like the redis benckmark
|
||||
* will be able to close/open sockets a zillion of times */
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#define ANET_CONNECT_NONE 0
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
if (inet_aton(addr, &sa.sin_addr) == 0) {
|
||||
struct hostent *he;
|
||||
|
||||
he = gethostbyname(addr);
|
||||
if (he == NULL) {
|
||||
anetSetError(err, "can't resolve: %s", addr);
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
|
||||
}
|
||||
if (flags & ANET_CONNECT_NONBLOCK) {
|
||||
if (anetNonBlock(err,s) != ANET_OK)
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS &&
|
||||
flags & ANET_CONNECT_NONBLOCK)
|
||||
return s;
|
||||
|
||||
anetSetError(err, "connect: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_LOCAL)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (flags & ANET_CONNECT_NONBLOCK) {
|
||||
if (anetNonBlock(err,s) != ANET_OK)
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (connect(s,(struct sockaddr*)&sa,sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS &&
|
||||
flags & ANET_CONNECT_NONBLOCK)
|
||||
return s;
|
||||
|
||||
anetSetError(err, "connect: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetUnixConnect(char *err, char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetUnixNonBlockConnect(char *err, char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
/* Like read(2) but make sure 'count' is read before to return
|
||||
* (unless error or EOF condition is encountered) */
|
||||
int anetRead(int fd, char *buf, int count)
|
||||
{
|
||||
int nread, totlen = 0;
|
||||
while(totlen != count) {
|
||||
nread = read(fd,buf,count-totlen);
|
||||
if (nread == 0) return totlen;
|
||||
if (nread == -1) return -1;
|
||||
totlen += nread;
|
||||
buf += nread;
|
||||
}
|
||||
return totlen;
|
||||
}
|
||||
|
||||
/* Like write(2) but make sure 'count' is read before to return
|
||||
* (unless error is encountered) */
|
||||
int anetWrite(int fd, char *buf, int count)
|
||||
{
|
||||
int nwritten, totlen = 0;
|
||||
while(totlen != count) {
|
||||
nwritten = write(fd,buf,count-totlen);
|
||||
if (nwritten == 0) return totlen;
|
||||
if (nwritten == -1) return -1;
|
||||
totlen += nwritten;
|
||||
buf += nwritten;
|
||||
}
|
||||
return totlen;
|
||||
}
|
||||
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
|
||||
if (bind(s,sa,len) == -1) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
/* Use a backlog of 512 entries. We pass 511 to the listen() call because
|
||||
* the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1);
|
||||
* which will thus give us a backlog of 512 entries */
|
||||
if (listen(s, 511) == -1) {
|
||||
anetSetError(err, "listen: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bindaddr && inet_aton(bindaddr, &sa.sin_addr) == 0) {
|
||||
anetSetError(err, "invalid bind address");
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetUnixServer(char *err, char *path, mode_t perm)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_LOCAL)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
if (perm)
|
||||
chmod(sa.sun_path, perm);
|
||||
return s;
|
||||
}
|
||||
|
||||
static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
|
||||
int fd;
|
||||
while(1) {
|
||||
fd = accept(s,sa,len);
|
||||
if (fd == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else {
|
||||
anetSetError(err, "accept: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetTcpAccept(char *err, int s, char *ip, int *port) {
|
||||
int fd;
|
||||
struct sockaddr_in sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetUnixAccept(char *err, int s) {
|
||||
int fd;
|
||||
struct sockaddr_un sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetPeerToString(int fd, char *ip, int *port) {
|
||||
struct sockaddr_in sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
*port = 0;
|
||||
ip[0] = '?';
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int anetSockName(int fd, char *ip, int *port) {
|
||||
struct sockaddr_in sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
*port = 0;
|
||||
ip[0] = '?';
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/* anet.c -- Basic TCP socket stuff made a bit less boring
|
||||
*
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef ANET_H
|
||||
#define ANET_H
|
||||
|
||||
#define ANET_OK 0
|
||||
#define ANET_ERR -1
|
||||
#define ANET_ERR_LEN 256
|
||||
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
int anetNonBlock(char *err, int fd);
|
||||
int anetTcpNoDelay(char *err, int fd);
|
||||
int anetTcpKeepAlive(char *err, int fd);
|
||||
int anetPeerToString(int fd, char *ip, int *port);
|
||||
int anetSetSendBuffer(char *err, int fd, int buffsize);
|
||||
|
||||
#endif
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,180 @@
|
|||
<!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% }
|
||||
#info {
|
||||
position: absolute;
|
||||
width:20%;
|
||||
height:100%;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
top:0px;
|
||||
background-color: white;
|
||||
border-left:1px #666 solid;
|
||||
font-family:Helvetica;
|
||||
}
|
||||
#info div {
|
||||
padding:0px;
|
||||
padding-left:10px;
|
||||
margin:0px;
|
||||
}
|
||||
#info div h1 {
|
||||
margin-top:10px;
|
||||
font-size:16px;
|
||||
}
|
||||
#info div p {
|
||||
font-size:14px;
|
||||
color:#333;
|
||||
}
|
||||
</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=45.0;
|
||||
CenterLon=9.0;
|
||||
Planes={};
|
||||
NumPlanes = 0;
|
||||
Selected=null
|
||||
|
||||
function getIconForPlane(plane) {
|
||||
var r = 255, g = 255, b = 0;
|
||||
var maxalt = 40000; /* Max altitude in the average case */
|
||||
var invalt = maxalt-plane.altitude;
|
||||
var selected = (Selected == plane.hex);
|
||||
|
||||
if (invalt < 0) invalt = 0;
|
||||
b = parseInt(255/maxalt*invalt);
|
||||
return {
|
||||
strokeWeight: (selected ? 2 : 1),
|
||||
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
|
||||
scale: 5,
|
||||
fillColor: 'rgb('+r+','+g+','+b+')',
|
||||
fillOpacity: 0.9,
|
||||
rotation: plane.track
|
||||
};
|
||||
}
|
||||
|
||||
function selectPlane() {
|
||||
if (!Planes[this.planehex]) return;
|
||||
var old = Selected;
|
||||
Selected = this.planehex;
|
||||
if (Planes[old]) {
|
||||
/* Remove the highlight in the previously selected plane. */
|
||||
Planes[old].marker.setIcon(getIconForPlane(Planes[old]));
|
||||
}
|
||||
Planes[Selected].marker.setIcon(getIconForPlane(Planes[Selected]));
|
||||
refreshSelectedInfo();
|
||||
}
|
||||
|
||||
function refreshGeneralInfo() {
|
||||
var i = document.getElementById('geninfo');
|
||||
|
||||
i.innerHTML = NumPlanes+' planes on screen.';
|
||||
}
|
||||
|
||||
function refreshSelectedInfo() {
|
||||
var i = document.getElementById('selinfo');
|
||||
var p = Planes[Selected];
|
||||
|
||||
if (!p) return;
|
||||
var html = 'ICAO: '+p.hex+'<br>';
|
||||
if (p.flight.length) {
|
||||
html += '<b>'+p.flight+'</b><br>';
|
||||
}
|
||||
html += 'Altitude: '+p.altitude+' feet<br>';
|
||||
html += 'Speed: '+p.speed+' knots<br>';
|
||||
html += 'Coordinates: '+p.lat+', '+p.lon+'<br>';
|
||||
i.innerHTML = html;
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
$.getJSON('/data.json', function(data) {
|
||||
var stillhere = {}
|
||||
for (var j=0; j < data.length; j++) {
|
||||
var plane = data[j];
|
||||
var marker = null;
|
||||
stillhere[plane.hex] = true;
|
||||
plane.flight = $.trim(plane.flight);
|
||||
|
||||
if (Planes[plane.hex]) {
|
||||
var myplane = Planes[plane.hex];
|
||||
marker = myplane.marker;
|
||||
var icon = marker.getIcon();
|
||||
var newpos = new google.maps.LatLng(plane.lat, plane.lon);
|
||||
marker.setPosition(newpos);
|
||||
marker.setIcon(getIconForPlane(plane));
|
||||
myplane.altitude = plane.altitude;
|
||||
myplane.speed = plane.speed;
|
||||
myplane.lat = plane.lat;
|
||||
myplane.lon = plane.lon;
|
||||
myplane.track = plane.track;
|
||||
myplane.flight = plane.flight;
|
||||
if (myplane.hex == Selected)
|
||||
refreshSelectedInfo();
|
||||
} else {
|
||||
marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(plane.lat, plane.lon),
|
||||
map: Map,
|
||||
icon: getIconForPlane(plane)
|
||||
});
|
||||
plane.marker = marker;
|
||||
marker.planehex = plane.hex;
|
||||
Planes[plane.hex] = plane;
|
||||
|
||||
/* Trap clicks for this marker. */
|
||||
google.maps.event.addListener(marker, 'click', selectPlane);
|
||||
}
|
||||
if (plane.flight.length == 0)
|
||||
marker.setTitle(plane.hex)
|
||||
else
|
||||
marker.setTitle(plane.flight+' ('+plane.hex+')')
|
||||
}
|
||||
NumPlanes = data.length;
|
||||
|
||||
/* 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: 5,
|
||||
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();
|
||||
refreshGeneralInfo();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body onload="initialize()">
|
||||
<div id="map_canvas" style="width:80%; height:100%"></div>
|
||||
<div id="info">
|
||||
<div>
|
||||
<h1>Dump1090</h1>
|
||||
<p id="geninfo"></p>
|
||||
<p id="selinfo">Click on a plane for info.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,193 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<head>
|
||||
<script>
|
||||
var frames = [];
|
||||
var currentFrame = 0;
|
||||
|
||||
var modes_checksum_table = [
|
||||
0x3935ea, 0x1c9af5, 0xf1b77e, 0x78dbbf, 0xc397db, 0x9e31e9, 0xb0e2f0, 0x587178,
|
||||
0x2c38bc, 0x161c5e, 0x0b0e2f, 0xfa7d13, 0x82c48d, 0xbe9842, 0x5f4c21, 0xd05c14,
|
||||
0x682e0a, 0x341705, 0xe5f186, 0x72f8c3, 0xc68665, 0x9cb936, 0x4e5c9b, 0xd8d449,
|
||||
0x939020, 0x49c810, 0x24e408, 0x127204, 0x093902, 0x049c81, 0xfdb444, 0x7eda22,
|
||||
0x3f6d11, 0xe04c8c, 0x702646, 0x381323, 0xe3f395, 0x8e03ce, 0x4701e7, 0xdc7af7,
|
||||
0x91c77f, 0xb719bb, 0xa476d9, 0xadc168, 0x56e0b4, 0x2b705a, 0x15b82d, 0xf52612,
|
||||
0x7a9309, 0xc2b380, 0x6159c0, 0x30ace0, 0x185670, 0x0c2b38, 0x06159c, 0x030ace,
|
||||
0x018567, 0xff38b7, 0x80665f, 0xbfc92b, 0xa01e91, 0xaff54c, 0x57faa6, 0x2bfd53,
|
||||
0xea04ad, 0x8af852, 0x457c29, 0xdd4410, 0x6ea208, 0x375104, 0x1ba882, 0x0dd441,
|
||||
0xf91024, 0x7c8812, 0x3e4409, 0xe0d800, 0x706c00, 0x383600, 0x1c1b00, 0x0e0d80,
|
||||
0x0706c0, 0x038360, 0x01c1b0, 0x00e0d8, 0x00706c, 0x003836, 0x001c1b, 0xfff409,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
||||
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000
|
||||
];
|
||||
|
||||
function modesChecksum(frame) {
|
||||
var crc = 0;
|
||||
var bits = frame.bits;
|
||||
var offset = (bits == 112) ? 0 : (112-56);
|
||||
|
||||
for(var j = 0; j < bits; j++) {
|
||||
var byte = j/8;
|
||||
var bit = j%8;
|
||||
var bitmask = 1 << (7-bit);
|
||||
|
||||
/* If bit is set, xor with corresponding table entry. */
|
||||
if (frame.hex.charCodeAt(byte) & bitmask)
|
||||
crc ^= modes_checksum_table[j+offset];
|
||||
}
|
||||
return crc; /* 24 bit checksum. */
|
||||
}
|
||||
|
||||
function getFrameChecksum(frame) {
|
||||
var res = "";
|
||||
for (j = 0; j < frame.hex.length; j++) {
|
||||
var val = frame.hex.charCodeAt(j);
|
||||
var h = val.toString(16);
|
||||
if (h.length == 1) h = "0"+h;
|
||||
res += h;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function displayFrame(i) {
|
||||
var div = document.getElementById("frame");
|
||||
var msgbits = 8+112;
|
||||
var frame = frames[i];
|
||||
var padding = frame.mag.length - msgbits*2;
|
||||
|
||||
/* Remove the old representation. */
|
||||
var nodes = div.childNodes.length;
|
||||
for(var j = 0; j < nodes; j++) {
|
||||
div.removeChild(div.firstChild);
|
||||
}
|
||||
|
||||
/* Display the new one. */
|
||||
for (var j = -padding; j < msgbits*2+padding; j++) {
|
||||
var m = frame.mag[j+padding];
|
||||
var type;
|
||||
|
||||
if (j < 0) type = "noise";
|
||||
if (j >= 0 && j < 16) type = "pre";
|
||||
if (j >= 16) {
|
||||
if (!(j % 2)) {
|
||||
var next = frame.mag[j+padding+1];
|
||||
if (m > next)
|
||||
type = "one";
|
||||
else
|
||||
type = "zero";
|
||||
}
|
||||
var bit = (j-16)/2;
|
||||
if (bit == frame.fix1 ||
|
||||
bit == frame.fix2)
|
||||
type = "err";
|
||||
}
|
||||
var sample = document.createElement("div");
|
||||
sample.setAttribute("class","sample "+type);
|
||||
sample.setAttribute("title","sample "+j+" ("+m+")");
|
||||
sample.style.left = ""+((j+padding)*4)+"px";
|
||||
sample.style.height = ""+(m/256)+"px";
|
||||
div.appendChild(sample);
|
||||
}
|
||||
document.getElementById("info").innerHTML =
|
||||
"#"+currentFrame+" "+frame.descr+"<br>"+
|
||||
"Bits:"+frame.bits+"<br>"+
|
||||
"DF : "+(frame.hex.charCodeAt(0) >> 3)+"<br>"+
|
||||
"fix1: "+frame.fix1+"<br>"+
|
||||
"fix2: "+frame.fix2+"<br>"+
|
||||
"hex : "+getFrameChecksum(frame)+"<br>"+
|
||||
"crc (computed): "+modesChecksum(frame).toString(16)+"<br>";
|
||||
}
|
||||
|
||||
function recomputeHex(frame) {
|
||||
var padding = frame.mag.length - (112+8)*2;
|
||||
var b = [];
|
||||
var hex = "";
|
||||
|
||||
/* Get bits */
|
||||
for (var j = 0; j < frame.bits*2; j += 2) {
|
||||
var bit;
|
||||
var l = frame.mag[padding+j+16];
|
||||
var r = frame.mag[padding+j+1+16];
|
||||
if (l > r)
|
||||
bit = 1;
|
||||
else
|
||||
bit = 0;
|
||||
b.push(bit);
|
||||
}
|
||||
/* Pack into bytes */
|
||||
for (j = 0; j < frame.bits; j+= 8) {
|
||||
hex += String.fromCharCode(
|
||||
b[j]<<7 |
|
||||
b[j+1]<<6 |
|
||||
b[j+2]<<5 |
|
||||
b[j+3]<<4 |
|
||||
b[j+4]<<3 |
|
||||
b[j+5]<<2 |
|
||||
b[j+6]<<1 |
|
||||
b[j+7]);
|
||||
}
|
||||
frame.hex = hex;
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
document.getElementById("next").onclick = function() {
|
||||
if (currentFrame != frames.length-1) currentFrame++;
|
||||
displayFrame(currentFrame);
|
||||
}
|
||||
document.getElementById("prev").onclick = function() {
|
||||
if (currentFrame != 0) currentFrame--;
|
||||
displayFrame(currentFrame);
|
||||
}
|
||||
document.getElementById("re").onclick = function() {
|
||||
recomputeHex(frames[currentFrame]);
|
||||
displayFrame(currentFrame);
|
||||
}
|
||||
displayFrame(currentFrame);
|
||||
}
|
||||
</script>
|
||||
<script src="frames.js"></script>
|
||||
<style>
|
||||
#frame {
|
||||
width: 1024px;
|
||||
height: 255px;
|
||||
border: 1px #aaa solid;
|
||||
position: relative;
|
||||
}
|
||||
.sample {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
}
|
||||
.pre {
|
||||
width:4px;
|
||||
background-color: orange;
|
||||
}
|
||||
.one {
|
||||
width:4px;
|
||||
background-color: #0000cc;
|
||||
}
|
||||
.zero {
|
||||
width:4px;
|
||||
background-color: #aaaaaa;
|
||||
}
|
||||
.err {
|
||||
width:4px;
|
||||
background-color: #cc6666;
|
||||
}
|
||||
.noise {
|
||||
width:2px;
|
||||
background-color: #ffffff;
|
||||
border: 1px #aaa dotted;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<div id="frame">
|
||||
</div>
|
||||
<pre id="info">
|
||||
</pre>
|
||||
<input type="button" id="prev" value="Prev frame">
|
||||
<input type="button" id="next" value="Next frame">
|
||||
<input type="button" id="re" value="Recompute Hex">
|
||||
</body>
|
||||
</html>
|
14
dump978.go
14
dump978.go
|
@ -3,6 +3,18 @@
|
|||
// that can be found in the LICENSE file.
|
||||
|
||||
// Package dump978 wraps libdump978, a 978MHz UAT demodulator.
|
||||
//
|
||||
// Build example
|
||||
//
|
||||
// dump978.so:
|
||||
// $ gcc -c -O2 -g -Wall -Werror -Ifec -fpic -DBUILD_LIB=1 dump978.c fec.c fec/decode_rs_char.c fec/init_rs_char.c
|
||||
// $ gcc -shared -lm -o ../libdump978.so dump978.o fec.o decode_rs_char.o init_rs_char.o
|
||||
//
|
||||
// dump978 go wrapper:
|
||||
// $ go build -o dump978.a dump978.go dump978_exports.go
|
||||
//
|
||||
// uat_read executable:
|
||||
// $ go build uat_read.go
|
||||
|
||||
package dump978
|
||||
|
||||
|
@ -47,4 +59,4 @@ func ProcessDataFromChannel() {
|
|||
inData := <-InChan
|
||||
ProcessData(inData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,5 +3,6 @@ driver=rtl871xdrv
|
|||
ssid=stratux
|
||||
hw_mode=g
|
||||
channel=1
|
||||
wmm_enabled=0
|
||||
wme_enabled=1
|
||||
ieee80211n=1
|
||||
ignore_broadcast_ssid=0
|
||||
|
|
|
@ -14,12 +14,12 @@ CURIMG=stage4.$IMGFORMAT
|
|||
|
||||
setup_stratux_bootconfig() {
|
||||
dotask attach_image_to_nbd $CURIMG $NBD_DEV
|
||||
dotask sudo mkdir -p boot
|
||||
mkdir boot
|
||||
dotask sudo mount $BOOT_DEV boot
|
||||
#usb power
|
||||
dotask sudo sh -c 'echo "max_usb_current=1" >>boot/config.txt'
|
||||
echo "max_usb_current=1" >>boot/config.txt
|
||||
#i2c
|
||||
dotask sudo sh -c 'echo "dtparam=i2c1=on" >>boot/config.txt'
|
||||
echo "dtparam=i2c1=on" >>boot/config.txt
|
||||
}
|
||||
|
||||
setup_stratux() {
|
||||
|
@ -32,11 +32,8 @@ echo "**** STRATUX SETUP *****"
|
|||
apt-get install -y screen
|
||||
#wifi
|
||||
apt-get install -y hostapd isc-dhcp-server
|
||||
#apache for web management scripts
|
||||
apt-get install -y apache2 php5 libapache2-mod-php5
|
||||
rm -rf /var/www/html/*
|
||||
service apache2 start
|
||||
update-rc.d apache2 enable
|
||||
#troubleshooting
|
||||
apt-get install -y tcpdump
|
||||
#wifi startup
|
||||
update-rc.d hostapd enable
|
||||
update-rc.d isc-dhcp-server enable
|
||||
|
@ -118,6 +115,7 @@ cd /root
|
|||
rm -rf stratux
|
||||
git clone https://github.com/cyoung/stratux
|
||||
cd stratux
|
||||
mkdir -p /var/www
|
||||
cp -r web/* /var/www/
|
||||
cd dump978
|
||||
make
|
||||
|
@ -155,9 +153,6 @@ echo "**** END STRATUX SETUP *****"
|
|||
cd $WORKDIR
|
||||
dotask branch_image ../$OUTDIR/stage3.$IMGFORMAT $CURIMG
|
||||
|
||||
#needs to be done before qemu is started.
|
||||
|
||||
dotask setup_stratux_bootconfig
|
||||
|
||||
dotask run_qemu $CURIMG
|
||||
dotask mount_apt_cache
|
||||
|
@ -172,4 +167,10 @@ dotask allow_starting_services
|
|||
dotask update_issue
|
||||
dotask fingerprint_debian
|
||||
dotask shutdown_qemu
|
||||
|
||||
|
||||
dotask setup_stratux_bootconfig
|
||||
universal_cleanup
|
||||
|
||||
|
||||
dotask finish_image
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
@ -17,7 +16,7 @@ import (
|
|||
const (
|
||||
stratuxVersion = "v0.2"
|
||||
configLocation = "/etc/stratux.conf"
|
||||
managementAddr = "127.0.0.1:9110"
|
||||
managementAddr = ":80"
|
||||
maxDatagramSize = 8192
|
||||
UPLINK_BLOCK_DATA_BITS = 576
|
||||
UPLINK_BLOCK_BITS = (UPLINK_BLOCK_DATA_BITS + 160)
|
||||
|
@ -390,55 +389,6 @@ type status struct {
|
|||
var globalSettings settings
|
||||
var globalStatus status
|
||||
|
||||
func handleManagementConnection(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
rw := bufio.NewReader(conn)
|
||||
for {
|
||||
s, err := rw.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
s = strings.Trim(s, "\r\n")
|
||||
if s == "STATUS" {
|
||||
resp, _ := json.Marshal(&globalStatus)
|
||||
conn.Write(resp)
|
||||
} else if s == "SETTINGS" {
|
||||
resp, _ := json.Marshal(&globalSettings)
|
||||
conn.Write(resp)
|
||||
} else if s == "QUIT" {
|
||||
break
|
||||
} else {
|
||||
// Assume settings.
|
||||
//TODO: Make this so that there is some positive way of doing this versus assuming that everything other than commands above are settings.
|
||||
var newSettings settings
|
||||
err := json.Unmarshal([]byte(s), &newSettings)
|
||||
if err != nil {
|
||||
log.Printf("%s - error: %s\n", s, err.Error())
|
||||
} else {
|
||||
log.Printf("new settings: %s\n", s)
|
||||
globalSettings = newSettings
|
||||
saveSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func managementInterface() {
|
||||
ln, err := net.Listen("tcp", managementAddr)
|
||||
if err != nil { //TODO
|
||||
log.Printf("couldn't open management port: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
defer ln.Close()
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil { //TODO
|
||||
continue
|
||||
}
|
||||
go handleManagementConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSettings() {
|
||||
globalSettings.UAT_Enabled = true //TODO
|
||||
globalSettings.ES_Enabled = false //TODO
|
|
@ -0,0 +1,78 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"golang.org/x/net/websocket"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SettingMessage struct {
|
||||
Setting string `json:"setting"`
|
||||
Value bool `json:"state"`
|
||||
}
|
||||
|
||||
func statusSender(conn *websocket.Conn) {
|
||||
timer := time.NewTicker(1 * time.Second)
|
||||
for {
|
||||
<-timer.C
|
||||
|
||||
statResp, _ := json.Marshal(&globalStatus)
|
||||
conn.Write(statResp)
|
||||
|
||||
settingResp, _ := json.Marshal(&globalSettings)
|
||||
_, err := conn.Write(settingResp)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Web client disconnected.\n")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleManagementConnection(conn *websocket.Conn) {
|
||||
go statusSender(conn)
|
||||
|
||||
for {
|
||||
var msg SettingMessage
|
||||
err := websocket.JSON.Receive(conn, &msg)
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Printf("handleManagementConnection: %s\n", err.Error())
|
||||
} else {
|
||||
if msg.Setting == "UAT_Enabled" {
|
||||
globalSettings.UAT_Enabled = msg.Value
|
||||
}
|
||||
if msg.Setting == "ES_Enabled" {
|
||||
globalSettings.ES_Enabled = msg.Value
|
||||
}
|
||||
if msg.Setting == "GPS_Enabled" {
|
||||
globalSettings.GPS_Enabled = msg.Value
|
||||
}
|
||||
if msg.Setting == "AHRS_Enabled" {
|
||||
globalSettings.AHRS_Enabled = msg.Value
|
||||
}
|
||||
|
||||
saveSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func managementInterface() {
|
||||
http.Handle("/", http.FileServer(http.Dir("/var/www")))
|
||||
http.HandleFunc("/control",
|
||||
func(w http.ResponseWriter, req *http.Request) {
|
||||
s := websocket.Server{
|
||||
Handler: websocket.Handler(handleManagementConnection)}
|
||||
s.ServeHTTP(w, req)
|
||||
})
|
||||
|
||||
err := http.ListenAndServe(managementAddr, nil)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("managementInterface ListenAndServe: %s\n", err.Error())
|
||||
}
|
||||
}
|
|
@ -28,10 +28,10 @@ var dhcpLeases map[string]string
|
|||
var netMutex *sync.Mutex
|
||||
|
||||
const (
|
||||
NETWORK_GDL90_STANDARD = 1
|
||||
NETWORK_AHRS_FFSIM = 2
|
||||
NETWORK_AHRS_GDL90 = 4
|
||||
dhcp_lease_file = "/var/lib/dhcp/dhcpd.leases"
|
||||
NETWORK_GDL90_STANDARD = 1
|
||||
NETWORK_AHRS_FFSIM = 2
|
||||
NETWORK_AHRS_GDL90 = 4
|
||||
dhcp_lease_file = "/var/lib/dhcp/dhcpd.leases"
|
||||
)
|
||||
|
||||
// Read the "dhcpd.leases" file and parse out IP/hostname.
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/kidoman/embd/sensor/bmp180"
|
||||
"github.com/tarm/serial"
|
||||
|
||||
"./mpu6050"
|
||||
"../mpu6050"
|
||||
)
|
||||
|
||||
type SituationData struct {
|
||||
|
@ -275,9 +275,9 @@ func makeAHRSGDL90Report() {
|
|||
pitch := int16(float64(mySituation.pitch) * float64(10.0))
|
||||
roll := int16(float64(mySituation.roll) * float64(10.0))
|
||||
hdg := uint16(float64(mySituation.gyro_heading) * float64(10.0)) //TODO.
|
||||
slip_skid := int16(float64(0) * float64(10.0)) //TODO.
|
||||
yaw_rate := int16(float64(0) * float64(10.0)) //TODO.
|
||||
g := int16(float64(1.0) * float64(10.0)) //TODO.
|
||||
slip_skid := int16(float64(0) * float64(10.0)) //TODO.
|
||||
yaw_rate := int16(float64(0) * float64(10.0)) //TODO.
|
||||
g := int16(float64(1.0) * float64(10.0)) //TODO.
|
||||
|
||||
// Roll.
|
||||
msg[4] = byte((roll >> 8) & 0xFF)
|
|
@ -344,7 +344,7 @@ func esListen() {
|
|||
continue
|
||||
}
|
||||
rdr := bufio.NewReader(inConn)
|
||||
for {
|
||||
for globalSettings.ES_Enabled {
|
||||
buf, err := rdr.ReadString('\n')
|
||||
if err != nil { // Must have disconnected?
|
||||
break
|
|
@ -1,3 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
screen -S stratux -d -m /usr/bin/start_uat
|
||||
screen -S dump1090 -d -m /usr/bin/dump1090 --net --device-index 1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
/usr/local/bin/rtl_sdr -f 978000000 -s 2083334 -g 48 - | /usr/bin/dump978 | /usr/bin/gen_gdl90
|
||||
/usr/local/bin/rtl_sdr -f 978000000 -s 2083334 -g 48 -d 0 - | /usr/bin/dump978 | /usr/bin/gen_gdl90
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"./mpu6050"
|
||||
"../mpu6050"
|
||||
"fmt"
|
||||
"github.com/kidoman/embd"
|
||||
_ "github.com/kidoman/embd/host/all"
|
|
@ -1,64 +0,0 @@
|
|||
<?php
|
||||
|
||||
function _str_send($sock, $str) {
|
||||
return socket_send($sock, $str, strlen($str), 0);
|
||||
}
|
||||
|
||||
function get_json($tp) {
|
||||
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
if (!socket_connect($sock, '127.0.0.1', '9110')) {
|
||||
throw new Exception("couldn't connect");
|
||||
}
|
||||
|
||||
_str_send($sock, $tp);
|
||||
|
||||
$buf = "";
|
||||
socket_recv($sock, $buf, 1024, 0);
|
||||
|
||||
$x = json_decode($buf, true);
|
||||
|
||||
socket_close($sock);
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
function get_settings() {
|
||||
return get_json("SETTINGS\n");
|
||||
}
|
||||
|
||||
function get_status() {
|
||||
return get_json("STATUS\n");
|
||||
}
|
||||
|
||||
function set_settings($to_set) {
|
||||
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
if (!socket_connect($sock, '127.0.0.1', '9110')) {
|
||||
print "couldn't connect\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
$buf = json_encode($to_set);
|
||||
_str_send($sock, $buf . "\n");
|
||||
_str_send($sock, "QUIT\n");
|
||||
}
|
||||
|
||||
$current_settings = get_settings();
|
||||
|
||||
// Copy over old settings to the new ones, such that if there is a field that doesn't change it gets sent over again.
|
||||
if (isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'POST')) {
|
||||
$new_settings = $current_settings;
|
||||
foreach ($_POST as $k => $v) {
|
||||
if ($v === "true") $v = true;
|
||||
else if ($v === "false") $v = false;
|
||||
$new_settings[$k] = $v; //FIXME.
|
||||
}
|
||||
set_settings($new_settings);
|
||||
$current_settings = get_settings();
|
||||
}
|
||||
|
||||
$current_status = get_status();
|
||||
|
||||
$p = array_merge($current_settings, $current_status);
|
||||
|
||||
print json_encode($p) . "\n";
|
||||
?>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,45 @@
|
|||
.onoffswitch {
|
||||
position: relative; width: 68px;
|
||||
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
|
||||
}
|
||||
.onoffswitch-checkbox {
|
||||
display: none;
|
||||
}
|
||||
.onoffswitch-label {
|
||||
display: block; overflow: hidden; cursor: pointer;
|
||||
border: 2px solid #ddd; border-radius: 20px;
|
||||
}
|
||||
.onoffswitch-inner {
|
||||
display: block; width: 200%; margin-left: -100%;
|
||||
transition: margin 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-inner:before, .onoffswitch-inner:after {
|
||||
display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
|
||||
font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.onoffswitch-inner:before {
|
||||
content: "ON";
|
||||
padding-left: 10px;
|
||||
background-color: #337AB7; color: #FFFFFF;
|
||||
}
|
||||
.onoffswitch-inner:after {
|
||||
content: "OFF";
|
||||
padding-right: 10px;
|
||||
background-color: #F5F5F5; color: #DDDDDD;
|
||||
text-align: right;
|
||||
}
|
||||
.onoffswitch-switch {
|
||||
display: block; width: 18px; margin: 6px;
|
||||
background: #FFFFFF;
|
||||
position: absolute; top: 0; bottom: 0;
|
||||
right: 34px;
|
||||
border: 2px solid #ddd; border-radius: 20px;
|
||||
transition: all 0.3s ease-in 0s;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
|
||||
margin-left: 0;
|
||||
}
|
||||
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
|
||||
right: 0px;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
@import url(bootstrap.min.css);
|
||||
@import url(onoff.css);
|
||||
|
||||
body
|
||||
{
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
#stratux-main
|
||||
{
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
background-color: #fff;
|
||||
border-color: #ddd;
|
||||
border-width: 1px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
box-shadow: none;
|
||||
margin: auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
#RY835AI_connected-container
|
||||
{
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.led-red
|
||||
{
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #940;
|
||||
border-radius: 50%;
|
||||
box-shadow: #000 0 -1px 7px 1px, inset #600 0 -1px 9px, #F00 0 2px 12px;
|
||||
}
|
||||
|
||||
.led-green
|
||||
{
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: #00FF00;
|
||||
border-radius: 50%;
|
||||
box-shadow: #000 0 -1px 7px 1px, inset #460 0 -1px 9px, #7D0 0 2px 12px;
|
||||
}
|
186
web/index.html
186
web/index.html
|
@ -1,70 +1,122 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Stratux
|
||||
</title>
|
||||
<script src="js/jquery-2.1.4.min.js" type="text/javascript"></script>
|
||||
<script src="js/jquery.form.min.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('input[name=UAT_Enabled]').change(function(){
|
||||
$('#settings').ajaxSubmit({url: 'control.php', type: 'post'})
|
||||
});
|
||||
$('input[name=ES_Enabled]').change(function(){
|
||||
$('#settings').ajaxSubmit({url: 'control.php', type: 'post'})
|
||||
});
|
||||
$('input[name=GPS_Enabled]').change(function(){
|
||||
$('#settings').ajaxSubmit({url: 'control.php', type: 'post'})
|
||||
});
|
||||
$('input[name=AHRS_Enabled]').change(function(){
|
||||
$('#settings').ajaxSubmit({url: 'control.php', type: 'post'})
|
||||
});
|
||||
});
|
||||
(function worker() {
|
||||
$.ajax({
|
||||
url: 'control.php',
|
||||
success: function(data) {
|
||||
obj = $.parseJSON(data);
|
||||
$.each(obj, function(k, v) {
|
||||
// Radio values.
|
||||
if ((k == "UAT_Enabled") || (k == "ES_Enabled") || (k == "GPS_Enabled") || (k == "AHRS_Enabled")) {
|
||||
$('[name=' + k + ']').val([v.toString()]);
|
||||
}
|
||||
$('#' + k).text(v);
|
||||
});
|
||||
|
||||
},
|
||||
complete: function() {
|
||||
// Schedule the next request when the current one is complete.
|
||||
setTimeout(worker, 1000);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
</script>
|
||||
Status:<br>
|
||||
RTL-SDR devices: <span id="Devices">1</span><br>
|
||||
Clients connected: <span id="Connected_Users">2</span><br>
|
||||
Current firmware: <span id="Version">v0.1</span><br>
|
||||
UAT msgs: <span id="UAT_messages_last_minute"></span> / <span id="UAT_messages_max"></span><br>
|
||||
1090ES msgs: <span id="ES_messages_last_minute"></span> / <span id="ES_messages_max"></span><br>
|
||||
GPS satellites: <span id="GPS_satellites_locked"></span><br>
|
||||
AHRS: <span id="RY835AI_connected"></span><br>
|
||||
Uptime: <span id="Uptime"></span><br>
|
||||
<br>
|
||||
<form id="settings">
|
||||
Set:<br>
|
||||
978MHz <input type="radio" name="UAT_Enabled" value="true">On
|
||||
<input type="radio" name="UAT_Enabled" value="false">Off<br>
|
||||
1090MHz <input type="radio" name="ES_Enabled" value="true">On
|
||||
<input type="radio" name="ES_Enabled" value="false">Off<br>
|
||||
GPS <input type="radio" name="GPS_Enabled" value="true">On
|
||||
<input type="radio" name="GPS_Enabled" value="false">Off<br>
|
||||
AHRS <input type="radio" name="AHRS_Enabled" value="true">On
|
||||
<input type="radio" name="AHRS_Enabled" value="false">Off<br>
|
||||
</form>
|
||||
</body>
|
||||
<head>
|
||||
<title>
|
||||
Stratux
|
||||
</title>
|
||||
<script src="js/jquery-2.1.4.min.js" type="text/javascript"></script>
|
||||
<script src="js/jquery.form.min.js" type="text/javascript"></script>
|
||||
<script src="js/stratux.js" type="text/javascript"></script>
|
||||
<link href="css/stratux.css" rel="stylesheet" media="screen">
|
||||
</head>
|
||||
<body>
|
||||
<div id="stratux-main" class="well well-large">
|
||||
<h1>Stratux</h1>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Status <span id="connectedLabel" class="label label-warning">Disconnected</span></div>
|
||||
<div class="panel-body">
|
||||
<div class="form-horizontal">
|
||||
<!--
|
||||
<div class="row">
|
||||
<label class="col-sm-6">RTL-SDR devices:</label>
|
||||
<span id="Devices" class="col-sm-2">--</span>
|
||||
</div>
|
||||
-->
|
||||
<div class="row">
|
||||
<label class="col-sm-6">Clients connected:</label>
|
||||
<span id="Connected_Users" class="col-sm-5">--</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-sm-6">Current firmware:</label>
|
||||
<span id="Version" class="col-sm-6">--</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-sm-6">UAT msgs:</label>
|
||||
<div class="col-sm-6">
|
||||
<span id="UAT_messages_last_minute">--</span>
|
||||
/
|
||||
<span id="UAT_messages_max">--</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-sm-6">1090ES msgs:</label>
|
||||
<div class="col-sm-6">
|
||||
<span id="ES_messages_last_minute">--</span>
|
||||
/
|
||||
<span id="ES_messages_max">--</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-sm-6">GPS satellites:</label>
|
||||
<span id="GPS_satellites_locked" class="col-sm-6">--</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-sm-6">AHRS:</label>
|
||||
<div id="RY835AI_connected-container" class="col-sm-6">
|
||||
<div id="RY835AI_connected" class="led-red"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-sm-6">Uptime:</label>
|
||||
<span id="Uptime" class="col-sm-6">--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Settings</div>
|
||||
<div class="panel-body form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">978MHz</label>
|
||||
<div class="col-sm-1">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="UAT_Enabled" class="onoffswitch-checkbox" id="uat-onoffswitch" checked>
|
||||
<label class="onoffswitch-label" for="uat-onoffswitch">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">1090MHz</label>
|
||||
<div class="col-sm-1">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="ES_Enabled" class="onoffswitch-checkbox" id="es-onoffswitch" checked>
|
||||
<label class="onoffswitch-label" for="es-onoffswitch">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">GPS</label>
|
||||
<div class="col-sm-1">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="GPS_Enabled" class="onoffswitch-checkbox" id="gps-onoffswitch" checked>
|
||||
<label class="onoffswitch-label" for="gps-onoffswitch">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">AHRS</label>
|
||||
<div class="col-sm-1">
|
||||
<div class="onoffswitch">
|
||||
<input type="checkbox" name="AHRS_Enabled" class="onoffswitch-checkbox" id="ahrs-onoffswitch" checked>
|
||||
<label class="onoffswitch-label" for="ahrs-onoffswitch">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
var socket;
|
||||
|
||||
function setLEDstatus (ledElement, status) {
|
||||
if(status) {
|
||||
ledElement.removeClass('led-red');
|
||||
ledElement.addClass('led-green');
|
||||
}
|
||||
else {
|
||||
ledElement.removeClass('led-green');
|
||||
ledElement.addClass('led-red');
|
||||
}
|
||||
}
|
||||
|
||||
function setConnectedClass (cssClass) {
|
||||
$('#connectedLabel').removeClass('label-success');
|
||||
$('#connectedLabel').removeClass('label-warning');
|
||||
$('#connectedLabel').removeClass('label-danger');
|
||||
$('#connectedLabel').addClass( cssClass );
|
||||
|
||||
if(cssClass == 'label-success')
|
||||
$('#connectedLabel').text('Connected');
|
||||
else
|
||||
$('#connectedLabel').text('Disconnected');
|
||||
}
|
||||
|
||||
function connect() {
|
||||
socket = new WebSocket('ws://' + window.location.hostname + '/control');
|
||||
|
||||
socket.onopen = function(msg) {
|
||||
setConnectedClass('label-success');
|
||||
};
|
||||
|
||||
socket.onclose = function(msg) {
|
||||
setConnectedClass('label-danger');
|
||||
setTimeout(connect,1000);
|
||||
};
|
||||
|
||||
socket.onerror = function(msg) {
|
||||
setConnectedClass('label-danger');
|
||||
};
|
||||
|
||||
socket.onmessage = function(msg) {
|
||||
console.log('Received status update.')
|
||||
|
||||
var status = JSON.parse(msg.data)
|
||||
|
||||
// Update Status
|
||||
$('#Version').text(status.Version);
|
||||
$('#Devices').text(status.Devices);
|
||||
$('#Connected_Users').text(status.Connected_Users);
|
||||
$('#UAT_messages_last_minute').text(status.UAT_messages_last_minute);
|
||||
$('#UAT_messages_max').text(status.UAT_messages_max);
|
||||
$('#ES_messages_last_minute').text(status.ES_messages_last_minute);
|
||||
$('#ES_messages_max').text(status.ES_messages_max);
|
||||
$('#GPS_satellites_locked').text(status.GPS_satellites_locked);
|
||||
setLEDstatus($('#RY835AI_connected'), status.RY835AI_connected);
|
||||
$('#Uptime').text(status.Uptime);
|
||||
|
||||
// Update Settings
|
||||
$('input[name=UAT_Enabled]').prop('checked', status.UAT_Enabled);
|
||||
$('input[name=ES_Enabled]').prop('checked', status.ES_Enabled);
|
||||
$('input[name=GPS_Enabled]').prop('checked', status.GPS_Enabled);
|
||||
$('input[name=AHRS_Enabled]').prop('checked', status.AHRS_Enabled);
|
||||
};
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
connect();
|
||||
|
||||
$('input[name=UAT_Enabled]').click(function () {
|
||||
console.log('UAT_Enabled clicked');
|
||||
|
||||
msg = {setting: 'UAT_Enabled', state: $('input[name=UAT_Enabled]').prop('checked') };
|
||||
socket.send(JSON.stringify(msg));
|
||||
});
|
||||
|
||||
$('input[name=ES_Enabled]').click(function () {
|
||||
console.log('ES_Enabled clicked');
|
||||
|
||||
msg = {setting: 'ES_Enabled', state: $('input[name=ES_Enabled]').prop('checked') };
|
||||
socket.send(JSON.stringify(msg));
|
||||
});
|
||||
|
||||
$('input[name=GPS_Enabled]').click(function () {
|
||||
console.log('GPS_Enabled clicked');
|
||||
|
||||
msg = {setting: 'GPS_Enabled', state: $('input[name=GPS_Enabled]').prop('checked') };
|
||||
socket.send(JSON.stringify(msg));
|
||||
});
|
||||
|
||||
$('input[name=AHRS_Enabled]').click(function () {
|
||||
console.log('AHRS_Enabled clicked');
|
||||
|
||||
msg = {setting: 'AHRS_Enabled', state: $('input[name=AHRS_Enabled]').prop('checked') };
|
||||
socket.send(JSON.stringify(msg));
|
||||
});
|
||||
|
||||
});
|
Ładowanie…
Reference in New Issue