initial packet decoding / generation and simple server

section_mults
bmo 2018-02-03 09:26:25 -08:00
rodzic f3eebef5b4
commit 7ca130d841
10 zmienionych plików z 668 dodań i 0 usunięć

12
LICENSE 100644
Wyświetl plik

@ -0,0 +1,12 @@
Copyright 2018 Brian Moran
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Wyświetl plik

@ -0,0 +1,3 @@
from pywsjtx.wsjtx_packets import *

Wyświetl plik

Wyświetl plik

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
class GPSException(Exception):
def __init__(self,*args):
Exception.__init__(*args)
# From K6WRU via stackexchange : see https://ham.stackexchange.com/questions/221/how-can-one-convert-from-lat-long-to-grid-square/244#244
# Convert latitude and longitude to Maidenhead grid locators.
#
# Arguments are in signed decimal latitude and longitude. For example,
# the location of my QTH Palo Alto, CA is: 37.429167, -122.138056 or
# in degrees, minutes, and seconds: 37° 24' 49" N 122° 6' 26" W
class LatLongToGridSquare(object):
upper = 'ABCDEFGHIJKLMNOPQRSTUVWX'
lower = 'abcdefghijklmnopqrstuvwx'
@classmethod
def to_grid(cls,dec_lat, dec_lon):
if not (-180<=dec_lon<180):
raise GPSException('longitude must be -180<=lon<180, given %f\n'%dec_lon)
if not (-90<=dec_lat<90):
raise GPSException('latitude must be -90<=lat<90, given %f\n'%dec_lat)
adj_lat = dec_lat + 90.0
adj_lon = dec_lon + 180.0
grid_lat_sq = LatLongToGridSquare.upper[int(adj_lat/10)]
grid_lon_sq = LatLongToGridSquare.upper[int(adj_lon/20)]
grid_lat_field = str(int(adj_lat%10))
grid_lon_field = str(int((adj_lon/2)%10))
adj_lat_remainder = (adj_lat - int(adj_lat)) * 60
adj_lon_remainder = ((adj_lon) - int(adj_lon/2)*2) * 60
grid_lat_subsq = LatLongToGridSquare.lower[int(adj_lat_remainder/2.5)]
grid_lon_subsq = LatLongToGridSquare.lower[int(adj_lon_remainder/5)]
return grid_lon_sq + grid_lat_sq + grid_lon_field + grid_lat_field + grid_lon_subsq + grid_lat_subsq
# GPS sentences are encoded
@classmethod
def convert_to_degrees(cls, gps_value, direction):
if direction not in ['N','S','E','W']:
raise GPSException("Invalid direction specifier for lat/long: {}".format(direction))
dir_mult = 1
if direction in ['S','W']:
dir_mult = -1
if len(gps_value) < 3:
raise GPSException("Invalid Value for lat/long: {}".format(gps_value))
dot_posn = gps_value.index('.')
if dot_posn < 0:
raise GPSException("Invalid Format for lat/long: {}".format(gps_value))
degrees = gps_value[0:dot_posn-2]
mins = gps_value[dot_posn-2:]
f_degrees = dir_mult * (float(degrees) + (float(mins) / 60.0))
return f_degrees
@classmethod
def GPGLL_to_grid(cls, GPSLLText):
# example: $GPGLL,4740.99254,N,12212.31179,W,223311.00,A,A*70\r\n
try:
components = GPSLLText.split(",")
if components[0]=='$GPGLL':
del components[0]
if components[5] != 'A':
raise GPSException("Not a valid GPS fix")
lat = LatLongToGridSquare.convert_to_degrees(components[0], components[1])
long = LatLongToGridSquare.convert_to_degrees(components[2], components [3])
grid = LatLongToGridSquare.to_grid(lat, long)
except GPSException:
grid = ""
return grid

Wyświetl plik

