Skip to content

Commit

Permalink
saving work
Browse files Browse the repository at this point in the history
  • Loading branch information
smarek committed Aug 13, 2023
1 parent c7f4112 commit 5299068
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 125 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ $ hytera-homebrew-bridge <path to settings.ini> <optionally path to logging.ini>
$ git clone https://github.com/OK-DMR/Hytera_Homebrew_Bridge.git
# change path into cloned repo
$ cd Hytera_Homebrew_Bridge
# You can use either settings.ini.default (all configuration params) or settings.ini.minimal.default (only required params)
$ cp settings.ini.default settings.ini
# You can use either settings.default.ini (all configuration params) or settings.minimal.default.ini (only required params)
$ cp settings.default.ini settings.ini
# install current directory to local site-packages in editable mode
$ python3 -m pip install -e .
# run hytera-homebrew-bridge with params
Expand All @@ -53,6 +53,9 @@ $ hytera-homebrew-bridge <path to settings.ini> <optionally path to logging.ini>
- A: Check if SNMP port is set to 161 in `Conventional > General Settings > Network` section `SNMP` at the bottom
- Q: I'm not getting the upstream connection and/or I'm seeing a lot of logs similar to "MMDVMProtocol - Sending Login Request"
- A: This is usually misconfiguration of Hytera repeater, if you do not see any logs with 'RDAC' or the long packet with 'REPEATER SNMP CONFIGURATION' info. In such cases you should check if the Hytera repeater is programmed correctly as slave and the IP/ports do match the HHB startup log saying 'Hytera Repeater is expected to connect at xxx.xxx.xxx.xxx'
- Q: where is hytera-homebrew-bridge.py launcher script?
- A: It was replaced by script installed by python environment, now you can use just `hytera-homebrew-bridge` command instead
- A: You can use `python -m okdmr.hhb.hytera_homebrew_bridge <settings.ini> <optionally logging.ini>` as an alternative command

----

Expand Down
75 changes: 75 additions & 0 deletions okdmr/hhb/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import asyncio
import importlib.util
import logging.config
import os
import sys
from signal import SIGINT, SIGTERM

from okdmr.hhb.hytera_homebrew_bridge import HyteraHomebrewBridge


def main():
logger_configured: bool = False
if len(sys.argv) > 2:
if os.path.isfile(path=sys.argv[2]):
logging.config.fileConfig(fname=sys.argv[2])
logger_configured = True
else:
logging.getLogger().error(f"logging ini file not valid {sys.argv[2]}")
exit()
if not logger_configured:
logging.basicConfig(
level=logging.DEBUG,
format="%(levelname)s - %(asctime)s - %(name)s - %(message)s",
)
logging.getLogger(name="puresnmp.transport").setLevel(logging.WARN)

mainlog = logging.getLogger(name="hytera-homebrew-bridge")

mainlog.info("Hytera Homebrew Bridge")

if len(sys.argv) < 2:
mainlog.error(
"use as hytera-homebrew-bridge <path to settings.ini> <optionally path to logger.ini>"
)
mainlog.error(
"If you do not have the settings.ini file, you can obtain one here: "
"https://github.com/OK-DMR/Hytera_Homebrew_Bridge/blob/master/settings.ini.default"
)
exit(1)

uvloop_spec = importlib.util.find_spec(name="uvloop")
if uvloop_spec:
# noinspection PyUnresolvedReferences
import uvloop

uvloop.install()

# suppress puresnmp_plugins experimental warning
if not sys.warnoptions:
import warnings

warnings.filterwarnings(
message="Experimental SNMPv1 support", category=UserWarning, action="ignore"
)

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop=loop)
# order is IMPORTANT, various asyncio object are created at bridge init
# and those must be created after the main loop is created
bridge: HyteraHomebrewBridge = HyteraHomebrewBridge(settings_ini_path=sys.argv[1])
if os.name != "nt":
for signal in [SIGINT, SIGTERM]:
loop.add_signal_handler(signal, bridge.stop_running)

