EchoLink® software allows licensed Amateur Radio stations to communicate with one another over the Internet, using streaming-audio technology.

http://echolink.org

Cross-linking with BrandMeister

To create cross-link you need several things:

  • An valid EchoLink node account
  • Patched version of SVXLink software that should be deployed on the same server with BrandMeister
  • One of supported AMBE Dongles

Patch is available here - https://code.brandmeister.network/r3abm/AutoPatch.git
Patched version of SVXLink is available here = https://github.com/stefansaraev/svxlink/tree/autopatch

Compiling

# create user and group
groupadd svxlink
useradd -r -g nogroup -s /sbin/nologin -d /opt/SVXLink -c "SvxLink Daemon" svxlink
usermod -G audio,nogroup,svxlink svxlink

# fetch patched sources, compile and install into /opt/SVXLink
cd /usr/src
git clone https://github.com/stefansaraev/svxlink.git -b autopatch
cd svxlink
./build.sh

# install the systemd unit if you want to run svxlink as systemd service
cp bm/svxlink.service /etc/systemd/system/
systemctl enable svxlink

System configuration

The supplied patch used to translate callsigns that come from EchoLink into DMR IDs.

svxlink.conf

[GLOBAL]
LOGICS=SimplexLogic
CFG_DIR=svxlink.d
TIMESTAMP_FORMAT="%c"
#  CARD_SAMPLE_RATE should be 8000
CARD_SAMPLE_RATE=8000
CARD_CHANNELS=1

[SimplexLogic]
TYPE=Simplex
RX=Rx1
TX=Tx1
MODULES=ModuleEchoLink
CALLSIGN=NOCALL
EVENT_HANDLER=/opt/SVXLink/share/svxlink/events.tcl

[Rx1]
TYPE=Local
AUDIO_DEV=alsa:hw:Loopback,1,2
AUDIO_CHANNEL=0
SQL_DET=VOX
SQL_START_DELAY=0
SQL_HANGTIME=1500
SQL_TIMEOUT=180
VOX_FILTER_DEPTH=20
VOX_THRESH=50

[Tx1]
TYPE=Local
AUDIO_DEV=alsa:hw:Loopback,0,0
AUDIO_CHANNEL=0
PTT_TYPE=NONE
TIMEOUT=180

ModuleEchoLink.conf

[ModuleEchoLink]
# DMR ID to use as a default when SVXLink cannot provide a source callsign
BRIDGE_DEFAULT=nnn
# MASTER ID : AUTOPATCH ID
BRIDGE_PROXY=xxx1:10
# Encoding of text used by EchoLink in your region
BRIDGE_ENCODING=cp1251

NAME=EchoLink
ID=2
TIMEOUT=0
SERVERS=servers.echolink.org
CALLSIGN=N0CALL-L
PASSWORD=trololol
SYSOPNAME=N0CALL
LOCATION=BrandMeister TGxxx0
MAX_QSOS=50
MAX_CONNECTIONS=51
LINK_IDLE_TIMEOUT=300
DESCRIPTION="Link to BrandMeister TGxxx0\n"

optional registry plugin for SQL_DET=PTY: 991-SvxLink.lua

require('Global')

local bit = require('bit')
local fio = require('fio')
local log = require('log')

--[[

  SvxLink PTY SQL_DET Plug-In

]]--

local cfg = {
  [2840]    = { 20, '/tmp/sql.2840',   nil },
  [28430]   = { 10, '/tmp/sql.28430',  nil },
  [284359]  = { 11, '/tmp/sql.284359', nil }
}

local function setSquelch(destination, state)
  local file = fio.readlink(cfg[destination][2])
  if file ~= nil then
    local handle = fio.open(file, { 'O_WRONLY' })
    handle:write(state)
    handle:close()
  end
end

local function resetSquelch(state)
  for k, v in pairs(cfg) do
    -- log.info("SvxLink: reset %s %s %s", k, v[1], v[2], v[3])
    setSquelch(k, state)
  end
end

local function checkSquelch(destination, state, newSessionID)
  if state == 'O' and cfg[destination][3] == nil then
    setSquelch(destination, state)
    cfg[destination][3] = newSessionID
    -- log.info("SvxLink: open %d %s", destination, newSessionID)
  end

  if state == 'Z' and cfg[destination][3] == newSessionID then
    setSquelch(destination, state)
    cfg[destination][3] = nil
    -- log.info("SvxLink: close %d %s", destination, newSessionID)
  end
end

local SvxLink = { }

function SvxLink.start()
  resetSquelch('Z')
end

function SvxLink.handleSystemEvent(network, data)
  -- work around to detect brandmeister core restart
  if data['Event'] == 'Initialize' and
     data['LinkName'] == 'FastForward'
  then
    resetSquelch('Z')
  end
end

function SvxLink.handleSessionEvent(network, data)
  if data['Event'] == 'Session-Start-Extended' or
     data['Event'] == 'Session-Stop-Extended'
  then
    local kind        = tonumber(data['LinkType'     ])
    local flavor      = tonumber(data['SessionType'  ])
    local number      = tonumber(data['Number'       ])
    local destination = tonumber(data['DestinationID'])
    local sessionid   =          data['SessionID'    ]

    if cfg[destination] ~= nil then
      if number ~= cfg[destination][1] and
         bit.band(flavor, SESSION_TYPE_FLAG_VOICE) ~= 0 and
         bit.band(flavor, SESSION_TYPE_FLAG_GROUP) ~= 0
      then
        if data['Event'] == 'Session-Start-Extended' then
          checkSquelch(destination, 'O', sessionid)
        else
          checkSquelch(destination, 'Z', sessionid)
        end
      end
    end
  end
