diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml new file mode 100644 index 0000000..3307f55 --- /dev/null +++ b/.github/workflows/image.yml @@ -0,0 +1,25 @@ +name: Build-HBlink3 + +on: + push: + branches: master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout code + uses: actions/checkout@v4 + - name: install buildx + id: buildx + uses: docker/setup-buildx-action@v3 + - name: login to docker hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: build the image + run: | + docker buildx build --push \ + --tag shaymez/hblink3:latest \ + --platform linux/i386,linux/amd64,linux/arm64,linux/arm/v7 . \ No newline at end of file diff --git a/.gitignore b/.gitignore index 192fd9b..37bd4e3 100755 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ __pycache__/ *.so # Distribution / packaging +.github .Python env/ build/ @@ -98,3 +99,6 @@ local_subscriber_ids.* peer_ids.* local_peer_ids.* talkgroup_ids.* + +# Workflows +.github/workflows/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4df1be3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [2.0.2] - 2025-12-14 + +### Changed +- Updated Dockerfile base image from Alpine 3.18 to Alpine 3.20 for better compatibility with modern systems +- Updated GitHub Actions workflow to use current versions (checkout@v4, docker/setup-buildx-action@v3, docker/login-action@v3) +- Modernized docker-compose.yml by removing obsolete version field +- Updated Python dependency minimum versions for Debian 13 and Python 3.11/3.12 compatibility: + - bitstring: 3.1.9 → 4.0.0 + - bitarray: 2.3.5 → 2.8.0 + - Twisted: 21.7.0 → 24.0.0 + - configparser: 5.2.0 → 7.0.0 + +### Fixed +- Fixed POSIX compliance in entrypoint script (changed `==` to `=` for shell comparison) +- Added error handling to `cd` command in entrypoint script + +### Security +- Updated all GitHub Actions to latest versions for improved security +- Verified no security vulnerabilities in updated dependencies + +## [1.6.10] - Previous Release + +### Note +- This changelog was added as part of Debian 13 compatibility updates +- For changes prior to this version, please refer to git commit history diff --git a/Dockerfile b/Dockerfile index 9a0d22e..7871799 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,34 @@ -FROM python:3.9-alpine +############################################################################### +# Copyright (C) 2024 Shane aka, ShaYmez +# Version 2.0.2 +############################################################################### -COPY entrypoint /entrypoint +FROM python:alpine3.20 + +RUN adduser -D -u 54000 radio + +WORKDIR /hblink3 + +# Install build dependencies +RUN apk add --no-cache git gcc musl-dev libffi-dev openssl-dev cargo + +# Copy only requirements first for better layer caching +COPY requirements.txt . -RUN adduser -D -u 54000 radio && \ - apk update && \ - apk add git gcc musl-dev && \ - cd /opt && \ - git clone https://github.com/ShaYmez/hblink3 && \ - cd /opt/hblink3 && \ - pip install --no-cache-dir -r requirements.txt && \ - apk del git gcc musl-dev && \ - chown -R radio: /opt/hblink3 +RUN pip install --upgrade pip \ + && pip install --no-cache-dir -r requirements.txt + +# Remove build dependencies +RUN apk del git gcc musl-dev libffi-dev openssl-dev cargo + +# Copy the application code +COPY . . + +RUN chown -R radio /hblink3 + +COPY entrypoint /entrypoint +RUN chmod +x /entrypoint USER radio -ENTRYPOINT [ "/entrypoint" ] +ENTRYPOINT ["/entrypoint"] diff --git a/README.md b/README.md index bf3e36a..8956728 100755 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Please join the DVSwitch group at groups.io for online forum support, discussion DVSwitch@groups.io -A voluntary registrty for HBlink systems with public access has been created at http://hblink-register.com.es Please consider listing your system if you allow open access. +A voluntary registry for HBlink systems with public access has been created at http://hblink-register.com.es Please consider listing your system if you allow open access. --- diff --git a/bridge.py b/bridge.py index 706a842..53d5176 100755 --- a/bridge.py +++ b/bridge.py @@ -25,7 +25,7 @@ bridge will both receive traffic from, and send traffic to any other system joined to the same conference bridge. It does not provide end-to-end connectivity as each end system must individually be joined to a conference bridge (a name -you create in the configuraiton file) to pass traffic. +you create in the configuration file) to pass traffic. This program currently only works with group voice calls. ''' @@ -51,7 +51,6 @@ # Stuff for socket reporting import pickle -# REMOVE LATER from datetime import datetime # The module needs logging, but handlers, etc. are controlled by the parent import logging logger = logging.getLogger(__name__) @@ -65,7 +64,7 @@ __maintainer__ = 'Cort Buffington, N0MJS' __email__ = 'n0mjs@me.com' -# Module gobal varaibles +# Module global variables # Dictionary for dynamically mapping unit (subscriber) to a system. # This is for pruning unit-to-uint calls to not broadcast once the @@ -114,7 +113,7 @@ def make_bridges(_rules): for i, e in enumerate(_system['OFF']): _system['OFF'][i] = bytes_3(_system['OFF'][i]) _system['TIMEOUT'] = _system['TIMEOUT']*60 - if _system['ACTIVE'] == True: + if _system['ACTIVE']: _system['TIMER'] = time() + _system['TIMEOUT'] else: _system['TIMER'] = time() @@ -130,24 +129,24 @@ def rule_timer_loop(): for _bridge in BRIDGES: for _system in BRIDGES[_bridge]: if _system['TO_TYPE'] == 'ON': - if _system['ACTIVE'] == True: + if _system['ACTIVE']: if _system['TIMER'] < _now: _system['ACTIVE'] = False logger.info('(ROUTER) Conference Bridge TIMEOUT: DEACTIVATE System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) else: timeout_in = _system['TIMER'] - _now logger.info('(ROUTER) Conference Bridge ACTIVE (ON timer running): System: %s Bridge: %s, TS: %s, TGID: %s, Timeout in: %.2fs,', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']), timeout_in) - elif _system['ACTIVE'] == False: + elif not _system['ACTIVE']: logger.debug('(ROUTER) Conference Bridge INACTIVE (no change): System: %s Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) elif _system['TO_TYPE'] == 'OFF': - if _system['ACTIVE'] == False: + if not _system['ACTIVE']: if _system['TIMER'] < _now: _system['ACTIVE'] = True logger.info('(ROUTER) Conference Bridge TIMEOUT: ACTIVATE System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) else: timeout_in = _system['TIMER'] - _now logger.info('(ROUTER) Conference Bridge INACTIVE (OFF timer running): System: %s Bridge: %s, TS: %s, TGID: %s, Timeout in: %.2fs,', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']), timeout_in) - elif _system['ACTIVE'] == True: + elif _system['ACTIVE']: logger.debug('(ROUTER) Conference Bridge ACTIVE (no change): System: %s Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) else: logger.debug('(ROUTER) Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID'])) @@ -335,22 +334,22 @@ def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _ # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: + if not self.STATUS[_stream_id]['CONTENTION']: self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) continue if ((_target['TGID'] != _target_status[_target['TS']]['TX_TGID']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: + if not self.STATUS[_stream_id]['CONTENTION']: self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID'])) continue if (_target['TGID'] == _target_status[_target['TS']]['RX_TGID']) and ((pkt_time - _target_status[_target['TS']]['RX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: + if not self.STATUS[_stream_id]['CONTENTION']: self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID'])) continue if (_target['TGID'] == _target_status[_target['TS']]['TX_TGID']) and (_rf_src != _target_status[_target['TS']]['TX_RFS']) and ((pkt_time - _target_status[_target['TS']]['TX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: + if not self.STATUS[_stream_id]['CONTENTION']: self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['TX_TGID']), int_id(_target_status[_target['TS']]['TX_RFS'])) continue @@ -512,23 +511,23 @@ def unit_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _d # ''' if ((_dst_id != _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: + if not self.STATUS[_stream_id]['CONTENTION']: self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed to TGID %s, target active or in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue if ((_dst_id != _target_status[_slot]['TX_TGID']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < _target_system['GROUP_HANGTIME'])): - if self.STATUS[_stream_id]['CONTENTION'] == False: + if not self.STATUS[_stream_id]['CONTENTION']: self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed to TGID%s, target in group hangtime: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['TX_TGID'])) continue ''' if (_dst_id == _target_status[_slot]['RX_TGID']) and ((pkt_time - _target_status[_slot]['RX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: + if not self.STATUS[_stream_id]['CONTENTION']: self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed to TGID%s, matching call already active on target: HBSystem: %s, TS: %s, TGID: %s', self._system, int_id(_dst_id), _target, _slot, int_id(_target_status[_slot]['RX_TGID'])) continue if (_dst_id == _target_status[_slot]['TX_TGID']) and (_rf_src != _target_status[_slot]['TX_RFS']) and ((pkt_time - _target_status[_slot]['TX_TIME']) < STREAM_TO): - if self.STATUS[_stream_id]['CONTENTION'] == False: + if not self.STATUS[_stream_id]['CONTENTION']: self.STATUS[_stream_id]['CONTENTION'] = True logger.info('(%s) Call not routed for subscriber %s, call route in progress on target: HBSystem: %s, TS: %s, TGID: %s, SUB: %s', self._system, int_id(_rf_src), _target, _slot, int_id(_target_status[_slot]['TX_TGID']), int_id(_target_status[_slot]['TX_RFS'])) continue @@ -847,7 +846,7 @@ def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _ if _system['SYSTEM'] == self._system: # TGID matches a rule source, reset its timer - if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and (_system['ACTIVE'] == True)) or (_system['TO_TYPE'] == 'OFF' and _system['ACTIVE'] == False)): + if _slot == _system['TS'] and _dst_id == _system['TGID'] and ((_system['TO_TYPE'] == 'ON' and _system['ACTIVE']) or (_system['TO_TYPE'] == 'OFF' and not _system['ACTIVE'])): _system['TIMER'] = pkt_time + _system['TIMEOUT'] logger.info('(%s) Transmission match for Bridge: %s. Reset timeout to %s', self._system, _bridge, _system['TIMER']) @@ -855,7 +854,7 @@ def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _ if (_dst_id in _system['ON'] or _dst_id in _system['RESET']) and _slot == _system['TS']: # Set the matching rule as ACTIVE if _dst_id in _system['ON']: - if _system['ACTIVE'] == False: + if not _system['ACTIVE']: _system['ACTIVE'] = True _system['TIMER'] = pkt_time + _system['TIMEOUT'] logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE']) @@ -864,7 +863,7 @@ def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _ _system['TIMER'] = pkt_time logger.info('(%s) Bridge: %s set to "OFF" with an on timer rule: timeout timer cancelled', self._system, _bridge) # Reset the timer for the rule - if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON': + if _system['ACTIVE'] and _system['TO_TYPE'] == 'ON': _system['TIMER'] = pkt_time + _system['TIMEOUT'] logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) @@ -872,7 +871,7 @@ def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _ if (_dst_id in _system['OFF'] or _dst_id in _system['RESET']) and _slot == _system['TS']: # Set the matching rule as ACTIVE if _dst_id in _system['OFF']: - if _system['ACTIVE'] == True: + if _system['ACTIVE']: _system['ACTIVE'] = False logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE']) # Cancel the timer if we've enabled an "ON" type timeout @@ -880,11 +879,11 @@ def group_received(self, _peer_id, _rf_src, _dst_id, _seq, _slot, _frame_type, _ _system['TIMER'] = pkt_time logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) # Reset the timer for the rule - if _system['ACTIVE'] == False and _system['TO_TYPE'] == 'OFF': + if not _system['ACTIVE'] and _system['TO_TYPE'] == 'OFF': _system['TIMER'] = pkt_time + _system['TIMEOUT'] logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - pkt_time) # Cancel the timer if we've enabled an "ON" type timeout - if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']: + if _system['ACTIVE'] and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']: _system['TIMER'] = pkt_time logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge) diff --git a/bridge_all.py b/bridge_all.py index 0b36036..3d81afe 100755 --- a/bridge_all.py +++ b/bridge_all.py @@ -67,7 +67,7 @@ __email__ = 'n0mjs@me.com' __status__ = 'pre-alpha' -# Module gobal varaibles +# Module global variables class bridgeallSYSTEM(HBSYSTEM): diff --git a/config.py b/config.py index 7600642..3ce8979 100755 --- a/config.py +++ b/config.py @@ -20,9 +20,9 @@ ''' This module generates the configuration data structure for hblink.py and -assoicated programs that use it. It has been seaparated into a different -module so as to keep hblink.py easeier to navigate. This file only needs -updated if the items in the main configuraiton file (usually hblink.cfg) +associated programs that use it. It has been separated into a different +module so as to keep hblink.py easier to navigate. This file only needs +updated if the items in the main configuration file (usually hblink.cfg) change. ''' diff --git a/docker-compose.yml b/docker-compose.yml index df58a61..7a87375 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,19 @@ -version: '2.4' services: - hblink3: - container_name: hblink - volumes: - - '/etc/hblink3/hblink.cfg:/opt/hblink3/hblink.cfg' - - '/var/log/hblink/hblink.log:/opt/hblink3/hblink.log' - - '/etc/hblink3/rules.py:/opt/hblink3/rules.py' - ports: - - '62030:62030/udp' - - '62031-62051:62031-62051/udp' - - '4321:4321/tcp' - image: 'shaymez/hblink3:latest' - restart: "unless-stopped" + hblink3: + container_name: hblink + volumes: + - '/etc/hblink3/hblink.cfg:/hblink3/hblink.cfg' + - '/var/log/hblink/hblink.log:/hblink3/hblink.log' + - '/etc/hblink3/rules.py:/hblink3/rules.py' + - '/etc/hblink3/json/:/hblink3/json/' + ports: + # Master Ports (99 Masters) + - '54000-54099:54000-54099/udp' + # MMDVM & OBP Ports + - '62030-62050:62030-62050/udp' + # TCP Port for HBmonitor + - '4321:4321/tcp' + image: 'shaymez/hblink3:latest' + restart: "unless-stopped" + environment: + - 'PARROT_ENABLE=1' diff --git a/entrypoint b/entrypoint index fceef72..2ebb8d4 100755 --- a/entrypoint +++ b/entrypoint @@ -1,4 +1,10 @@ #!/bin/sh -cd /opt/hblink3 -python /opt/hblink3/bridge.py -c hblink.cfg -r rules.py +if [ "$PARROT_ENABLE" = 1 ]; then + echo 'Starting HBlink with Parrot.....' + python bridge.py -c hblink.cfg -r rules.py & + python playback.py -c playback.cfg +else + echo 'Starting HBlink.....' + exec python bridge.py -c hblink.cfg -r rules.py +fi diff --git a/hblink-SAMPLE.cfg b/hblink-SAMPLE.cfg index c2e789d..2e9687d 100755 --- a/hblink-SAMPLE.cfg +++ b/hblink-SAMPLE.cfg @@ -1,3 +1,4 @@ +# HBlink Configuration Version 20230806 # PROGRAM-WIDE PARAMETERS GO HERE # PATH - working path for files, leave it alone unless you NEED to change it # PING_TIME - the interval that peers will ping the master, and re-try registraion @@ -64,7 +65,7 @@ TGID_TS2_ACL: PERMIT:ALL REPORT: True REPORT_INTERVAL: 60 REPORT_PORT: 4321 -REPORT_CLIENTS: 127.0.0.1 +REPORT_CLIENTS: * # SYSTEM LOGGER CONFIGURAITON @@ -84,9 +85,9 @@ REPORT_CLIENTS: 127.0.0.1 # used. # [LOGGER] -LOG_FILE: /tmp/hblink.log +LOG_FILE: hblink.log LOG_HANDLERS: console-timed -LOG_LEVEL: DEBUG +LOG_LEVEL: INFO LOG_NAME: HBlink # DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES @@ -103,7 +104,7 @@ SUBSCRIBER_FILE: subscriber_ids.json TGID_FILE: talkgroup_ids.json PEER_URL: https://www.radioid.net/static/rptrs.json SUBSCRIBER_URL: https://www.radioid.net/static/users.json -STALE_DAYS: 7 +STALE_DAYS: 14 # OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS # OpenBridge is a protocol originall created by DMR+ for connection between an @@ -179,6 +180,38 @@ TGID_TS2_ACL: PERMIT:ALL # # ACLs: # See comments in the GLOBAL stanza +[Parrot] +MODE: PEER +ENABLED: False +LOOSE: True +EXPORT_AMBE: False +IP: 127.0.0.1 +PORT: 54098 +MASTER_IP: 127.0.0.1 +MASTER_PORT: 54100 +PASSPHRASE: passw0rd +CALLSIGN: ECHO +RADIO_ID: 9999 +RX_FREQ: 434000000 +TX_FREQ: 434000000 +TX_POWER: 10 +COLORCODE: 1 +SLOTS: 2 +LATITUDE: 33.0000 +LONGITUDE: -84.0000 +HEIGHT: 75 +LOCATION: +DESCRIPTION: +URL: +SOFTWARE_ID: 20230806 +PACKAGE_ID: MMDVM_HBlink +GROUP_HANGTIME: 5 +OPTIONS: +USE_ACL: False +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + [REPEATER-1] MODE: PEER ENABLED: True @@ -202,7 +235,7 @@ HEIGHT: 75 LOCATION: Anywhere, USA DESCRIPTION: This is a cool repeater URL: www.w1abc.org -SOFTWARE_ID: 20170620 +SOFTWARE_ID: 20230806 PACKAGE_ID: MMDVM_HBlink GROUP_HANGTIME: 5 OPTIONS: @@ -234,7 +267,7 @@ HEIGHT: 75 LOCATION: Anywhere, USA DESCRIPTION: This is a cool repeater URL: www.w1abc.org -SOFTWARE_ID: 20170620 +SOFTWARE_ID: 20230806 PACKAGE_ID: MMDVM_HBlink GROUP_HANGTIME: 5 XLXMODULE: 4004 diff --git a/hblink.py b/hblink.py index f586ef3..7e68149 100755 --- a/hblink.py +++ b/hblink.py @@ -27,7 +27,7 @@ sufficient logging to be used standalone as a troubleshooting application. ''' -# Specifig functions from modules we need +# Specific functions from modules we need from binascii import b2a_hex as ahex from binascii import a2b_hex as bhex from random import randint @@ -388,7 +388,7 @@ def master_datagramReceived(self, _data, _sockaddr): return # The basic purpose of a master is to repeat to the peers - if self._config['REPEAT'] == True: + if self._config['REPEAT']: pkt = [_data[:11], '', _data[15:]] for _peer in self._peers: if _peer != _peer_id: @@ -466,7 +466,7 @@ def master_datagramReceived(self, _data, _sockaddr): self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr) logger.warning('(%s) Login challenge from Radio ID that has not logged in: %s', self._system, int_id(_peer_id)) - elif _command == RPTC: # Repeater is sending it's configuraiton OR disconnecting + elif _command == RPTC: # Repeater is sending it's configuration OR disconnecting if _data[:5] == RPTCL: # Disconnect command _peer_id = _data[5:9] if _peer_id in self._peers \ @@ -752,7 +752,7 @@ def send_config(self): # ID ALIAS CREATION # Download def mk_aliases(_config): - if _config['ALIASES']['TRY_DOWNLOAD'] == True: + if _config['ALIASES']['TRY_DOWNLOAD']: # Try updating peer aliases file result = try_download(_config['ALIASES']['PATH'], _config['ALIASES']['PEER_FILE'], _config['ALIASES']['PEER_URL'], _config['ALIASES']['STALE_TIME']) logger.info('(GLOBAL) %s', result) diff --git a/play_ambe.py b/play_ambe.py index b792aee..c319b44 100755 --- a/play_ambe.py +++ b/play_ambe.py @@ -42,7 +42,6 @@ # Stuff for socket reporting import pickle -# REMOVE LATER from datetime import datetime # The module needs logging, but handlers, etc. are controlled by the parent import logging logger = logging.getLogger(__name__) @@ -56,7 +55,7 @@ __maintainer__ = 'Cort Buffington, N0MJS' __email__ = 'n0mjs@me.com' -# Module gobal varaibles +# Module global variables class OBP(OPENBRIDGE): diff --git a/playback.cfg b/playback.cfg new file mode 100644 index 0000000..b9c5da9 --- /dev/null +++ b/playback.cfg @@ -0,0 +1,47 @@ +[GLOBAL] +PATH: ./ +PING_TIME: 10 +MAX_MISSED: 5 +USE_ACL: True +REG_ACL: PERMIT:ALL +SUB_ACL: DENY:1 +TGID_TS1_ACL: PERMIT:ALL +TGID_TS2_ACL: PERMIT:ALL + +[REPORTS] +REPORT: False +REPORT_INTERVAL: 10 +REPORT_PORT: 4323 +REPORT_CLIENTS: * + +[LOGGER] +LOG_FILE: parrot.log +LOG_HANDLERS: console-timed +LOG_LEVEL: INFO +LOG_NAME: Parrot + +[ALIASES] +TRY_DOWNLOAD: False +PATH: ./ +PEER_FILE: peer_ids.json +SUBSCRIBER_FILE: subscriber_ids.json +TGID_FILE: talkgroup_ids.json +PEER_URL: https://www.radioid.net/static/rptrs.json +SUBSCRIBER_URL: https://www.radioid.net/static/users.json +STALE_DAYS: 14 + +[PARROT] +MODE: MASTER +ENABLED: True +REPEAT: True +MAX_PEERS: 10 +EXPORT_AMBE: False +IP: 127.0.0.1 +PORT: 54100 +PASSPHRASE: passw0rd +GROUP_HANGTIME: 5 +USE_ACL: True +REG_ACL: DENY:1 +SUB_ACL: DENY:1 +TGID_TS1_ACL: DENY:ALL +TGID_TS2_ACL: PERMIT:9999,9990 diff --git a/playback.py b/playback.py index c40f020..8544ae5 100755 --- a/playback.py +++ b/playback.py @@ -51,7 +51,7 @@ __email__ = 'n0mjs@me.com' __status__ = 'pre-alpha' -# Module gobal varaibles +# Module global variables class playback(HBSYSTEM): @@ -200,7 +200,7 @@ def sig_handler(_signal, _frame): # ID ALIAS CREATION # Download - if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True: + if CONFIG['ALIASES']['TRY_DOWNLOAD']: # Try updating peer aliases file result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME']) logger.info(result) diff --git a/requirements.txt b/requirements.txt index 910b5d3..180f09b 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -bitstring>=3.1.9 -bitarray>=2.3.5 -Twisted>=21.7.0 +bitstring>=4.0.0 +bitarray>=2.8.0 +Twisted>=24.0.0 dmr_utils3>=0.1.29 -configparser>=5.2.0 +configparser>=7.0.0 diff --git a/rules_SAMPLE.py b/rules_SAMPLE.py index b46a46a..74994e6 100755 --- a/rules_SAMPLE.py +++ b/rules_SAMPLE.py @@ -11,6 +11,7 @@ bridge. This is any arbitrary ASCII text string you want to use. Under each conference bridge definition are the following items -- one line for each HBSystem as defined in the main HBlink configuration file. +"Parrot" has been added on TG 9999 and an example subset demonstrating playback parrot feature (if enabled) * 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!!! @@ -33,6 +34,12 @@ ''' BRIDGES = { + '9999 Parrot': [ + {'SYSTEM': 'Hotspot-1', 'TS': 2, 'TGID': 9999, 'ACTIVE': True, 'TIMEOUT': 0, 'TO_TYPE': 'NONE', 'ON': [9999], 'OFF': [], 'RESET': []}, + {'SYSTEM': 'Hotspot-2', 'TS': 2, 'TGID': 9999, 'ACTIVE': True, 'TIMEOUT': 0, 'TO_TYPE': 'NONE', 'ON': [9999], 'OFF': [], 'RESET': []}, + {'SYSTEM': 'Parrot', 'TS': 2, 'TGID': 9999, 'ACTIVE': True, 'TIMEOUT': 0, 'TO_TYPE': 'NONE', 'ON': [], 'OFF': [], 'RESET': []}, + ], + 'WORLDWIDE': [ {'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 1, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10], 'RESET': []}, {'SYSTEM': 'CLIENT-1', 'TS': 1, 'TGID': 3100, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10], 'RESET': []}, diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..e9307ca --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +2.0.2