@ -0,0 +1,51 @@
#
# In WSJTX parlance, the 'network server' is a program external to the wsjtx.exe program that handles packets emitted by wsjtx
#
# TODO: handle multicast groups.
#
# see dump_wsjtx_packets.py example for some simple usage
#
import socket
import struct
import pywsjtx
import logging
class SimpleServer(object):
logger = logging.getLogger()
MAX_BUFFER_SIZE = pywsjtx.GenericWSJTXPacket.MAXIMUM_NETWORK_MESSAGE_SIZE
DEFAULT_UDP_PORT = 2237
#
# note that '' and '127.0.0.1' behave differently. On Windows, try '' instead of 127.0.0.1 if you're not receiving any packets.
#
def __init__(self, ip_address='', udp_port=DEFAULT_UDP_PORT, **kwargs):
self.timeout = None
if kwargs.get("timeout") is not None:
self.timeout = kwargs.get("timeout")
self.sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
if self.timeout is not None:
self.sock.settimeout(self.timeout)
self.sock.bind((ip_address, int(udp_port)))
# can through a sock.timeout exception if it's configured that way.
def rx_packet(self):
pkt, addr_port = self.sock.recvfrom(self.MAX_BUFFER_SIZE) # buffer size is 1024 bytes
return(pkt, addr_port)
def send_packet(self, addr_port, pkt):
bytes_sent = self.sock.sendto(pkt,addr_port)
self.logger.debug("send_packet: Bytes sent ",bytes_sent)
def demo_run(self):
while True:
(pkt, addr_port) = self.rx_packet()
if (pkt != None):
the_packet = pywsjtx.WSJTXPacketClassFactory.from_udp_packet(addr_port, pkt)
print(the_packet)

Wyświetl plik

