diff --git a/config.py b/config.py index 5de0d69..3efe39f 100755 --- a/config.py +++ b/config.py @@ -198,7 +198,53 @@ def build_config(_config_file): 'LAST_PING_TX_TIME': 0, 'LAST_PING_ACK_TIME': 0, }}) - + + if config.get(section, 'MODE') == 'XLXPEER': + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), + 'ENABLED': config.getboolean(section, 'ENABLED'), + 'LOOSE': config.getboolean(section, 'LOOSE'), + 'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')), + 'IP': gethostbyname(config.get(section, 'IP')), + 'PORT': config.getint(section, 'PORT'), + 'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')), + 'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')), + 'MASTER_PORT': config.getint(section, 'MASTER_PORT'), + 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), + 'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'), + 'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'), + 'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'), + 'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'), + 'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'), + 'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'), + 'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'), + 'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'), + 'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'), + 'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'), + 'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'), + 'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'), + 'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'), + 'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'), + 'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'), + 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), + 'XLXMODULE': config.getint(section, 'XLXMODULE'), + 'OPTIONS': '', + 'USE_ACL': config.getboolean(section, 'USE_ACL'), + 'SUB_ACL': config.get(section, 'SUB_ACL'), + 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), + 'TG2_ACL': config.get(section, 'TGID_TS2_ACL') + }}) + CONFIG['SYSTEMS'][section].update({'XLXSTATS': { + 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES + 'CONNECTED': None, + 'PINGS_SENT': 0, + 'PINGS_ACKD': 0, + 'NUM_OUTSTANDING': 0, + 'PING_OUTSTANDING': False, + 'LAST_PING_TX_TIME': 0, + 'LAST_PING_ACK_TIME': 0, + }}) + elif config.get(section, 'MODE') == 'MASTER': CONFIG['SYSTEMS'].update({section: { 'MODE': config.get(section, 'MODE'), diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index 6611016..a743373 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -207,3 +207,35 @@ USE_ACL: True SUB_ACL: DENY:1 TGID_TS1_ACL: PERMIT:ALL TGID_TS2_ACL: PERMIT:ALL + +[XLX-1] +MODE: XLXPEER +ENABLED: True +LOOSE: False +EXPORT_AMBE: False +IP: +PORT: 54002 +MASTER_IP: 172.16.1.1 +MASTER_PORT: 62030 +PASSPHRASE: passw0rd +CALLSIGN: W1ABC +RADIO_ID: 312000 +RX_FREQ: 449000000 +TX_FREQ: 444000000 +TX_POWER: 25 +COLORCODE: 1 +SLOTS: 1 +LATITUDE: 38.0000 +LONGITUDE: -095.0000 +HEIGHT: 75 +LOCATION: Anywhere, USA +DESCRIPTION: This is a cool repeater +URL: www.w1abc.org +SOFTWARE_ID: 20170620 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +XLXMODULE: 4004 +USE_ACL: True +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL diff --git a/hblink.py b/hblink.py index 2693981..9a61fdd 100755 --- a/hblink.py +++ b/hblink.py @@ -224,6 +224,13 @@ class HBSYSTEM(DatagramProtocol): self.datagramReceived = self.peer_datagramReceived self.dereg = self.peer_dereg + elif self._config['MODE'] == 'XLXPEER': + self._stats = self._config['XLXSTATS'] + self.send_system = self.send_master + self.maintenance_loop = self.peer_maintenance_loop + self.datagramReceived = self.peer_datagramReceived + self.dereg = self.peer_dereg + def startProtocol(self): # Set up periodic loop for tracking pings from peers. Run every 'PING_TIME' seconds self._system_maintenance = task.LoopingCall(self.maintenance_loop) @@ -283,6 +290,29 @@ class HBSYSTEM(DatagramProtocol): # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! # logger.debug('(%s) TX Packet to %s:%s -- %s', self._system, self._config['MASTER_IP'], self._config['MASTER_PORT'], ahex(_packet)) + def send_xlxmaster(self, radio, xlx, mastersock): + radio3 = int.from_bytes(radio, 'big').to_bytes(3, 'big') + radio4 = int.from_bytes(radio, 'big').to_bytes(4, 'big') + xlx3 = xlx.to_bytes(3, 'big') + streamid = randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big')+randint(0,255).to_bytes(1, 'big') + # Wait for .5 secs for the XLX to log us in + for packetnr in range(5): + if packetnr < 3: + # First 3 packets, voice start, stream type e1 + strmtype = 225 + payload = bytearray.fromhex('4f2e00b501ae3a001c40a0c1cc7dff57d75df5d5065026f82880bd616f13f185890000') + else: + # Last 2 packets, voice end, stream type e2 + strmtype = 226 + payload = bytearray.fromhex('4f410061011e3a781c30a061ccbdff57d75df5d2534425c02fe0b1216713e885ba0000') + packetnr1 = packetnr.to_bytes(1, 'big') + strmtype1 = strmtype.to_bytes(1, 'big') + _packet = b''.join([DMRD, packetnr1, radio3, xlx3, radio4, strmtype1, streamid, payload]) + self.transport.write(_packet, mastersock) + # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! + #logger.debug('(%s) XLX Module Change Packet: %s', self._system, ahex(_packet)) + return + def dmrd_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data): pass @@ -631,6 +661,11 @@ class HBSYSTEM(DatagramProtocol): self._stats['CONNECTION'] = 'YES' self._stats['CONNECTED'] = time() logger.info('(%s) Connection to Master Completed', self._system) + # If we are an XLX, send the XLX module request here. + if self._config['MODE'] == 'XLXPEER': + self.send_xlxmaster(self._config['RADIO_ID'], int(4000), self._config['MASTER_SOCKADDR']) + self.send_xlxmaster(self._config['RADIO_ID'], self._config['XLXMODULE'], self._config['MASTER_SOCKADDR']) + logger.info('(%s) Sending XLX Module request', self._system) else: self._stats['CONNECTION'] = 'NO' logger.error('(%s) Master ACK Contained wrong ID - Connection Reset', self._system) diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..c47b9db --- /dev/null +++ b/install.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +# Install the required support programs +apt-get install python3 python3-pip -y +pip3 install -r requirements.txt + diff --git a/requirements.txt b/requirements.txt index 7bb55eb..3d17f35 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -python>=3.5.0 bitstring>=3.1.5 bitarray>=0.8.1 Twisted>=16.3.0 dmr_utils3>=0.1.19 +configparser>=3.0.0 diff --git a/rules_SAMPLE.py b/rules_SAMPLE.py index 3781e57..a3c3701 100755 --- a/rules_SAMPLE.py +++ b/rules_SAMPLE.py @@ -15,7 +15,9 @@ configuration file. * SYSTEM - The name of the sytem as listed in the main hblink configuration file (e.g. hblink.cfg) This MUST be the exact same name as in the main config file!!! * TS - Timeslot used for matching traffic to this confernce bridge + XLX connections should *ALWAYS* use TS 2 only. * TGID - Talkgroup ID used for matching traffic to this conference bridge + XLX connections should *ALWAYS* use TG 9 only. * ON and OFF are LISTS of Talkgroup IDs used to trigger this system off and on. Even if you only want one (as shown in the ON example), it has to be in list format. None can be handled with an empty list, such as " 'ON': [] ".