end

return SvxLink

EchoLink® software allows licensed Amateur Radio stations to communicate with one another over the Internet, using streaming-audio technology.

http://echolink.org

Cross-linking with BrandMeister[edit]

To create cross-link you need several things:

Patch is available here - https://code.brandmeister.network/r3abm/AutoPatch.git
Patched version of SVXLink is available here = https://github.com/stefansaraev/svxlink/tree/autopatch

Compiling[edit]

# create user and group
groupadd svxlink
useradd -r -g nogroup -s /sbin/nologin -d /opt/SVXLink -c "SvxLink Daemon" svxlink
usermod -G audio,nogroup,svxlink svxlink

# fetch patched sources, compile and install into /opt/SVXLink
cd /usr/src
git clone https://github.com/stefansaraev/svxlink.git -b autopatch
cd svxlink
./build.sh

# install the systemd unit if you want to run svxlink as systemd service
cp bm/svxlink.service /etc/systemd/system/
systemctl enable svxlink

System configuration[edit]

The supplied patch used to translate callsigns that come from EchoLink into DMR IDs.

svxlink.conf[edit]

[GLOBAL]
LOGICS=SimplexLogic
CFG_DIR=svxlink.d
TIMESTAMP_FORMAT="%c"
#  CARD_SAMPLE_RATE should be 8000
CARD_SAMPLE_RATE=8000
CARD_CHANNELS=1

[SimplexLogic]
TYPE=Simplex
RX=Rx1
TX=Tx1
MODULES=ModuleEchoLink
CALLSIGN=NOCALL
EVENT_HANDLER=/opt/SVXLink/share/svxlink/events.tcl

[Rx1]
TYPE=Local
AUDIO_DEV=alsa:hw:Loopback,1,2
AUDIO_CHANNEL=0
SQL_DET=VOX
SQL_START_DELAY=0
SQL_HANGTIME=1500
SQL_TIMEOUT=180
VOX_FILTER_DEPTH=20
VOX_THRESH=50

[Tx1]
TYPE=Local
AUDIO_DEV=alsa:hw:Loopback,0,0
AUDIO_CHANNEL=0
PTT_TYPE=NONE
TIMEOUT=180

ModuleEchoLink.conf[edit]

[ModuleEchoLink]
# DMR ID to use as a default when SVXLink cannot provide a source callsign
BRIDGE_DEFAULT=nnn
# MASTER ID : AUTOPATCH ID
BRIDGE_PROXY=xxx1:10
# Encoding of text used by EchoLink in your region
BRIDGE_ENCODING=cp1251

NAME=EchoLink
ID=2
TIMEOUT=0
SERVERS=servers.echolink.org
CALLSIGN=N0CALL-L
PASSWORD=trololol
SYSOPNAME=N0CALL
LOCATION=BrandMeister TGxxx0
MAX_QSOS=50
MAX_CONNECTIONS=51
LINK_IDLE_TIMEOUT=300
DESCRIPTION="Link to BrandMeister TGxxx0\n"

optional registry plugin for SQL_DET=PTY: 991-SvxLink.lua[edit]

require('Global')

local bit = require('bit')
local fio = require('fio')
local log = require('log')

--[[

  SvxLink PTY SQL_DET Plug-In

]]--

local cfg = {
  [2840]    = { 20, '/tmp/sql.2840',   nil },
  [28430]   = { 10, '/tmp/sql.28430',  nil },
  [284359]  = { 11, '/tmp/sql.284359', nil }
}

local function setSquelch(destination, state)
  local file = fio.readlink(cfg[destination][2])
  if file ~= nil then
    local handle = fio.open(file, { 'O_WRONLY' })
    handle:write(state)
    handle:close()
  end
end

local function resetSquelch(state)
  for k, v in pairs(cfg) do
    -- log.info("SvxLink: reset %s %s %s", k, v[1], v[2], v[3])
    setSquelch(k, state)
  end
end

local function checkSquelch(destination, state, newSessionID)
  if state == 'O' and cfg[destination][3] == nil then
    setSquelch(destination, state)
    cfg[destination][3] = newSessionID
    -- log.info("SvxLink: open %d %s", destination, newSessionID)
  end

  if state == 'Z' and cfg[destination][3] == newSessionID then
    setSquelch(destination, state)
    cfg[destination][3] = nil
    -- log.info("SvxLink: close %d %s", destination, newSessionID)
  end
end

local SvxLink = { }

function SvxLink.start()
  resetSquelch('Z')
end

function SvxLink.handleSystemEvent(network, data)
  -- work around to detect brandmeister core restart
  if data['Event'] == 'Initialize' and
     data['LinkName'] == 'FastForward'
  then
    resetSquelch('Z')
  end
end

function SvxLink.handleSessionEvent(network, data)
  if data['Event'] == 'Session-Start-Extended' or
     data['Event'] == 'Session-Stop-Extended'
  then
    local kind        = tonumber(data['LinkType'     ])
    local flavor      = tonumber(data['SessionType'  ])
    local number      = tonumber(data['Number'       ])
    local destination = tonumber(data['DestinationID'])
    local sessionid   =          data['SessionID'    ]

    if cfg[destination] ~= nil then
      if number ~= cfg[destination][1] and
         bit.band(flavor, SESSION_TYPE_FLAG_VOICE) ~= 0 and
         bit.band(flavor, SESSION_TYPE_FLAG_GROUP) ~= 0
      then
        if data['Event'] == 'Session-Start-Extended' then
          checkSquelch(destination, 'O', sessionid)
        else
          checkSquelch(destination, 'Z', sessionid)
        end
      end
    end
  end
end

return SvxLink