@ -0,0 +1,361 @@
import struct
import datetime
class PacketUtil:
@classmethod
# this hexdump brought to you by Stack Overflow
def hexdump(cls, src, length=16):
FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])
lines = []
for c in range(0, len(src), length):
chars = src[c:c + length]
hex = ' '.join(["%02x" % x for x in chars])
printable = ''.join(["%s" % ((x <= 127 and FILTER[x]) or '.') for x in chars])
lines.append("%04x %-*s %s\n" % (c, length * 3, hex, printable))
return ''.join(lines)
# timezone tomfoolery
@classmethod
def midnight_utc(cls):
utcnow = datetime.datetime.utcnow()
utcmidnight = datetime.datetime(utcnow.year, utcnow.month, utcnow.day, 0, 0)
return utcmidnight
class PacketWriter(object):
def __init__(self ):
self.ptr_pos = 0
self.packet = bytearray()
# self.max_ptr_pos
self.write_header()
def write_header(self):
self.write_QUInt32(GenericWSJTXPacket.MAGIC_NUMBER)
self.write_QInt32(GenericWSJTXPacket.SCHEMA_VERSION)
def write_QInt8(self, val):
self.packet.extend(struct.pack('>b', val))
def write_QInt32(self, val):
self.packet.extend(struct.pack('>l',val))
def write_QUInt32(self, val):
self.packet.extend(struct.pack('>L', val))
def write_QInt64(self, val):
self.packet.extend(struct.pack('>q',val))
def write_QFloat(self, val):
self.packet.extent(struct.pack('>d', val))
def write_QString(self, str_val):
length = len(str_val)
self.write_QInt32(length)
b_values = str_val
if type(str_val) != bytes:
b_values = str_val.encode()
self.packet.extend(b_values)
class PacketReader(object):
def __init__(self, packet):
self.ptr_pos = 0
self.packet = packet
self.max_ptr_pos = len(packet)-1
self.skip_header()
def at_eof(self):
return self.ptr_pos > self.max_ptr_pos
def skip_header(self):
if self.max_ptr_pos < 8:
raise Exception('Not enough data to skip header')
self.ptr_pos = 8
def check_ptr_bound(self,field_type, length):
if self.ptr_pos + length > self.max_ptr_pos+1:
raise Exception('Not enough data to extract {}'.format(field_type))
## grab data from the packet, incrementing the ptr_pos on the basis of the data we've gleaned
def QInt32(self):
self.check_ptr_bound('QInt32', 4) # sure we could inspect that, but that is slow.
(the_int32,) = struct.unpack('>l',self.packet[self.ptr_pos:self.ptr_pos+4])
self.ptr_pos += 4
return the_int32
def QInt8(self):
self.check_ptr_bound('QInt8', 1)
(the_int8,) = struct.unpack('>b', self.packet[self.ptr_pos:self.ptr_pos+1])
self.ptr_pos += 1
return the_int8
def QInt64(self):
self.check_ptr_bound('QInt64', 8)
(the_int64,) = struct.unpack('>q', self.packet[self.ptr_pos:self.ptr_pos+8])
self.ptr_pos += 8
return the_int64
def QFloat(self):
self.check_ptr_bound('QFloat', 8)
(the_double,) = struct.unpack('>d', self.packet[self.ptr_pos:self.ptr_pos+8])
self.ptr_pos += 8
return the_double
def QString(self):
str_len = self.QInt32()
if str_len == -1:
return None
self.check_ptr_bound('QString[{}]'.format(str_len),str_len)
(str,) = struct.unpack('{}s'.format(str_len), self.packet[self.ptr_pos:self.ptr_pos + str_len])
self.ptr_pos += str_len
return str.decode('utf-8')
class GenericWSJTXPacket(object):
SCHEMA_VERSION = 2
MINIMUM_SCHEMA_SUPPORTED = 2
MAXIMUM_SCHEMA_SUPPORTED = 2
MINIMUM_NETWORK_MESSAGE_SIZE = 8
MAXIMUM_NETWORK_MESSAGE_SIZE = 2048
MAGIC_NUMBER = 0xadbccbda
SCHEMA_VERSION = 2
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
self.addr_port = addr_port
self.magic = magic
self.schema = schema
self.pkt_type = pkt_type
self.id = id
self.pkt = pkt
class InvalidPacket(GenericWSJTXPacket):
TYPE_VALUE = -1
def __init__(self, addr_port, packet, message):
self.packet = packet
self.message = message
self.addr_port = addr_port
def __repr__(self):
return 'Invalid Packet: %s from %s:%s\n%s' % (self.message, self.addr_port[0], self.addr_port[1], PacketUtil.hexdump(self.packet))
class HeartBeatPacket(GenericWSJTXPacket):
TYPE_VALUE = 0
def __init__(self, addr_port: object, magic: object, schema: object, pkt_type: object, id: object, pkt: object) -> object:
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
ps = PacketReader(pkt)
the_type = ps.QInt32()
self.wsjtx_id = ps.QString()
self.max_schema = ps.QInt32()
self.version = ps.QInt8()
self.revision = ps.QInt8()
def __repr__(self):
return 'HeartBeatPacket: from {}:{}\n\twsjtx id:{}\tmax_schema:{}\tversion:{}\trevision:{}' .format(self.addr_port[0], self.addr_port[1],
self.wsjtx_id, self.max_schema, self.version, self.revision)
@classmethod
# make a heartbeat packet (a byte array) we can send to a 'client'. This should be it's own class.
def Builder(cls,wsjtx_id='pywsjtx', max_schema=2, version=1, revision=1):
# build the packet to send
pkt = PacketWriter()
pkt.write_QInt32(HeartBeatPacket.TYPE_VALUE)
pkt.write_QString(wsjtx_id)
pkt.write_QInt32(max_schema)
pkt.write_QInt32(version)
pkt.write_QInt32(revision)
return pkt.packet
class StatusPacket(GenericWSJTXPacket):
TYPE_VALUE = 1
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
ps = PacketReader(pkt)
the_type = ps.QInt32()
self.wsjtx_id = ps.QString()
self.dial_frequency = ps.QInt64()
self.mode = ps.QString()
self.dx_call = ps.QString()
self.report = ps.QString()
self.tx_mode = ps.QString()
self.tx_enabled = ps.QInt8()
self.transmitting = ps.QInt8()
self.decoding = ps.QInt8()
self.rx_df = ps.QInt32()
self.tx_df = ps.QInt32()
self.de_call = ps.QString()
self.de_grid = ps.QString()
self.dx_grid = ps.QString()
self.tx_watchdog = ps.QInt8()
self.sub_mode = ps.QString()
self.fast_mode = ps.QInt8()
def __repr__(self):
str = 'StatusPacket: from {}:{}\n\twsjtx id:{}\tde_call:{}\tde_grid:{}\n'.format(self.addr_port[0], self.addr_port[1],self.wsjtx_id,
self.de_call, self.de_grid)
str += "\tfrequency:{}\trx_df:{}\ttx_df:{}\tdx_call:{}\tdx_grid:{}\treport:{}\n".format(self.dial_frequency, self.rx_df, self.tx_df, self.dx_call, self.dx_grid, self.report)
str += "\ttransmitting:{}\t decoding:{}\ttx_enabled:{}\ttx_watchdog:{}\tsub_mode:{}\tfast_mode:{}".format(self.transmitting, self.decoding, self.tx_enabled, self.tx_watchdog,
self.sub_mode, self.fast_mode)
return str
class DecodePacket(GenericWSJTXPacket):
TYPE_VALUE = 2
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
ps = PacketReader(pkt)
the_type = ps.QInt32()
self.wsjtx_id = ps.QString()
self.new_decode = ps.QInt8()
self.millis_since_midnight = ps.QInt32()
self.time = PacketUtil.midnight_utc() + datetime.timedelta(milliseconds=self.millis_since_midnight)
self.snr = ps.QInt32()
self.delta_t = ps.QFloat()
self.delta_f = ps.QInt32()
self.mode = ps.QString()
self.message = ps.QString()
self.low_confidence = ps.QInt8()
self.off_air = ps.QInt8()
def __repr__(self):
str = 'DecodePacket: from {}:{}\n\twsjtx id:{}\tmessage:{}\n'.format(self.addr_port[0],
self.addr_port[1],
self.wsjtx_id,
self.message)
str += "\tdelta_f:{}\tnew:{}\ttime:{}\tsnr:{}\tdelta_f:{}\tmode:{}".format(self.delta_f,
self.new_decode,
self.time,
self.snr,
self.delta_f,
self.mode)
return str
class ClearPacket(GenericWSJTXPacket):
TYPE_VALUE = 3
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
class ReplyPacket(GenericWSJTXPacket):
TYPE_VALUE = 4
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
class QSOLoggedPacket(GenericWSJTXPacket):
TYPE_VALUE = 5
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
class ClosePacket(GenericWSJTXPacket):
TYPE_VALUE = 6
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
class ReplayPacket(GenericWSJTXPacket):
TYPE_VALUE = 7
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
class HaltTxPacket(GenericWSJTXPacket):
TYPE_VALUE = 8
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
class FreeTextPacket(GenericWSJTXPacket):
TYPE_VALUE = 9
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
@classmethod
def Builder(cls,to_wsjtx_id='WSJT-X', text="", send=False):
# build the packet to send
pkt = PacketWriter()
print('To_wsjtx_id ',to_wsjtx_id,' text ',text, 'send ',send)
pkt.write_QInt32(FreeTextPacket.TYPE_VALUE)
pkt.write_QString(to_wsjtx_id)
pkt.write_QString(text)
pkt.write_QInt8(send)
return pkt.packet
class WSPRDecodePacket(GenericWSJTXPacket):
TYPE_VALUE = 10
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
class LocationChangePacket(GenericWSJTXPacket):
TYPE_VALUE = 11
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
GenericWSJTXPacket.__init__(self, addr_port, magic, schema, pkt_type, id, pkt)
# handle packet-specific stuff.
@classmethod
def Builder(cls, to_wsjtx_id='WSJT-X', new_grid=""):
# build the packet to send
pkt = PacketWriter()
pkt.write_QInt32(LocationChangePacket.TYPE_VALUE)
pkt.write_QString(to_wsjtx_id)
pkt.write_QString(new_grid)
return pkt.packet
class WSJTXPacketClassFactory(GenericWSJTXPacket):
PACKET_TYPE_TO_OBJ_MAP = {
HeartBeatPacket.TYPE_VALUE: HeartBeatPacket,
StatusPacket.TYPE_VALUE: StatusPacket,
DecodePacket.TYPE_VALUE: DecodePacket,
ClearPacket.TYPE_VALUE: ClearPacket,
ReplyPacket.TYPE_VALUE: ReplyPacket,
QSOLoggedPacket.TYPE_VALUE: QSOLoggedPacket,
ClosePacket.TYPE_VALUE: ClosePacket,
ReplayPacket.TYPE_VALUE: ReplayPacket,
HaltTxPacket.TYPE_VALUE: HaltTxPacket,
FreeTextPacket.TYPE_VALUE: FreeTextPacket,
WSPRDecodePacket.TYPE_VALUE: WSPRDecodePacket
}
def __init__(self, addr_port, magic, schema, pkt_type, id, pkt):
self.addr_port = addr_port
self.magic = magic
self.schema = schema
self.pkt_type = pkt_type
self.pkt_id = id
self.pkt = pkt
def __repr__(self):
return 'WSJTXPacketFactory: from {}:{}\n{}' .format(self.addr_port[0], self.addr_port[1], PacketUtil.hexdump(self.pkt))
# Factory-like method
@classmethod
def from_udp_packet(cls, addr_port, udp_packet):
if len(udp_packet) < GenericWSJTXPacket.MINIMUM_NETWORK_MESSAGE_SIZE:
return InvalidPacket( addr_port, udp_packet, "Packet too small")
if len(udp_packet) > GenericWSJTXPacket.MAXIMUM_NETWORK_MESSAGE_SIZE:
return InvalidPacket( addr_port, udp_packet, "Packet too large")
(magic, schema, pkt_type, id_len) = struct.unpack('>LLLL', udp_packet[0:16])
if magic != GenericWSJTXPacket.MAGIC_NUMBER:
return InvalidPacket( addr_port, udp_packet, "Invalid Magic Value")
if schema < GenericWSJTXPacket.MINIMUM_SCHEMA_SUPPORTED or schema > GenericWSJTXPacket.MAXIMUM_SCHEMA_SUPPORTED:
return InvalidPacket( addr_port, udp_packet, "Unsupported schema value {}".format(schema))
klass = WSJTXPacketClassFactory.PACKET_TYPE_TO_OBJ_MAP.get(pkt_type)
if klass is None:
return InvalidPacket( addr_port, udp_packet, "Unknown packet type {}".format(pkt_type))
return klass(addr_port, magic, schema, pkt_type, id, udp_packet)