try:
loop.run_until_complete(future=bridge.go())
loop.run_forever()
except BaseException as e:
mainlog.exception(msg="", exc_info=e)
finally:
mainlog.info("Hytera Homebrew Bridge Ended")


if __name__ == "__main__":
main()
6 changes: 5 additions & 1 deletion okdmr/hhb/callback_interface.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
class CallbackInterface:
async def homebrew_connect(self, ip: str) -> None:
"""
This interface doesn't have other purpose than code de-duplication
"""

async def homebrew_connect(self, ip: str, port: int) -> None:
pass
30 changes: 26 additions & 4 deletions okdmr/hhb/custom_bridge_datagram_protocol.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
#!/usr/bin/env python3
from asyncio import protocols

from okdmr.dmrlib.utils.logging_trait import LoggingTrait

from okdmr.hhb.settings import BridgeSettings
from okdmr.hhb.snmp import SNMP


class CustomBridgeDatagramProtocol(protocols.DatagramProtocol, LoggingTrait):
"""
Code de-duplication
"""

def __init__(self, settings: BridgeSettings) -> None:
"""
:param settings:
"""
super().__init__()
self.settings = settings

async def hytera_repeater_obtain_snmp(
self, address: tuple, force: bool = False
) -> None:
"""
:param address:
:param force:
:return:
"""
self.settings.hytera_repeater_ip = address[0]
if self.settings.snmp_enabled:
if force or not self.settings.hytera_snmp_data.get(address[0]):
await SNMP().walk_ip(address, self.settings)
if self.settings.snmp_enabled and (
force or not self.settings.hytera_snmp_data.get(address[0])
):
await SNMP().walk_ip(address, self.settings)
else:
self.log_warning("SNMP is disabled")
self.log_warning(
f"SNMP is disabled or not available "
f"snmp_enabled:{self.settings.snmp_enabled} "
f"force:{force} "
f"hytera_snmp_data:{address[0] in self.settings.hytera_snmp_data}"
)
85 changes: 18 additions & 67 deletions okdmr/hhb/hytera_homebrew_bridge.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
#!/usr/bin/env python3
import asyncio
import importlib.util
import logging.config
import os
import socket
import sys
from asyncio import AbstractEventLoop, Queue
from signal import SIGINT, SIGTERM
from typing import Optional, Dict

from okdmr.hhb.callback_interface import CallbackInterface
from okdmr.hhb.hytera_mmdvm_translator import HyteraMmdvmTranslator
from okdmr.hhb.hytera_protocols import (
HyteraP2PProtocol,
HyteraDMRProtocol,
HyteraRDACProtocol,
)
from okdmr.hhb.mmdvm_protocol import MMDVMProtocol
from okdmr.hhb.settings import BridgeSettings
from okdmr.hhb.hytera_mmdvm_translator import HyteraMmdvmTranslator

from okdmr.hhb.callback_interface import CallbackInterface


class HyteraRepeater(CallbackInterface):
Expand Down Expand Up @@ -56,24 +50,25 @@ def __init__(self, ip: str, settings: BridgeSettings, asyncloop: AbstractEventLo
hytera_repeater_ip=self.ip,
)

def homebrew_connection_lost(self, ip: str) -> None:
asyncio.run(self.homebrew_connect(ip=ip))
def homebrew_connection_lost(self, ip: str, port: int) -> None:
asyncio.run(self.homebrew_connect(ip=ip, port=port))

async def hytera_dmr_connect(self) -> None:
(transport, _) = await self.loop.create_datagram_endpoint(
lambda: self.hytera_dmr_protocol,
sock=self.settings.hytera_repeater_data[self.ip].dmr_socket,
)

