kopia lustrzana https://github.com/cyoung/stratux
Merge branch 'master' of https://github.com/cyoung/stratux
commit
6de59749f0
|
@ -1,3 +0,0 @@
|
|||
[submodule "dump1090"]
|
||||
path = dump1090
|
||||
url = https://github.com/antirez/dump1090
|
1
dump1090
1
dump1090
|
@ -1 +0,0 @@
|
|||
Subproject commit 823631979b74f83caa48da69c86f8967e4c17c47
|
|
@ -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>
|
Ładowanie…
Reference in New Issue