Making some changes v1

This commit is contained in:
30hours 2024-02-18 00:45:38 +00:00
commit 3738f78eee
29 changed files with 2506 additions and 1789 deletions

View file

@ -3,39 +3,40 @@
# ubuntu-22.04 by default
ARG VARIANT="jammy"
FROM mcr.microsoft.com/vscode/devcontainers/cpp:0-${VARIANT}
LABEL maintainer="30hours <nathan@30hours.dev>"
ENV DEBIAN_FRONTEND=noninteractive
# Feel like this shouldn't be needed but it drops me in / during build
WORKDIR /workspace
RUN apt-get update \
#
# Install dev tools and package dependencies
&& apt-get install -y clang-tidy clang-format doxygen graphviz gfortran \
libfftw3-dev liblapack-dev libopenblas-dev libudev-dev libusb-1.0.0-dev \
#
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
# Install dependencies from vcpkg
RUN vcpkg integrate install \
&& vcpkg install catch2 \
&& vcpkg install rapidjson \
&& vcpkg install asio \
&& vcpkg install cpp-httplib \
&& vcpkg install armadillo \
&& vcpkg install ryml
COPY lib/sdrplay-3.0.7/SDRplay_RSP_API-Linux-3.07.1.run /workspace/
WORKDIR /blah2
ADD lib lib
RUN apt-get update && apt-get install -y software-properties-common \
&& apt-add-repository ppa:ettusresearch/uhd \
&& apt-get update \
&& DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y \
g++ make cmake git curl zip unzip doxygen graphviz \
libfftw3-dev pkg-config gfortran \
libuhd-dev=4.6.0.0-0ubuntu1~jammy1 \
uhd-host=4.6.0.0-0ubuntu1~jammy1 \
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
# Install shitty sdrplay API
RUN chmod +x /workspace/SDRplay_RSP_API-Linux-3.07.1.run \
&& /workspace/SDRplay_RSP_API-Linux-3.07.1.run --tar -xf \
&& cp x86_64/libsdrplay_api.so.3.07 /usr/local/lib/libsdrplay_api.so \
&& ln -s /usr/local/lib/libsdrplay_api.so /usr/local/lib/libsdrplay_api.so.3.07 \
&& cp inc/* /usr/local/include \
&& chmod 644 /usr/local/lib/libsdrplay_api.so /usr/local/lib/libsdrplay_api.so.3.07 \
&& ldconfig
# install dependencies from vcpkg
RUN git clone https://github.com/microsoft/vcpkg /opt/vcpkg \
&& /opt/vcpkg/bootstrap-vcpkg.sh
ENV PATH="/opt/vcpkg:${PATH}" VCPKG_ROOT=/opt/vcpkg
RUN cd /blah2/lib && vcpkg integrate install \
&& vcpkg install --clean-after-build
# install SDRplay API
RUN chmod +x /blah2/lib/sdrplay-3.14.0/SDRplay_RSP_API-Linux-3.14.0.run \
&& /blah2/lib/sdrplay-3.14.0/SDRplay_RSP_API-Linux-3.14.0.run --tar -xvf -C /blah2/lib/sdrplay-3.14.0 \
&& cp /blah2/lib/sdrplay-3.14.0/x86_64/libsdrplay_api.so.3.14 /usr/local/lib/libsdrplay_api.so \
&& cp /blah2/lib/sdrplay-3.14.0/x86_64/libsdrplay_api.so.3.14 /usr/local/lib/libsdrplay_api.so.3.14 \
&& cp /blah2/lib/sdrplay-3.14.0/inc/* /usr/local/include \
&& chmod 644 /usr/local/lib/libsdrplay_api.so /usr/local/lib/libsdrplay_api.so.3.14 \
&& ldconfig
# install UHD API
RUN uhd_images_downloader

View file

@ -1 +1,23 @@
# todo: write readme on using devcontainer with dev-release
# todo: currently not working
## Usage
Install a recent `nodejs` using [nvm](https://github.com/nvm-sh/nvm).
```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install node 21.6.2
```
Install the latest [devcontainer CLI](https://code.visualstudio.com/docs/devcontainers/devcontainer-cli).
```
npm install -g @devcontainers/cli
devcontainer --version
```
Run the devcontainer.
```
devcontainer up --workspace-folder .
```

View file

@ -1,5 +1,7 @@
version: "3.2"
services:
blah2-dev:
user: vscode
build:

View file

@ -35,7 +35,7 @@ include_directories(src ${UHD_INCLUDE_DIRS})
# TODO: create FindSdrplay.cmake for this
add_library(sdrplay /usr/local/include/sdrplay_api.h)
set_target_properties(sdrplay PROPERTIES LINKER_LANGUAGE C)
target_link_libraries(sdrplay PUBLIC /usr/local/lib/libsdrplay_api.so.3.12)
target_link_libraries(sdrplay PUBLIC /usr/local/lib/libsdrplay_api.so.3.14)
add_executable(blah2
src/blah2.cpp

View file

@ -1,5 +1,6 @@
FROM ubuntu:22.04 as blah2_env
LABEL maintainer="30hours <nathan@30hours.dev>"
LABEL org.opencontainers.image.source https://github.com/30hours/blah2
WORKDIR /blah2
ADD lib lib
@ -23,12 +24,12 @@ RUN cd /blah2/lib && vcpkg integrate install \
&& vcpkg install --clean-after-build
# install SDRplay API
RUN chmod +x /blah2/lib/sdrplay-3.12.1/SDRplay_RSP_API-Linux-3.12.1.run \
&& /blah2/lib/sdrplay-3.12.1/SDRplay_RSP_API-Linux-3.12.1.run --tar -xvf -C /blah2/lib/sdrplay-3.12.1 \
&& cp /blah2/lib/sdrplay-3.12.1/x86_64/libsdrplay_api.so.3.12 /usr/local/lib/libsdrplay_api.so \
&& cp /blah2/lib/sdrplay-3.12.1/x86_64/libsdrplay_api.so.3.12 /usr/local/lib/libsdrplay_api.so.3.12 \
&& cp /blah2/lib/sdrplay-3.12.1/inc/* /usr/local/include \
&& chmod 644 /usr/local/lib/libsdrplay_api.so /usr/local/lib/libsdrplay_api.so.3.12 \
RUN chmod +x /blah2/lib/sdrplay-3.14.0/SDRplay_RSP_API-Linux-3.14.0.run \
&& /blah2/lib/sdrplay-3.14.0/SDRplay_RSP_API-Linux-3.14.0.run --tar -xvf -C /blah2/lib/sdrplay-3.14.0 \
&& cp /blah2/lib/sdrplay-3.14.0/x86_64/libsdrplay_api.so.3.14 /usr/local/lib/libsdrplay_api.so \
&& cp /blah2/lib/sdrplay-3.14.0/x86_64/libsdrplay_api.so.3.14 /usr/local/lib/libsdrplay_api.so.3.14 \
&& cp /blah2/lib/sdrplay-3.14.0/inc/* /usr/local/include \
&& chmod 644 /usr/local/lib/libsdrplay_api.so /usr/local/lib/libsdrplay_api.so.3.14 \
&& ldconfig
# install UHD API

40
Jenkinsfile vendored Normal file
View file

@ -0,0 +1,40 @@
pipeline {
agent any
environment {
GHCR_REGISTRY = "ghcr.io"
GHCR_TOKEN = credentials('ghcr-login')
BLAH2_NAME = "30hours/blah2"
BLAH2_API_NAME = "30hours/blah2_api"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
echo 'Building the project'
sh 'docker build -t $BLAH2_NAME .'
sh 'docker build -t $BLAH2_API_NAME --file ./api/Dockerfile ./api'
}
}
stage('Test') {
steps {
echo 'Running tests'
}
}
stage('Push') {
steps {
sh 'echo $GHCR_TOKEN_PSW | docker login ghcr.io -u $GHCR_TOKEN_USR --password-stdin'
sh 'docker tag $BLAH2_NAME ghcr.io/$BLAH2_NAME'
sh 'docker tag $BLAH2_API_NAME ghcr.io/$BLAH2_API_NAME'
sh 'docker push ghcr.io/$BLAH2_NAME'
sh 'docker push ghcr.io/$BLAH2_API_NAME'
sh 'docker logout'
}
}
}
}

View file

@ -33,8 +33,8 @@ The build environment consists of a docker-compose.yml file running the followin
sudo git clone http://github.com/30hours/blah2 /opt/blah2
cd /opt/blah2
vim config/config.yml
./lib/sdrplay-3.12.1/SDRplay_RSP_API-Linux-3.12.1.run --tar -xvf -C ./lib/sdrplay-3.12.1
./lib/sdrplay-3.12.1/install_lib.sh
./lib/sdrplay-3.14.0/SDRplay_RSP_API-Linux-3.14.0.run --tar -xvf -C ./lib/sdrplay-3.14.0
./lib/sdrplay-3.14.0/install_lib.sh
sudo docker network create blah2
sudo systemctl enable docker
sudo docker compose up -d
@ -61,7 +61,9 @@ The radar processing output is available on [http://localhost:49152](http://loca
## Contributing
Pull requests are welcome - especially for adding support for a new SDR.
Join the [Discord](https://discord.gg/ewNQbeK5Zn) chat for sharing results and support.
Pull requests are welcome - especially for adding support for a new SDR.
- Currently have an issue where the USRP B210 is timing out after 5-10 mins and crashes the code. Convinced it's an issue with my usage of the API - email me for more info.

View file

@ -1,5 +1,8 @@
FROM node:16
LABEL maintainer="30hours <nathan@30hours.dev>"
LABEL org.opencontainers.image.source https://github.com/30hours/blah2
# Create app directory
WORKDIR /usr/src/app

View file

@ -8,6 +8,7 @@
"start": "node server.js"
},
"dependencies": {
"express": "^4.16.1"
"express": "^4.16.1",
"js-yaml": "^4.1.0"
}
}

View file

@ -1,6 +1,17 @@
const express = require('express');
const dgram = require('dgram');
const net = require("net");
const fs = require('fs');
const yaml = require('js-yaml');
const dns = require('dns');
// parse config file
var config;
try {
const file = process.argv[2];
config = yaml.load(fs.readFileSync(file, 'utf8'));
} catch (e) {
console.error('Error reading or parsing the YAML file:', e);
}
var stash_map = require('./stash/maxhold.js');
var stash_detection = require('./stash/detection.js');
@ -9,8 +20,8 @@ var stash_timing = require('./stash/timing.js');
var stash_falsetargets = require('./stash/falsetargets.js');
// constants
const PORT = 3000;
const HOST = '0.0.0.0';
const PORT = config.network.ports.api;
const HOST = config.network.ip;
var map = '';
var detection = '';
var track = '';
@ -18,7 +29,6 @@ var timestamp = '';
var timing = '';
var iqdata = '';
var falsetargets = '';
var data = '';
var data_map;
var data_detection;
var data_tracker;
@ -59,9 +69,34 @@ app.get('/api/timing', (req, res) => {
app.get('/api/iqdata', (req, res) => {
res.send(iqdata);
});
app.get('/api/adsb2dd', (req, res) => {
if (config.truth.adsb.enabled == true) {
const api_url = "https://adsb2dd.30hours.dev/api/dd";
const api_query =
api_url +
"?rx=" + config.location.rx.latitude + "," +
config.location.rx.longitude + "," +
config.location.rx.altitude +
"&tx=" + config.location.tx.latitude + "," +
config.location.tx.longitude + "," +
config.location.tx.altitude +
"&fc=" + (config.capture.fc / 1000000) +
"&server=" + "http://" + config.truth.adsb.ip;
const jsonResponse = {
url: api_query
};
res.json(jsonResponse);
}
else {
res.status(400).end();
}
});
app.get('/api/falsetargets', (req, res) => {
res.send(falsetargets);
});
app.get('/api/config', (req, res) => {
res.send(config);
});
// stash API
app.get('/stash/map', (req, res) => {
@ -94,57 +129,56 @@ app.listen(PORT, HOST, () => {
});
// tcp listener map
const server_map = net.createServer((socket) => {
socket.write("Hello From Server!")
socket.on("data", (msg) => {
data_map = data_map + msg.toString();
if (data_map.slice(-1) === "}") {
map = data_map;
data_map = '';
}
});
socket.on("close", () => {
console.log("Connection closed.");
})
const server_map = net.createServer((socket)=>{
socket.on("data",(msg)=>{
data_map = data_map + msg.toString();
if (data_map.slice(-1) === "}")
{
map = data_map;
data_map = '';
}
});
socket.on("close",()=>{
console.log("Connection closed.");
})
});
server_map.listen(3001);
server_map.listen(config.network.ports.map);
// tcp listener detection
const server_detection = net.createServer((socket) => {
socket.write("Hello From Server!")
socket.on("data", (msg) => {
data_detection = data_detection + msg.toString();
if (data_detection.slice(-1) === "}") {
detection = data_detection;
data_detection = '';
}
const server_detection = net.createServer((socket)=>{
socket.on("data",(msg)=>{
data_detection = data_detection + msg.toString();
if (data_detection.slice(-1) === "}")
{
detection = data_detection;
data_detection = '';
}
});
socket.on("close", () => {
console.log("Connection closed.");
})
});
server_detection.listen(3002);
server_detection.listen(config.network.ports.detection);
// tcp listener tracker
const server_tracker = net.createServer((socket) => {
socket.write("Hello From Server!")
socket.on("data", (msg) => {
data_tracker = data_tracker + msg.toString();
if (data_tracker.slice(-1) === "}") {
track = data_tracker;
data_tracker = '';
}
const server_tracker = net.createServer((socket)=>{
socket.on("data",(msg)=>{
data_tracker = data_tracker + msg.toString();
if (data_tracker.slice(-1) === "}")
{
track = data_tracker;
data_tracker = '';
}
});
socket.on("close", () => {
console.log("Connection closed.");
})
});
server_tracker.listen(3003);
server_tracker.listen(config.network.ports.track);
// tcp listener timestamp
const server_timestamp = net.createServer((socket) => {
socket.write("Hello From Server!")
socket.on("data", (msg) => {
const server_timestamp = net.createServer((socket)=>{
socket.on("data",(msg)=>{
data_timestamp = data_timestamp + msg.toString();
timestamp = data_timestamp;
data_timestamp = '';
@ -153,12 +187,11 @@ const server_timestamp = net.createServer((socket) => {
console.log("Connection closed.");
})
});
server_timestamp.listen(4000);
server_timestamp.listen(config.network.ports.timestamp);
// tcp listener timing
const server_timing = net.createServer((socket) => {
socket.write("Hello From Server!")
socket.on("data", (msg) => {
const server_timing = net.createServer((socket)=>{
socket.on("data",(msg)=>{
data_timing = data_timing + msg.toString();
if (data_timing.slice(-1) === "}") {
timing = data_timing;
@ -169,12 +202,11 @@ const server_timing = net.createServer((socket) => {
console.log("Connection closed.");
})
});
server_timing.listen(4001);
server_timing.listen(config.network.ports.timing);
// tcp listener iqdata metadata
const server_iqdata = net.createServer((socket) => {
socket.write("Hello From Server!")
socket.on("data", (msg) => {
const server_iqdata = net.createServer((socket)=>{
socket.on("data",(msg)=>{
data_iqdata = data_iqdata + msg.toString();
if (data_iqdata.slice(-1) === "}") {
iqdata = data_iqdata;
@ -185,7 +217,8 @@ const server_iqdata = net.createServer((socket) => {
console.log("Connection closed.");
})
});
server_iqdata.listen(4002);
server_iqdata.listen(config.network.ports.iqdata);
// tcp listener falsetargets
const server_falsetargets = net.createServer((socket) => {
@ -201,4 +234,4 @@ const server_falsetargets = net.createServer((socket) => {
console.log("Connection closed.");
})
});
server_falsetargets.listen(4003);
server_falsetargets.listen(4004);

View file

@ -14,13 +14,15 @@ process:
buffer: 1.5
overlap: 0
ambiguity:
delayMin: -10 # bins
delayMax: 400 # bins
dopplerMin: -200 # Hz
dopplerMax: 200 # Hz
# delay in bins
delayMin: -10
delayMax: 400
# Doppler in Hz
dopplerMin: -200
dopplerMax: 200
clutter:
delayMin: -10 # bins
delayMax: 400 # bins
delayMin: -10
delayMax: 400
detection:
pfa: 0.00001
nGuard: 2
@ -46,21 +48,34 @@ network:
timestamp: 4000
timing: 4001
iqdata: 4002
falsetargets: 4003
config: 4003
falsetargets: 4004
truth:
asdb:
adsb:
enabled: false
ip: 0.0.0.0
port: 30000
ip: 'adsb.30hours.dev'
port: 80
ais:
enabled: false
ip: 0.0.0.0
port: 30001
location:
rx:
latitude: -34.9286
longitude: 138.5999
altitude: 50
name: "Adelaide"
tx:
latitude: -34.9810
longitude: 138.7081
altitude: 750
name: "Mount Lofty"
save:
iq: false
map: false
detection: false
timing: false
path: "/blah2/save/"
path: "/blah2/save/"

View file

@ -50,20 +50,33 @@ network:
timestamp: 4000
timing: 4001
iqdata: 4002
config: 4003
truth:
asdb:
adsb:
enabled: false
ip: 0.0.0.0
port: 30000
ip: 'adsb.30hours.dev'
port: 80
ais:
enabled: false
ip: 0.0.0.0
port: 30001
location:
rx:
latitude: -34.9286
longitude: 138.5999
altitude: 50
name: "Adelaide"
tx:
latitude: -34.9810
longitude: 138.7081
altitude: 750
name: "Mount Lofty"
save:
iq: true
map: false
detection: false
timing: false
path: "/blah2/save/"
path: "/blah2/save/"

View file

@ -46,20 +46,33 @@ network:
timestamp: 4000
timing: 4001
iqdata: 4002
config: 4003
truth:
asdb:
adsb:
enabled: false
ip: 0.0.0.0
port: 30000
ip: 'adsb.30hours.dev'
port: 80
ais:
enabled: false
ip: 0.0.0.0
port: 30001
location:
rx:
latitude: -34.9286
longitude: 138.5999
altitude: 50
name: "Adelaide"
tx:
latitude: -34.9810
longitude: 138.7081
altitude: 750
name: "Mount Lofty"
save:
iq: true
map: false
detection: false
timing: false
path: "/blah2/save/"
path: "/blah2/save/"

View file

@ -17,7 +17,7 @@ services:
volumes:
- ./config:/blah2/config
- /opt/blah2/save:/blah2/save
- /dev/shm:/dev/shm
- /dev/shm:/dev/shm:z
- /dev/usb:/dev/usb
network_mode: host
privileged: true
@ -25,7 +25,7 @@ services:
sh -c "/blah2/bin/blah2 -c config/config.yml"
container_name: blah2
blah2_frontend:
blah2_web:
restart: always
image: httpd:2.4
ports:
@ -40,7 +40,9 @@ services:
restart: always
build: ./api
image: blah2_api
ports:
- 3000:8080
volumes:
- ./config:/usr/src/app/config
network_mode: host
command: >
sh -c "node server.js /usr/src/app/config/config.yml"
container_name: blah2-api

19
host/README.md Normal file
View file

@ -0,0 +1,19 @@
# blah2 Host
A reverse proxy to host blah2 on the internet.
## Description
This can be used to forward the radar to the internet. The radar front-end is at `localhost:49152`, and the API is located at `localhost:3000/api/<data>`. This reverse proxy forwards `localhost:49152` to a port of your choosing, and forwards `localhost:3000` to the same port at `/api/`.
## Usage
**docker-compose.yml**
- Change the output port from `8080` as desired in `docker-compose.yml`.
- The environment variable `VIRTUAL_HOST=domain.tld` is only applicable if also using [jwilder/nginx](https://github.com/nginx-proxy/nginx-proxy).
- If using [jwilder/nginx](https://github.com/nginx-proxy/nginx-proxy), ensure both containers are on the same network with `sudo docker network create <name>`. Otherwise the network configuration can be deleted.
**nginx.conf**
- Edit the `backend_ip` and `domain_name` variables in `nginx.conf`.

20
host/docker-compose.yml Normal file
View file

@ -0,0 +1,20 @@
version: '3'
networks:
nginx-web:
external: true
services:
httpd:
restart: always
image: nginx:1.25.2-alpine
ports:
- 8080:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./html:/usr/local/apache2/htdocs
environment:
- VIRTUAL_HOST=domain.tld
networks:
- nginx-web
container_name: blah2

1
host/html/error.html Normal file
View file

@ -0,0 +1 @@
radar/down

58
host/nginx.conf Normal file
View file

@ -0,0 +1,58 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
default_type application/octet-stream;
include /etc/nginx/mime.types;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen [::]:80 default_server;
include /etc/nginx/mime.types;
set $backend_ip localhost;
set $domain_name localhost;
proxy_pass_header Content-Type;
proxy_set_header X-Real-IP $domain_name;
proxy_set_header X-Forwarded-For $domain_name;
proxy_set_header Host $domain_name;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 1;
proxy_next_upstream error timeout http_500 http_502 http_503 http_504 http_404;
proxy_intercept_errors on;
location / {
proxy_pass http://$backend_ip:49152;
}
location ~ ^/(maxhold|api|stash)/(.*) {
proxy_pass http://$backend_ip:3000/$1/$2;
}
error_page 501 502 503 504 =200 /error.html;
location = /error.html {
root /usr/local/apache2/htdocs;
}
}
}

View file

@ -8,6 +8,9 @@ var range_y = [];
// setup API
var urlTimestamp;
var urlDetection;
var urlAdsb;
var urlAdsbLink;
var urlConfig;
if (isLocalHost) {
urlTimestamp = '//' + host + ':3000/api/timestamp';
} else {
@ -23,10 +26,33 @@ if (isLocalHost) {
} else {
urlMap = '//' + host + urlMap;
}
if (isLocalHost) {
urlAdsbLink = '//' + host + ':3000/api/adsb2dd';
} else {
urlAdsbLink = '//' + host + '/api/adsb2dd';
}
if (isLocalHost) {
urlConfig = '//' + host + ':3000/api/config';
} else {
urlConfig = '//' + host + '/api/config';
}
urlTimestamp = urlTimestamp + '?timestamp=' + Date.now();
urlDetection = urlDetection + '?timestamp=' + Date.now();
urlMap = urlMap + '?timestamp=' + Date.now();
// get truth flag
var isTruth = false;
var configData = $.getJSON(urlConfig, function () { })
.done(function (data_config) {
if (data_config.truth.adsb.enabled === true) {
isTruth = true;
var adsbLinkData = $.getJSON(urlAdsbLink, function () { })
.done(function (data) {
urlAdsb = data.url;
})
}
});
// setup plotly
var layout = {
autosize: true,
@ -65,7 +91,8 @@ var layout = {
ticksuffix: ' ',
autosize: false,
categoryorder: "total descending"
}
},
showlegend: false
};
var config = {
responsive: true,
@ -82,6 +109,7 @@ var data = [
}
];
var detection = [];
var adsb = {};
Plotly.newPlot('data', data, layout, config);
@ -101,6 +129,23 @@ var intervalId = window.setInterval(function () {
detection = data_detection;
});
// get ADS-B data if enabled in config
if (isTruth) {
var adsbData = $.getJSON(urlAdsb, function () { })
.done(function (data_adsb) {
adsb['delay'] = [];
adsb['doppler'] = [];
adsb['flight'] = [];
for (const aircraft in data_adsb) {
if ('doppler' in data_adsb[aircraft]) {
adsb['delay'].push(data_adsb[aircraft]['delay'])
adsb['doppler'].push(data_adsb[aircraft]['doppler'])
adsb['flight'].push(data_adsb[aircraft]['flight'])
}
}
});
}
// get new map data
var apiData = $.getJSON(urlMap, function () { })
.done(function (data) {
@ -136,17 +181,28 @@ var intervalId = window.setInterval(function () {
opacity: 0.6
}
};
var trace3 = {
x: adsb.delay,
y: adsb.doppler,
mode: 'markers',
type: 'scatter',
marker: {
size: 16,
opacity: 0.6
}
};
var data_trace = [trace1, trace2];
var data_trace = [trace1, trace2, trace3];
Plotly.newPlot('data', data_trace, layout, config);
}
// case update plot
else {
var trace_update = {
x: [data.delay, detection.delay],
y: [data.doppler, detection.doppler],
z: [data.data, []],
zmax: [Math.max(13, data.maxPower), []]
x: [data.delay, detection.delay, adsb.delay],
y: [data.doppler, detection.doppler, adsb.doppler],
z: [data.data, [], []],
zmax: [Math.max(13, data.maxPower), [], []],
text: [[], [], adsb.flight]
};
Plotly.update('data', trace_update);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -61,7 +61,8 @@ int main(int argc, char **argv)
// setup capture
uint32_t fs, fc;
std::string type, path, replayFile;
uint16_t port_capture;
std::string type, path, replayFile, ip_capture;
bool saveIq, state, loop;
tree["capture"]["fs"] >> fs;
tree["capture"]["fc"] >> fc;
@ -71,6 +72,8 @@ int main(int argc, char **argv)
tree["capture"]["replay"]["state"] >> state;
tree["capture"]["replay"]["loop"] >> loop;
tree["capture"]["replay"]["file"] >> replayFile;
tree["network"]["ip"] >> ip_capture;
tree["network"]["ports"]["api"] >> port_capture;
Capture *capture = new Capture(type, fs, fc, path);
if (state)
{
@ -85,7 +88,8 @@ int main(int argc, char **argv)
// run capture
std::thread t1([&]{capture->process(buffer1, buffer2,
tree["capture"]["device"]);});
tree["capture"]["device"], ip_capture, port_capture);
});
// setup process CPI
double tCpi;

View file

@ -20,7 +20,8 @@ Capture::Capture(std::string _type, uint32_t _fs, uint32_t _fc, std::string _pat
saveIq = false;
}
void Capture::process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config)
void Capture::process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config,
std::string ip_capture, uint16_t port_capture)
{
std::cout << "Setting up device " + type << std::endl;
@ -31,7 +32,8 @@ void Capture::process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config)
{
while (true)
{
httplib::Client cli("http://127.0.0.1:3000");
httplib::Client cli("http://" + ip_capture + ":"
+ std::to_string(port_capture));
httplib::Result res = cli.Get("/capture");
// if capture status changed

View file

@ -59,8 +59,11 @@ public:
/// @param buffer1 Buffer for reference samples.
/// @param buffer2 Buffer for surveillance samples.
/// @param config Yaml config for device.
/// @param ip_capture IP address of capture API.
/// @param port_capture Port of capture API.
/// @return Void.
void process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config);
void process(IqData *buffer1, IqData *buffer2, c4::yml::NodeRef config,
std::string ip_capture, uint16_t port_capture);
std::unique_ptr<Source> factory_source(const std::string& type,
c4::yml::NodeRef config);

View file

@ -0,0 +1,67 @@
#include "Simulator.h"
// constructor
Simulator::Simulator(std::string _type, uint32_t _fc, uint32_t _fs,
std::string _path, bool *_saveIq,
uint32_t _n_min = 1000,
std::string _falseTargetsConfigFilePath,
std::string _configFilePath)
: Source(_type, _fc, _fs, _path, _saveIq)
{
n_min = _n_min;
u_int64_t total_samples = 0;
false_targets_config_file_path = _falseTargetsConfigFilePath;
config_file_path = _configFilePath;
}
void Simulator::start()
{
}
void Simulator::stop()
{
}
void Simulator::process(IqData *buffer1, IqData *buffer2)
{
const u_int32_t samples_per_iteration = 1000;
Target false_targets = Target(false_targets_config_file_path, config_file_path, fs, fc);
while (true)
{
uint32_t n_start = buffer1->get_length();
if (n_start < n_min)
{
// create a random number generator
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(-(2 ^ 14), 2 ^ 14);
buffer1->lock();
buffer2->lock();
for (uint16_t i = 0; i < samples_per_iteration; i++)
{
buffer1->push_back({(double)dis(gen), (double)dis(gen)});
try
{
std::complex<double> response = false_targets.process(buffer1);
response += std::complex<double>((double)dis(gen), (double)dis(gen));
buffer2->push_back(response);
}
catch (const std::exception &e)
{
buffer2->push_back({(double)dis(gen), (double)dis(gen)});
}
}
total_samples += samples_per_iteration;
buffer1->unlock();
buffer2->unlock();
}
}
}
void Simulator::replay(IqData *buffer1, IqData *buffer2, std::string file, bool loop)
{
}

View file

@ -0,0 +1,85 @@
/// @file Simulator.h
/// @class Simulator
/// @brief A class to generate simulated IQ data with false targets.
/// @details This class generates simulated IQ data with false targets.
/// It generates a random reference and surveillance signal and uses the
/// Target class to add false targets to the surveillance signal.
///
/// @author bennysomers
/// @todo Simulate a single false target
/// @todo Simulate false targets
/// @todo Simulate realistic target motion
/// @todo Simulate N channels, instead of just 2
/// @todo Add more waveforms (e.g. LFM).
#ifndef SIMULATOR_H
#define SIMULATOR_H
#include "capture/Source.h"
#include "Target.h"
#include "data/IqData.h"
#include <stdint.h>
#include <string>
#include <iostream>
#include <vector>
#include <complex>
#include <random>
class Simulator : public Source
{
private:
/// @brief Number of samples to generate each loop.
/// @details This is the threshold for the minimum number of samples
/// left in the buffer before new samples will be generated.
uint32_t n_min;
/// @brief Total number of samples generated.
/// @details This is used to keep track of the total number of samples
/// generated, so that the Doppler shift can be calculated.
u_int64_t total_samples;
/// @brief Path to the false targets configuration file.
std::string false_targets_config_file_path;
/// @brief Path to the radar configuration file.
std::string config_file_path;
public:
/// @brief Constructor.
/// @param type Type of source. = "IQSimulator"
/// @param fc Center frequency (Hz).
/// @param fs Sample rate (Hz).
/// @param path Path to save IQ data.
/// @param saveIq Pointer to boolean to save IQ data.
/// @param n Number of samples.
/// @return The object.
Simulator(std::string type, uint32_t fc, uint32_t fs, std::string path,
bool *saveIq, uint32_t n_min,
std::string false_targets_config_file_path = "config/false_targets.yml",
std::string config_file_path = "config/config.yml");
/// @brief Implement capture function on IQSimulator.
/// @param buffer1 Pointer to reference buffer.
/// @param buffer2 Pointer to surveillance buffer.
/// @return Void.
void process(IqData *buffer1, IqData *buffer2);
/// @brief Call methods to start capture.
/// @return Void.
void start();
/// @brief Call methods to gracefully stop capture.
/// @return Void.
void stop();
/// @brief Implement replay function on IQSimulator.
/// @param buffer1 Pointer to reference buffer.
/// @param buffer2 Pointer to surveillance buffer.
/// @param file Path to file to replay data from.
/// @param loop True if samples should loop at EOF.
/// @return Void.
void replay(IqData *buffer1, IqData *buffer2, std::string file, bool loop);
};
#endif

View file

@ -0,0 +1,226 @@
#include "Target.h"
// this is straight up copied from blah2.cpp, but I don't know the best way to access that function here.
// edit: put it in utilities?
std::string ryml_get_file_copy(const char *filename);
// constants
const std::string Target::VALID_TYPE[2] = {"static", "moving_radar"};
const std::string Target::VALID_STATE[1] = {"active"};
// constructor
Target::Target(std::string false_tgt_config_path, std::string config_path, uint32_t fs, uint32_t fc)
{
// Read in the false targets config file
std::string config = ryml_get_file_copy(false_tgt_config_path.c_str());
ryml::Tree tree = ryml::parse_in_arena(ryml::to_csubstr(config));
// Create a FalseTarget object for each target in the config file
for (auto target_node : tree["targets"].children())
{
if (target_node["state"].val() == VALID_STATE[0])
{
try
{
targets.push_back(FalseTarget(target_node, fs, fc));
}
catch (const std::exception &e)
{
std::cerr << e.what() << '\n';
}
}
}
// Create the socket using details from the config file.
config = ryml_get_file_copy(config_path.c_str());
tree = ryml::parse_in_arena(ryml::to_csubstr(config));
std::string ip;
uint16_t port;
tree["network"]["ip"] >> ip;
tree["network"]["ports"]["falsetargets"] >> port;
socket = new Socket(ip, port);
sample_counter = 0;
}
std::complex<double> Target::process(IqData *ref_buffer)
{
std::complex<double> response = std::complex<double>(0, 0);
// loop through each target
for (auto &target : targets)
{
response += target.process(ref_buffer);
}
// output false target truth
if (sample_counter % 100000 == 0)
{
rapidjson::Document document;
document.SetObject();
rapidjson::Document::AllocatorType &allocator = document.GetAllocator();
rapidjson::Value json_false_targets(rapidjson::kArrayType);
for (auto target : targets)
{
json_false_targets.PushBack(target.to_json(allocator), allocator);
}
document.AddMember("false_targets", json_false_targets, allocator);
rapidjson::StringBuffer strbuf;
rapidjson::Writer<rapidjson::StringBuffer> writer(strbuf);
writer.SetMaxDecimalPlaces(2);
document.Accept(writer);
socket->sendData(strbuf.GetString());
}
sample_counter++;
return response;
}
double FalseTarget::get_range()
{
return range;
}
void FalseTarget::set_range(double _range)
{
range = _range;
delay = range / Constants::c;
delay_samples = delay * fs;
}
double FalseTarget::get_delay()
{
return delay;
}
void FalseTarget::set_delay(double _delay)
{
delay = _delay;
range = delay * Constants::c;
delay_samples = delay * fs;
}
FalseTarget::FalseTarget(c4::yml::NodeRef target_node, uint32_t _fs, uint32_t _fc)
{
target_node["id"] >> id;
target_node["type"] >> type;
fs = _fs;
fc = _fc;
sample_counter = 0;
if (type == TgtGen::VALID_TYPE[0])
{
target_node["location"]["range"] >> range;
delay = range / Constants::c;
delay_samples = delay * fs;
target_node["velocity"]["doppler"] >> doppler;
target_node["rcs"] >> rcs;
}
else if (type == TgtGen::VALID_TYPE[1])
{
target_node["location"]["range"] >> range;
start_range = range;
delay = range / Constants::c;
delay_samples = delay * fs;
target_node["velocity"]["doppler"] >> doppler;
target_node["velocity"]["dopplerRate"] >> doppler_rate;
target_node["rcs"] >> rcs;
}
else
{
throw std::invalid_argument("Invalid target type");
}
}
std::complex<double> FalseTarget::process(IqData *ref_buffer)
{
uint32_t buffer_length = ref_buffer->get_length();
std::complex<double> response = 0;
try
{
response = Conversions::db2lin(rcs) * ref_buffer->get_sample(buffer_length - delay_samples);
response *= std::exp(std::polar<double>(1, 2 * M_PI * doppler * buffer_length / fs));
if (type == TgtGen::VALID_TYPE[1])
{
double range_rate = -1 * doppler * Constants::c / 2.0 / fc;
set_range(range + (range_rate / fs));
// very basic PD controller
// will need tuning for different fs
doppler_rate += 0.0000001 * (range - start_range) / start_range; // target tends towards start_range
// doppler_rate -= doppler / std::abs(doppler) / std::max(std::abs(doppler), 0.1) / 100; // target tends towards 0 Doppler
doppler_rate = std::clamp(doppler_rate, -5.0, 5.0); // clamp to a reasonable value
doppler += doppler_rate / fs; // update doppler
doppler = std::clamp(doppler,
std::max(-range, -250.0),
std::min(range, 250.0)); // clamp to range
}
sample_counter++;
}
catch (const std::exception &e)
{
}
return response;
}
rapidjson::Value FalseTarget::to_json(rapidjson::Document::AllocatorType &allocator)
{
rapidjson::Value target(rapidjson::kObjectType);
target.AddMember("id", id, allocator);
target.AddMember("type", rapidjson::Value(type.c_str(), allocator).Move(), allocator);
try
{
if (type == TgtGen::VALID_TYPE[0])
{
target.AddMember("range", range, allocator);
target.AddMember("delay", delay, allocator);
target.AddMember("delay_samples", delay_samples, allocator);
target.AddMember("doppler", doppler, allocator);
target.AddMember("rcs", rcs, allocator);
}
else if (type == TgtGen::VALID_TYPE[1])
{
target.AddMember("range", range, allocator);
target.AddMember("start_range", start_range, allocator);
target.AddMember("delay", delay, allocator);
target.AddMember("delay_samples", delay_samples, allocator);
target.AddMember("doppler", doppler, allocator);
target.AddMember("doppler_rate", doppler_rate, allocator);
target.AddMember("rcs", rcs, allocator);
}
else
{
throw std::invalid_argument("Invalid target type");
}
}
catch (const std::exception &e)
{
std::cerr << e.what() << '\n';
}
return target;
}
std::string ryml_get_file_copy(const char *filename)
{
std::ifstream in(filename, std::ios::in | std::ios::binary);
if (!in)
{
std::cerr << "could not open " << filename << std::endl;
exit(1);
}
std::ostringstream contents;
contents << in.rdbuf();
return contents.str();
}

View file

@ -0,0 +1,143 @@
/// @file TgtGen.h
/// @class TgtGen
/// @brief A class to generate false targets.
/// @details
/// Static Targets: remain at a fixed range/delay and Doppler.
/// @author bennysomers
/// @todo Simulate a false target moving in radar coordinates
/// @todo Simulate a false target moving in spatial coordinates
#ifndef TGTGEN_H
#define TGTGEN_H
#include "data/IqData.h"
#include "utilities/Conversions.h"
#include "data/meta/Constants.h"
#include "process/utility/Socket.h"
#include <ryml/ryml.hpp>
#include <ryml/ryml_std.hpp>
#include <c4/format.hpp>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/filewritestream.h"
#include <stdint.h>
#include <string>
#include <iostream>
#include <vector>
#include <fstream>
#include <complex>
#include <math.h>
#include <iomanip>
class FalseTarget
{
private:
/// @brief fs
uint32_t fs;
/// @brief fc
uint32_t fc;
/// @brief Target delay
double delay;
/// @brief Target delay in samples
uint32_t delay_samples;
/// @brief Target range
double range;
/// @brief Target starting range
double start_range;
/// @brief Sample counter
uint64_t sample_counter;
public:
/// @brief Target type.
std::string type;
/// @brief Target Doppler
double doppler;
/// @brief Target Doppler Rate
double doppler_rate;
/// @brief Target RCS
double rcs;
/// @brief Target ID
u_int32_t id;
/// @brief Constructor for targets.
/// @return The object.
FalseTarget(c4::yml::NodeRef target_node, uint32_t _fs, uint32_t _fc);
/// @brief Generate the signal from a false target.
/// @param ref_buffer Pointer to reference buffer.
/// @return Target reflection signal.
std::complex<double> process(IqData *ref_buffer);
/// @brief Getter for range.
/// @return Range in meters.
double get_range();
/// @brief Setter for range.
/// @param range Range in meters.
void set_range(double range);
/// @brief Getter for delay.
/// @return Delay in seconds.
double get_delay();
/// @brief Setter for delay.
/// @param delay Delay in seconds.
void set_delay(double delay);
/// @brief Outputs false target truth as JSON
/// @return JSON string.
rapidjson::Value to_json(rapidjson::Document::AllocatorType &allocator);
};
class TgtGen
{
private:
/// @brief Vector of false targets.
std::vector<FalseTarget> targets;
/// @brief Socket to send false target data.
Socket *socket;
/// @brief Sample counter
uint64_t sample_counter;
public:
/// @brief The valid false target types.
static const std::string VALID_TYPE[2];
/// @brief The valid false target states.
static const std::string VALID_STATE[1];
/// @brief Constructor.
/// @param false_tgt_config_path Path to false targets configuration file.
/// @param config_path Path to blah2 config file.
/// @param fs Sample rate (Hz).
/// @param fc Center frequency (Hz).
/// @return The object.
TgtGen(std::string false_tgt_config_path, std::string config_path, uint32_t fs, uint32_t fc);
/// @brief Generate the signal from all false targets.
/// @param ref_buffer Pointer to reference buffer.
/// @return Targets reflection signal.
std::complex<double> process(IqData *ref_buffer);
};
#endif
std::string ryml_get_file(const char *filename);

View file

@ -6,7 +6,12 @@ const uint32_t Socket::MTU = 1024;
Socket::Socket(const std::string& ip, uint16_t port)
: endpoint(asio::ip::address::from_string(ip), port), socket(io_context) {
socket.connect(endpoint);
try {
socket.connect(endpoint);
} catch (const std::exception& e) {
std::cerr << "Error connecting to endpoint: " << e.what() << std::endl;
throw;
}
}
Socket::~Socket()