mirror of
https://github.com/ShaYmez/hblink3.git
synced 2024-11-24 00:38:45 -05:00
commit
23eb56666e
32
Dockerfile
Normal file
32
Dockerfile
Normal file
@ -0,0 +1,32 @@
|
||||
FROM python:3.7-slim-stretch
|
||||
|
||||
RUN apt update && \
|
||||
apt install -y git && \
|
||||
cd /usr/src/ && \
|
||||
git clone https://github.com/n0mjs710/dmr_utils3 && \
|
||||
cd /usr/src/dmr_utils3 && \
|
||||
./install.sh && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
cd /opt && \
|
||||
rm -rf /usr/src/dmr_utils3 && \
|
||||
git clone https://github.com/n0mjs710/hblink3
|
||||
|
||||
RUN cd /opt/hblink3/ && \
|
||||
sed -i s/.*python.*//g requirements.txt && \
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
|
||||
ADD entrypoint /entrypoint
|
||||
|
||||
RUN adduser -u 54000 radio && \
|
||||
adduser radio radio && \
|
||||
chmod 755 /entrypoint && \
|
||||
chown radio:radio /entrypoint && \
|
||||
chown radio /opt/hblink3
|
||||
|
||||
RUN chmod 755 /entrypoint
|
||||
|
||||
USER radio
|
||||
EXPOSE 54000
|
||||
|
||||
ENTRYPOINT [ "/entrypoint" ]
|
30
README.md
30
README.md
@ -11,7 +11,7 @@ DVSwitch@groups.io
|
||||
|
||||
**UPDATES:**
|
||||
|
||||
**PURPOSE:** Thanks to the work of Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT we have an open protocol for internetworking DMR repeaters. Unfortunately, there's no generic client and/or master stacks. This project is to build an open-source, python-based implementation. This is a non-commercial license. Atribution is *required* if you use it.
|
||||
**PURPOSE:** Thanks to the work of Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT we have an open protocol for internetworking DMR repeaters. Unfortunately, there's no generic client and/or master stacks. This project is to build an open-source, python-based implementation. You are free to use this software however you want, however we ask that you provide attribution in some public venue (such as project, club, organization web site). This helps us see where the software is in use and track how it is used.
|
||||
|
||||
For those who will ask: This is a piece of software that implements an open-source, amateur radio networking protocol. It is not a network. It is not indended to be a network. It is not intended to replace or circumvent a network. People do those things, code doesn't.
|
||||
|
||||
@ -24,11 +24,37 @@ None. The owners of this work make absolutely no warranty, express or implied. U
|
||||
**PRE-REQUISITE KNOWLEDGE:**
|
||||
This document assumes the reader is familiar with Linux/UNIX, the Python programming language and DMR.
|
||||
|
||||
**Using docker version**
|
||||
|
||||
To work with provided docker setup you will need:
|
||||
* A private repository with your configuration files (all .cfg files in repo will be copyed to the application root directory on start up)
|
||||
* A service user able to read your private repository (or be brave and publish your configuration, or be really brave and give your username and password to the docker)
|
||||
* A server with docker installed
|
||||
* Follow this simple steps:
|
||||
|
||||
Build your own image from source
|
||||
|
||||
```bash
|
||||
|
||||
docker build . -t millaguie/hblink:3.0.0
|
||||
|
||||
```
|
||||
|
||||
Or user a prebuilt one in docker hub: millaguie/hblink:3.0.0
|
||||
|
||||
Wake up your container
|
||||
|
||||
```bash
|
||||
touch /var/log/hblink.log
|
||||
chown 65000 /var/log/hblink.log
|
||||
run -v /var/log/hblink.log:/var/log/hblink.log -e GIT_USER=$USER -e GIT_PASSWORD=$PASSWORD -e GIT_REPO=$URL_TO_REPO_WITHOUT_HTTPS:// -p 54000:54000 millaguie/hblink:3.0.0
|
||||
```
|
||||
|
||||
**MORE DOCUMENTATION TO COME**
|
||||
|
||||
***0x49 DE N0MJS***
|
||||
|
||||
Copyright (C) 2016-2017 Cortney T. Buffington, N0MJS n0mjs@me.com
|
||||
Copyright (C) 2016-2019 Cortney T. Buffington, N0MJS n0mjs@me.com
|
||||
|
||||
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.
|
||||
|
||||
|
48
config.py
48
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'),
|
||||
|
2
const.py
2
const.py
@ -52,7 +52,7 @@ HBPF_SLT_VTERM = 0x2
|
||||
# HomeBrew Protocol Commands
|
||||
DMRD = b'DMRD'
|
||||
MSTCL = b'MSTCL'
|
||||
MSTNAK = b'MSTNAC'
|
||||
MSTNAK = b'MSTNAK'
|
||||
MSTPONG = b'MSTPONG'
|
||||
MSTN = b'MSTN'
|
||||
MSTP = b'MSTP'
|
||||
|
10
entrypoint
Normal file
10
entrypoint
Normal file
@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p /var/tmp/config
|
||||
cd /var/tmp/config
|
||||
git clone https://${GIT_USER}:${GIT_PASSWORD}@${GIT_REPO}
|
||||
|
||||
DIR=$(echo ${GIT_REPO}| sed s/.git$//g | sed s#^.*/##g)
|
||||
|
||||
cp -a /var/tmp/config/${DIR}/*cfg /opt/hblink3/
|
||||
python /opt/hblink3/hblink.py
|
@ -101,8 +101,8 @@ PATH: ./
|
||||
PEER_FILE: peer_ids.json
|
||||
SUBSCRIBER_FILE: subscriber_ids.json
|
||||
TGID_FILE: talkgroup_ids.json
|
||||
PEER_URL: https://www.radioid.net/api/dmr/repeater/?country=united%%20states
|
||||
SUBSCRIBER_URL: https://www.radioid.net/api/dmr/user/?country=united%%20states
|
||||
PEER_URL: https://www.radioid.net/static/rptrs.json
|
||||
SUBSCRIBER_URL: https://www.radioid.net/static/users.json
|
||||
STALE_DAYS: 7
|
||||
|
||||
# OPENBRIDGE INSTANCES - DUPLICATE SECTION FOR MULTIPLE CONNECTIONS
|
||||
@ -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: True
|
||||
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
|
||||
|
35
hblink.py
35
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)
|
||||
|
6
install.sh
Executable file
6
install.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#! /bin/bash
|
||||
|
||||
# Install the required support programs
|
||||
apt-get install python3 python3-pip -y
|
||||
pip3 install -r 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
|
||||
|
@ -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': [] ".
|
||||
|
Loading…
Reference in New Issue
Block a user