Added MQTT support
This commit is contained in:
16
aprs_utils/aprs-mqtt-bridge_old/CHANGELOG.md
Normal file
16
aprs_utils/aprs-mqtt-bridge_old/CHANGELOG.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
Added : for new features.
|
||||
Changed : for changes in existing functionality.
|
||||
Deprecated: for soon-to-be removed features.
|
||||
Removed : for now removed features.
|
||||
Fixed : for any bug fixes.
|
||||
Security : in case of vulnerabilities.
|
||||
|
||||
## [1.0.0] - 2023-01-13
|
||||
First working version.
|
||||
|
||||
## [1.0.1] - 2023-01-14
|
||||
Changed: aprs_status (published on the MQTT broker) now returns actual state of transmision (sending, retrying, send or failed) instead if just 'ready' and 'busy'.
|
||||
67
aprs_utils/aprs-mqtt-bridge_old/README.md
Normal file
67
aprs_utils/aprs-mqtt-bridge_old/README.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# APRS to MQTT bridge
|
||||
|
||||
The APRS to MQTT bridge can relay commands from an MQTT broker to the APRS nodes via the Linux AX.25 stack. For now, only commands which response with a defined acknowledge (commands 10 and higher) are supported.
|
||||
|
||||
This program is a utility for the APRS telemetry system used by PE1RXF. The telemetry is embedded in an APRS message which can travel over the existing APRS network. For more information about this open protocol visit this link: https://www.meezenest.nl/mees-elektronica/projects/aprs_telemetry/APRS_protocol_nodes_PE1RXF.pdf
|
||||
|
||||
## Configuration
|
||||
|
||||
The program is configured via a YAML file. The global section defines the MQTT broker and some APRS transmit settings. The topics define the MQTT topics on which a client can publish a request. The full path of the topic is 'topic_root/topic_name'. Call, server and port define the AX.25 settings where 'call' is the call of the APRS node the message is send to, 'server' is the call of the APRS server which sends the message (typically this is the call assigned to the ax25 port) and 'port' is the Linux AX.25 port on which the radio is connected.
|
||||
|
||||
Lets say we have an APRS node which can switch several power rails. (https://www.meezenest.nl/mees-elektronica/RPi-pico-LoRa-APRS.html). We connected a 5GHz HamNet dish to output 2 of this APRS node. The node is assigned the call 'PE1RXF-6'. Assume this node can be reached via the radio connected to AX.25 port ax2 on our server. The node switches the output to high when it receives command '33{33'. We want the bridge to send this command when it receives payload 'ON' via MQTT on topic 'hamnet_aprs_nodes/ubiquity_dish_ptmp_workshop'. With the below example configuration, we can achieve just that. And by sending MQTT payload 'OFF', the APRS node switches the ouptut off again.
|
||||
|
||||
Multiple topics for the same or another APRS node can be defined as shown in the example.
|
||||
|
||||
```
|
||||
# Global settings apply to all other entries
|
||||
global:
|
||||
broker: pe1rxf.ampr.org # The MQTT broker we are going to use
|
||||
port: 1883 # The tcp port of the MQTT broker
|
||||
topic_root: hamnet_aprs_nodes # MQTT topic root
|
||||
transmit_rate: 20 # Number of seconds between each transmision
|
||||
retry: 3 # Try this often before giving up
|
||||
destination: APRX29 # Destination or program ID
|
||||
digi_path: WIDE2-1 # Digi path of APRS messages
|
||||
#beacon_program: /usr/sbin/beacon # The external AX.25 beacon program => obsolete
|
||||
|
||||
|
||||
topics:
|
||||
# MQTT topic: 5GHz dish at workshop (must be unique name)
|
||||
- name: ubiquity_dish_ptmp_workshop
|
||||
call: PE1RXF-6 # Call of node to which commands below are send
|
||||
server: PE1RXF-3 # Call of APRS server sending the commands
|
||||
port: ax2 # Name of AX.25 port to use
|
||||
command:
|
||||
- payload: 'ON' # This is the payload we have to receive
|
||||
cmd: 33{33 # This command is send to the node
|
||||
response: ack33 # This response is expected from the node
|
||||
- payload: 'OFF'
|
||||
cmd: 32{32
|
||||
response: ack32
|
||||
|
||||
# Server at tiny house
|
||||
- name: server_tiny_house
|
||||
call: PE1RXF-7
|
||||
server: PE1RXF-3
|
||||
port: ax2
|
||||
command:
|
||||
- payload: 'ON'
|
||||
cmd: 31{31
|
||||
response: ack31
|
||||
- payload: 'OFF'
|
||||
cmd: 30{30
|
||||
response: ack30
|
||||
```
|
||||
The configuration entry 'global:retry' sets the amount of APRS message retries we attempt before giving up. The configuration entry 'global:transmit_rate' sets the time between retries.
|
||||
|
||||
For now, the state of the outputs of the APRS nodes is not published to the MQTT broker. In the future, this could be implemented in the software. It is possible to poll the nodes via command '06' to get the current state of the outputs.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python3
|
||||
- Python AX.25 Module for Python3 (https://github.com/ha5di/pyax25)
|
||||
- pathlib
|
||||
- yaml
|
||||
- paho-mqtt
|
||||
- Linux AX.25 stack
|
||||
|
||||
392
aprs_utils/aprs-mqtt-bridge_old/aprs-mqtt-bridge.py
Executable file
392
aprs_utils/aprs-mqtt-bridge_old/aprs-mqtt-bridge.py
Executable file
@@ -0,0 +1,392 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
A bridge between APRS messaging and MQTT, designed to control my lora_aprs_node_pico
|
||||
|
||||
(C)2022 M.T. Konstapel https://meezenest.nl/mees
|
||||
|
||||
This file is part of aprs-mqtt-bridge.
|
||||
|
||||
aprs-mqtt-bridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
aprs-mqtt-bridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with aprs-mqtt-bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import sys
|
||||
import random
|
||||
import time
|
||||
import os
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
from yaml.loader import SafeLoader
|
||||
from paho.mqtt import client as mqtt_client
|
||||
import pythonax25
|
||||
|
||||
configuration_file = "aprs-mqtt-bridge.yml"
|
||||
|
||||
# This is where we keep our settings
|
||||
class mqtt_settings:
|
||||
#broker
|
||||
#topic_root
|
||||
#port
|
||||
#client_id
|
||||
#transmit_rate
|
||||
#retry
|
||||
#topics
|
||||
state = 'ready'
|
||||
aprs_state = 'idle'
|
||||
pass
|
||||
mqtt = mqtt_settings()
|
||||
|
||||
axport = []
|
||||
axdevice = []
|
||||
axaddress = []
|
||||
class aprs_status:
|
||||
nr_of_ports = 0
|
||||
busy = 0
|
||||
wait_for_ack = 0
|
||||
call_of_wait_for_ack = 0
|
||||
time_out_timer = 0
|
||||
retry_counter = 0
|
||||
selected_port = 0
|
||||
request_to_send = 0
|
||||
pass
|
||||
aprs = aprs_status()
|
||||
|
||||
def parsePacket(string):
|
||||
# Split the address and payload separated by APRS PID
|
||||
buffer = string.split(b'\x03\xf0')
|
||||
address = buffer[0]
|
||||
|
||||
# Check if the first byte indicates it is a data packet
|
||||
if address[0] == 0:
|
||||
# Cut the first byte and feed it to the address parser
|
||||
listAddress = getAllAddress(address[1:])
|
||||
|
||||
if listAddress != 0:
|
||||
# Get the source, destination, and digipeaters from the address list
|
||||
source = listAddress[1]
|
||||
destination = listAddress[0]
|
||||
digipeaters = listAddress[2:]
|
||||
payload = buffer[1]
|
||||
else:
|
||||
# If there was an error decoding the address we return save values which will be ignored by the rest of the program
|
||||
source = 'NOCALL'
|
||||
destination = 'NOCALL'
|
||||
digipeaters = 'NOCALL'
|
||||
payload = 'NOT VALID'
|
||||
else:
|
||||
# If there was an error decoding the address we return save values which will be ignored by the rest of the program
|
||||
source = 'NOCALL'
|
||||
destination = 'NOCALL'
|
||||
digipeaters = 'NOCALL'
|
||||
payload = 'NOT VALID'
|
||||
#raise Exception('Not a data packet')
|
||||
|
||||
return (source, destination, digipeaters, payload)
|
||||
|
||||
def getAllAddress(packetAddress):
|
||||
addressSize = 7
|
||||
# Check if the networked address string is valid
|
||||
if (len(packetAddress) % 7) == 0:
|
||||
# Create a list of all address in ASCII form
|
||||
try:
|
||||
allAddress = [pythonax25.network_to_ascii(packetAddress[i:i+addressSize])
|
||||
for i in range(0, len(packetAddress), addressSize)]
|
||||
except:
|
||||
allAddress = 0
|
||||
|
||||
return allAddress
|
||||
else:
|
||||
# Received a non valid address. Fill return value with NULL so we don't crash
|
||||
allAddress = 0
|
||||
return allAddress
|
||||
#raise Exception('Error: Address is not a multiple of 7')
|
||||
|
||||
def bind_ax25():
|
||||
# Check if there's any active AX25 port
|
||||
current_port = 0;
|
||||
port_nr = pythonax25.config_load_ports()
|
||||
aprs.nr_of_ports = port_nr
|
||||
if port_nr > 0:
|
||||
# Get the device name of the first port
|
||||
axport.append(pythonax25.config_get_first_port())
|
||||
axdevice.append(pythonax25.config_get_device(axport[current_port]))
|
||||
axaddress.append(pythonax25.config_get_address(axport[current_port]))
|
||||
print (axport[current_port], axdevice[current_port], axaddress[current_port])
|
||||
current_port = current_port + 1
|
||||
|
||||
while port_nr - current_port > 0:
|
||||
axport.append(pythonax25.config_get_next_port(axport[current_port-1]))
|
||||
axdevice.append(pythonax25.config_get_device(axport[current_port]))
|
||||
axaddress.append(pythonax25.config_get_address(axport[current_port]))
|
||||
print (axport[current_port], axdevice[current_port], axaddress[current_port])
|
||||
current_port = current_port + 1
|
||||
|
||||
else:
|
||||
exit(0)
|
||||
|
||||
# Initiate a PF_PACKET socket (RX)
|
||||
rx_socket = pythonax25.packet_socket()
|
||||
|
||||
return rx_socket
|
||||
|
||||
def receive_ax25(rx_socket):
|
||||
# Blocking receive packet, 10 ms timeout
|
||||
receive = pythonax25.packet_rx(rx_socket,10)
|
||||
return receive
|
||||
|
||||
def send_ax25(portCall, srcCall, dest, digi, msg):
|
||||
# Initiate a datagram socket (TX)
|
||||
tx_socket = pythonax25.datagram_socket()
|
||||
res = pythonax25.datagram_bind(tx_socket, srcCall, portCall)
|
||||
#print(res)
|
||||
|
||||
if digi == 0:
|
||||
res = pythonax25.datagram_tx(tx_socket, dest, msg)
|
||||
else:
|
||||
res = pythonax25.datagram_tx_digi(tx_socket, dest, digi, msg)
|
||||
#print(res)
|
||||
pythonax25.close_socket(tx_socket)
|
||||
|
||||
def connect_mqtt():
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
print("Connected to MQTT Broker!")
|
||||
else:
|
||||
print("Failed to connect, return code %d\n", rc)
|
||||
# Set Connecting Client ID
|
||||
client = mqtt_client.Client(mqtt.client_id)
|
||||
#client.username_pw_set(username, password)
|
||||
client.on_connect = on_connect
|
||||
client.connect(mqtt.broker, mqtt.port)
|
||||
return client
|
||||
|
||||
def publish(client, topic, message):
|
||||
result = client.publish(topic, message)
|
||||
status = result[0]
|
||||
if status == 0:
|
||||
print(f"Send `{message}` to topic `{topic}`")
|
||||
else:
|
||||
print(f"Failed to send message to topic {topic}")
|
||||
|
||||
def subscribe(client: mqtt_client, topic):
|
||||
def on_message(client, userdata, message):
|
||||
received_payload = message.payload.decode()
|
||||
received_topic = Path(message.topic).name
|
||||
print(f"Received `{received_payload}` from `{message.topic}` topic")
|
||||
# Find corresponding topic in configuration-file and send this to the next function
|
||||
for topics in mqtt.topics:
|
||||
if received_topic == topics['name']:
|
||||
#print ('Found topic in list!')
|
||||
#print (topics)
|
||||
process_message(topics, received_payload)
|
||||
break
|
||||
# print(topic['name'])
|
||||
# print(topic['command'])
|
||||
|
||||
client.subscribe(topic)
|
||||
client.on_message = on_message
|
||||
|
||||
def read_config():
|
||||
try:
|
||||
with open(configuration_file) as f:
|
||||
cfg = yaml.load(f, Loader=SafeLoader)
|
||||
|
||||
mqtt.topics = cfg['topics']
|
||||
#print(mqtt.topics)
|
||||
#for topic in mqtt.topics:
|
||||
# print(topic['name'])
|
||||
# print(topic['command'])
|
||||
except:
|
||||
print ("Configuration file ./" + configuration_file + " not found.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
mqtt.broker = cfg['global']['broker']
|
||||
except:
|
||||
print ("Error in configuration file: no broker defined.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
mqtt.port = cfg['global']['port']
|
||||
except:
|
||||
print ("Error in configuration file: no port defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.topic_root = cfg['global']['topic_root']
|
||||
except:
|
||||
print ("Error in configuration file: no topic defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.transmit_rate = cfg['global']['transmit_rate']
|
||||
except:
|
||||
print ("Error in configuration file: no transmit_rate defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.retry = cfg['global']['retry']
|
||||
except:
|
||||
print ("Error in configuration file: no retry defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.destination = cfg['global']['destination']
|
||||
except:
|
||||
print ("Error in configuration file: no retry defined.")
|
||||
sys.exit(1)
|
||||
|
||||
mqtt.client_id = f'{mqtt.topic_root}-{random.randint(0, 1000)}'
|
||||
|
||||
print (mqtt.broker)
|
||||
print (mqtt.topic_root)
|
||||
print (mqtt.port)
|
||||
print (mqtt.client_id)
|
||||
|
||||
# Loop through all topics and activate them
|
||||
def add_subscribtions_from_configfile(client):
|
||||
for topics in mqtt.topics:
|
||||
current_topic = mqtt.topic_root + '/' + topics['name']
|
||||
subscribe(client,current_topic)
|
||||
print('Topic ' + topics['name'] + ' added')
|
||||
|
||||
def process_message(data, payload):
|
||||
#print ('Payload: '+ payload)
|
||||
#print (data['call'])
|
||||
#print (data['port'])
|
||||
|
||||
if aprs.busy == 0:
|
||||
# find payload in configuration file
|
||||
for commands in data['command']:
|
||||
if payload == commands['payload']:
|
||||
aprs.time_out_timer = time.time() # Start timeout timer
|
||||
aprs.busy = 1
|
||||
aprs.selected_port = data['port']
|
||||
|
||||
# Find call of ax25 port
|
||||
for position in range(len(axdevice)):
|
||||
if axdevice[position] == aprs.selected_port:
|
||||
aprs.port_call = axaddress[position]
|
||||
|
||||
aprs.source_call = data['server']
|
||||
aprs.wait_for_ack = commands['response']
|
||||
aprs.call_of_wait_for_ack = data['call']
|
||||
aprs.message = ':' + data['call'].ljust(9) + ':' + commands['cmd']
|
||||
arguments = '-d \"APRX29\" -s ' + data['port'] + ' \"' + aprs.message + '\"'
|
||||
beacon_program = "/usr/sbin/beacon"
|
||||
aprs.beacon_program_with_arguments = beacon_program + " " + arguments
|
||||
#os.system(aprs.beacon_program_with_arguments)
|
||||
print ('APRS message ' + aprs.message + ' send to ' + aprs.call_of_wait_for_ack + '.')
|
||||
mqtt.state = 'busy'
|
||||
mqtt.aprs_state = 'sending message'
|
||||
aprs.request_to_send = 1;
|
||||
else:
|
||||
mqtt.state = 'busy'
|
||||
|
||||
def run():
|
||||
read_config()
|
||||
|
||||
rx_socket = bind_ax25()
|
||||
|
||||
client = connect_mqtt()
|
||||
|
||||
add_subscribtions_from_configfile(client)
|
||||
#topic = mqtt.topic_root + '/set'
|
||||
#subscribe(client,topic)
|
||||
client.loop_start()
|
||||
|
||||
# Send ready to MQTT broker to indicate we are meaning business
|
||||
mqtt.aprs_state = 'ready'
|
||||
#topic = mqtt.topic_root + '/aprs_status'
|
||||
#publish(client,topic,'ready')
|
||||
mqtt.state = 'busy'
|
||||
|
||||
aprs.time_out_timer = time.time()
|
||||
while True:
|
||||
|
||||
if aprs.request_to_send == 1:
|
||||
send_ax25(aprs.port_call, aprs.source_call, mqtt.destination, 0, aprs.message)
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
#print(aprs.selected_port)
|
||||
#print(aprs.message)
|
||||
aprs.request_to_send = 0
|
||||
|
||||
receive = receive_ax25(rx_socket)
|
||||
for port in range(len(axdevice)):
|
||||
if receive[0][1] == axdevice[port]:
|
||||
#print(receive)
|
||||
source, destination, digipeaters, payload = parsePacket(receive[1])
|
||||
# bug UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf8 in position 47: invalid start byte
|
||||
try:
|
||||
payload = payload.decode()
|
||||
except:
|
||||
payload = 'NOT VALID'
|
||||
|
||||
#print("Packet Received by = %s"%axaddress[0])
|
||||
#print("Source Address = %s"%source)
|
||||
#print("Destination Address = %s"%destination)
|
||||
#print("Digipeaters =")
|
||||
#print(digipeaters)
|
||||
#print("Payload = %s"%payload)
|
||||
#print("")
|
||||
|
||||
if aprs.wait_for_ack != 0:
|
||||
if source == aprs.call_of_wait_for_ack:
|
||||
# split payload at colon. If it is a valid acknowledge, we should get three
|
||||
# substrings: the first in empty, the second with the call of the ax25
|
||||
# interface and the thirth with the acknowledge
|
||||
|
||||
split_message=payload.split(":")
|
||||
if len(split_message) == 3:
|
||||
if split_message[1].replace(" ", "") == axaddress[port]:
|
||||
if split_message[2] == aprs.wait_for_ack:
|
||||
print ('Received acknowledge ' + aprs.wait_for_ack + ' from ' + aprs.call_of_wait_for_ack + ".")
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
aprs.wait_for_ack = 0
|
||||
aprs.busy = 0
|
||||
aprs.retry_counter = 0
|
||||
mqtt.aprs_state = 'message sent'
|
||||
#topic = mqtt.topic_root + '/aprs_status'
|
||||
#publish(client,topic,mqtt.aprs_state)
|
||||
mqtt.state = 'busy'
|
||||
|
||||
|
||||
# Time out waiting for acknowledge
|
||||
if aprs.wait_for_ack != 0:
|
||||
if time.time() - aprs.time_out_timer > mqtt.transmit_rate:
|
||||
aprs.retry_counter = aprs.retry_counter + 1
|
||||
if aprs.retry_counter < mqtt.retry:
|
||||
# Try again
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
aprs.request_to_send = 1;
|
||||
#os.system(aprs.beacon_program_with_arguments)
|
||||
print ('Retry: APRS ' + aprs.message + ' message send to ' + aprs.call_of_wait_for_ack + '.')
|
||||
mqtt.aprs_state = 'sending message (retry ' + str(aprs.retry_counter) + ')'
|
||||
mqtt.state = 'busy'
|
||||
else:
|
||||
# Give up
|
||||
print ('No acknowledge received from ' + aprs.call_of_wait_for_ack + '. Giving up.')
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
aprs.wait_for_ack = 0
|
||||
aprs.busy = 0
|
||||
aprs.retry_counter = 0
|
||||
mqtt.aprs_state = 'sending message failed'
|
||||
#topic = mqtt.topic_root + '/aprs_status'
|
||||
#publish(client,topic,mqtt.aprs_state)
|
||||
mqtt.state = 'busy'
|
||||
# If APRS system is transmitting, retrying and still waiting for acknowledge, keep on waiting and send an MQTT update
|
||||
if mqtt.state == 'busy':
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,mqtt.aprs_state)
|
||||
mqtt.state = 'ready'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#sys.stdout = sys.stderr = open('debug.log', 'w')
|
||||
run()
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
A bridge between APRS messaging and MQTT, designed to control my lora_aprs_node_pico
|
||||
|
||||
(C)2022 M.T. Konstapel https://meezenest.nl/mees
|
||||
|
||||
This file is part of aprs-mqtt-bridge.
|
||||
|
||||
aprs-mqtt-bridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
aprs-mqtt-bridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with aprs-mqtt-bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import sys
|
||||
import random
|
||||
import time
|
||||
import config_with_yaml as config
|
||||
from paho.mqtt import client as mqtt_client
|
||||
|
||||
configuration_file = "aprs-mqtt-bridge.yml"
|
||||
|
||||
# This is where we keep our settings
|
||||
class mqtt_settings:
|
||||
pass
|
||||
mqtt = mqtt_settings()
|
||||
|
||||
def connect_mqtt():
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
print("Connected to MQTT Broker!")
|
||||
else:
|
||||
print("Failed to connect, return code %d\n", rc)
|
||||
# Set Connecting Client ID
|
||||
client = mqtt_client.Client(mqtt.client_id)
|
||||
#client.username_pw_set(username, password)
|
||||
client.on_connect = on_connect
|
||||
client.connect(mqtt.broker, mqtt.port)
|
||||
return client
|
||||
|
||||
def publish(client, topic, message):
|
||||
result = client.publish(topic, message)
|
||||
status = result[0]
|
||||
if status == 0:
|
||||
print(f"Send `{message}` to topic `{topic}`")
|
||||
else:
|
||||
print(f"Failed to send message to topic {topic}")
|
||||
|
||||
def subscribe(client: mqtt_client, topic):
|
||||
def on_message(client, userdata, message):
|
||||
print(f"Received `{message.payload.decode()}` from `{message.topic}` topic")
|
||||
|
||||
client.subscribe(topic)
|
||||
client.on_message = on_message
|
||||
|
||||
def read_config():
|
||||
try:
|
||||
cfg = config.load(configuration_file)
|
||||
except:
|
||||
print ("Configuration file ./" + configuration_file + " not found.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.broker = cfg.getProperty("global.broker")
|
||||
except:
|
||||
print ("Error in configuration file: no broker defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.port = cfg.getPropertyWithDefault("global.port", 1883)
|
||||
except:
|
||||
print ("Error in configuration file: no port defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.topic = cfg.getPropertyWithDefault("global.topic", "aprs-mqtt-bridge")
|
||||
except:
|
||||
print ("Error in configuration file: no topic defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.transmit_rate = cfg.getPropertyWithDefault("global.transmit_rate", 30)
|
||||
except:
|
||||
print ("Error in configuration file: no transmit_rate defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.retry = cfg.getPropertyWithDefault("global.retry", 3)
|
||||
except:
|
||||
print ("Error in configuration file: no retry defined.")
|
||||
sys.exit(1)
|
||||
|
||||
mqtt.client_id = f'{mqtt.topic}-{random.randint(0, 1000)}'
|
||||
|
||||
print (mqtt.broker)
|
||||
print (mqtt.topic)
|
||||
print (mqtt.port)
|
||||
print (mqtt.client_id)
|
||||
print (cfg)
|
||||
|
||||
def run():
|
||||
read_config()
|
||||
|
||||
client = connect_mqtt()
|
||||
topic = mqtt.topic + '/set'
|
||||
subscribe(client,topic)
|
||||
client.loop_start()
|
||||
while True:
|
||||
time.sleep(1)
|
||||
topic = mqtt.topic + '/result'
|
||||
publish(client,topic,'test')
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
385
aprs_utils/aprs-mqtt-bridge_old/aprs-mqtt-bridge.py_2022-01-14
Executable file
385
aprs_utils/aprs-mqtt-bridge_old/aprs-mqtt-bridge.py_2022-01-14
Executable file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
A bridge between APRS messaging and MQTT, designed to control my lora_aprs_node_pico
|
||||
|
||||
(C)2022 M.T. Konstapel https://meezenest.nl/mees
|
||||
|
||||
This file is part of aprs-mqtt-bridge.
|
||||
|
||||
aprs-mqtt-bridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
aprs-mqtt-bridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with aprs-mqtt-bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import sys
|
||||
import random
|
||||
import time
|
||||
import os
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
from yaml.loader import SafeLoader
|
||||
from paho.mqtt import client as mqtt_client
|
||||
import pythonax25
|
||||
|
||||
configuration_file = "aprs-mqtt-bridge.yml"
|
||||
|
||||
# This is where we keep our settings
|
||||
class mqtt_settings:
|
||||
#broker
|
||||
#topic_root
|
||||
#port
|
||||
#client_id
|
||||
#transmit_rate
|
||||
#retry
|
||||
#topics
|
||||
state = 'ready'
|
||||
pass
|
||||
mqtt = mqtt_settings()
|
||||
|
||||
axport = []
|
||||
axdevice = []
|
||||
axaddress = []
|
||||
class aprs_status:
|
||||
nr_of_ports = 0
|
||||
busy = 0
|
||||
wait_for_ack = 0
|
||||
call_of_wait_for_ack = 0
|
||||
time_out_timer = 0
|
||||
retry_counter = 0
|
||||
selected_port = 0
|
||||
request_to_send = 0
|
||||
pass
|
||||
aprs = aprs_status()
|
||||
|
||||
def parsePacket(string):
|
||||
# Split the address and payload separated by APRS PID
|
||||
buffer = string.split(b'\x03\xf0')
|
||||
address = buffer[0]
|
||||
|
||||
# Check if the first byte indicates it is a data packet
|
||||
if address[0] == 0:
|
||||
# Cut the first byte and feed it to the address parser
|
||||
listAddress = getAllAddress(address[1:])
|
||||
|
||||
if listAddress != 0:
|
||||
# Get the source, destination, and digipeaters from the address list
|
||||
source = listAddress[1]
|
||||
destination = listAddress[0]
|
||||
digipeaters = listAddress[2:]
|
||||
payload = buffer[1]
|
||||
else:
|
||||
# If there was an error decoding the address we return save values which will be ignored by the rest of the program
|
||||
source = 'NOCALL'
|
||||
destination = 'NOCALL'
|
||||
digipeaters = 'NOCALL'
|
||||
payload = 'NOT VALID'
|
||||
else:
|
||||
# If there was an error decoding the address we return save values which will be ignored by the rest of the program
|
||||
source = 'NOCALL'
|
||||
destination = 'NOCALL'
|
||||
digipeaters = 'NOCALL'
|
||||
payload = 'NOT VALID'
|
||||
#raise Exception('Not a data packet')
|
||||
|
||||
return (source, destination, digipeaters, payload)
|
||||
|
||||
def getAllAddress(packetAddress):
|
||||
addressSize = 7
|
||||
# Check if the networked address string is valid
|
||||
if (len(packetAddress) % 7) == 0:
|
||||
# Create a list of all address in ASCII form
|
||||
try:
|
||||
allAddress = [pythonax25.network_to_ascii(packetAddress[i:i+addressSize])
|
||||
for i in range(0, len(packetAddress), addressSize)]
|
||||
except:
|
||||
allAddress = 0
|
||||
|
||||
return allAddress
|
||||
else:
|
||||
# Received a non valid address. Fill return value with NULL so we don't crash
|
||||
allAddress = 0
|
||||
return allAddress
|
||||
#raise Exception('Error: Address is not a multiple of 7')
|
||||
|
||||
def bind_ax25():
|
||||
# Check if there's any active AX25 port
|
||||
current_port = 0;
|
||||
port_nr = pythonax25.config_load_ports()
|
||||
aprs.nr_of_ports = port_nr
|
||||
if port_nr > 0:
|
||||
# Get the device name of the first port
|
||||
axport.append(pythonax25.config_get_first_port())
|
||||
axdevice.append(pythonax25.config_get_device(axport[current_port]))
|
||||
axaddress.append(pythonax25.config_get_address(axport[current_port]))
|
||||
print (axport[current_port], axdevice[current_port], axaddress[current_port])
|
||||
current_port = current_port + 1
|
||||
|
||||
while port_nr - current_port > 0:
|
||||
axport.append(pythonax25.config_get_next_port(axport[current_port-1]))
|
||||
axdevice.append(pythonax25.config_get_device(axport[current_port]))
|
||||
axaddress.append(pythonax25.config_get_address(axport[current_port]))
|
||||
print (axport[current_port], axdevice[current_port], axaddress[current_port])
|
||||
current_port = current_port + 1
|
||||
|
||||
else:
|
||||
exit(0)
|
||||
|
||||
# Initiate a PF_PACKET socket (RX)
|
||||
rx_socket = pythonax25.packet_socket()
|
||||
|
||||
return rx_socket
|
||||
|
||||
def receive_ax25(rx_socket):
|
||||
# Blocking receive packet, 10 ms timeout
|
||||
receive = pythonax25.packet_rx(rx_socket,10)
|
||||
return receive
|
||||
|
||||
def send_ax25(portCall, srcCall, dest, digi, msg):
|
||||
# Initiate a datagram socket (TX)
|
||||
tx_socket = pythonax25.datagram_socket()
|
||||
res = pythonax25.datagram_bind(tx_socket, srcCall, portCall)
|
||||
#print(res)
|
||||
|
||||
if digi == 0:
|
||||
res = pythonax25.datagram_tx(tx_socket, dest, msg)
|
||||
else:
|
||||
res = pythonax25.datagram_tx_digi(tx_socket, dest, digi, msg)
|
||||
#print(res)
|
||||
pythonax25.close_socket(tx_socket)
|
||||
|
||||
def connect_mqtt():
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
print("Connected to MQTT Broker!")
|
||||
else:
|
||||
print("Failed to connect, return code %d\n", rc)
|
||||
# Set Connecting Client ID
|
||||
client = mqtt_client.Client(mqtt.client_id)
|
||||
#client.username_pw_set(username, password)
|
||||
client.on_connect = on_connect
|
||||
client.connect(mqtt.broker, mqtt.port)
|
||||
return client
|
||||
|
||||
def publish(client, topic, message):
|
||||
result = client.publish(topic, message)
|
||||
status = result[0]
|
||||
if status == 0:
|
||||
print(f"Send `{message}` to topic `{topic}`")
|
||||
else:
|
||||
print(f"Failed to send message to topic {topic}")
|
||||
|
||||
def subscribe(client: mqtt_client, topic):
|
||||
def on_message(client, userdata, message):
|
||||
received_payload = message.payload.decode()
|
||||
received_topic = Path(message.topic).name
|
||||
print(f"Received `{received_payload}` from `{message.topic}` topic")
|
||||
# Find corresponding topic in configuration-file and send this to the next function
|
||||
for topics in mqtt.topics:
|
||||
if received_topic == topics['name']:
|
||||
#print ('Found topic in list!')
|
||||
#print (topics)
|
||||
process_message(topics, received_payload)
|
||||
break
|
||||
# print(topic['name'])
|
||||
# print(topic['command'])
|
||||
|
||||
client.subscribe(topic)
|
||||
client.on_message = on_message
|
||||
|
||||
def read_config():
|
||||
try:
|
||||
with open(configuration_file) as f:
|
||||
cfg = yaml.load(f, Loader=SafeLoader)
|
||||
|
||||
mqtt.topics = cfg['topics']
|
||||
#print(mqtt.topics)
|
||||
#for topic in mqtt.topics:
|
||||
# print(topic['name'])
|
||||
# print(topic['command'])
|
||||
except:
|
||||
print ("Configuration file ./" + configuration_file + " not found.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
mqtt.broker = cfg['global']['broker']
|
||||
except:
|
||||
print ("Error in configuration file: no broker defined.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
mqtt.port = cfg['global']['port']
|
||||
except:
|
||||
print ("Error in configuration file: no port defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.topic_root = cfg['global']['topic_root']
|
||||
except:
|
||||
print ("Error in configuration file: no topic defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.transmit_rate = cfg['global']['transmit_rate']
|
||||
except:
|
||||
print ("Error in configuration file: no transmit_rate defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.retry = cfg['global']['retry']
|
||||
except:
|
||||
print ("Error in configuration file: no retry defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.destination = cfg['global']['destination']
|
||||
except:
|
||||
print ("Error in configuration file: no retry defined.")
|
||||
sys.exit(1)
|
||||
|
||||
mqtt.client_id = f'{mqtt.topic_root}-{random.randint(0, 1000)}'
|
||||
|
||||
print (mqtt.broker)
|
||||
print (mqtt.topic_root)
|
||||
print (mqtt.port)
|
||||
print (mqtt.client_id)
|
||||
|
||||
# Loop through all topics and activate them
|
||||
def add_subscribtions_from_configfile(client):
|
||||
for topics in mqtt.topics:
|
||||
current_topic = mqtt.topic_root + '/' + topics['name']
|
||||
subscribe(client,current_topic)
|
||||
print('Topic ' + topics['name'] + ' added')
|
||||
|
||||
def process_message(data, payload):
|
||||
#print ('Payload: '+ payload)
|
||||
#print (data['call'])
|
||||
#print (data['port'])
|
||||
|
||||
if aprs.busy == 0:
|
||||
# find payload in configuration file
|
||||
for commands in data['command']:
|
||||
if payload == commands['payload']:
|
||||
aprs.time_out_timer = time.time() # Start timeout timer
|
||||
aprs.busy = 1
|
||||
aprs.selected_port = data['port']
|
||||
|
||||
# Find call of ax25 port
|
||||
for position in range(len(axdevice)):
|
||||
if axdevice[position] == aprs.selected_port:
|
||||
aprs.port_call = axaddress[position]
|
||||
|
||||
aprs.source_call = data['server']
|
||||
aprs.wait_for_ack = commands['response']
|
||||
aprs.call_of_wait_for_ack = data['call']
|
||||
aprs.message = ':' + data['call'].ljust(9) + ':' + commands['cmd']
|
||||
arguments = '-d \"APRX29\" -s ' + data['port'] + ' \"' + aprs.message + '\"'
|
||||
beacon_program = "/usr/sbin/beacon"
|
||||
aprs.beacon_program_with_arguments = beacon_program + " " + arguments
|
||||
#os.system(aprs.beacon_program_with_arguments)
|
||||
print ('APRS message ' + aprs.message + ' send to ' + aprs.call_of_wait_for_ack + '.')
|
||||
mqtt.state = 'busy'
|
||||
aprs.request_to_send = 1;
|
||||
else:
|
||||
mqtt.state = 'busy'
|
||||
|
||||
def run():
|
||||
read_config()
|
||||
|
||||
rx_socket = bind_ax25()
|
||||
|
||||
client = connect_mqtt()
|
||||
|
||||
add_subscribtions_from_configfile(client)
|
||||
#topic = mqtt.topic_root + '/set'
|
||||
#subscribe(client,topic)
|
||||
client.loop_start()
|
||||
|
||||
# Send ready to MQTT broker to indicate we are meaning business
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,'ready')
|
||||
mqtt.state = 'ready'
|
||||
|
||||
aprs.time_out_timer = time.time()
|
||||
while True:
|
||||
|
||||
if aprs.request_to_send == 1:
|
||||
send_ax25(aprs.port_call, aprs.source_call, mqtt.destination, 0, aprs.message)
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
#print(aprs.selected_port)
|
||||
#print(aprs.message)
|
||||
aprs.request_to_send = 0
|
||||
|
||||
receive = receive_ax25(rx_socket)
|
||||
for port in range(len(axdevice)):
|
||||
if receive[0][1] == axdevice[port]:
|
||||
#print(receive)
|
||||
source, destination, digipeaters, payload = parsePacket(receive[1])
|
||||
# bug UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf8 in position 47: invalid start byte
|
||||
try:
|
||||
payload = payload.decode()
|
||||
except:
|
||||
payload = 'NOT VALID'
|
||||
|
||||
#print("Packet Received by = %s"%axaddress[0])
|
||||
#print("Source Address = %s"%source)
|
||||
#print("Destination Address = %s"%destination)
|
||||
#print("Digipeaters =")
|
||||
#print(digipeaters)
|
||||
#print("Payload = %s"%payload)
|
||||
#print("")
|
||||
|
||||
if aprs.wait_for_ack != 0:
|
||||
if source == aprs.call_of_wait_for_ack:
|
||||
# split payload at colon. If it is a valid acknowledge, we should get three
|
||||
# substrings: the first in empty, the second with the call of the ax25
|
||||
# interface and the thirth with the acknowledge
|
||||
|
||||
split_message=payload.split(":")
|
||||
if len(split_message) == 3:
|
||||
if split_message[1].replace(" ", "") == axaddress[port]:
|
||||
if split_message[2] == aprs.wait_for_ack:
|
||||
print ('Received acknowledge ' + aprs.wait_for_ack + ' from ' + aprs.call_of_wait_for_ack + ".")
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
aprs.wait_for_ack = 0
|
||||
aprs.busy = 0
|
||||
aprs.retry_counter = 0
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,'ready')
|
||||
mqtt.state = 'ready'
|
||||
|
||||
|
||||
# Time out waiting for acknowledge
|
||||
if aprs.wait_for_ack != 0:
|
||||
if time.time() - aprs.time_out_timer > mqtt.transmit_rate:
|
||||
aprs.retry_counter = aprs.retry_counter + 1
|
||||
if aprs.retry_counter < mqtt.retry:
|
||||
# Try again
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
aprs.request_to_send = 1;
|
||||
#os.system(aprs.beacon_program_with_arguments)
|
||||
print ('Retry: APRS ' + aprs.message + ' message send to ' + aprs.call_of_wait_for_ack + '.')
|
||||
else:
|
||||
# Give up
|
||||
print ('No acknowledge received from ' + aprs.call_of_wait_for_ack + '. Giving up.')
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
aprs.wait_for_ack = 0
|
||||
aprs.busy = 0
|
||||
aprs.retry_counter = 0
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,'ready')
|
||||
mqtt.state = 'ready'
|
||||
# If APRS system is still waiting for acknowledge, keep on waiting and send an MQTT update
|
||||
if mqtt.state == 'busy':
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,'busy')
|
||||
mqtt.state = 'ready'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#sys.stdout = sys.stderr = open('debug.log', 'w')
|
||||
run()
|
||||
|
||||
393
aprs_utils/aprs-mqtt-bridge_old/aprs-mqtt-bridge.py_new
Executable file
393
aprs_utils/aprs-mqtt-bridge_old/aprs-mqtt-bridge.py_new
Executable file
@@ -0,0 +1,393 @@
|
||||
#!/usr/bin/python3
|
||||
"""
|
||||
A bridge between APRS messaging and MQTT, designed to control my lora_aprs_node_pico
|
||||
|
||||
(C)2022 M.T. Konstapel https://meezenest.nl/mees
|
||||
|
||||
This file is part of aprs-mqtt-bridge.
|
||||
|
||||
aprs-mqtt-bridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
aprs-mqtt-bridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with aprs-mqtt-bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import sys
|
||||
import random
|
||||
import time
|
||||
import os
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
from yaml.loader import SafeLoader
|
||||
from paho.mqtt import client as mqtt_client
|
||||
import pythonax25
|
||||
|
||||
configuration_file = "aprs-mqtt-bridge.yml"
|
||||
|
||||
# This is where we keep our settings
|
||||
class mqtt_settings:
|
||||
#broker
|
||||
#topic_root
|
||||
#port
|
||||
#client_id
|
||||
#transmit_rate
|
||||
#retry
|
||||
#topics
|
||||
state = 'ready'
|
||||
topic_message_state = 'idle'
|
||||
pass
|
||||
mqtt = mqtt_settings()
|
||||
|
||||
axport = []
|
||||
axdevice = []
|
||||
axaddress = []
|
||||
class aprs_status:
|
||||
nr_of_ports = 0
|
||||
busy = 0
|
||||
message_state = 0
|
||||
wait_for_ack = 0
|
||||
call_of_wait_for_ack = 0
|
||||
time_out_timer = 0
|
||||
retry_counter = 0
|
||||
selected_port = 0
|
||||
request_to_send = 0
|
||||
pass
|
||||
aprs = aprs_status()
|
||||
|
||||
def parsePacket(string):
|
||||
# Split the address and payload separated by APRS PID
|
||||
buffer = string.split(b'\x03\xf0')
|
||||
address = buffer[0]
|
||||
|
||||
# Check if the first byte indicates it is a data packet
|
||||
if address[0] == 0:
|
||||
# Cut the first byte and feed it to the address parser
|
||||
listAddress = getAllAddress(address[1:])
|
||||
|
||||
# Get the source, destination, and digipeaters from the address list
|
||||
source = listAddress[1]
|
||||
destination = listAddress[0]
|
||||
digipeaters = listAddress[2:]
|
||||
else:
|
||||
raise Exception('Not a data packet')
|
||||
|
||||
payload = buffer[1]
|
||||
|
||||
# bug UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf8 in position 47: invalid start byte
|
||||
try:
|
||||
payload = payload.decode()
|
||||
except:
|
||||
payload = 0;
|
||||
|
||||
return (source, destination, digipeaters, payload)
|
||||
|
||||
def getAllAddress(packetAddress):
|
||||
addressSize = 7
|
||||
# Check if the networked address string is valid
|
||||
if (len(packetAddress) % 7) == 0:
|
||||
# Create a list of all address in ASCII form
|
||||
allAddress = [pythonax25.network_to_ascii(packetAddress[i:i+addressSize])
|
||||
for i in range(0, len(packetAddress), addressSize)]
|
||||
return allAddress
|
||||
else:
|
||||
raise Exception('Error: Address is not a multiple of 7')
|
||||
|
||||
def bind_ax25():
|
||||
# Check if there's any active AX25 port
|
||||
current_port = 0;
|
||||
port_nr = pythonax25.config_load_ports()
|
||||
aprs.nr_of_ports = port_nr
|
||||
if port_nr > 0:
|
||||
# Get the device name of the first port
|
||||
axport.append(pythonax25.config_get_first_port())
|
||||
axdevice.append(pythonax25.config_get_device(axport[current_port]))
|
||||
axaddress.append(pythonax25.config_get_address(axport[current_port]))
|
||||
print (axport[current_port], axdevice[current_port], axaddress[current_port])
|
||||
current_port = current_port + 1
|
||||
|
||||
while port_nr - current_port > 0:
|
||||
axport.append(pythonax25.config_get_next_port(axport[current_port-1]))
|
||||
axdevice.append(pythonax25.config_get_device(axport[current_port]))
|
||||
axaddress.append(pythonax25.config_get_address(axport[current_port]))
|
||||
print (axport[current_port], axdevice[current_port], axaddress[current_port])
|
||||
current_port = current_port + 1
|
||||
|
||||
else:
|
||||
exit(0)
|
||||
|
||||
# Initiate a PF_PACKET socket (RX)
|
||||
rx_socket = pythonax25.packet_socket()
|
||||
|
||||
return rx_socket
|
||||
|
||||
def receive_ax25(rx_socket):
|
||||
# Blocking receive packet, 10 ms timeout
|
||||
receive = pythonax25.packet_rx(rx_socket,10)
|
||||
return receive
|
||||
|
||||
def send_ax25(portCall, srcCall, dest, digi, msg):
|
||||
# Initiate a datagram socket (TX)
|
||||
tx_socket = pythonax25.datagram_socket()
|
||||
res = pythonax25.datagram_bind(tx_socket, srcCall, portCall)
|
||||
#print(res)
|
||||
|
||||
if digi == 0:
|
||||
res = pythonax25.datagram_tx(tx_socket, dest, msg)
|
||||
else:
|
||||
res = pythonax25.datagram_tx_digi(tx_socket, dest, digi, msg)
|
||||
#print(res)
|
||||
pythonax25.close_socket(tx_socket)
|
||||
|
||||
def connect_mqtt():
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if rc == 0:
|
||||
print("Connected to MQTT Broker!")
|
||||
else:
|
||||
print("Failed to connect, return code %d\n", rc)
|
||||
# Set Connecting Client ID
|
||||
client = mqtt_client.Client(mqtt.client_id)
|
||||
#client.username_pw_set(username, password)
|
||||
client.on_connect = on_connect
|
||||
client.connect(mqtt.broker, mqtt.port)
|
||||
return client
|
||||
|
||||
def publish(client, topic, message):
|
||||
result = client.publish(topic, message)
|
||||
status = result[0]
|
||||
if status == 0:
|
||||
print(f"Send `{message}` to topic `{topic}`")
|
||||
else:
|
||||
print(f"Failed to send message to topic {topic}")
|
||||
|
||||
def subscribe(client: mqtt_client, topic):
|
||||
def on_message(client, userdata, message):
|
||||
received_payload = message.payload.decode()
|
||||
received_topic = Path(message.topic).name
|
||||
print(f"Received `{received_payload}` from `{message.topic}` topic")
|
||||
# Find corresponding topic in configuration-file and send this to the next function
|
||||
for topics in mqtt.topics:
|
||||
if received_topic == topics['name']:
|
||||
#print ('Found topic in list!')
|
||||
#print (topics)
|
||||
process_message(topics, received_payload)
|
||||
break
|
||||
# print(topic['name'])
|
||||
# print(topic['command'])
|
||||
|
||||
client.subscribe(topic)
|
||||
client.on_message = on_message
|
||||
|
||||
def read_config():
|
||||
try:
|
||||
with open(configuration_file) as f:
|
||||
cfg = yaml.load(f, Loader=SafeLoader)
|
||||
|
||||
mqtt.topics = cfg['topics']
|
||||
#print(mqtt.topics)
|
||||
#for topic in mqtt.topics:
|
||||
# print(topic['name'])
|
||||
# print(topic['command'])
|
||||
except:
|
||||
print ("Configuration file ./" + configuration_file + " not found.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
mqtt.broker = cfg['global']['broker']
|
||||
except:
|
||||
print ("Error in configuration file: no broker defined.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
mqtt.port = cfg['global']['port']
|
||||
except:
|
||||
print ("Error in configuration file: no port defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.topic_root = cfg['global']['topic_root']
|
||||
except:
|
||||
print ("Error in configuration file: no topic defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.transmit_rate = cfg['global']['transmit_rate']
|
||||
except:
|
||||
print ("Error in configuration file: no transmit_rate defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.retry = cfg['global']['retry']
|
||||
except:
|
||||
print ("Error in configuration file: no retry defined.")
|
||||
sys.exit(1)
|
||||
try:
|
||||
mqtt.destination = cfg['global']['destination']
|
||||
except:
|
||||
print ("Error in configuration file: no retry defined.")
|
||||
sys.exit(1)
|
||||
|
||||
mqtt.client_id = f'{mqtt.topic_root}-{random.randint(0, 1000)}'
|
||||
|
||||
print (mqtt.broker)
|
||||
print (mqtt.topic_root)
|
||||
print (mqtt.port)
|
||||
print (mqtt.client_id)
|
||||
|
||||
# Loop through all topics and activate them
|
||||
def add_subscrubtions_from_configfile(client):
|
||||
for topics in mqtt.topics:
|
||||
current_topic = mqtt.topic_root + '/' + topics['name']
|
||||
subscribe(client,current_topic)
|
||||
print('Topic ' + topics['name'] + ' added')
|
||||
|
||||
def process_message(data, payload):
|
||||
#print ('Payload: '+ payload)
|
||||
#print (data['call'])
|
||||
#print (data['port'])
|
||||
|
||||
if aprs.busy == 0:
|
||||
# find payload in configuration file
|
||||
for commands in data['command']:
|
||||
if payload == commands['payload']:
|
||||
aprs.busy = 1
|
||||
aprs.message_state = 1
|
||||
aprs.selected_port = data['port']
|
||||
|
||||
# Find call of ax25 port
|
||||
for position in range(len(axdevice)):
|
||||
if axdevice[position] == aprs.selected_port:
|
||||
aprs.port_call = axaddress[position]
|
||||
|
||||
aprs.source_call = data['server']
|
||||
aprs.wait_for_ack = commands['response']
|
||||
aprs.call_of_wait_for_ack = data['call']
|
||||
aprs.message = ':' + data['call'].ljust(9) + ':' + commands['cmd']
|
||||
arguments = '-d \"APRX29\" -s ' + data['port'] + ' \"' + aprs.message + '\"'
|
||||
beacon_program = "/usr/sbin/beacon"
|
||||
aprs.beacon_program_with_arguments = beacon_program + " " + arguments
|
||||
#os.system(aprs.beacon_program_with_arguments)
|
||||
print ('APRS message ' + aprs.message + ' send to ' + aprs.call_of_wait_for_ack + '.')
|
||||
# Start timeout timer
|
||||
aprs.time_out_timer = time.time()
|
||||
mqtt.state = 'busy'
|
||||
aprs.request_to_send = 1;
|
||||
else:
|
||||
mqtt.state = 'busy'
|
||||
|
||||
def run():
|
||||
read_config()
|
||||
|
||||
rx_socket = bind_ax25()
|
||||
|
||||
client = connect_mqtt()
|
||||
|
||||
add_subscrubtions_from_configfile(client)
|
||||
#topic = mqtt.topic_root + '/set'
|
||||
#subscribe(client,topic)
|
||||
client.loop_start()
|
||||
|
||||
# Send ready to MQTT broker to indicate we are meaning business
|
||||
topic = mqtt.topic_root + '/' + mqtt.topic_message_state
|
||||
publish(client,topic,'idle')
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,'ready')
|
||||
mqtt.state = 'ready'
|
||||
|
||||
aprs.time_out_timer = time.time()
|
||||
while True:
|
||||
|
||||
if aprs.request_to_send == 1:
|
||||
send_ax25(aprs.port_call, aprs.source_call, mqtt.destination, 0, aprs.message)
|
||||
#print(aprs.selected_port)
|
||||
#print(aprs.message)
|
||||
aprs.request_to_send = 0
|
||||
|
||||
receive = receive_ax25(rx_socket)
|
||||
for port in range(len(axdevice)):
|
||||
if receive[0][1] == axdevice[port]:
|
||||
#print(receive)
|
||||
source, destination, digipeaters, payload = parsePacket(receive[1])
|
||||
#print("Packet Received by = %s"%axaddress[0])
|
||||
#print("Source Address = %s"%source)
|
||||
#print("Destination Address = %s"%destination)
|
||||
#print("Digipeaters =")
|
||||
#print(digipeaters)
|
||||
#print("Payload = %s"%payload)
|
||||
#print("")
|
||||
|
||||
if aprs.wait_for_ack != 0:
|
||||
if source == aprs.call_of_wait_for_ack:
|
||||
# split payload at colon. If it is a valid acknowledge, we should get three
|
||||
# substrings: the first in empty, the second with the call of the ax25
|
||||
# interface and the thirth with the acknowledge
|
||||
|
||||
split_message=payload.split(":")
|
||||
if len(split_message) == 3:
|
||||
if split_message[1].replace(" ", "") == axaddress[port]:
|
||||
if split_message[2] == aprs.wait_for_ack:
|
||||
print ('Received acknowledge ' + aprs.wait_for_ack + ' from ' + aprs.call_of_wait_for_ack + ".")
|
||||
aprs.wait_for_ack = 0
|
||||
aprs.busy = 0
|
||||
aprs.retry_counter = 0
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,'ready')
|
||||
|
||||
# Update message_status via MQTT
|
||||
update_message = 'Command send'
|
||||
|
||||
topic = mqtt.topic_root + '/' + mqtt.topic_message_state
|
||||
publish(client,topic, update_message)
|
||||
mqtt.state = 'ready'
|
||||
|
||||
|
||||
# Time out waiting for acknowledge
|
||||
if aprs.wait_for_ack != 0:
|
||||
if time.time() - aprs.time_out_timer > mqtt.transmit_rate:
|
||||
aprs.retry_counter = aprs.retry_counter + 1
|
||||
if aprs.retry_counter < mqtt.retry:
|
||||
# Try again
|
||||
aprs.time_out_timer = time.time() # Restart timeout timer
|
||||
aprs.request_to_send = 1;
|
||||
#os.system(aprs.beacon_program_with_arguments)
|
||||
print ('Retry: APRS ' + aprs.message + ' message send to ' + aprs.call_of_wait_for_ack + '.')
|
||||
else:
|
||||
# Give up
|
||||
print ('No acknowledge received from ' + aprs.call_of_wait_for_ack + '. Giving up.')
|
||||
aprs.wait_for_ack = 0
|
||||
aprs.busy = 0
|
||||
aprs.retry_counter = 0
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,'ready')
|
||||
|
||||
# Update message_status via MQTT
|
||||
update_message = 'No ack received'
|
||||
|
||||
topic = mqtt.topic_root + '/' + mqtt.topic_message_state
|
||||
publish(client,topic, update_message)
|
||||
|
||||
mqtt.state = 'ready'
|
||||
# If APRS system is still waiting for acknowledge, keep on waiting and send an MQTT update
|
||||
if mqtt.state == 'busy':
|
||||
topic = mqtt.topic_root + '/aprs_status'
|
||||
publish(client,topic,'busy')
|
||||
|
||||
# Update message_status via MQTT
|
||||
if aprs.retry_counter == 1:
|
||||
update_message = 'Sending command'
|
||||
elif aprs.retry_counter < mqtt.retry:
|
||||
update_message = 'Retry ' + aprs.retry_counter
|
||||
|
||||
topic = mqtt.topic_root + '/' + mqtt.topic_message_state
|
||||
publish(client,topic, update_message)
|
||||
|
||||
mqtt.state = 'ready'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#sys.stdout = sys.stderr = open('debug.log', 'w')
|
||||
run()
|
||||
|
||||
116
aprs_utils/aprs-mqtt-bridge_old/aprs-mqtt-bridge.yml
Normal file
116
aprs_utils/aprs-mqtt-bridge_old/aprs-mqtt-bridge.yml
Normal file
@@ -0,0 +1,116 @@
|
||||
# Global settings apply to all other entries
|
||||
global:
|
||||
broker: pe1rxf.ampr.org # The MQTT broker we are going to use
|
||||
port: 1883 # The tcp port of the MQTT broker
|
||||
topic_root: hamnet_aprs_nodes # MQTT topic root
|
||||
transmit_rate: 20 # Number of seconds between each transmision
|
||||
retry: 3 # Try this often before giving up
|
||||
destination: APRX29 # Destination or program ID
|
||||
digi_path: WIDE2-1 # Digi path of APRS messages
|
||||
#beacon_program: /usr/sbin/beacon # The external AX.25 beacon program => obsolete
|
||||
|
||||
|
||||
topics:
|
||||
# MQTT topic: 5GHz dish at workshop (must be unique name)
|
||||
- name: ubiquity_dish_ptmp_workshop
|
||||
call: PE1RXF-6 # Call of node to which commands below are send
|
||||
server: PE1RXF-3 # Call of APRS server sending the commands
|
||||
port: ax2 # Name of AX.25 port to use
|
||||
command:
|
||||
- payload: 'ON' # This is the payload we have to receive
|
||||
cmd: 33{33 # This command is send to the node
|
||||
response: ack33 # This response is expected from the node
|
||||
- payload: 'OFF'
|
||||
cmd: 32{32
|
||||
response: ack32
|
||||
|
||||
# Server at workshop
|
||||
- name: server_workshop
|
||||
call: PE1RXF-6
|
||||
server: PE1RXF-3
|
||||
port: ax2
|
||||
command:
|
||||
- payload: 'ON'
|
||||
cmd: 31{31
|
||||
response: ack31
|
||||
- payload: 'OFF'
|
||||
cmd: 30{30
|
||||
response: ack30
|
||||
|
||||
# Server at tiny house
|
||||
- name: server_tiny_house
|
||||
call: PE1RXF-5
|
||||
server: PE1RXF-3
|
||||
port: ax2
|
||||
command:
|
||||
- payload: 'ON'
|
||||
cmd: 35{35
|
||||
response: ack35
|
||||
- payload: 'OFF'
|
||||
cmd: 34{34
|
||||
response: ack34
|
||||
|
||||
# MQTT topic: 5GHz dish at tiny house
|
||||
- name: ubiquity_dish_ptp_tiny_house
|
||||
call: PE1RXF-5
|
||||
server: PE1RXF-3
|
||||
port: ax2
|
||||
command:
|
||||
- payload: 'ON'
|
||||
cmd: 31{31
|
||||
response: ack31
|
||||
- payload: 'OFF'
|
||||
cmd: 30{30
|
||||
response: ack30
|
||||
|
||||
# MQTT topic: 5GHz dish in orchard
|
||||
- name: ubiquity_dish_ptmp_orchard
|
||||
call: PE1RXF-8
|
||||
server: PE1RXF-3
|
||||
port: ax2
|
||||
command:
|
||||
- payload: 'ON'
|
||||
cmd: 31{31
|
||||
response: ack31
|
||||
- payload: 'OFF'
|
||||
cmd: 30{30
|
||||
response: ack30
|
||||
|
||||
# MQTT topic: 5GHz dish in vegetable garden
|
||||
- name: ubiquity_dish_ptp_vegetable_garden
|
||||
call: PE1RXF-8
|
||||
server: PE1RXF-3
|
||||
port: ax2
|
||||
command:
|
||||
- payload: 'ON'
|
||||
cmd: 33{33
|
||||
response: ack33
|
||||
- payload: 'OFF'
|
||||
cmd: 32{32
|
||||
response: ack32
|
||||
|
||||
# MQTT topic: QRP-lab QDX transceiver at tiny house
|
||||
- name: qdx_transceiver_tiny_house
|
||||
call: PE1RXF-5
|
||||
server: PE1RXF-3
|
||||
port: ax2
|
||||
command:
|
||||
- payload: 'ON'
|
||||
cmd: 33{33
|
||||
response: ack33
|
||||
- payload: 'OFF'
|
||||
cmd: 32{32
|
||||
response: ack32
|
||||
|
||||
# MQTT topic: Switched 12V output at tiny house
|
||||
- name: switched_12v_tiny_house
|
||||
call: PE1RXF-5
|
||||
server: PE1RXF-3
|
||||
port: ax2
|
||||
command:
|
||||
- payload: 'ON'
|
||||
cmd: 37{37
|
||||
response: ack37
|
||||
- payload: 'OFF'
|
||||
cmd: 36{36
|
||||
response: ack36
|
||||
57
aprs_utils/aprs-mqtt-bridge_old/concept.md
Normal file
57
aprs_utils/aprs-mqtt-bridge_old/concept.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Globale werking
|
||||
Een Python programma opent een YAML-bestand met daarin alle beschikbare comando's die naar een APRS LoRa node gestuurd kunnen worden.
|
||||
|
||||
Er wordt een MQTT client gestart, die luistert naar commando's vanuit Home Assistant. Wordt er een commando ontvangen dat voorkomt in het YAML-bestand dan wordt dit verstuurd naar de APRS LoRa node.
|
||||
|
||||
## Voorbeeld YAML-bestand
|
||||
|
||||
```
|
||||
# Global settings apply to all other entries
|
||||
global:
|
||||
broker: pe1rxf.ampr.org # The broker we are going to use
|
||||
port: 1883 # The tcp port of the broker
|
||||
topic: hamnet_aprs_nodes # MQTT topic root
|
||||
transmit_rate: 30 # Number of seconds between each transmision
|
||||
retry: 3 # Try this often before giving up
|
||||
|
||||
# MQTT topic: 5GHz dish at workshop (must be unique name)
|
||||
ubiquity_dish_ptmp_workshop:
|
||||
ON: # MQTT payload
|
||||
call: PE1RXF-6 # Call of node to which commands below are send
|
||||
port: ax2 # Name of AX.25 port to use
|
||||
command: 31{31 # This command is send to the node
|
||||
response: ack31 # This command is send to the node
|
||||
OFF:
|
||||
call: PE1RXF-6
|
||||
port: ax2
|
||||
command: 30{30
|
||||
response: ack30
|
||||
|
||||
# MQTT topic: 5GHz dish at tiny house
|
||||
ubiquity_dish_ptp_tiny_house:
|
||||
ON:
|
||||
call: PE1RXF-5
|
||||
port: ax2
|
||||
command: 35{35
|
||||
response: ack35
|
||||
OFF:
|
||||
call: PE1RXF-5
|
||||
port: ax2
|
||||
command: 34{34
|
||||
response: ack34
|
||||
```
|
||||
|
||||
Home Assistant stuurt payload 'ON' naar 'ubiquity_dish_ptmp_workshop/cmd' (via de broker). Het Python programma zoekt 'ubiquity_dish_workshop.ON.command' op. Als dit bestaat dan wordt dit commando verstuurd naar de APRS LoRa node (via het externe beacon-programma).
|
||||
Als 'ubiquity_dish_workshop.ON.response' bestaat wordt er gewacht totdat deze verwachte response binnenkomt in bestand ./aprs_utils/aprs-log/aprs_received_messages.log. Eventueel wordt er een retry verstuurd.
|
||||
|
||||
Het Python programma houdt intern bij of een schakelaar aan of uit staat. Periodiek kan dit gecontroleerd worden door de status van de LoRa nodes op te vragen. De status van de schakelaar wordt gepubliceerd op op hetzelfde topic, maar dan eindigend op '/state'
|
||||
|
||||
```
|
||||
import config_with_yaml as config
|
||||
|
||||
cfg = config.load("demo.yml")
|
||||
|
||||
print (cfg.getProperty("Demo.Motors.Server"))
|
||||
print (cfg.getPropertyWithDefault("Demo.Motors.Server2", "Server2"))
|
||||
print (cfg)
|
||||
```
|
||||
45
aprs_utils/aprs-mqtt-bridge_old/debug.log
Normal file
45
aprs_utils/aprs-mqtt-bridge_old/debug.log
Normal file
@@ -0,0 +1,45 @@
|
||||
pe1rxf.ampr.org
|
||||
hamnet_aprs_nodes
|
||||
1883
|
||||
hamnet_aprs_nodes-752
|
||||
ax0 ax0 PE1RXF-1
|
||||
ax1 ax1 PE1RXF-2
|
||||
ax2 ax2 PE1RXF-3
|
||||
Topic ubiquity_dish_ptmp_workshop added
|
||||
Topic server_workshop added
|
||||
Topic ubiquity_dish_ptp_tiny_house added
|
||||
Send `ready` to topic `hamnet_aprs_nodes/aprs_status`
|
||||
Connected to MQTT Broker!
|
||||
Traceback (most recent call last):
|
||||
File "/home/marcel/ham/aprs_utils/aprs-mqtt-bridge/aprs-mqtt-bridge.py", line 364, in <module>
|
||||
run()
|
||||
File "/home/marcel/ham/aprs_utils/aprs-mqtt-bridge/aprs-mqtt-bridge.py", line 304, in run
|
||||
receive = receive_ax25(rx_socket)
|
||||
File "/home/marcel/ham/aprs_utils/aprs-mqtt-bridge/aprs-mqtt-bridge.py", line 130, in receive_ax25
|
||||
receive = pythonax25.packet_rx(rx_socket,10)
|
||||
KeyboardInterrupt
|
||||
pe1rxf.ampr.org
|
||||
hamnet_aprs_nodes
|
||||
1883
|
||||
hamnet_aprs_nodes-187
|
||||
ax0 ax0 PE1RXF-1
|
||||
ax1 ax1 PE1RXF-2
|
||||
ax2 ax2 PE1RXF-3
|
||||
Topic ubiquity_dish_ptmp_workshop added
|
||||
Topic server_workshop added
|
||||
Topic ubiquity_dish_ptp_tiny_house added
|
||||
Send `ready` to topic `hamnet_aprs_nodes/aprs_status`
|
||||
Connected to MQTT Broker!
|
||||
Received `OFF` from `hamnet_aprs_nodes/server_workshop` topic
|
||||
APRS message :PE1RXF-6 :30{30 send to PE1RXF-6.
|
||||
Send `busy` to topic `hamnet_aprs_nodes/aprs_status`
|
||||
Received acknowledge ack30 from PE1RXF-6.
|
||||
Send `ready` to topic `hamnet_aprs_nodes/aprs_status`
|
||||
Traceback (most recent call last):
|
||||
File "/home/marcel/ham/aprs_utils/aprs-mqtt-bridge/aprs-mqtt-bridge.py", line 364, in <module>
|
||||
run()
|
||||
File "/home/marcel/ham/aprs_utils/aprs-mqtt-bridge/aprs-mqtt-bridge.py", line 304, in run
|
||||
receive = receive_ax25(rx_socket)
|
||||
File "/home/marcel/ham/aprs_utils/aprs-mqtt-bridge/aprs-mqtt-bridge.py", line 130, in receive_ax25
|
||||
receive = pythonax25.packet_rx(rx_socket,10)
|
||||
KeyboardInterrupt
|
||||
1
aprs_utils/aprs-mqtt-bridge_old/python-ax25
Submodule
1
aprs_utils/aprs-mqtt-bridge_old/python-ax25
Submodule
Submodule aprs_utils/aprs-mqtt-bridge_old/python-ax25 added at dfe9976dd1
93
aprs_utils/aprs-mqtt-bridge_old/readAPRS.py
Normal file
93
aprs_utils/aprs-mqtt-bridge_old/readAPRS.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import pythonax25
|
||||
from time import sleep, perf_counter
|
||||
from threading import Thread
|
||||
|
||||
axport = []
|
||||
axdevice = []
|
||||
axaddress = []
|
||||
|
||||
def parsePacket(string):
|
||||
# Split the address and payload separated by APRS PID
|
||||
buffer = string.split(b'\x03\xf0')
|
||||
address = buffer[0]
|
||||
|
||||
# Check if the first byte indicates it is a data packet
|
||||
if address[0] == 0:
|
||||
# Cut the first byte and feed it to the address parser
|
||||
listAddress = getAllAddress(address[1:])
|
||||
|
||||
# Get the source, destination, and digipeaters from the address list
|
||||
source = listAddress[1]
|
||||
destination = listAddress[0]
|
||||
digipeaters = listAddress[2:]
|
||||
else:
|
||||
raise Exception('Not a data packet')
|
||||
|
||||
payload = buffer[1]
|
||||
return (source, destination, digipeaters, payload)
|
||||
|
||||
def getAllAddress(packetAddress):
|
||||
addressSize = 7
|
||||
# Check if the networked address string is valid
|
||||
if (len(packetAddress) % 7) == 0:
|
||||
# Create a list of all address in ASCII form
|
||||
allAddress = [pythonax25.network_to_ascii(packetAddress[i:i+addressSize])
|
||||
for i in range(0, len(packetAddress), addressSize)]
|
||||
return allAddress
|
||||
else:
|
||||
raise Exception('Error: Address is not a multiple of 7')
|
||||
|
||||
def bind_ax25():
|
||||
# Check if there's any active AX25 port
|
||||
current_port = 0;
|
||||
port_nr = pythonax25.config_load_ports()
|
||||
if port_nr > 0:
|
||||
# Get the device name of the first port
|
||||
axport.append(pythonax25.config_get_first_port())
|
||||
axdevice.append(pythonax25.config_get_device(axport[current_port]))
|
||||
axaddress.append(pythonax25.config_get_address(axport[current_port]))
|
||||
print (axport[current_port], axdevice[current_port], axaddress[current_port])
|
||||
current_port = current_port + 1
|
||||
|
||||
while port_nr - current_port > 0:
|
||||
axport.append(pythonax25.config_get_next_port(axport[current_port-1]))
|
||||
axdevice.append(pythonax25.config_get_device(axport[current_port]))
|
||||
axaddress.append(pythonax25.config_get_address(axport[current_port]))
|
||||
print (axport[current_port], axdevice[current_port], axaddress[current_port])
|
||||
current_port = current_port + 1
|
||||
|
||||
else:
|
||||
exit(0)
|
||||
|
||||
# Initiate a PF_PACKET socket
|
||||
socket = pythonax25.packet_socket()
|
||||
return socket
|
||||
|
||||
def receive_ax25(socket):
|
||||
# Blocking receive packet, 10 ms timeout
|
||||
receive = pythonax25.packet_rx(socket,10)
|
||||
return receive
|
||||
|
||||
def main():
|
||||
|
||||
socket = bind_ax25()
|
||||
|
||||
while True:
|
||||
receive = receive_ax25(socket)
|
||||
|
||||
if receive[0][1] == axdevice[0]:
|
||||
print(receive)
|
||||
source, destination, digipeaters, payload = parsePacket(receive[1])
|
||||
print("Packet Received by = %s"%axaddress[0])
|
||||
print("Source Address = %s"%source)
|
||||
print("Destination Address = %s"%destination)
|
||||
print("Digipeaters =")
|
||||
print(digipeaters)
|
||||
print("Payload = %s"%payload)
|
||||
print("")
|
||||
else:
|
||||
continue
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user