mirror of
https://github.com/skinnyrad/Lora-Scanner.git
synced 2026-03-28 17:43:00 +01:00
Feat: Integrating Dragino lps8n
This commit is contained in:
89
app.py
89
app.py
@@ -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,7 +20,6 @@ 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 = {}
|
||||
|
||||
@@ -75,8 +76,78 @@ def read_serial_data(port, ser, buffer):
|
||||
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)
|
||||
|
||||
# Extract DevEui or DevAddr from the response
|
||||
dev_id = extract_dev_id(formatted_row) # Implement this function based on your data format
|
||||
freq = extract_freq(formatted_row) # Implement this function based on your data format
|
||||
|
||||
# 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])
|
||||
#surveydata[dev_id]['decoded_values'].append(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 +215,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 +326,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)
|
||||
|
||||
58
draginoReq.py
Normal file
58
draginoReq.py
Normal file
@@ -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
examplereq
Normal file
1767
examplereq
Normal file
File diff suppressed because it is too large
Load Diff
122
lorawan.py
Normal file
122
lorawan.py
Normal file
@@ -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)
|
||||
1
rn2903-lorawan.txt
Normal file
1
rn2903-lorawan.txt
Normal file
@@ -0,0 +1 @@
|
||||
python3 lorawan.py --joinmode otaa --appkey "814BFA6E589EE49376628B0CDD642E1F" --deveui "70B3D57ED80022D3" --appeui 0000000000000000 /dev/tty.usbserial-1140
|
||||
@@ -81,8 +81,11 @@
|
||||
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)
|
||||
|
||||
|
||||
// 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 +93,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 +117,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 +129,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" />
|
||||
|
||||
|
||||
@@ -79,46 +79,51 @@
|
||||
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
|
||||
|
||||
22
udp-listener.py
Normal file
22
udp-listener.py
Normal file
@@ -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