mirror of
https://github.com/skinnyrad/Lora-Scanner.git
synced 2026-05-07 13:55:04 +02:00
Merge pull request #4 from skinnyrad/feat/dragino-lps8n
Feat/dragino lps8n
This commit is contained in:
@@ -3,10 +3,12 @@ from markupsafe import escape
|
||||
from flask_socketio import SocketIO, emit
|
||||
import serial
|
||||
import threading
|
||||
from threading import Timer
|
||||
import time
|
||||
from collections import deque
|
||||
import pandas as pd
|
||||
import re
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
app = Flask(__name__)
|
||||
socketio = SocketIO(app)
|
||||
@@ -18,9 +20,9 @@ port3_status = True
|
||||
global ser1
|
||||
global ser2
|
||||
global ser3
|
||||
global_dataframe = pd.DataFrame(columns=['Device Name', 'Frequency', 'Signal Strength', 'Plaintext'])
|
||||
frequency = lambda port: {'port1': 433, 'port2': 868,'port3': 915}.get(port, None)
|
||||
surveydata = {}
|
||||
parsed_entries = set()
|
||||
|
||||
def read_serial_data(port, ser, buffer):
|
||||
global surveydata
|
||||
@@ -54,7 +56,8 @@ def read_serial_data(port, ser, buffer):
|
||||
surveydata[key] = []
|
||||
|
||||
# Append the new values to the list for this frequency
|
||||
surveydata[key].append([freq, rssi, decoded_value])
|
||||
current_timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
|
||||
surveydata[key].append([freq, rssi, f"{decoded_value} -- Timestamp:{current_timestamp}"])
|
||||
|
||||
# Reset rssi and decoded_value for next packet
|
||||
rssi = None
|
||||
@@ -72,11 +75,88 @@ def read_serial_data(port, ser, buffer):
|
||||
time.sleep(0.1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
#print("Could not access Serial Port")
|
||||
#print(f"Error: {e}")
|
||||
pass
|
||||
|
||||
|
||||
def parse_and_store_data():
|
||||
global surveydata
|
||||
url = "http://10.130.1.1/cgi-bin/log-traffic.has" # Your target URL
|
||||
headers = {
|
||||
"Host": "10.130.1.1",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"DNT": "1",
|
||||
"Sec-GPC": "1",
|
||||
"Authorization": "Basic cm9vdDpkcmFnaW5v",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "http://10.130.1.1/cgi-bin/log-lora.has",
|
||||
"Upgrade-Insecure-Requests": "1"
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
table = soup.find('table')
|
||||
rows = table.find_all('tr')
|
||||
headers = [header.text.strip() for header in rows[0].find_all('th')][1:]
|
||||
|
||||
for row in rows[1:]:
|
||||
cells = row.find_all('td')
|
||||
cell_data = [cell.text.strip() for cell in cells[1:] if cells.index(cell) < len(headers) + 1]
|
||||
formatted_row = ' | '.join(cell_data)
|
||||
|
||||
dev_id = extract_dev_id(formatted_row) # Your existing function to extract DevEui or DevAddr
|
||||
freq = extract_freq(formatted_row) # Your existing function to extract frequency
|
||||
|
||||
if dev_id and freq:
|
||||
entry_identifier = f"{dev_id}_{formatted_row}" # Create a unique identifier for the entry
|
||||
|
||||
# Only process the entry if we haven't seen this identifier before
|
||||
if entry_identifier not in parsed_entries:
|
||||
parsed_entries.add(entry_identifier) # Add the identifier to the set
|
||||
|
||||
# Initialize dictionary for dev_id if not present
|
||||
if dev_id not in surveydata:
|
||||
surveydata[dev_id] = []
|
||||
|
||||
# Append new data to the list associated with the DevEui or DevAddr
|
||||
surveydata[dev_id].append([freq, 0, formatted_row])
|
||||
|
||||
print("Data parsed and stored.")
|
||||
else:
|
||||
print(f"Request failed with status code: {response.status_code}")
|
||||
|
||||
|
||||
# Schedule the next call to this function
|
||||
Timer(60, parse_and_store_data).start() # Call this function every 60 seconds
|
||||
|
||||
|
||||
def extract_dev_id(formatted_row):
|
||||
# Assuming DevEui or DevAddr is in the 'Content' part of the formatted_row
|
||||
# and it's formatted like 'Dev Addr: {DevEui}, Size: {Size}'
|
||||
try:
|
||||
content_part = formatted_row.split('|')[-1].strip() # Get the last part of the formatted_row, which is 'Content'
|
||||
dev_id = content_part.split(',')[0].split(':')[-1].strip() # Extract the DevEui or DevAddr
|
||||
return dev_id
|
||||
except Exception as e:
|
||||
print(f"Error extracting DevEui/DevAddr: {e}")
|
||||
return None # Return None or some default value if extraction fails
|
||||
|
||||
|
||||
def extract_freq(formatted_row):
|
||||
# Assuming 'Freq' is a standalone field in the formatted_row
|
||||
try:
|
||||
freq_part = formatted_row.split('|')[3].strip() # Get the 'Freq' part (assuming it's the fifth field)
|
||||
freq = float(freq_part) # Convert the frequency to float
|
||||
return freq
|
||||
except Exception as e:
|
||||
print(f"Error extracting frequency: {e}")
|
||||
return None # Return None or some default value if extraction fails
|
||||
|
||||
|
||||
def connect_serial(port,frequency):
|
||||
@@ -144,7 +224,7 @@ def analysis():
|
||||
|
||||
@app.route('/survey')
|
||||
def survey():
|
||||
return render_template('survey.html', data=global_dataframe)
|
||||
return render_template('survey.html')
|
||||
|
||||
@app.route('/tracking')
|
||||
def tracking():
|
||||
@@ -255,8 +335,16 @@ def checkSer():
|
||||
@app.route('/get_table_data')
|
||||
def get_table_data():
|
||||
global surveydata
|
||||
print(surveydata)
|
||||
return jsonify(surveydata)
|
||||
cleaned_data = {}
|
||||
|
||||
for dev_id, data in surveydata.items():
|
||||
if dev_id: # Check if dev_id is not empty
|
||||
cleaned_data[dev_id] = data
|
||||
|
||||
#print(cleaned_data) # For debugging
|
||||
return jsonify(cleaned_data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Timer(60, parse_and_store_data).start()
|
||||
socketio.run(app, debug=True)
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# Define the URL and headers
|
||||
url = "http://10.130.1.1/cgi-bin/log-traffic.has"
|
||||
headers = {
|
||||
"Host": "10.130.1.1",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"DNT": "1",
|
||||
"Sec-GPC": "1",
|
||||
"Authorization": "Basic cm9vdDpkcmFnaW5v",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "http://10.130.1.1/cgi-bin/log-lora.has",
|
||||
"Upgrade-Insecure-Requests": "1"
|
||||
}
|
||||
|
||||
# Send the GET request
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
# Parse the HTML content using BeautifulSoup
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
|
||||
# Find the table
|
||||
table = soup.find('table')
|
||||
|
||||
# Initialize an empty list to store formatted strings for each row
|
||||
formatted_rows = []
|
||||
|
||||
# Find all table rows
|
||||
rows = table.find_all('tr')
|
||||
|
||||
# Get column headers from the first row
|
||||
# Using .text.strip() to clean the text and [1:] to skip the empty first column
|
||||
headers = [header.text.strip() for header in rows[0].find_all('th')][1:]
|
||||
|
||||
# Iterate through each row (skipping the first row with the headers)
|
||||
for row in rows[1:]:
|
||||
# Find all data cells (td tags) in the row
|
||||
cells = row.find_all('td')
|
||||
|
||||
# Extract text from each cell
|
||||
# Using [1:] to skip the first cell with the arrow icon
|
||||
cell_data = [cell.text.strip() for cell in cells[1:] if cells.index(cell) < len(headers) + 1]
|
||||
|
||||
# Format the row data into a neat line
|
||||
formatted_row = ' | '.join(cell_data)
|
||||
|
||||
# Append the formatted string to the list
|
||||
formatted_rows.append(formatted_row)
|
||||
|
||||
# Print the formatted string to display the row
|
||||
print(formatted_row)
|
||||
else:
|
||||
print("Request failed with status code:", response.status_code)
|
||||
+1767
File diff suppressed because it is too large
Load Diff
+122
@@ -0,0 +1,122 @@
|
||||
##!/usr/bin/env python3
|
||||
import io
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import argparse
|
||||
from enum import IntEnum
|
||||
import serial
|
||||
from serial.threaded import LineReader, ReaderThread
|
||||
|
||||
parser = argparse.ArgumentParser(description='Connect to LoRaWAN network')
|
||||
parser.add_argument('port', help="Serial port of LoStik")
|
||||
parser.add_argument('--joinmode', '-j', help="otaa, abp", default="otaa")
|
||||
|
||||
# ABP credentials
|
||||
parser.add_argument('--appskey', help="App Session Key", default="")
|
||||
parser.add_argument('--nwkskey', help="Network Session Key", default="")
|
||||
parser.add_argument('--devaddr', help="Device Address", default="")
|
||||
|
||||
# OTAA credentials
|
||||
parser.add_argument('--appeui', help="App EUI", default="")
|
||||
parser.add_argument('--appkey', help="App Key", default="")
|
||||
parser.add_argument('--deveui', help="Device EUI", default="")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
OTAA_RETRIES = 5
|
||||
|
||||
class MaxRetriesError(Exception):
|
||||
pass
|
||||
|
||||
class ConnectionState(IntEnum):
|
||||
SUCCESS = 0
|
||||
CONNECTING = 100
|
||||
CONNECTED = 200
|
||||
FAILED = 500
|
||||
TO_MANY_RETRIES = 520
|
||||
|
||||
|
||||
class PrintLines(LineReader):
|
||||
|
||||
retries = 0
|
||||
state = ConnectionState.CONNECTING
|
||||
|
||||
def retry(self, action):
|
||||
if(self.retries >= OTAA_RETRIES):
|
||||
print("Too many retries, exiting")
|
||||
self.state = ConnectionState.TO_MANY_RETRIES
|
||||
return
|
||||
self.retries = self.retries + 1
|
||||
action()
|
||||
|
||||
def get_var(self, cmd):
|
||||
self.send_cmd(cmd)
|
||||
return self.transport.serial.readline()
|
||||
|
||||
def join(self):
|
||||
if args.joinmode == "abp":
|
||||
self.join_abp()
|
||||
else:
|
||||
self.join_otaa()
|
||||
|
||||
def join_otaa(self):
|
||||
if len(args.appeui):
|
||||
self.send_cmd('mac set appeui %s' % args.appeui)
|
||||
if len(args.appkey):
|
||||
self.send_cmd('mac set appkey %s' % args.appkey)
|
||||
if len(args.deveui):
|
||||
self.send_cmd('mac set deveui %s' % args.deveui)
|
||||
self.send_cmd('mac join otaa')
|
||||
|
||||
def join_abp(self):
|
||||
if len(args.devaddr):
|
||||
self.send_cmd('mac set devaddr %s' % args.devaddr)
|
||||
if len(args.appskey):
|
||||
self.send_cmd('mac set appskey %s' % args.appskey)
|
||||
if len(args.nwkskey):
|
||||
self.send_cmd('mac set nwkskey %s' % args.nwkskey)
|
||||
self.send_cmd('mac join abp')
|
||||
|
||||
def connection_made(self, transport):
|
||||
"""
|
||||
Fires when connection is made to serial port device
|
||||
"""
|
||||
print("Connection to LoStik established")
|
||||
self.transport = transport
|
||||
self.retry(self.join)
|
||||
|
||||
def handle_line(self, data):
|
||||
# if data == "ok" or data == 'busy':
|
||||
# return
|
||||
print("STATUS: %s" % data)
|
||||
if data.strip() == "denied" or data.strip() == "no_free_ch":
|
||||
print("Retrying OTAA connection")
|
||||
self.retry(self.join)
|
||||
elif data.strip() == "accepted":
|
||||
print("UPDATING STATE to connected")
|
||||
self.state = ConnectionState.CONNECTED
|
||||
|
||||
def connection_lost(self, exc):
|
||||
"""
|
||||
Called when serial connection is severed to device
|
||||
"""
|
||||
if exc:
|
||||
print(exc)
|
||||
print("Lost connection to serial device")
|
||||
|
||||
def send_cmd(self, cmd, delay=.5):
|
||||
print(cmd)
|
||||
self.transport.write(('%s\r\n' % cmd).encode('UTF-8'))
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
ser = serial.Serial(args.port, baudrate=57600)
|
||||
with ReaderThread(ser, PrintLines) as protocol:
|
||||
while protocol.state < ConnectionState.FAILED:
|
||||
if protocol.state != ConnectionState.CONNECTED:
|
||||
time.sleep(1)
|
||||
continue
|
||||
protocol.send_cmd("mac tx uncnf 1 %d" % int(time.time()))
|
||||
time.sleep(10)
|
||||
exit(protocol.state)
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
beautifulsoup4==4.12.3
|
||||
Flask==2.3.2
|
||||
Flask_SocketIO==5.3.6
|
||||
MarkupSafe==2.1.3
|
||||
pandas==2.0.3
|
||||
pyserial==3.5
|
||||
Requests==2.31.0
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
python3 lorawan.py --joinmode otaa --appkey "814BFA6E589EE49376628B0CDD642E1F" --deveui "70B3D57ED80022D3" --appeui 0000000000000000 /dev/tty.usbserial-1140
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
from archlinux:latest
|
||||
|
||||
workdir /lib
|
||||
|
||||
# Update to latest arch
|
||||
run pacman -Syu --noconfirm
|
||||
|
||||
# Install required dependencies
|
||||
run pacman -S git python2-scipy swig cppunit fftw boost boost-libs gnuradio gnuradio-osmosdr libvolk log4cpp base-devel cmake wxgtk-common wxgtk2 wxgtk3 wxpython libuhd-firmware gnuradio-companion python2-requests --noconfirm
|
||||
|
||||
workdir /liquid
|
||||
|
||||
# Manual liquid-dsp install
|
||||
run git clone https://github.com/jgaeddert/liquid-dsp.git . && \
|
||||
sh ./bootstrap.sh && \
|
||||
sh ./configure --prefix=/usr && \
|
||||
make && \
|
||||
make install
|
||||
|
||||
# Install gr-lora
|
||||
workdir /src
|
||||
|
||||
arg CACHEBUST
|
||||
run git clone https://github.com/rpp0/gr-lora.git . && \
|
||||
mkdir build && \
|
||||
cd build && \
|
||||
cmake ../ -DCMAKE_INSTALL_PREFIX=/usr && \
|
||||
make && \
|
||||
make install && \
|
||||
ldconfig
|
||||
|
||||
workdir /src/apps
|
||||
|
||||
expose 40868
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
LATEST_VERSION=`git ls-remote https://github.com/rpp0/gr-lora.git | grep HEAD | cut -f 1`
|
||||
docker build -t rpp0/gr-lora --build-arg CACHEBUST=$LATEST_VERSION .
|
||||
#docker tag rpp0/gr-lora:latest rpp0/gr-lora:$LATEST_VERSION
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
DOCKER_XAUTH=/tmp/.docker.xauth
|
||||
touch /tmp/.docker.xauth
|
||||
xauth nlist $DISPLAY | sed -e 's/^..../ffff/' | xauth -f $DOCKER_XAUTH nmerge -
|
||||
docker run -i -t --rm --privileged -e DISPLAY=$DISPLAY -e XAUTHORITY=$DOCKER_XAUTH -v /tmp/.docker.xauth:/tmp/.docker.xauth:ro -v /tmp/.X11-unix:/tmp/.X11-unix:ro -v /dev/bus/usb:/dev/bus/usb --entrypoint /bin/bash rpp0/gr-lora:latest
|
||||
@@ -0,0 +1,91 @@
|
||||
#
|
||||
# /etc/pacman.conf
|
||||
#
|
||||
# See the pacman.conf(5) manpage for option and repository directives
|
||||
|
||||
#
|
||||
# GENERAL OPTIONS
|
||||
#
|
||||
[options]
|
||||
# The following paths are commented out with their default values listed.
|
||||
# If you wish to use different paths, uncomment and update the paths.
|
||||
#RootDir = /
|
||||
#DBPath = /var/lib/pacman/
|
||||
#CacheDir = /var/cache/pacman/pkg/
|
||||
#LogFile = /var/log/pacman.log
|
||||
#GPGDir = /etc/pacman.d/gnupg/
|
||||
HoldPkg = pacman glibc
|
||||
#XferCommand = /usr/bin/curl -C - -f %u > %o
|
||||
#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u
|
||||
#CleanMethod = KeepInstalled
|
||||
#UseDelta = 0.7
|
||||
Architecture = auto
|
||||
|
||||
# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup
|
||||
#IgnorePkg =
|
||||
#IgnoreGroup =
|
||||
|
||||
#NoUpgrade =
|
||||
#NoExtract =
|
||||
|
||||
# Misc options
|
||||
#UseSyslog
|
||||
#Color
|
||||
#TotalDownload
|
||||
# We cannot check disk space from within a chroot environment
|
||||
#CheckSpace
|
||||
#VerbosePkgLists
|
||||
|
||||
# By default, pacman accepts packages signed by keys that its local keyring
|
||||
# trusts (see pacman-key and its man page), as well as unsigned packages.
|
||||
SigLevel = Required DatabaseOptional
|
||||
LocalFileSigLevel = Optional
|
||||
#RemoteFileSigLevel = Required
|
||||
|
||||
# NOTE: You must run `pacman-key --init` before first using pacman; the local
|
||||
# keyring can then be populated with the keys of all official Arch Linux
|
||||
# packagers with `pacman-key --populate archlinux`.
|
||||
|
||||
#
|
||||
# REPOSITORIES
|
||||
# - can be defined here or included from another file
|
||||
# - pacman will search repositories in the order defined here
|
||||
# - local/custom mirrors can be added here or in separate files
|
||||
# - repositories listed first will take precedence when packages
|
||||
# have identical names, regardless of version number
|
||||
# - URLs will have $repo replaced by the name of the current repo
|
||||
# - URLs will have $arch replaced by the name of the architecture
|
||||
#
|
||||
# Repository entries are of the format:
|
||||
# [repo-name]
|
||||
# Server = ServerName
|
||||
# Include = IncludePath
|
||||
#
|
||||
# The header [repo-name] is crucial - it must be present and
|
||||
# uncommented to enable the repo.
|
||||
#
|
||||
|
||||
# The testing repositories are disabled by default. To enable, uncomment the
|
||||
# repo name header and Include lines. You can add preferred servers immediately
|
||||
# after the header, and they will be used before the default mirrors.
|
||||
|
||||
#[testing]
|
||||
#Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[core]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[extra]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
#[community-testing]
|
||||
#Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[community]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
# An example of a custom package repository. See the pacman manpage for
|
||||
# tips on creating your own repositories.
|
||||
#[custom]
|
||||
#SigLevel = Optional TrustAll
|
||||
#Server = file:///home/custompkgs
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate a minimal filesystem for archlinux and load it into the local
|
||||
# docker as "archlinux"
|
||||
# requires root
|
||||
set -e
|
||||
|
||||
hash pacstrap &>/dev/null || {
|
||||
echo "Could not find pacstrap. Run pacman -S arch-install-scripts"
|
||||
exit 1
|
||||
}
|
||||
|
||||
hash expect &>/dev/null || {
|
||||
echo "Could not find expect. Run pacman -S expect"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
export LANG="C.UTF-8"
|
||||
|
||||
ROOTFS=$(mktemp -d ${TMPDIR:-/var/tmp}/rootfs-archlinux-XXXXXXXXXX)
|
||||
chmod 755 $ROOTFS
|
||||
|
||||
# packages to ignore for space savings
|
||||
PKGIGNORE=(
|
||||
cryptsetup
|
||||
device-mapper
|
||||
dhcpcd
|
||||
iproute2
|
||||
jfsutils
|
||||
linux
|
||||
lvm2
|
||||
man-db
|
||||
man-pages
|
||||
mdadm
|
||||
nano
|
||||
netctl
|
||||
openresolv
|
||||
pciutils
|
||||
pcmciautils
|
||||
reiserfsprogs
|
||||
s-nail
|
||||
systemd-sysvcompat
|
||||
usbutils
|
||||
vi
|
||||
xfsprogs
|
||||
)
|
||||
IFS=','
|
||||
PKGIGNORE="${PKGIGNORE[*]}"
|
||||
unset IFS
|
||||
|
||||
arch="$(uname -m)"
|
||||
# Set to arch="armv7hf" in order to cross-compile
|
||||
case "$arch" in
|
||||
armv*)
|
||||
if pacman -Q archlinuxarm-keyring >/dev/null 2>&1; then
|
||||
pacman-key --init
|
||||
pacman-key --populate archlinuxarm
|
||||
else
|
||||
echo "Could not find archlinuxarm-keyring. Please, install it and run pacman-key --populate archlinuxarm"
|
||||
exit 1
|
||||
fi
|
||||
PACMAN_CONF=$(mktemp ${TMPDIR:-/var/tmp}/pacman-conf-archlinux-XXXXXXXXX)
|
||||
version="$(echo $arch | cut -c 5)"
|
||||
sed "s/Architecture = armv/Architecture = armv${version}h/g" './mkimage-archarm-pacman.conf' > "${PACMAN_CONF}"
|
||||
PACMAN_MIRRORLIST='Server = http://mirror.archlinuxarm.org/$arch/$repo'
|
||||
PACMAN_EXTRA_PKGS='archlinuxarm-keyring'
|
||||
EXPECT_TIMEOUT=1800 # Most armv* based devices can be very slow (e.g. RPiv1)
|
||||
ARCH_KEYRING=archlinuxarm
|
||||
DOCKER_IMAGE_NAME="armv${version}h/archlinux"
|
||||
;;
|
||||
*)
|
||||
PACMAN_CONF='./mkimage-arch-pacman.conf'
|
||||
PACMAN_MIRRORLIST='Server = https://mirrors.kernel.org/archlinux/$repo/os/$arch'
|
||||
PACMAN_EXTRA_PKGS=''
|
||||
EXPECT_TIMEOUT=60
|
||||
ARCH_KEYRING=archlinux
|
||||
DOCKER_IMAGE_NAME=archlinux
|
||||
;;
|
||||
esac
|
||||
|
||||
export PACMAN_MIRRORLIST
|
||||
|
||||
expect <<EOF
|
||||
set send_slow {1 .1}
|
||||
proc send {ignore arg} {
|
||||
sleep .1
|
||||
exp_send -s -- \$arg
|
||||
}
|
||||
set timeout $EXPECT_TIMEOUT
|
||||
|
||||
spawn pacstrap -C $PACMAN_CONF -c -d -G -i $ROOTFS base haveged $PACMAN_EXTRA_PKGS --ignore $PKGIGNORE
|
||||
expect {
|
||||
-exact "anyway? \[Y/n\] " { send -- "n\r"; exp_continue }
|
||||
-exact "(default=all): " { send -- "\r"; exp_continue }
|
||||
-exact "installation? \[Y/n\]" { send -- "y\r"; exp_continue }
|
||||
-exact "delete it? \[Y/n\]" { send -- "y\r"; exp_continue }
|
||||
}
|
||||
EOF
|
||||
|
||||
arch-chroot $ROOTFS /bin/sh -c 'rm -r /usr/share/man/*'
|
||||
arch-chroot $ROOTFS /bin/sh -c "haveged -w 1024; pacman-key --init; pkill haveged; pacman -Rs --noconfirm haveged; pacman-key --populate $ARCH_KEYRING; pkill gpg-agent"
|
||||
arch-chroot $ROOTFS /bin/sh -c "ln -s /usr/share/zoneinfo/UTC /etc/localtime"
|
||||
echo 'en_US.UTF-8 UTF-8' > $ROOTFS/etc/locale.gen
|
||||
arch-chroot $ROOTFS locale-gen
|
||||
arch-chroot $ROOTFS /bin/sh -c 'echo $PACMAN_MIRRORLIST > /etc/pacman.d/mirrorlist'
|
||||
|
||||
# udev doesn't work in containers, rebuild /dev
|
||||
DEV=$ROOTFS/dev
|
||||
rm -rf $DEV
|
||||
mkdir -p $DEV
|
||||
mknod -m 666 $DEV/null c 1 3
|
||||
mknod -m 666 $DEV/zero c 1 5
|
||||
mknod -m 666 $DEV/random c 1 8
|
||||
mknod -m 666 $DEV/urandom c 1 9
|
||||
mkdir -m 755 $DEV/pts
|
||||
mkdir -m 1777 $DEV/shm
|
||||
mknod -m 666 $DEV/tty c 5 0
|
||||
mknod -m 600 $DEV/console c 5 1
|
||||
mknod -m 666 $DEV/tty0 c 4 0
|
||||
mknod -m 666 $DEV/full c 1 7
|
||||
mknod -m 600 $DEV/initctl p
|
||||
mknod -m 666 $DEV/ptmx c 5 2
|
||||
ln -sf /proc/self/fd $DEV/fd
|
||||
|
||||
tar --numeric-owner --xattrs --acls -C $ROOTFS -c . | docker import - $DOCKER_IMAGE_NAME
|
||||
docker run --rm -t $DOCKER_IMAGE_NAME echo Success.
|
||||
rm -rf $ROOTFS
|
||||
@@ -0,0 +1,75 @@
|
||||
# Introduction
|
||||
|
||||
In certain scenarios, traditional LoRa receivers, such as the Adafruit Feather LoRa or Lostik, may not adequately decode LoRa traffic. To address this, we introduce a Software Defined Radio (SDR) companion module designed to offer a more robust alternative for decoding when the conventional LoRa Scanner application falls short.
|
||||
|
||||
SDRs provide unparalleled flexibility compared to traditional hardware radios, which are often limited by their specific design and capabilities. With an SDR, you can decode a wide array of signals beyond LoRa, making it an invaluable tool for radio signal research and experimentation. The ability to tweak settings and experiment with different decoding algorithms allows for a deeper exploration into radio communications. This is especially beneficial in the evolving field of LoRa technology, where ongoing research and experimentation can yield significant insights. Projects like the HackRF demonstrate the power of a supportive community, offering extensive resources to enhance your SDR experience.
|
||||
|
||||
## gr-lora
|
||||
|
||||
The **gr-lora** project comprises GNU Radio blocks designed to decode LoRa-modulated radio messages using an SDR. LoRa (Long Range) is crucial for Internet of Things (IoT) applications due to its long-range, low-power, and low-bitrate communication capabilities.
|
||||
|
||||
While gr-lora supports the majority of LoRa's physical-layer modulation features, it does not facilitate CRC checks of payload and header or decode multiple channels simultaneously. Tested primarily with a USRP B201 and a Microchip RN2483 transmitter, it is also compatible with popular SDRs like the HackRF.
|
||||
|
||||
A standout feature of gr-lora is its clock recovery algorithm, introduced in version 0.6, enhancing the handling of long LoRa messages. Improvements in whitening, detection, and decoding further solidify its utility in IoT applications. This ongoing project exemplifies the collaborative spirit of the open-source community, continually evolving to meet users' needs.
|
||||
|
||||
## Running the sdr-companion
|
||||
|
||||
Ensure [Docker](https://www.docker.com/get-started/) is installed on your system to proceed with the sdr-companion setup.
|
||||
|
||||
1. **Download the Docker Image**: Begin by downloading the gr-lora image using the following command:
|
||||
|
||||
```bash
|
||||
docker pull rpp0/gr-lora:latest
|
||||
```
|
||||
|
||||
2. **Prepare Your SDR**: Connect your SDR (e.g., HackRF) to your machine. Ensure device permissions are correctly set to avoid detection issues.
|
||||
|
||||
3. **Run the Docker Container**: Execute the script to start the Docker container with gr-lora:
|
||||
|
||||
```bash
|
||||
./docker_run_grlora.sh
|
||||
```
|
||||
|
||||
Upon success, you'll enter the Docker container environment:
|
||||
|
||||
```bash
|
||||
[root@9089ddfe32b5 apps]#
|
||||
```
|
||||
|
||||
4. **Launch GNU Radio Companion**: Inside the container, open the lora_receive_realtime.grc flowgraph with GNU Radio Companion:
|
||||
|
||||
```bash
|
||||
gnuradio-companion lora_receive_realtime.grc
|
||||
```
|
||||
|
||||
5. **Configure and Execute**: Adjust the frequency, spreading factor, and other settings as needed at the gnuradio-companion interface. Start receiving and decoding by selecting *Run -> Execute*.
|
||||
|
||||
Decoded traffic will be displayed in hex format in the gnuradio-companion console.
|
||||
|
||||
## Prebuilt Flowgraphs
|
||||
|
||||
For convenience, prebuilt flowgraphs for specific frequencies are available:
|
||||
|
||||
- **915 MHz SF7 125k BW CR 4/5**: `915-sf7-125bw-cr4_5.grc`
|
||||
- **868 MHz SF7 125k BW CR 4/5**: `868-sf7-125bw-cr4_5.grc`
|
||||
|
||||
To download these into the Docker container:
|
||||
|
||||
1. **Identify Host IP**: Use `ip addr` (or `ifconfig` if available) to find your host machine's IP address.
|
||||
|
||||
2. **Host Web Server**: On your host machine, in the directory with the flowgraphs, start a web server:
|
||||
|
||||
```bash
|
||||
python3 -m http.server 80
|
||||
```
|
||||
|
||||
3. **Download Flowgraphs**: Inside the container, use curl to download the desired flowgraph:
|
||||
|
||||
```bash
|
||||
curl -O http://<your-host-ip>/915-sf7-125bw-cr4_5.grc
|
||||
curl -O http://<your-host-ip>/868-sf7-125bw-cr4_5.grc
|
||||
```
|
||||
|
||||
## Troubleshooting and Support
|
||||
|
||||
For troubleshooting common issues or seeking additional guidance, refer to the gr-lora project documentation and community forums. These resources can provide valuable support as you navigate installation and operation challenges.
|
||||
+55
-32
@@ -67,12 +67,31 @@
|
||||
<div style="text-align:center;">
|
||||
<button class="styled-button" onclick="promptUser433()">Connect Serial Port</button>
|
||||
<button class="disconnect-button" onclick="deleteSerial433()">Disconnect Serial Port</button>
|
||||
<button class="transmit-button" onclick="transmit433()">Transmit Data</button><br>
|
||||
<button class="transmit-button" onclick="transmit433()">Transmit Data</button><br><br>
|
||||
<button class="transmit-button" onclick="clearDataPort1()">Clear Data</button>
|
||||
<input type="checkbox" id="autoscroll-port1" checked>
|
||||
<label for="autoscroll-port1">Autoscroll</label>
|
||||
<p id="status-label433" style="text-align: center">Status: Checking...</p>
|
||||
<div class="indicator-container">
|
||||
<div id="indicator433" class="indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function clearDataPort1() {
|
||||
var serialDataDiv1 = document.getElementById('serial-data-port1');
|
||||
serialDataDiv1.innerHTML = '';
|
||||
}
|
||||
|
||||
function clearDataPort2() {
|
||||
var serialDataDiv2 = document.getElementById('serial-data-port2');
|
||||
serialDataDiv2.innerHTML = '';
|
||||
}
|
||||
|
||||
function clearDataPort3() {
|
||||
var serialDataDiv3 = document.getElementById('serial-data-port3');
|
||||
serialDataDiv3.innerHTML = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- JavaScript for updating the indicator -->
|
||||
<script>
|
||||
@@ -124,9 +143,12 @@
|
||||
socket.on('serial_data_port1', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port1');
|
||||
serialDataDiv.innerHTML += data.data + '<br>';
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
var autoscrollCheckbox = document.getElementById('autoscroll-port1');
|
||||
if (autoscrollCheckbox.checked) {
|
||||
serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -247,7 +269,10 @@
|
||||
<div style="text-align:center;">
|
||||
<button class="styled-button" onclick="promptUser868()">Connect Serial Port</button>
|
||||
<button class="disconnect-button" onclick="deleteSerial868()">Disconnect Serial Port</button>
|
||||
<button class="transmit-button" onclick="transmit868()">Transmit Data</button><br>
|
||||
<button class="transmit-button" onclick="transmit868()">Transmit Data</button><br><br>
|
||||
<button class="transmit-button" onclick="clearDataPort2()">Clear Data</button>
|
||||
<input type="checkbox" id="autoscroll-port2" checked>
|
||||
<label for="autoscroll-port2">Autoscroll</label>
|
||||
<p id="status-label868" style="text-align: center">Status: Checking...</p>
|
||||
<div class="indicator-container">
|
||||
<div id="indicator868" class="indicator"></div>
|
||||
@@ -288,39 +313,29 @@
|
||||
// Periodically check and update the indicator (e.g., every 5 seconds)
|
||||
setInterval(updateIndicator868, 5000);
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
||||
|
||||
socket.on('serial_data_port1', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port1');
|
||||
if (data.data instanceof Array) {
|
||||
data.data.forEach(function(line) {
|
||||
serialDataDiv.innerHTML += line + '<br>';
|
||||
});
|
||||
}
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// For port2
|
||||
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
||||
|
||||
socket.on('initial_serial_data_port2', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port2');
|
||||
var serialDataDiv2 = document.getElementById('serial-data-port2');
|
||||
data.data.forEach(function(line) {
|
||||
serialDataDiv.innerHTML += line + '<br>';
|
||||
serialDataDiv2.innerHTML += line + '<br>';
|
||||
});
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
});
|
||||
|
||||
socket.on('serial_data_port2', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port2');
|
||||
serialDataDiv.innerHTML += data.data + '<br>';
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
var serialDataDiv2 = document.getElementById('serial-data-port2');
|
||||
serialDataDiv2.innerHTML += data.data + '<br>';
|
||||
var autoscrollCheckbox2 = document.getElementById('autoscroll-port2');
|
||||
if (autoscrollCheckbox2.checked) {
|
||||
serialDataDiv2.scrollTop = serialDataDiv2.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -442,7 +457,10 @@
|
||||
<div style="text-align:center;">
|
||||
<button class="styled-button" onclick="promptUser915()">Connect Serial Port</button>
|
||||
<button class="disconnect-button" onclick="deleteSerial915()">Disconnect Serial Port</button>
|
||||
<button class="transmit-button" onclick="transmit915()">Transmit Data</button><br>
|
||||
<button class="transmit-button" onclick="transmit915()">Transmit Data</button><br><br>
|
||||
<button class="transmit-button" onclick="clearDataPort3()">Clear Data</button>
|
||||
<input type="checkbox" id="autoscroll-port3" checked>
|
||||
<label for="autoscroll-port3">Autoscroll</label>
|
||||
<p id="status-label915" style="text-align: center">Status: Checking...</p>
|
||||
<div class="indicator-container">
|
||||
<div id="indicator915" class="indicator"></div>
|
||||
@@ -485,21 +503,26 @@
|
||||
</script>
|
||||
<script>
|
||||
// For port3
|
||||
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
||||
|
||||
socket.on('initial_serial_data_port3', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port3');
|
||||
var serialDataDiv3 = document.getElementById('serial-data-port3');
|
||||
data.data.forEach(function(line) {
|
||||
serialDataDiv.innerHTML += line + '<br>';
|
||||
serialDataDiv3.innerHTML += line + '<br>';
|
||||
});
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
});
|
||||
|
||||
socket.on('serial_data_port3', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port3');
|
||||
serialDataDiv.innerHTML += data.data + '<br>';
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
var serialDataDiv3 = document.getElementById('serial-data-port3');
|
||||
serialDataDiv3.innerHTML += data.data + '<br>';
|
||||
var autoscrollCheckbox3 = document.getElementById('autoscroll-port3');
|
||||
if (autoscrollCheckbox3.checked) {
|
||||
serialDataDiv3.scrollTop = serialDataDiv3.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
+38
-15
@@ -62,11 +62,26 @@
|
||||
<tbody>
|
||||
<!-- Rows will be inserted here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</table><br><br>
|
||||
<div style="text-align:center;">
|
||||
<button class="transmit-button" onclick="manualUpdateTable()">Refresh Table</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
let expandedRows = {};
|
||||
|
||||
function manualUpdateTable() {
|
||||
// Display a loading alert
|
||||
Swal.fire({
|
||||
title: 'Table Updated!',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
updateTableData();
|
||||
}
|
||||
|
||||
function updateTableData() {
|
||||
fetch('/get_table_data')
|
||||
.then(response => response.json())
|
||||
@@ -80,9 +95,17 @@
|
||||
let cell3 = mainRow.insertCell();
|
||||
let cell4 = mainRow.insertCell();
|
||||
cell1.innerHTML = key;
|
||||
cell2.innerHTML = data[key][0][0]; // Frequency (from the first entry)
|
||||
cell3.innerHTML = data[key][0][1]; // Signal Strength (from the first entry)
|
||||
let lastIndex = data[key].length - 1; // Get the index of the last entry
|
||||
cell2.innerHTML = data[key][lastIndex][0]; // Frequency (from the last entry)
|
||||
let rssiValue = data[key][lastIndex][1];
|
||||
cell3.innerHTML = rssiValue === 0 ? 'unknown' : rssiValue; // Signal Strength (from the last entry)
|
||||
|
||||
//cell2.innerHTML = data[key][0][0]; // Frequency (from the first entry)
|
||||
|
||||
// Check for RSSI value and display 'unknown' if it is 0
|
||||
//let rssiValue = data[key][0][1];
|
||||
//cell3.innerHTML = rssiValue === 0 ? 'unknown' : rssiValue; // Signal Strength (from the first entry)
|
||||
|
||||
// Create a Bootstrap styled button
|
||||
let expandBtn = document.createElement('button');
|
||||
expandBtn.classList.add('btn', 'btn-secondary', 'btn-sm'); // Bootstrap button classes
|
||||
@@ -90,21 +113,21 @@
|
||||
expandBtn.onclick = function() {
|
||||
let deviceKey = key; // Capture the current device key
|
||||
let isExpanded = !expandedRows[deviceKey]; // Toggle the expanded state
|
||||
|
||||
|
||||
// Show or hide the expandable rows
|
||||
let nextRow = mainRow.nextSibling;
|
||||
while (nextRow && nextRow.classList.contains('expandable-row')) {
|
||||
nextRow.style.display = isExpanded ? 'table-row' : 'none';
|
||||
nextRow = nextRow.nextSibling;
|
||||
}
|
||||
|
||||
|
||||
// Update the button text and expandedRows object
|
||||
expandBtn.innerHTML = isExpanded ? 'Hide Values' : 'Show Values';
|
||||
expandedRows[deviceKey] = isExpanded;
|
||||
};
|
||||
cell4.appendChild(expandBtn);
|
||||
cell4.style.textAlign = 'center'; // Center align the button
|
||||
|
||||
|
||||
// Add expandable rows for each decoded value
|
||||
data[key].forEach(entry => {
|
||||
let expandableRow = tableBody.insertRow();
|
||||
@@ -114,7 +137,7 @@
|
||||
cell.colSpan = "4";
|
||||
cell.innerHTML = `Decoded Value: ${entry[2]}`;
|
||||
});
|
||||
|
||||
|
||||
if (expandedRows[key]) {
|
||||
let nextRow = mainRow.nextSibling;
|
||||
while (nextRow && nextRow.classList.contains('expandable-row')) {
|
||||
@@ -126,15 +149,15 @@
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
}
|
||||
|
||||
// Initial update of the table
|
||||
updateTableData();
|
||||
|
||||
// Periodically update the table every 30 seconds (30000 milliseconds)
|
||||
setInterval(updateTableData, 30000);
|
||||
|
||||
|
||||
// Initial update of the table
|
||||
updateTableData();
|
||||
|
||||
// Periodically update the table every 30 seconds (30000 milliseconds)
|
||||
setInterval(updateTableData, 30000);
|
||||
</script>
|
||||
|
||||
|
||||
</section>
|
||||
<hr class="m-0" />
|
||||
|
||||
|
||||
+170
-114
@@ -75,50 +75,71 @@
|
||||
|
||||
|
||||
<br>
|
||||
<script>
|
||||
function clearDataPort1() {
|
||||
var serialDataDiv1 = document.getElementById('serial-data-port1');
|
||||
serialDataDiv1.innerHTML = '';
|
||||
}
|
||||
|
||||
function clearDataPort2() {
|
||||
var serialDataDiv2 = document.getElementById('serial-data-port2');
|
||||
serialDataDiv2.innerHTML = '';
|
||||
}
|
||||
|
||||
function clearDataPort3() {
|
||||
var serialDataDiv3 = document.getElementById('serial-data-port3');
|
||||
serialDataDiv3.innerHTML = '';
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
let selectedDeviceName = null; // Global variable to store the selected device name
|
||||
let dataCache = {}; // Cache for storing fetched data
|
||||
|
||||
function updateTableData() {
|
||||
fetch('/get_table_data')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const tableBody = document.getElementById('data-table').getElementsByTagName('tbody')[0];
|
||||
tableBody.innerHTML = ''; // Clear existing table rows
|
||||
|
||||
|
||||
let isRowSelected = selectedDeviceName !== null;
|
||||
function updateTableData() {
|
||||
fetch('/get_table_data')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const tableBody = document.getElementById('data-table').getElementsByTagName('tbody')[0];
|
||||
tableBody.innerHTML = ''; // Clear existing table rows
|
||||
|
||||
for (let key in data) {
|
||||
let row = tableBody.insertRow();
|
||||
row.addEventListener('click', function() {
|
||||
// Handle row click event for selection
|
||||
handleRowSelection(row, key);
|
||||
});
|
||||
let isRowSelected = selectedDeviceName !== null;
|
||||
|
||||
let cell1 = row.insertCell();
|
||||
let cell2 = row.insertCell();
|
||||
let cell3 = row.insertCell();
|
||||
cell1.innerHTML = key; // Device Name
|
||||
|
||||
// Assuming that the latest data is the most relevant
|
||||
let latestData = data[key][data[key].length - 1];
|
||||
cell2.innerHTML = latestData[0]; // Frequency
|
||||
cell3.innerHTML = latestData[1]; // Signal Strength
|
||||
for (let key in data) {
|
||||
let row = tableBody.insertRow();
|
||||
row.addEventListener('click', function() {
|
||||
// Handle row click event for selection
|
||||
handleRowSelection(row, key);
|
||||
});
|
||||
|
||||
// Apply hiding logic based on selection
|
||||
if (isRowSelected) {
|
||||
if (key !== selectedDeviceName) {
|
||||
row.classList.add('hidden-row');
|
||||
} else {
|
||||
row.classList.add('selected-row');
|
||||
}
|
||||
let cell1 = row.insertCell();
|
||||
let cell2 = row.insertCell();
|
||||
let cell3 = row.insertCell();
|
||||
cell1.innerHTML = key; // Device Name
|
||||
|
||||
// Assuming that the latest data is the most relevant
|
||||
let latestData = data[key][data[key].length - 1];
|
||||
cell2.innerHTML = latestData[0]; // Frequency
|
||||
|
||||
// Check for RSSI value and display 'unknown' if it is 0
|
||||
let rssiValue = latestData[1];
|
||||
cell3.innerHTML = rssiValue === 0 ? 'unknown' : rssiValue; // Signal Strength
|
||||
|
||||
// Apply hiding logic based on selection
|
||||
if (isRowSelected) {
|
||||
if (key !== selectedDeviceName) {
|
||||
row.classList.add('hidden-row');
|
||||
} else {
|
||||
row.classList.add('selected-row');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSelectedDataDisplay(); // Update the selected data display after refreshing the table
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
}
|
||||
updateSelectedDataDisplay(); // Update the selected data display after refreshing the table
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
}
|
||||
|
||||
function handleRowSelection(row, key) {
|
||||
// Reset all rows
|
||||
@@ -145,7 +166,7 @@
|
||||
updateTableData();
|
||||
|
||||
// Periodically update the table every second
|
||||
setInterval(updateTableData, 1000);
|
||||
setInterval(updateTableData, 10000);
|
||||
|
||||
|
||||
|
||||
@@ -192,7 +213,10 @@
|
||||
<button class="styled-button" onclick="promptUser433()">Connect Serial Port</button>
|
||||
<button class="disconnect-button" onclick="deleteSerial433()">Disconnect Serial Port</button>
|
||||
<button class="transmit-button" onclick="transmit433()">Start Beacon</button>
|
||||
<button class="disconnect-button" onclick="confirmStopTransmission433()">Stop Beacon</button><br>
|
||||
<button class="disconnect-button" onclick="confirmStopTransmission433()">Stop Beacon</button><br><br>
|
||||
<button class="transmit-button" onclick="clearDataPort1()">Clear Data</button>
|
||||
<input type="checkbox" id="autoscroll-port1" checked>
|
||||
<label for="autoscroll-port1">Autoscroll</label>
|
||||
<p id="status-label433" style="text-align: center">Status: Checking...</p>
|
||||
<div class="indicator-container">
|
||||
<div id="indicator433" class="indicator"></div>
|
||||
@@ -249,9 +273,12 @@
|
||||
socket.on('serial_data_port1', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port1');
|
||||
serialDataDiv.innerHTML += data.data + '<br>';
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
var autoscrollCheckbox = document.getElementById('autoscroll-port1');
|
||||
if (autoscrollCheckbox.checked) {
|
||||
serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -320,23 +347,32 @@
|
||||
|
||||
function transmit433() {
|
||||
Swal.fire({
|
||||
title: 'Enter data to transmit:',
|
||||
input: 'text',
|
||||
inputAttributes: {
|
||||
autocapitalize: 'off'
|
||||
},
|
||||
title: 'Enter data to transmit and set interval:',
|
||||
html:
|
||||
'<input id="swal-input1" class="swal2-input" placeholder="Enter data">' +
|
||||
'<input id="swal-input2" class="swal2-input" placeholder="Interval in milliseconds">',
|
||||
focusConfirm: false,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Submit',
|
||||
cancelButtonText: 'Cancel',
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: (value) => {
|
||||
return startTransmission433(value); // Call the function to start transmission
|
||||
},
|
||||
allowOutsideClick: () => !Swal.isLoading()
|
||||
preConfirm: () => {
|
||||
const data = document.getElementById('swal-input1').value;
|
||||
const interval = document.getElementById('swal-input2').value;
|
||||
|
||||
if (!data || !interval) {
|
||||
Swal.showValidationMessage('Please enter both data and interval');
|
||||
} else if (isNaN(interval) || interval < 1000) { // Ensuring the interval is a number and at least 1 second (1000 milliseconds)
|
||||
Swal.showValidationMessage('Please enter a valid interval (number >= 1000)');
|
||||
} else {
|
||||
return { data: data, interval: interval };
|
||||
}
|
||||
}
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
if (result.isConfirmed && result.value) {
|
||||
startTransmission433(result.value.data, result.value.interval);
|
||||
Swal.fire({
|
||||
title: 'Transmission Started!',
|
||||
html: `Data: ${result.value.data}<br>Interval: ${result.value.interval} milliseconds`,
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
@@ -344,19 +380,21 @@
|
||||
}
|
||||
|
||||
|
||||
function startTransmission433(data) {
|
||||
|
||||
function startTransmission433(data, interval) {
|
||||
if (transmitIntervalId433) {
|
||||
clearInterval(transmitIntervalId433); // Clear existing interval
|
||||
}
|
||||
transmitIntervalId433 = setInterval(() => {
|
||||
sendTransmissionData433(data);
|
||||
}, 10000); // Transmit data every 10 seconds
|
||||
}, parseInt(interval)); // Use the user-provided interval, ensuring it's an integer
|
||||
|
||||
// Send the first transmission immediately
|
||||
return sendTransmissionData433(data);
|
||||
sendTransmissionData433(data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function sendTransmissionData433(data) {
|
||||
return fetch('/transmit433', {
|
||||
method: 'POST',
|
||||
@@ -423,14 +461,16 @@
|
||||
<button class="styled-button" onclick="promptUser868()">Connect Serial Port</button>
|
||||
<button class="disconnect-button" onclick="deleteSerial868()">Disconnect Serial Port</button>
|
||||
<button class="transmit-button" onclick="transmit868()">Start Beacon</button>
|
||||
<button class="disconnect-button" onclick="confirmStopTransmission868()">Stop Beacon</button><br>
|
||||
<button class="disconnect-button" onclick="confirmStopTransmission868()">Stop Beacon</button><br><br>
|
||||
<button class="transmit-button" onclick="clearDataPort2()">Clear Data</button>
|
||||
<input type="checkbox" id="autoscroll-port2" checked>
|
||||
<label for="autoscroll-port2">Autoscroll</label>
|
||||
<p id="status-label868" style="text-align: center">Status: Checking...</p>
|
||||
<div class="indicator-container">
|
||||
<div id="indicator868" class="indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateIndicator868() {
|
||||
// Fetch the status from the Flask route
|
||||
@@ -464,39 +504,26 @@
|
||||
// Periodically check and update the indicator (e.g., every 5 seconds)
|
||||
setInterval(updateIndicator868, 5000);
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
||||
|
||||
socket.on('serial_data_port1', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port1');
|
||||
if (data.data instanceof Array) {
|
||||
data.data.forEach(function(line) {
|
||||
serialDataDiv.innerHTML += line + '<br>';
|
||||
});
|
||||
}
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// For port2
|
||||
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
||||
socket.on('initial_serial_data_port2', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port2');
|
||||
var serialDataDiv2 = document.getElementById('serial-data-port2');
|
||||
data.data.forEach(function(line) {
|
||||
serialDataDiv.innerHTML += line + '<br>';
|
||||
serialDataDiv2.innerHTML += line + '<br>';
|
||||
});
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
});
|
||||
|
||||
socket.on('serial_data_port2', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port2');
|
||||
serialDataDiv.innerHTML += data.data + '<br>';
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
var serialDataDiv2 = document.getElementById('serial-data-port2');
|
||||
serialDataDiv2.innerHTML += data.data + '<br>';
|
||||
var autoscrollCheckbox2 = document.getElementById('autoscroll-port2');
|
||||
if (autoscrollCheckbox2.checked) {
|
||||
serialDataDiv2.scrollTop = serialDataDiv2.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -566,23 +593,32 @@
|
||||
|
||||
function transmit868() {
|
||||
Swal.fire({
|
||||
title: 'Enter data to transmit:',
|
||||
input: 'text',
|
||||
inputAttributes: {
|
||||
autocapitalize: 'off'
|
||||
},
|
||||
title: 'Enter data to transmit and set interval:',
|
||||
html:
|
||||
'<input id="swal-input1-868" class="swal2-input" placeholder="Enter data">' +
|
||||
'<input id="swal-input2-868" class="swal2-input" placeholder="Interval in milliseconds">',
|
||||
focusConfirm: false,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Submit',
|
||||
cancelButtonText: 'Cancel',
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: (value) => {
|
||||
return startTransmission868(value); // Call the function to start transmission
|
||||
},
|
||||
allowOutsideClick: () => !Swal.isLoading()
|
||||
preConfirm: () => {
|
||||
const data = document.getElementById('swal-input1-868').value;
|
||||
const interval = document.getElementById('swal-input2-868').value;
|
||||
|
||||
if (!data || !interval) {
|
||||
Swal.showValidationMessage('Please enter both data and interval');
|
||||
} else if (isNaN(interval) || interval < 1000) {
|
||||
Swal.showValidationMessage('Please enter a valid interval (number >= 1000)');
|
||||
} else {
|
||||
return { data: data, interval: interval };
|
||||
}
|
||||
}
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
if (result.isConfirmed && result.value) {
|
||||
startTransmission868(result.value.data, result.value.interval);
|
||||
Swal.fire({
|
||||
title: 'Transmission Started!',
|
||||
html: `Data: ${result.value.data}<br>Interval: ${result.value.interval} milliseconds`,
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
@@ -590,19 +626,21 @@
|
||||
}
|
||||
|
||||
|
||||
function startTransmission868(data) {
|
||||
|
||||
function startTransmission868(data, interval) {
|
||||
if (transmitIntervalId868) {
|
||||
clearInterval(transmitIntervalId868); // Clear existing interval
|
||||
}
|
||||
transmitIntervalId868 = setInterval(() => {
|
||||
sendTransmissionData868(data);
|
||||
}, 10000); // Transmit data every 10 seconds
|
||||
}, parseInt(interval));
|
||||
|
||||
// Send the first transmission immediately
|
||||
return sendTransmissionData868(data);
|
||||
sendTransmissionData868(data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function sendTransmissionData868(data) {
|
||||
return fetch('/transmit868', {
|
||||
method: 'POST',
|
||||
@@ -658,6 +696,7 @@
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<!--915 MHz-->
|
||||
<div class="resume-section-content">
|
||||
<div class="collapse" id="collapse915">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
|
||||
@@ -669,7 +708,10 @@
|
||||
<button class="styled-button" onclick="promptUser915()">Connect Serial Port</button>
|
||||
<button class="disconnect-button" onclick="deleteSerial915()">Disconnect Serial Port</button>
|
||||
<button class="transmit-button" onclick="transmit915()">Start Beacon</button>
|
||||
<button class="disconnect-button" onclick="confirmStopTransmission915()">Stop Beacon</button><br>
|
||||
<button class="disconnect-button" onclick="confirmStopTransmission915()">Stop Beacon</button><br><br>
|
||||
<button class="transmit-button" onclick="clearDataPort3()">Clear Data</button>
|
||||
<input type="checkbox" id="autoscroll-port3" checked>
|
||||
<label for="autoscroll-port3">Autoscroll</label>
|
||||
<p id="status-label915" style="text-align: center">Status: Checking...</p>
|
||||
<div class="indicator-container">
|
||||
<div id="indicator915" class="indicator"></div>
|
||||
@@ -712,21 +754,24 @@
|
||||
</script>
|
||||
<script>
|
||||
// For port3
|
||||
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
||||
socket.on('initial_serial_data_port3', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port3');
|
||||
var serialDataDiv3 = document.getElementById('serial-data-port3');
|
||||
data.data.forEach(function(line) {
|
||||
serialDataDiv.innerHTML += line + '<br>';
|
||||
serialDataDiv3.innerHTML += line + '<br>';
|
||||
});
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
|
||||
});
|
||||
|
||||
socket.on('serial_data_port3', function(data) {
|
||||
var serialDataDiv = document.getElementById('serial-data-port3');
|
||||
serialDataDiv.innerHTML += data.data + '<br>';
|
||||
// Scroll to the bottom for new data
|
||||
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
|
||||
var serialDataDiv3 = document.getElementById('serial-data-port3');
|
||||
serialDataDiv3.innerHTML += data.data + '<br>';
|
||||
var autoscrollCheckbox3 = document.getElementById('autoscroll-port3');
|
||||
if (autoscrollCheckbox3.checked) {
|
||||
serialDataDiv3.scrollTop = serialDataDiv3.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -795,41 +840,52 @@
|
||||
|
||||
function transmit915() {
|
||||
Swal.fire({
|
||||
title: 'Enter data to transmit:',
|
||||
input: 'text',
|
||||
inputAttributes: {
|
||||
autocapitalize: 'off'
|
||||
},
|
||||
title: 'Enter data to transmit and set interval:',
|
||||
html:
|
||||
'<input id="swal-input1-915" class="swal2-input" placeholder="Enter data">' +
|
||||
'<input id="swal-input2-915" class="swal2-input" placeholder="Interval in milliseconds">',
|
||||
focusConfirm: false,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Submit',
|
||||
cancelButtonText: 'Cancel',
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: (value) => {
|
||||
return startTransmission915(value); // Call the function to start transmission
|
||||
},
|
||||
allowOutsideClick: () => !Swal.isLoading()
|
||||
preConfirm: () => {
|
||||
const data = document.getElementById('swal-input1-915').value;
|
||||
const interval = document.getElementById('swal-input2-915').value;
|
||||
|
||||
if (!data || !interval) {
|
||||
Swal.showValidationMessage('Please enter both data and interval');
|
||||
} else if (isNaN(interval) || interval < 1000) {
|
||||
Swal.showValidationMessage('Please enter a valid interval (number >= 1000)');
|
||||
} else {
|
||||
return { data: data, interval: interval };
|
||||
}
|
||||
}
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
if (result.isConfirmed && result.value) {
|
||||
startTransmission915(result.value.data, result.value.interval);
|
||||
Swal.fire({
|
||||
title: 'Transmission Started!',
|
||||
html: `Data: ${result.value.data}<br>Interval: ${result.value.interval} milliseconds`,
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function startTransmission915(data) {
|
||||
function startTransmission915(data, interval) {
|
||||
if (transmitIntervalId915) {
|
||||
clearInterval(transmitIntervalId915); // Clear existing interval
|
||||
}
|
||||
transmitIntervalId915 = setInterval(() => {
|
||||
sendTransmissionData915(data);
|
||||
}, 10000); // Transmit data every 10 seconds
|
||||
|
||||
}, parseInt(interval));
|
||||
|
||||
// Send the first transmission immediately
|
||||
return sendTransmissionData915(data);
|
||||
sendTransmissionData915(data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function sendTransmissionData915(data) {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import socket
|
||||
|
||||
# Define the IP address and the port number to listen on.
|
||||
# '' means the script will listen on all available IPs.
|
||||
IP = '10.130.1.235'
|
||||
PORT = 1700
|
||||
|
||||
def main():
|
||||
# Create a UDP socket
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||
# Bind the socket to the address and port
|
||||
s.bind((IP, PORT))
|
||||
print(f"Listening for UDP traffic on port {PORT}...")
|
||||
|
||||
# Continuously listen for UDP packets
|
||||
while True:
|
||||
# Receive data from the client
|
||||
data, addr = s.recvfrom(1024) # buffer size is 1024 bytes
|
||||
print(f"Received message from {addr}: {data}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user