0
requirements.txt 100644
Wyświetl plik

Wyświetl plik

@ -0,0 +1,15 @@
#sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import pywsjtx.extra.simple_server
s = pywsjtx.SimpleServer()
while True:
(pkt, addr_port) = s.rx_packet()
if (pkt != None):
the_packet = pywsjtx.WSJTXPacketClassFactory.from_udp_packet(addr_port, pkt)
if type(the_packet) == pywsjtx.StatusPacket:
pass
print(the_packet)

Wyświetl plik

@ -0,0 +1,146 @@
# using standard NMEA sentences
import os
import sys
import threading
from datetime import datetime
import serial
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import pywsjtx
import pywsjtx.extra.simple_server
import pywsjtx.extra.latlong_to_grid_square
class NMEALocation(object):
# parse the NMEA message for location into a grid square
def __init__(self, grid_changed_callback = None):
self.valid = False
self.grid = ""
self.last_fix_at = None
self.grid_changed_callback = grid_changed_callback
def handle_serial(self,text):
# should be a single line.
if text.startswith('$GPGLL'):
print('nmea sentence: ', text)
grid = pywsjtx.extra.latlong_to_grid_square.LatLongToGridSquare.GPGLL_to_grid(text)
if grid != "":
self.valid = True
self.last_fix_at = datetime.utcnow()
else:
self.valid = False
if self.grid != grid:
print("NMEA = grid mismatch old: {} new: {}".format(self.grid,grid))
self.grid = grid
if (self.grid_changed_callback):
c_thr = threading.Thread(target=self.grid_changed_callback, args=(grid,), kwargs={})
c_thr.start()
class SerialGPS(object):
def __init__(self):
# TODO arbitrate access to line_handlers[]
self.line_handlers = []
self.comm_thread = None
self.comm_device = None
self.stop_signalled = False
pass
def add_handler(self, line_handler):
if (not (line_handler is None)) and (not (line_handler in self.line_handlers)):
self.line_handlers.append(line_handler)
def open(self, comport, baud, line_handler, **serial_kwargs):
if self.comm_device is not None:
self.close()
self.stop_signalled = False
self.comm_device = serial.Serial(comport, baud, **serial_kwargs)
if self.comm_device is not None:
self.add_handler(line_handler)
self.comm_thread = threading.Thread(target=self.serial_worker, args=())
self.comm_thread.start()
def close(self):
self.stop_signalled = True
self.comm_thread.join()
self.comm_device.close()
self.line_handlers = []
self.comm_device = None
self.stop_signalled = False
def remove_handler(self, line_handler):
self.line_handlers.remove(line_handler)
def serial_worker(self):
while (True):
if self.stop_signalled:
return # terminate
line = self.comm_device.readline()
# dispatch the line
if line.startswith(b'$'):
str_line = line.decode("utf-8")
for p in self.line_handlers:
p(str_line)
@classmethod
def example_line_handler(cls, text):
print('serial: ',text)
# set up the serial_gps to run
# get location data from the GPS, update the grid
# get the grid out of the status message from the WSJT-X instance
# if we have a grid, and it's not the same as GPS, then make it the same by sending the message.
# But only do that if triggered by a status message.
# rinse and repeat -- wait for a status message. Once we have a status message, see if
wsjtx_id = None
nmea_p = None
gps_grid = ""
def example_callback(new_grid):
global gps_grid
print("New Grid! {}".format(new_grid))
# this sets the
gps_grid = new_grid
sgps = SerialGPS()
s = pywsjtx.extra.simple_server.SimpleServer()
print("Starting wsjt-x message server")
while True:
(pkt, addr_port) = s.rx_packet()
if (pkt != None):
the_packet = pywsjtx.WSJTXPacketClassFactory.from_udp_packet(addr_port, pkt)
if wsjtx_id is None and (type(the_packet) == pywsjtx.HeartBeatPacket):
# we have an instance of WSJTX
print("wsjtx detected, id is {}".format(the_packet.wsjtx_id))
print("starting gps monitoring")
wsjtx_id = the_packet.wsjtx_id
# start up the GPS reader
nmea_p = NMEALocation(example_callback)
sgps.open('COM8', 9600, nmea_p.handle_serial, timeout=1.0)
if type(the_packet) == pywsjtx.StatusPacket:
if gps_grid != "" and the_packet.de_grid != gps_grid:
print("Sending Grid Change to wsjtx-x, old grid:{} new grid: {}".format(the_packet.de_grid, gps_grid))
grid_change_packet = pywsjtx.LocationChangePacket.Builder(wsjtx_id, "GRID:"+gps_grid)
print(pywsjtx.PacketUtil.hexdump(grid_change_packet))
s.send_packet(the_packet.addr_port, grid_change_packet)
# for fun, change the TX5 message to our grid square, so we don't have to call CQ again
# this only works if the length of the free text message is less than 13 characters.
# if len(the_packet.de_call <= 5):
# free_text_packet = pywsjtx.FreeTextPacket.Builder(wsjtx_id,"73 {} {}".format(the_packet.de_call, the_packet[0:4]),False)
# s.send_packet(addr_port, free_text_packet)
print(the_packet)

0
setup.py 100644
Wyświetl plik