async def homebrew_connect(self, ip: str) -> None:
async def homebrew_connect(self, ip: str, port: int) -> None:
incorrect_config_params = self.settings.get_incorrect_configurations(ip)
if len(incorrect_config_params) > 0:
self.homebrew_protocol.log_error(
"Current configuration is not valid for connection"
)
for triplet in incorrect_config_params:
self.settings.print_settings()
for param, current_value, error_message in incorrect_config_params:
self.homebrew_protocol.log_error(
f"PARAM: {triplet[0]} CURRENT_VALUE: {triplet[1]} MESSAGE: {triplet[2]}"
f"PARAM: {param} CURRENT_VALUE: {current_value} MESSAGE: {error_message}"
)
return

Expand Down Expand Up @@ -130,23 +125,30 @@ async def go(self) -> None:
if not self.settings.hytera_disable_rdac:
await self.hytera_rdac_connect()

async def homebrew_connect(self, ip: str) -> None:
async def homebrew_connect(self, ip: str, port: int) -> None:
if not self.repeaters.get(ip):
self.repeaters[ip] = HyteraRepeater(
ip=ip, settings=self.settings, asyncloop=self.loop
)
await self.repeaters[ip].go()

await self.repeaters[ip].homebrew_connect(ip)
await self.repeaters[ip].homebrew_connect(ip=ip, port=port)

async def hytera_p2p_connect(self) -> None:
# P2P/IPSC Service address
"""
Start P2P/IPSC Service handler
:return:
"""
await self.loop.create_datagram_endpoint(
lambda: self.hytera_p2p_protocol,
local_addr=(self.settings.ipsc_ip, self.settings.p2p_port),
)

