parent
8c7895ea92
commit
447603cf62
14 changed files with 2918 additions and 37 deletions
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 227 KiB |
After Width: | Height: | Size: 133 KiB |
@ -0,0 +1,115 @@ |
||||
# python-ax25 |
||||
|
||||
Python AX.25 Module for Python3 |
||||
|
||||
## Introduction |
||||
|
||||
This is a python module designed for Python3 to access AX.25 features. This module is a C extension that can access the AX.25 interface from the Linux kernel. |
||||
|
||||
This C extension is inspired from pyax25 https://github.com/ha5di/pyax25 |
||||
|
||||
## Installing the Module |
||||
|
||||
Clone the Git repository |
||||
|
||||
``` |
||||
$ git clone https://github.com/josefmtd/python-ax25 |
||||
``` |
||||
|
||||
Install the module by running the install script inside the python-ax25 directory |
||||
|
||||
``` |
||||
$ cd python-ax25 |
||||
# ./install.sh |
||||
``` |
||||
|
||||
## Module Functions |
||||
|
||||
Before using any of the functions, make sure to load all the available ports using `config_load_ports()` |
||||
|
||||
``` |
||||
pythonax25.config_load_ports() |
||||
|
||||
Returns = number of available ports (int) |
||||
``` |
||||
|
||||
To get the names of available ports, use the `config_get_first_port` and `config_get_next_port` |
||||
|
||||
``` |
||||
pythonax25.config_get_first_port() |
||||
|
||||
Returns = name of first port (unicode string) |
||||
|
||||
|
||||
pythonax25.config_get_next_port(portname) |
||||
|
||||
Returns = name of port after 'portname' (unicode string) |
||||
``` |
||||
|
||||
To retrieve further information for each available port, use these functions: |
||||
1. `config_get_port_name(device)` |
||||
2. `config_get_address(portname)` |
||||
3. `config_get_device(portname)` |
||||
4. `config_get_window(portname)` |
||||
5. `config_get_packet_length(portname)` |
||||
6. `config_get_baudrate(portname)` |
||||
7. `config_get_description(portname)` |
||||
|
||||
To change the callsign from ASCII to network format and vice versa, use the functions `ascii_to_network(callsignascii)` and `network_to_ascii(callsignnetwork)` |
||||
|
||||
``` |
||||
pythonax25.ascii_to_network(callsignascii) |
||||
|
||||
Returns = callsign in network format (byte literal string) |
||||
|
||||
|
||||
pythonax25.network_to_ascii(callsignnetwork) |
||||
|
||||
Returns = callsign in ascii format (unicode string) |
||||
``` |
||||
|
||||
For receiving AX.25 packets, the packet socket is mostly used in C programs. Start a socket by using `packet_socket()` and begin receiving by using `packet_rx(fd, timeout)` |
||||
|
||||
``` |
||||
pythonax25.packet_socket() |
||||
|
||||
Returns = file descriptor (int) |
||||
|
||||
|
||||
pythonax25.packet_rx(fd, timeout) |
||||
|
||||
Returns = Protocol and Address (tuple of int and string) and packet (byte-literal string) |
||||
``` |
||||
|
||||
For sending APRS messages, the datagram socket is used. Socket is started by using `datagram_socket()`, bound to a port by using `datagram_bind(fd, srccall, portcall)` and send packets via `datagram_tx(fd, destcall, message)` or `datagram_tx(fd, destcall, digicall, message)` |
||||
|
||||
``` |
||||
pythonax25.datagram_socket() |
||||
|
||||
Returns = file descriptor (int) |
||||
|
||||
|
||||
pythonax25.datagram_bind(fd, srccall, destcall) |
||||
|
||||
Returns = result of bind (int) |
||||
|
||||
|
||||
pythonax25.datagram_tx(fd, destcall, message) |
||||
|
||||
Returns = result of transmission (int) |
||||
|
||||
|
||||
pythonax25.datagram_tx_digi(fd, destcall, digicall, message) |
||||
|
||||
Returns = result of transmission (int) |
||||
``` |
||||
|
||||
Closing socket is done by using `close_socket(fd)` |
||||
|
||||
``` |
||||
pythonax25.close_socket(fd) |
||||
|
||||
Returns = result of close (int) |
||||
``` |
||||
|
||||
2020 - Josef Matondang |
@ -0,0 +1,65 @@ |
||||
#!/usr/bin/python3 |
||||
|
||||
import pythonax25 |
||||
|
||||
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] is 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) is 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 main(): |
||||
# Check if there's any active AX25 port |
||||
if pythonax25.config_load_ports() > 0: |
||||
# Get the device name of the first port |
||||
axport = pythonax25.config_get_first_port() |
||||
axdevice = pythonax25.config_get_device(axport) |
||||
axaddress = pythonax25.config_get_address(axport) |
||||
else: |
||||
exit(0) |
||||
|
||||
# Initiate a PF_PACKET socket |
||||
socket = pythonax25.packet_socket() |
||||
|
||||
while True: |
||||
# Blocking receive packet, 10 ms timeout |
||||
receive = pythonax25.packet_rx(socket,10) |
||||
if receive[0][1] == axdevice: |
||||
print(receive) |
||||
source, destination, digipeaters, payload = parsePacket(receive[1]) |
||||
print("Packet Received by = %s"%axaddress) |
||||
print("Source Address = %s"%source) |
||||
print("Destination Address = %s"%destination) |
||||
print("Digipeaters =") |
||||
print(digipeaters) |
||||
print("Payload = %s"%payload) |
||||
print("") |
||||
else: |
||||
continue |
||||
|
||||
main() |
@ -0,0 +1,49 @@ |
||||
#!/usr/bin/python3 |
||||
|
||||
import pythonax25 |
||||
import time |
||||
|
||||
def main(): |
||||
# Check if there's any active AX25 port |
||||
if pythonax25.config_load_ports() > 0: |
||||
# Get the device name of the first port |
||||
axport = pythonax25.config_get_first_port() |
||||
axdevice = pythonax25.config_get_device(axport) |
||||
axaddress = pythonax25.config_get_address(axport) |
||||
else: |
||||
exit(0) |
||||
|
||||
# Initiate a datagram socket |
||||
socket = pythonax25.datagram_socket() |
||||
|
||||
srcCall = 'YD0ABH-13' |
||||
portCall = axaddress |
||||
|
||||
res = pythonax25.datagram_bind(socket, srcCall, portCall) |
||||
print(res) |
||||
|
||||
dest = 'APZINA' |
||||
digi = 'WIDE2-2' |
||||
msg = '!0611.08S/10649.35E$ INARad LoRa APRS#CO2=500' |
||||
|
||||
res = pythonax25.datagram_tx_digi(socket, dest, digi, msg) |
||||
print(res) |
||||
|
||||
time.sleep(1) |
||||
|
||||
msg = 'T#001,034,034,034,034,000,11111111' |
||||
res = pythonax25.datagram_tx_digi(socket, dest, digi, msg) |
||||
print(res) |
||||
|
||||
time.sleep(1) |
||||
|
||||
msg = '_07190749c045s055g055t076r001h45b10101' |
||||
res = pythonax25.datagram_tx_digi(socket, dest, digi, msg) |
||||
print(res) |
||||
|
||||
pythonax25.close_socket(socket) |
||||
|
||||
return res |
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -0,0 +1,17 @@ |
||||
#!/bin/bash |
||||
|
||||
DIR=`dirname $0` |
||||
|
||||
# Update the system |
||||
#/usr/bin/apt update |
||||
#/usr/bin/apt -y upgrade |
||||
|
||||
# Install the dependencies |
||||
#/usr/bin/apt -y install libax25 libax25-dev ax25-apps ax25-tools python3-dev |
||||
|
||||
# Install the Python module |
||||
${DIR}/setup.py build |
||||
${DIR}/setup.py install |
||||
|
||||
# Remove the build |
||||
/bin/rm -rf "${DIR}/build" |
@ -0,0 +1,352 @@ |
||||
#define PY_SSIZE_T_CLEAN |
||||
#include <Python.h> |
||||
|
||||
#include <sys/types.h> |
||||
#include <netinet/in.h> |
||||
#include <sys/ioctl.h> |
||||
#include <netdb.h> |
||||
|
||||
#include <unistd.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <syslog.h> |
||||
#include <signal.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
#include <poll.h> |
||||
|
||||
// #include <curses.h>
|
||||
|
||||
#include <sys/socket.h> |
||||
#include <net/if.h> |
||||
#include <net/ethernet.h> |
||||
|
||||
#include <netax25/ax25.h> |
||||
#include <netax25/axconfig.h> |
||||
#include <netax25/axlib.h> |
||||
|
||||
static PyObject * config_load_ports(PyObject* self, PyObject* args) { |
||||
int activePort; |
||||
activePort = ax25_config_load_ports(); |
||||
return PyLong_FromLong(activePort); |
||||
} |
||||
|
||||
static PyObject * config_get_first_port(PyObject* self, PyObject* args) { |
||||
char *portName; |
||||
portName = ax25_config_get_next(NULL); |
||||
return Py_BuildValue("s", portName); |
||||
} |
||||
|
||||
static PyObject * config_get_next_port(PyObject* self, PyObject* args) { |
||||
char *portName; |
||||
char *nextPort; |
||||
PyArg_ParseTuple(args, "s", &portName); |
||||
nextPort = ax25_config_get_next(portName); |
||||
return Py_BuildValue("s", nextPort); |
||||
} |
||||
|
||||
static PyObject * config_get_port_name(PyObject* self, PyObject* args) { |
||||
char *device; |
||||
char *portName; |
||||
PyArg_ParseTuple(args, "s", &device); |
||||
portName = ax25_config_get_name(device); |
||||
return Py_BuildValue("s", portName); |
||||
} |
||||
|
||||
static PyObject * config_get_address(PyObject* self, PyObject* args) { |
||||
char *portName; |
||||
char *address; |
||||
PyArg_ParseTuple(args, "s", &portName); |
||||
address = ax25_config_get_addr(portName); |
||||
return Py_BuildValue("s", address); |
||||
} |
||||
|
||||
static PyObject * config_get_device(PyObject* self, PyObject* args) { |
||||
char *device; |
||||
char *portName; |
||||
PyArg_ParseTuple(args, "s", &portName); |
||||
device = ax25_config_get_dev(portName); |
||||
return Py_BuildValue("s", device); |
||||
} |
||||
|
||||
static PyObject * config_get_window(PyObject* self, PyObject* args) { |
||||
int window; |
||||
char *portName; |
||||
PyArg_ParseTuple(args, "s", &portName); |
||||
window = ax25_config_get_window(portName); |
||||
return PyLong_FromLong(window); |
||||
} |
||||
|
||||
static PyObject * config_get_packet_length(PyObject* self, PyObject* args) { |
||||
int packetLength; |
||||
char *portName; |
||||
PyArg_ParseTuple(args, "s", &portName); |
||||
packetLength = ax25_config_get_paclen(portName); |
||||
return PyLong_FromLong(packetLength); |
||||
} |
||||
|
||||
static PyObject * config_get_baudrate(PyObject* self, PyObject* args) { |
||||
int baudRate; |
||||
char *portName; |
||||
PyArg_ParseTuple(args, "s", &portName); |
||||
baudRate = ax25_config_get_baud(portName); |
||||
return Py_BuildValue("i", baudRate); |
||||
} |
||||
|
||||
static PyObject * config_get_description(PyObject* self, PyObject* args) { |
||||
char *description; |
||||
char *portName; |
||||
PyArg_ParseTuple(args, "s", &portName); |
||||
description = ax25_config_get_desc(portName); |
||||
return Py_BuildValue("s", description); |
||||
} |
||||
|
||||
static PyObject * aton_entry(PyObject* self, PyObject* args) { |
||||
char *callsignNetwork = null_ax25_address.ax25_call; |
||||
char *callsignString; |
||||
int result; |
||||
|
||||
PyArg_ParseTuple(args, "s", &callsignString); |
||||
result = ax25_aton_entry(callsignString, callsignNetwork); |
||||
return Py_BuildValue("iy", result, callsignNetwork); |
||||
} |
||||
|
||||
static PyObject * ntoa(PyObject* self, PyObject* args) { |
||||
static PyObject * callsignPython; |
||||
char *callsignNetwork; |
||||
char *callsignString; |
||||
|
||||
ax25_address *callsign = &null_ax25_address; |
||||
|
||||
if (!PyArg_ParseTuple(args, "y", &callsignNetwork)) |
||||
fprintf(stderr, "ERROR: CANNOT ASSIGN\n"); |
||||
|
||||
strncpy(callsign->ax25_call, callsignNetwork, 7); |
||||
callsignString = ax25_ntoa(callsign); |
||||
|
||||
callsignPython = Py_BuildValue("s", callsignString); |
||||
|
||||
return callsignPython; |
||||
} |
||||
|
||||
static PyObject * datagram_socket(PyObject* self, PyObject* args) { |
||||
int fileDescriptor; |
||||
fileDescriptor = socket(AF_AX25, SOCK_DGRAM, 0); |
||||
return PyLong_FromLong(fileDescriptor); |
||||
} |
||||
|
||||
static PyObject * datagram_bind(PyObject* self, PyObject* args) { |
||||
struct full_sockaddr_ax25 src; |
||||
char *portcall, *srccall; |
||||
int len, sock, result; |
||||
|
||||
PyArg_ParseTuple(args, "iss", &sock, &srccall, &portcall); |
||||
|
||||
char * addr = malloc(sizeof(char*) * (strlen(srccall) + strlen(portcall) + 2)); |
||||
sprintf(addr, "%s %s", srccall, portcall); |
||||
|
||||
len = ax25_aton(addr, &src); |
||||
|
||||
free(addr); |
||||
|
||||
// Binding the socket to source
|
||||
if (bind(sock, (struct sockaddr *)&src, len) == -1) { |
||||
result = 1; |
||||
} |
||||
else { |
||||
result = 0; |
||||
} |
||||
|
||||
return PyLong_FromLong(result); |
||||
|
||||
} |
||||
|
||||
static PyObject * datagram_tx_digi(PyObject* self, PyObject* args) { |
||||
struct full_sockaddr_ax25 dest; |
||||
char *destcall = NULL, *digicall = NULL; |
||||
char *message; |
||||
int dlen, sock, result; |
||||
|
||||
PyArg_ParseTuple(args, "isss", &sock, &destcall, &digicall, &message); |
||||
|
||||
char * addr = malloc(sizeof(char*) * (strlen(destcall) + strlen(digicall) + 2)); |
||||
sprintf(addr, "%s %s", destcall, digicall); |
||||
|
||||
dlen = ax25_aton(addr, &dest); |
||||
|
||||
free(addr); |
||||
|
||||
// Send a datagram packet to socket
|
||||
if (sendto(sock, message, strlen(message), 0, (struct sockaddr *)&dest, dlen) == -1) { |
||||
result = 1; |
||||
} |
||||
|
||||
result = 0; |
||||
return PyLong_FromLong(result); |
||||
} |
||||
|
||||
static PyObject * datagram_tx(PyObject* self, PyObject* args) { |
||||
struct full_sockaddr_ax25 dest; |
||||
char *destcall = NULL; |
||||
char *message; |
||||
int dlen, sock, result; |
||||
|
||||
PyArg_ParseTuple(args, "iss", &sock, &destcall, &message); |
||||
|
||||
dlen = ax25_aton(destcall, &dest); |
||||
|
||||
// Send a datagram packet to socket
|
||||
if (sendto(sock, message, strlen(message), 0, (struct sockaddr *)&dest, dlen) == -1) { |
||||
result = 1; |
||||
} |
||||
|
||||
result = 0; |
||||
return PyLong_FromLong(result); |
||||
} |
||||
|
||||
// Using PF_PACKET Socket
|
||||
|
||||
static PyObject * packet_socket(PyObject* self, PyObject* args) { |
||||
int fileDescriptor; |
||||
fileDescriptor = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_AX25)); |
||||
return PyLong_FromLong(fileDescriptor); |
||||
} |
||||
|
||||
// Close a socket
|
||||
static PyObject * close_socket(PyObject* self, PyObject* args) { |
||||
int fileDescriptor; |
||||
int result; |
||||
|
||||
PyArg_ParseTuple(args, "i", &fileDescriptor); |
||||
result = close(fileDescriptor); |
||||
return PyLong_FromLong(result); |
||||
} |
||||
|
||||
static PyObject * packet_tx(PyObject* self, PyObject* args) { |
||||
int fileDescriptor; |
||||
int result; |
||||
int length; |
||||
char *buffer; |
||||
char *destination; |
||||
struct sockaddr socketAddress; |
||||
int addressSize = sizeof(socketAddress); |
||||
unsigned char newBuffer[1000]; |
||||
int bufferLength; |
||||
int i; |
||||
int k; |
||||
unsigned char charBuffer; |
||||
|
||||
PyArg_ParseTuple(args, "isis", &fileDescriptor, &buffer, &length, &destination); |
||||
|
||||
bufferLength = strlen(buffer); |
||||
|
||||
i = 0; |
||||
k = 0; |
||||
|
||||
while ( i < bufferLength ) { |
||||
charBuffer = (buffer[i++] & 0x0f) << 4; |
||||
charBuffer = charBuffer | (buffer[i++] & 0x0f); |
||||
newBuffer[k++] = charBuffer; |
||||
} |
||||
|
||||
strcpy(socketAddress.sa_data, destination); |
||||
socketAddress.sa_family = AF_AX25; |
||||
|
||||
result = sendto(fileDescriptor, newBuffer, k, 0, &socketAddress, addressSize); |
||||
|
||||
return Py_BuildValue("i", result); |
||||
} |
||||
|
||||
static PyObject * packet_rx(PyObject* self, PyObject* args) { |
||||
int fileDescriptor; |
||||
int result; |
||||
int addressSize; |
||||
int packetSize; |
||||
int timeout; |
||||
|
||||
struct sockaddr socketAddress; |
||||
struct pollfd pollFileDescriptor; |
||||
|
||||
unsigned char receiveBuffer[1024]; |
||||
|
||||
PyArg_ParseTuple(args, "ii", &fileDescriptor, &timeout); |
||||
|
||||
// Poll the socket for an available data
|
||||
pollFileDescriptor.fd = fileDescriptor; |
||||
pollFileDescriptor.events = POLLRDNORM; |
||||
|
||||
result = poll(&pollFileDescriptor, 1, timeout); |
||||
|
||||
// Read all packet received
|
||||
packetSize = 0; |
||||
socketAddress.sa_family = AF_UNSPEC; |
||||
strcpy(socketAddress.sa_data, ""); |
||||
|
||||
if (result == 1) { |
||||
addressSize = sizeof(socketAddress); |
||||
packetSize = recvfrom(fileDescriptor, receiveBuffer, sizeof(receiveBuffer), |
||||
0, &socketAddress, (socklen_t*)&addressSize); |
||||
} |
||||
|
||||
return Py_BuildValue("(is)y#", socketAddress.sa_family, socketAddress.sa_data, |
||||
receiveBuffer, packetSize); |
||||
} |
||||
|
||||
static PyObject *PythonAx25Error; |
||||
|
||||
//////////////////////////////////////////
|
||||
// Define methods
|
||||
//////////////////////////////////////////
|
||||
|
||||
static PyMethodDef python_ax25_functions[] = { |
||||
{"config_load_ports", config_load_ports, METH_VARARGS, ""}, |
||||
{"config_get_first_port", config_get_first_port, METH_VARARGS, ""}, |
||||
{"config_get_next_port", config_get_next_port, METH_VARARGS, ""}, |
||||
{"config_get_port_name", config_get_port_name, METH_VARARGS, ""}, |
||||
{"config_get_address", config_get_address, METH_VARARGS, ""}, |
||||
{"config_get_device", config_get_device, METH_VARARGS, ""}, |
||||
{"config_get_window", config_get_window, METH_VARARGS, ""}, |
||||
{"config_get_packet_length", config_get_packet_length, METH_VARARGS, ""}, |
||||
{"config_get_baudrate", config_get_baudrate, METH_VARARGS, ""}, |
||||
{"config_get_description", config_get_description, METH_VARARGS, ""}, |
||||
{"network_to_ascii", ntoa, METH_VARARGS, ""}, |
||||
{"ascii_to_network", aton_entry, METH_VARARGS, ""}, |
||||
{"datagram_socket", datagram_socket, METH_VARARGS, ""}, |
||||
{"datagram_bind", datagram_bind, METH_VARARGS, ""}, |
||||
{"datagram_tx_digi", datagram_tx_digi, METH_VARARGS, ""}, |
||||
{"datagram_tx", datagram_tx, METH_VARARGS, ""}, |
||||
{"packet_socket", packet_socket, METH_VARARGS, ""}, |
||||
{"packet_rx", packet_rx, METH_VARARGS, ""}, |
||||
{"packet_tx", packet_tx, METH_VARARGS, ""}, |
||||
{"close_socket", close_socket, METH_VARARGS, ""}, |
||||
{NULL, NULL, 0, NULL} |
||||
}; |
||||
|
||||
// Initialize module
|
||||
static struct PyModuleDef moduledef = { |
||||
PyModuleDef_HEAD_INIT, |
||||
"pythonax25", |
||||
"This is a python module for ax.25", |
||||
-1, |
||||
python_ax25_functions, |
||||
NULL, |
||||
NULL, |
||||
NULL, |
||||
NULL, |
||||
}; |
||||
|
||||
PyMODINIT_FUNC |
||||
|
||||
PyInit_pythonax25(void) { |
||||
PyObject * m; |
||||
m = PyModule_Create(&moduledef); |
||||
|
||||
if (m == NULL) |
||||
return NULL; |
||||
|
||||
PythonAx25Error = PyErr_NewException("pythonax25.error", NULL, NULL); |
||||
Py_INCREF(PythonAx25Error); |
||||
PyModule_AddObject(m, "error", PythonAx25Error); |
||||
|
||||
return m; |
||||
} |
@ -0,0 +1,10 @@ |
||||
#!/usr/bin/python3 |
||||
|
||||
from distutils.core import setup, Extension |
||||
|
||||
module1 = Extension('pythonax25', libraries = ['ax25', 'ax25io'], sources = ['pythonax25module.c']) |
||||
|
||||
setup (name = 'pythonax25', |
||||
version = '1.0', |
||||
description = 'CPython extension for LINUX ax.25 stack', |
||||
ext_modules = [module1]) |
Loading…
Reference in new issue