/** * 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 #include #ifdef _WINDOWS #error This module is not for Windows #else extern "C" { #ifdef HAVE_ZAP #ifdef NEW_ZAPTEL_LOCATION #define __LINUX__ #include #else #include #endif #else #include #endif }; #include #include #include #include #include #include #include #include #define PATH_SEP "/" 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); static int findSpanOffset(const String& 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(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,¶ms,"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); } int ZapInterface::findSpanOffset(const String& name) { int offset = -1; String path = "/sys/bus/dahdi_spans/devices"; DIR *dir = ::opendir(path); if (!dir) return 0; struct dirent* entry; while ((entry = ::readdir(dir)) != 0) { if (entry->d_name[0] == '.') continue; struct stat stat_buf; if (::stat(path + PATH_SEP + entry->d_name,&stat_buf)) continue; String name_filename = path + PATH_SEP + entry->d_name + PATH_SEP + "name"; int name_fd = ::open(name_filename.c_str(), O_RDONLY); if (name_fd >= 0) { char buf[128]; int tmp = ::read(name_fd,buf,sizeof(buf)); if (tmp > 0) { // last character of the name is a \n, let's skip that. buf[tmp - 1] = 0x00; if (String(buf) == name) { String basechan_filename = path + PATH_SEP + entry->d_name + PATH_SEP + "basechan"; int basechan_fd = ::open(basechan_filename.c_str(), O_RDONLY); if (basechan_fd >= 0) { char buf[128]; int tmp = ::read(basechan_fd,buf,sizeof(buf)); if (tmp > 0) { // last character of the basechan is a \n, let's skip that. buf[tmp - 1] = 0x00; offset = String(buf).toInteger() - 1; } ::close(basechan_fd); } } } ::close(name_fd); } } ::closedir(dir); return offset; } // 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 sSpan = config->getValue("span"); int span_offset = -1; unsigned int offset = 0; String sOffset; if (sSpan) { span_offset = findSpanOffset(sSpan); if (span_offset >= 0) { Debug(&plugin,DebugWarn,"found span offset: %d for span %s", findSpanOffset(sSpan), sSpan.c_str()); offset = span_offset; } else { Debug(&plugin,DebugWarn,"Couldn't determine span offset %s from DAHDI", sSpan.c_str()); return 0; } } else { sOffset = config->getValue("offset"); 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 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 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 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(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(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(o->get())->zapName(),partWord); return true; } return ok; } }; // anonymous namespace #endif /* _WINDOWS */ /* vi: set ts=8 sw=4 sts=4 noet: */