async def hytera_rdac_connect(self) -> None:
"""
Start RDAC service handler
:return:
"""
await self.loop.create_datagram_endpoint(
lambda: self.hytera_rdac_protocol,
local_addr=(self.settings.ipsc_ip, self.settings.rdac_port),
Expand All @@ -161,54 +163,3 @@ def stop_running(self) -> None:
for task in asyncio.all_tasks():
task.cancel()
task.done()


if __name__ == "__main__":
loggerConfigured: bool = False
if len(sys.argv) > 2:
if os.path.isfile(sys.argv[2]):
logging.config.fileConfig(sys.argv[2])
loggerConfigured = True
if not loggerConfigured:
logging.basicConfig(
level=logging.DEBUG,
format="%(levelname)s - %(asctime)s - %(name)s - %(message)s",
)
logging.getLogger("puresnmp.transport").setLevel(logging.WARN)

mainlog = logging.getLogger("hytera-homebrew-bridge.py")

mainlog.info("Hytera Homebrew Bridge")

if len(sys.argv) < 2:
mainlog.error(
"use as hytera-homebrew-bridge <path to settings.ini> <optionally path to logger.ini>"
)
mainlog.error(
"If you do not have the settings.ini file, you can obtain one here: "
"https://github.com/OK-DMR/Hytera_Homebrew_Bridge/blob/master/settings.ini.default"
)
exit(1)

uvloop_spec = importlib.util.find_spec("uvloop")
if uvloop_spec:
import uvloop

uvloop.install()

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# order is IMPORTANT, various asyncio object are created at bridge init
# and those must be created after the main loop is created
bridge: HyteraHomebrewBridge = HyteraHomebrewBridge(sys.argv[1])
if os.name != "nt":
for signal in [SIGINT, SIGTERM]:
loop.add_signal_handler(signal, bridge.stop_running)

try:
loop.run_until_complete(bridge.go())
loop.run_forever()
except BaseException as e:
mainlog.exception(e)
finally:
mainlog.info("Hytera Homebrew Bridge Ended")
4 changes: 2 additions & 2 deletions okdmr/hhb/hytera_mmdvm_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ async def translate_from_hytera(self):
IpSiteConnectProtocol.SlotTypes.slot_type_wakeup_request,
IpSiteConnectProtocol.SlotTypes.slot_type_sync,
]:
print(
"Slot Type", packet.slot_type, "Frame Type", packet.frame_type
self.log_info(
f"Slot Type: {packet.slot_type}, Frame Type: {packet.frame_type}"
)
burst.debug()
self.queue_hytera_to_translate.task_done()
Expand Down
19 changes: 11 additions & 8 deletions okdmr/hhb/hytera_protocols.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import asyncio
import logging
import socket
from asyncio import transports, Queue
from binascii import hexlify
Expand Down Expand Up @@ -71,12 +72,13 @@ def handle_registration(self, data: bytes, address: Tuple[str, int]) -> None:

asyncio.gather(self.hytera_repeater_obtain_snmp(address))
self.settings.hytera_is_registered[address[0]] = True
self.log_info(f"handle_registration for {address} launching homebrew_connect")
asyncio.get_running_loop().create_task(
self.repeater_accepted_callback.homebrew_connect(address[0])
self.repeater_accepted_callback.homebrew_connect(address[0], address[1])
)

def handle_rdac_request(self, data: bytes, address: Tuple[str, int]) -> None:
if not self.settings.hytera_is_registered.get(address[0]):
if not self.settings.is_repeater_registered(address[0]):
self.log_debug(
f"Rejecting RDAC request for not-registered repeater {address[0]}"
)
Expand All @@ -103,7 +105,7 @@ def handle_rdac_request(self, data: bytes, address: Tuple[str, int]) -> None:

@staticmethod
def get_redirect_packet(data: bytearray, target_port: int):
print(f"Providing redirect packet to port {target_port}")
logging.getLogger().debug(f"Providing redirect packet to port {target_port}")
data = data[: len(data) - 1]
data[4] = 0x0B
data[12] = 0xFF
Expand All @@ -115,7 +117,7 @@ def get_redirect_packet(data: bytearray, target_port: int):
return data

def handle_dmr_request(self, data: bytes, address: Tuple[str, int]) -> None:
if not self.settings.hytera_is_registered.get(address[0]):
if not self.settings.is_repeater_registered(address[0]):
self.log_debug(
f"Rejecting DMR request for not-registered repeater {address[0]}"
)
Expand Down Expand Up @@ -485,8 +487,9 @@ def step13(self, data: bytes, address: Tuple[str, int]) -> None:
self.log_debug("rdac completed identification")
self.settings.print_repeater_configuration()
asyncio.gather(self.hytera_repeater_obtain_snmp(address))
self.log_info(f"RDAC step13 for {address} launching homebrew_connect")
asyncio.get_running_loop().create_task(
self.rdac_completed_callback.homebrew_connect(address[0])
self.rdac_completed_callback.homebrew_connect(address[0], address[1])
)

def step14(self, data: bytes, address: Tuple[str, int]) -> None:
Expand All @@ -509,11 +512,11 @@ def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None:

if len(data) == 1 and self.step[addr[0]] != 14:
if self.step[addr[0]] == 4:
self.log_warning(
self.log_error(
"check repeater zone programming, if Digital IP"
"Multi-Site Connect mode allows data pass from timeslots"
)
self.log_warning(
self.log_error(
"restart process if response is protocol reset and current step is not 14"
)
self.step[addr[0]] = 0
Expand Down Expand Up @@ -541,7 +544,7 @@ def __init__(
self.queue_incoming = queue_incoming
self.queue_outgoing = queue_outgoing
self.ip: str = hytera_repeater_ip
print(
self.log_info(
f"HyteraDMRProtocol on creation expecting ip {self.ip} and port {self.settings.get_repeater_dmr_port(self.ip)}"
)

Expand Down
4 changes: 0 additions & 4 deletions okdmr/hhb/mmdvm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
from okdmr.kaitai.hytera.ip_site_connect_protocol import IpSiteConnectProtocol


def get_mmdvm_timeslot(mmdvmdata: Mmdvm2020.TypeDmrData) -> int:
return 1 if mmdvmdata.slot_no == Mmdvm2020.Timeslots.timeslot_1 else 2


def get_ipsc_timeslot(ipscdata: IpSiteConnectProtocol) -> int:
return (
1 if ipscdata.timeslot_raw == IpSiteConnectProtocol.Timeslots.timeslot_1 else 2
Expand Down
Loading

0 comments on commit 5299068

Please sign in to comment.