-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconnect.py
363 lines (280 loc) · 12.1 KB
/
connect.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
#!/usr/local/python3
# This code was developed for MacOS but should work under Windows and Linux
# This program 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.
# This program 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 this program. If not,
# see <https://www.gnu.org/licenses/>.
# Having said that, it would be great to know if this software gets used. If you want, buy me a coffee, or send me some hardware
# Darryl Smith, VK2TDS. [email protected] Copyright 2023
import queue
import asyncio
import aioax25
from aioax25.kiss import make_device
from aioax25.version import AX25Version
import logging
import commands
import library
class PeerStream():
'''
Connecting a PEER to a STREAM
Links to a Stream Name
'''
def __init__(self, tnc):
self._peerStream = {}
self._tnc = tnc
def linkname (self, peer):
# Their end = peer.address
# Our end = peer._station().address
# Therefore, uniqueness ===
return str(peer.address) + ':' + str(peer._station()._address)
def whichstream (self, peer):
linkname = self.linkname (peer)
# if this peer is NOT connected to a stream, make it conencted to the current stream
if not linkname in self._peerStream:
if not self._tnc.currentStream in self._peerStream:
# There is a slot in the curren stream
self._peerStream[linkname] = self._tnc.currentStream
else:
# there is no slot in the current stream. Therefore, we need to find a space...
freeStream = None
for stream in commands.streamlist:
if not stream in self._peerStream:
freeStream = stream
if freeStream is None:
# There are no slots available in streams. So dont assign this peer to a stream
return None
self._peerStream[linkname] = freeStream # Connect this peer to a stream
# Since this peer is conencted to a stream, either because it was or because we made it, we
# can return it.
return self._peerStream[linkname]
def disconnect (self, peer):
# remove the linking
linkname = self.linkname (peer)
if linkname in self._peerStream:
self._peerStream.pop(linkname)
class Stream:
def __init__(self, stream, logging):
self._stream = stream
self._Port = None
self._logging = logging
self._completer = None
self._peer = None
self._name = None # The text name of this stream.
self._state = None
self._lastTX = None
self._peer = None
self._rxBuffer = queue.Queue()
def received (self, payload):
self._rxBuffer.put(payload)
def send (self, payload):
if not self.peer is None:
# Some convoluted code to remove top bit if needed
# also, add CR if needed
if self._completer.options['CR'].Value:
pay = (payload + '\r').encode()
else:
pay = payload.encode()
if self._completer.options['8BITCONV'].Value:
pay = bytearray(pay)
for i in range(len(pay)):
pay[i] = pay[i] & 0x7f
self._peer.send(pay)
self._lastTX = library.datetimenow(self.completer)
else:
logging.info ('*** NOT CONENCTED TO ACTUAL STREAM SEND')
def disconnect(self):
if not self.peer is None:
self._peer.disconnect()
@property
def peer(self):
return self._peer
@peer.setter
def peer(self, apeer):
self._peer = apeer
@property
def lastTX(self):
return self._lastTX
@property
def completer (self):
return self._completer
@completer.setter
def completer (self, c):
self._completer = c
@property
def state (self):
return self._state
@state.setter
def state (self, c):
self._state = c
@property
def Stream(self):
return self._stream
@property
def Port(self):
return self._Port
@Port.setter
def Port(self, port):
self._Port = port
class kiss_interface():
#ToDo: Split call from kiss_interface
def __init__ (self, tnc, on_rx, logging):
self._kissDevices = {}
self._kissInts = {}
self._kissIntsLastTX = {} # time of last transmission on this port
self._stations = {}
self.logging = logging
self.loop = asyncio.get_event_loop()
self._on_rx = on_rx
self.call = ""
self.ssid = None
self._myCall = ""
self._mySsid = None
self._tnc = tnc
self._peerstream = PeerStream(self._tnc)
def __del__(self):
for x in self._kissInts:
del self._kissInts[x]
for x in self._kissDevices:
del self._kissDevices[x]
for x in self._stations:
del self._stations[x]
def callsign(self, call):
if '-' in call:
(c,s) = call.split ('-')
self.call = c
self.ssid = int(s)
else:
self.call = c
self.ssid = None
def myCallsign(self, call):
if '-' in call:
(c,s) = call.split ('-')
self._myCall = c
self._mySsid = int(s)
else:
self._myCall = c
self._mySsid = None
def kissDeviceTCP (self, device, host, port):
self._kissDevices[device] = make_device(
type="tcp", host=host, port=port,
log=self.logging, #.getLogger("ax25.kiss"),
loop=self.loop
)
self._kissDevices[device].open() # happens in background asynchronously
def kissDeviceSerial (self, device, baud, reset_on_close=False):
self._kissDevices[device] = make_device(
type="serial", device=device, baudrate=baud,
log=self.logging, #.getLogger("ax25.kiss"),
loop=self.loop
)
self._kissDevices[device].open() # happens in background asynchronously
def kissPort (self, interface):
if not interface in self._kissInts:
(device,kissPort) = interface.split(':')
self._kissInts[interface] = aioax25.interface.AX25Interface(
kissport=self._kissDevices[device][int(kissPort)], # 0 = HF; 2 = USB
loop=self.loop,
log=self.logging,
)
self._kissIntsLastTX[interface] = None
self._kissInts[interface].bind (self._on_rx, '(.*?)', ssid=None, regex=True)
# when we open a KISSport, also open a STATION.
# TODO What happens when the callsign changes
station = aioax25.station.AX25Station (self._kissInts[interface],
self._myCall,
self._mySsid,
protocol=AX25Version.AX25_20,
log=self.logging,
loop=self.loop)
station.connection_request.connect(self._on_connection_rq) # incoming
station.attach() # Connect the station to the interface
self._stations[interface] = station
def closedown (self):
for device in self._kissDevices:
self._kissDevices[device].close()
@property
def kissInts(self):
return self._kissInts
@property
def kissIntsLastTX(self):
return self._kissIntsLastTX
@property
def kissDevices(self):
return self._kissDevices
def _on_connection_rq(self, peer, **kwargs):
log = logging.getLogger("connection.%s" % peer.address)
log.info("Incoming connection from %s", peer.address)
mystreamname = self._peerstream.whichstream(peer) # TODO fix to self
mystream = self._tnc.streams[mystreamname]
def _on_state_change(state, **kwargs):
nonlocal mystream
mystream.state = state
log.info("State is now %s", state)
constamp = self._tnc.completer.options['CONSTAMP'].Value
cbell = self._tnc.completer.options['CBELL'].Value
if state is peer.AX25PeerState.CONNECTING:
mystream.peer = peer # I *THINK* I want to connect peer at this point so I could send data ready to go out.
logging.info ('----> CONNECTING')
elif state is peer.AX25PeerState.CONNECTED:
mystream.peer = peer
self._tnc.connected = True # TODO fix this up
message = '*** CONNECTED TO ' + str(peer.address) #TODO: Add DIGIPEATER ADDRESSES
if constamp:
message = message + ' ' + library.displaydatetime (library.datetimenow(self._tnc.completer), self._tnc.completer)
if cbell:
message = message + '\a' # Termainals often have the BELL silenced
self._tnc.justConnected ( mystream, message)
cmsg = self._tnc.completer.options['CMSG'].Value
if cmsg == True or cmsg.upper() == 'DISC':
# TNC2 says if CMSG == 'DISC', send CTEXT then DISCONNECT.
ctext = self._tnc.completer.options['CTEXT'].Value
if len(ctext) > 0:
mystream.send (ctext)
if str(cmsg.upper()) == 'DISC':
peer.disconnect()
elif state is peer.AX25PeerState.DISCONNECTING:
print ('DISCONNECTING')
mystream.peer = None # I *THINK* this is what I want here.
message = message + ' ' + library.displaydatetime (library.datetimenow(self._tnc.completer), self._tnc.completer)
self._tnc.connected = False # TODO fix this up
elif state is peer.AX25PeerState.DISCONNECTED:
print ('DISCONNECTED')
if mystreamname == self._tnc.currentStream: # move to command mode if we have disconnected
self._tnc.mode = self._tnc.modeCommand
mystream.peer = None
message = '*** DISCONNECTED '
if constamp:
message = message + ' ' + library.displaydatetime (library.datetimenow(self._tnc.completer), self._tnc.completer)
logging.info (message)
self._tnc.connected = False # TODO fix this up
def _on_rx(payload, **kwargs):
nonlocal mystream
try:
payload = payload.decode()
except Exception as e:
log.exception("Could not decode %r", payload)
mystream.send("Could not decode %r: %s" % (payload, e))
return
mystream.received (payload)
self._tnc.receive()
log.info("Received: %r", payload)
mystream.send(("You sent: %r\r\n" % payload))
if payload == "bye\r":
mystream.send(("Disconnecting\r\n"))
peer.disconnect()
peer.connect_state_changed.connect(_on_state_change)
peer.received_information.connect(_on_rx)
peer.accept()
# *********************************************
# THIS FOLLOWING CODE IS NOT YET ACTIVE
# This might never be used.
# *********************************************
def connect_ax25_station(self, device, kissPort):
dev = str(int(device))
axint = self.kissDevices[dev].KissPorts(kissPort)
# Is the following code for an outgoing connection?
peer = axint.station.getpeer ('N0CALL', 0, []) # callsign, ssid, repeaters[]
axint.axint.Peer = peer
peer.connect()