commit 660133c812931584d4c1968de8cc803ea18352e3
Author: Artem Prilutskiy <cyanide.burnout@gmail.com>
Date:   Wed Mar 23 20:08:20 2016 +0300

    Initial import

diff --git a/CallCapture/BrandMeisterBridge.cpp b/CallCapture/BrandMeisterBridge.cpp
new file mode 100644
index 0000000..1f13fd6
--- /dev/null
+++ b/CallCapture/BrandMeisterBridge.cpp
@@ -0,0 +1,90 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#include "BrandMeisterBridge.h"
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <stdio.h>
+
+#define ECHOLINK_DEFAULT_USER_NUMBER  1
+#define ECHOLINK_DEFAULT_CORD_NUMBER  10
+#define REGISTRY_CONFIGURATION_FILE   "/opt/BrandMeister/Registry.cnf"
+
+BrandMeisterBridge::BrandMeisterBridge() :
+  proxy(ECHOLINK_DEFAULT_CORD_NUMBER),
+  store(REGISTRY_CONFIGURATION_FILE),
+  talker(NULL)
+{
+
+}
+
+BrandMeisterBridge::~BrandMeisterBridge()
+{
+  free(talker);
+}
+
+// Interface methods for ModuleEchoLink
+
+const char* BrandMeisterBridge::getTalker()
+{
+  free(talker);
+  uint32_t number = proxy.getTalkerID();
+
+  char call[LONG_CALLSIGN_LENGTH];
+  char text[SLOW_DATA_TEXT_LENGTH];
+  if ((number != 0) &&
+      (store.getCredentialsForID(number, call, text)))
+  {
+    asprintf(&talker, "%s %s", call, text);
+    return talker;
+  }
+
+  asprintf(&talker, "DMR ID: %d", number);
+  return talker;
+}
+
+void BrandMeisterBridge::setTalker(const char* call, const char* name)
+{
+  if (*call == '*')
+  {
+    // Do not process conference call-sign
+    return;
+  }
+
+  const char* delimiter = strpbrk(call, " -\n");
+  if (delimiter != NULL)
+  {
+    // Remove characters after call-sign
+    size_t length = delimiter - call;
+    char* buffer = (char*)alloca(length + 1);
+    strncpy(buffer, call, length);
+    call = buffer;
+  }
+
+  uint32_t number = store.getPrivateIDForCall(call);
+  if (number == 0)
+  {
+    syslog(LOG_INFO, "DMR ID for call-sign %s not found", call);
+    number = ECHOLINK_DEFAULT_USER_NUMBER;
+  }
+
+  syslog(LOG_DEBUG, "Set talker DMR ID to %d", number);
+  proxy.setTalkerID(number);
+}
+
+void BrandMeisterBridge::handleChatMessage(const char* text)
+{
+  // CONF Russian Reflector, Open 24/7, Contacts: rv3dhc.link@qip.ru * Call CQ / Use pauses 2sec * [28/500]
+  // R3ABM-L *DSTAR.SU DMR Bridge*
+  // UB3AMO Moscow T I N A O
+  // ->UA0LQE-L USSURIISK
+
+  const char* delimiter;
+  if ((strncmp(text, "CONF ", 5) == 0) &&
+      ((delimiter = strstr(text, "\n->")) != NULL))
+  {
+    const char* call = delimiter + 3;
+    setTalker(call, NULL);
+  }
+}
+
diff --git a/CallCapture/BrandMeisterBridge.h b/CallCapture/BrandMeisterBridge.h
new file mode 100644
index 0000000..554bbe8
--- /dev/null
+++ b/CallCapture/BrandMeisterBridge.h
@@ -0,0 +1,28 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#ifndef BRANDMEISTERBRIDGE_H
+#define BRANDMEISTERBRIDGE_H
+
+#include "PatchCordProxy.h"
+#include "UserDataStore.h"
+
+class BrandMeisterBridge
+{
+  public:
+
+    BrandMeisterBridge();
+    ~BrandMeisterBridge();
+
+    const char* getTalker();
+    void setTalker(const char* call, const char* name);
+    void handleChatMessage(const char* text);
+
+  private:
+
+    PatchCordProxy proxy;
+    UserDataStore store;
+    char* talker;
+
+};
+
+#endif
\ No newline at end of file
diff --git a/CallCapture/CallCapture.cpp b/CallCapture/CallCapture.cpp
new file mode 100644
index 0000000..ada3df8
--- /dev/null
+++ b/CallCapture/CallCapture.cpp
@@ -0,0 +1,113 @@
+#include <stdlib.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <stdio.h>
+#include <pcre.h>
+
+#include "PatchCordProxy.h"
+#include "UserDataStore.h"
+
+#define VECTORS_COUNT        8
+#define DEFAULT_USER_NUMBER  1
+
+int main(int argc, const char* argv[])
+{
+  printf("\n");
+  printf("CallCapture for BrandMeister DMR Master Server\n");
+  printf("Copyright 2015 Artem Prilutskiy (R3ABM, cyanide.burnout@gmail.com)\n");
+  printf("\n");
+  
+  // Start up
+  
+  struct option options[] =
+  {
+    { "expression", required_argument, NULL, 'e' },
+    { "connection", required_argument, NULL, 'c' },
+    { "identity",   required_argument, NULL, 'i' },
+    { "link",       required_argument, NULL, 'l' },
+    { NULL,         0,                 NULL, 0   }
+  };
+
+  pcre* expression = NULL;
+  const char* file = NULL;
+  uint32_t number = 10;
+
+  int position = 0;
+  const char* error = NULL;
+
+  int selection = 0;
+  while ((selection = getopt_long(argc, const_cast<char* const*>(argv), "e:c:l:", options, NULL)) != EOF)
+    switch (selection)
+    {
+      case 'e':
+        expression = pcre_compile(optarg, 0, &error, &position, NULL);
+        break;
+
+      case 'c':
+        file = optarg;
+        break;
+
+      case 'i':
+        openlog(optarg, 0, LOG_USER);
+        break;
+
+      case 'l':
+        number = atoi(optarg);
+        break;
+    }
+
+  if ((expression == NULL) &&
+      (error != NULL))
+  {
+    printf("Error compiling regular expression: %s (at %d)\n", error, position);
+    return EXIT_FAILURE;
+  }
+
+  if ((expression == NULL) ||
+      (file == NULL))
+  {
+    printf(
+      "Usage: %s"
+      " --expression <regular expression>"
+      " --connection <path to configuration file>"
+      " --link <link number>"
+      " [--identity <identity>]"
+      "\n",
+      argv[0]);
+    return EXIT_FAILURE;
+  }
+
+  // Main
+
+  UserDataStore store(file);
+  PatchCordProxy proxy(number);
+
+  char* line = NULL;
+  size_t length = 0;
+  ssize_t read;
+
+  while ((read = getline(&line, &length, stdin)) != EOF)
+  {
+    syslog(LOG_DEBUG, "%s", line);
+
+    int vectors[VECTORS_COUNT];
+    int count = pcre_exec(expression, NULL, line, length, 0, 0, vectors, VECTORS_COUNT);
+    if (count > 0)
+    {
+      const char* call;
+      pcre_get_substring(line, vectors, count, 1, &call);
+
+      uint32_t number = store.getPrivateIDForCall(call);
+      if (number == 0)
+        number = DEFAULT_USER_NUMBER;
+      proxy.setTalkerID(number);
+      syslog(LOG_INFO, "*** Found call-sign: %s (ID: %d)", call, number);
+
+      pcre_free_substring(call);
+    }
+  }
+
+  pcre_free(expression);
+
+  return EXIT_SUCCESS;
+};
diff --git a/CallCapture/Makefile b/CallCapture/Makefile
new file mode 100644
index 0000000..a2e3cb8
--- /dev/null
+++ b/CallCapture/Makefile
@@ -0,0 +1,40 @@
+OBJECTS = \
+  PatchCordProxy.o \
+  UserDataStore.o \
+  CallCapture.o
+
+LIBRARIES = \
+  mysqlclient \
+  pcre
+
+DEPENDENCIES = \
+  dbus-1
+
+CXXFLAGS := -fno-implicit-templates -D__STDC_CONSTANT_MACROS
+FLAGS := -g -rdynamic -fno-omit-frame-pointer -O3 -MMD $(foreach directory, $(DIRECTORIES), -I$(directory)) $(shell pkg-config --cflags $(DEPENDENCIES)) -DBUILD=\"$(BUILD)\" -DVERSION=$(VERSION)
+LIBS := -lstdc++ $(foreach library, $(LIBRARIES), -l$(library)) $(shell pkg-config --libs $(DEPENDENCIES))
+
+# PREREQUISITES =
+
+CC = gcc
+CXX = g++
+COMPILER_VERSION_FLAG := $(shell expr `${CXX} -dumpversion | cut -f1-2 -d.` \>= 4.9)
+
+CFLAGS += $(FLAGS) -std=gnu99
+
+ifeq ($(COMPILER_VERSION_FLAG), 1)
+	CXXFLAGS += $(FLAGS) -std=gnu++11
+else
+	CXXFLAGS += $(FLAGS) -std=gnu++0x
+endif
+
+all: build
+
+build: $(PREREQUISITES) $(OBJECTS)
+	$(CC) $(OBJECTS) $(FLAGS) $(LIBS) -o callcapture
+
+clean:
+	rm -f $(PREREQUISITES) $(OBJECTS) callcapture
+	rm -f *.d */*.d
+
+.PHONY: all build clean install
diff --git a/CallCapture/PatchCordProxy.cpp b/CallCapture/PatchCordProxy.cpp
new file mode 100644
index 0000000..bb3c453
--- /dev/null
+++ b/CallCapture/PatchCordProxy.cpp
@@ -0,0 +1,154 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#include "PatchCordProxy.h"
+#include <stdlib.h>
+#include <string.h>
+
+#include <syslog.h>
+#include <stdio.h>
+
+#define INTERFACE_NAME  "me.burnaway.BrandMeister"
+#define SERVICE_NAME    INTERFACE_NAME
+#define OBJECT_PATH     "/me/burnaway/BrandMeister"
+
+// From AutoPatch.cpp
+#define AUTOPATCH_LINK_NAME  "AutoPatch"
+
+// From PatchCord.h
+#define VALUE_CORD_OUTGOING_SOURCE_ID     1
+#define VALUE_CORD_INCOMING_SOURCE_ID     4
+
+PatchCordProxy::PatchCordProxy(uint32_t number) :
+  number(number),
+  banner(NULL)
+{
+  connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+}
+
+PatchCordProxy::~PatchCordProxy()
+{
+  free(banner);
+  dbus_connection_unref(connection);
+}
+
+void PatchCordProxy::setTalkerID(uint32_t value)
+{
+  getContextBanner();
+  setVendorSpecificValue(VALUE_CORD_OUTGOING_SOURCE_ID, value);
+}
+
+uint32_t PatchCordProxy::getTalkerID()
+{
+  getContextBanner();
+  return getVendorSpecificValue(VALUE_CORD_INCOMING_SOURCE_ID);
+}
+
+void PatchCordProxy::getContextBanner()
+{
+  DBusMessage* message = dbus_message_new_method_call(
+    SERVICE_NAME, OBJECT_PATH,
+    INTERFACE_NAME, "getContextList");
+
+  const char* name = AUTOPATCH_LINK_NAME;
+
+  dbus_message_append_args(message,
+    DBUS_TYPE_STRING, &name,
+    DBUS_TYPE_UINT32, &number,
+    DBUS_TYPE_INVALID);
+
+  DBusPendingCall* pending;
+  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
+  {
+    dbus_connection_flush(connection);
+    dbus_message_unref(message);
+    dbus_pending_call_block(pending);
+    message = dbus_pending_call_steal_reply(pending);
+    char** array;
+    int count;
+    if ((dbus_message_get_args(message, NULL,
+          DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &array, &count,
+          DBUS_TYPE_INVALID)) &&
+        (count > 0))
+    {
+      free(banner);
+      banner = strdup(*array);
+      dbus_free_string_array(array);
+    }
+    dbus_pending_call_unref(pending);
+  }
+  dbus_message_unref(message);
+}
+
+void PatchCordProxy::setVendorSpecificValue(uint32_t key, uint32_t value)
+{
+  DBusMessage* message = dbus_message_new_method_call(
+    SERVICE_NAME, OBJECT_PATH,
+    INTERFACE_NAME, "setVendorSpecificValue");
+
+  dbus_message_append_args(message,
+    DBUS_TYPE_STRING, &banner,
+    DBUS_TYPE_UINT32, &key,
+    DBUS_TYPE_UINT32, &value,
+    DBUS_TYPE_INVALID);
+  
+  DBusPendingCall* pending;
+  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
+  {
+    dbus_connection_flush(connection);
+    dbus_message_unref(message);
+    dbus_pending_call_block(pending);
+    message = dbus_pending_call_steal_reply(pending);
+    dbus_pending_call_unref(pending);
+  }
+  dbus_message_unref(message);
+  
+  // Each call of dbus_message_unref removes banner from the heap
+  banner = NULL;
+}
+
+uint32_t PatchCordProxy::getVendorSpecificValue(uint32_t key)
+{
+  DBusMessage* message = dbus_message_new_method_call(
+    SERVICE_NAME, OBJECT_PATH,
+    INTERFACE_NAME, "getContextData");
+
+  dbus_message_append_args(message,
+    DBUS_TYPE_STRING, &banner,
+    DBUS_TYPE_INVALID);
+
+  uint32_t value = 0;
+
+  DBusPendingCall* pending;
+  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
+  {
+    dbus_connection_flush(connection);
+    dbus_message_unref(message);
+    dbus_pending_call_block(pending);
+    message = dbus_pending_call_steal_reply(pending);
+    const char* name;
+    const char* address;
+    dbus_uint32_t type;
+    dbus_uint32_t state;
+    dbus_uint32_t number;
+    dbus_uint32_t* values;
+    int count;
+    if (dbus_message_get_args(message, NULL,
+          DBUS_TYPE_STRING, &banner,
+          DBUS_TYPE_STRING, &name,
+          DBUS_TYPE_UINT32, &type,
+          DBUS_TYPE_UINT32, &number,
+          DBUS_TYPE_STRING, &address,
+          DBUS_TYPE_UINT32, &state,
+          DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &values, &count,
+          DBUS_TYPE_INVALID))
+      value = values[key];
+    dbus_pending_call_unref(pending);
+  }
+
+  dbus_message_unref(message);
+
+  // Each call of dbus_message_unref removes banner from the heap
+  banner = NULL;
+
+  return value;
+}
diff --git a/CallCapture/PatchCordProxy.h b/CallCapture/PatchCordProxy.h
new file mode 100644
index 0000000..75149f0
--- /dev/null
+++ b/CallCapture/PatchCordProxy.h
@@ -0,0 +1,31 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#ifndef PATCHCORDPROXY_H
+#define PATCHCORDPROXY_H
+
+#include <stdint.h>
+#include <dbus/dbus.h>
+
+class PatchCordProxy
+{
+  public:
+
+    PatchCordProxy(uint32_t number);
+    ~PatchCordProxy();
+
+    void setTalkerID(uint32_t value);
+    uint32_t getTalkerID();
+
+  private:
+
+    DBusConnection* connection;
+    uint32_t number;
+    char* banner;
+
+    void getContextBanner();
+    void setVendorSpecificValue(uint32_t key, uint32_t value);
+    uint32_t getVendorSpecificValue(uint32_t key);
+
+};
+
+#endif
\ No newline at end of file
diff --git a/CallCapture/UserDataStore.cpp b/CallCapture/UserDataStore.cpp
new file mode 100644
index 0000000..9fbe681
--- /dev/null
+++ b/CallCapture/UserDataStore.cpp
@@ -0,0 +1,137 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#include "UserDataStore.h"
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+
+#define GET_ID_FOR_CALL         0
+#define GET_CREDENTIALS_FOR_ID  1
+
+UserDataStore::UserDataStore(const char* file) :
+  connection(NULL)
+{
+  const char* queries[] =
+  {
+    // GET_ID_FOR_CALL
+    "SELECT `ID`"
+    " FROM Users"
+    " WHERE `Call` = ?"
+    " ORDER BY `Priority`"
+    " LIMIT 1",
+    // GET_CREDENTIALS_FOR_ID
+    "SELECT `Call`, `Text`"
+    " FROM Users"
+    " WHERE `ID` = ?",
+  };
+
+  connection = mysql_init(NULL);
+
+  mysql_options(connection, MYSQL_READ_DEFAULT_FILE, realpath(file, NULL));
+  mysql_real_connect(connection, NULL, NULL, NULL, NULL, 0, NULL, CLIENT_REMEMBER_OPTIONS);
+
+  my_bool active = true;
+  mysql_options(connection, MYSQL_OPT_RECONNECT, &active);
+
+  for (size_t index = 0; index < PREPARED_STATEMENT_COUNT; index ++)
+  {
+    const char* query = queries[index];
+    statements[index] = mysql_stmt_init(connection);
+    mysql_stmt_prepare(statements[index], query, strlen(query));
+  }
+
+  const char* error = mysql_error(connection);
+  if ((error != NULL) && (*error != '\0'))
+    syslog(LOG_CRIT, "MySQL error: %s\n", error);
+}
+
+UserDataStore::~UserDataStore()
+{
+  for (size_t index = 0; index < PREPARED_STATEMENT_COUNT; index ++)
+    mysql_stmt_close(statements[index]);
+
+  mysql_close(connection);
+}
+
+// Public methods
+
+uint32_t UserDataStore::getPrivateIDForCall(const char* call)
+{
+  uint32_t number;
+  size_t length = strlen(call);
+
+  return
+    execute(GET_ID_FOR_CALL, 1, 1,
+      MYSQL_TYPE_STRING, call, length,
+      MYSQL_TYPE_LONG, &number) *
+    number;
+}
+
+
+bool UserDataStore::getCredentialsForID(uint32_t number, char* call, char* text)
+{
+  return
+    execute(GET_CREDENTIALS_FOR_ID, 1, 2,
+      MYSQL_TYPE_LONG, &number,
+      MYSQL_TYPE_STRING, call, LONG_CALLSIGN_LENGTH + 1,
+      MYSQL_TYPE_STRING, text, SLOW_DATA_TEXT_LENGTH + 1);
+}
+
+// Internals
+
+MYSQL_BIND* UserDataStore::bind(int count, va_list* arguments)
+{
+  MYSQL_BIND* bindings = NULL;
+  if (count > 0)
+  {
+    bindings = (MYSQL_BIND*)calloc(count, sizeof(MYSQL_BIND));
+    for (int index = 0; index < count; index ++)
+    {
+      int type = va_arg(*arguments, int);
+      bindings[index].buffer_type = (enum_field_types)type;
+      bindings[index].buffer = va_arg(*arguments, void*);
+      if ((type == MYSQL_TYPE_STRING) ||
+          (type == MYSQL_TYPE_DATETIME))
+      {
+        bindings[index].buffer_length = va_arg(*arguments, int);
+        bindings[index].length = &bindings[index].buffer_length;
+      }
+    }
+  }
+  return bindings;
+}
+
+bool UserDataStore::execute(int index, int count1, int count2, ...)
+{
+  bool result = true;
+  MYSQL_STMT* statement = statements[index];
+  
+  va_list arguments;
+  va_start(arguments, count2);
+  MYSQL_BIND* parameters = bind(count1, &arguments);
+  MYSQL_BIND* columns = bind(count2, &arguments);
+  va_end(arguments);
+  
+  if ((parameters && mysql_stmt_bind_param(statement, parameters)) ||
+      (columns    && mysql_stmt_bind_result(statement, columns))   ||
+                     mysql_stmt_execute(statement)                 ||
+      (columns    && mysql_stmt_store_result(statement)))
+  {
+    const char* error = mysql_stmt_error(statement);
+    syslog(LOG_CRIT, "MySQL error while processing statement %d: %s\n", index, error);
+    result = false;
+  }
+  
+  if (result && columns)
+  {
+    result = !mysql_stmt_fetch(statement);
+    mysql_stmt_free_result(statement);
+  }
+  
+  free(columns);
+  free(parameters);
+  return result;
+}
+
diff --git a/CallCapture/UserDataStore.h b/CallCapture/UserDataStore.h
new file mode 100644
index 0000000..1f2ed01
--- /dev/null
+++ b/CallCapture/UserDataStore.h
@@ -0,0 +1,35 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#ifndef USERDATASTORE_H
+#define USERDATASTORE_H
+
+#include <stdint.h>
+#include <stdarg.h>
+#include <mysql/mysql.h>
+
+#define PREPARED_STATEMENT_COUNT  2
+
+#define LONG_CALLSIGN_LENGTH      8
+#define SLOW_DATA_TEXT_LENGTH     20
+
+class UserDataStore
+{
+  public:
+
+    UserDataStore(const char* file);
+    ~UserDataStore();
+
+    uint32_t getPrivateIDForCall(const char* call);
+    bool getCredentialsForID(uint32_t number, char* call, char* text);
+
+  protected:
+
+    MYSQL* connection;
+    MYSQL_STMT* statements[PREPARED_STATEMENT_COUNT];
+
+    MYSQL_BIND* bind(int count, va_list* arguments);
+    bool execute(int index, int count1, int count2, ...);
+
+};
+
+#endif
diff --git a/SVXLink/Makefile b/SVXLink/Makefile
new file mode 100644
index 0000000..8a8d8e7
--- /dev/null
+++ b/SVXLink/Makefile
@@ -0,0 +1,43 @@
+DIRECTORIES = \
+  echolink
+
+OBJECTS = \
+  echolink/UserDataStore.o \
+  echolink/PatchCordProxy.o \
+  echolink/BrandMeisterBridge.o \
+  Test.o
+
+LIBRARIES = \
+  mysqlclient
+
+DEPENDENCIES = \
+  dbus-1
+
+CXXFLAGS := -fno-implicit-templates -D__STDC_CONSTANT_MACROS
+FLAGS := -g -rdynamic -fno-omit-frame-pointer -O3 -MMD $(foreach directory, $(DIRECTORIES), -I$(directory)) $(shell pkg-config --cflags $(DEPENDENCIES)) -DBUILD=\"$(BUILD)\" -DVERSION=$(VERSION)
+LIBS := -lstdc++ $(foreach library, $(LIBRARIES), -l$(library)) $(shell pkg-config --libs $(DEPENDENCIES))
+
+# PREREQUISITES =
+
+CC = gcc
+CXX = g++
+COMPILER_VERSION_FLAG := $(shell expr `${CXX} -dumpversion | cut -f1-2 -d.` \>= 4.9)
+
+CFLAGS += $(FLAGS) -std=gnu99
+
+ifeq ($(COMPILER_VERSION_FLAG), 1)
+	CXXFLAGS += $(FLAGS) -std=gnu++11
+else
+	CXXFLAGS += $(FLAGS) -std=gnu++0x
+endif
+
+all: build
+
+build: $(PREREQUISITES) $(OBJECTS)
+	$(CC) $(OBJECTS) $(FLAGS) $(LIBS) -o unit-test
+
+clean:
+	rm -f $(PREREQUISITES) $(OBJECTS) unit-test
+	rm -f *.d */*.d
+
+.PHONY: all build clean install config binary system
diff --git a/SVXLink/ModuleEchoLink.conf b/SVXLink/ModuleEchoLink.conf
new file mode 100644
index 0000000..4d75aa1
--- /dev/null
+++ b/SVXLink/ModuleEchoLink.conf
@@ -0,0 +1,21 @@
+[ModuleEchoLink]
+NAME=EchoLink
+ID=2
+TIMEOUT=0
+#DROP_INCOMING=^()$
+#REJECT_INCOMING=^(.*-[LR])$
+#REJECT_INCOMING=^(.*)$
+#ACCEPT_INCOMING=^(.*)$
+#REJECT_OUTGOING=^()$
+#ACCEPT_OUTGOING=^(.*)$
+SERVERS=servers.echolink.org
+CALLSIGN=MY0CALL-L
+PASSWORD=password
+SYSOPNAME=*DSTAR.SU DMR Bridge*
+LOCATION=Moscow, Russia
+MAX_QSOS=10
+MAX_CONNECTIONS=11
+LINK_IDLE_TIMEOUT=0
+DESCRIPTION="You have connected to a DSTAR.SU DMR Bridge\n"
+AUTOCON_ECHOLINK_ID=196189
+AUTOCON_TIME=30
diff --git a/SVXLink/Test.cpp b/SVXLink/Test.cpp
new file mode 100644
index 0000000..0681bba
--- /dev/null
+++ b/SVXLink/Test.cpp
@@ -0,0 +1,13 @@
+#include <stdio.h>
+
+#include "BrandMeisterBridge.h"
+
+int main(int argc, const char* argv[])
+{
+  BrandMeisterBridge bridge;
+  // Test 1
+  printf("Talker: %s\n", bridge.getTalker());
+  // Test 2
+  bridge.setTalker("R3ABM", "Artem");
+  return 0;
+};
diff --git a/SVXLink/build.sh b/SVXLink/build.sh
new file mode 100755
index 0000000..bb8ece9
--- /dev/null
+++ b/SVXLink/build.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+PREFIX=/opt/SVXLink
+sudo apt-get install libsigc++-2.0-dev libpopt-dev libgcrypt11-dev libasound2-dev libgsm1-dev
+
+sudo adduser svxlink --system --home $PREFIX --shell /bin/false --disabled-login --disabled-password
+sudo addgroup svxlink
+sudo adduser svxlink audio
+sudo adduser svxlink master
+sudo adduser svxlink svxlink
+
+pushd .
+mkdir src/build
+cd src/build
+cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DUSE_QT=NO -DUSE_OSS=NO ..
+make
+sudo make install
+popd
+
+sudo echo $PREFIX/lib > /etc/ld.so.conf.d/svxlink.conf
+sudo ldconfig
diff --git a/SVXLink/echolink/BrandMeisterBridge.cpp b/SVXLink/echolink/BrandMeisterBridge.cpp
new file mode 100644
index 0000000..7e7931b
--- /dev/null
+++ b/SVXLink/echolink/BrandMeisterBridge.cpp
@@ -0,0 +1,95 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#include "BrandMeisterBridge.h"
+#include <string.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <stdio.h>
+
+#define ECHOLINK_DEFAULT_USER_NUMBER  1
+#define ECHOLINK_DEFAULT_CORD_NUMBER  10
+#define REGISTRY_CONFIGURATION_FILE   "/opt/BrandMeister/Registry.cnf"
+
+BrandMeisterBridge::BrandMeisterBridge() :
+  proxy(ECHOLINK_DEFAULT_CORD_NUMBER),
+  store(REGISTRY_CONFIGURATION_FILE),
+  talker(NULL)
+{
+
+}
+
+BrandMeisterBridge::~BrandMeisterBridge()
+{
+  free(talker);
+}
+
+// Interface methods for ModuleEchoLink
+
+const char* BrandMeisterBridge::getTalker()
+{
+  free(talker);
+  uint32_t number = proxy.getTalkerID();
+
+  char call[LONG_CALLSIGN_LENGTH];
+  char text[SLOW_DATA_TEXT_LENGTH];
+  if ((number != 0) &&
+      (store.getCredentialsForID(number, call, text)))
+  {
+    asprintf(&talker, "%s %s", call, text);
+    return talker;
+  }
+
+  asprintf(&talker, "DMR ID: %d", number);
+  return talker;
+}
+
+void BrandMeisterBridge::setTalker(const char* call, const char* name)
+{
+  if (*call == '*')
+  {
+    // Do not process conference call-sign
+    return;
+  }
+
+  const char* delimiter = strpbrk(call, " -\n");
+  if (delimiter != NULL)
+  {
+    // Remove characters after call-sign
+    size_t length = delimiter - call;
+    char* buffer = (char*)alloca(length + 1);
+    strncpy(buffer, call, length);
+    call = buffer;
+  }
+
+  uint32_t number = store.getPrivateIDForCall(call);
+  if (number == 0)
+    number = ECHOLINK_DEFAULT_USER_NUMBER;
+
+  syslog(LOG_INFO, "Set talker ID to %d for call-sign %s", number, call);
+  proxy.setTalkerID(number);
+}
+
+void BrandMeisterBridge::handleChatMessage(const char* text)
+{
+  // CONF Russian Reflector, Open 24/7, Contacts: rv3dhc.link@qip.ru * Call CQ / Use pauses 2sec * [28/500]
+  // R3ABM-L *DSTAR.SU DMR Bridge*
+  // UB3AMO Moscow T I N A O
+  // ->UA0LQE-L USSURIISK
+
+  if (strncmp(text, "CONF ", 5) == 0)
+  {
+    const char* delimiter = strstr(text, "\n->");
+    if (delimiter != NULL)
+    {
+      const char* call = delimiter + 3;
+      setTalker(call, NULL);
+    }
+    else
+    {
+      uint32_t number = ECHOLINK_DEFAULT_USER_NUMBER;
+      syslog(LOG_INFO, "Set talker ID to %d (call-sign was not fit into chat message)", number);
+      proxy.setTalkerID(number);
+    }
+  }
+}
+
diff --git a/SVXLink/echolink/BrandMeisterBridge.h b/SVXLink/echolink/BrandMeisterBridge.h
new file mode 100644
index 0000000..554bbe8
--- /dev/null
+++ b/SVXLink/echolink/BrandMeisterBridge.h
@@ -0,0 +1,28 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#ifndef BRANDMEISTERBRIDGE_H
+#define BRANDMEISTERBRIDGE_H
+
+#include "PatchCordProxy.h"
+#include "UserDataStore.h"
+
+class BrandMeisterBridge
+{
+  public:
+
+    BrandMeisterBridge();
+    ~BrandMeisterBridge();
+
+    const char* getTalker();
+    void setTalker(const char* call, const char* name);
+    void handleChatMessage(const char* text);
+
+  private:
+
+    PatchCordProxy proxy;
+    UserDataStore store;
+    char* talker;
+
+};
+
+#endif
\ No newline at end of file
diff --git a/SVXLink/echolink/CMakeLists.txt b/SVXLink/echolink/CMakeLists.txt
new file mode 100644
index 0000000..6f89c10
--- /dev/null
+++ b/SVXLink/echolink/CMakeLists.txt
@@ -0,0 +1,59 @@
+# The name of the module without the Module prefix
+set(MODNAME EchoLink)
+
+# Module source code
+set(MODSRC QsoImpl.cpp)
+
+# ***
+pkg_check_modules(DBUS dbus-1)
+include_directories(${DBUS_INCLUDE_DIRS})
+set(LIBS ${LIBS} ${DBUS_LIBRARIES} mysqlclient)
+set(MODSRC ${MODSRC} PatchCordProxy.cpp UserDataStore.cpp BrandMeisterBridge.cpp)
+# ***
+
+# Project libraries to link to
+set(LIBS ${LIBS} echolib)
+
+# Find the TCL library
+if(TCL_LIBRARY)
+  set(TCL_LIBRARY_CACHED TRUE)
+endif(TCL_LIBRARY)
+find_package(TCL QUIET)
+if(TCL_FOUND)
+  if (NOT TCL_LIBRARY_CACHED)
+    message("-- Found TCL: ${TCL_LIBRARY}")
+  endif(NOT TCL_LIBRARY_CACHED)
+else(TCL_FOUND)
+  message(FATAL_ERROR "-- Could NOT find the TCL scripting language")
+endif(TCL_FOUND)
+set(LIBS ${LIBS} ${TCL_LIBRARY})
+include_directories(${TCL_INCLUDE_PATH})
+
+# Find the GSM codec library and include directory
+find_package(GSM REQUIRED)
+if(NOT GSM_FOUND)
+  message(FATAL_ERROR "libgsm not found")
+endif(NOT GSM_FOUND)
+include_directories(${GSM_INCLUDE_DIR})
+set(LIBS ${LIBS} ${GSM_LIBRARY})
+
+string(TOUPPER MODULE_${MODNAME} VERNAME)
+
+# Add targets for version files
+set(VERSION_DEPENDS)
+add_version_target(${VERNAME} VERSION_DEPENDS)
+add_version_target(SVXLINK VERSION_DEPENDS)
+
+# Build the plugin
+add_library(Module${MODNAME} MODULE Module${MODNAME}.cpp ${MODSRC}
+  ${VERSION_DEPENDS}
+)
+set_target_properties(Module${MODNAME} PROPERTIES PREFIX "")
+target_link_libraries(Module${MODNAME} ${LIBS})
+
+# Install targets
+install(TARGETS Module${MODNAME} DESTINATION ${SVX_MODULE_INSTALL_DIR})
+install(FILES ${MODNAME}.tcl DESTINATION ${SVX_SHARE_INSTALL_DIR}/events.d)
+install_if_not_exists(Module${MODNAME}.conf
+  ${SVX_SYSCONF_INSTALL_DIR}/svxlink.d
+  )
diff --git a/SVXLink/echolink/ModuleEchoLink.cpp b/SVXLink/echolink/ModuleEchoLink.cpp
new file mode 100644
index 0000000..2331e3b
--- /dev/null
+++ b/SVXLink/echolink/ModuleEchoLink.cpp
@@ -0,0 +1,2084 @@
+/**
+@file	 ModuleEchoLink.cpp
+@brief   A module that provides EchoLink connection possibility
+@author  Tobias Blomberg / SM0SVX
+@date	 2004-03-07
+
+\verbatim
+A module (plugin) for the multi purpose tranciever frontend system.
+Copyright (C) 2004-2014 Tobias Blomberg / SM0SVX
+
+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 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+\endverbatim
+*/
+
+/****************************************************************************
+ *
+ * System Includes
+ *
+ ****************************************************************************/
+
+#include <stdio.h>
+#include <time.h>
+
+#include <algorithm>
+#include <cassert>
+#include <sstream>
+#include <cstdlib>
+#include <vector>
+
+
+/****************************************************************************
+ *
+ * Project Includes
+ *
+ ****************************************************************************/
+
+#include <AsyncTimer.h>
+#include <AsyncConfig.h>
+#include <AsyncAudioSplitter.h>
+#include <AsyncAudioValve.h>
+#include <AsyncAudioSelector.h>
+#include <EchoLinkDirectory.h>
+#include <EchoLinkDispatcher.h>
+#include <EchoLinkProxy.h>
+#include <LocationInfo.h>
+#include <common.h>
+
+
+/****************************************************************************
+ *
+ * Local Includes
+ *
+ ****************************************************************************/
+
+#include "version/MODULE_ECHOLINK.h"
+#include "ModuleEchoLink.h"
+#include "QsoImpl.h"
+
+
+/****************************************************************************
+ *
+ * Namespaces to use
+ *
+ ****************************************************************************/
+
+using namespace std;
+using namespace sigc;
+using namespace Async;
+using namespace EchoLink;
+
+
+
+/****************************************************************************
+ *
+ * Defines & typedefs
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Local class definitions
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Prototypes
+ *
+ ****************************************************************************/
+
+
+
+
+/****************************************************************************
+ *
+ * Exported Global Variables
+ *
+ ****************************************************************************/
+
+
+
+
+/****************************************************************************
+ *
+ * Local Global Variables
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Pure C-functions
+ *
+ ****************************************************************************/
+
+
+extern "C" {
+  Module *module_init(void *dl_handle, Logic *logic, const char *cfg_name)
+  {
+    return new ModuleEchoLink(dl_handle, logic, cfg_name);
+  }
+} /* extern "C" */
+
+
+
+/****************************************************************************
+ *
+ * Public member functions
+ *
+ ****************************************************************************/
+
+
+ModuleEchoLink::ModuleEchoLink(void *dl_handle, Logic *logic,
+      	      	      	       const string& cfg_name)
+  : Module(dl_handle, logic, cfg_name), dir(0), dir_refresh_timer(0),
+    remote_activation(false), pending_connect_id(-1), last_message(""),
+    max_connections(1), max_qsos(1), talker(0), squelch_is_open(false),
+    state(STATE_NORMAL), cbc_timer(0), dbc_timer(0), drop_incoming_regex(0),
+    reject_incoming_regex(0), accept_incoming_regex(0),
+    reject_outgoing_regex(0), accept_outgoing_regex(0), splitter(0),
+    listen_only_valve(0), selector(0), num_con_max(0), num_con_ttl(5*60),
+    num_con_block_time(120*60), num_con_update_timer(0), reject_conf(false),
+    autocon_echolink_id(0), autocon_time(DEFAULT_AUTOCON_TIME),
+    autocon_timer(0), proxy(0)
+{
+  cout << "\tModule EchoLink v" MODULE_ECHOLINK_VERSION " starting...\n";
+  
+} /* ModuleEchoLink */
+
+
+ModuleEchoLink::~ModuleEchoLink(void)
+{
+  moduleCleanup();
+} /* ~ModuleEchoLink */
+
+
+bool ModuleEchoLink::initialize(void)
+{
+  if (!Module::initialize())
+  {
+    return false;
+  }
+  
+  vector<string> servers;
+  if (!cfg().getValue(cfgName(), "SERVERS", servers))
+  {
+    string server;
+    if (cfg().getValue(cfgName(), "SERVER", server))
+    {
+      cerr << "*** WARNING: Config variable " << cfgName()
+           << "/SERVER is deprecated. Use SERVERS instead.\n";
+      servers.push_back(server);
+    }
+  }
+  if (servers.empty())
+  {
+    cerr << "*** ERROR: Config variable " << cfgName() << "/SERVERS not set "
+            "or empty\n";
+    return false;
+  }
+  
+  if (!cfg().getValue(cfgName(), "CALLSIGN", mycall))
+  {
+    cerr << "*** ERROR: Config variable " << cfgName() << "/CALLSIGN not set\n";
+    return false;
+  }
+  if (mycall == "MYCALL-L")
+  {
+    cerr << "*** ERROR: Please set the EchoLink callsign (" << cfgName()
+      	 << "/CALLSIGN) to a real callsign\n";
+    return false;
+  }
+  
+  string password;
+  if (!cfg().getValue(cfgName(), "PASSWORD", password))
+  {
+    cerr << "*** ERROR: Config variable " << cfgName() << "/PASSWORD not set\n";
+    return false;
+  }
+  if (password == "MyPass")
+  {
+    cerr << "*** ERROR: Please set the EchoLink password (" << cfgName()
+      	 << "/PASSWORD) to a real password\n";
+    return false;
+  }
+  
+  if (!cfg().getValue(cfgName(), "LOCATION", location))
+  {
+    cerr << "*** ERROR: Config variable " << cfgName() << "/LOCATION not set\n";
+    return false;
+  }
+  
+  if (location.size() > Directory::MAX_DESCRIPTION_SIZE)
+  {
+    cerr << "*** WARNING: The value of " << cfgName() << "/LOCATION is too "
+      	    "long. Maximum length is " << Directory::MAX_DESCRIPTION_SIZE <<
+	    " characters.\n";
+    location.resize(Directory::MAX_DESCRIPTION_SIZE);
+  }
+  
+  if (!cfg().getValue(cfgName(), "SYSOPNAME", sysop_name))
+  {
+    cerr << "*** ERROR: Config variable " << cfgName()
+      	 << "/SYSOPNAME not set\n";
+    return false;
+  }
+  
+  if (!cfg().getValue(cfgName(), "DESCRIPTION", description))
+  {
+    cerr << "*** ERROR: Config variable " << cfgName()
+      	 << "/DESCRIPTION not set\n";
+    return false;
+  }
+  
+  string value;
+  if (cfg().getValue(cfgName(), "MAX_CONNECTIONS", value))
+  {
+    max_connections = atoi(value.c_str());
+  }
+  
+  if (cfg().getValue(cfgName(), "MAX_QSOS", value))
+  {
+    max_qsos = atoi(value.c_str());
+  }
+  
+  if (max_qsos > max_connections)
+  {
+    cerr << "*** ERROR: The value of " << cfgName() << "/MAX_CONNECTIONS ("
+      	 << max_connections << ") must be greater or equal to the value of "
+	 << cfgName() << "/MAX_QSOS (" << max_qsos << ").\n";
+    return false;
+  }
+  
+  cfg().getValue(cfgName(), "ALLOW_IP", allow_ip);
+  
+  if (!cfg().getValue(cfgName(), "DROP_INCOMING", value))
+  {
+    value = "^$";
+  }
+  drop_incoming_regex = new regex_t;
+  int err = regcomp(drop_incoming_regex, value.c_str(),
+                    REG_EXTENDED | REG_NOSUB | REG_ICASE);
+  if (err != 0)
+  {
+    size_t msg_size = regerror(err, drop_incoming_regex, 0, 0);
+    char msg[msg_size];
+    size_t err_size = regerror(err, drop_incoming_regex, msg, msg_size);
+    assert(err_size == msg_size);
+    cerr << "*** ERROR: Syntax error in " << cfgName() << "/DROP_INCOMING: "
+         << msg << endl;
+    moduleCleanup();
+    return false;
+  }
+  
+    // To reduce the number of senseless connects
+  if (cfg().getValue(cfgName(), "CHECK_NR_CONNECTS", value))
+  {
+    vector<unsigned> params;
+    if (SvxLink::splitStr(params, value, ",") != 3)
+    {
+       cerr << "*** ERROR: Syntax error in " << cfgName()
+            << "/CHECK_NR_CONNECTS\n"
+            << "Example: CHECK_NR_CONNECTS=3,300,60 where\n"
+            << "  3   = max number of connects\n"
+            << "  300 = time in seconds that a connection is remembered\n"
+            << "  60  = time in minutes that the party is blocked\n";
+       return false;
+    }
+    num_con_max = params[0];
+    num_con_ttl = params[1];
+    num_con_block_time = params[2] * 60;
+  }
+
+  if (!cfg().getValue(cfgName(), "REJECT_INCOMING", value))
+  {
+    value = "^$";
+  }
+  reject_incoming_regex = new regex_t;
+  err = regcomp(reject_incoming_regex, value.c_str(),
+                REG_EXTENDED | REG_NOSUB | REG_ICASE);
+  if (err != 0)
+  {
+    size_t msg_size = regerror(err, reject_incoming_regex, 0, 0);
+    char msg[msg_size];
+    size_t err_size = regerror(err, reject_incoming_regex, msg, msg_size);
+    assert(err_size == msg_size);
+    cerr << "*** ERROR: Syntax error in " << cfgName() << "/REJECT_INCOMING: "
+         << msg << endl;
+    moduleCleanup();
+    return false;
+  }
+  
+  if (!cfg().getValue(cfgName(), "ACCEPT_INCOMING", value))
+  {
+    value = "^.*$";
+  }
+  accept_incoming_regex = new regex_t;
+  err = regcomp(accept_incoming_regex, value.c_str(),
+                REG_EXTENDED | REG_NOSUB | REG_ICASE);
+  if (err != 0)
+  {
+    size_t msg_size = regerror(err, accept_incoming_regex, 0, 0);
+    char msg[msg_size];
+    size_t err_size = regerror(err, accept_incoming_regex, msg, msg_size);
+    assert(err_size == msg_size);
+    cerr << "*** ERROR: Syntax error in " << cfgName() << "/ACCEPT_INCOMING: "
+         << msg << endl;
+    moduleCleanup();
+    return false;
+  }
+
+  if (!cfg().getValue(cfgName(), "REJECT_OUTGOING", value))
+  {
+    value = "^$";
+  }
+  reject_outgoing_regex = new regex_t;
+  err = regcomp(reject_outgoing_regex, value.c_str(),
+                REG_EXTENDED | REG_NOSUB | REG_ICASE);
+  if (err != 0)
+  {
+    size_t msg_size = regerror(err, reject_outgoing_regex, 0, 0);
+    char msg[msg_size];
+    size_t err_size = regerror(err, reject_outgoing_regex, msg, msg_size);
+    assert(err_size == msg_size);
+    cerr << "*** ERROR: Syntax error in " << cfgName() << "/REJECT_OUTGOING: "
+         << msg << endl;
+    moduleCleanup();
+    return false;
+  }
+
+  if (!cfg().getValue(cfgName(), "ACCEPT_OUTGOING", value))
+  {
+    value = "^.*$";
+  }
+  accept_outgoing_regex = new regex_t;
+  err = regcomp(accept_outgoing_regex, value.c_str(),
+                REG_EXTENDED | REG_NOSUB | REG_ICASE);
+  if (err != 0)
+  {
+    size_t msg_size = regerror(err, accept_outgoing_regex, 0, 0);
+    char msg[msg_size];
+    size_t err_size = regerror(err, accept_outgoing_regex, msg, msg_size);
+    assert(err_size == msg_size);
+    cerr << "*** ERROR: Syntax error in " << cfgName() << "/ACCEPT_OUTGOING: "
+         << msg << endl;
+    moduleCleanup();
+    return false;
+  }
+  
+  cfg().getValue(cfgName(), "REJECT_CONF", reject_conf);
+  cfg().getValue(cfgName(), "AUTOCON_ECHOLINK_ID", autocon_echolink_id);
+  int autocon_time_secs = autocon_time / 1000;
+  cfg().getValue(cfgName(), "AUTOCON_TIME", autocon_time_secs);
+  autocon_time = 1000 * max(autocon_time_secs, 5); // At least five seconds
+
+  string proxy_server;
+  cfg().getValue(cfgName(), "PROXY_SERVER", proxy_server);
+  uint16_t proxy_port = 8100;
+  cfg().getValue(cfgName(), "PROXY_PORT", proxy_port);
+  string proxy_password;
+  cfg().getValue(cfgName(), "PROXY_PASSWORD", proxy_password);
+  
+  if (!proxy_server.empty())
+  {
+    proxy = new Proxy(proxy_server, proxy_port, mycall, proxy_password);
+    proxy->connect();
+  }
+
+  IpAddress bind_addr;
+  if (cfg().getValue(cfgName(), "BIND_ADDR", bind_addr) && bind_addr.isEmpty())
+  {
+    cerr << "*** ERROR: Invalid configuration value for " << cfgName()
+         << "/BIND_ADDR specified.\n";
+    moduleCleanup();
+    return false;
+  }
+
+    // Initialize directory server communication
+  dir = new Directory(servers, mycall, password, location, bind_addr);
+  dir->statusChanged.connect(mem_fun(*this, &ModuleEchoLink::onStatusChanged));
+  dir->stationListUpdated.connect(
+      	  mem_fun(*this, &ModuleEchoLink::onStationListUpdated));
+  dir->error.connect(mem_fun(*this, &ModuleEchoLink::onError));
+  dir->makeOnline();
+  
+    // Start listening to the EchoLink UDP ports
+  Dispatcher::setBindAddr(bind_addr);
+  if (Dispatcher::instance() == 0)
+  {
+    cerr << "*** ERROR: Could not create EchoLink listener (Dispatcher) "
+      	    "object\n";
+    moduleCleanup();
+    return false;
+  }
+  Dispatcher::instance()->incomingConnection.connect(
+      mem_fun(*this, &ModuleEchoLink::onIncomingConnection));
+
+    // Create audio pipe chain for audio transmitted to the remote EchoLink
+    // stations: <from core> -> Valve -> Splitter (-> QsoImpl ...)
+  listen_only_valve = new AudioValve;
+  AudioSink::setHandler(listen_only_valve);
+  
+  splitter = new AudioSplitter;
+  listen_only_valve->registerSink(splitter);
+
+    // Create audio pipe chain for audio received from the remove EchoLink
+    // stations: (QsoImpl -> ) Selector -> Fifo -> <to core>
+  selector = new AudioSelector;
+  AudioSource::setHandler(selector);
+  
+    // Periodic updates of the "watch num connects" list
+  if (num_con_max > 0)
+  {
+    num_con_update_timer = new Timer(6000000); // One hour
+    num_con_update_timer->expired.connect(sigc::hide(
+        mem_fun(*this, &ModuleEchoLink::numConUpdate)));
+  }
+
+  if (autocon_echolink_id > 0)
+  {
+      // Initially set the timer to 15 seconds for quick activation on statup
+    autocon_timer = new Timer(15000, Timer::TYPE_PERIODIC);
+    autocon_timer->expired.connect(
+        mem_fun(*this, &ModuleEchoLink::checkAutoCon));
+  }
+
+  return true;
+  
+} /* ModuleEchoLink::initialize */
+
+
+
+/****************************************************************************
+ *
+ * Protected member functions
+ *
+ ****************************************************************************/
+
+void ModuleEchoLink::logicIdleStateChanged(bool is_idle)
+{
+  /*
+  printf("ModuleEchoLink::logicIdleStateChanged: is_idle=%s\n",
+      is_idle ? "TRUE" : "FALSE");
+  */
+
+  if (qsos.size() > 0)
+  {
+    vector<QsoImpl*>::iterator it;
+    for (it=qsos.begin(); it!=qsos.end(); ++it)
+    {
+      (*it)->logicIdleStateChanged(is_idle);
+    }
+  }
+  
+  checkIdle();
+  
+} /* ModuleEchoLink::logicIdleStateChanged */
+
+
+
+/****************************************************************************
+ *
+ * Private member functions
+ *
+ ****************************************************************************/
+
+
+void ModuleEchoLink::moduleCleanup(void)
+{
+  //FIXME: Delete qso objects
+  
+  delete num_con_update_timer;
+  num_con_update_timer = 0;
+
+  if (accept_incoming_regex != 0)
+  {
+    regfree(accept_incoming_regex);
+    delete accept_incoming_regex;
+    accept_incoming_regex = 0;
+  }
+  if (reject_incoming_regex != 0)
+  {
+    regfree(reject_incoming_regex);
+    delete reject_incoming_regex;
+    reject_incoming_regex = 0;
+  }
+  if (drop_incoming_regex != 0)
+  {
+    regfree(drop_incoming_regex);
+    delete drop_incoming_regex;
+    drop_incoming_regex = 0;
+  }
+  if (accept_outgoing_regex != 0)
+  {
+    regfree(accept_outgoing_regex);
+    delete accept_outgoing_regex;
+    accept_outgoing_regex = 0;
+  }
+  if (reject_outgoing_regex != 0)
+  {
+    regfree(reject_outgoing_regex);
+    delete reject_outgoing_regex;
+    reject_outgoing_regex = 0;
+  }
+  
+  delete dir_refresh_timer;
+  dir_refresh_timer = 0;
+  Dispatcher::deleteInstance();
+  delete dir;
+  dir = 0;
+  delete proxy;
+  proxy = 0;
+  delete cbc_timer;
+  cbc_timer = 0;
+  delete dbc_timer;
+  dbc_timer = 0;
+  state = STATE_NORMAL;
+  delete autocon_timer;
+  autocon_timer = 0;
+  
+  AudioSink::clearHandler();
+  delete splitter;
+  splitter = 0;
+  delete listen_only_valve;
+  listen_only_valve = 0;
+  
+  AudioSource::clearHandler();
+  delete selector;
+  selector = 0;
+} /* ModuleEchoLink::moduleCleanup */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    activateInit
+ * Purpose:   Called by the core system when this module is activated.
+ * Input:     None
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::activateInit(void)
+{
+  updateEventVariables();
+  state = STATE_NORMAL;
+  listen_only_valve->setOpen(true);
+} /* activateInit */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    deactivateCleanup
+ * Purpose:   Called by the core system when this module is deactivated.
+ * Input:     None
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:   Do NOT call this function directly unless you really know what
+ *    	      you are doing. Use Module::deactivate() instead.
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::deactivateCleanup(void)
+{
+  vector<QsoImpl*> qsos_tmp(qsos);
+  vector<QsoImpl*>::iterator it;
+  for (it=qsos_tmp.begin(); it!=qsos_tmp.end(); ++it)
+  {
+    if ((*it)->currentState() != Qso::STATE_DISCONNECTED)
+    {
+      (*it)->disconnect();
+    }
+  }
+
+  outgoing_con_pending.clear();
+
+  remote_activation = false;
+  delete cbc_timer;
+  cbc_timer = 0;
+  delete dbc_timer;
+  dbc_timer = 0;
+  state = STATE_NORMAL;
+  listen_only_valve->setOpen(true);
+} /* deactivateCleanup */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    dtmfDigitReceived
+ * Purpose:   Called by the core system when a DTMF digit has been
+ *    	      received.
+ * Input:     digit   	- The DTMF digit received (0-9, A-D, *, #)
+ *            duration	- The length in milliseconds of the received digit
+ * Output:    Return true if the digit is handled or false if not
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+#if 0
+bool ModuleEchoLink::dtmfDigitReceived(char digit, int duration)
+{
+  //cout << "DTMF digit received in module " << name() << ": " << digit << endl;
+  
+  return false;
+  
+} /* dtmfDigitReceived */
+#endif
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    dtmfCmdReceived
+ * Purpose:   Called by the core system when a DTMF command has been
+ *    	      received. A DTMF command consists of a string of digits ended
+ *    	      with a number sign (#). The number sign is not included in the
+ *    	      command string.
+ * Input:     cmd - The received command.
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::dtmfCmdReceived(const string& cmd)
+{
+  cout << "DTMF command received in module " << name() << ": " << cmd << endl;
+  
+  remote_activation = false;
+  
+  if (state == STATE_CONNECT_BY_CALL)
+  {
+    handleConnectByCall(cmd);
+    return;
+  }
+  
+  if (state == STATE_DISCONNECT_BY_CALL)
+  {
+    handleDisconnectByCall(cmd);
+    return;
+  }
+  
+  if (cmd.size() == 0)      	    // Disconnect node or deactivate module
+  {
+    if ((qsos.size() != 0) &&
+      	(qsos.back()->currentState() != Qso::STATE_DISCONNECTED))
+    {
+      qsos.back()->disconnect();
+    }
+    else if (outgoing_con_pending.empty())
+    {
+      deactivateMe();
+    }
+  }
+  /*
+  else if (cmd[0] == '*')   // Connect by callsign
+  {
+    connectByCallsign(cmd);
+  }
+  */
+  else if ((cmd.size() < 4) || (cmd[1] == '*'))  // Dispatch to command handling
+  {
+    handleCommand(cmd);
+  }
+  else
+  {
+    connectByNodeId(atoi(cmd.c_str()));
+  }
+} /* dtmfCmdReceived */
+
+
+void ModuleEchoLink::dtmfCmdReceivedWhenIdle(const std::string &cmd)
+{
+  if (cmd == "2")   // Play own node id
+  {
+    stringstream ss;
+    ss << "play_node_id ";
+    const StationData *station = dir->findCall(dir->callsign());
+    ss << (station ? station->id() : 0);
+    processEvent(ss.str());
+  }
+  else
+  {
+    commandFailed(cmd);
+  }
+} /* dtmfCmdReceivedWhenIdle */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    squelchOpen
+ * Purpose:   Called by the core system when activity is detected
+ *    	      on the receiver.
+ * Input:     is_open - true if the squelch is open or else false.
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::squelchOpen(bool is_open)
+{
+  //printf("RX squelch is %s...\n", is_open ? "open" : "closed");
+  
+  squelch_is_open = is_open;
+  if (listen_only_valve->isOpen())
+  {
+    broadcastTalkerStatus();
+  }
+
+  for (vector<QsoImpl*>::iterator it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    (*it)->squelchOpen(is_open);
+  }
+} /* squelchOpen */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    allMsgsWritten
+ * Purpose:   Called by the core system when all audio messages queued
+ *            for playing have been played.
+ * Input:     None
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-05-22
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::allMsgsWritten(void)
+{
+  if (!outgoing_con_pending.empty())
+  {
+    vector<QsoImpl*>::iterator it;
+    for (it=outgoing_con_pending.begin(); it!=outgoing_con_pending.end(); ++it)
+    {
+      (*it)->connect();
+    }
+    //outgoing_con_pending->connect();
+    updateDescription();
+    broadcastTalkerStatus();
+    outgoing_con_pending.clear();
+  }
+} /* allMsgsWritten */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    onStatusChanged
+ * Purpose:   Called by the EchoLink::Directory object when the status of
+ *    	      the registration is changed in the directory server.
+ * Input:     status  - The new status
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::onStatusChanged(StationData::Status status)
+{
+  cout << "EchoLink directory status changed to "
+       << StationData::statusStr(status) << endl;
+  
+    // Get the directory list on first connection to the directory server
+  if ((status == StationData::STAT_ONLINE) ||
+      (status == StationData::STAT_BUSY))
+  {
+    if (dir_refresh_timer == 0)
+    {
+      getDirectoryList();
+    }
+  }
+  else
+  {
+    delete dir_refresh_timer;
+    dir_refresh_timer = 0;
+  }
+  
+    // Update status at aprs.echolink.org
+  if (LocationInfo::has_instance())
+  {
+    LocationInfo::instance()->updateDirectoryStatus(status);
+  }
+} /* onStatusChanged */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    onStationListUpdated
+ * Purpose:   Called by the EchoLink::Directory object when the station list
+ *    	      has been updated.
+ * Input:     None
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::onStationListUpdated(void)
+{
+  if (pending_connect_id > 0)
+  {
+    const StationData *station = dir->findStation(pending_connect_id);
+    if (station != 0)
+    {
+      createOutgoingConnection(*station);
+    }
+    else
+    {
+      cout << "The EchoLink ID " << pending_connect_id
+      	   << " could not be found.\n";
+      stringstream ss;
+      ss << "station_id_not_found " << pending_connect_id;
+      processEvent(ss.str());
+    }
+    pending_connect_id = -1;
+  }
+  
+  if (dir->message() != last_message)
+  {
+    cout << "--- EchoLink directory server message: ---" << endl;
+    cout << dir->message() << endl;
+    last_message = dir->message();
+  }
+} /* onStationListUpdated */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    onError
+ * Purpose:   Called by the EchoLink::Directory object when a communication
+ *    	      error occurs.
+ * Input:     msg - The error message
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::onError(const string& msg)
+{
+  cerr << "*** ERROR: " << msg << endl;
+  
+  if (pending_connect_id > 0)
+  {
+    stringstream ss;
+    ss << "lookup_failed " << pending_connect_id;
+    processEvent(ss.str());
+  }
+  
+} /* onError */
+
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    onIncomingConnection
+ * Purpose:   Called by the EchoLink::Dispatcher object when a new remote
+ *    	      connection is coming in.
+ * Input:     callsign	- The callsign of the remote station
+ *    	      name    	- The name of the remote station
+ *            priv      - A private string for passing connection parameters
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::onIncomingConnection(const IpAddress& ip,
+      	      	      	      	      	  const string& callsign,
+      	      	      	      	      	  const string& name,
+      	      	      	      	      	  const string& priv)
+{
+  cout << "Incoming EchoLink connection from " << callsign
+       << " (" << name << ") at " << ip << "\n";
+  
+  if (regexec(drop_incoming_regex, callsign.c_str(), 0, 0, 0) == 0)
+  {
+    cerr << "*** WARNING: Dropping incoming connection due to configuration.\n";
+    return;
+  }
+  
+  if (qsos.size() >= max_connections)
+  {
+    cerr << "*** WARNING: Ignoring incoming connection (too many "
+      	    "connections)\n";
+    return;
+  }
+  
+  const StationData *station;
+  StationData tmp_stn_data;
+  if (ip.isWithinSubet(allow_ip))
+  {
+    tmp_stn_data.setIp(ip);
+    tmp_stn_data.setCallsign(callsign);
+    station = &tmp_stn_data;
+  }
+  else
+  {
+      // Check if the incoming callsign is valid
+    station = dir->findCall(callsign);
+    if (station == 0)
+    {
+      getDirectoryList();
+      return;
+    }
+  }
+  
+  if (station->ip() != ip)
+  {
+    cerr << "*** WARNING: Ignoring incoming connection from " << callsign
+      	 << " since the IP address registered in the directory server "
+	 << "(" << station->ip() << ") is not the same as the remote IP "
+	 << "address (" << ip << ") of the incoming connection\n";
+    getDirectoryList();
+    return;
+  }
+
+    // Create a new Qso object to accept the connection
+  QsoImpl *qso = new QsoImpl(*station, this);
+  if (!qso->initOk())
+  {
+    delete qso;
+    cerr << "*** ERROR: Creation of Qso object failed\n";
+    return;
+  }
+  qsos.push_back(qso);
+  updateEventVariables();
+  qso->setRemoteCallsign(callsign);
+  qso->setRemoteName(name);
+  qso->setRemoteParams(priv);
+  qso->setListenOnly(!listen_only_valve->isOpen());
+  qso->stateChange.connect(mem_fun(*this, &ModuleEchoLink::onStateChange));
+  qso->chatMsgReceived.connect(
+          mem_fun(*this, &ModuleEchoLink::onChatMsgReceived));
+  qso->isReceiving.connect(mem_fun(*this, &ModuleEchoLink::onIsReceiving));
+  qso->audioReceivedRaw.connect(
+      	  mem_fun(*this, &ModuleEchoLink::audioFromRemoteRaw));
+  qso->destroyMe.connect(mem_fun(*this, &ModuleEchoLink::destroyQsoObject));
+
+  splitter->addSink(qso);
+  selector->addSource(qso);
+  selector->enableAutoSelect(qso, 0);
+
+  if (qsos.size() > max_qsos)
+  {
+    qso->reject(false);
+    return;
+  }
+  
+    // Check if it is a station that connects very often senselessly
+  if ((num_con_max > 0) && !numConCheck(callsign))
+  {
+    qso->reject(false);
+    return;
+  }
+
+  if ((regexec(reject_incoming_regex, callsign.c_str(), 0, 0, 0) == 0) ||
+      (regexec(accept_incoming_regex, callsign.c_str(), 0, 0, 0) != 0) ||
+      (reject_conf && (name.size() > 3) &&
+       (name.rfind("CONF") == (name.size()-4))))
+  {
+    qso->reject(true);
+    return;
+  }
+
+  if (!isActive())
+  {
+    remote_activation = true;
+  }
+  
+  if (!activateMe())
+  {
+    qso->reject(false);
+    cerr << "*** WARNING: Could not accept incoming connection from "
+      	 << callsign
+	 << " since the frontend was busy doing something else.\n";
+    return;
+  }
+  
+  qso->accept();
+  broadcastTalkerStatus();
+  updateDescription();
+
+  if (LocationInfo::has_instance())
+  {
+    list<string> call_list;
+    listQsoCallsigns(call_list);
+    
+    LocationInfo::instance()->updateQsoStatus(2, callsign, name, call_list);
+  }
+  
+  checkIdle();
+  
+} /* onIncomingConnection */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    onStateChange
+ * Purpose:   Called by the EchoLink::QsoImpl object when a state change has
+ *    	      occured on the connection.
+ * Input:     qso     	- The QSO object
+ *    	      qso_state - The new QSO connection state
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2006-03-12
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::onStateChange(QsoImpl *qso, Qso::State qso_state)
+{
+  switch (qso_state)
+  {
+    case Qso::STATE_DISCONNECTED:
+    {
+      vector<QsoImpl*>::iterator it = find(qsos.begin(), qsos.end(), qso);
+      assert (it != qsos.end());
+      qsos.erase(it);
+      it=qsos.begin();
+      qsos.insert(it,qso);
+      updateEventVariables();
+      
+      if (!qso->connectionRejected())
+      {
+        last_disc_stn = qso->stationData();
+      }
+              
+      if (remote_activation &&
+      	  (qsos.back()->currentState() == Qso::STATE_DISCONNECTED))
+      {
+      	deactivateMe();
+      }
+
+      if (autocon_timer != 0)
+      {
+        autocon_timer->setTimeout(autocon_time);
+      }
+
+      broadcastTalkerStatus();
+      updateDescription();
+      break;
+    }
+    
+    default:
+      updateEventVariables();
+      break;
+  }
+} /* ModuleEchoLink::onStateChange */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    onChatMsgReceived
+ * Purpose:   Called by the EchoLink::Qso object when a chat message has been
+ *    	      received from the remote station.
+ * Input:     qso - The QSO object
+ *    	      msg - The received message
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-05-04
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::onChatMsgReceived(QsoImpl *qso, const string& msg)
+{
+  //cout << "--- EchoLink chat message received from " << qso->remoteCallsign()
+  //     << " ---" << endl
+  //     << msg << endl;
+  
+  vector<QsoImpl*>::iterator it;
+  for (it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    if (*it != qso)
+    {
+      (*it)->sendChatData(msg);
+    }
+  }
+
+    // Escape TCL control characters
+  string escaped(msg);
+  replaceAll(escaped, "\\", "\\\\");
+  replaceAll(escaped, "{", "\\{");
+  replaceAll(escaped, "}", "\\}");
+  stringstream ss;
+    // FIXME: This TCL specific code should not be here
+  ss << "chat_received [subst -nocommands -novariables {";
+  ss << escaped;
+  ss << "}]";
+  bridge.handleChatMessage(escaped.c_str());
+  processEvent(ss.str());
+} /* onChatMsgReceived */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    onIsReceiving
+ * Purpose:   Called by the EchoLink::Qso object to indicate whether the
+ *    	      remote station is transmitting or not.
+ * Input:     qso     	    - The QSO object
+ *    	      is_receiving  - true=remote station is transmitting
+ *    	      	      	      false=remote station is not transmitting
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::onIsReceiving(bool is_receiving, QsoImpl *qso)
+{
+  //cerr << qso->remoteCallsign() << ": EchoLink receiving: "
+  //     << (is_receiving ? "TRUE" : "FALSE") << endl;
+  
+  stringstream ss;
+  ss << "is_receiving " << (is_receiving ? "1" : "0");
+  processEvent(ss.str());
+
+  if ((talker == 0) && is_receiving)
+  {
+    if (reject_conf)
+    {
+      string name = qso->remoteName();
+      if ((name.size() > 3) && (name.rfind("CONF") == (name.size()-4)))
+      {
+	qso->sendChatData("Connects from a conference are not allowed");
+	qso->disconnect();
+	return;
+      }
+    }
+    talker = qso;
+    broadcastTalkerStatus();
+  }
+  
+  if (talker == qso)
+  {
+    if (!is_receiving)
+    {
+      talker = findFirstTalker();
+      if (talker != 0)
+      {
+      	is_receiving = true;
+      }
+      broadcastTalkerStatus();
+    }
+  }
+} /* onIsReceiving */
+
+
+void ModuleEchoLink::destroyQsoObject(QsoImpl *qso)
+{
+  //cout << qso->remoteCallsign() << ": Destroying QSO object" << endl;
+  string callsign = qso->remoteCallsign();
+
+  splitter->removeSink(qso);
+  selector->removeSource(qso);
+      
+  vector<QsoImpl*>::iterator it = find(qsos.begin(), qsos.end(), qso);
+  assert (it != qsos.end());
+  qsos.erase(it);
+
+  updateEventVariables();
+  delete qso;
+  
+  if (talker == qso)
+  {
+    talker = findFirstTalker();
+  }
+
+  it = find(outgoing_con_pending.begin(), outgoing_con_pending.end(), qso);
+  if (it != outgoing_con_pending.end())
+  {
+    outgoing_con_pending.erase(it);
+  }
+
+  qso = 0;
+  
+  //broadcastTalkerStatus();
+  //updateDescription();
+
+  if (LocationInfo::has_instance())
+  {
+    list<string> call_list;
+    listQsoCallsigns(call_list);
+    
+    LocationInfo::instance()->updateQsoStatus(0, callsign, "", call_list);
+  }
+
+  checkIdle();
+  
+} /* ModuleEchoLink::destroyQsoObject */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    getDirectoryList
+ * Purpose:   Initiate the process of getting a directory list from the
+ *    	      directory server. A timer is also setup to automatically
+ *    	      refresh the directory listing.
+ * Input:     timer - The timer instance (not used)
+ * Output:    None
+ * Author:    Tobias Blomberg / SM0SVX
+ * Created:   2004-03-07
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::getDirectoryList(Timer *timer)
+{
+  delete dir_refresh_timer;
+  dir_refresh_timer = 0;
+  
+  if ((dir->status() == StationData::STAT_ONLINE) ||
+      (dir->status() == StationData::STAT_BUSY))
+  {
+    dir->getCalls();
+
+    dir_refresh_timer = new Timer(600000);
+    dir_refresh_timer->expired.connect(
+      	    mem_fun(*this, &ModuleEchoLink::getDirectoryList));
+  }
+} /* ModuleEchoLink::getDirectoryList */
+
+
+void ModuleEchoLink::createOutgoingConnection(const StationData &station)
+{
+  if (station.callsign() == mycall)
+  {
+    cerr << "Cannot connect to myself (" << mycall << "/" << station.id()
+      	 << ")...\n";
+    processEvent("self_connect");
+    return;
+  }
+
+  if ((regexec(reject_outgoing_regex, station.callsign().c_str(),
+	       0, 0, 0) == 0) ||
+      (regexec(accept_outgoing_regex, station.callsign().c_str(),
+	       0, 0, 0) != 0))
+  {
+    cerr << "Rejecting outgoing connection to " << station.callsign() << " ("
+	 << station.id() << ")\n";
+    stringstream ss;
+    ss << "reject_outgoing_connection " << station.callsign();
+    processEvent(ss.str());
+    return;
+  }
+
+  if (qsos.size() >= max_qsos)
+  {
+    cerr << "Couldn't connect to " << station.callsign() << " due to the "
+         << "number of active connections (" << qsos.size() << " > "
+         << max_qsos << ")" << endl;
+    processEvent("no_more_connections_allowed");
+    return;
+  }
+
+  cout << "Connecting to " << station.callsign() << " (" << station.id()
+       << ")\n";
+  
+  QsoImpl *qso = 0;
+  
+  vector<QsoImpl*>::iterator it;
+  for (it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    if ((*it)->remoteCallsign() == station.callsign())
+    {
+      if ((*it)->currentState() != Qso::STATE_DISCONNECTED)
+      {
+	cerr << "*** WARNING: Already connected to " << station.callsign()
+      	     << ". Ignoring connect request.\n";
+	stringstream ss;
+	ss << "already_connected_to " << station.callsign();
+	processEvent(ss.str());
+	return;
+      }
+      qso = *it;
+      qsos.erase(it);
+      qsos.push_back(qso);
+      break;
+    }
+  }
+
+  if (qso == 0)
+  {
+    qso = new QsoImpl(station, this);
+    if (!qso->initOk())
+    {
+      delete qso;
+      cerr << "*** ERROR: Creation of Qso failed\n";
+      processEvent("internal_error");
+      return;
+    }
+    qsos.push_back(qso);
+    updateEventVariables();
+    qso->setRemoteCallsign(station.callsign());
+    qso->setListenOnly(!listen_only_valve->isOpen());
+    qso->stateChange.connect(mem_fun(*this, &ModuleEchoLink::onStateChange));
+    qso->chatMsgReceived.connect(
+        mem_fun(*this, &ModuleEchoLink::onChatMsgReceived));
+    qso->isReceiving.connect(mem_fun(*this, &ModuleEchoLink::onIsReceiving));
+    qso->audioReceivedRaw.connect(
+      	    mem_fun(*this, &ModuleEchoLink::audioFromRemoteRaw));
+    qso->destroyMe.connect(mem_fun(*this, &ModuleEchoLink::destroyQsoObject));
+
+    splitter->addSink(qso);
+    selector->addSource(qso);
+    selector->enableAutoSelect(qso, 0);
+  }
+    
+  stringstream ss;
+  ss << "connecting_to " << qso->remoteCallsign();
+  processEvent(ss.str());
+  outgoing_con_pending.push_back(qso);
+  
+  if (LocationInfo::has_instance())
+  {
+    stringstream info;
+    info << station.id();
+
+    list<string> call_list;
+    listQsoCallsigns(call_list);
+
+    LocationInfo::instance()->updateQsoStatus(1, station.callsign(),
+                                              info.str(), call_list);
+  }
+    
+  checkIdle();
+  
+} /* ModuleEchoLink::createOutgoingConnection */
+
+
+void ModuleEchoLink::audioFromRemoteRaw(Qso::RawPacket *packet,
+      	QsoImpl *qso)
+{
+  if (!listen_only_valve->isOpen())
+  {
+    return;
+  }
+
+  if ((qso == talker) && !squelch_is_open)
+  {
+    vector<QsoImpl*>::iterator it;
+    for (it=qsos.begin(); it!=qsos.end(); ++it)
+    {
+      if (*it != qso)
+      {
+	(*it)->sendAudioRaw(packet);
+      }
+    }
+  }
+} /* ModuleEchoLink::audioFromRemoteRaw */
+
+
+QsoImpl *ModuleEchoLink::findFirstTalker(void) const
+{
+  vector<QsoImpl*>::const_iterator it;
+  for (it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    if ((*it)->receivingAudio())
+    {
+      return *it;
+    }
+  }
+  
+  return 0;
+  
+} /* ModuleEchoLink::findFirstTalker */
+
+
+void ModuleEchoLink::broadcastTalkerStatus(void)
+{
+  if (max_qsos < 2)
+  {
+    return;
+  }
+  
+  stringstream msg;
+  msg << "SvxLink " << SVXLINK_VERSION << " - " << mycall
+      << " (" << numConnectedStations() << ")\n\n";
+
+  if (squelch_is_open && listen_only_valve->isOpen())
+  {
+    const char* sysop_name = bridge.getTalker();
+    msg << "> " << mycall << "         " << sysop_name << "\n\n";
+  }
+  else
+  {
+    if (talker != 0)
+    {
+      msg << "> " << talker->remoteCallsign() << "         "
+      	  << talker->remoteName() << "\n\n";
+      bridge.setTalker(talker->remoteCallsign().c_str(), talker->remoteName().c_str());
+    }
+    msg << mycall << "         ";
+    if (!listen_only_valve->isOpen())
+    {
+      msg << "[listen only] ";
+    }
+    msg << sysop_name << "\n";
+  }
+  
+  vector<QsoImpl*>::const_iterator it;
+  for (it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    if ((*it)->currentState() == Qso::STATE_DISCONNECTED)
+    {
+      continue;
+    }
+    if ((*it != talker) || squelch_is_open)
+    {
+      msg << (*it)->remoteCallsign() << "         "
+      	  << (*it)->remoteName() << "\n";
+    }
+  }
+  
+  for (it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    (*it)->sendInfoData(msg.str());
+  }
+  
+} /* ModuleEchoLink::broadcastTalkerStatus */
+
+
+void ModuleEchoLink::updateDescription(void)
+{
+  if (max_qsos < 2)
+  {
+    return;
+  }
+  
+  string desc(location);
+  if (numConnectedStations() > 0)
+  {
+    stringstream sstr;
+    sstr << " (" << numConnectedStations() << ")";
+    desc.resize(Directory::MAX_DESCRIPTION_SIZE - sstr.str().size(), ' ');
+    desc += sstr.str();
+  }
+    
+  dir->setDescription(desc);
+  dir->refreshRegistration();
+  
+} /* ModuleEchoLink::updateDescription */
+
+
+void ModuleEchoLink::updateEventVariables(void)
+{
+  stringstream ss;
+  ss << numConnectedStations();
+  string var_name(name());
+  var_name +=  "::num_connected_stations";
+  setEventVariable(var_name, ss.str());
+} /* ModuleEchoLink::updateEventVariables */
+
+
+void ModuleEchoLink::connectByCallsign(string cmd)
+{
+  stringstream ss;
+
+  if (cmd.length() < 5)
+  {
+    ss << "cbc_too_short_cmd " << cmd;
+    processEvent(ss.str());
+    return;
+  }
+
+  string code;
+  bool exact;
+  if (cmd[cmd.size()-1] == '*')
+  {
+    code = cmd.substr(2, cmd.size() - 3);
+    exact = false;
+  }
+  else
+  {
+    code = cmd.substr(2);
+    exact = true;
+  }
+
+  cout << "Looking up callsign code: " << code << " "
+       << (exact ? "(exact match)" : "(wildcard match)") << endl;
+  dir->findStationsByCode(cbc_stns, code, exact);
+  cout << "Found " << cbc_stns.size() << " stations:\n";
+  StnList::const_iterator it;
+  int cnt = 0;
+  for (it = cbc_stns.begin(); it != cbc_stns.end(); ++it)
+  {
+    cout << *it << endl;
+    if (++cnt >= 9)
+    {
+      break;
+    }
+  }
+
+  if (cbc_stns.size() == 0)
+  {
+    ss << "cbc_no_match " << code;
+    processEvent(ss.str());
+    return;
+  }
+
+  if (cbc_stns.size() > 9)
+  {
+    cout << "Too many matches. The search must be narrowed down.\n";
+    processEvent("cbc_too_many_matches");
+    return;
+  }
+
+  ss << "cbc_list [list";
+  for (it = cbc_stns.begin(); it != cbc_stns.end(); ++it)
+  {
+    ss << " " << (*it).callsign();
+  }
+  ss << "]";
+  processEvent(ss.str());
+  state = STATE_CONNECT_BY_CALL;
+  delete cbc_timer;
+  cbc_timer = new Timer(60000);
+  cbc_timer->expired.connect(mem_fun(*this, &ModuleEchoLink::cbcTimeout));
+
+} /* ModuleEchoLink::connectByCallsign */
+
+
+void ModuleEchoLink::handleConnectByCall(const string& cmd)
+{
+  if (cmd.empty())
+  {
+    processEvent("cbc_aborted");
+    cbc_stns.clear();
+    delete cbc_timer;
+    cbc_timer = 0;
+    state = STATE_NORMAL;
+    return;
+  }
+  
+  unsigned idx = static_cast<unsigned>(atoi(cmd.c_str()));
+  stringstream ss;
+
+  if (idx == 0)
+  {
+    ss << "cbc_list [list";
+    StnList::const_iterator it;
+    for (it = cbc_stns.begin(); it != cbc_stns.end(); ++it)
+    {
+      ss << " " << (*it).callsign();
+    }
+    ss << "]";
+    processEvent(ss.str());
+    cbc_timer->reset();
+    return;
+  }
+
+  if (idx > cbc_stns.size())
+  {
+    ss << "cbc_index_out_of_range " << idx;
+    processEvent(ss.str());
+    cbc_timer->reset();
+    return;
+  }
+
+  createOutgoingConnection(cbc_stns[idx-1]);
+  cbc_stns.clear();
+  delete cbc_timer;
+  cbc_timer = 0;
+  state = STATE_NORMAL;
+} /* ModuleEchoLink::handleConnectByCall  */
+
+
+void ModuleEchoLink::cbcTimeout(Timer *t)
+{
+  delete cbc_timer;
+  cbc_timer = 0;
+  cbc_stns.clear();
+  state = STATE_NORMAL;
+  cout << "Connect by call command timeout\n";
+  processEvent("cbc_timeout");
+} /* ModuleEchoLink::cbcTimeout  */
+
+
+void ModuleEchoLink::disconnectByCallsign(const string &cmd)
+{
+  if ((cmd.size() != 1) || qsos.empty())
+  {
+    commandFailed(cmd);
+    return;
+  }
+
+  stringstream ss;
+  ss << "dbc_list [list";
+  vector<QsoImpl*>::iterator it;
+  for (it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    if ((*it)->currentState() != Qso::STATE_DISCONNECTED)
+    {
+    	ss << " " << (*it)->remoteCallsign();
+    }
+  }
+  ss << "]";
+  processEvent(ss.str());
+  state = STATE_DISCONNECT_BY_CALL;
+  delete dbc_timer;
+  dbc_timer = new Timer(60000);
+  dbc_timer->expired.connect(mem_fun(*this, &ModuleEchoLink::dbcTimeout));
+} /* ModuleEchoLink::disconnectByCallsign  */
+
+
+void ModuleEchoLink::handleDisconnectByCall(const string& cmd)
+{
+  if (cmd.empty())
+  {
+    processEvent("dbc_aborted");
+    delete dbc_timer;
+    dbc_timer = 0;
+    state = STATE_NORMAL;
+    return;
+  }
+  
+  unsigned idx = static_cast<unsigned>(atoi(cmd.c_str()));
+  stringstream ss;
+
+  if (idx == 0)
+  {
+    ss << "dbc_list [list";
+    vector<QsoImpl*>::const_iterator it;
+    for (it = qsos.begin(); it != qsos.end(); ++it)
+    {
+      ss << " " << (*it)->remoteCallsign();
+    }
+    ss << "]";
+    processEvent(ss.str());
+    dbc_timer->reset();
+    return;
+  }
+
+  if (idx > qsos.size())
+  {
+    ss << "dbc_index_out_of_range " << idx;
+    processEvent(ss.str());
+    dbc_timer->reset();
+    return;
+  }
+
+  qsos[idx-1]->disconnect();
+  delete dbc_timer;
+  dbc_timer = 0;
+  state = STATE_NORMAL;
+
+} /* ModuleEchoLink::handleDisconnectByCall  */
+
+
+void ModuleEchoLink::dbcTimeout(Timer *t)
+{
+  delete dbc_timer;
+  dbc_timer = 0;
+  state = STATE_NORMAL;
+  cout << "Disconnect by call command timeout\n";
+  processEvent("dbc_timeout");
+} /* ModuleEchoLink::dbcTimeout  */
+
+
+int ModuleEchoLink::numConnectedStations(void)
+{
+  int cnt = 0;
+  vector<QsoImpl*>::iterator it;
+  for (it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    if ((*it)->currentState() != Qso::STATE_DISCONNECTED)
+    {
+      ++cnt;
+    }
+  }
+  
+  return cnt;
+  
+} /* ModuleEchoLink::numConnectedStations */
+
+
+int ModuleEchoLink::listQsoCallsigns(list<string>& call_list)
+{
+  call_list.clear();
+  vector<QsoImpl*>::iterator it;
+  for (it=qsos.begin(); it!=qsos.end(); ++it)
+  {
+    call_list.push_back((*it)->remoteCallsign());
+  }
+  
+  return call_list.size();
+  
+} /* ModuleEchoLink::listQsoCallsigns */
+
+
+void ModuleEchoLink::handleCommand(const string& cmd)
+{
+  if (cmd[0] == '0')	    // Help
+  {
+    playHelpMsg();
+  }
+  else if (cmd[0] == '1')   // Connection status
+  {
+    if (cmd.size() != 1)
+    {
+      commandFailed(cmd);
+      return;
+    }
+    
+    stringstream ss;
+    ss << "list_connected_stations [list";
+    vector<QsoImpl*>::iterator it;
+    for (it=qsos.begin(); it!=qsos.end(); ++it)
+    {
+      if ((*it)->currentState() != Qso::STATE_DISCONNECTED)
+      {
+      	ss << " " << (*it)->remoteCallsign();
+      }
+    }
+    ss << "]";
+    processEvent(ss.str());
+  }
+  else if (cmd[0] == '2')   // Play own node id
+  {
+    if (cmd.size() != 1)
+    {
+      commandFailed(cmd);
+      return;
+    }
+    
+    stringstream ss;
+    ss << "play_node_id ";
+    const StationData *station = dir->findCall(dir->callsign());
+    ss << (station ? station->id() : 0);
+    processEvent(ss.str());
+  }
+  else if (cmd[0] == '3')   // Random connect
+  {
+    stringstream ss;
+    
+    if (cmd.size() != 2)
+    {
+      commandFailed(cmd);
+      return;
+    }
+    
+    vector<StationData> nodes;
+    
+    if (cmd[1] == '1')	// Random connect to link or repeater
+    {
+      const list<StationData>& links = dir->links();
+      const list<StationData>& repeaters = dir->repeaters();
+      list<StationData>::const_iterator it;
+      for (it=links.begin(); it!=links.end(); it++)
+      {
+	nodes.push_back(*it);
+      }
+      for (it=repeaters.begin(); it!=repeaters.end(); it++)
+      {
+	nodes.push_back(*it);
+      }
+    }
+    else if (cmd[1] == '2') // Random connect to conference
+    {
+      const list<StationData>& conferences = dir->conferences();
+      list<StationData>::const_iterator it;
+      for (it=conferences.begin(); it!=conferences.end(); it++)
+      {
+	nodes.push_back(*it);
+      }
+    }
+    else
+    {
+      commandFailed(cmd);
+      return;
+    }
+
+    double count = nodes.size();
+    if (count > 0)
+    {
+      srand(time(NULL));
+        // coverity[dont_call]
+      size_t random_idx = (size_t)(count * ((double)rand() / (1.0 + RAND_MAX)));
+      StationData station = nodes[random_idx];
+      
+      cout << "Creating random connection to node:\n";
+      cout << station << endl;
+      
+      createOutgoingConnection(station);
+    }
+    else
+    {
+      commandFailed(cmd);
+      return;
+    }
+  }
+  else if (cmd[0] == '4')   // Reconnect to the last disconnected station
+  {
+    if ((cmd.size() != 1) || last_disc_stn.callsign().empty())
+    {
+      commandFailed(cmd);
+      return;
+    }
+    
+    cout << "Trying to reconnect to " << last_disc_stn.callsign() << endl;
+    connectByNodeId(last_disc_stn.id());
+  }
+  else if (cmd[0] == '5')   // Listen only
+  {
+    if (cmd.size() < 2)
+    {
+      commandFailed(cmd);
+      return;
+    }
+    
+    bool activate = (cmd[1] != '0');
+    
+    vector<QsoImpl*>::iterator it;
+    for (it=qsos.begin(); it!=qsos.end(); ++it)
+    {
+      (*it)->setListenOnly(activate);
+    }
+
+    stringstream ss;
+    ss << "listen_only " << (!listen_only_valve->isOpen() ? "1 " : "0 ")
+       << (activate ? "1" : "0");
+    processEvent(ss.str());
+    
+    listen_only_valve->setOpen(!activate);
+  }
+  else if (cmd[0] == '6')   // Connect by callsign
+  {
+    connectByCallsign(cmd);
+  }
+  else if (cmd[0] == '7')   // Disconnect by callsign
+  {
+    disconnectByCallsign(cmd);
+  }
+  else
+  {
+    stringstream ss;
+    ss << "unknown_command " << cmd;
+    processEvent(ss.str());
+  }
+
+} /* ModuleEchoLink::handleCommand */
+
+
+void ModuleEchoLink::commandFailed(const string& cmd)
+{
+  stringstream ss;
+  ss << "command_failed " << cmd;
+  processEvent(ss.str());
+} /* ModuleEchoLink::commandFailed */
+
+
+void ModuleEchoLink::connectByNodeId(int node_id)
+{
+  if ((dir->status() == StationData::STAT_OFFLINE) ||
+      (dir->status() == StationData::STAT_UNKNOWN))
+  {
+    cout << "*** ERROR: Directory server offline (status="
+         << dir->statusStr() << "). Can't create outgoing connection.\n";
+    processEvent("directory_server_offline");
+    return;
+  }
+  
+  const StationData *station = dir->findStation(node_id);
+  if (station != 0)
+  {
+    createOutgoingConnection(*station);
+  }
+  else
+  {
+    cout << "EchoLink ID " << node_id << " is not in the list. "
+            "Refreshing the list...\n";
+    getDirectoryList();
+    pending_connect_id = node_id;
+  }
+} /* ModuleEchoLink::connectByNodeId */
+
+
+void ModuleEchoLink::checkIdle(void)
+{
+  setIdle(qsos.empty() &&
+      	  logicIsIdle() &&
+	  (state == STATE_NORMAL));
+} /* ModuleEchoLink::checkIdle */
+
+
+/*
+ *----------------------------------------------------------------------------
+ * Method:    checkAutoCon
+ * Purpose:   Initiate the process of connecting to autocon_echolink_id
+ * Input:     timer - the timer instance (not used)
+ * Output:    None
+ * Author:    Robbie De Lise / ON4SAX
+ * Created:   2010-07-30
+ * Remarks:
+ * Bugs:
+ *----------------------------------------------------------------------------
+ */
+void ModuleEchoLink::checkAutoCon(Timer *)
+{
+    // Only try to activate the link if we are online and not
+    // currently connected to any station. A connection will only be attempted
+    // if module activation is successful.
+  if ((dir->status() == StationData::STAT_ONLINE)
+      && (numConnectedStations() == 0)
+      && activateMe())
+  {
+    cout << "ModuleEchoLink: Trying autoconnect to "
+         << autocon_echolink_id << "\n";
+    connectByNodeId(autocon_echolink_id);
+  }
+} /* ModuleEchoLink::checkAutoCon */
+
+
+bool ModuleEchoLink::numConCheck(const std::string &callsign)
+{
+    // Get current time
+  struct timeval con_time;
+  gettimeofday(&con_time, NULL);
+
+    // Refresh connect watch list
+  numConUpdate();
+
+  NumConMap::iterator cit = num_con_map.find(callsign);
+  if (cit != num_con_map.end())
+  {
+      // Get an alias (reference) to the callsign and NumConStn objects
+    const string &t_callsign = (*cit).first;
+    NumConStn &stn = (*cit).second;
+
+      // Calculate time difference from last connection
+    struct timeval diff_tv;
+    timersub(&con_time, &stn.last_con, &diff_tv);
+
+      // Bug in Win-Echolink? Number of connect requests up to 5/sec
+      // do not count stations if it's requesting 3-5/seconds
+    if (diff_tv.tv_sec > 3)
+    {
+      ++stn.num_con;
+      stn.last_con = con_time;
+      cout << "### Station " << t_callsign << ", count " << stn.num_con << " of "
+           << num_con_max << " possible number of connects" << endl;
+    }
+
+      // Number of connects are too high
+    if (stn.num_con > num_con_max)
+    {
+      time_t next = con_time.tv_sec + num_con_block_time;
+      char time_str[64];
+      strftime(time_str, sizeof(time_str), "%c", localtime(&next));
+      cerr << "*** WARNING: Ingnoring incoming connection because "
+           << "the station (" << callsign << ") has connected "
+           << "to often (" << stn.num_con << " times). "
+           << "Next connect is possible after " << time_str << ".\n";
+      return false;
+    }
+  }
+  else
+  {
+      // Insert initial entry on first connect
+    cout << "### Register incoming station, count 1 of " << num_con_max
+         << " possible number of connects" << endl;
+    num_con_map.insert(make_pair(callsign, NumConStn(1, con_time)));
+  }
+
+  return true;
+
+} /* ModuleEchoLink::numConCheck */
+
+
+void ModuleEchoLink::numConUpdate(void)
+{
+    // Get current time
+  struct timeval now;
+  gettimeofday(&now, NULL);
+
+  NumConMap::iterator cit = num_con_map.begin();
+  while (cit != num_con_map.end())
+  {
+      // Get an alias (reference) to the callsign and NumConStn objects
+    const string &t_callsign = (*cit).first;
+    const NumConStn &stn = (*cit).second;
+
+    struct timeval remove_at = stn.last_con;
+    if (stn.num_con > num_con_max)
+    {
+      remove_at.tv_sec += num_con_block_time;
+    }
+    else
+    {
+      remove_at.tv_sec += num_con_ttl;
+    }
+
+      // If the entry have timed out, delete it
+    if (timercmp(&remove_at, &now, <))
+    {
+      cout << "### Delete " << t_callsign << " from watchlist" << endl;
+      num_con_map.erase(cit++);
+    }
+    else
+    {
+      if (stn.num_con > num_con_max)
+      {
+        cout << "### " << t_callsign << " is blocked" << endl;
+      }
+      ++cit;
+    }
+  }
+
+  num_con_update_timer->reset();
+
+} /* ModuleEchoLink::numConUpdate */
+
+
+void ModuleEchoLink::replaceAll(std::string &str, const std::string &from,
+                                const std::string &to) const
+{
+  if(from.empty())
+  {
+    return;
+  }
+  size_t start_pos = 0;
+  while((start_pos = str.find(from, start_pos)) != std::string::npos)
+  {
+    str.replace(start_pos, from.length(), to);
+    start_pos += to.length();
+  }
+} /* ModuleEchoLink::replaceAll */
+
+
+
+/*
+ * This file has not been truncated
+ */
diff --git a/SVXLink/echolink/ModuleEchoLink.h b/SVXLink/echolink/ModuleEchoLink.h
new file mode 100644
index 0000000..ed9a4ff
--- /dev/null
+++ b/SVXLink/echolink/ModuleEchoLink.h
@@ -0,0 +1,276 @@
+/**
+@file	 ModuleEchoLink.h
+@brief   A module that provides EchoLink connection possibility
+@author  Tobias Blomberg / SM0SVX
+@date	 2004-03-07
+
+\verbatim
+A module (plugin) for the multi purpose tranciever frontend system.
+Copyright (C) 2004-2014 Tobias Blomberg / SM0SVX
+
+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 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+\endverbatim
+*/
+
+
+#ifndef MODULE_ECHOLINK_INCLUDED
+#define MODULE_ECHOLINK_INCLUDED
+
+
+/****************************************************************************
+ *
+ * System Includes
+ *
+ ****************************************************************************/
+
+#include <string>
+#include <vector>
+
+#include <sys/types.h>
+#include <regex.h>
+
+
+/****************************************************************************
+ *
+ * Project Includes
+ *
+ ****************************************************************************/
+
+#include <Module.h>
+#include <EchoLinkQso.h>
+#include <EchoLinkStationData.h>
+
+
+/****************************************************************************
+ *
+ * Local Includes
+ *
+ ****************************************************************************/
+
+#include "version/SVXLINK.h"
+#include "BrandMeisterBridge.h"
+
+/****************************************************************************
+ *
+ * Forward declarations
+ *
+ ****************************************************************************/
+
+namespace Async
+{
+  class Timer;
+  class AudioSplitter;
+  class AudioValve;
+  class AudioSelector;
+};
+namespace EchoLink
+{
+  class Directory;
+  class StationData;
+  class Proxy;
+};
+
+
+/****************************************************************************
+ *
+ * Namespace
+ *
+ ****************************************************************************/
+
+//namespace MyNameSpace
+//{
+
+
+/****************************************************************************
+ *
+ * Forward declarations of classes inside of the declared namespace
+ *
+ ****************************************************************************/
+
+class MsgHandler;
+class QsoImpl;
+class LocationInfo;
+  
+
+/****************************************************************************
+ *
+ * Defines & typedefs
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Exported Global Variables
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Class definitions
+ *
+ ****************************************************************************/
+
+/**
+@brief	A module for providing EchoLink connections
+@author Tobias Blomberg
+@date   2004-03-07
+*/
+class ModuleEchoLink : public Module
+{
+  public:
+    ModuleEchoLink(void *dl_handle, Logic *logic, const std::string& cfg_name);
+    ~ModuleEchoLink(void);
+    bool initialize(void);
+    const char *compiledForVersion(void) const { return SVXLINK_VERSION; }
+
+    
+  protected:
+    /**
+     * @brief 	Notify the module that the logic core idle state has changed
+     * @param 	is_idle Set to \em true if the logic core is idle or else
+     *	      	\em false.
+     *
+     * This function is called by the logic core when the idle state changes.
+     */
+    virtual void logicIdleStateChanged(bool is_idle);
+
+
+  private:
+
+    BrandMeisterBridge bridge;
+
+    typedef enum
+    {
+      STATE_NORMAL,
+      STATE_CONNECT_BY_CALL,
+      STATE_DISCONNECT_BY_CALL
+    } State;
+    typedef std::vector<EchoLink::StationData> StnList;
+    struct NumConStn
+    {
+      unsigned        num_con;
+      struct timeval  last_con;
+
+      NumConStn(unsigned num, struct timeval &t) : num_con(num), last_con(t) {}
+    };
+    typedef std::map<const std::string, NumConStn> NumConMap;
+
+    static const int	  DEFAULT_AUTOCON_TIME = 3*60*1000; // Three minutes
+
+    EchoLink::Directory   *dir;
+    Async::Timer      	  *dir_refresh_timer;
+    std::string       	  mycall;
+    std::string       	  location;
+    std::string       	  sysop_name;
+    std::string       	  description;
+    std::string       	  allow_ip;
+    bool      	      	  remote_activation;
+    int       	      	  pending_connect_id;
+    std::string       	  last_message;
+    std::vector<QsoImpl*> outgoing_con_pending;
+    std::vector<QsoImpl*> qsos;
+    unsigned       	  max_connections;
+    unsigned       	  max_qsos;
+    QsoImpl   	      	  *talker;
+    bool      	      	  squelch_is_open;
+    State		  state;
+    StnList		  cbc_stns;
+    Async::Timer          *cbc_timer;
+    Async::Timer	  *dbc_timer;
+    regex_t   	      	  *drop_incoming_regex;
+    regex_t   	      	  *reject_incoming_regex;
+    regex_t   	      	  *accept_incoming_regex;
+    regex_t   	      	  *reject_outgoing_regex;
+    regex_t   	      	  *accept_outgoing_regex;
+    EchoLink::StationData last_disc_stn;
+    Async::AudioSplitter  *splitter;
+    Async::AudioValve 	  *listen_only_valve;
+    Async::AudioSelector  *selector;
+    unsigned              num_con_max;
+    time_t                num_con_ttl;
+    time_t                num_con_block_time;
+    NumConMap             num_con_map;
+    Async::Timer          *num_con_update_timer;
+    bool		  reject_conf;
+    int   	      	  autocon_echolink_id;
+    int   	      	  autocon_time;
+    Async::Timer	  *autocon_timer;
+    EchoLink::Proxy       *proxy;
+
+    void moduleCleanup(void);
+    void activateInit(void);
+    void deactivateCleanup(void);
+    //bool dtmfDigitReceived(char digit, int duration);
+    void dtmfCmdReceived(const std::string& cmd);
+    void dtmfCmdReceivedWhenIdle(const std::string &cmd);
+    void squelchOpen(bool is_open);
+    int audioFromRx(float *samples, int count);
+    void allMsgsWritten(void);
+
+    void onStatusChanged(EchoLink::StationData::Status status);
+    void onStationListUpdated(void);
+    void onError(const std::string& msg);
+    void onIncomingConnection(const Async::IpAddress& ip,
+      	    const std::string& callsign, const std::string& name,
+      	    const std::string& priv);
+    void onStateChange(QsoImpl *qso, EchoLink::Qso::State qso_state);
+    void onChatMsgReceived(QsoImpl *qso, const std::string& msg);
+    void onIsReceiving(bool is_receiving, QsoImpl *qso);
+    void destroyQsoObject(QsoImpl *qso);
+
+    void getDirectoryList(Async::Timer *timer=0);
+
+    void createOutgoingConnection(const EchoLink::StationData &station);
+    int audioFromRemote(float *samples, int count, QsoImpl *qso);
+    void audioFromRemoteRaw(EchoLink::Qso::RawPacket *packet,
+      	      	      	    QsoImpl *qso);
+    QsoImpl *findFirstTalker(void) const;
+    void broadcastTalkerStatus(void);
+    void updateDescription(void);
+    void updateEventVariables(void);
+    void connectByCallsign(std::string cmd);
+    void handleConnectByCall(const std::string& cmd);
+    void cbcTimeout(Async::Timer *t);
+    void disconnectByCallsign(const std::string &cmd);
+    void handleDisconnectByCall(const std::string& cmd);
+    void dbcTimeout(Async::Timer *t);
+    int numConnectedStations(void);
+    int listQsoCallsigns(std::list<std::string>& call_list);
+    void handleCommand(const std::string& cmd);
+    void commandFailed(const std::string& cmd);
+    void connectByNodeId(int node_id);
+    void checkIdle(void);
+    void checkAutoCon(Async::Timer *timer=0);
+    bool numConCheck(const std::string &callsign);
+    void numConUpdate(void);
+    void replaceAll(std::string &str, const std::string &from,
+                    const std::string &to) const;
+
+};  /* class ModuleEchoLink */
+
+
+//} /* namespace */
+
+#endif /* MODULE_ECHOLINK_INCLUDED */
+
+
+
+/*
+ * This file has not been truncated
+ */
diff --git a/SVXLink/echolink/PatchCordProxy.cpp b/SVXLink/echolink/PatchCordProxy.cpp
new file mode 100644
index 0000000..bb3c453
--- /dev/null
+++ b/SVXLink/echolink/PatchCordProxy.cpp
@@ -0,0 +1,154 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#include "PatchCordProxy.h"
+#include <stdlib.h>
+#include <string.h>
+
+#include <syslog.h>
+#include <stdio.h>
+
+#define INTERFACE_NAME  "me.burnaway.BrandMeister"
+#define SERVICE_NAME    INTERFACE_NAME
+#define OBJECT_PATH     "/me/burnaway/BrandMeister"
+
+// From AutoPatch.cpp
+#define AUTOPATCH_LINK_NAME  "AutoPatch"
+
+// From PatchCord.h
+#define VALUE_CORD_OUTGOING_SOURCE_ID     1
+#define VALUE_CORD_INCOMING_SOURCE_ID     4
+
+PatchCordProxy::PatchCordProxy(uint32_t number) :
+  number(number),
+  banner(NULL)
+{
+  connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+}
+
+PatchCordProxy::~PatchCordProxy()
+{
+  free(banner);
+  dbus_connection_unref(connection);
+}
+
+void PatchCordProxy::setTalkerID(uint32_t value)
+{
+  getContextBanner();
+  setVendorSpecificValue(VALUE_CORD_OUTGOING_SOURCE_ID, value);
+}
+
+uint32_t PatchCordProxy::getTalkerID()
+{
+  getContextBanner();
+  return getVendorSpecificValue(VALUE_CORD_INCOMING_SOURCE_ID);
+}
+
+void PatchCordProxy::getContextBanner()
+{
+  DBusMessage* message = dbus_message_new_method_call(
+    SERVICE_NAME, OBJECT_PATH,
+    INTERFACE_NAME, "getContextList");
+
+  const char* name = AUTOPATCH_LINK_NAME;
+
+  dbus_message_append_args(message,
+    DBUS_TYPE_STRING, &name,
+    DBUS_TYPE_UINT32, &number,
+    DBUS_TYPE_INVALID);
+
+  DBusPendingCall* pending;
+  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
+  {
+    dbus_connection_flush(connection);
+    dbus_message_unref(message);
+    dbus_pending_call_block(pending);
+    message = dbus_pending_call_steal_reply(pending);
+    char** array;
+    int count;
+    if ((dbus_message_get_args(message, NULL,
+          DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &array, &count,
+          DBUS_TYPE_INVALID)) &&
+        (count > 0))
+    {
+      free(banner);
+      banner = strdup(*array);
+      dbus_free_string_array(array);
+    }
+    dbus_pending_call_unref(pending);
+  }
+  dbus_message_unref(message);
+}
+
+void PatchCordProxy::setVendorSpecificValue(uint32_t key, uint32_t value)
+{
+  DBusMessage* message = dbus_message_new_method_call(
+    SERVICE_NAME, OBJECT_PATH,
+    INTERFACE_NAME, "setVendorSpecificValue");
+
+  dbus_message_append_args(message,
+    DBUS_TYPE_STRING, &banner,
+    DBUS_TYPE_UINT32, &key,
+    DBUS_TYPE_UINT32, &value,
+    DBUS_TYPE_INVALID);
+  
+  DBusPendingCall* pending;
+  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
+  {
+    dbus_connection_flush(connection);
+    dbus_message_unref(message);
+    dbus_pending_call_block(pending);
+    message = dbus_pending_call_steal_reply(pending);
+    dbus_pending_call_unref(pending);
+  }
+  dbus_message_unref(message);
+  
+  // Each call of dbus_message_unref removes banner from the heap
+  banner = NULL;
+}
+
+uint32_t PatchCordProxy::getVendorSpecificValue(uint32_t key)
+{
+  DBusMessage* message = dbus_message_new_method_call(
+    SERVICE_NAME, OBJECT_PATH,
+    INTERFACE_NAME, "getContextData");
+
+  dbus_message_append_args(message,
+    DBUS_TYPE_STRING, &banner,
+    DBUS_TYPE_INVALID);
+
+  uint32_t value = 0;
+
+  DBusPendingCall* pending;
+  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
+  {
+    dbus_connection_flush(connection);
+    dbus_message_unref(message);
+    dbus_pending_call_block(pending);
+    message = dbus_pending_call_steal_reply(pending);
+    const char* name;
+    const char* address;
+    dbus_uint32_t type;
+    dbus_uint32_t state;
+    dbus_uint32_t number;
+    dbus_uint32_t* values;
+    int count;
+    if (dbus_message_get_args(message, NULL,
+          DBUS_TYPE_STRING, &banner,
+          DBUS_TYPE_STRING, &name,
+          DBUS_TYPE_UINT32, &type,
+          DBUS_TYPE_UINT32, &number,
+          DBUS_TYPE_STRING, &address,
+          DBUS_TYPE_UINT32, &state,
+          DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &values, &count,
+          DBUS_TYPE_INVALID))
+      value = values[key];
+    dbus_pending_call_unref(pending);
+  }
+
+  dbus_message_unref(message);
+
+  // Each call of dbus_message_unref removes banner from the heap
+  banner = NULL;
+
+  return value;
+}
diff --git a/SVXLink/echolink/PatchCordProxy.h b/SVXLink/echolink/PatchCordProxy.h
new file mode 100644
index 0000000..75149f0
--- /dev/null
+++ b/SVXLink/echolink/PatchCordProxy.h
@@ -0,0 +1,31 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#ifndef PATCHCORDPROXY_H
+#define PATCHCORDPROXY_H
+
+#include <stdint.h>
+#include <dbus/dbus.h>
+
+class PatchCordProxy
+{
+  public:
+
+    PatchCordProxy(uint32_t number);
+    ~PatchCordProxy();
+
+    void setTalkerID(uint32_t value);
+    uint32_t getTalkerID();
+
+  private:
+
+    DBusConnection* connection;
+    uint32_t number;
+    char* banner;
+
+    void getContextBanner();
+    void setVendorSpecificValue(uint32_t key, uint32_t value);
+    uint32_t getVendorSpecificValue(uint32_t key);
+
+};
+
+#endif
\ No newline at end of file
diff --git a/SVXLink/echolink/UserDataStore.cpp b/SVXLink/echolink/UserDataStore.cpp
new file mode 100644
index 0000000..9fbe681
--- /dev/null
+++ b/SVXLink/echolink/UserDataStore.cpp
@@ -0,0 +1,137 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#include "UserDataStore.h"
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+
+#define GET_ID_FOR_CALL         0
+#define GET_CREDENTIALS_FOR_ID  1
+
+UserDataStore::UserDataStore(const char* file) :
+  connection(NULL)
+{
+  const char* queries[] =
+  {
+    // GET_ID_FOR_CALL
+    "SELECT `ID`"
+    " FROM Users"
+    " WHERE `Call` = ?"
+    " ORDER BY `Priority`"
+    " LIMIT 1",
+    // GET_CREDENTIALS_FOR_ID
+    "SELECT `Call`, `Text`"
+    " FROM Users"
+    " WHERE `ID` = ?",
+  };
+
+  connection = mysql_init(NULL);
+
+  mysql_options(connection, MYSQL_READ_DEFAULT_FILE, realpath(file, NULL));
+  mysql_real_connect(connection, NULL, NULL, NULL, NULL, 0, NULL, CLIENT_REMEMBER_OPTIONS);
+
+  my_bool active = true;
+  mysql_options(connection, MYSQL_OPT_RECONNECT, &active);
+
+  for (size_t index = 0; index < PREPARED_STATEMENT_COUNT; index ++)
+  {
+    const char* query = queries[index];
+    statements[index] = mysql_stmt_init(connection);
+    mysql_stmt_prepare(statements[index], query, strlen(query));
+  }
+
+  const char* error = mysql_error(connection);
+  if ((error != NULL) && (*error != '\0'))
+    syslog(LOG_CRIT, "MySQL error: %s\n", error);
+}
+
+UserDataStore::~UserDataStore()
+{
+  for (size_t index = 0; index < PREPARED_STATEMENT_COUNT; index ++)
+    mysql_stmt_close(statements[index]);
+
+  mysql_close(connection);
+}
+
+// Public methods
+
+uint32_t UserDataStore::getPrivateIDForCall(const char* call)
+{
+  uint32_t number;
+  size_t length = strlen(call);
+
+  return
+    execute(GET_ID_FOR_CALL, 1, 1,
+      MYSQL_TYPE_STRING, call, length,
+      MYSQL_TYPE_LONG, &number) *
+    number;
+}
+
+
+bool UserDataStore::getCredentialsForID(uint32_t number, char* call, char* text)
+{
+  return
+    execute(GET_CREDENTIALS_FOR_ID, 1, 2,
+      MYSQL_TYPE_LONG, &number,
+      MYSQL_TYPE_STRING, call, LONG_CALLSIGN_LENGTH + 1,
+      MYSQL_TYPE_STRING, text, SLOW_DATA_TEXT_LENGTH + 1);
+}
+
+// Internals
+
+MYSQL_BIND* UserDataStore::bind(int count, va_list* arguments)
+{
+  MYSQL_BIND* bindings = NULL;
+  if (count > 0)
+  {
+    bindings = (MYSQL_BIND*)calloc(count, sizeof(MYSQL_BIND));
+    for (int index = 0; index < count; index ++)
+    {
+      int type = va_arg(*arguments, int);
+      bindings[index].buffer_type = (enum_field_types)type;
+      bindings[index].buffer = va_arg(*arguments, void*);
+      if ((type == MYSQL_TYPE_STRING) ||
+          (type == MYSQL_TYPE_DATETIME))
+      {
+        bindings[index].buffer_length = va_arg(*arguments, int);
+        bindings[index].length = &bindings[index].buffer_length;
+      }
+    }
+  }
+  return bindings;
+}
+
+bool UserDataStore::execute(int index, int count1, int count2, ...)
+{
+  bool result = true;
+  MYSQL_STMT* statement = statements[index];
+  
+  va_list arguments;
+  va_start(arguments, count2);
+  MYSQL_BIND* parameters = bind(count1, &arguments);
+  MYSQL_BIND* columns = bind(count2, &arguments);
+  va_end(arguments);
+  
+  if ((parameters && mysql_stmt_bind_param(statement, parameters)) ||
+      (columns    && mysql_stmt_bind_result(statement, columns))   ||
+                     mysql_stmt_execute(statement)                 ||
+      (columns    && mysql_stmt_store_result(statement)))
+  {
+    const char* error = mysql_stmt_error(statement);
+    syslog(LOG_CRIT, "MySQL error while processing statement %d: %s\n", index, error);
+    result = false;
+  }
+  
+  if (result && columns)
+  {
+    result = !mysql_stmt_fetch(statement);
+    mysql_stmt_free_result(statement);
+  }
+  
+  free(columns);
+  free(parameters);
+  return result;
+}
+
diff --git a/SVXLink/echolink/UserDataStore.h b/SVXLink/echolink/UserDataStore.h
new file mode 100644
index 0000000..1f2ed01
--- /dev/null
+++ b/SVXLink/echolink/UserDataStore.h
@@ -0,0 +1,35 @@
+// Copyright 2015 by Artem Prilutskiy
+
+#ifndef USERDATASTORE_H
+#define USERDATASTORE_H
+
+#include <stdint.h>
+#include <stdarg.h>
+#include <mysql/mysql.h>
+
+#define PREPARED_STATEMENT_COUNT  2
+
+#define LONG_CALLSIGN_LENGTH      8
+#define SLOW_DATA_TEXT_LENGTH     20
+
+class UserDataStore
+{
+  public:
+
+    UserDataStore(const char* file);
+    ~UserDataStore();
+
+    uint32_t getPrivateIDForCall(const char* call);
+    bool getCredentialsForID(uint32_t number, char* call, char* text);
+
+  protected:
+
+    MYSQL* connection;
+    MYSQL_STMT* statements[PREPARED_STATEMENT_COUNT];
+
+    MYSQL_BIND* bind(int count, va_list* arguments);
+    bool execute(int index, int count1, int count2, ...);
+
+};
+
+#endif
diff --git a/SVXLink/readme.txt b/SVXLink/readme.txt
new file mode 100644
index 0000000..93d9b59
--- /dev/null
+++ b/SVXLink/readme.txt
@@ -0,0 +1,45 @@
+SVXLink patch to bridge with BrandMeister
+Copyright 2015 by Artem Prilutskiy
+-----------------------------------------
+
+Put UserDataStore.*, PatchCordProxy.* and BrandMeisterBridge.* into src/modules/echolink
+
+Add following lines to CMakeLists.txt after "set(MODSRC QsoImpl.cpp)":
+
+pkg_check_modules(DBUS dbus-1)
+include_directories(${DBUS_INCLUDE_DIRS})
+set(LIBS ${LIBS} ${DBUS_LIBRARIES} mysqlclient)
+set(MODSRC ${MODSRC} PatchCordProxy.cpp UserDataStore.cpp BrandMeisterBridge.cpp)
+
+Add following lines to ModuleEchoLink.h:
+
+[#include "BrandMeisterBridge.h"]
+  after
+    [#include "version/SVXLINK.h"]
+
+[BrandMeisterBridge bridge;]
+  after
+    [private:]
+
+
+Add following lines to method "ModuleEchoLink::broadcastTalkerStatus" of ModuleEchoLink.cpp:
+
+[const char* sysop_name = bridge.getTalker();]
+  before
+    [msg << "> " << mycall << "         " << sysop_name << "\n\n";]
+
+[bridge.setTalker(talker->remoteCallsign().c_str(), talker->remoteName().c_str());]
+  before
+    [msg << "> " << talker->remoteCallsign() << "         " << talker->remoteName() << "\n\n";]
+
+
+Add following lines to method "ModuleEchoLink::onChatMsgReceived" of ModuleEchoLink.cpp:
+[bridge.handleChatMessage(escaped.c_str());]
+  before
+    [processEvent(ss.str());]
+
+
+Configuration of bridge are hand-coded inside BrandMeisterBridge.cpp:
+
+Please configure PatchCord to number 10 in BrandMeister.conf
+Configuration of MySQL connection should be placed in /opt/BrandMeister/Registry.cnf
\ No newline at end of file
diff --git a/SVXLink/svxlink.conf b/SVXLink/svxlink.conf
new file mode 100644
index 0000000..8e87ebb
--- /dev/null
+++ b/SVXLink/svxlink.conf
@@ -0,0 +1,58 @@
+###############################################################################
+#                                                                             #
+#                Configuration file for the SvxLink server                    #
+#                                                                             #
+###############################################################################
+
+[GLOBAL]
+MODULE_PATH=/opt/SVXLink/lib/svxlink
+CFG_DIR=/opt/SVXLink/etc/svxlink/svxlink.d
+LOGICS=SimplexLogic
+TIMESTAMP_FORMAT="%c"
+CARD_SAMPLE_RATE=16000
+
+[SimplexLogic]
+TYPE=Simplex
+RX=Rx1
+TX=Tx1
+MODULES=ModuleEchoLink
+EVENT_HANDLER=/opt/SVXLink/share/svxlink/events.tcl
+
+SQL_DET=VOX
+SQL_START_DELAY=0
+SQL_DELAY=0
+SQL_HANGTIME=2000
+VOX_FILTER_DEPTH=20
+VOX_THRESH=1000
+
+CALLSIGN=R3ABM-L
+SHORT_IDENT_INTERVAL=60
+LONG_IDENT_INTERVAL=60
+EVENT_HANDLER=/opt/SVXLink/share/svxlink/events.tcl
+DEFAULT_LANG=ru_RU
+RGR_SOUND_DELAY=0
+FX_GAIN_NORMAL=0
+FX_GAIN_LOW=-12
+
+[Rx1]
+TYPE=Local
+AUDIO_DEV=alsa:dsp0
+AUDIO_CHANNEL=0
+SQL_DET=VOX
+SQL_START_DELAY=0
+SQL_DELAY=0
+SQL_HANGTIME=2000
+VOX_FILTER_DEPTH=20
+VOX_THRESH=1000
+DTMF_DEC_TYPE=INTERNAL
+DEEMPHASIS=0
+PEAK_METER=1
+
+[Tx1]
+TYPE=Local
+AUDIO_DEV=alsa:dsp1
+AUDIO_CHANNEL=0
+PTT_TYPE=NONE
+TIMEOUT=300
+TX_DELAY=500
+PREEMPHASIS=0
diff --git a/SVXLink/svxlink.service b/SVXLink/svxlink.service
new file mode 100644
index 0000000..ff3d913
--- /dev/null
+++ b/SVXLink/svxlink.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=SvxLink Server
+After=network.target sound.target brandmeister.service
+
+[Service]
+Restart=always
+KillMode=process
+ExecStart=/opt/SVXLink/bin/svxlink –config=/opt/SVXLink/etc/svxlink/svxlink.conf –logfile=/opt/SVXLink/var/log/svxlink
+RestartSec=5
+TimeoutSec=5
+User=svxlink
+Group=svxlink
+Environment="HOME=/opt/SVXLink"
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/Scripts/alterfrn.service b/Scripts/alterfrn.service
new file mode 100644
index 0000000..4b719da
--- /dev/null
+++ b/Scripts/alterfrn.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=AlterFRN Client
+Afer=network.target sound.target
+
+[Service]
+; system.service
+Type=simple
+ExecStart=/opt/AlterFRN/alterfrn.sh
+Restart=on-failure
+; system.exec
+User=frn
+Group=frn
+StandardOutput=null
+WorkingDirectory=/opt/AlterFRN
+
+[Install]
+WantedBy=multi-user.target
diff --git a/Scripts/alterfrn.sh b/Scripts/alterfrn.sh
new file mode 100755
index 0000000..b2dba6e
--- /dev/null
+++ b/Scripts/alterfrn.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+./FRNClientConsole | ./callcapture --identity AlterFRN --expression "RX is started: ([A-Z0-9]{3,7})[-,]" --connection Registry.cnf --link 11
diff --git a/Scripts/asound.conf b/Scripts/asound.conf
new file mode 100644
index 0000000..2de52e1
--- /dev/null
+++ b/Scripts/asound.conf
@@ -0,0 +1,51 @@
+
+# Record device for SVXLink
+
+pcm.convert1 {
+  type rate
+  slave  {
+    pcm "hw:Loopback,1,2"
+    rate 8000
+  }
+}
+
+pcm.dsp0 {
+  type plug
+  slave {
+    pcm "convert1"
+    rate 16000
+  }
+}
+
+
+# Playback device for SVXLink
+
+pcm.convert2 {
+  type rate
+  slave  {
+    pcm "hw:Loopback,0,0"
+    rate 8000
+  }
+}
+
+pcm.dsp1 {
+  type plug
+  slave {
+    pcm "convert2"
+    rate 16000
+  }
+}
+
+# Record device for AlterFRN
+
+pcm.dsp2 {
+  type plug
+  slave.pcm "hw:Loopback,1,6"
+}
+
+# Playback device for AlterFRN
+
+pcm.dsp3 {
+  type plug
+  slave.pcm "hw:Loopback,0,4"
+}
diff --git a/Scripts/frnconsole.cfg.unix b/Scripts/frnconsole.cfg.unix
new file mode 100644
index 0000000..4827275
--- /dev/null
+++ b/Scripts/frnconsole.cfg.unix
@@ -0,0 +1,168 @@
+# Конфигурационный файл программы FRN-линка (FRNClientConsole)
+# Configuration for FRN-link (FRNClientConsole)
+
+# основное руководство тут http://alterfrn.ucoz.ru/index/freebsd_manual_russian/0-5
+# main manual here http://alterfrn.ucoz.ru/index/freebsd_manual_russian/0-5
+#
+# примечания для ARM/Linux тут http://alterfrn.ucoz.ru/index/linux_arm_manual_russian/0-6
+# comments for ARM/Linux here http://alterfrn.ucoz.ru/index/linux_arm_manual_russian/0-6
+
+
+# Ревизия(revision) r2781
+
+[Auth]
+Callsign=MY0CALL
+OperatorName=Name
+EMailAddress=my@email
+City=N/A
+CityPart=
+Password=password
+DynamicPassword=
+Country=Russian Federation
+Description=DSTAR.SU DMR Bridge
+BandChannel=
+ClientType=CROSSLINK
+CharsetName=WINDOWS-1251
+
+
+[Audio]
+#### Имя устройства ввода звука в формате вывода по параметрам командной строки "audioconfig"
+#### Input audio device name like output with command line parameter "audioconfig"
+InDevice=ALSA:dsp2
+
+## Частота дискретизации звука при ввода: 
+##   8000(рекомендуется, не требуется преобразования), 11025, 12000, 16000,
+##   22050, 24000, 32000, 44100(по умолчанию), 48000
+## Input (capture) sample rate: 
+##   8000(recommended, no need samplerate conversion), 11025, 12000, 16000,
+##   22050, 24000, 32000, 44100(default), 48000
+InSampleRate=8000
+
+#### Качество преобразования частоты дискретизации в 8Кгц: 0/NONE/NO/N - совсем без качества(по умолчанию); 1/LOW/LO/L - низкое качество; 2/MEDIUM/MED/M - среднее качество; 3/HIGH/HI/H - высокое качество(высокая нагрузка процессора)
+#### Quality for input(capture) audio samplerate conversion into 8KHz: 0/NONE/NO/N - no any quality(default); 1/LOW/LO/L - low quality; 2/MEDIUM/MED/M - medium quality; 3/HIGH/HI/H - high qualityо(highest CPU usage)
+InQuality=L
+
+InFactor=1
+
+
+# Автоматическая регулировка усиления (АРУ) по входу с радиостанции
+# Input (capture) Automatic Gain Control (AGC)
+InAGCEnabled=no
+# level after AGC in percents(%), range 50..100; default 90
+InAGCLevel=99
+# AGC maximal gain in decibells(dB), range 0..60; default 30
+InAGCMaxGain=20
+
+# Входной фильтр высоких частот Баттерворта, частота среза 300Гц; перед АРУ
+# Input High Pass Filter, Butterworth, 300Hz; before AGC
+InHPFEnabled=no
+InHPFOrder=5
+InHPFDouble=no
+
+
+
+
+#### Имя устройства вывода звука в формате вывода по параметрам командной строки "audioconfig"
+#### Output audio device name like output with command line parameter "audioconfig"
+OutDevice=ALSA:dsp3
+
+## Частота дискретизации звука при выводе: 
+##   8000(рекомендуется, не требуется преобразования), 11025, 12000, 16000,
+##   22050, 24000, 32000, 44100(по умолчанию), 48000
+## Output (playback) sample rate: 
+##   8000(recommended, no need samplerate conversion), 11025, 12000, 16000, 
+##   22050, 24000, 32000, 44100(default), 48000
+OutSampleRate=8000
+
+#### Качество преобразования частоты дискретизации из 8КГц: 0/NONE/NO/N - совсем без качества(по умолчанию); 1/LOW/LO/L - низкое качество; 2/MEDIUM/MED/M - среднее качество; 3/HIGH/HI/H - высокое качество(высокая нагрузка процессора)
+#### Quality for output(playback) audio conversion from 8KHz: 0/NONE/NO/N - no any quality(default); 1/LOW/LO/L - low quality; 2/MEDIUM/MED/M - medium quality; 3/HIGH/HI/H - high qualityо(highest CPU usage)
+OutQuality=L
+OutFactor=1
+
+# Автоматическая регулировка усиления (АРУ) по выходу на радиостанцию
+# Output (playback) Automatic Gain Control (AGC)
+OutAGCEnabled=no
+# level after AGC in percents(%), range 50..100; default 90
+OutAGCLevel=99
+# AGC maximal gain in decibells(dB), range 0..60; default 30
+OutAGCMaxGain=40
+
+# Выходной фильтр высоких частот Баттерворта, частота среза 300Гц; перед АРУ
+# Output High Pass Filter, Butterworth, 300Hz; before AGC
+OutHPFEnabled=no
+OutHPFOrder=5
+OutHPFDouble=no
+
+
+
+[Radio]
+PTT=
+COS=VOX:1200
+LIGHT=
+CTCSSWakeTime=500
+CarrierCatchTime=100
+CarrierLostTime=600
+
+[Server]
+ServerReconnectCount=3
+ServerReconnectInterval=3000
+ServerAddress=frn.radiocult.ru
+ServerPort=10024
+CharsetName=WINDOWS-1251
+#### VisibleStatus: Visible status in server list of clients:  AVAILABLE/AV (default), NOTAVAILABLE/NA, ABSENT/AB/AS
+VisibleStatus=AV
+Network=Radiocult
+BackupServerMode=YES
+ForcedBackupServerAddress=frn3.radiocult.ru
+ForcedBackupServerPort=10027
+
+
+[Manager]
+ManagerAddress=sysman.freeradionetwork.eu
+ManagerPort=10025
+DynamicPasswordMode=YES
+
+
+[Internet]
+#### ProxyType: режим использования прокси-сервера: NONE/NO/N - прокси не используется (по умолчанию); SYSTEM/SYS/S - используется прокси в соответствии с настройками системы; HTTP/HT/H - используется конкретный прокси
+## ProxyType: mode for proxy-server usage: NONE/NO/N - do not use proxy-server (default); SYSTEM/SYS/S - use system settings for proxy; HTTP/HT/H - use certain http-proxy
+ProxyType=NONE
+# ProxyAddress=192.168.1.1
+# ProxyPort=3128
+# CharsetName=UTF-8
+
+[Message]
+PrivateAutoResponse=
+
+[Sounds]
+SoundsDir=
+SoundCourtesy=
+EnableCourtesy=
+SoundCourtesyEmptyNet=
+EnableCourtesyEmptyNet=
+SoundRoger=
+EnableRoger=
+SoundNoConnection=
+EnableNoConnection=
+SoundReject=
+EnableReject=
+SoundError=
+EnableError=
+
+
+[System]
+CharsetName=UTF-8
+
+[Hours]
+Enabled=no
+
+
+[Informer]
+Enabled=no
+Dir=informer
+Interval=1200
+Mode=SEQ
+SilenceEnabled=no
+SilenceInterval=300
+SilenceTime=2000
+