yate/modules/server/zapcard.cpp

3214 lines
96 KiB
C++

/**
* zapcard.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Zaptel PRI/TDM/FXS/FXO cards signalling and data driver
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2014 Null Team
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*
* 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.
*/
#include <yatephone.h>
#include <yatesig.h>
#ifdef _WINDOWS
#error This module is not for Windows
#else
extern "C" {
#ifdef HAVE_ZAP
#ifdef NEW_ZAPTEL_LOCATION
#define __LINUX__
#include <zaptel/zaptel.h>
#else
#include <linux/zaptel.h>
#endif
#else
#include <dahdi/user.h>
#endif
};
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
using namespace TelEngine;
namespace { // anonymous
class ZapWorkerClient; // Worker thread client (implements process())
class ZapWorkerThread; // Worker thread (calls client's process() in a loop)
class ZapDevice; // Zaptel I/O device. Implements the interface with the Zaptel driver
class ZapInterface; // D-channel signalling interface
class ZapSpan; // Signalling span used to create voice circuits
class ZapCircuit; // A voice circuit
class ZapAnalogCircuit; // A analog circuit
class ZapSource; // Data source
class ZapConsumer; // Data consumer
class ZapModule; // The module
#define ZAP_ERR_OVERRUN 0x01 // Flags used to filter interface errors
#define ZAP_ERR_ABORT 0x02
#define ZAP_CRC_LEN 2 // The length of the CRC field in signalling packets
#ifdef HAVE_ZAP
// alarms
#define DAHDI_ALARM_RECOVER ZT_ALARM_RECOVER
#define DAHDI_ALARM_LOOPBACK ZT_ALARM_LOOPBACK
#define DAHDI_ALARM_RED ZT_ALARM_RED
#define DAHDI_ALARM_YELLOW ZT_ALARM_YELLOW
#define DAHDI_ALARM_BLUE ZT_ALARM_BLUE
#define DAHDI_ALARM_NOTOPEN ZT_ALARM_NOTOPEN
// events
#define DAHDI_EVENT_NONE ZT_EVENT_NONE
#define DAHDI_EVENT_ONHOOK ZT_EVENT_ONHOOK
#define DAHDI_EVENT_RINGOFFHOOK ZT_EVENT_RINGOFFHOOK
#define DAHDI_EVENT_WINKFLASH ZT_EVENT_WINKFLASH
#define DAHDI_EVENT_ALARM ZT_EVENT_ALARM
#define DAHDI_EVENT_NOALARM ZT_EVENT_NOALARM
#define DAHDI_EVENT_ABORT ZT_EVENT_ABORT
#define DAHDI_EVENT_OVERRUN ZT_EVENT_OVERRUN
#define DAHDI_EVENT_BADFCS ZT_EVENT_BADFCS
#define DAHDI_EVENT_DIALCOMPLETE ZT_EVENT_DIALCOMPLETE
#define DAHDI_EVENT_RINGERON ZT_EVENT_RINGERON
#define DAHDI_EVENT_RINGEROFF ZT_EVENT_RINGEROFF
#define DAHDI_EVENT_HOOKCOMPLETE ZT_EVENT_HOOKCOMPLETE
#define DAHDI_EVENT_BITSCHANGED ZT_EVENT_BITSCHANGED
#define DAHDI_EVENT_PULSE_START ZT_EVENT_PULSE_START
#define DAHDI_EVENT_TIMER_EXPIRED ZT_EVENT_TIMER_EXPIRED
#define DAHDI_EVENT_TIMER_PING ZT_EVENT_TIMER_PING
#define DAHDI_EVENT_RINGBEGIN ZT_EVENT_RINGBEGIN
#define DAHDI_EVENT_POLARITY ZT_EVENT_POLARITY
#ifdef ZT_EVENT_REMOVED
#define DAHDI_EVENT_REMOVED ZT_EVENT_REMOVED
#endif
#define DAHDI_EVENT_PULSEDIGIT ZT_EVENT_PULSEDIGIT
#define DAHDI_EVENT_DTMFDOWN ZT_EVENT_DTMFDOWN
#define DAHDI_EVENT_DTMFUP ZT_EVENT_DTMFUP
#define DAHDI_EVENT_PULSEDIGIT ZT_EVENT_PULSEDIGIT
// hook events
#define DAHDI_ONHOOK ZT_ONHOOK
#define DAHDI_OFFHOOK ZT_OFFHOOK
#define DAHDI_WINK ZT_WINK
#define DAHDI_FLASH ZT_FLASH
#define DAHDI_START ZT_START
#define DAHDI_RING ZT_RING
#define DAHDI_RINGOFF ZT_RINGOFF
// flush buffers
#define DAHDI_FLUSH_READ ZT_FLUSH_READ
#define DAHDI_FLUSH_WRITE ZT_FLUSH_WRITE
#define DAHDI_FLUSH_BOTH ZT_FLUSH_BOTH
#define DAHDI_FLUSH_EVENT ZT_FLUSH_EVENT
#define DAHDI_FLUSH_ALL ZT_FLUSH_ALL
// formats
#define DAHDI_LAW_DEFAULT ZT_LAW_DEFAULT
#define DAHDI_LAW_MULAW ZT_LAW_MULAW
#define DAHDI_LAW_ALAW ZT_LAW_ALAW
// dial operations
#define DAHDI_DIAL_OP_APPEND ZT_DIAL_OP_APPEND
#define DAHDI_DIAL_OP_REPLACE ZT_DIAL_OP_REPLACE
#define DAHDI_DIAL_OP_CANCEL ZT_DIAL_OP_CANCEL
// signalling types
#define DAHDI_SIG_NONE ZT_SIG_NONE
#define DAHDI_SIG_FXSLS ZT_SIG_FXSLS
#define DAHDI_SIG_FXSGS ZT_SIG_FXSGS
#define DAHDI_SIG_FXSKS ZT_SIG_FXSKS
#define DAHDI_SIG_FXOLS ZT_SIG_FXOLS
#define DAHDI_SIG_FXOGS ZT_SIG_FXOGS
#define DAHDI_SIG_FXOKS ZT_SIG_FXOKS
#define DAHDI_SIG_EM ZT_SIG_EM
#define DAHDI_SIG_CLEAR ZT_SIG_CLEAR
#define DAHDI_SIG_HDLCRAW ZT_SIG_HDLCRAW
#define DAHDI_SIG_HDLCFCS ZT_SIG_HDLCFCS
#define DAHDI_SIG_HDLCNET ZT_SIG_HDLCNET
#define DAHDI_SIG_SLAVE ZT_SIG_SLAVE
#define DAHDI_SIG_SF ZT_SIG_SF
#define DAHDI_SIG_CAS ZT_SIG_CAS
#define DAHDI_SIG_DACS ZT_SIG_DACS
#define DAHDI_SIG_EM_E1 ZT_SIG_EM_E1
#define DAHDI_SIG_DACS_RBS ZT_SIG_DACS_RBS
#define DAHDI_SIG_HARDHDLC ZT_SIG_HARDHDLC
// tonedetect
#define DAHDI_TONEDETECT_ON ZT_TONEDETECT_ON
#define DAHDI_TONEDETECT_MUTE ZT_TONEDETECT_MUTE
// dtmfs
#define DAHDI_MAX_DTMF_BUF ZT_MAX_DTMF_BUF
#define DAHDI_TONE_DTMF_0 ZT_TONE_DTMF_0
#define DAHDI_TONE_DTMF_1 ZT_TONE_DTMF_1
#define DAHDI_TONE_DTMF_2 ZT_TONE_DTMF_2
#define DAHDI_TONE_DTMF_3 ZT_TONE_DTMF_3
#define DAHDI_TONE_DTMF_4 ZT_TONE_DTMF_4
#define DAHDI_TONE_DTMF_5 ZT_TONE_DTMF_5
#define DAHDI_TONE_DTMF_6 ZT_TONE_DTMF_6
#define DAHDI_TONE_DTMF_7 ZT_TONE_DTMF_7
#define DAHDI_TONE_DTMF_8 ZT_TONE_DTMF_8
#define DAHDI_TONE_DTMF_9 ZT_TONE_DTMF_9
#define DAHDI_TONE_DTMF_s ZT_TONE_DTMF_s
#define DAHDI_TONE_DTMF_p ZT_TONE_DTMF_p
#define DAHDI_TONE_DTMF_A ZT_TONE_DTMF_A
#define DAHDI_TONE_DTMF_B ZT_TONE_DTMF_B
#define DAHDI_TONE_DTMF_C ZT_TONE_DTMF_C
#define DAHDI_TONE_DTMF_D ZT_TONE_DTMF_D
// ioctl operations
#define DAHDI_SIG_EM ZT_SIG_EM
#define DAHDI_GETEVENT ZT_GETEVENT
#define DAHDI_SPECIFY ZT_SPECIFY
#define DAHDI_SET_BLOCKSIZE ZT_SET_BLOCKSIZE
#define DAHDI_SET_BUFINFO ZT_SET_BUFINFO
#define DAHDI_SETLAW ZT_SETLAW
#define DAHDI_AUDIOMODE ZT_AUDIOMODE
#define DAHDI_ECHOCANCEL ZT_ECHOCANCEL
#define DAHDI_DIAL ZT_DIAL
#define DAHDI_HOOK ZT_HOOK
#define DAHDI_TONEDETECT ZT_TONEDETECT
#define DAHDI_SETPOLARITY ZT_SETPOLARITY
#define DAHDI_SETLINEAR ZT_SETLINEAR
#define DAHDI_SET_DIALPARAMS ZT_SET_DIALPARAMS
#define DAHDI_GET_PARAMS ZT_GET_PARAMS
#define DAHDI_SPANSTAT ZT_SPANSTAT
#define DAHDI_GET_DIALPARAMS ZT_GET_DIALPARAMS
#define DAHDI_ECHOTRAIN ZT_ECHOTRAIN
#define DAHDI_FLUSH ZT_FLUSH
#define DAHDI_SENDTONE ZT_SENDTONE
#define DAHDI_GETVERSION ZT_GETVERSION
// policies
#define DAHDI_POLICY_IMMEDIATE ZT_POLICY_IMMEDIATE
// data types
#define dahdi_params zt_params
#define dahdi_bufferinfo zt_bufferinfo
#define dahdi_dialoperation zt_dialoperation
#define dahdi_dialparams zt_dialparams
#define dahdi_spaninfo zt_spaninfo
#define dahdi_versioninfo zt_versioninfo
#endif
// Worker thread client (implements process())
class ZapWorkerClient
{
friend class ZapWorkerThread;
public:
virtual ~ZapWorkerClient() { stop(); }
bool running() const;
// Return true to tell the worker to call again
// Return false to yield
virtual bool process() = 0;
protected:
inline ZapWorkerClient() : m_thread(0) {}
// Start thread if not started
bool start(Thread::Priority prio, DebugEnabler* dbg, const String& addr);
// Stop thread if started
void stop();
private:
ZapWorkerThread* m_thread;
};
// Worker thread (calls client's process() in a loop)
class ZapWorkerThread : public Thread
{
public:
inline ZapWorkerThread(ZapWorkerClient* client, const String& addr, Priority prio = Normal)
: Thread(s_threadName,prio), m_client(client), m_address(addr)
{}
virtual ~ZapWorkerThread();
// Call client's process() in a loop
virtual void run();
static const char* s_threadName;
private:
ZapWorkerClient* m_client;
String m_address;
};
// I/O device
class ZapDevice : public GenObject
{
public:
// Flags to check alarms
enum Alarm {
Recover = DAHDI_ALARM_RECOVER, // Recovering from alarm
Loopback = DAHDI_ALARM_LOOPBACK, // In loopback
Red = DAHDI_ALARM_RED, // Interface is down
Yellow = DAHDI_ALARM_YELLOW, // Remote peer doesn't see us
Blue = DAHDI_ALARM_BLUE, // We don't see the remote peer
NotOpen = DAHDI_ALARM_NOTOPEN
};
// List of events
enum Event {
None = DAHDI_EVENT_NONE,
OnHook = DAHDI_EVENT_ONHOOK,
OffHookRing = DAHDI_EVENT_RINGOFFHOOK,
WinkFlash = DAHDI_EVENT_WINKFLASH,
Alarm = DAHDI_EVENT_ALARM,
NoAlarm = DAHDI_EVENT_NOALARM,
HdlcAbort = DAHDI_EVENT_ABORT,
HdlcOverrun = DAHDI_EVENT_OVERRUN,
BadFCS = DAHDI_EVENT_BADFCS,
DialComplete = DAHDI_EVENT_DIALCOMPLETE,
RingerOn = DAHDI_EVENT_RINGERON,
RingerOff = DAHDI_EVENT_RINGEROFF,
HookComplete = DAHDI_EVENT_HOOKCOMPLETE,
BitsChanged = DAHDI_EVENT_BITSCHANGED, // Bits changing on a CAS/User channel
PulseStart = DAHDI_EVENT_PULSE_START, // Beginning of a pulse coming on its way
Timeout = DAHDI_EVENT_TIMER_EXPIRED,
TimerPing = DAHDI_EVENT_TIMER_PING,
RingBegin = DAHDI_EVENT_RINGBEGIN,
Polarity = DAHDI_EVENT_POLARITY, // Polarity reversal event
#ifdef DAHDI_EVENT_REMOVED
Removed = DAHDI_EVENT_REMOVED,
#endif
// These are event masks
PulseDigit = DAHDI_EVENT_PULSEDIGIT, // This is OR'd with the digit received
DtmfDown = DAHDI_EVENT_DTMFDOWN, // Ditto for DTMF key down event
DtmfUp = DAHDI_EVENT_DTMFUP, // Ditto for DTMF key up event
DigitEvent = DAHDI_EVENT_PULSEDIGIT | DAHDI_EVENT_DTMFDOWN | DAHDI_EVENT_DTMFUP
};
// List of hook to send events
enum HookEvent {
HookOn = DAHDI_ONHOOK,
HookOff = DAHDI_OFFHOOK,
HookWink = DAHDI_WINK,
HookFlash = DAHDI_FLASH,
HookStart = DAHDI_START,
HookRing = DAHDI_RING,
HookRingOff = DAHDI_RINGOFF
};
// Rx Hook states, not exported from zaptel.h in user mode
enum RxSigState {
RxSigOnHook = 0,
RxSigOffHook,
RxSigStart,
RxSigRing,
RxSigInitial
};
// List of valid IOCTL requests
enum IoctlRequest {
SetChannel = 0, // Specify a channel number for an opened device
SetBlkSize = 1, // Set data I/O block size
SetBuffers = 2, // Set buffers
SetFormat = 3, // Set format
SetAudioMode = 4, // Set audio mode
SetEchoCancel = 5, // Set echo cancel
SetDial = 6, // Append, replace, or cancel a dial string
SetHook = 7, // Set Hookswitch Status
#ifdef DAHDI_TONEDETECT
SetToneDetect = 8, // Set tone detection
#else
SetToneDetect = 101,
#endif
SetPolarity = 10, // Set line polarity
SetLinear = 11, // Temporarily set the channel to operate in linear mode
SetDialParams = 12, // Set dialing parameters
GetParams = 20, // Get device (channel) parameters
GetEvent = 21, // Get events from device
GetInfo = 22, // Get device status
GetVersion = 23, // Get version
GetDialParams = 24, // Get dialing parameters
StartEchoTrain = 30, // Start echo training
FlushBuffers = 31, // Flush buffer(s) and stop I/O
SendTone = 32, // Send tone
};
enum FlushTarget {
FlushRead = DAHDI_FLUSH_READ,
FlushWrite = DAHDI_FLUSH_WRITE,
FlushRdWr = DAHDI_FLUSH_BOTH,
FlushEvent = DAHDI_FLUSH_EVENT,
FlushAll = DAHDI_FLUSH_ALL,
};
// Zaptel formats
enum Format {
Slin = -1,
Default = DAHDI_LAW_DEFAULT,
Mulaw = DAHDI_LAW_MULAW,
Alaw = DAHDI_LAW_ALAW
};
// Device type: D-channel, voice/data circuit or control
enum Type {
DChan,
E1,
T1,
BRI,
FXO,
FXS,
Control,
TypeUnknown
};
// Dial operations
enum DialOperation {
DialAppend = DAHDI_DIAL_OP_APPEND,
DialReplace = DAHDI_DIAL_OP_REPLACE,
DialCancel = DAHDI_DIAL_OP_CANCEL
};
// Create a device used to query the driver (chan=0) or a zaptel channel
// Open it if requested
ZapDevice(unsigned int chan, bool disableDbg = true, bool open = true);
ZapDevice(Type t, SignallingComponent* dbg, unsigned int chan,
unsigned int circuit);
virtual ~ZapDevice();
inline Type type() const
{ return m_type; }
inline int zapsig() const
{ return m_zapsig; }
inline SignallingComponent* owner() const
{ return m_owner; }
inline const String& address() const
{ return m_address; }
inline bool valid() const
{ return m_handle >= 0; }
inline unsigned int channel() const
{ return m_channel; }
inline int span() const
{ return m_span; }
inline int spanPos() const
{ return m_spanPos; }
void channel(unsigned int chan, unsigned int circuit);
inline int alarms() const
{ return m_alarms; }
inline const String& alarmsText() const
{ return m_alarmsText; }
inline bool canRead() const
{ return m_canRead; }
inline bool event() const
{ return m_event || m_savedEvent; }
inline const char* zapDevName() const
{ return (m_type != Control) ? s_zapDevName : s_zapCtlName; }
// Get driver/chan format
inline const String& zapName() const
{ return m_zapName; }
// Open the device. Specify channel to use.
// Circuit: Set block size (ignore numbufs)
// Interface: Check channel mode. Set buffers
bool open(unsigned int numbufs, unsigned int bufsize);
// Close device. Reset handle
void close();
// Set data format. Fails if called for an interface
bool setFormat(Format format);
// Set/unset tone detection
bool setDtmfDetect(bool detect);
// Update echo canceller (disable if taps is 0)
bool setEchoCancel(bool enable, unsigned int taps);
// Start echo canceller training for a given period of time (in miliseconds)
bool startEchoTrain(unsigned int period);
// Enable polling of off-hook state, call only for passive FXO
inline void initHook()
{ m_rxHookSig = RxSigInitial; }
// Poll the hook state and save event since a FXO does not generate events
void pollHook();
// Send hook events
bool sendHook(HookEvent event);
// Send DTMFs events using dialing or tone structure
// dtmf: true - send DTMF (tone dialing), false - send MF (pulse dialing)
// op: The dial operation to use
// allDigits: true to send all digits at once
// useTone: Used only if 'allDigits' is false and 'dtmf' is true
bool sendDtmf(const char* tone, bool dtmf = true, DialOperation op = DialAppend,
bool allDigits = true, bool useTone = false);
// Send DTMF event using tone structure
bool sendDtmf(char tone);
// Get an event. Return 0 if no events. Set digit if the event is a DTMF/PULSE
int getEvent(char& digit);
// Check alarms from this device. Return true if alarms changed
bool checkAlarms();
// Reset alarms
void resetAlarms();
// Set clear channel
inline bool setLinear(int val, int level = DebugWarn)
{ return ioctl(SetLinear,&val,level); }
// Set line polarity
inline bool setPolarity(int val, int level = DebugWarn)
{ return ioctl(SetPolarity,&val,level); }
// Flush read and write buffers
bool flushBuffers(FlushTarget target = FlushAll);
// Check if received data. Wait usec microseconds before returning
bool select(unsigned int usec);
// Receive data. Return -1 on error or the number of bytes read
// If -1 is returned, the caller should check if m_event is set
int recv(void* buffer, int len);
// Send data. Return -1 on error or the number of bytes written
int send(const void* buffer, int len);
// Send data. Return -1 on error or the number of bytes written
inline int write(const void* buffer, int len)
{ return ::write(m_handle,buffer,len); }
// Get driver version and echo canceller
bool getVersion(NamedList& dest);
// Get driver version and echo canceller
bool getSpanInfo(int span, NamedList& dest, int* spans = 0);
// Get channel parameters
bool getChanParams(NamedList& dest);
// Set/get dial parameters (DTMF/MF length)
bool dialParams(bool set, int& toneLen, int& mfLen);
// Zaptel device names and headers for status
static const char* s_zapCtlName;
static const char* s_zapDevName;
protected:
inline bool canRetry()
{ return errno == EAGAIN || errno == EINTR; }
// Make IOCTL requests on this device
bool ioctl(IoctlRequest request, void* param, int level = DebugWarn);
private:
Type m_type; // Device type
int m_zapsig; // Zaptel signalling type
SignallingComponent* m_owner; // Signalling component owning this device
String m_name; // Additional debug name for circuits
String m_address; // User address (interface or circuit)
String m_zapName; // Zaptel name (Zaptel/channel)
int m_handle; // The handler
unsigned int m_channel; // The channel this file is used for
int m_span; // Span this device's channel belongs to
int m_spanPos; // Physical channel inside span
int m_alarms; // Device alarms flag
int m_rxHookSig; // Keep hook status for bridged lines
int m_savedEvent; // Event saved asynchronously for later
String m_alarmsText; // Alarms text
bool m_canRead; // True if there is data to read
bool m_event; // True if an event occurred when recv/select
bool m_readError; // Flag used to print read errors
bool m_writeError; // Flag used to print write errors
bool m_selectError; // Flag used to print select errors
fd_set m_rdfds;
fd_set m_errfds;
struct timeval m_tv;
};
// D-channel signalling interface
class ZapInterface : public SignallingInterface, public ZapWorkerClient
{
public:
ZapInterface(const NamedList& params);
virtual ~ZapInterface();
inline bool valid() const
{ return m_device.valid() && running(); }
// Initialize interface. Return false on failure
bool init(ZapDevice::Type type, unsigned int code, unsigned int channel,
const NamedList& config, const NamedList& defaults, const NamedList& params);
// Remove links. Dispose memory
virtual void destruct()
{ cleanup(true); }
// Get this object or an object from the base class
virtual void* getObject(const String& name) const;
// Send signalling packet
virtual bool transmitPacket(const DataBlock& packet, bool repeat, PacketType type);
// Interface control. Open device and start worker when enabled, cleanup when disabled
virtual bool control(Operation oper, NamedList* params = 0);
// Process incoming data
virtual bool process();
// Called by the factory to create Zaptel interfaces or spans
static SignallingComponent* create(const String& type, NamedList& name);
protected:
// Check if received any data in the last interval. Notify receiver
virtual void timerTick(const Time& when);
// Check for device events. Notify receiver
void checkEvents();
private:
inline void cleanup(bool release) {
control(Disable,0);
attach(0);
if (release)
RefObject::destruct();
}
ZapDevice m_device; // The device
Thread::Priority m_priority; // Worker thread priority
unsigned char m_errorMask; // Error mask to filter received error events
unsigned int m_numbufs; // The number of buffers used by the channel
unsigned int m_bufsize; // The buffer size
unsigned char* m_buffer; // Read buffer
bool m_readOnly; // Read only interface
bool m_sendReadOnly; // Print send attempt on readonly interface error
int m_notify; // Notify receiver on channel non idle (0: success. 1: not notified. 2: notified)
SignallingTimer m_timerRxUnder; // RX underrun notification
bool m_down; // Interface status
};
// Signalling span used to create voice circuits
class ZapSpan : public SignallingCircuitSpan
{
public:
inline ZapSpan(const NamedList& params)
: SignallingCircuitSpan(params.getValue("debugname"),
static_cast<SignallingCircuitGroup*>(params.getObject("SignallingCircuitGroup")))
{}
virtual ~ZapSpan()
{}
// Create circuits. Insert them into the group
bool init(ZapDevice::Type type, unsigned int offset,
const NamedList& config, const NamedList& defaults, const NamedList& params);
};
// A voice circuit
class ZapCircuit : public SignallingCircuit, public ZapWorkerClient
{
public:
ZapCircuit(ZapDevice::Type type, unsigned int code, unsigned int channel,
ZapSpan* span, const NamedList& config, const NamedList& defaults,
const NamedList& params);
virtual ~ZapCircuit()
{ cleanup(false); }
virtual void destroyed()
{ cleanup(true); }
// Change circuit status. Clear events on status change
// New status is Connect: Open device. Create source/consumer. Start worker
// Cleanup on disconnect
virtual bool status(Status newStat, bool sync = false);
// Update data format for zaptel device and source/consumer
virtual bool updateFormat(const char* format, int direction);
// Setup echo canceller or start echo canceller training
virtual bool setParam(const String& param, const String& value);
// Get circuit data
virtual bool getParam(const String& param, String& value) const;
// Get this circuit or source/consumer
virtual void* getObject(const String& name) const;
// Process incoming data
virtual bool process();
// Send an event
virtual bool sendEvent(SignallingCircuitEvent::Type type, NamedList* params = 0);
// Consume data sent by the consumer
void consume(const DataBlock& data);
protected:
// Close device. Stop worker. Remove source consumer. Change status. Release memory if requested
// Reset echo canceller and tone detector if the device is not closed
void cleanup(bool release, Status stat = Missing, bool stop = true);
// Update format, echo canceller, dtmf detection
bool setFormat(ZapDevice::Format format);
// Get and process some events
void checkEvents();
// Process additional events. Return false if not processed
virtual bool processEvent(int event, char c = 0)
{ return false; }
// Create source buffer and data source and consumer
void createData();
// Enqueue received events
bool enqueueEvent(SignallingCircuitEvent* event);
bool enqueueEvent(int event, SignallingCircuitEvent::Type type);
// Enqueue received digits
bool enqueueDigit(bool tone, char digit);
ZapDevice m_device; // The device
ZapDevice::Type m_type; // Circuit type
ZapDevice::Format m_format; // The data format
String m_specialMode; // Special test mode
bool m_echoCancel; // Echo canceller state
bool m_crtEchoCancel; // Current echo canceller state
unsigned int m_echoTaps; // Echo cancel taps
unsigned int m_echoTrain; // Echo canceller's train period in miliseconds
bool m_dtmfDetect; // Dtmf detection flag
bool m_crtDtmfDetect; // Current dtmf detection state
bool m_canSend; // Not a read only circuit
unsigned char m_idleValue; // Value used to fill incomplete source buffer
Thread::Priority m_priority; // Worker thread priority
ZapSource* m_source; // The data source
ZapConsumer* m_consumer; // The data consumer
DataBlock m_sourceBuffer; // Data source buffer
DataBlock m_consBuffer; // Data consumer buffer
unsigned int m_buflen; // Data block length
unsigned int m_consBufMax; // Max consumer buffer length
unsigned int m_consErrors; // Consumer. Total number of send failures
unsigned int m_consErrorBytes; // Consumer. Total number of lost bytes
unsigned int m_consTotal; // Consumer. Total number of bytes transferred
int m_errno; // Last write error
};
// An analog circuit
class ZapAnalogCircuit : public ZapCircuit
{
public:
inline ZapAnalogCircuit(ZapDevice::Type type, unsigned int code, unsigned int channel,
ZapSpan* span, const NamedList& config, const NamedList& defaults,
const NamedList& params)
: ZapCircuit(type,code,channel,span,config,defaults,params),
m_hook(true)
{}
virtual ~ZapAnalogCircuit()
{}
// Change circuit status. Clear events on status change
// Reserved: Open device and start worker if old status is not Connected
// Connect: Create source/consumer
// Cleanup on disconnect
virtual bool status(Status newStat, bool sync);
// Get circuit data
virtual bool getParam(const String& param, String& value) const;
// Set line polarity
virtual bool setParam(const String& param, const String& value);
// Send an event
virtual bool sendEvent(SignallingCircuitEvent::Type type, NamedList* params = 0);
// Process incoming data
virtual bool process();
protected:
// Process additional events. Return false if not processed
virtual bool processEvent(int event, char c = 0);
// Change hook state if different
void changeHook(bool hook);
bool m_hook; // The remote end's hook status
};
// Data source
class ZapSource : public DataSource
{
public:
ZapSource(ZapCircuit* circuit, const char* format);
virtual ~ZapSource();
inline void changeFormat(const char* format)
{ m_format = format; }
private:
String m_address;
};
// Data consumer
class ZapConsumer : public DataConsumer
{
friend class ZapCircuit;
public:
ZapConsumer(ZapCircuit* circuit, const char* format);
virtual ~ZapConsumer();
inline void changeFormat(const char* format)
{ m_format = format; }
virtual unsigned long Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags)
{ if (m_circuit) m_circuit->consume(data); return invalidStamp(); }
private:
ZapCircuit* m_circuit;
String m_address;
};
// The Zaptel module
class ZapModule : public Module
{
public:
// Additional module commands
enum StatusCommands {
ZapSpans = 0, // Show all zaptel spans
ZapChannels = 1, // Show all configured zaptel channels
ZapChannelsAll = 2, // Show all zaptel channels
StatusCmdCount = 3
};
ZapModule();
~ZapModule();
inline const String& prefix()
{ return m_prefix; }
void append(ZapDevice* dev);
void remove(ZapDevice* dev);
inline void openClose(bool open) {
Lock lock(this);
if (open)
m_active++;
else
m_active--;
}
virtual void initialize();
// Find a device by its Zaptel channel
ZapDevice* findZaptelChan(int chan);
// Additional module status commands
static String s_statusCmd[StatusCmdCount];
protected:
virtual bool received(Message& msg, int id);
virtual void statusModule(String& str);
virtual void statusParams(String& str);
virtual void statusDetail(String& str);
virtual bool commandComplete(Message& msg, const String& partLine,
const String& partWord);
private:
bool m_init; // Already initialized flag
String m_prefix; // Module prefix
String m_statusCmd; // Status command for this module (status Zaptel)
ObjList m_devices; // Device list
// Statistics
unsigned int m_count; // The number of devices in the list
unsigned int m_active; // The number of active(opened) devices
};
/**
* Module data and functions
*/
static ZapModule plugin;
YSIGFACTORY2(ZapInterface); // Factory used to create zaptel interfaces and spans
static Mutex s_ifaceNotifyMutex(true,"ZapCard::notify"); // ZapInterface: lock recv data notification counter
static Mutex s_sourceAccessMutex(false,"ZapCard::source"); // ZapSource access to pointers
static const char* s_chanParamsHdr = "format=Type|ZaptelType|Span|SpanPos|Alarms|UsedBy";
static const char* s_spanParamsHdr = "format=Channels|Total|Alarms|Name|Description";
// Get a boolean value from received parameters or other sections in config
// Priority: parameters, config, defaults
static inline bool getBoolValue(const char* param, const NamedList& config,
const NamedList& defaults, const NamedList& params, bool defVal = false)
{
defVal = config.getBoolValue(param,defaults.getBoolValue(param,defVal));
return params.getBoolValue(param,defVal);
}
static void sendModuleUpdate(const String& notif, const String& device, bool& notifStat, int status = 0)
{
Message* msg = new Message("module.update");
msg->addParam("module",plugin.name());
msg->addParam("interface",device);
msg->addParam("notify",notif);
if(notifStat && status == SignallingInterface::LinkUp) {
notifStat = false;
Engine::enqueue(msg);
return;
}
if (!notifStat && status == SignallingInterface::LinkDown) {
notifStat = true;
Engine::enqueue(msg);
return;
}
if (notif == "alarm") {
if (status == ZapDevice::Yellow)
msg->addParam("notify","RAI");
if (status == ZapDevice::Blue)
msg->addParam("notify","AIS");
Engine::enqueue(msg);
return;
}
TelEngine::destruct(msg);
}
/**
* ZapWorkerClient
*/
bool ZapWorkerClient::running() const
{
return m_thread && m_thread->running();
}
bool ZapWorkerClient::start(Thread::Priority prio, DebugEnabler* dbg, const String& addr)
{
if (!m_thread)
m_thread = new ZapWorkerThread(this,addr,prio);
if (m_thread->running())
return true;
if (m_thread->startup())
return true;
m_thread->cancel(true);
m_thread = 0;
Debug(dbg,DebugWarn,"Failed to start %s for %s [%p]",
ZapWorkerThread::s_threadName,addr.c_str(),dbg);
return false;
}
void ZapWorkerClient::stop()
{
if (!m_thread)
return;
m_thread->cancel();
while (m_thread)
Thread::yield();
}
/**
* ZapWorkerThread
*/
const char* ZapWorkerThread::s_threadName = "Zap Worker";
ZapWorkerThread::~ZapWorkerThread()
{
DDebug(&plugin,DebugAll,"%s is terminated for client (%p): %s",
s_threadName,m_client,m_address.c_str());
if (m_client)
m_client->m_thread = 0;
}
void ZapWorkerThread::run()
{
if (!m_client)
return;
DDebug(&plugin,DebugAll,"%s is running for client (%p): %s",
s_threadName,m_client,m_address.c_str());
while (true) {
if (m_client->process())
Thread::check(true);
else
Thread::yield(true);
}
}
/**
* ZapDevice
*/
static TokenDict s_alarms[] = {
{"recover", ZapDevice::Recover},
{"loopback", ZapDevice::Loopback},
{"yellow", ZapDevice::Yellow},
{"red", ZapDevice::Red},
{"blue", ZapDevice::Blue},
{"not-open", ZapDevice::NotOpen},
{0,0}
};
// Zaptel signalling type
static TokenDict s_zaptelSig[] = {
{"NONE", DAHDI_SIG_NONE}, // Channel not configured
{"FXSLS", DAHDI_SIG_FXSLS},
{"FXSGS", DAHDI_SIG_FXSGS},
{"FXSKS", DAHDI_SIG_FXSKS},
{"FXOLS", DAHDI_SIG_FXOLS},
{"FXOGS", DAHDI_SIG_FXOGS},
{"FXOKS", DAHDI_SIG_FXOKS},
{"E&M", DAHDI_SIG_EM}, // Ear & mouth
{"CLEAR", DAHDI_SIG_CLEAR}, // Clear channel
{"HDLCRAW", DAHDI_SIG_HDLCRAW}, // Raw unchecked HDLC
{"HDLCFCS", DAHDI_SIG_HDLCFCS}, // HDLC with FCS calculation
{"HDLCNET", DAHDI_SIG_HDLCNET}, // HDLC Network
{"SLAVE", DAHDI_SIG_SLAVE}, // Slave to another channel
{"SF", DAHDI_SIG_SF}, // Single Freq. tone only, no sig bits
{"CAS", DAHDI_SIG_CAS}, // Just get bits
{"DACS", DAHDI_SIG_DACS}, // Cross connect
{"EM_E1", DAHDI_SIG_EM_E1}, // E1 E&M Variation
{"DACS_RBS", DAHDI_SIG_DACS_RBS}, // Cross connect w/ RBS
{"HARDHDLC", DAHDI_SIG_HARDHDLC},
{0,0}
};
#define MAKE_NAME(x) { #x, ZapDevice::x }
static TokenDict s_events[] = {
MAKE_NAME(None),
MAKE_NAME(OnHook),
MAKE_NAME(OffHookRing),
MAKE_NAME(WinkFlash),
MAKE_NAME(Alarm),
MAKE_NAME(NoAlarm),
MAKE_NAME(HdlcAbort),
MAKE_NAME(HdlcOverrun),
MAKE_NAME(BadFCS),
MAKE_NAME(DialComplete),
MAKE_NAME(RingerOn),
MAKE_NAME(RingerOff),
MAKE_NAME(HookComplete),
MAKE_NAME(BitsChanged),
MAKE_NAME(PulseStart),
MAKE_NAME(Timeout),
MAKE_NAME(TimerPing),
MAKE_NAME(RingBegin),
MAKE_NAME(Polarity),
MAKE_NAME(PulseDigit),
MAKE_NAME(DtmfDown),
MAKE_NAME(DtmfUp),
MAKE_NAME(DigitEvent),
#ifdef DAHDI_EVENT_REMOVED
MAKE_NAME(Removed),
#endif
{0,0}
};
static TokenDict s_hookEvents[] = {
MAKE_NAME(HookOn),
MAKE_NAME(HookOff),
MAKE_NAME(HookWink),
MAKE_NAME(HookFlash),
MAKE_NAME(HookStart),
MAKE_NAME(HookRing),
MAKE_NAME(HookRingOff),
{0,0}
};
static TokenDict s_ioctl_request[] = {
MAKE_NAME(SetChannel),
MAKE_NAME(SetBlkSize),
MAKE_NAME(SetBuffers),
MAKE_NAME(SetFormat),
MAKE_NAME(SetAudioMode),
MAKE_NAME(SetEchoCancel),
MAKE_NAME(SetDial),
MAKE_NAME(SetHook),
MAKE_NAME(SetToneDetect),
MAKE_NAME(SetPolarity),
MAKE_NAME(SetLinear),
MAKE_NAME(SetDialParams),
MAKE_NAME(GetParams),
MAKE_NAME(GetEvent),
MAKE_NAME(GetInfo),
MAKE_NAME(GetVersion),
MAKE_NAME(GetDialParams),
MAKE_NAME(StartEchoTrain),
MAKE_NAME(FlushBuffers),
MAKE_NAME(SendTone),
{0,0}
};
static TokenDict s_types[] = {
MAKE_NAME(DChan),
MAKE_NAME(E1),
MAKE_NAME(T1),
MAKE_NAME(BRI),
MAKE_NAME(FXO),
MAKE_NAME(FXS),
MAKE_NAME(Control),
{"not-used", ZapDevice::TypeUnknown},
{0,0}
};
#undef MAKE_NAME
static TokenDict s_formats[] = {
{"slin", ZapDevice::Slin},
{"default", ZapDevice::Default},
{"mulaw", ZapDevice::Mulaw},
{"alaw", ZapDevice::Alaw},
{0,0}
};
#ifdef HAVE_ZAP
const char* ZapDevice::s_zapCtlName = "/dev/zap/ctl";
const char* ZapDevice::s_zapDevName = "/dev/zap/channel";
#else
const char* ZapDevice::s_zapCtlName = "/dev/dahdi/ctl";
const char* ZapDevice::s_zapDevName = "/dev/dahdi/channel";
#endif
ZapDevice::ZapDevice(Type t, SignallingComponent* dbg, unsigned int chan,
unsigned int circuit)
: m_type(t),
m_zapsig(-1),
m_owner(dbg),
m_handle(-1),
m_channel(chan),
m_span(-1),
m_spanPos(-1),
m_alarms(NotOpen),
m_rxHookSig(-1),
m_savedEvent(0),
m_canRead(false),
m_event(false),
m_readError(false),
m_writeError(false),
m_selectError(false)
{
XDebug(&plugin,DebugNote,"ZapDevice type=%s chan=%u owner=%s cic=%u [%p]",
lookup(t,s_types),chan,dbg?dbg->debugName():"",circuit,this);
close();
this->channel(chan,circuit);
if (m_type == Control || m_type == TypeUnknown) {
m_owner = 0;
return;
}
plugin.append(this);
}
// Create a device used to query the driver (chan=0) or a zaptel channel
ZapDevice::ZapDevice(unsigned int chan, bool disableDbg, bool open)
: m_type(chan ? TypeUnknown : Control),
m_zapsig(-1),
m_owner(0),
m_handle(-1),
m_channel(chan),
m_span(-1),
m_spanPos(-1),
m_alarms(NotOpen),
m_rxHookSig(-1),
m_savedEvent(0),
m_canRead(false),
m_event(false),
m_readError(false),
m_writeError(false),
m_selectError(false)
{
XDebug(&plugin,DebugNote,"ZapDevice(ZaptelQuery) type=%s chan=%u [%p]",
lookup(m_type,s_types),chan,this);
close();
channel(chan,0);
m_owner = new SignallingCircuitGroup(0,0,"ZaptelQuery");
if (disableDbg)
m_owner->debugEnabled(false);
if (open)
this->open(0,160);
}
ZapDevice::~ZapDevice()
{
XDebug(&plugin,DebugNote,"ZapDevice destruct type=%s chan=%u owner=%s [%p]",
lookup(m_type,s_types),m_channel,m_owner?m_owner->debugName():"",this);
if (m_type == Control || m_type == TypeUnknown)
TelEngine::destruct(m_owner);
plugin.remove(this);
close();
}
void ZapDevice::channel(unsigned int chan, unsigned int circuit)
{
m_channel = chan;
m_zapName << plugin.name() << "/" << m_channel;
m_address << (m_owner ? m_owner->debugName() : "");
if (m_type != DChan && m_type != Control && m_address) {
m_name << "ZapCircuit(" << circuit << "). ";
m_address << "/" << circuit;
}
}
// Open the device. Specify channel to use.
// Circuit: Set block size
// Interface: Check channel mode. Set buffers
bool ZapDevice::open(unsigned int numbufs, unsigned int bufsize)
{
close();
if (m_type == DChan || m_type == Control)
m_handle = ::open(zapDevName(),O_RDWR,0600);
else
m_handle = ::open(zapDevName(),O_RDWR|O_NONBLOCK);
if (m_handle < 0) {
Debug(m_owner,DebugWarn,"%sFailed to open '%s'. %d: %s [%p]",
m_name.safe(),zapDevName(),errno,::strerror(errno),m_owner);
return false;
}
// Done if opening the main zaptel device
if (m_type == Control)
return true;
// Notify plugin if opened for normal (not for query properties) use
if (m_type != TypeUnknown)
plugin.openClose(true);
m_alarms = 0;
m_alarmsText = "";
while (true) {
// Specify the channel to use
if (!ioctl(SetChannel,&m_channel))
break;
struct dahdi_params par;
if (!ioctl(GetParams,&par))
break;
m_span = par.spanno;
m_spanPos = par.chanpos;
m_zapsig = par.sigtype;
checkAlarms();
if (m_type != DChan) {
if (bufsize && !ioctl(SetBlkSize,&bufsize))
break;
DDebug(m_owner,DebugAll,"%sBlock size set to %u on channel %u [%p]",
m_name.safe(),bufsize,m_channel,m_owner);
return true;
}
// Open for an interface
// Check channel mode
if (par.sigtype != DAHDI_SIG_HDLCFCS && par.sigtype != DAHDI_SIG_HARDHDLC) {
Debug(m_owner,DebugWarn,"Channel %u is not in '%s' or '%s' mode [%p]",
m_channel,lookup(DAHDI_SIG_HDLCFCS,s_zaptelSig),
lookup(DAHDI_SIG_HARDHDLC,s_zaptelSig),m_owner);
break;
}
// Set buffers
struct dahdi_bufferinfo bi;
bi.txbufpolicy = DAHDI_POLICY_IMMEDIATE;
bi.rxbufpolicy = DAHDI_POLICY_IMMEDIATE;
bi.numbufs = numbufs;
bi.bufsize = bufsize;
if (ioctl(SetBuffers,&bi))
DDebug(m_owner,DebugAll,"%snumbufs=%u bufsize=%u on channel %u [%p]",
m_name.safe(),numbufs,bufsize,m_channel,m_owner);
return true;
}
close();
return false;
}
// Close device. Reset handle
void ZapDevice::close()
{
m_alarms = NotOpen;
m_alarmsText = lookup(NotOpen,s_alarms);
m_span = -1;
m_spanPos = -1;
m_zapsig = -1;
if (!valid())
return;
::close(m_handle);
m_handle = -1;
if (m_type != Control && m_type != TypeUnknown)
plugin.openClose(false);
}
// Set data format. Fails if called for an interface
bool ZapDevice::setFormat(Format format)
{
if (m_type == DChan)
return false;
if (!ioctl(SetFormat,&format,0)) {
Debug(m_owner,DebugNote,"%sFailed to set format '%s' on channel %u [%p]",
m_name.safe(),lookup(format,s_formats,String((int)format)),
m_channel,m_owner);
return false;
}
DDebug(m_owner,DebugAll,"%sFormat set to '%s' on channel %u [%p]",
m_name.safe(),lookup(format,s_formats),m_channel,m_owner);
return true;
}
// Set/unset tone detection
bool ZapDevice::setDtmfDetect(bool detect)
{
int tmp = 0;
#ifdef DAHDI_TONEDETECT
setLinear(0,DebugAll);
if (detect)
tmp = DAHDI_TONEDETECT_ON | DAHDI_TONEDETECT_MUTE;
#endif
if (!ioctl(SetToneDetect,&tmp,detect?DebugNote:DebugAll))
return false;
DDebug(m_owner,DebugAll,"%sTone detector %s on channel %u [%p]",
m_name.safe(),detect?"started":"stopped",m_channel,m_owner);
return true;
}
// Update echo canceller (0: disable)
bool ZapDevice::setEchoCancel(bool enable, unsigned int taps)
{
enable = enable && taps;
int tmp = 1;
if (enable && (type() == E1 || type() == T1) &&
!ioctl(SetAudioMode,&tmp,DebugMild))
return false;
if (!enable)
taps = 0;
if (!ioctl(SetEchoCancel,&taps,DebugMild))
return false;
if (taps)
DDebug(m_owner,DebugAll,
"%sEcho canceller enabled on channel %u (taps=%u) [%p]",
m_name.safe(),m_channel,taps,m_owner);
else
DDebug(m_owner,DebugAll,"%sEcho canceller disabled on channel %u [%p]",
m_name.safe(),m_channel,m_owner);
return true;
}
// Start echo training
bool ZapDevice::startEchoTrain(unsigned int period)
{
if (!period)
return true;
if (!ioctl(StartEchoTrain,&period,DebugNote))
return false;
DDebug(m_owner,DebugAll,"%sEcho train started for %u ms on channel %u [%p]",
m_name.safe(),period,m_channel,m_owner);
return true;
}
// Poll for hook events (passive FXO)
void ZapDevice::pollHook()
{
if (m_rxHookSig < 0)
return;
struct dahdi_params par;
if (!ioctl(GetParams,&par))
return;
int rxsig = par.rxhooksig;
if (rxsig != RxSigOnHook)
rxsig = RxSigOffHook;
if (m_rxHookSig == rxsig)
return;
// state changed, save the event for later
m_rxHookSig = rxsig;
// states are reversed but that's how Zaptel is...
m_savedEvent = (rxsig == RxSigOnHook) ? DAHDI_EVENT_WINKFLASH : DAHDI_EVENT_ONHOOK;
}
// Send hook events
bool ZapDevice::sendHook(HookEvent event)
{
const char* name = lookup(event,s_hookEvents);
if (!name) {
Debug(m_owner,DebugStub,"%sRequest to send unhandled hook event %u [%p]",
m_name.safe(),event,this);
return false;
}
DDebug(m_owner,DebugAll,"%sSending hook event '%s' on channel %u [%p]",
m_name.safe(),name,m_channel,m_owner);
return ioctl(SetHook,&event);
}
// Send DTMFs events using dialing or tone structure
// dtmf: true - send DTMF (tone dialing), false - send MF (pulse dialing)
// op: The dial operation to use
// allDigits: true to send all digits at once
// useTone: Used only if 'allDigits' is false and 'dtmf' is true
bool ZapDevice::sendDtmf(const char* tone, bool dtmf, DialOperation op,
bool allDigits, bool useTone)
{
XDebug(m_owner,DebugAll,"%ssendDtmf('%s',%u,%u,%u,%u) [%p]",
m_name.safe(),tone,dtmf,op,allDigits,useTone,this);
if (!(tone && *tone))
return false;
struct dahdi_dialoperation dop;
dop.op = op;
dop.dialstr[0] = dtmf ? 'T' : 'P';
if (allDigits) {
int len = strlen(tone);
int maxLen = DAHDI_MAX_DTMF_BUF - 2;
if (len > maxLen) {
Debug(m_owner,DebugNote,
"%sCan't send DTMF '%s' (len %d greater then max len %d) [%p]",
m_name.safe(),tone,len,maxLen,this);
return false;
}
strcpy(dop.dialstr+1,tone);
DDebug(m_owner,DebugAll,"%sSending DTMF '%s' on channel %u [%p]",
m_name.safe(),dop.dialstr,m_channel,this);
return ioctl(ZapDevice::SetDial,&dop,DebugMild);
}
if (useTone && dtmf)
for (; *tone; tone++) {
if (!sendDtmf(*tone))
return false;
}
else {
dop.dialstr[2] = 0;
for (; *tone; tone++) {
dop.dialstr[1] = *tone;
DDebug(m_owner,DebugAll,"%sSending DTMF '%s' on channel %u [%p]",
m_name.safe(),dop.dialstr,m_channel,this);
if (!ioctl(ZapDevice::SetDial,&dop,DebugMild))
return false;
}
}
return true;
}
// Send DTMF event using tone structure
bool ZapDevice::sendDtmf(char tone)
{
XDebug(m_owner,DebugAll,"%ssendDtmf('%c') [%p]",m_name.safe(),tone,this);
#define YZAP_TONES 20
static char tones[YZAP_TONES+1] = "0123456789*#ABCDabcd";
static int zapTones[YZAP_TONES] = {
DAHDI_TONE_DTMF_0, DAHDI_TONE_DTMF_1, DAHDI_TONE_DTMF_2, DAHDI_TONE_DTMF_3,
DAHDI_TONE_DTMF_4, DAHDI_TONE_DTMF_5, DAHDI_TONE_DTMF_6, DAHDI_TONE_DTMF_7,
DAHDI_TONE_DTMF_8, DAHDI_TONE_DTMF_9, DAHDI_TONE_DTMF_s, DAHDI_TONE_DTMF_p,
DAHDI_TONE_DTMF_A, DAHDI_TONE_DTMF_B, DAHDI_TONE_DTMF_C, DAHDI_TONE_DTMF_D,
DAHDI_TONE_DTMF_A, DAHDI_TONE_DTMF_B, DAHDI_TONE_DTMF_C, DAHDI_TONE_DTMF_D
};
// Get zaptel tone
int zapTone = 0;
for (; zapTone < YZAP_TONES; zapTone++)
if (tone == tones[zapTone])
break;
if (zapTone == YZAP_TONES) {
Debug(m_owner,DebugNote,"%sCan't send invalid DTMF '%c' on channel %u [%p]",
m_name.safe(),tone,m_channel,this);
return false;
}
DDebug(m_owner,DebugAll,"%sSending DTMF '%c' (%d) on channel %u [%p]",
m_name.safe(),tone,zapTones[zapTone],m_channel,this);
return ioctl(ZapDevice::SendTone,&zapTones[zapTone],DebugMild);
#undef YZAP_TONES
}
// Get an event. Return 0 if no events. Set digit if the event is a DTMF/PULSE digit
int ZapDevice::getEvent(char& digit)
{
int event = m_savedEvent;
if (event)
m_savedEvent = 0;
else if (!ioctl(GetEvent,&event,DebugMild))
return 0;
if ((m_zapsig == DAHDI_SIG_EM) && (m_type == FXO)) {
// For an "E&M FXO" the meanings of on/off hook change
switch (event) {
case OnHook:
event = OffHookRing;
break;
case OffHookRing:
event = RingBegin;
break;
}
}
if (event & DigitEvent) {
digit = (char)event;
event &= DigitEvent;
XDebug(m_owner ,DebugAll,"%sGot digit event %d '%s'=%c on channel %u [%p]",
m_name.safe(),event,lookup(event,s_events),digit,m_channel,m_owner);
}
#ifdef DEBUG
else if (event)
Debug(m_owner,DebugAll,"%sGot event %d on channel %u [%p]",
m_name.safe(),event,m_channel,m_owner);
#endif
return event;
}
// Get alarms from this device. Return true if alarms changed
bool ZapDevice::checkAlarms()
{
struct dahdi_spaninfo info;
memset(&info,0,sizeof(info));
info.spanno = m_span;
if (!(ioctl(GetInfo,&info,DebugAll)))
return false;
if (m_alarms == info.alarms)
return false;
m_alarms = info.alarms;
m_alarmsText = "";
if (m_alarms) {
for(int i = 0; s_alarms[i].token; i++)
if (m_alarms & s_alarms[i].value) {
m_alarmsText.append(s_alarms[i].token,",");
if (s_alarms[i].value == ZapDevice::Yellow || s_alarms[i].value == ZapDevice::Blue) {
bool notifStat = false;
sendModuleUpdate("alarm",zapName(),notifStat,s_alarms[i].value);
}
}
Debug(m_owner,DebugNote,"%sAlarms changed (%d,'%s') on channel %u [%p]",
m_name.safe(),m_alarms,m_alarmsText.safe(),m_channel,m_owner);
}
return true;
}
// Reset device's alarms
void ZapDevice::resetAlarms()
{
m_alarms = 0;
m_alarmsText = "";
Debug(m_owner,DebugInfo,"%sNo more alarms on channel %u [%p]",
m_name.safe(),m_channel,m_owner);
}
// Flush read/write buffers
bool ZapDevice::flushBuffers(FlushTarget target)
{
if (!ioctl(FlushBuffers,&target,DebugNote))
return false;
#ifdef DEBUG
String tmp;
if (target & FlushRead)
tmp.append("read","/");
if (target & FlushWrite)
tmp.append("write","/");
if (target & FlushEvent)
tmp.append("events","/");
DDebug(m_owner,DebugAll,"%sFlushed buffers (%s) on channel %u [%p]",
m_name.safe(),tmp.c_str(),m_channel,m_owner);
#endif
return true;
}
// Check if received data. Wait usec microseconds before returning
bool ZapDevice::select(unsigned int usec)
{
FD_ZERO(&m_rdfds);
FD_SET(m_handle, &m_rdfds);
FD_ZERO(&m_errfds);
FD_SET(m_handle, &m_errfds);
m_tv.tv_sec = 0;
m_tv.tv_usec = usec;
int sel = ::select(m_handle+1,&m_rdfds,NULL,&m_errfds,&m_tv);
if (sel >= 0) {
m_event = FD_ISSET(m_handle,&m_errfds);
m_canRead = FD_ISSET(m_handle,&m_rdfds);
m_selectError = false;
return true;
}
if (!(canRetry() || m_selectError)) {
Debug(m_owner,DebugWarn,"%sSelect failed on channel %u. %d: %s [%p]",
m_name.safe(),m_channel,errno,::strerror(errno),m_owner);
m_selectError = true;
}
return false;
}
int ZapDevice::recv(void* buffer, int len)
{
errno = 0;
int r = ::read(m_handle,buffer,len);
if (r >= 0) {
m_event = false;
m_readError = false;
return r;
}
// The caller should check for events if the error is ELAST
m_event = (errno == ELAST);
if (m_event)
return -1;
if (!(canRetry() || m_readError)) {
Debug(m_owner,DebugWarn,"%sRead failed on channel %u. %d: %s [%p]",
m_name.safe(),m_channel,errno,::strerror(errno),m_owner);
m_readError = true;
}
return -1;
}
int ZapDevice::send(const void* buffer, int len)
{
errno = 0;
int w = ::write(m_handle,buffer,len);
if (w == len) {
m_writeError = false;
return w;
}
if (errno == ELAST)
m_event = true;
else if (!m_writeError) {
Debug(m_owner,DebugWarn,
"%sWrite failed on channel %u (sent %d instead of %d). %d: %s [%p]",
m_name.safe(),m_channel,w>=0?w:0,len,errno,::strerror(errno),m_owner);
m_writeError = true;
}
return (w < 0 ? -1 : w);
}
// Get driver version and echo canceller
bool ZapDevice::getVersion(NamedList& dest)
{
struct dahdi_versioninfo info;
if (!ioctl(GetVersion,&info,DebugNote))
return false;
dest.setParam("version",info.version);
dest.setParam("echocanceller",info.echo_canceller);
return true;
}
// Get span info
bool ZapDevice::getSpanInfo(int span, NamedList& dest, int* spans)
{
struct dahdi_spaninfo info;
memset(&info,0,sizeof(info));
info.spanno = (span != -1) ? span : m_span;
if (!ioctl(GetInfo,&info,DebugNote))
return false;
dest.addParam("span",String(span));
dest.addParam("name",info.name);
dest.addParam("desc",info.desc);
dest.addParam("alarms",String(info.alarms));
String alarmsText;
for(int i = 0; s_alarms[i].token; i++)
if (info.alarms & s_alarms[i].value)
alarmsText.append(s_alarms[i].token,",");
dest.addParam("alarmstext",alarmsText);
dest.addParam("configured-chans",String(info.numchans));
dest.addParam("total-chans",String(info.totalchans));
if (spans)
*spans = info.totalspans;
return true;
}
// Get channel parameters
bool ZapDevice::getChanParams(NamedList& dest)
{
struct dahdi_params par;
if (!ioctl(GetParams,&par))
return false;
dest.addParam("format",lookup(par.curlaw,s_formats));
dest.addParam("prewinktime",String(par.prewinktime));
dest.addParam("preflashtime",String(par.preflashtime));
dest.addParam("winktime",String(par.winktime));
dest.addParam("flashtime",String(par.flashtime));
dest.addParam("starttime",String(par.starttime));
dest.addParam("rxwinktime",String(par.rxwinktime));
dest.addParam("rxflashtime",String(par.rxflashtime));
dest.addParam("debouncetime",String(par.debouncetime));
dest.addParam("pulsebreaktime",String(par.pulsebreaktime));
dest.addParam("pulsemaketime",String(par.pulsemaketime));
dest.addParam("pulseaftertime",String(par.pulseaftertime));
return true;
}
// Set/get dial parameters (DTMF/MF length)
bool ZapDevice::dialParams(bool set, int& toneLen, int& mfLen)
{
struct dahdi_dialparams dp;
::memset(&dp,0,sizeof(struct dahdi_dialparams));
if (!set) {
if (!ioctl(GetDialParams,&dp,DebugMild))
return false;
toneLen = dp.dtmf_tonelen;
mfLen = dp.mfv1_tonelen;
return true;
}
dp.dtmf_tonelen = toneLen;
dp.mfv1_tonelen = mfLen;
return ioctl(SetDialParams,&dp,DebugNote);
}
// Make IOCTL requests on this device
bool ZapDevice::ioctl(IoctlRequest request, void* param, int level)
{
if (!param) {
Debug(&plugin,DebugStub,"ZapDevice::ioctl(). 'param' is missing");
return false;
}
int ret = -1;
switch (request) {
case GetEvent:
ret = ::ioctl(m_handle,DAHDI_GETEVENT,param);
break;
case SetChannel:
ret = ::ioctl(m_handle,DAHDI_SPECIFY,param);
break;
case SetBlkSize:
ret = ::ioctl(m_handle,DAHDI_SET_BLOCKSIZE,param);
break;
case SetBuffers:
ret = ::ioctl(m_handle,DAHDI_SET_BUFINFO,param);
break;
case SetFormat:
ret = ::ioctl(m_handle,DAHDI_SETLAW,param);
break;
case SetAudioMode:
ret = ::ioctl(m_handle,DAHDI_AUDIOMODE,param);
break;
case SetEchoCancel:
ret = ::ioctl(m_handle,DAHDI_ECHOCANCEL,param);
break;
case SetDial:
ret = ::ioctl(m_handle,DAHDI_DIAL,param);
break;
case SetHook:
ret = ::ioctl(m_handle,DAHDI_HOOK,param);
break;
case SetToneDetect:
#ifdef DAHDI_TONEDETECT
ret = ::ioctl(m_handle,DAHDI_TONEDETECT,param);
break;
#else
// Show message only if requested to set tone detection
if (param && *param)
Debug(m_owner,level,"%sIOCTL(%s) failed: unsupported request [%p]",
m_name.safe(),lookup(SetToneDetect,s_ioctl_request),m_owner);
return false;
#endif
case SetPolarity:
ret = ::ioctl(m_handle,DAHDI_SETPOLARITY,param);
break;
case SetLinear:
ret = ::ioctl(m_handle,DAHDI_SETLINEAR,param);
break;
case SetDialParams:
ret = ::ioctl(m_handle,DAHDI_SET_DIALPARAMS,param);
break;
case GetParams:
ret = ::ioctl(m_handle,DAHDI_GET_PARAMS,param);
break;
case GetInfo:
ret = ::ioctl(m_handle,DAHDI_SPANSTAT,param);
break;
case GetDialParams:
ret = ::ioctl(m_handle,DAHDI_GET_DIALPARAMS,param);
break;
case StartEchoTrain:
ret = ::ioctl(m_handle,DAHDI_ECHOTRAIN,param);
break;
case FlushBuffers:
ret = ::ioctl(m_handle,DAHDI_FLUSH,param);
break;
case SendTone:
ret = ::ioctl(m_handle,DAHDI_SENDTONE,param);
break;
case GetVersion:
ret = ::ioctl(m_handle,DAHDI_GETVERSION,param);
break;
}
if (!ret || errno == EINPROGRESS) {
if (errno == EINPROGRESS)
DDebug(m_owner,DebugAll,"%sIOCTL(%s) in progress on channel %u (param=%d) [%p]",
m_name.safe(),lookup(request,s_ioctl_request),
m_channel,*(int*)param,m_owner);
#ifdef XDEBUG
else if (request != GetEvent)
Debug(m_owner,DebugAll,"%sIOCTL(%s) succedded on channel %u (param=%d) [%p]",
m_name.safe(),lookup(request,s_ioctl_request),
m_channel,*(int*)param,m_owner);
#endif
return true;
}
Debug(m_owner,level,"%sIOCTL(%s) failed on channel %u (param=%d). %d: %s [%p]",
m_name.safe(),lookup(request,s_ioctl_request),
m_channel,*(int*)param,errno,::strerror(errno),m_owner);
return false;
}
/**
* ZapInterface
*/
ZapInterface::ZapInterface(const NamedList& params)
: SignallingComponent(params,&params,"tdm"),
m_device(ZapDevice::DChan,this,0,0),
m_priority(Thread::Normal),
m_errorMask(255),
m_numbufs(16), m_bufsize(1024), m_buffer(0),
m_readOnly(false), m_sendReadOnly(false),
m_notify(0),
m_timerRxUnder(0)
{
m_buffer = new unsigned char[m_bufsize + ZAP_CRC_LEN];
XDebug(this,DebugAll,"ZapInterface::ZapInterface() [%p]",this);
}
ZapInterface::~ZapInterface()
{
cleanup(false);
delete[] m_buffer;
XDebug(this,DebugAll,"ZapInterface::~ZapInterface() [%p]",this);
}
// Called by the factory to create Zaptel interfaces or spans
SignallingComponent* ZapInterface::create(const String& type, NamedList& name)
{
bool circuit = true;
if (type == "SignallingInterface")
circuit = false;
else if (type == "SignallingCircuitSpan")
;
else
return 0;
TempObjectCounter cnt(plugin.objectsCounter());
const String* module = name.getParam("module");
if (module && *module != "zapcard")
return 0;
Configuration cfg(Engine::configFile("zapcard"));
const char* sectName = name.getValue((circuit ? "voice" : "sig"),name.getValue("basename",name));
NamedList* config = cfg.getSection(sectName);
if (!name.getBoolValue(YSTRING("local-config"),false))
config = &name;
else if (!config) {
DDebug(&plugin,DebugConf,"No section '%s' in configuration",c_safe(sectName));
return 0;
} else
name.copyParams(*config);
#ifdef DEBUG
if (plugin.debugAt(DebugAll)) {
String tmp;
name.dump(tmp,"\r\n ",'\'',true);
Debug(&plugin,DebugAll,"ZapInterface::create %s%s",
(circuit ? "span" : "interface"),tmp.c_str());
}
#endif
String sDevType = config->getValue("type");
ZapDevice::Type devType = (ZapDevice::Type)lookup(sDevType,s_types,ZapDevice::E1);
NamedList dummy("general");
NamedList* general = cfg.getSection("general");
if (!general)
general = &dummy;
String sOffset = config->getValue("offset");
unsigned int offset = (unsigned int)sOffset.toInteger(-1);
if (offset == (unsigned int)-1) {
Debug(&plugin,DebugWarn,"Section '%s'. Invalid offset='%s'",
config->c_str(),sOffset.safe());
return 0;
}
if (circuit) {
ZapSpan* span = new ZapSpan(name);
bool ok = false;
if (span->group())
ok = span->init(devType,offset,*config,*general,name);
else
Debug(&plugin,DebugWarn,"Can't create span '%s'. Group is missing",
span->id().safe());
if (ok)
return span;
TelEngine::destruct(span);
return 0;
}
// Check span type
if (devType != ZapDevice::E1 && devType != ZapDevice::T1 && devType != ZapDevice::BRI) {
Debug(&plugin,DebugWarn,"Section '%s'. Can't create D-channel for type='%s'",
config->c_str(),sDevType.c_str());
return 0;
}
// Check channel
String sig = config->getValue("sigchan");
unsigned int count = (devType == ZapDevice::E1 ? 31 : 24);
if (sig.null()) {
switch (devType) {
case ZapDevice::E1:
sig = 16;
break;
case ZapDevice::T1:
sig = 24;
break;
case ZapDevice::BRI:
sig = 3;
break;
default:
break;
}
}
unsigned int code = (unsigned int)sig.toInteger(0);
if (!(sig && code && code <= count)) {
Debug(&plugin,DebugWarn,"Section '%s'. Invalid sigchan='%s' for type='%s'",
config->c_str(),sig.safe(),sDevType.c_str());
return 0;
}
ZapInterface* iface = new ZapInterface(name);
if (iface->init(devType,code,offset+code,*config,*general,name))
return iface;
TelEngine::destruct(iface);
return 0;
}
bool ZapInterface::init(ZapDevice::Type type, unsigned int code, unsigned int channel,
const NamedList& config, const NamedList& defaults, const NamedList& params)
{
TempObjectCounter cnt(plugin.objectsCounter());
m_device.channel(channel,code);
m_readOnly = getBoolValue("readonly",config,defaults,params);
m_priority = Thread::priority(config.getValue("priority",defaults.getValue("priority")));
int rx = params.getIntValue("rxunderrun");
if (rx > 0)
m_timerRxUnder.interval(rx);
int i = params.getIntValue("errormask",config.getIntValue("errormask",255));
m_errorMask = ((i >= 0 && i < 256) ? i : 255);
if (debugAt(DebugInfo)) {
String s;
s << "driver=" << plugin.debugName();
s << " section=" << config.c_str();
s << " type=" << lookup(type,s_types);
s << " channel=" << channel;
s << " errormask=" << (unsigned int)m_errorMask;
s << " readonly=" << String::boolText(m_readOnly);
s << " rxunderruninterval=" << (unsigned int)m_timerRxUnder.interval() << " ms";
s << " numbufs=" << (unsigned int)m_numbufs;
s << " bufsize=" << (unsigned int)m_bufsize;
s << " priority=" << Thread::priority(m_priority);
Debug(this,DebugInfo,"D-channel: %s [%p]",s.c_str(),this);
}
m_down = false;
return true;
}
// Process incoming data
bool ZapInterface::process()
{
if (!m_device.select(100))
return false;
if (!m_device.canRead()) {
if (m_device.event())
checkEvents();
return false;
}
int r = m_device.recv(m_buffer,m_bufsize + ZAP_CRC_LEN);
if (r == -1) {
if (m_device.event())
checkEvents();
return false;
}
if (r < ZAP_CRC_LEN + 1) {
Debug(this,DebugMild,"Short read %u bytes (with CRC) [%p]",r,this);
return false;
}
s_ifaceNotifyMutex.lock();
m_notify = 0;
s_ifaceNotifyMutex.unlock();
DataBlock packet(m_buffer,r - ZAP_CRC_LEN,false);
#ifdef XDEBUG
String hex;
hex.hexify(packet.data(),packet.length(),' ');
Debug(this,DebugAll,"Received data: %s [%p]",hex.safe(),this);
#endif
receivedPacket(packet);
packet.clear(false);
return true;
}
void* ZapInterface::getObject(const String& name) const
{
if (name == "ZapInterface")
return (void*)this;
return SignallingInterface::getObject(name);
}
// Send signalling packet
bool ZapInterface::transmitPacket(const DataBlock& packet, bool repeat, PacketType type)
{
static DataBlock crc(0,ZAP_CRC_LEN);
if (m_readOnly) {
if (!m_sendReadOnly)
Debug(this,DebugWarn,"Attempt to send data on read only interface");
m_sendReadOnly = true;
return false;
}
if (!m_device.valid())
return false;
#ifdef XDEBUG
String hex;
hex.hexify(packet.data(),packet.length(),' ');
Debug(this,DebugAll,"Sending data: %s [%p]",hex.safe(),this);
#endif
DataBlock pkt(packet);
// zaptel needs the extra space to write the CRC there
pkt += crc;
return m_device.send(pkt.data(),pkt.length());
}
// Interface control. Open device and start worker when enabled, cleanup when disabled
bool ZapInterface::control(Operation oper, NamedList* params)
{
DDebug(this,DebugAll,"Control with oper=%u [%p]",oper,this);
switch (oper) {
case Enable:
case Disable:
break;
case EnableTx:
case DisableTx:
if (m_readOnly == (oper == DisableTx))
return TelEngine::controlReturn(params,true);
m_readOnly = (oper == DisableTx);
m_sendReadOnly = false;
Debug(this,DebugInfo,"Tx is %sabled [%p]",m_readOnly?"dis":"en",this);
return TelEngine::controlReturn(params,true);
case Query:
return TelEngine::controlReturn(params,valid());
default:
return SignallingInterface::control(oper,params);
}
if (oper == Enable) {
if (valid())
return TelEngine::controlReturn(params,true);
bool ok = m_device.valid() || m_device.open(m_numbufs,m_bufsize);
if (ok)
ok = ZapWorkerClient::start(m_priority,this,debugName());
if (ok) {
Debug(this,DebugAll,"Enabled [%p]",this);
m_timerRxUnder.start();
}
else {
Debug(this,DebugWarn,"Enable failed [%p]",this);
control(Disable,0);
}
return TelEngine::controlReturn(params,ok);
}
// oper is Disable
bool ok = valid();
m_timerRxUnder.stop();
ZapWorkerClient::stop();
m_device.close();
if (ok)
Debug(this,DebugAll,"Disabled [%p]",this);
return TelEngine::controlReturn(params,true);
}
// Check if received any data in the last interval. Notify receiver
void ZapInterface::timerTick(const Time& when)
{
if (!m_timerRxUnder.timeout(when.msec()))
return;
s_ifaceNotifyMutex.lock();
if (m_notify) {
if (m_notify == 1) {
DDebug(this,DebugMild,"RX idle for " FMT64 "ms. Notifying receiver [%p]",
m_timerRxUnder.interval(),this);
notify(RxUnderrun);
m_notify = 2;
}
}
else
m_notify = 1;
s_ifaceNotifyMutex.unlock();
m_timerRxUnder.start(when.msec());
}
void ZapInterface::checkEvents()
{
char c = 0;
int event = m_device.getEvent(c);
if (!event)
return;
int level = DebugWarn;
switch (event) {
case ZapDevice::Alarm:
case ZapDevice::NoAlarm:
if (event == ZapDevice::Alarm) {
m_device.checkAlarms();
Debug(this,DebugNote,"Alarms changed '%s' [%p]",
m_device.alarmsText().safe(),this);
notify(LinkDown);
sendModuleUpdate("interfaceDown",m_device.zapName(),m_down,LinkDown);
}
else {
m_device.resetAlarms();
DDebug(this,DebugInfo,"No more alarms [%p]",this);
notify(LinkUp);
sendModuleUpdate("interfaceUp",m_device.zapName(),m_down,LinkUp);
}
return;
case ZapDevice::HdlcAbort:
if (m_errorMask & ZAP_ERR_ABORT)
notify(AlignError);
break;
case ZapDevice::HdlcOverrun:
if (m_errorMask & ZAP_ERR_OVERRUN)
notify(RxOverflow);
break;
case ZapDevice::PulseDigit:
case ZapDevice::DtmfDown:
case ZapDevice::DtmfUp:
Debug(this,DebugNote,"Got DTMF event '%s' on D-channel [%p]",
lookup(event,s_events,""),this);
return;
default:
level = DebugStub;
}
DDebug(this,level,"Got event %d ('%s') [%p]",event,lookup(event,s_events,""),this);
}
/**
* ZapSpan
*/
// Create circuits
bool ZapSpan::init(ZapDevice::Type type, unsigned int offset,
const NamedList& config, const NamedList& defaults, const NamedList& params)
{
TempObjectCounter cnt(plugin.objectsCounter());
String voice = config.getValue("voicechans");
unsigned int chans = 0;
bool digital = true;
switch (type) {
case ZapDevice::E1:
if (!voice)
voice = "1-15.17-31";
chans = 31;
m_increment = 32;
break;
case ZapDevice::T1:
if (!voice)
voice = "1-23";
chans = 24;
m_increment = 24;
break;
case ZapDevice::BRI:
if (!voice)
voice = "1-2";
chans = 3;
m_increment = 3;
break;
case ZapDevice::FXO:
case ZapDevice::FXS:
digital = false;
if (!voice)
voice = "1";
chans = (unsigned int)-1;
break;
default:
Debug(m_group,DebugStub,
"ZapSpan('%s'). Can't create circuits for type=%s [%p]",
id().safe(),lookup(type,s_types),this);
return false;
}
unsigned int count = 0;
unsigned int* cics = SignallingUtils::parseUIntArray(voice,1,chans,count,true);
if (!cics) {
Debug(m_group,DebugWarn,
"ZapSpan('%s'). Invalid voicechans='%s' (type=%s,chans=%u) [%p]",
id().safe(),voice.safe(),lookup(type,s_types),chans,this);
return false;
}
if (!digital)
m_increment = chans = count;
m_increment = config.getIntValue("increment",m_increment);
unsigned int start = config.getIntValue("start",params.getIntValue("start",0));
// Create and insert circuits
unsigned int added = 0;
DDebug(m_group,DebugAll,
"ZapSpan('%s'). Creating circuits starting with %u [%p]",
id().safe(),start,this);
for (unsigned int i = 0; i < count; i++) {
unsigned int code = start + cics[i];
unsigned int channel = offset + cics[i];
ZapCircuit* cic = 0;
DDebug(m_group,DebugAll,
"ZapSpan('%s'). Creating circuit code=%u channel=%u [%p]",
id().safe(),code,channel,this);
if (digital)
cic = new ZapCircuit(type,code,channel,this,config,defaults,params);
else
cic = new ZapAnalogCircuit(type,code,channel,this,config,defaults,params);
if (m_group->insert(cic)) {
added++;
continue;
}
TelEngine::destruct(cic);
Debug(m_group,DebugConf,
"ZapSpan('%s'). Duplicate circuit code=%u (channel=%u) [%p]",
id().safe(),code,channel,this);
}
if (!added) {
Debug(m_group,DebugWarn,"ZapSpan('%s'). No circuits inserted for this span [%p]",
id().safe(),this);
delete[] cics;
return false;
}
if (m_group && m_group->debugAt(DebugInfo)) {
String s;
s << "driver=" << plugin.debugName();
s << " section=" << config.c_str();
s << " type=" << lookup(type,s_types);
String c,ch;
for (unsigned int i = 0; i < count; i++) {
c.append(String(start+cics[i]),",");
ch.append(String(offset+cics[i]),",");
}
s << " channels=" << ch;
s << " circuits=" << c;
Debug(m_group,DebugInfo,"ZapSpan('%s') %s [%p]",id().safe(),s.c_str(),this);
}
delete[] cics;
return true;
}
/**
* ZapCircuit
*/
ZapCircuit::ZapCircuit(ZapDevice::Type type, unsigned int code, unsigned int channel,
ZapSpan* span, const NamedList& config, const NamedList& defaults,
const NamedList& params)
: SignallingCircuit(TDM,code,Idle,span->group(),span),
m_device(type,span->group(),channel,code),
m_format(ZapDevice::Alaw),
m_echoCancel(false),
m_crtEchoCancel(false),
m_echoTaps(0),
m_echoTrain(400),
m_dtmfDetect(false),
m_crtDtmfDetect(false),
m_canSend(true),
m_idleValue(255),
m_priority(Thread::Normal),
m_source(0),
m_consumer(0),
m_buflen(0),
m_consBufMax(0),
m_consErrors(0),
m_consErrorBytes(0),
m_consTotal(0),
m_errno(0)
{
m_dtmfDetect = config.getBoolValue("dtmfdetect",true);
if (m_dtmfDetect && ZapDevice::SetToneDetect > 100) {
Debug(group(),DebugWarn,
"ZapCircuit(%u). DTMF detection is not supported by hardware [%p]",
code,this);
m_dtmfDetect = false;
}
m_crtDtmfDetect = m_dtmfDetect;
int tmp = config.getIntValue("echotaps",defaults.getIntValue("echotaps",0));
m_echoTaps = tmp >= 0 ? tmp : 0;
m_crtEchoCancel = m_echoCancel = m_echoTaps;
tmp = (unsigned int)config.getIntValue("echotrain",defaults.getIntValue("echotrain",400));
m_echoTrain = tmp >= 0 ? tmp : 0;
m_canSend = !getBoolValue("readonly",config,defaults,params);
m_buflen = (unsigned int)config.getIntValue("buflen",defaults.getIntValue("buflen",160));
if (!m_buflen)
m_buflen = 160;
m_consBufMax = m_buflen * 4;
m_sourceBuffer.assign(0,m_buflen);
m_idleValue = defaults.getIntValue("idlevalue",0xff);
m_idleValue = params.getIntValue("idlevalue",config.getIntValue("idlevalue",m_idleValue));
m_priority = Thread::priority(config.getValue("priority",defaults.getValue("priority")));
switch (type) {
case ZapDevice::E1:
case ZapDevice::BRI:
m_format = ZapDevice::Alaw;
break;
case ZapDevice::T1:
m_format = ZapDevice::Mulaw;
break;
case ZapDevice::FXO:
if (getBoolValue("trackhook",config,defaults,params)) {
if (m_canSend)
Debug(group(),DebugNote,"ZapCircuit(%u): Hook tracking for active FXO [%p]",code,this);
m_device.initHook();
}
// fall through
case ZapDevice::FXS:
{
const char* f = config.getValue("format",defaults.getValue("format"));
m_format = (ZapDevice::Format)lookup(f,s_formats,ZapDevice::Mulaw);
if (m_format != ZapDevice::Alaw && m_format != ZapDevice::Mulaw)
m_format = ZapDevice::Mulaw;
}
break;
default:
Debug(group(),DebugStub,"ZapCircuit(%u). Unhandled circuit type=%d [%p]",
code,type,this);
}
if (group() && group()->debugAt(DebugAll)) {
String s;
s << "driver=" << plugin.debugName();
s << " type=" << lookup(type,s_types);
s << " channel=" << channel;
s << " cic=" << code;
s << " dtmfdetect=" << String::boolText(m_dtmfDetect);
s << " echotaps=" << m_echoTaps;
s << " echotrain=" << m_echoTrain;
s << " buflen=" << m_buflen;
s << " readonly=" << String::boolText(!m_canSend);
s << " idlevalue=" << (unsigned int)m_idleValue;
s << " priority=" << Thread::priority(m_priority);
Debug(group(),DebugAll,"ZapCircuit %s [%p]",s.c_str(),this);
}
}
// Change circuit status. Clear events on status change
// New status is Connect: Open device. Create source/consumer. Start worker
// Cleanup on disconnect
bool ZapCircuit::status(Status newStat, bool sync)
{
if (SignallingCircuit::status() == newStat)
return true;
if (SignallingCircuit::status() == Missing) {
Debug(group(),DebugNote,
"ZapCircuit(%u). Can't change status to '%s'. Circuit is missing [%p]",
code(),lookupStatus(newStat),this);
return false;
}
TempObjectCounter cnt(plugin.objectsCounter());
Status oldStat = SignallingCircuit::status();
// Allow status change for the following values
switch (newStat) {
case Missing:
case Disabled:
case Idle:
case Reserved:
case Connected:
if (!SignallingCircuit::status(newStat,sync))
return false;
clearEvents();
if (!Engine::exiting())
DDebug(group(),DebugAll,"ZapCircuit(%u). Changed status to '%s' [%p]",
code(),lookupStatus(newStat),this);
if (newStat == Connected)
break;
if (oldStat == Connected)
cleanup(false,newStat);
return true;
default: ;
Debug(group(),DebugStub,
"ZapCircuit(%u). Can't change status to unhandled value %u [%p]",
code(),newStat,this);
return false;
}
// Connected: open device, create source/consumer, start worker
while (true) {
if (!m_device.open(0,m_buflen))
break;
m_device.flushBuffers();
setFormat(m_format);
createData();
String addr;
if (group())
addr << group()->debugName() << "/";
addr << code();
if (!ZapWorkerClient::start(m_priority,group(),addr))
break;
return true;
}
// Rollback on error
cleanup(false,oldStat);
return false;
}
// Update data format for zaptel device and source/consumer
bool ZapCircuit::updateFormat(const char* format, int direction)
{
s_sourceAccessMutex.lock();
RefPointer<ZapSource> src = m_source;
s_sourceAccessMutex.unlock();
if (!(src && format && *format))
return false;
// Do nothing if format is the same
if (src->getFormat() == format && m_consumer && m_consumer->getFormat() == format)
return false;
TempObjectCounter cnt(plugin.objectsCounter());
// Check format
// T1,E1: allow alaw or mulaw
int f = lookup(format,s_formats,-2);
switch (m_device.type()) {
case ZapDevice::E1:
case ZapDevice::T1:
case ZapDevice::BRI:
case ZapDevice::FXS:
case ZapDevice::FXO:
if (f == ZapDevice::Alaw || f == ZapDevice::Mulaw)
break;
// Fallthrough to deny format change
default:
Debug(group(),DebugNote,
"ZapCircuit(%u). Can't set format to '%s' for type=%s [%p]",
code(),format,lookup(m_device.type(),s_types),this);
return false;
}
// Update the format for Zaptel device
if (setFormat((ZapDevice::Format)f)) {
src->changeFormat(format);
if (m_consumer)
m_consumer->changeFormat(format);
return true;
}
Debug(group(),DebugNote,
"ZapCircuit(%u). Failed to update data format to '%s' [%p]",
code(),format,this);
return false;
}
// Setup echo canceller or start echo canceller training
bool ZapCircuit::setParam(const String& param, const String& value)
{
TempObjectCounter cnt(plugin.objectsCounter());
if (param == "echotrain") {
int tmp = value.toInteger(-1);
if (tmp >= 0)
m_echoTrain = tmp;
return m_device.valid() && m_crtEchoCancel && m_device.startEchoTrain(m_echoTrain);
}
if (param == "echocancel") {
if (!value.isBoolean())
return false;
bool tmp = value.toBoolean();
if (tmp == m_crtEchoCancel)
return true;
if (m_echoTaps)
m_crtEchoCancel = tmp;
else if (tmp)
return false;
else
m_crtEchoCancel = false;
if (!m_device.valid())
return false;
bool ok = m_device.setEchoCancel(m_crtEchoCancel,m_echoTaps);
if (m_crtEchoCancel)
m_crtEchoCancel = ok;
return ok;
}
if (param == "echotaps") {
int tmp = value.toInteger();
m_echoTaps = tmp >= 0 ? tmp : 0;
return true;
}
if (param == "tonedetect") {
bool tmp = value.toBoolean();
if (tmp == m_crtDtmfDetect)
return true;
m_crtDtmfDetect = tmp;
if (!m_device.valid())
return true;
bool ok = m_device.setDtmfDetect(m_crtDtmfDetect);
if (m_crtDtmfDetect)
m_crtDtmfDetect = ok;
return ok;
}
if (param == "special_mode") {
m_specialMode = value;
return true;
}
return false;
}
// Get circuit data
bool ZapCircuit::getParam(const String& param, String& value) const
{
TempObjectCounter cnt(plugin.objectsCounter());
if (param == "buflen")
value = m_buflen;
else if (param == "tonedetect")
value = String::boolText(m_crtDtmfDetect);
else if (param == "channel")
value = m_device.channel();
else if (param == "echocancel")
value = String::boolText(m_crtEchoCancel);
else if (param == "echotaps")
value = m_echoTaps;
else if (param == "alarms")
value = m_device.alarmsText();
else if (param == "driver")
value = plugin.debugName();
else
return false;
return true;
}
// Get source or consumer
void* ZapCircuit::getObject(const String& name) const
{
if (name == "ZapCircuit")
return (void*)this;
if (SignallingCircuit::status() == Connected) {
if (name == "DataSource")
return m_source;
if (name == "DataConsumer")
return m_consumer;
}
return SignallingCircuit::getObject(name);
}
// Process incoming data
bool ZapCircuit::process()
{
s_sourceAccessMutex.lock();
RefPointer<ZapSource> src = m_source;
s_sourceAccessMutex.unlock();
if (!(m_device.valid() && SignallingCircuit::status() == Connected && src))
return false;
if (!m_device.select(10))
return false;
if (!m_device.canRead()) {
if (m_device.event())
checkEvents();
return false;
}
int r = m_device.recv(m_sourceBuffer.data(),m_sourceBuffer.length());
if (m_device.event())
checkEvents();
if (r > 0) {
if ((unsigned int)r != m_sourceBuffer.length())
::memset((unsigned char*)m_sourceBuffer.data() + r,m_idleValue,m_sourceBuffer.length() - r);
src->Forward(m_sourceBuffer);
return true;
}
return false;
}
// Send an event through the circuit
bool ZapCircuit::sendEvent(SignallingCircuitEvent::Type type, NamedList* params)
{
XDebug(group(),DebugAll,"ZapCircuit(%u). sendEvent(%u) [%p]",code(),type,this);
if (!m_canSend)
return false;
TempObjectCounter cnt(plugin.objectsCounter());
if (type == SignallingCircuitEvent::Dtmf) {
const char* tones = 0;
bool dtmf = true;
bool dial = true;
if (params) {
tones = params->getValue("tone");
dtmf = !params->getBoolValue("pulse",false);
dial = params->getBoolValue("dial",true);
}
if (dial)
return m_device.sendDtmf(tones,dtmf,ZapDevice::DialReplace,true,false);
return m_device.sendDtmf(tones,dtmf,ZapDevice::DialAppend,false,true);
}
Debug(group(),DebugNote,"ZapCircuit(%u). Unable to send unknown event %u [%p]",
code(),type,this);
return false;
}
// Consume data sent by the consumer
void ZapCircuit::consume(const DataBlock& data)
{
if (!(SignallingCircuit::status() >= Special && m_canSend && data.length()))
return;
// Copy data in buffer
// Throw old data on buffer overrun
m_consTotal += data.length();
if (m_consBuffer.length() + data.length() <= m_consBufMax)
m_consBuffer += data;
else {
Debug(group(),DebugAll,
"ZapCircuit(%u). Buffer overrun old=%u channel=%u (%d: %s) [%p]",
code(),m_consBuffer.length(),m_device.channel(),m_errno,
::strerror(m_errno),this);
m_consErrors++;
m_consErrorBytes += m_consBuffer.length();
m_consBuffer = data;
}
// Send buffer. Stop on error
while (m_consBuffer.length() >= m_buflen) {
int w = m_device.write(m_consBuffer.data(),m_buflen);
if (w <= 0) {
m_errno = errno;
break;
}
m_errno = 0;
m_consBuffer.cut(-w);
XDebug(group(),DebugAll,"ZapCircuit(%u). Sent %d bytes. Remaining: %u [%p]",
code(),w,m_consBuffer.length(),this);
}
}
// Close device. Stop worker. Remove source consumer. Change status. Release memory if requested
// Reset echo canceller and tone detector if the device is not closed
void ZapCircuit::cleanup(bool release, Status stat, bool stop)
{
if (stop || release) {
ZapWorkerClient::stop();
m_device.close();
}
if (m_consumer) {
if (m_consErrors)
Debug(group(),DebugNote,
"ZapCircuit(%u). Consumer errors: %u. Lost: %u/%u [%p]",
code(),m_consErrors,m_consErrorBytes,m_consTotal,this);
TelEngine::destruct(m_consumer);
}
if (m_source) {
s_sourceAccessMutex.lock();
ZapSource* tmp = m_source;
m_source = 0;
s_sourceAccessMutex.unlock();
if (tmp) {
tmp->clear();
TelEngine::destruct(tmp);
}
}
if (release) {
SignallingCircuit::destroyed();
return;
}
status(stat);
m_specialMode.clear();
m_sourceBuffer.clear();
m_consBuffer.clear();
m_consErrors = m_consErrorBytes = m_consTotal = 0;
// Reset echo canceller and tone detector
if (m_device.valid() && (m_crtEchoCancel != m_echoCancel))
m_device.setEchoCancel(m_echoCancel,m_echoTaps);
m_crtEchoCancel = m_echoCancel;
if (m_device.valid() && (m_crtDtmfDetect != m_dtmfDetect))
m_device.setDtmfDetect(m_dtmfDetect);
m_crtDtmfDetect = m_dtmfDetect;
}
// Update format, echo canceller, dtmf detection
bool ZapCircuit::setFormat(ZapDevice::Format format)
{
m_device.flushBuffers();
if (!m_device.setFormat(format))
return false;
if (m_crtEchoCancel)
m_crtEchoCancel = m_device.setEchoCancel(m_crtEchoCancel,m_echoTaps);
if (m_crtDtmfDetect)
m_crtDtmfDetect = m_device.setDtmfDetect(true);
else
m_device.setDtmfDetect(false);
return true;
}
// Get events
void ZapCircuit::checkEvents()
{
char c = 0;
int event = m_device.getEvent(c);
if (!event)
return;
switch (event) {
case ZapDevice::DtmfDown:
case ZapDevice::DtmfUp:
if (!m_crtDtmfDetect) {
DDebug(group(),DebugAll,"ZapCircuit(%u). Ignoring DTMF '%s'=%c [%p]",
code(),lookup(event,s_events,""),c,this);
return;
}
if (event == ZapDevice::DtmfUp)
enqueueDigit(true,c);
else
DDebug(group(),DebugAll,"ZapCircuit(%u). Ignoring '%s'=%c [%p]",
code(),lookup(event,s_events,""),c,this);
return;
case ZapDevice::Alarm:
case ZapDevice::NoAlarm:
if (event == ZapDevice::Alarm) {
if (!m_device.checkAlarms())
return;
SignallingCircuitEvent* e = new SignallingCircuitEvent(this,
SignallingCircuitEvent::Alarm,lookup(event,s_events));
e->addParam("alarms",m_device.alarmsText());
enqueueEvent(e);
}
else {
m_device.resetAlarms();
enqueueEvent(event,SignallingCircuitEvent::NoAlarm);
}
return;
default: ;
}
if (processEvent(event,c))
return;
enqueueEvent(event,SignallingCircuitEvent::Unknown);
}
// Create source buffer and data source and consumer
void ZapCircuit::createData()
{
m_sourceBuffer.assign(0,m_buflen);
const char* format = lookup(m_format,s_formats,"alaw");
m_source = new ZapSource(this,format);
if (m_canSend)
m_consumer = new ZapConsumer(this,format);
}
// Enqueue received events
inline bool ZapCircuit::enqueueEvent(SignallingCircuitEvent* e)
{
if (e) {
addEvent(e);
DDebug(group(),e->type()!=SignallingCircuitEvent::Unknown?DebugAll:DebugStub,
"ZapCircuit(%u). Enqueued event '%s' [%p]",code(),e->c_str(),this);
}
return true;
}
// Enqueue received events
inline bool ZapCircuit::enqueueEvent(int event, SignallingCircuitEvent::Type type)
{
return enqueueEvent(new SignallingCircuitEvent(this,type,lookup(event,s_events)));
}
// Enqueue received digits
bool ZapCircuit::enqueueDigit(bool tone, char digit)
{
char digits[2] = {digit,0};
SignallingCircuitEvent* e = 0;
if (tone) {
e = new SignallingCircuitEvent(this,SignallingCircuitEvent::Dtmf,
lookup(ZapDevice::DtmfUp,s_events));
e->addParam("tone",digits);
}
else {
e = new SignallingCircuitEvent(this,SignallingCircuitEvent::PulseDigit,
lookup(ZapDevice::PulseDigit,s_events));
e->addParam("pulse",digits);
}
return enqueueEvent(e);
}
/**
* ZapAnalogCircuit
*/
// Change circuit status. Clear events on status change
// New status is Reserved: Open device and start worker if old status is not Connected
// New status is Connect: Create source/consumer
// Cleanup on disconnect
bool ZapAnalogCircuit::status(Status newStat, bool sync)
{
if (SignallingCircuit::status() == newStat)
return true;
if (SignallingCircuit::status() == Missing) {
Debug(group(),DebugNote,
"ZapCircuit(%u). Can't change status to '%u'. Circuit is missing [%p]",
code(),newStat,this);
return false;
}
TempObjectCounter cnt(plugin.objectsCounter());
// Allow status change for the following values
switch (newStat) {
case Missing:
case Disabled:
case Idle:
case Reserved:
case Special:
case Connected:
break;
default: ;
Debug(group(),DebugStub,
"ZapCircuit(%u). Can't change status to unhandled value %u [%p]",
code(),newStat,this);
return false;
}
Status oldStat = SignallingCircuit::status();
if (!SignallingCircuit::status(newStat,sync))
return false;
clearEvents();
if (!Engine::exiting())
DDebug(group(),DebugAll,"ZapCircuit(%u). Changed status to %u [%p]",
code(),newStat,this);
if (newStat < Special && m_device.valid())
m_device.flushBuffers();
if (newStat == Reserved) {
// Just cleanup if old status was Connected or the device is already valid
// Otherwise: open device and start worker
if (oldStat == Connected || m_device.valid())
cleanup(false,Reserved,false);
else {
String addr;
if (group())
addr << group()->debugName() << "/";
addr << code();
if (m_device.open(0,m_buflen) && ZapWorkerClient::start(m_priority,group(),addr))
setFormat(m_format);
else
cleanup(false,Idle,true);
}
return SignallingCircuit::status() == Reserved;
}
else if (newStat >= Special) {
if (m_device.valid()) {
createData();
if (newStat == Special) {
Message m("circuit.special");
m.userData(this);
if (group())
m.addParam("group",group()->toString());
if (span())
m.addParam("span",span()->toString());
if (m_specialMode)
m.addParam("mode",m_specialMode);
if (!Engine::dispatch(m))
cleanup(false,Idle,true);
}
}
else
cleanup(false,Idle,true);
return SignallingCircuit::status() == newStat;
}
return true;
}
// Get circuit data
bool ZapAnalogCircuit::getParam(const String& param, String& value) const
{
if (param == "hook") {
value = String::boolText(m_hook);
return true;
}
return ZapCircuit::getParam(param,value);
}
// Set line polarity
bool ZapAnalogCircuit::setParam(const String& param, const String& value)
{
TempObjectCounter cnt(plugin.objectsCounter());
if (param == "polarity") {
if (!(m_device.valid() && value.isBoolean()))
return false;
int state = value.toBoolean() ? 1 : 0;
return m_device.setPolarity(state,DebugNote);
}
return ZapCircuit::setParam(param,value);
}
// Send an event
bool ZapAnalogCircuit::sendEvent(SignallingCircuitEvent::Type type, NamedList* params)
{
if (!m_canSend)
return false;
if (type == SignallingCircuitEvent::Dtmf)
return ZapCircuit::sendEvent(type,params);
TempObjectCounter cnt(plugin.objectsCounter());
XDebug(group(),DebugAll,"ZapAnalogCircuit(%u). sendEvent(%u) [%p]",code(),type,this);
switch (type) {
case SignallingCircuitEvent::OnHook:
if (!m_device.sendHook(ZapDevice::HookOn))
return false;
changeHook(true);
return true;
case SignallingCircuitEvent::OffHook:
if (!m_device.sendHook(ZapDevice::HookOff))
return false;
changeHook(false);
return true;
case SignallingCircuitEvent::Polarity:
if (!params)
return false;
return setParam("polarity",params->getValue("polarity"));
case SignallingCircuitEvent::Wink:
return m_device.sendHook(ZapDevice::HookWink);
case SignallingCircuitEvent::Flash:
return m_device.sendHook(ZapDevice::HookFlash);
case SignallingCircuitEvent::RingBegin:
return m_device.sendHook(ZapDevice::HookRing);
case SignallingCircuitEvent::RingEnd:
return m_device.sendHook(ZapDevice::HookRingOff);
case SignallingCircuitEvent::StartLine:
return m_device.sendHook(ZapDevice::HookStart);
default: ;
}
return ZapCircuit::sendEvent(type,params);
}
// Process additional events. Return false if not processed
bool ZapAnalogCircuit::processEvent(int event, char c)
{
switch (event) {
case ZapDevice::RingerOn:
return enqueueEvent(event,SignallingCircuitEvent::RingerOn);
case ZapDevice::RingerOff:
return enqueueEvent(event,SignallingCircuitEvent::RingerOff);
#ifdef DAHDI_EVENT_REMOVED
case ZapDevice::Removed:
#endif
case ZapDevice::OnHook:
changeHook(true);
return enqueueEvent(event,SignallingCircuitEvent::OnHook);
case ZapDevice::RingBegin:
m_device.setLinear(0,DebugAll);
return enqueueEvent(event,SignallingCircuitEvent::RingBegin);
case ZapDevice::OffHookRing:
if (m_device.type() == ZapDevice::FXS) {
changeHook(false);
return enqueueEvent(event,SignallingCircuitEvent::OffHook);
}
return enqueueEvent(event,SignallingCircuitEvent::RingerOff);
case ZapDevice::Polarity:
return enqueueEvent(event,SignallingCircuitEvent::Polarity);
case ZapDevice::WinkFlash:
if (m_hook)
return enqueueEvent(event,SignallingCircuitEvent::Wink);
return enqueueEvent(event,SignallingCircuitEvent::Flash);
case ZapDevice::HookComplete:
return enqueueEvent(event,SignallingCircuitEvent::LineStarted);
case ZapDevice::DialComplete:
return enqueueEvent(event,SignallingCircuitEvent::DialComplete);
case ZapDevice::PulseDigit:
return enqueueDigit(false,c);
case ZapDevice::PulseStart:
return enqueueEvent(event,SignallingCircuitEvent::PulseStart);
case ZapDevice::Timeout:
return enqueueEvent(event,SignallingCircuitEvent::Timeout);
case ZapDevice::BitsChanged:
case ZapDevice::TimerPing:
DDebug(group(),DebugStub,"ZapCircuit(%u). Unhandled event %u [%p]",
code(),event,this);
break;
default:
Debug(group(),DebugStub,"ZapCircuit(%u). Unknown event %u [%p]",
code(),event,this);
}
return false;
}
// Process incoming data
bool ZapAnalogCircuit::process()
{
if (!(m_device.valid() && SignallingCircuit::status() != SignallingCircuit::Disabled))
return false;
m_device.pollHook();
checkEvents();
s_sourceAccessMutex.lock();
RefPointer<ZapSource> src = m_source;
s_sourceAccessMutex.unlock();
if (!(src && m_device.select(10) && m_device.canRead()))
return false;
int r = m_device.recv(m_sourceBuffer.data(),m_sourceBuffer.length());
if (m_device.event())
checkEvents();
if (r > 0) {
if ((unsigned int)r != m_sourceBuffer.length())
::memset((unsigned char*)m_sourceBuffer.data() + r,m_idleValue,m_sourceBuffer.length() - r);
XDebug(group(),DebugAll,"ZapCircuit(%u). Forwarding %u bytes [%p]",
code(),m_sourceBuffer.length(),this);
src->Forward(m_sourceBuffer);
return true;
}
return false;
}
// Change hook state if different
void ZapAnalogCircuit::changeHook(bool hook)
{
if (m_hook == hook)
return;
DDebug(group(),DebugInfo,"ZapCircuit(%u). Hook state changed to %s [%p]",
code(),hook?"ON":"OFF",this);
m_hook = hook;
}
/**
* ZapSource
*/
inline void setAddr(String& addr, ZapCircuit* cic)
{
#ifdef XDEBUG
if (cic) {
if (cic->group())
addr << cic->group()->debugName() << "/";
addr << cic->code();
}
else
addr = -1;
#endif
}
ZapSource::ZapSource(ZapCircuit* circuit, const char* format)
: DataSource(format)
{
setAddr(m_address,circuit);
XDebug(&plugin,DebugAll,"ZapSource::ZapSource() cic=%s [%p]",m_address.c_str(),this);
}
ZapSource::~ZapSource()
{
XDebug(&plugin,DebugAll,"ZapSource::~ZapSource() cic=%s [%p]",m_address.c_str(),this);
}
/**
* ZapConsumer
*/
ZapConsumer::ZapConsumer(ZapCircuit* circuit, const char* format)
: DataConsumer(format),
m_circuit(circuit)
{
setAddr(m_address,circuit);
XDebug(&plugin,DebugAll,"ZapConsumer::ZapConsumer() cic=%s [%p]",m_address.c_str(),this);
}
ZapConsumer::~ZapConsumer()
{
XDebug(&plugin,DebugAll,"ZapConsumer::~ZapConsumer() cic=%s [%p]",m_address.c_str(),this);
}
/**
* ZapModule
*/
String ZapModule::s_statusCmd[StatusCmdCount] = {"spans","channels","all"};
ZapModule::ZapModule()
: Module("zaptel","misc",true),
m_init(false),
m_count(0),
m_active(0)
{
Output("Loaded module Zaptel");
m_prefix << name() << "/";
m_statusCmd << "status " << name();
}
ZapModule::~ZapModule()
{
Output("Unloading module Zaptel");
}
void ZapModule::append(ZapDevice* dev)
{
if (!dev)
return;
Lock lock(this);
m_devices.append(dev)->setDelete(false);
m_count = m_devices.count();
}
void ZapModule::remove(ZapDevice* dev)
{
if (!dev)
return;
Lock lock(this);
m_devices.remove(dev,false);
m_count = m_devices.count();
}
void ZapModule::initialize()
{
Output("Initializing module Zaptel");
Configuration cfg(Engine::configFile("zapcard"));
cfg.load();
NamedList dummy("");
NamedList* general = cfg.getSection("general");
if (!general)
general = &dummy;
ZapDevice dev(0,false,true);
if (!dev.valid())
Debug(this,DebugNote,"Failed to open zaptel device: driver might not be loaded");
int dtmfLen = 0;
if (!m_init) {
m_init = true;
setup();
installRelay(Command);
// Set DTMF/MF length
if (dev.valid() && dev.dialParams(false,dtmfLen,dtmfLen)) {
dtmfLen = general->getIntValue("tonelength",dtmfLen);
dev.dialParams(true,dtmfLen,dtmfLen);
}
}
if (dev.valid() && debugAt(DebugAll)) {
NamedList nl("");
dev.getVersion(nl);
dtmfLen = 0;
dev.dialParams(false,dtmfLen,dtmfLen);
Debug(this,DebugAll,"version=%s echocanceller=%s tonelength=%d samples",
nl.getValue("version"),nl.getValue("echocanceller"),dtmfLen);
}
}
// Find a device by its Zaptel channel
ZapDevice* ZapModule::findZaptelChan(int chan)
{
Lock lock(this);
for (ObjList* o = m_devices.skipNull(); o; o = o->skipNext()) {
ZapDevice* dev = static_cast<ZapDevice*>(o->get());
if ((int)dev->channel() == chan)
return dev;
}
return 0;
}
bool ZapModule::received(Message& msg, int id)
{
if (id == Status) {
String dest = msg.getValue("module");
// Module status
if (!dest || dest == name()) {
Module::msgStatus(msg);
return false;
}
Lock lock(this);
// Device status
if (dest.startSkip(prefix(),false)) {
ZapDevice* dev = findZaptelChan((unsigned int)dest.toInteger());
if (!dev)
return false;
msg.retValue().clear();
msg.retValue() << "name=" << dev->zapName();
msg.retValue() << ",module=" << name();
msg.retValue() << ",type=" << lookup(dev->type(),s_types);
if (dev->span() != -1) {
msg.retValue() << ",zapteltype=" << lookup(dev->zapsig(),s_zaptelSig);
msg.retValue() << ",span=" << dev->span();
msg.retValue() << ",spanpos=" << dev->spanPos();
msg.retValue() << ",alarms=" << dev->alarmsText();
}
else
msg.retValue() << ",zapteltype=not-configured,span=,spanpos=,alarms=";
msg.retValue() << ",address=" << dev->address();
msg.retValue() << "\r\n";
return true;
}
// Additional commands
if (dest.startSkip(name(),false)) {
dest.trimBlanks();
int cmd = 0;
for (; cmd < StatusCmdCount; cmd++)
if (s_statusCmd[cmd] == dest)
break;
if (cmd == ZapSpans) {
ZapDevice* ctl = new ZapDevice(0);
NamedList ver("");
ctl->getVersion(ver);
msg.retValue().clear();
msg.retValue() << "module=" << name() << "," << s_spanParamsHdr;
msg.retValue() << ";version=" << ver.getValue("version");
msg.retValue() << ",echocanceller=" << ver.getValue("echocanceller");
for (int span = 1; true; span++) {
NamedList p("");
int total = 0;
bool ok = ctl->getSpanInfo(span,p,&total);
if (span == 1)
msg.retValue() << ",count=" << total;
if (!ok)
break;
// format=Channels|Total|Alarms|Name|Description
msg.retValue() << ";" << span << "=" << p.getValue("configured-chans");
msg.retValue() << "|" << p.getValue("total-chans");
msg.retValue() << "|" << p.getValue("alarmstext");
msg.retValue() << "|" << p.getValue("name");
msg.retValue() << "|" << p.getValue("desc");
}
TelEngine::destruct(ctl);
}
else if (cmd == ZapChannels || cmd == ZapChannelsAll) {
ZapDevice* ctl = new ZapDevice(0);
String s;
unsigned int chan = 0;
for (int span = 1; ctl->valid(); span++) {
// Check span
NamedList p("");
if (!ctl->getSpanInfo(span,p))
break;
// Get info
int chans = p.getIntValue("total-chans");
for (int i = 0; i < chans; i++) {
chan++;
// Get device
// Create or reset debuger to avoid unwanted debug output to console
bool created = false;
bool opened = false;
ZapDevice* dev = findZaptelChan(chan);
if (!dev) {
dev = new ZapDevice(chan);
created = true;
}
else if (dev->owner())
dev->owner()->debugEnabled(false);
if (!dev->valid()) {
dev->open(0,0);
opened = true;
}
bool show = (dev->span() == span) || (cmd == ZapChannelsAll);
if (show) {
// format=Type|ZaptelType|Span|SpanPos|Alarms|Address
s << ";" << dev->channel() << "=" << lookup(dev->type(),s_types);
if (dev->span() == span) {
s << "|" << lookup(dev->zapsig(),s_zaptelSig);
s << "|" << dev->span();
s << "|" << dev->spanPos();
s << "|" << dev->alarmsText();
}
else
s << "|not-configured|||";
s << "|" << dev->address();
}
// Cleanup if we opened/created the device
if (created) {
TelEngine::destruct(dev);
continue;
}
if (opened)
dev->close();
if (dev->owner())
dev->owner()->debugEnabled(true);
}
}
TelEngine::destruct(ctl);
msg.retValue().clear();
msg.retValue() << "module=" << name() << "," << s_chanParamsHdr;
msg.retValue() << ";used=" << m_count << ",total=" << chan;
msg.retValue() << s;
}
else
return false;
msg.retValue() << "\r\n";
return true;
}
return false;
}
return Module::received(msg,id);
}
void ZapModule::statusModule(String& str)
{
Module::statusModule(str);
str.append(s_chanParamsHdr,",");
}
void ZapModule::statusParams(String& str)
{
Module::statusParams(str);
str.append("active=",",") << m_active;
str << ",count=" << m_count;
}
void ZapModule::statusDetail(String& str)
{
// format=Type|ZaptelType|Span|SpanPos|Alarms|Address
for (ObjList* o = m_devices.skipNull(); o; o = o->skipNext()) {
ZapDevice* dev = static_cast<ZapDevice*>(o->get());
str.append(String(dev->channel()),";") << "=" << lookup(dev->type(),s_types);
str << "|" << lookup(dev->zapsig(),s_zaptelSig);
str << "|" << dev->span();
str << "|" << dev->spanPos();
str << "|" << dev->alarmsText();
str << "|" << dev->address();
}
}
bool ZapModule::commandComplete(Message& msg, const String& partLine,
const String& partWord)
{
bool ok = Module::commandComplete(msg,partLine,partWord);
if (!partLine.startsWith("status"))
return ok;
Lock lock(this);
if (name().startsWith(partWord)) {
if (m_devices.skipNull())
msg.retValue().append(prefix(),"\t");
return ok;
}
if (partLine == m_statusCmd) {
for (unsigned int i = 0; i < StatusCmdCount; i++)
itemComplete(msg.retValue(),s_statusCmd[i],partWord);
return true;
}
if (partWord.startsWith(prefix())) {
for (ObjList* o = m_devices.skipNull(); o; o = o->skipNext())
itemComplete(msg.retValue(),static_cast<ZapDevice*>(o->get())->zapName(),partWord);
return true;
}
return ok;
}
}; // anonymous namespace
#endif /* _WINDOWS */
/* vi: set ts=8 sw=4 sts=4 noet: */