commit 4f7e2b2a4e65c3b64d0295cdeed18ce7c15ea800 Author: gernot Date: Wed Feb 19 08:19:51 2003 +0000 Initial revision git-svn-id: https://svn.ibp.de/svn/capisuite/trunk/capisuite@3 4ebea2bb-67d4-0310-8558-a5799e421b66 diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..ab4d6ad --- /dev/null +++ b/.cvsignore @@ -0,0 +1,11 @@ +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +stamp-h1 +config.* +configure +configure.scan +autoscan.log +capisuite.cron +rc.capisuite diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..223932c --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Gernot Hillier diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..fc217f2 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,25 @@ +spooldir = @localstatedir@/spool/capisuite +pkgsysconfdir = @sysconfdir@/capisuite +docdir = @docdir@ +doc_DATA = COPYING NEWS README +EXTRA_DIST = rc.capisuite.in capisuite.spec capisuite.cronin + +SUBDIRS = src scripts docs + +all: capisuite.cron rc.capisuite + +capisuite.cron: capisuite.cronin + rm -f $@ + sed -e 's,@pkgsysconfdir\@,$(pkgsysconfdir),g' \ + -e 's,@spooldir\@,$(spooldir),g' $< >$@ + chmod a+x $@ + +rc.capisuite: rc.capisuite.in + rm -f $@ + sed -e 's,@pkgsysconfdir\@,$(pkgsysconfdir),g' \ + -e 's,@bindir\@,$(bindir),g' $< >$@ + chmod a+x $@ + +clean-local: + rm -f rc.capisuite capisuite.cron + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..8ef2520 --- /dev/null +++ b/NEWS @@ -0,0 +1,103 @@ +0.01 (tag CAPISUITE_001): +========================= + + * changed name from CapiCom to CapiSuite (name conflict with MS crypto API) + * added doxygen-created documentation for classes and python exported functions + * get_DTMF() was renamed to read_DTMF() and can wait for DTMF now + * connect_telephony() renamed to connect_voice() + +0.02 (tag CAPISUITE_002): +========================= + + * many bug fixes as usual (SEGV, ...) + * service constants SERVICE_VOICE, SERVICE_FAXG3 and SERVICE_OTHER + available in python now, no need to use CIP values any more + * audio_send and audio_receive return length in seconds now + * added support for idle script which can initiate outgoing calls + +0.03 (tag CAPISUITE_003): +========================= + + * improvement in idle script handling, own class for it (IdleScript) + * new classes for Python script handling (PythonScript) and derived classes + (IncomingScript & IdleScript) + * new python functions call_voice and call_faxG3 to initiate outgoing calls + * changed python exception handling to allow multiple calls in one script to be + handled correctly + * python functions disconnect() and reject() wait for complete disconnection and + return the disconnect cause now + * assure nice disconnection in any error case (hopefully) + * when error occured in script, physical connection is finished immediately leading + to an error visible at the sending side (e.g. when using the fax protocol) + * cleaned up python reference counting and threads, no known memory leaks any more + * many changes to support outgoing calls (new module, many small changes) + * Connection objects will be destroyed by application level now so dangling pointers + are avoided + * exception handling generally improved + +0.1 (tag CAPISUITE_01): +======================= + * "make install" and "make dist" work now, use config.h + * added main docu page for doxygen + * added capisuitefax-script (command line tool for sending faxes) + * added support for sending faxes in idle.py + * added support for "capisuite.conf" (global configuration file) + * capisuite can write its output to logfiles now + * faxsend module added, new python function fax_send() + * idle script will be disabled after 10 subsequent errors + * B3 disconnect cause now returned by disconnect() python function + +0.2 (tag CAPISUITE_02): +======================= + * log improvements: log-level configurable (see capisuite.conf), appending log-file instead of re-creating + * configure allows to set docdir with --with-docdir + * CapiSuite can be finished using Ctrl-C and SIGTERM nicely + * very limited support for reload (kill -1) - only re-activates de-actived idle script yet, + no reload of configuration + * all configuration for the scripts put in own config file + * support for various new configuration options, multi-user-ready scripts (different user dirs in spool_dir/users) + * audio_receive does truncate recorded silence away + * remote inquiry supports recording of own announcement + * commandline option "-d" runs CapiSuite as daemon + * new python commands: capisuite.log and capisuite.error let scripts write messages to the CapiSuite log + and error log + +0.2.1 (tag CAPISUITE_021): +========================== + * many document improvements (new DocBook manual) + +0.3 (tag CAPISUITE_03): +======================= + * split up script configuration in two files (anwering machine, fax), + some new features configurable now (e.g. actions) + +0.3.1 (tag CAPISUITE_031): +========================== + * dist: included spec and init file in CVS and dist + * scripts: use different sendqueues for each user + * core: fixed some bugs: + - capisuite.error() didn't work, + - logging in outgoing connections didn't work + - callingParty wasn't set correctly + * scripts: answering machine switches to fax when incoming service indicator says fax + * scripts: sayNumber can now handle all number from 0 to 99, so all dates and times are + now said nearly correctly for the remote inquiry + * scripts: fixed a typo in incoming.py + * docs: added ISDN/CAPI error codes to manual + +0.3.2 (tag CAPISUITE_032): +========================== + * core: finally got rid of the CommonC++ library: + - threading implemented using native pthread_* calls + - rewritten CapiSuite::parseConfigFile() to use STL string routines + - changed Connection class to use pthread_mutex_* + * scripts: fixed bug which lead to hanging processes of externally started + progs like sendmail + * scripts: minor fixes + +0.4 (tag CAPISUITE_04): +======================= + * added cron script for cleaning up spool dirs + * fixed bug in rc.capisuite (was also started when not configured) + * scripts: remote inquiry supports new and old messages now + * scripts: capisuitefax can show sendqueue and delete jobs now diff --git a/README b/README new file mode 100644 index 0000000..a4ad7a0 --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +CapiSuite +========= + +For the documentation see the created HTML documents +situated in docs/manual/index.html or in the installed version see +PREFIX/share/doc/capisuite/manual/index.html diff --git a/TODO b/TODO new file mode 100644 index 0000000..8c1a14d --- /dev/null +++ b/TODO @@ -0,0 +1,24 @@ +CRITICAL: +- finish manual.docbook, provide simple examples +- somewhere there's a bad thing (tm) which holds the python global lock + despite it mustn't (try incoming + outgoing call at the same time, + idlescript will stop working sometime...) + +IMPORTANT: +- fax headline + +NICE: +- ?valgrind-clean the used libs and Python? +- support more than one send_controller +- don't use 34xx codes, define constants instead and print meaningful messages +- include email account check in idly.py +- add docbook -> html to Makefile.am +- any solution for sending fax when having no fax config? + +FUTURE PLANS: +- setuid away from root (problem: chown of recorded file to user) +- log syntax errors in scripts, too + - PyRun_SimpleFile must be replaced by PyRun_File for this IMHO +- test-implement the whole application part in Python +- rewrite capisuitefax and idle.py to use named socket communication + diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..576cffc --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,98 @@ +# +# Autoconf macros for configuring the build of Python extension modules +# +# $Header: /root/cvs2svn/capisuite/capisuite/acinclude.m4,v 1.1 2003/02/19 08:19:52 gernot Exp $ +# +# taken out of Postgres CVS by Gernot Hillier +# + +# CS_SET_DOCDIR +# ------------- +# Set the name of the docdir to the given value. This is not nice, but I +# found no other name to do it than with AC_ARG_WITH. Please tell me if +# you have better ideas... +AC_DEFUN(CS_SET_DOCDIR, +[AC_ARG_WITH(docdir, + AC_HELP_STRING([--with-docdir=DOCDIR], + [use DOCDIR to install documentation to (default is PREFIX/share/doc/capisuite)]), + docdir=$withval, docdir=$datadir/doc/capisuite) +AC_SUBST(docdir) +]) + +# PGAC_CHECK_PYTHON_DIRS +# ----------------------- +# Determine the name of various directory of a given Python installation. +AC_DEFUN([PGAC_CHECK_PYTHON_DIRS], +[AC_REQUIRE([AM_PATH_PYTHON]) +AC_MSG_CHECKING([Python installation directories]) +python_version=`${PYTHON} -c "import sys; print sys.version[[:3]]"` +python_prefix=`${PYTHON} -c "import sys; print sys.prefix"` +python_execprefix=`${PYTHON} -c "import sys; print sys.exec_prefix"` +python_libdir=`${PYTHON} -c "from distutils import sysconfig; print sysconfig.get_python_lib(1,1)"` +python_configdir="${python_libdir}/config" +python_moduledir="${python_libdir}/site-packages" +python_moduleexecdir="${python_libdir}/site-packages" +python_includespec="-I${python_prefix}/include/python${python_version}" +python_linkforshared=`${PYTHON} -c "import distutils.sysconfig; print distutils.sysconfig.get_config_var('LINKFORSHARED')"` +if test "$python_prefix" != "$python_execprefix"; then + python_includespec="-I${python_execprefix}/include/python${python_version} $python_includespec" +fi + +AC_SUBST(python_version)[]dnl +AC_SUBST(python_prefix)[]dnl +AC_SUBST(python_execprefix)[]dnl +AC_SUBST(python_configdir)[]dnl +AC_SUBST(python_moduledir)[]dnl +AC_SUBST(python_moduleexecdir)[]dnl +AC_SUBST(python_includespec)[]dnl +AC_SUBST(python_linkforshared)[]dnl +# This should be enough of a message. +if test "$python_prefix" != "$python_execprefix"; then + AC_MSG_RESULT([$python_libdir and $python_execprefix]) +else + AC_MSG_RESULT([$python_libdir]) +fi +])# _PGAC_CHECK_PYTHON_DIRS + + +# PGAC_CHECK_PYTHON_MODULE_SETUP +# ------------------------------ +# Finds things required to build a Python extension module. +# This used to do more, that's why it's separate. +# +# It would be nice if we could check whether the current setup allows +# the build of the shared module. Future project. +AC_DEFUN([PGAC_CHECK_PYTHON_MODULE_SETUP], +[ + AC_REQUIRE([PGAC_CHECK_PYTHON_DIRS]) +])# PGAC_CHECK_PYTHON_MODULE_SETUP + + +# PGAC_CHECK_PYTHON_EMBED_SETUP +# ----------------------------- +# Courtesy of the INN 2.3.1 package... +AC_DEFUN([PGAC_CHECK_PYTHON_EMBED_SETUP], +[AC_REQUIRE([PGAC_CHECK_PYTHON_DIRS]) +AC_MSG_CHECKING([how to link an embedded Python application]) + +if test ! -f "$python_configdir/Makefile"; then + AC_MSG_RESULT(no) + AC_MSG_ERROR([Python Makefile not found]) +fi + +_python_libs=`grep '^LIBS=' $python_configdir/Makefile | sed 's/^.*=//'` +_python_libc=`grep '^LIBC=' $python_configdir/Makefile | sed 's/^.*=//'` +_python_libm=`grep '^LIBM=' $python_configdir/Makefile | sed 's/^.*=//'` +_python_liblocalmod=`grep '^LOCALMODLIBS=' $python_configdir/Makefile | sed 's/^.*=//'` +_python_libbasemod=`grep '^BASEMODLIBS=' $python_configdir/Makefile | sed 's/^.*=//'` + +pgac_tab=" " # tab character +python_libspec=`echo X"$_python_libs $_python_libc $_python_libm -lpython$python_version $_python_liblocalmod $_python_libbasemod" | sed -e 's/^X//' -e "s/[[ $pgac_tab]][[ $pgac_tab]]*/ /g"` +LIBS="$LIBS $python_libspec" +LDFLAGS="$LDFLAGS -L$python_configdir $python_linkforshared" +AC_MSG_RESULT([${python_libspec}]) + +AC_SUBST(LIBS)[]dnl +AC_SUBST(LDFLAGS) +])# PGAC_CHECK_PYTHON_EMBED_SETUP + diff --git a/capisuite.cronin b/capisuite.cronin new file mode 100755 index 0000000..e9f3013 --- /dev/null +++ b/capisuite.cronin @@ -0,0 +1,38 @@ +#!/bin/sh +# +# capisuite. CapiSuite cleanup script, should be run regularly +# by cron. It's only useful for the default scripts provided +# with CapiSuite. +# +# It will read a central configuration file placed in +# /etc/capisuite/cronjob.conf where you must define a variable +# called MAX_DAYS which defines how many days a received or sent +# file may stay in the spool dirs. +# +# Author: Gernot Hillier +# + +# +# paranoia settings +# +umask 022 + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +export PATH + +# do nothing if there is no global config +test -r @pkgsysconfdir@/cronjob.conf || exit + +for i in @spooldir@/users/*/received @spooldir@/done @spooldir@/failed; do + # reset defaults + test -r @pkgsysconfdir@/cronjob.conf && . @pkgsysconfdir@/cronjob.conf + # user can overwrite default values + test -r $i/cronjob.conf && . $i/cronjob.conf + + test "$MAX_DAYS" -gt 0 2> /dev/null || continue + find $i/. -name "*fax-[0-9]*.*" ! -type d ! -type s -atime +$MAX_DAYS -exec rm {} \; + find $i/. -name "*voice-[0-9]*.*" ! -type d ! -type s -atime +$MAX_DAYS -exec rm {} \; +done; + +exit 0 + diff --git a/capisuite.spec b/capisuite.spec new file mode 100644 index 0000000..870eef3 --- /dev/null +++ b/capisuite.spec @@ -0,0 +1,72 @@ +# +# spec file for package capisuite +# +# Author: Gernot Hillier +# +# This spec file was developed for the use with SuSE Linux. But it +# should also work for any other distribution with slight changes. +# If you created your own RPM, please tell me and I'll happily include +# the spec or a link to your RPM on the homepage. + +# neededforbuild capi4linux gcc-c++ libstdc++-devel libxml2-devel python python-devel + +Name: capisuite +License: GPL +Group: Applications/Communications +Autoreqprov: on +Version: 0.3 +Release: 0 +Requires: sfftobmp sox +Summary: capisuite +Source0: capisuite-%{version}.tar.gz +Source1: rc.capisuite +Url: http://www.capisuite.de +BuildRoot: %{_tmppath}/%{name}-%{version}-build +PreReq: %insserv_prereq + +%description +CapiSuite is a ISDN telecommunication suite providing easy to use +telecommunication functions which can be controlled from Python scripts. + +It uses a CAPI-compatible driver for accessing the ISDN-hardware, so you'll +need a Eicon or AVM card with the according driver. + +CapiSuite is distributed with two example scripts for call incoming handling +and fax sending. See /usr/share/capisuite/scripts and +/usr/share/doc/packages/capisuite for further information. + +Authors: +-------- + Gernot Hillier + +%prep +%setup +./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --with-docdir=/usr/share/doc/packages/capisuite + +%build +make + +%install +make DESTDIR=$RPM_BUILD_ROOT install +mkdir -p $RPM_BUILD_ROOT/etc/init.d +mkdir -p $RPM_BUILD_ROOT/usr/sbin +install -g root -m 755 -o root %{SOURCE1} $RPM_BUILD_ROOT/etc/init.d/capisuite +ln -sf ../../etc/init.d/capisuite $RPM_BUILD_ROOT/usr/sbin/rccapisuite + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%config /etc/capisuite/capisuite.conf +%config /etc/capisuite/fax.conf +%config /etc/capisuite/answering_machine.conf +/usr/bin/capisuite +/usr/bin/capisuitefax +%doc /usr/share/doc/packages/capisuite +/usr/share/capisuite +/usr/lib/capisuite +/var/spool/capisuite +/usr/%{_lib}/python2.2/site-packages/cs_helpers.py +/etc/init.d/capisuite +/usr/sbin/rccapisuite + diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..cc674d6 --- /dev/null +++ b/configure.in @@ -0,0 +1,26 @@ +AC_INIT(src/main.cpp) +AM_INIT_AUTOMAKE(capisuite,0.4) +AM_CONFIG_HEADER(config.h) + +AC_LANG_CPLUSPLUS +AC_PROG_CC +AC_PROG_CXX +AC_PROG_INSTALL +AC_PROG_RANLIB +AC_PROG_MAKE_SET +AC_PATH_PROG(doxygen,doxygen) +dnl suggested by autoscan: + +AC_CHECK_FUNCS([gettimeofday]) +AC_CHECK_HEADERS([sys/time.h]) +AC_HEADER_TIME + +CS_SET_DOCDIR + +AC_CHECK_LIB(capi20,capi20_register,,AC_MSG_ERROR(libcapi20 not found)) +AC_CHECK_LIB(pthread,pthread_create,,AC_MSG_ERROR(libpthread not found)) +AM_PATH_PYTHON(2.2) +PGAC_CHECK_PYTHON_EMBED_SETUP +CPPFLAGS='-DLOCALSTATEDIR=\"$(localstatedir)\" -DPKGDATADIR=\"$(pkgdatadir)\" -DPKGSYSCONFDIR=\"$(sysconfdir)/capisuite\" -DPKGLIBDIR=\"$(pkglibdir)\" $(python_includespec)' + +AC_OUTPUT(Makefile src/Makefile src/backend/Makefile src/modules/Makefile src/application/Makefile scripts/Makefile scripts/waves/Makefile docs/Makefile) diff --git a/docs/.cvsignore b/docs/.cvsignore new file mode 100644 index 0000000..9e3ec88 --- /dev/null +++ b/docs/.cvsignore @@ -0,0 +1,9 @@ +reference +reference/* +manual +manual/* +susebuch +susebuch/* +Doxyfile +Makefile +Makefile.in diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 0000000..c64b157 --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,969 @@ +# Doxyfile 1.2.17 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = CapiSuite + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @version@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en +# (Japanese with english messages), Korean, Norwegian, Polish, Portuguese, +# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explict @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consist of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @capisuite_sources@ @srcdir@ + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl + +FILE_PATTERNS = *.cpp *.h *.doxy + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse. + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = reference + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output dir. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non empty doxygen will try to run +# the html help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the Html help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript and frames is required (for instance Mozilla, Netscape 4.0+, +# or Internet explorer 4.0+). Note that for large projects the tree generation +# can take a very long time. In such cases it is better to disable this feature. +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_XML = NO + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse the +# parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in Html, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superceded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yield more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermedate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = search.cgi + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = /usr/local/bin/ + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/docs/Makefile.am b/docs/Makefile.am new file mode 100644 index 0000000..822f7fd --- /dev/null +++ b/docs/Makefile.am @@ -0,0 +1,41 @@ +docdir = @docdir@ +EXTRA_DIST = Doxyfile.in mainpage.doxy manual.docbook manual.README + +all: docs + +docs: Doxyfile + if test "x$(doxygen)" != "x"; then \ + $(doxygen) Doxyfile ;\ + fi + +dist-hook: Doxyfile + $(doxygen) Doxyfile + mkdir $(distdir)/reference + cp $(srcdir)/reference/* $(distdir)/reference/ + mkdir $(distdir)/manual + cp $(srcdir)/manual/* $(distdir)/manual/ + +Doxyfile: Doxyfile.in + sed -e 's,@version\@,$(VERSION),g' \ + -e 's,@capisuite_sources\@,$(top_srcdir)/src,g' \ + -e 's,@srcdir\@,$(srcdir),g' $< >$@ + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(docdir)/reference ; \ + (cd reference; for i in *; do \ + $(INSTALL_DATA) $$i $(DESTDIR)$(docdir)/reference/$$i ;\ + done;) + $(mkinstalldirs) $(DESTDIR)$(docdir)/manual ; \ + (cd manual; for i in *; do \ + $(INSTALL_DATA) $$i $(DESTDIR)$(docdir)/manual/$$i ;\ + done;) + +uninstall-local: + rm -rf $(DESTDIR)$(docdir) + +clean-local: + rm -f Doxyfile + +maintainer-clean-local: + rm -rf reference manual; + diff --git a/docs/mainpage.doxy b/docs/mainpage.doxy new file mode 100644 index 0000000..7aa47c1 --- /dev/null +++ b/docs/mainpage.doxy @@ -0,0 +1,11 @@ +/** @mainpage %CapiSuite Documentation + +This is the reference manual describing the internal structures of %CapiSuite. If you're interested in developing, +you're right here. +@htmlonly +If you "just" want to use it, please refer to ../manual/index.html. +@endhtmlonly +Thx! + +@author Gernot Hillier +*/ diff --git a/docs/manual.README b/docs/manual.README new file mode 100644 index 0000000..4ad16a8 --- /dev/null +++ b/docs/manual.README @@ -0,0 +1,22 @@ +The CapiSuite manual is written in the DocBook format. Only +read on if you want to change it. You'll need some knowledge +of DocBook and the appropriate tools for that. + +Otherwise just use the prepared HTML documentation which +will be installed in your doc_dir when you do "make install". +Have a look into /usr/local/share/doc/capisuite/manual/ +after the installation and please READ it. :-) + +------ Only experts read on here, please ------ + +You can create HTML pages by using tools like xsltproc and +the DocBook stylesheets of Norman Walsh. + +An example of how to call xsltproc: + +xsltproc -o manual/ /usr/share/sgml/docbook/docbook-xsl-stylesheets/xhtml/chunk.xsl manual.docbook + +To validate the document, use e.g. xmllint: + +xmllint --noout --valid manual.docbook + diff --git a/docs/manual.docbook b/docs/manual.docbook new file mode 100644 index 0000000..d8a7044 --- /dev/null +++ b/docs/manual.docbook @@ -0,0 +1,1371 @@ + +CapiSuite"> +]> + +CapiSuite 0.4 + + + + + + GernotHillier +
gernot@hillier.de
+
+
+ + +Introduction + + Welcome to &cs; + Welcome to &cs;, a python-scriptable ISDN telecommunication suite. It uses the new CAPI interface for accessing + your ISDN-hardware - so you'll need a card for which a CAPI compatible + driver is available. Currently these are all cards manufactured by AVM + and some Eicon cards. + + + + This manual should help you to be able to use &cs; as quick as possible. + As I hate reading long documentation just as much as you do, let's jump + right in. + + + What the heck is "&cs;"?! + &cs; tries to give the user the ability to code his own ISDN applications + without having to fiddle around with all the dirty programming details like callback + functions, data buffers, protocol settings and so on. + + I took a scripting language which is (in my opinion) very easy to understand, + to use and to learn - especially for beginners: Python. I extended it with some + functions providing the basic ISDN "building blocks" for the users application. + Behind these functions the heart of &cs; implements all the dirty details + a user isn't interested in. My goal was to make script-coding as + simple as possible but to also give you the flexibility to realize what you + want. + + To give you an impression, coding a simple answering machine is as easy as: + + + def callIncoming (call, service, call_from, call_to): # defines a function executed at incoming calls + connect_voice (call, 10) # answer the call after 10 seconds + audio_send (call, "my-announcement.la") # wave file containing the announcement + audio_send (call, "beep.la") # wave file containing BEEP + audio_receive (call, "call.la", 10) # record max. 10 seconds into call.la + + + + Of course some details are missing like creating a unique filename or storing + the additional information (called and calling party numbers, time, ...) - but I assume + you got my idea. + + And - don't be afraid - if you just want to have a normal answering machine or send and + receive some fax documents, you can use the default scripts distributed with &cs;. + They give you already some nice features - e.g. the answering machine is multi-user + ready, supports automatic fax detection and remote inquiry functions. You'll only + need to tell &cs; some details like your own number, record an own announcement + and that's it. + + So &cs; is already equipped for your daily telecommunication needs - but if you don't + like to do the things the way I do - just change it or completely do it on your + own (and if you write nice scripts or have changes to my default scripts, I would + love to get and perhaps make them available for all users if you don't mind). + + + Structure of the manual + This manual is split into three big parts. + + The first part () explains how you install &cs;, what + you can do with the default scripts you have after installing it and how + to configure them. No line of code will be presented here. If you just want + to use the default scripts that should be all the reading you need. + + The second part () will tell you how to write your own scripts. It will + give you a very, very small introduction into Python and a complete reference + of the commands &cs; adds to it. Last, an overview over the default + scripts is given which will tell you how they work so you can easily take them + as starting points and/or examples for your own application. + + The last part () is intended for programmers who want to + help in developing the &cs; core. It provides a rough overview of the internal + structure of the program. All the rest is documented in doxygen pages, which give you + a thorough description for each single class, method and attribute... + + There are also some additional parts containing "what I also wanted to mention": + + Look at @ref knownbugs to find a list of known problems and what you should do + if you think you found a bug. + + As &cs; started as a diploma thesis, I want to thank all who helped me so far in this section + @ref blubber. + + When you want to code your own scripts or want to help in developing the &cs; core, + you'll soon stumble upon some special ISDN and CAPI error codes, which are explained in + . + + + Hope I managed to whet your appetite - so let's now really start over to get you ready to + use it. + + + +Getting Started + Requirements and installation of &cs; + + Requirements + Hardware and drivers + As &cs; uses the CAPI (Common ISDN Application Programming Interface) + for accessing your ISDN-hardware, you'll need a card for which a CAPI compatible + driver is available. + + Currently these are all cards manufactured by AVM and some Eicon cards. + If you have one of the passive cards of AVM, you'll + have to download and install their CAPI drivers - you can't use the + drivers of the ISDN4Linux project included in the default Linux kernel yet. + There are also some distributions (e.g. current versions of SuSE) which + include the Capi4Linux drivers from AVM already - you'll only have to + activate them (use YaST2 in SuSE Linux). If you own an active card of AVM + (e.g. the B1, C2 or C4), then you'll have everything you need already installed. + + No, there's currently no way to get it working with the old ISDN4Linux interface. + Perhaps there never will be one as the ISDN4Linux project will provide a CAPI + compatible interface some near day in the future - so all supported ISDN cards + will work with &cs; then. If you nevertheless want to write a backend for + ISDN4Linux, just contact me - I'll be more than happy to help you with that. + + &cs; has mainly been tested on AVM ISDN cards, esp. the Fritz!PCI, the Fritz!USB and + the B1 on the i386 platform but there should be no problem with other + CAPI-compatible drivers for other cards or on other platforms. Nevertheless, + some features aren't mandatory for all CAPI-compatible cards, so perhaps + you may not be able to fax or to switch from voice to fax mode with all + cards. + + Software + &cs; depends on some packages which must be installed before &cs; can be used. + + I will list them here with a short information why this packages are needed and where to + find further information on how to install them. It may be always a good idea to check the + installation tool of your favourite distribution first and see if they're included with it before + trying to download and install them from the net. Don't be afraid, because there are so many - + most of them will be included in nearly every distribution and perhaps are already installed on your system. + + + + Python + &cs; uses an embedded Python interpreter to interpret the given scripts - + so you'll need an installed and working version of Python. This should be included + in mostly every up-to-date Linux distribution. For further infos on Python, a nice + tutorial and much more, please go to + + + + sox + This is the swiss-knife for converting audio formats. It's not required + by the &cs; core, but will be very helpful if you want to hear or record the + voice files used for calls on your machine. It's also required if you want to + use the default scripts of &cs;. I'll bet this is included in your distribution + and most likely already installed on your system. Just try to start sox + to get sure. You'll find more details on + + + + sfftobmp + &cs; will save fax files in the CAPI specific format Structured Fax File (SFF). + sfftobmp is a small but useful converter to convert this files to more + common formats like JPEG, TIFF or BMP. Get it on . + It's again not needed by the &cs; core, but for the default scripts. + + + + sffview + This tool is a simple but useful SFF viewer. It's not needed by any + &cs; component, but very useful if you just want to see a fax file without + the need to convert it first. You can get it from . + + + + tiff2ps + A small utility to convert TIFF files to the Postscript format. It's needed by + the default script to convert faxes to PDF files (SFF->TIFF->PS->PDF :-} ). + Surely already on your system. Details on + + + + ps2pdf + Again a small utility for the SFF->PDF chain - this time for the + conversion of Adobe PostScript to Adobe PDF. It's part of Ghostscript, so + you have it definitely already. () + + + + current Ghostscript with cfax patch + Current Ghostscript versions will include a device to create the above mentioned + SFF files. If you have an older version, you'll need the patch from + . To see if your GhostScript + version already has this patch, please call gs --help and see if you can + find the device cfax in the long list of supported devices. + + + + + + Installation + First of all, I would suggest to check if your CAPI-driver is setup correctly. + To do this, simply run capiinfo on a root shell. + + If you get many lines of output, your CAPI driver works. If you just get + an error message, you'll have to install CAPI-compatible drivers. Refer + to the documentation of your ISDN card vendor, your Linux distribution + and/or some ISDN mailing lists for this, please. If you really can't find + anyone to support you in doing this, you may ask on the &cs; mailing + lists for support as last resort. + + The rest of the installation depends on wether you use binary or source + packages for installing &cs;. If you don't want to change the + &cs; sources, I would recommend you to use the binary packages + when available for your distribution and platform. + + You can download both binary packages and sources from the download section on + . If you built up your own RPM packages + for other distributions, please send me them and I'll copy them there... + + Installation from binary packages + + If you can get binary packages for your distribution and platform, + I would advise to use them. Currently, there are only RPM packages + for SuSE Linux available as this is the distribution I use (and BTW + the company which paid and supported me to write &cs; as diploma + thesis ;-) ). + + To install the &cs; RPM packages you can either use your favorite setup tool - + either given by your distributor or the community - or you can do manually + (as root): + + rpm -Uvh capisuite-version.rpm + + To install binary packages not in the RPM format, please refer to the + documentation of you package manager. + + If you managed to install &cs; on a system not mentioned above, + please tell me and I'll include the instructions here certainly. + + Now everything should be setup ready to run. So please read on in + . + + + Installation from the source packages + If there are no binary packages you can use or if you like to do + everything on your own, you can get the sources from the download section. + + Download the newest source tarball (capisuite-X.Y.tar.gz) from the + &cs; homepage and copy it to some location. Go there and issue the following commands: + + ./configure +make +su # get root now +make install + + This will install &cs; completely in the /usr/local-tree. If you + want it to stay in other directories, please see the commandline-help + printed by + + ./configure --help + + for options to customize the installation directories. + + Installation from CVS + If you want to live on the bleeding edge and always test the newest features, + you may also checkout the current sources of &cs; from the CVS + repository. + + This is not recommended unless you want to test the newest features or + want to help in developing &cs;! The sources in CVS may do anything, + may not work or not even compile. Do this on your own risk! + + You'll need installed and working versions of the usual development tools like + autoconf, autoheader, automake, GNU make, gcc/g++ and also the components described + above (esp. development packages of Python). + + If you want to build the documentation out of the sources, you'll also need + Doxygen. + + For instructions on where to find the CVS repository and how to checkout + the sources, please refer to the download section on the &cs; homepage + on . + + After you checked out the sources to some directory, please do + + automake -a +autoreconf + + + After that you can continue with the normal installation process as described in + . + + + + + + How &cs; works, how it is configured and started + First, let's start with a short introduction what &cs; actually + is and how it works. After that, the configuration and startup of &cs; + will be explained in short. + + How does &cs; work? + &cs; is a daemon (program which runs in the background) whos + main task is to sit around and wait until a call is incoming. + If this happens it will start a special Python script - the + incoming script - and do what this script tells it, for example + record a voice call to implement an answering machine. + + To also be able to issue outgoing calls, another script is called + at regular intervals - the idle script. It can check any resource + to get instructions for placing a call - one can for example imagine + to check a special mail account or watch a special directory where + tasks are placed by the user. + + So all user-visible actions and the behaviour of &cs; are defined + in these two scripts. + + You'll need to do two things now: + + + provide scripts by either + + using and configuring the default scripts distributed with &cs; or + writing your own scripts (perhaps by using the default ones as templates) + + + configure &cs; itself and tell it where to find the two scripts + + + This page concentrates on the general configuration of &cs; - that consists mainly of + options telling it which scripts to use and where and how to log its activities. After + that, some details about starting &cs; are described. + + The next pages will then introduce the standard scripts you already installed along with + &cs; and tell you how to use the answering maching and fax functions provided + by them. + + The details on how to write your own scripts are covered in another part of the + documentation (). + + Configuration of &cs; + &cs; uses a general configuration file for the core functions. This file should + be located in /etc/capisuite/capisuite.conf or /usr/local/etc/capisuite/capisuite.conf + depending on how you installed &cs;. + + Most options are set to reasonable defaults already for using the standard scripts + - so if you want you can also skip this section and read on in + + The options will be presented in brief here - for further details please + refer to the comments in the configuration file itself. + + Options available in capisuite.conf + + + This option tells &cs; which script should be executed at incoming + calls. Only change this if you want to use your own script. + + + + This option reflects the path and name of the idle script. + This script is called in regular intervalls to check if any outgoing + call should be done. As above, the default should be ok if you don't + use your own script. + + + + Here you can define how often the idle script should be executed. The + number given is the interval between subsequent invocations in seconds. + Lesser numbers give you quicker response to queued jobs but also a higher + system load. The default should be ok in most cases. + + + + This file will be used for all "normal" messages printed by + &cs; telling you what it does. Error messages are written to a + special log (see below). + + + + You can define how detailled the log output of &cs; will be. + The default will give you some informational messages for each + incoming and outgoing call and should be enough for normal use. I would + recommend to only increase it if you encounter some problems. + Logs of higher level are mainly intended for developers, so just use + them if you want to report a problem or have some know-how of the CAPI + interface and the internals of &cs;. + + + + All errors which &cs; detects internally and in your scripts + will end up here. These were put to an extra file so that they don't + get lost in the normal log. Please check this log regularly for any + messages - especially when you encounter problems. Please report all + messages you don't understand and which aren't caused by your + own script-modifications to the &cs; team. + + + + Startup of CapiSuite + As &cs; is a daemon, it is normally activated during the system + startup process. Just add a call to + + /path/to/capisuite -d + + in your startup scripts. In LSB conforming Linux distributions, you'll + find the startup scripts in /etc/init.d. For detailled documentation + how to add a service there please refer to the documentation of your + distribution. There's an example startup script written for SuSE Linux included + in the source distribution (see rc.capisuite) which should (hopefully) + work with other LSB compliant distributions, too. If you need to modify it, I'll + welcome your feedback and happily add instructions for other distributions here. + + If you use the right RPM packages of &cs;, the necessary scripts + should already be included. For activating them, please use your + distributors config tool. If you use the RPM distributed with SuSE Linux + and want to stay with the default scripts, everything should work "out of the box". + As soon as you have configured the default scripts, simply run + rccapisuite restart. + + For debug puposes, you can also start capisuite manually at any time + by just calling + + /path/to/capisuite + + There are also some other commandline options available: + + + commandline options of &cs; + + + show a short summary of commandline options + + + + use a custom configuration file instead + of /etc/capisuite/capisuite.conf or + /usr/local/etc/capisuite/capisuite.conf. + + + + run as daemon (used in your startup script, see above) + + + + + Features and configuration of the default scripts + As already written above, &cs; comes with default scripts + giving you the most used communication functions of an answering machine + and a fax device. + + This section should help you to use them for your daily needs. + + Script features + The scripts distributed with &cs; give you the following main + functions: + + + + multi-user answering machine + + different users using different numbers and different announcements are supported + incoming calls are saved and sent to the user by email + the delay until a call is accepted and the maximum record length are freely adjustable + silence is detected and the call terminated after an adjustable silence time + incoming fax calls are automatically detected by the CNG/CED signals and received + comfortable, menu-controlled remote inquiry functions are supported telling you + the date/time when the call was received and the called and calling numbers (currently only german). + record your own announcement via the remote inquiry menu + nearly each setting is configurable globally but can be overwritten for each user also + + + + fax machine + + different users using different numbers are supported for incoming faxes + incoming faxes are stored and sent to the user by email + command line tool for sending PostScript documents as fax included + number of tries and delays for sending faxes freely configurable + currently supports only one ISDN controller for outgoing faxes + + + + + + How the scripts work + Here follows a rough overview of how the scripts work generally. I will only explain + the behaviour which is important for the user here. If you want to understand the internals, + please refer to @ref userguidescriptchapter + + When an incoming call is received, several lists for the different users are + searched for the called number. The different users can define their own numbers in + the configuration (see below). So the scripts decide by looking on the called number + to which user the call destinates. If they find the number in the voice- or fax-number + list of any user, they'll answer the call with this service and give the caller the possibility to + leave his message or send his fax. + + The received document is then saved to a local directory in some native format + and also converted to a well-known format and mailed to the user along with some + details of the call. Voice calls are sent as a WAV attachment, while fax calls + are sent as PDF documents attached to the mail. + + So you'll normally get your incoming calls as a mail to a specified address - + but they're also saved in the local filesystem to be on the safe side. + It's your task to delete old files you don't need any more - perhaps via + some script called by a cronjob. + + There's also the possibility to do a remote inquiry on the answering machine + via a normal phone. The caller is presented a menu where he can choose to record + his announcement or to hear the saved voice calls. He will be told how many calls + are available, from whom and when they were received and so on. He'll also be + able to delete recorded calls he doesn't need any more. + + Another script will check a special queue directory for fax send jobs + regularly. To put jobs in this directory, a special commandline tool is also + provided. See for further details on this. + + + Script configuration + There are some important options which the scripts need to know before you can use them - + things like numbers and some details of how to handle the calls. + + These options are read from two configuration files. Each configuration file is divided into + one or more sections. A section begins with the section name in square brackets like [section] + while the options are key="value" lines. + + Each file must have a special section called [GLOBAL] and one section + for each user called [<username>] (with <username> being a + valid system user). + + The [GLOBAL]-section defines some global options like + pathnames and default settings for options users can change on their own. The user-sections + hold all the options which belong to the particular user. + + All options for the two files are described in short below. For all details, please see the comments + in the sample configuration files installed with &cs;. + + Configuration for fax service + This file holds all available config options for the fax services (fax receive and send). + + It's read from /etc/capisuite/fax.conf or + /usr/local/etc/capisuite/fax.conf (depending on the installation). + + available options for [GLOBAL] section in fax config + + + This directory is used to archive sent (or failed) jobs. It must exist and + the user &cs; runs as must have write permission to its subdirectories. + Two subdirectories are used: + + + spooldir/done/ + Successfully finished jobs are moved to this directory. + + + spooldir/failed/ + A job which has failed finally ends up here. + + + + + + + This directory is used to save faxes to. It must exist and + the user &cs; runs as must have write permission to it. It will contain + one subdirectory for each configured user (named like his userid). The + following subdirectories are used below the user-specific dir: + + + user_dir/username/received/ + Received faxes are saved here. + + + user_dir/username/sendq/ + Fax files to be sent are queued here by capisuitefax. + + + + + + + When a fax can't be sent to the destination for any reason, it's tried for several times. + This setting limits the number of tries. If all tries failed, the job will be considered + failed, moved to the failed dir (see ) and the user will get a mail. + + + + + When a fax can't be sent to the destination for any reason, it's tried again. + This setting specifies the delays in seconds between subsequent tries. The different values are + separated with commas and no blanks. The list should have send_tries-1 + (see ) values - if not, surplus entries are ignored and missing + entries are filled up with the last value. The default should just be ok giving you increasing + delays for up to 10 tries. + + + + + If you have more than one ISDN controller installed (some active cards for more than + one basic rate interface like the AVM C2 or C4 are also represented as multiple controllers for + CAPI applications like &cs;), you can decide which controller (and therefore which basic rate + interface) should be used for sending your faxes. All controllers are numbered starting with 1. + If you're not sure which controller has which number, increase the log level to at least 2 + in &cs; (see ), restart it and have a look in the log file where all + controllers will be listed then. Unfortunately, &cs; isn't able to use more than one controller + for sending faxes at the moment, so no list is allowed here. If you have only one controller, + just leave it at 1 + + + + + This number is used as our own number for outgoing calls. If it's not given, + the first number of fax_numbers is used (see ). Please + replace with one valid MSN of your ISDN interface or leave empty. This value can be + overwritten in the user sections individually. + + + + + Default setting which defines how many seconds we will wait for a successful connection after + dialing the number. This value can be overwritten in the user sections individually. + + + + + Default fax station ID to use when sending a fax document. The station ID is + usually the number of your fax station in international format, so an example would be + "+49 89 123456" for a number in Munich, Germany. Station IDs may only consist of the "+"-sign, + spaces and the digits 0-9. The maximal length is 20. This value can be overwritten in the user + sections individually. + + + + + Default fax headline to use when sending a fax document. Where and if this + headline will be presented depends on the implementation of your CAPI driver. The headline + should have a reasonable length to fit on the top of a page, but there's no definite limit + given. + + + + available options for user sections in fax config + + + User specific value for the global option above + + + + User specific value for the global option above + + + + User specific value for the global option above + + + + User specific value for the global option above + + + + A list containing the numbers on which this user wants to receive incoming fax calls. + These numbers are used to differ between users - so the same number must not appear in more + than one user section! The numbers are separated with commas and no blanks + are allowed. The first number of the list also serves as our own number when + sending a fax if outgoing_MSN is not set (see ) + If you want to use the same number for receiving fax and voice calls, please + do not enter it here. Use the voice_numbers option instead + (see ) - the answering machine has a built in fax detection + and can also receive faxes. + When this list is set to *, + all incoming calls will be accepted for this user (use with care!). + This is only useful for a setup with only one user which wants to receive any call as fax. + + + + + + If given, this string indicates an email-address where the received faxes and + voice calls will be sent to. If it is empty, they will be sent to the user account on the + system &cs; is running on. The address is also used to send status reports + for sent fax jobs to. If you don't want emails to be sent at all, use the + action option (see ). + + + + + Here you can define what action will be taken when a call is received. + Currently, three possible actions are supported: + + + + the received call will be mailed to the given address (see + above) and saved to the user_dir (see ) + + + + + + the call will be only saved to the user_dir (see ) + + + + + + + + + Configuration for the answering machine + This file holds all available config options for the answering machine. + + It's read from /etc/capisuite/answering_machine.conf or + /usr/local/etc/capisuite/answering_machine.conf (depending on the installation). + + available options for [GLOBAL] section in answering machine config + + + The answering machine script uses several wave files, for example + a global announcement if the user hasn't set his own and some spoken word fragments + for the remote inquiry and the menu presented there. These audio files are searched + in this directory. If user_audio_files is enabled (see ), each user can also + provide his own audio snippets in his user_dir (see ). + + + + This directory is used to save user specific data to. It must exist and + the user &cs; runs as must have write permission to it. It will contain + one subdirectory for each configured user (named like his userid). The + following subdirectories are used below the user-specific dir: + + + user_dir/username/ + Here the user may provide his own audio_files + (see also option above). + The user defined announcement is also saved here. + + + user_dir/username/received/ + Received voice calls are saved here. + + + + + + + If set to 1, each user may provide his own audio files + in his user directory (see ). If set to 0, + only the audio_dir (see ) will be searched. + + + + + Sets the default value for the delay for accepting an incoming + call in (in seconds). A value of 10 means that the answering + machine accepts incoming calls 10 seconds after the incoming connection request. + This value can be overwritten in the user sections individually. + + + + + Sets the default name for the announcement files for the users. + The announcement is searched in user_dir/username/announcement then. If not found, + a global announcement containing the called MSN will be played. This value can + be overwritten in the user sections individually. + + + + + Default setting for the maximum record length in seconds. This value can + be overwritten in the user sections individually. + + + + + Default setting for the record silence timeout in seconds. When set to a value + greater than 0, the recording will be aborted if silence is detected for the given + amount of seconds. Set this to 0 to disable it. This value can + be overwritten in the user sections individually. + + + + available options for user sections in answering machine config + + + User specific value for the global option above + + + + User specific value for the global option above + + + + User specific value for the global option above + + + + User specific value for the global option above + + + + A list containing the numbers on which this user wants to receive incoming voice calls. + These numbers are used to differ between users - so the same number must not appear in more + than one user section! The numbers are separated with commas and no blanks + are allowed. The answering machine script does also automatic fax detection, so a fax can + also be sent to this number. When this list is set to *, + all incoming calls will be accepted for this user (use with care!). + This is only useful for a setup with only one user which wants to receive any call. + + + + + If given, this string indicates an email-address where the received faxes and + voice calls will be sent to. If it is empty, they will be sent to the user account on the + system &cs; is running on. The address is also used to send status reports + for sent fax jobs to. If you don't want emails to be sent at all, use the + action option (see ). + + + + + The answering machine also supports a remote inquiry function. + This function is used by entering a PIN (Personal Identification Number) + while the announcement is played. This PIN can be setup here. + If you don't want to use the remote inquiry function, just use an empty + PIN setting. The PIN doesn't have a maximal length - but perhaps you should + not use 200 digits or you perhaps won't be able to remember them (I won't at least). ;-) + + + + + Here you can define what action will be taken when a call is received. + Currently, three possible actions are supported: + + + + the received call will be mailed to the given address (see + above) and saved to the user_dir (see ) + + + + + + the call will be only saved to the user_dir (see ) + + + + + only the announcement will be played - no voice file will + be recorded + + + + + + + + + + Deleting old files + + As written above, all incoming and outgoing calls will be saved on + the local file system to assure nothing gets lost. There's no cleaning + up done by &cs;, so these files will stay forever on your system + if you don't clean them up from time to time. + + As it's not very convenient to do this manually, I would advise to + automate this process. cron is predestinated for + such a task. On most modern GNU/Linux distributions, you can simply place + scripts in /etc/cron.daily and they will be called + automatically once a day. + + An example for a bash script you can use is also in the &cs; distribution. + Just copy capisuite.cron to /etc/cron.daily/capisuite + and assure it has correct permissions (owner root, executable bit set). + + Now create a file cronjob.conf holding a line like + + MAX_DAYS=30 + + in your &cs; configuration directory (usually /etc/capisuite + or /usr/local/etc/capisuite). This tells the cron job how + long the files should be stored. Each file which wasn't accessed in the + last MAX_DAYS days will be deleted when + /etc/cron.daily/capisuite is started. If + MAX_DAYS is set to 0, no cleaning up is done. You can + also place a cronjob.conf file with an own value for + MAX_DAYS in each directory which is cleaned up. + + + Using &cs; together with the default scripts + Receiving calls + Now this is a nice, short section. Once you have configured + &cs;, the scripts and started &cs; successfully, there's nothing + more you have to do. You'll get your mails as described in + and that's it. You only have + to setup your mail program to receive local mails. Enjoy! :-) + + Doing a remote inquiry + To do a remote inquiry, please enter your PIN (see ) + while the announcement of the answering machine is played. After some seconds + you will get a "voice menu" telling you how to record your own announcement + for your answering machine or how to playback the received calls. + + Sending fax jobs + The default scripts for &cs; also include a commandline + tool for sending faxes called capisuitefax. + + capisuitefax will be called with some parameters + telling it which file to send (it currently only supports PostScript files) + and to which number. It will then enqueue the job converted to the + right format into the send queue + from which it's collected by another &cs; script and sent to the + destination. If the sending was completed successfully or failed finally + after trying for some times, the according user will get an email + telling him what has happened. + + The following options are recognized by capisuitefax: + + capisuitefax -d dialstring [-h] [-q] file1 [file2...] + + + + the number which should be called (destination of the fax) + + + + show a short commandline help + + + + be quiet, don't output informational messages + + + + one or more PostScript files to send to this destination (more than + one PostScript file will produce several separate fax jobs) + + + + + + + +Users Guide + + In the last chapter you've seen how to use the default scripts distributed with &cs;. + But the main goal in developing &cs; was not to provide a perfect ready-to-use + application. I intended to develop a tool where you can write your own + applications very easy. I'll show you how to do this in the next sections. + + + Introduction to Python + + As I tought about the scripting language I wanted to integrate into &cs;, + my first idea was to develop an own, simple one. But as more as I looked into it, as + more I found that a general purpose language having common features already will be much + more helpful than re-inventing every wheel that I would need. So I looked for some + easy to integrate (and to learn) language. The one I liked most was Python - and it + also had a nice documentation about embedding, so I chose it and I'm still happy about + that decision. :-) + + So the first thing you'll have to do is to learn a little bit of Python. Don't be afraid - + it was developed as a beginners language and Guido (Guido van Rossum, the inventor of Python) + has done very well in my opinion. + + In the next few sections, I'll give you a short introduction to the features of Python + you most probably will need for &cs;. As this shouldn't be a manual about Python or a tutorial + in computer programming, I assume you're already familiar with the basic concepts of todays + wide-spread procedural and object-oriented languages. + + If not, I would advise you to get and read a book for learning Python - there are many + available in different languages - or to have a look on the Python home page on + where nice and comprehensive manuals and tutorials are + available for free. + + Python Basics + + Python supports most features you know from other common languages. So here's the + syntax of the basic operations shown in a Python session. A python session is another + fine feature of its interpreter: just start it by typing python + in a shell and you'll get the prompt of Python: + + + + + + Used file formats + &cs; always reads and saves files in the native format as they will be expected and given + by the CAPI ISDN drivers. This preserves it from having to convert everything from and to other + formats thus reducing unnecessary overhead. + + As these formats aren't that well-known and you will need special tools to convert or + view/play them, I'll give you a short overview of how you can do this. + + All tools which I refer to here are described in . See + there for informations how to get them. + + Format for voice files (inversed A-Law, 8kHz, mono) + ISDN transmits voice data as waves with a sample-rate of 8kHz in mono. To + save bandwith, a compression called A-Law is used (at least in Europe, other countries + like the USA use u-Law which is quite similar to A-Law). For any reason + beyond my understanding, they use a bit-reversed form of A-Law called + inversed A-Law. + + Creating A-Law files + + There are two possible ways to create A-Law files. + + The first one is to call your computer with your phone (either use the default answering + machine script and configure it as described in + or write a simple script yourself), record whatever you want and take the created + output file (when you use the default scripts please take the file from the + user_dir, not the attachment of the mail as this is already converted) and use it. + + You eventually want to trim the recorded file and remove unwanted + noise and silence at the beginning and the end. This can easily be done + by sox and play (which comes together + with sox). + + sox is used to convert a file while play + is used to just play it. Both support the same effects including the trim option. + Both also detect what type of file you are using by looking at the suffix of + your file name. So all your inversed A-Law files should be named something.la + (.la is the inversed form of .al which stands for A-Law). + + So let's first try to find the optimal values for the trim effect by calling + play: + + play myfile.la trim <start-offset> <duration> + + Now play around with start-offset and duration (both given in seconds) until + you find the right values. If you found them, you can use sox + to actually produce the needed file: + + sox myfile.la outfile.la trim <start-offset> <duration> + + You'll now get a file named outfile.la which should + contain what you want. + + The second way to create an inversed A-Law file is to record a normal WAV-file + with your favourite sound-tools and convert it to the destination format using + sox. You'll get the best results when your WAV file already + is in 8kHz, mono, 8 bit format. sox is able to convert + other waves if necessary but this usually will result in bad quality. + + Now you can convert the WAV to inversed A-Law by calling: + + sox myfile.wav -r 8000 -c 1 -b outfile.la + + + Playing A-Law files + Again, there are two possibilities. The play command + of sox is able to just play the inversed A-Law format without + any conversion. Just call play with the filename as parameter: + + play myfile.la + + But you can also use sox to convert the A-Law files to the more common + WAV format by just invoking: + + sox myfile.la outfile.wav + + The created outfile.wav can be played + by nearly any audio player without problems. + + + Format of fax files (Structured Fax Files) + CAPI-compliant drivers will expect and provide fax files in a so called + Structured Fax File (SFF). As this seems to be a CAPI-specific format, there + are not much tools out there for GNU/Linux which are capable of handling it. + Finally I found some small tools written by Peter Schäfer, so + we can use them thankfully :-). + + Creating a SFF + In current Ghostscript releases, a patch from Peter has been + included to produce SF files. To see if your Ghostscript already + supports it, enter gs --help and look for + the so-called cfax-device in the long device list + presented to you. If it's not listed, you have to take a newer Ghostscript + or recompile it, sorry. I don't know any other way to produce SFF currently. + + You need a PostScript file (as produced by nearly every Linux program + when you choose "print to file") first. Now you can call GhostScript to + convert it to a SFF: + + gs -dNOPAUSE -dQUIET -dBATCH -sDEVICE=cfax -sOutputFile=outfile.sff myfile.ps + + If you're not sure if it worked you can use sffview. + + Viewing / converting from SFF + To simply view a received SFF, you can use the sffview + program. It's a simple but useful tool for viewing SF files without the need + to convert them. Just start it and you will get a GUI where you can open the + desired file. + + If you want to convert a fax file to a more common format, I recommend + using sfftobmp. It supports quite some common output formats + like JPEG, TIFF, PBM or BMP. I prefer multipage TIFF files as this is the only + format being able to contain several pages in one file. To convert + SFF to multipage TIFF, call: + + sfftobmp -tif myfile.sff outfile.tiff + + This will give you a TIFF file which you can convert now to nearly + any other useful format with the TIFF tools, for example tiff2ps + + + + + + +Developers Guide + + +CAPI 2.0 Error Codes + The CAPI interface used here has its own coding of standard ISDN + error codes. Most of the errors described in + are only important for developers of the &cs; core. As user, you only need + to know the codes shown in + as they'll be used in the &cs; Python functions like disconnect (@ref). + + In the next two sections, you'll find a list of all codes and a short description. + A detailled description of the CAPI codes can be found in the CAPI specification available at + . + + All numbers are given hexadecimal! + +
CAPI errors describing connection problems + + All errors described here indicate some problem with the connection. + These errors are also important for script writers as they're returned by + some CapiSuite Python functions like capisuite.disconnect() (@ref). + +
Protocol errors + Protocol errors indicate some problem during data transfer. Only messages for + voice (transparent) and fax are shown here as these are the only protocols spoken + by &cs;. + + + 3301 - Protocol error layer 1 (broken line or B-channel removed by signalling protocol) + 3302 - Protocol error layer 2 + 3303 - Protocol error layer 3 + 3304 - Another application got that call + 3311 - T.30 (fax) error: Connection not successful (remote station is not a G3 fax device) + 3312 - T.30 (fax) error: Connection not successful (training error) + 3313 - T.30 (fax) error: Disconnect before transfer (remote station doesn' support transfer mode, e.g. wrong resolution) + 3314 - T.30 (fax) error: Disconnect during transfer (remote abort) + 3315 - T.30 (fax) error: Disconnect during transfer (remote procedure error) + 3316 - T.30 (fax) error: Disconnect during transfer (local transmit data underflow) + 3317 - T.30 (fax) error: Disconnect during transfer (local receive data overflow) + 3318 - T.30 (fax) error: Disconnect during transfer (local abort) + 3319 - T.30 (fax) error: Illegal parameter coding (e.g. defective SFF file) + +
+
ISDN error codes + + The codes shown here are ISDN error codes which are described by the + ETS 300 102-01 standard in more detail. It's currently available for private use at + without fee. For details how the ISDN codes + are mapped to the CAPI numbers see the CAPI specification, parameter "Info". + + + 3480 - Normal termination + 3481 - Unallocated (unassigned) number + 3482 - No route to specified transit network + 3483 - No route to destination + 3486 - Channel unacceptable + 3487 - Call awarded and being delivered in an established channel + 3490 - Normal call clearing + 3491 - User busy + 3492 - No user responding + 3493 - No answer from user (user alerted) + 3495 - Call rejected + 3496 - Number changed + 349A - Non-selected user clearing + 349B - Destination out of order + 349C - Invalid number format + 349D - Facility rejected + 349E - Response to STATUS ENQUIRY + 349F - Normal, unspecified + 34A2 - No circuit / channel available + 34A6 - Network out of order + 34A9 - Temporary failure + 34AA - Switching equipment congestion + 34AB - Access information discarded + 34AC - Requested circuit / channel not available + 34AF - Resources unavailable, unspecified + 34B1 - Quality of service unavailable + 34B2 - Requested facility not subscribed + 34B9 - Bearer capability not authorized + 34BA - Bearer capability not presently available + 34BF - Service or option not available, unspecified + 34C1 - Bearer capability not implemented + 34C2 - Channel type not implemented + 34C5 - Requested facility not implemented + 34C6 - Only restricted digital information bearer capability is available + 34CF - Service or option not implemented, unspecified + 34D1 - Invalid call reference value + 34D2 - Identified channel does not exist + 34D3 - A suspended call exists, but this call identity does not + 34D4 - Call identity in use + 34D5 - No call suspended + 34D6 - Call having the requested call identity has been cleared + 34D8 - Incompatible destination + 34DB - Invalid transit network selection + 34DF - Invalid message, unspecified + 34E0 - Mandatory information element is missing + 34E1 - Message type non-existent or not implemented + 34E2 - Message not compatible with call state or message type non-existent or not implemented + 34E3 - Information element non-existent or not implemented + 34E4 - Invalid information element contents + 34E5 - Message not compatible with call state + 34E6 - Recovery on timer expiry + 34EF - Protocol error, unspecified + 34FF - Interworking, unspecified + +
+
+ +
Internal CAPI errors + These errors are mainly of interest for developers of the CapiSuite core. If you're just a user, you + won't need most of them. + +
Informative values (no error) + These values are only warnings and may appear in the extensive CapiSuite log only in CAPI messages. + + 0000 - No error, request accepted + 0001 - NCPI not supported by current protocol, NCPI ignored + 0002 - Flags not supported by current protocol, flags ignored + 0003 - Alert already sent by another application + +
+
Errors concerning CAPI_REGISTER + These errors may appear when the application starts and mostly indicate problems with your driver installation. + + + 1001 - Too many applications. + 1002 - Logical Block size too small; must be at least 128 bytes. + 1003 - Buffer exceeds 64 kbytes. + 1004 - Message buffer size too small, must be at least 1024 bytes. + 1005 - Max. number of logical connections not supported. + 1006 - reserved (unknown error). + 1007 - The message could not be accepted because of an internal busy condition. + 1008 - OS Resource error (out of memory?). + 1009 - CAPI not installed. + 100A - Controller does not support external equipment. + 100B - Controller does only support external equipment. + +
+
Message exchange errors + These errors are really internal: they're raised if the application calls + CAPI in a wrong way. If they occur, it's usually a bug which you should tell + the &cs; team. + + + 1101 - Illegal application number. + 1102 - Illegal command or subcommand, or message length less than 12 octets. + 1103 - The message could not be accepted because of a queue full condition. + 1104 - Queue is empty. + 1105 - Queue overflow: a message was lost!! + 1106 - Unknown notification parameter. + 1107 - The message could not be accepted because on an internal busy condition. + 1108 - OS resource error (out of memory?). + 1109 - CAPI not installed. + 110A - Controller does not support external equipment. + 110B - Controller does only support external equipment. + +
+
Resource/Coding Errors + The errors described here are issued when the application tries to use a ressource which isn't available. + These are mostly also bugs in the application. Please tell us. + + + 2001 - Message not supported in current state + 2002 - Illegal Controller / PLCI / NCCI + 2003 - Out of PLCI + 2004 - Out of NCCI + 2005 - Out of LISTEN + 2007 - llegal message parameter coding + +
+
Errors concerning requested services + The errors described here are issued when the application tries to request a service wrong. + Again these are mostly bugs you should tell us. + + + 3001 - B1 protocol not supported + 3002 - B2 protocol not supported + 3003 - B3 protocol not supported + 3004 - B1 protocol parameter not supported + 3005 - B2 protocol parameter not supported + 3006 - B3 protocol parameter not supported + 3007 - B protocol combination not supported + 3008 - NCPI not supported + 3009 - CIP Value unknown + 300A - Flags not supported (reserved bits) + 300B - Facility not supported + 300C - Data length not supported by current protocol + 300D - Reset procedure not supported by current protocol + +
+
+
+ + + + +
diff --git a/rc.capisuite.in b/rc.capisuite.in new file mode 100755 index 0000000..4a258b2 --- /dev/null +++ b/rc.capisuite.in @@ -0,0 +1,180 @@ +#! /bin/bash +# Copyright (c) 1995-2002 SuSE Linux AG, Nuernberg, Germany. +# All rights reserved. +# +# Author: Kurt Garloff , Gernot Hillier +# +# This file was written for the use with SuSE Linux, but it should +# (hopefully) work for any other LSB compliant distribution. If you need to +# modify it, I'll welcome your feedback. TIA! +# +# /etc/init.d/capisuite +# and its symbolic link +# /usr/sbin/rccapisuite +# +# system startup script for the CapiSuite daemon +# +# LSB compatible service control script; see http://www.linuxbase.org/spec/ +# +### BEGIN INIT INFO +# Provides: capisuite +# Required-Start: $syslog $remote_fs isdn +# X-UnitedLinux-Should-Start: $time ypbind sendmail +# Required-Stop: $syslog $remote_fs isdn +# X-UnitedLinux-Should-Stop: $time ypbind sendmail +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Short-Description: CapiSuite daemon providing ISDN fax and voice services +# Description: Start CapiSuite to use the default scripts included +# for the ISDN fax and answering machine. It tests for configured +# fax and answering machine users so please modify if you want +# to use your own scripts! +### END INIT INFO +# + +# Check for missing binaries (stale symlinks should not happen) +CAPISUITE_BIN=@bindir@/capisuite +test -x $CAPISUITE_BIN || exit 5 + +# Check for existence of needed config file and read it +CAPISUITE_CONFIG=@pkgsysconfdir@/capisuite.conf +test -r $CAPISUITE_CONFIG || exit 6 + +# Shell functions sourced from /etc/rc.status: +# rc_check check and set local and overall rc status +# rc_status check and set local and overall rc status +# rc_status -v ditto but be verbose in local rc status +# rc_status -v -r ditto and clear the local rc status +# rc_status -s display "skipped" and exit with status 3 +# rc_status -u display "unused" and exit with status 3 +# rc_failed set local and overall rc status to failed +# rc_failed set local and overall rc status to +# rc_reset clear local rc status (overall remains) +# rc_exit exit appropriate to overall rc status +# rc_active checks whether a service is activated by symlinks +# rc_splash arg sets the boot splash screen to arg (if active) +. /etc/rc.status + +# Reset status of this service +rc_reset + +# Return values acc. to LSB for all commands but status: +# 0 - success +# 1 - generic or unspecified error +# 2 - invalid or excess argument(s) +# 3 - unimplemented feature (e.g. "reload") +# 4 - user had insufficient privileges +# 5 - program is not installed +# 6 - program is not configured +# 7 - program is not running +# 8--199 - reserved (8--99 LSB, 100--149 distrib, 150--199 appl) +# +# Note that starting an already running service, stopping +# or restarting a not-running service as well as the restart +# with force-reload (in case signaling is not supported) are +# considered a success. + +case "$1" in + start) + echo -n "Starting CapiSuite " + configured=yes + # Check if there are configured users for fax or + # answering machine. Otherwise exit. + # IMPORTANT: Change this or comment it out if you want to use + # your own CapiSuite scripts. + while read -r sec rest ; do + if [ "${sec:0:1}" = "[" -a "$sec" != "[GLOBAL]" ]; then + configured_fax=yes + break + fi + done < <(cat @pkgsysconfdir@/fax.conf) + while read -r sec rest ; do + if [ "${sec:0:1}" = "[" -a "$sec" != "[GLOBAL]" ]; then + configured_voice=yes + break + fi + done < <(cat @pkgsysconfdir@/answering_machine.conf) + test "$configured_fax" -o "$configured_voice" || configured=no + # end check for configured users + + ## Start daemon with startproc(8). If this fails + ## the return value is set appropriately by startproc. + if [ $configured = "yes" ]; then + startproc $CAPISUITE_BIN -d + else + rc_failed 6 + fi + + # Remember status and be verbose + rc_status -v + ;; + stop) + echo -n "Shutting down CapiSuite " + ## Stop daemon with killproc(8) and if this fails + ## killproc sets the return value according to LSB. + + killproc -TERM $CAPISUITE_BIN + + # Remember status and be verbose + rc_status -v + ;; + try-restart) + ## Do a restart only if the service was active before. + ## Note: try-restart is not (yet) part of LSB (as of 1.2) + $0 status >/dev/null && $0 restart + + # Remember status and be quiet + rc_status + ;; + restart) + ## Stop the service and regardless of whether it was + ## running or not, start it again. + $0 stop + $0 start + + # Remember status and be quiet + rc_status + ;; + force-reload) + ## Signal the daemon to reload its config. Most daemons + ## do this on signal 1 (SIGHUP). + ## If it does not support it, restart. + + echo -n "Reload service CapiSuite " + killproc -HUP $CAPISUITE_BIN + rc_status -v + ;; + reload) + ## Like force-reload, but if daemon does not support + ## signaling, do nothing (!) + + echo -n "Reload service CapiSuite " + killproc -HUP $CAPISUITE_BIN + rc_status -v + + ;; + status) + echo -n "Checking for service CapiSuite " + ## Check status with checkproc(8), if process is running + ## checkproc will return with exit status 0. + + # Return value is slightly different for the status command: + # 0 - service up and running + # 1 - service dead, but /var/run/ pid file exists + # 2 - service dead, but /var/lock/ lock file exists + # 3 - service not running (unused) + # 4 - service status unknown :-( + # 5--199 reserved (5--99 LSB, 100--149 distro, 150--199 appl.) + + # NOTE: checkproc returns LSB compliant status values. + checkproc $CAPISUITE_BIN + # NOTE: rc_status knows that we called this init script with + # "status" option and adapts its messages accordingly. + rc_status -v + ;; + *) + echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}" + exit 1 + ;; +esac +rc_exit diff --git a/scripts/.cvsignore b/scripts/.cvsignore new file mode 100644 index 0000000..6866f16 --- /dev/null +++ b/scripts/.cvsignore @@ -0,0 +1,5 @@ +answering_machine.conf +fax.conf +cs_helpers.py +Makefile +Makefile.in diff --git a/scripts/Makefile.am b/scripts/Makefile.am new file mode 100644 index 0000000..f1ed3fa --- /dev/null +++ b/scripts/Makefile.am @@ -0,0 +1,37 @@ +spooldir = @localstatedir@/spool/capisuite +pkgsysconfdir = @sysconfdir@/capisuite + +dist_pkglib_DATA = idle.py incoming.py README +python_module_DATA = cs_helpers.py +EXTRA_DIST = cs_helpers.pyin fax.confin answering_machine.confin + +pkgsysconf_DATA = fax.conf answering_machine.conf + +dist_bin_SCRIPTS = capisuitefax + +SUBDIRS = waves + +.pyin.py: + rm -f $@ + sed -e 's,@\pkgsysconfdir@,$(pkgsysconfdir),g' $< >$@ + +.confin.conf: + rm -f $@ + sed -e 's,@pkgdatadir\@,$(pkgdatadir),g' \ + -e 's,@spooldir\@,$(spooldir),g' $< >$@ + +uninstall-hook: + -rmdir $(DESTDIR)$(pkglibdir) + -rmdir $(DESTDIR)$(spooldir)/sendq $(DESTDIR)$(spooldir)/done \ + $(DESTDIR)$(spooldir)/failed $(DESTDIR)$(spooldir)/users $(DESTDIR)$(spooldir) + +install-exec-hook: + $(mkinstalldirs) $(DESTDIR)$(spooldir)/sendq + $(mkinstalldirs) $(DESTDIR)$(spooldir)/done + $(mkinstalldirs) $(DESTDIR)$(spooldir)/failed + $(mkinstalldirs) $(DESTDIR)$(spooldir)/users + +clean-local: + rm -f cs_helpers.py + rm -f fax.conf answering_machine.conf + diff --git a/scripts/README b/scripts/README new file mode 100644 index 0000000..ada1dc9 --- /dev/null +++ b/scripts/README @@ -0,0 +1,2 @@ +This directory holds the global python scripts for CapiSuite. Please +see the CapiSuite documentation for further details. diff --git a/scripts/answering_machine.confin b/scripts/answering_machine.confin new file mode 100644 index 0000000..ab431bb --- /dev/null +++ b/scripts/answering_machine.confin @@ -0,0 +1,125 @@ +# $Id: answering_machine.confin,v 1.1 2003/02/19 08:19:54 gernot Exp $ +# +# This is the configuration file for the answering machine scripts distributed +# with CapiSuite +# +# It is read by the incoming.py script which is distributed with CapiSuite. +# If you don't want to use it but develop your completely own application, +# you won't need it! CapiSuite itself (the daemon) doesn't read it. +# +# For a further description, please see the CapiSuite documentation - +# there's a part describing the scripts. +# +# As usual, lines starting with # or empty lines will be ignored +# +# The rest must be key value pairs written as key=value or section names. +# +# Additional whitespaces and quotation marks (") surrounding +# the values will be ignored. +# +# The file is split in sections starting with "[sectionname]". The section +# [GLOBAL] contains all options common for all users. For each different user, +# an own section is used which must at least contain "voice_numbers". +# +# Nearly all global options can be overwritten in the [user]-sections + +############################################################################### +############################ global settings ################################## +############################################################################### + +[GLOBAL] + +# Directory where audio snippets used in the answering machine script are +# located. If user_audio_files is enabled (see below), each user can also +# provide his own audio snippets in his user_dir (see below). +audio_dir="@pkgdatadir@/" + +# Directory for all user-specific data. Contains one subdirectory +# for each user (named like his userid). The following directory tree is used: +# +# user_dir/username/ - here the user may provide his own audio_files +# (see user_audio_files option). The user defined announcement +# is also saved here. +# user_dir/username/received - all received calls (voice and fax) will be saved here +voice_user_dir="@spooldir@/users/" + +# Controls wether the user_dir (see below) will also be searched for audio +# files. If set to "1", the script will look in the user_dir and then in +# audio_dir for a needed audio file. If "0", only audio_dir is used. +# This doesn't affect the announcement, which can and should be different +# for each user in any case. +user_audio_files="1" + +# Global setting for the time in seconds before incoming voice calls are +# accepted. +voice_delay="15" + +# This value gives the default name of the announcement file which is searched +# in the user_dir. Each user should provide one in his/her own dir. +announcement="announcement.la" + +# record_length +# +# Global setting for the maximal record length of the answering machine +# in seconds +record_length="60" + +# record_silence_timeout +# +# Global setting for the length of silence after which recording is +# finished by the answering machine. +record_silence_timeout="5" + +############################################################################### +############################# user settings ################################### +############################################################################### + +# The following sections start with the name of the users which want to use +# CapiSuite. The names must be exactly equal to system users. +# +# Each user section can override the following default options given above: +# +# voice_delay, announcement, record_length, record_silence_timeout +# +# Additionally, the following options are possible: +# +# voice_numbers="13,14" +# This list contains the numbers on which this user wants to receive incoming +# voice calls. The values are separated by commas. You can also use the special +# entry "*" which stands for accepting ALL incoming calls (use with care!). +# +# voice_email="name@domain.de" +# If given, this string indicates an email-address where the received faxes +# and voice calls will be sent to. If it is empty, the recorded calls and +# faxes will be sent to the user on the current system. It's also used to +# send status reports for sent fax jobs to. If you don't want to get emails, +# see the "action" option below +# +# pin="" +# pin for activating the remote inquiry. Start typing when the announcement +# is played. If you don't want remote inquiry function for your answering +# machine for security or other reasons, just set this to an empty string. +# You can use as many digits as you want. The script will wait 2 seconds after +# each typed digit for the next one. +# +# voice_action="" +# Here you can define what action will be taken when a call is received. +# Currently, three possible actions are supported: +# +# MailAndSave - the received call will be mailed to the given address (see +# "email" above) and saved to the user_dir. +# SaveOnly - the call will be only saved to the user_dir +# None - only the announcement will be played - no voice file will +# be recorded +# +# Here's an example of a valid user configuration for "gernot" - just remove +# the leading #-signs and edit it: +# +# [gernot] +# voice_numbers="13,14" +# voice_action="MailAndSave" +# voice_delay="10" +# record_length="60" +# voice_email="" # sent to gernot@localhost +# pin="99*45" + diff --git a/scripts/capisuitefax b/scripts/capisuitefax new file mode 100755 index 0000000..70d6a54 --- /dev/null +++ b/scripts/capisuitefax @@ -0,0 +1,168 @@ +#!/usr/bin/python +# +# capisuitefax - capisuite tool for enqueuing faxes +# --------------------------------------------------- +# copyright : (C) 2002 by Gernot Hillier +# email : gernot@hillier.de +# version : $Revision: 1.1 $ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +import getopt,os,sys,re,time,pwd,errno,fcntl +# capisuite stuff +import cs_helpers + +dialstring="" +abort="" +quiet=0 +listqueue=0 + +def usage(error=""): + print "capisuitefax - capisuite tool for enqueueing faxes" + print + print "usage:" + print "capisuitefax [-q] -d file1 [file2...] or" + print "capisuitefax [-q] -a or" + print "capisuitefax [-h] [-l]" + print + print "possible options are:" + print + print "-d , --dialstring= send fax to this number (required)" + print "-a , --abort= abort fax job with id (id is a number)" + print "-l, --list print the jobs in the send queue" + print "-h, --help print this usage information" + print "-q, --quiet be quiet, don't output informational messages" + print + print "The given files must be in Adobe PostScript format" + if (error!=""): + print + print "ERROR:",error + sys.exit(1) + +def showlist(config,user): + sendq=config.get("GLOBAL","fax_user_dir")+user+"/sendq/" + + print "ID Number Tries Next try" + + files=os.listdir(sendq) + files=filter (lambda s: re.match("fax-.*\.txt",s),files) + if (not len(files)): + print "--- queue empty ---" + + for job in files: + control=cs_helpers.readConfig(sendq+job) + sys.stdout.write(re.match("fax-([0-9]+)\.txt",job).group(1)) + sys.stdout.write("\t") + sys.stdout.write(control.get("GLOBAL","dialstring")) + if (len(control.get("GLOBAL","dialstring"))<8): + sys.stdout.write("\t") + sys.stdout.write("\t") + sys.stdout.write(control.get("GLOBAL","tries")) + sys.stdout.write("\t\t") + sys.stdout.write(control.get("GLOBAL","starttime")+"\n") + + sys.exit(0) + +def abortjob(config,user,job): + sendq=config.get("GLOBAL","fax_user_dir")+user+"/sendq/" + job="fax-"+job+".txt" + + if (not os.access(sendq+job,os.W_OK)): + print "job to abort not valid" + sys.exit(1) + + try: + lockfile=open(sendq+job[:-3]+"lock","w") + fcntl.lockf(lockfile,fcntl.LOCK_EX | fcntl.LOCK_NB) # lock so that it isn't deleted while sending + os.unlink(sendq+job) + os.unlink(sendq+job[:-3]+"sff") + fcntl.lockf(lockfile,fcntl.LOCK_UN) + os.unlink(sendq+job[:-3]+"lock") + except IOError,err: + if (err.errno in (errno.EACCES,errno.EAGAIN)): + print "Sorry, this job is currently in transmission. Can't abort." + + +try: + optlist,args = getopt.getopt(sys.argv[1:], "d:a:lhq", ['dialstring=','help',"abort=","list","quiet"]) + +except getopt.GetoptError, e: + usage(e.msg) + +# read options +for option,param in optlist: + if option in ('-d','--dialstring'): dialstring=param + if option in ('-h','--help'): usage() + if option in ('-l','--list'): listqueue=1 + if option in ('-a','--abort'): abort=param + if option in ('-q','--quiet'): quiet=1 +if (not abort and not listqueue and not dialstring): + usage("No usable command given.") +for i in dialstring: + if ((i>'9' or i<'0') and i not in ('+')): + usage("Invalid dialstring given.") + +if (dialstring and len(args)==0): + usage("No fax files given") + +# test if this user is allowed to send faxes +config=cs_helpers.readConfig() +user=pwd.getpwuid(os.getuid())[0] +if (not config.has_section(user)): + print "Sorry, you're no valid user for CapiSuite" + sys.exit(1) +if (not config.has_option(user,"fax_numbers")): + print "Sorry, you're not allowed to use fax services" + sys.exit(1) + +# test environment +sendq=config.get("GLOBAL","fax_user_dir")+user+"/sendq/" +if (not os.access(sendq,os.W_OK)): + print "can't write to queue dir" + sys.exit(1) + +if (listqueue): + showlist(config,user) + +if (abort): + abortjob(config,user,abort) + +# convert and enqueue files +for i in args: + if (not os.access(i,os.R_OK)): + sys.stderr.write("can't open "+i+'\n') + continue + t=os.popen("file -b -i "+i+" 2>/dev/null") + filetype=t.read() + if (t.close()): + usage("can't execute \"file\"") + if (not re.search("application/postscript",filetype)): + sys.stderr.write(i+" is not a PostScript file\n") + continue + + newname=cs_helpers.uniqueName(sendq,"fax","sff") + + ret=(os.system("gs -dNOPAUSE -dQUIET -dBATCH -sDEVICE=cfax -sOutputFile="+newname+" "+i))>>8 + if (ret): + sys.stderr.write("error during SFF-conversion at file "+i+'. Ghostscript not installed?\n') + sys.exit() + + cs_helpers.writeDescription(newname,"dialstring=\""+dialstring+"\"\n" + +"starttime=\""+time.ctime()+"\"\ntries=\"0\"\n" + +"user=\""+user+"\"\n") + print i,"successful enqueued as",newname,"for",dialstring + + + + + + + + + + + + diff --git a/scripts/cs_helpers.pyin b/scripts/cs_helpers.pyin new file mode 100644 index 0000000..fc8dc6f --- /dev/null +++ b/scripts/cs_helpers.pyin @@ -0,0 +1,340 @@ +# cs_helpers.py - some helper functions for CapiSuite scripts +# ----------------------------------------------------------- +# copyright : (C) 2002 by Gernot Hillier +# email : gernot@hillier.de +# version : $Revision: 1.1 $ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# the name of the config file read by the scripts; see there for options and +# descriptions +configfile_fax="@pkgsysconfdir@/fax.conf" +configfile_voice="@pkgsysconfdir@/answering_machine.conf" + +# @brief read configuration file and return a ConfigParser object +# +# The configfile is read from the path given above and the surrounding +# quotation marks from the values are removed +# +# @return the constructed config file object +def readConfig(file=""): + import ConfigParser + config=ConfigParser.ConfigParser() + if (file==""): + config.readfp(open(configfile_fax)) + config.readfp(open(configfile_voice)) + else: + config.readfp(open(file)) + for s in config.sections(): + for o in config.options(s): + value=config.get(s,o) + if (len(value)>1 and value[0]=='"'): + config.set(s,o,value[1:-1]) + if (not config.has_section('GLOBAL')): + raise IOError("invalid configuration file - section GLOBAL is missing") + return config + +# @brief get an option from the user or global section +# +# The option is searched in the users section and if not found +# in the global section. +# +# @param config the ConfigParser object containing the values +# @param user the name of the user section to use +# @param option the name of the option to search for +# +# @return the value for this option or None if it's not found +def getOption(config,user,option): + if config.has_option(user,option): + return config.get(user,option) + elif config.has_option('GLOBAL',option): + return config.get('GLOBAL',option) + else: + return None + +# @brief Search for an audio file first in user_dir, than in audio_dir +# +# @param config the ConfigParser object containing the configuration +# @param user the name of the user +# @param filename the filename of the wave file +# +# @return the found file with full path +def getAudio(config,user,filename): + import os + systemdir=config.get('GLOBAL','audio_dir') + userdir=config.get('GLOBAL','voice_user_dir')+user+"/" + if (config.getint('GLOBAL','user_audio_files') and os.access(userdir+filename,os.R_OK)): + return userdir+filename + else: + return systemdir+filename + +# @brief thread-safe creation of a unique filename in a directory +# +# This function reads the nextnumber from then "nextnr"-file in the given +# directory and updates it. It holds the next free file number. +# +# If nextnr doesn't exist, it's created. +# +# The filenames created will have the format +# +# basename-number.suffix +# +# @param directory name of the directory to work in +# @param basename the basename of the filename +# @param suffix the suffix of the filename (without ".") +# +# @return new file name +def uniqueName(directory,basename,suffix): + import fcntl,os,re + # acquire lock + lockfile=open(directory+"cs_lock","w") + fcntl.lockf(lockfile,fcntl.LOCK_EX) + + try: + countfile=open(directory+basename+"-nextnr","r") + nextnr=int(countfile.readline()) + countfile.close() + except IOError: + # search for next free sequence number + files=os.listdir(directory) + files=filter (lambda s: re.match(re.escape(basename)+"-.*\."+re.escape(suffix),s),files) + if (len(files)): + files=map(lambda s: int(s[len(basename)+1:-len(suffix)-1]),files) + nextnr=max(files)+1 # take nr of last file and increase it by one + else: + nextnr=0 + files.sort() + + newname=directory+basename+"-"+str(nextnr)+"."+suffix + + countfile=open(directory+basename+"-nextnr","w") + countfile.write(str(nextnr+1)+'\n') + countfile.close() + + # unlock + fcntl.lockf(lockfile,fcntl.LOCK_UN) + lockfile.close() + os.unlink(directory+"cs_lock") + return newname + +# @brief send email with text and attachment of type sff or la converted to pdf/wav +# +# This function creates a multipart MIME-message containing a text/plain +# part with a string and one attachment of type application/pdf or audio/wav. +# +# The given attachment is automatically converted from Structured Fax File +# (.sff) or inversed A-Law (.la) to the well known PDF or WAV format. +# +# @param mail_from the From: address for the mail +# @param mail_to the To: address for the mail +# @param mail_subject the subject of the mail +# @param mail_type containing either "sff" or "la" +# @param text a string containing the text of the first part of the mail +# @param attachment name of the file to send as attachment +def sendMIMEMail(mail_from,mail_to,mail_subject,mail_type,text,attachment): + import email.MIMEBase,email.MIMEText,email.MIMEAudio,email.Encoders,os,sys,popen2,capisuite + msg = email.MIMEBase.MIMEBase("multipart","mixed") + msg['Subject']=mail_subject + msg['From']=mail_from + msg['To']=mail_to + + msg.preamble = 'This is a Multipart-MIME-message. Please use a capable mailer.\n' + msg.epilogue = '' # To guarantee the message ends with a newline + + basename=attachment[:attachment.rindex('.')+1] + try: + if (mail_type=="sff"): + # sff -> tif + ret=os.spawnlp(os.P_WAIT,"sfftobmp","sfftobmp","-tif",attachment,basename+"tif") + if (ret or not os.access(basename+"tif",os.F_OK)): + raise "conv-error","Can't convert sff to tif. sfftobmp not installed?" + # tif -> ps -> pdf + tiff2ps=popen2.Popen3("tiff2ps -a "+basename+"tif") + if (tiff2ps.poll()!=-1): + raise "conv-error","Error while calling tiff2ps. Not installed?" + tiff2ps.tochild.close() # we don't need the input pipe + ps2pdf=popen2.Popen3("ps2pdf - -") + if (ps2pdf.poll()!=-1): + raise "conv-error","Error while calling ps2pdf. Not installed?\n" + ps2pdf.tochild.write(tiff2ps.fromchild.read()) + tiff2ps.fromchild.close() + ret=tiff2ps.wait() + if (ret!=0): + raise "conv-error","Error "+str(ret)+" occured during tiff2ps" + os.unlink(basename+"tif") + ps2pdf.tochild.close() # send EOF, so that it starts to convert + # create attachment with pdf stream + filepart = email.MIMEBase.MIMEBase("application","pdf") + filepart.add_payload(ps2pdf.fromchild.read()) + ps2pdf.fromchild.close() + ret=ps2pdf.wait() + if (ret!=0): + raise "conv-error","Error "+str(ret)+" occured during ps2pdf" + email.Encoders.encode_base64(filepart) + elif (mail_type=="la"): + # la -> wav + # don't use stdout as sox needs a file to be able to seek in it otherwise the header will be incomplete + ret = os.spawnlp(os.P_WAIT,"sox","sox",attachment,basename+"wav") + if (ret or not os.access(basename+"wav",os.R_OK)): + raise "conv-error","Error while calling sox. Not installed?" + filepart = email.MIMEAudio.MIMEAudio(open(basename+"wav").read(),"x-wav") + os.unlink(basename+"wav") + textpart = email.MIMEText.MIMEText(text) + msg.attach(textpart) + msg.attach(filepart) + except "conv-error",errormessage: + text+="\n\nERROR occured while converting file: "+errormessage+"\nPlease talk to your friendly administrator.\n" + textpart = email.MIMEText.MIMEText(text) + msg.attach(textpart) + + sendmail = popen2.Popen3("sendmail -f "+mail_from+" "+mail_to) + if (sendmail.poll()!=-1): + capisuite.error("Error while calling sendmail. Not installed?\n") + return + sendmail.tochild.write(msg.as_string()) + sendmail.tochild.close() + sendmail.fromchild.close() + ret=sendmail.wait() + if (ret!=0): + capisuite.error("Error while calling sendmail, return code="+str(ret)) + else: + capisuite.log("sendmail finished successful",3) + +# @brief send a simple text email +# +# This function creates a simple mail +# +# @param mail_from the From: address for the mail +# @param mail_to the To: address for the mail +# @param mail_subject the subject of the mail +# @param text a string containing the text of the first part of the mail +def sendSimpleMail(mail_from,mail_to,mail_subject,text): + import email.Encoders, email.MIMEText, popen2, sys,capisuite + # Create a text/plain message, using Quoted-Printable encoding for non-ASCII + # characters. + msg = email.MIMEText.MIMEText(text, _encoder=email.Encoders.encode_quopri) + + msg['Subject'] = mail_subject + msg['From'] = mail_from + msg['To'] = mail_to + + sendmail = popen2.Popen3("sendmail -f "+mail_from+" "+mail_to) + if (sendmail.poll()!=-1): + capisuite.error("Error while calling sendmail. Not installed?\n") + return + sendmail.tochild.write(msg.as_string()) + sendmail.tochild.close() + sendmail.fromchild.close() + ret=sendmail.wait() + if (ret!=0): + capisuite.error("Error while calling sendmail, return code="+str(ret)) + else: + capisuite.log("sendmail finished successful",3) + + +# @brief write description file for received fax or voice +# +# This function writes an INI-style description file for the given data file +# which can later on be read by a ConfigParser instance. The data file name +# is used, the extension stripped and replaced by .txt +# +# @param filename the data filename (with extension!) +# @param content the content as string +def writeDescription(filename,content): + descr=open(filename[:filename.rindex('.')+1]+"txt","w") + descr.write("# Description file for "+filename+"\n") + descr.write("# This if for internal use of CapiSuite.\n") + descr.write("# Only change if you know what you do!!\n") + descr.write("[GLOBAL]\n") + descr.write("filename=\""+filename+"\"\n") + descr.write(content) + descr.close() + +# @brief say a german number +# +# All numbers from 0 to 99 are said correctly, while all larger ones are +# split into numbers and only the numbers are said one after another +# +# @param call reference to the call +# @param number the number to say +# @param curr_user the current user named +# @param config the ConfigParser instance holding the configuration info +def sayNumber(call,number,curr_user,config): + import capisuite + if (len(number)==2 and number[0]!="0"): + if (number[0]=="1"): + if (number[1]=="0"): + capisuite.audio_send(call,getAudio(config,curr_user,"10.la"),1) + elif (number[1]=="1"): + capisuite.audio_send(call,getAudio(config,curr_user,"11.la"),1) + elif (number[1]=="2"): + capisuite.audio_send(call,getAudio(config,curr_user,"12.la"),1) + elif (number[1]=="3"): + capisuite.audio_send(call,getAudio(config,curr_user,"13.la"),1) + elif (number[1]=="4"): + capisuite.audio_send(call,getAudio(config,curr_user,"14.la"),1) + elif (number[1]=="5"): + capisuite.audio_send(call,getAudio(config,curr_user,"15.la"),1) + elif (number[1]=="6"): + capisuite.audio_send(call,getAudio(config,curr_user,"16.la"),1) + elif (number[1]=="7"): + capisuite.audio_send(call,getAudio(config,curr_user,"17.la"),1) + elif (number[1]=="8"): + capisuite.audio_send(call,getAudio(config,curr_user,"18.la"),1) + elif (number[1]=="9"): + capisuite.audio_send(call,getAudio(config,curr_user,"19.la"),1) + else: + if (number[1]=="0"): + capisuite.audio_send(call,getAudio(config,curr_user,number+".la"),1) + elif (number[1]=="1"): + capisuite.audio_send(call,getAudio(config,curr_user,"ein.la"),1) + capisuite.audio_send(call,getAudio(config,curr_user,"und.la"),1) + capisuite.audio_send(call,getAudio(config,curr_user,number[0]+"0.la"),1) + else: + capisuite.audio_send(call,getAudio(config,curr_user,number[1]+".la"),1) + capisuite.audio_send(call,getAudio(config,curr_user,"und.la"),1) + capisuite.audio_send(call,getAudio(config,curr_user,number[0]+"0.la"),1) + else: + for i in number: + capisuite.audio_send(call,getAudio(config,curr_user,i+".la"),1) + +# $Log: cs_helpers.pyin,v $ +# Revision 1.1 2003/02/19 08:19:54 gernot +# Initial revision +# +# Revision 1.8 2003/02/10 14:03:34 ghillie +# - cosmetical fixes in sendMIMEMail +# - added wait() calls to popen objects, otherwise processes will hang +# after CapiSuite has run them (i.e. sendmail stays as Zombie) +# +# Revision 1.7 2003/02/03 14:47:49 ghillie +# - sayNumber now works correctly for all numbers between 0 and 99 +# (in german). Added the necessary voice files and improved "1"-"9" +# +# Revision 1.6 2003/01/27 21:55:10 ghillie +# - getOption returns now None if option isn't found at all (no exception) +# - removed capisuite.log from uniqueName() (not possible in capisuitefax!) +# - added some missing "import capisuite" statements +# +# Revision 1.5 2003/01/27 19:24:29 ghillie +# - updated to use new configuration files for fax & answering machine +# +# Revision 1.4 2003/01/19 12:02:40 ghillie +# - use capisuite log functions instead of stdout/stderr +# +# Revision 1.3 2003/01/17 15:08:17 ghillie +# - typos as usual... +# - added sendSimpleMail for normal text messages +# +# Revision 1.2 2003/01/15 15:52:49 ghillie +# - readConfig now takes filename as parameter +# - uniqueName: countfile now has basename as prefix, fixed small bug +# in countfile creation +# - sendMail: added .la->.wav convertion, error messages now included +# in messages to user +# - writeDescription: [data] renamed to [global] +# - sayNumber: small fixes +# diff --git a/scripts/fax.confin b/scripts/fax.confin new file mode 100644 index 0000000..0570690 --- /dev/null +++ b/scripts/fax.confin @@ -0,0 +1,142 @@ +# $Id: fax.confin,v 1.1 2003/02/19 08:19:54 gernot Exp $ +# +# This is the fax configuration file for the scripts distributed with CapiSuite +# +# It is read by the scripts which are distributed with CapiSuite (incoming.py, +# idle.py and capisuitefax). If you don't want to use these scripts and develop +# your completely own application, you won't need it! CapiSuite itself (the +# daemon) doesn't read it. +# +# For a further description, please see the CapiSuite documentation - there's a +# part describing the scripts and this config file. +# +# As usual, lines starting with # or empty lines will be ignored +# +# The rest must be key value pairs written as key=value or section names. +# +# Additional whitespaces and quotation marks (") surrounding +# the values will be ignored. +# +# The file is split in sections starting with "[sectionname]". The section +# [GLOBAL] contains all options common for all users. For each different user, +# an own section is used which must at least contain "fax_numbers" +# +# Nearly all options are available in the [GLOBAL] section and +# the user sections. The defaults from the global section can be overwritten in +# [user]-sections. + +############################################################################### +############################ global settings ################################## +############################################################################### + +[GLOBAL] + +# Directory where idle.py will save its data. There must exist two +# subdirectories: +# +# spool_dir/done - a successful delivered job is moved here +# spool_dir/failed - jobs which have finally failed live here +spool_dir="@spooldir@/" + +# Directory for all user-specific data. Contains one subdirectory +# for each user (named like his userid). The following directory tree is used below: +# +# user_dir/username/received - all received calls (voice and fax) will be saved here +# user_dir/username/sendq - the files to send will be queued here +fax_user_dir="@spooldir@/users/" + +# send_tries +# +# Number of tries for sending a fax document. After completing the +# given number of tries, the document will considered as failed. +send_tries="10" + +# send_delays +# +# Delays in seconds between the send_tries. The different values are separated +# by commas. The first value gives the delay between the first and the second +# try and so on. The list should have send_tries-1 values. If some values are +# missing, the last value will be used for all subsequent tries. Superfluous +# values will be ignored. +send_delays="60,60,60,300,300,3600,3600,18000,36000" + +# send_controller +# +# This value defines which one of the installed controllers will be used for +# sending faxes. All controllers are numbered beginning with "1". If you +# have only one controller installed, leave this value alone. Unfortunately, +# there's only one send_controller supported currently. +send_controller="1" + +# outgoing_MSN +# +# The MSN (number) to use for outgoing calls. You can also leave this empty. +# Then default MSN of your ISDN interface will be used automatically. Will +# be overwritten by the first entry of fax_numbers if set. +outgoing_MSN="" + +# outgoing_timeout +# +# This value decides how long to wait for a successful connection if the other +# party doesn't answer the call at once. +outgoing_timeout="60" + +# fax_stationID +# +# This is the default for the fax station ID (fax number send to the other +# party). It must only contain the following characters ,'+','0'..'9'. +# The maximal length is 20 chars. +fax_stationID="+49 000 0000" + +# fax_headline +# +# This is the default for the fax headline. There's no definitive length +# constraint given by the CAPI specification, so it may be dependent on +# the driver you use. Just use a reasonable short string. +fax_headline="Fax sent by CapiSuite (http://www.capisuite.de)" + +############################################################################### +############################# user settings ################################### +############################################################################### + +# The following sections start with the name of the users which want to use +# CapiSuite. The names must be exactly equal to system users. +# +# Each user section can override the following default options given above: +# +# outgoing_MSN, outgoing_timeout, fax_stationID, fax_headline +# +# Additionally, the following options are possible: +# +# fax_numbers=",, ..." +# A list containing the numbers on which this user wants to receive incoming +# fax calls. The values are separated by commas. The first number is also +# used as our own number for outgoing calls. This overrides outgoing_MSN. +# You can also use the special entry "*" which stands for accepting ALL +# incoming calls as fax (use with care!) +# +# fax_email="name@domain.de" +# If given, this string indicates an email-address where the received faxes +# and voice calls will be sent to. If it is empty, the recorded calls and +# faxes will be sent to the user on the current system. It's also used to +# send status reports for sent fax jobs to. If you don't want to get emails, +# see the "action" option below +# +# fax_action="" +# Here you can define what action will be taken when a fax is received. +# Currently, three possible actions are supported: +# +# MailAndSave - the received call will be mailed to the given address (see +# "email" above) and saved to the user_dir. +# SaveOnly - the fax will be only saved to the user_dir +# +# Here's an example of a valid user configuration for "gernot" - just remove +# the leading #-signs and edit it: +# +# [gernot] +# fax_numbers="11,12" +# fax_stationID="+49 89 123456" +# fax_headline="Gernot Hillier - sent by CapiSuite" +# fax_email="" # sent to gernot@localhost +# fax_action="MailAndSave" + diff --git a/scripts/idle.py b/scripts/idle.py new file mode 100644 index 0000000..ee748d0 --- /dev/null +++ b/scripts/idle.py @@ -0,0 +1,187 @@ +# idle.py - default script for capisuite +# --------------------------------------------- +# copyright : (C) 2002 by Gernot Hillier +# email : gernot@hillier.de +# version : $Revision: 1.1 $ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + +import os,re,time,pwd,fcntl +# capisuite stuff +import capisuite,cs_helpers + +def idle(capi): + config=cs_helpers.readConfig() + done=config.get('GLOBAL','spool_dir')+"done/" + failed=config.get('GLOBAL','spool_dir')+"failed/" + if (not os.access(done,os.W_OK) or not os.access(failed,os.W_OK)): + raise "Can't read/write to the necessary spool dirs" + + userlist=config.sections() + userlist.remove('GLOBAL') + + for user in userlist: # search in all user-specified sendq's + userdata=pwd.getpwnam(user) + if (not config.has_option(user,"fax_numbers")): + continue + + udir=config.get("GLOBAL","fax_user_dir")+user+"/" + sendq=udir+"sendq/" + if (not os.access(udir,os.F_OK)): + os.mkdir(udir) + os.chown(udir,userdata[2],userdata[3]) + if (not os.access(sendq,os.F_OK)): + os.mkdir(sendq) + os.chown(sendq,userdata[2],userdata[3]) + + files=os.listdir(sendq) + files=filter (lambda s: re.match("fax-.*\.txt",s),files) + + for job in files: + job_fax=job[:-3]+"sff" + real_user_c=os.stat(sendq+job).st_uid + real_user_j=os.stat(sendq+job_fax).st_uid + if (real_user_j!=pwd.getpwnam(user)[2] or real_user_c!=pwd.getpwnam(user)[2]): + capisuite.error("job "+sendq+job_fax+" seems to be manipulated (wrong uid)! Ignoring...") + fcntl.lockf(lockfile,fcntl.LOCK_UN) + lockfile.close() + os.unlink(sendq+job[:-3]+"lock") + continue + + lockfile=open(sendq+job[:-3]+"lock","w") + # read directory contents + fcntl.lockf(lockfile,fcntl.LOCK_EX) # lock so that it isn't deleted while sending + + if (not os.access(sendq+job,os.W_OK)): # perhaps it was cancelled? + fcntl.lockf(lockfile,fcntl.LOCK_UN) + lockfile.close() + os.unlink(sendq+job[:-3]+"lock") + continue + + control=cs_helpers.readConfig(sendq+job) + starttime=time.mktime(time.strptime(control.get("GLOBAL","starttime"))) + if (starttime>time.time()): + fcntl.lockf(lockfile,fcntl.LOCK_UN) + lockfile.close() + os.unlink(sendq+job[:-3]+"lock") + continue + + tries=control.getint("GLOBAL","tries") + dialstring=control.get("GLOBAL","dialstring") + mailaddress=cs_helpers.getOption(config,user,"fax_email") + if (mailaddress=="" or mailaddress==None): + mailaddress=user + + capisuite.log("job "+job_fax+" from "+user+" to "+dialstring+" initiated",1) + result,resultB3 = sendfax(capi,sendq+job_fax,dialstring,user,config) + + capisuite.log("job "+job_fax+": result was %x,%x" % (result,resultB3),1) + + if (result in (0,0x3400,0x3480,0x3490) and resultB3==0): + movejob(job_fax,sendq,done,user) + capisuite.log("job "+job_fax+": finished successfully",1) + cs_helpers.sendSimpleMail(user,mailaddress,"Fax to "+dialstring+" sent successfully.", + "Your fax job to "+dialstring+" was sent successfully.\n\n" + +"Filename: "+job_fax+"\nNeeded tries: "+str(tries) + +("\nLast result: 0x%x/0x%x" % (result,resultB3)) + +"\n\nIt was moved to "+done+user+"-"+job_fax) + else: + max_tries=config.getint('GLOBAL','send_tries') + delays=config.get('GLOBAL','send_delays').split(",") + delays=map(int,delays) + if (tries=max_tries): + movejob(job_fax,sendq,failed,user) + capisuite.log("job "+job_fax+": failed finally",1) + cs_helpers.sendSimpleMail(user,mailaddress,"Fax to "+dialstring+" FAILED.", + "I'm sorry, but your fax job to "+dialstring+" failed finally.\n\n" + +"Filename: "+job_fax+"\nTries: "+str(tries) + +"\nLast result: 0x%x/0x%x" % (result,resultB3) + +"\n\nIt was moved to "+failed+user+"-"+job_fax) + + fcntl.lockf(lockfile,fcntl.LOCK_UN) + lockfile.close() + os.unlink(sendq+job[:-3]+"lock") + +def sendfax(capi,job,dialstring,user,config): + try: + outgoing_nr=cs_helpers.getOption(config,user,'outgoing_MSN') + if (outgoing_nr==""): + outgoing_nr=(config.get(user,'fax_numbers').split(','))[0] + (call,result)=capisuite.call_faxG3(capi,int(config.get('GLOBAL','send_controller')), + outgoing_nr, dialstring, int(cs_helpers.getOption(config,user,'outgoing_timeout')), + cs_helpers.getOption(config,user,'fax_stationID'), cs_helpers.getOption(config,user,'fax_headline')) + if (result!=0): + return(result,0) + capisuite.fax_send(call,job) + return(capisuite.disconnect(call)) + except capisuite.CallGoneError: + return(capisuite.disconnect(call)) + +def movejob(job,olddir,newdir,user): + os.rename(olddir+job,newdir+user+"-"+job) + os.rename(olddir+job[:-3]+"txt",newdir+user+"-"+job[:-3]+"txt") + +# +# History: +# +# $Log: idle.py,v $ +# Revision 1.1 2003/02/19 08:19:54 gernot +# Initial revision +# +# Revision 1.12 2003/02/18 09:54:22 ghillie +# - added missing lockfile deletions, corrected locking protocol +# -> fixes Bugzilla 23731 +# +# Revision 1.11 2003/02/17 16:48:43 ghillie +# - do locking, so that jobs can be deleted +# +# Revision 1.10 2003/02/10 14:50:52 ghillie +# - revert logic of outgoing_MSN: it's overriding the first number of +# fax_numbers now +# +# Revision 1.9 2003/02/05 15:59:11 ghillie +# - search for *.txt instead of *.sff so no *.sff which is currently created +# by capisuitefax will be found! +# +# Revision 1.8 2003/01/31 11:22:00 ghillie +# - use different sendq's for each user (in his user_dir). +# - use prefix user- for names in done and failed +# +# Revision 1.7 2003/01/27 21:56:46 ghillie +# - mailaddress may be not set, that's the same as "" +# - use first entry of fax_numbers as outgoing MSN if it exists +# +# Revision 1.6 2003/01/27 19:24:29 ghillie +# - updated to use new configuration files for fax & answering machine +# +# Revision 1.5 2003/01/19 12:03:27 ghillie +# - use capisuite log functions instead of stdout/stderr +# +# Revision 1.4 2003/01/17 15:09:26 ghillie +# - updated to use new configuration file capisuite-script.conf +# +# Revision 1.3 2003/01/13 16:12:00 ghillie +# - renamed from idle.pyin to idle.py as all previously processed variables +# stay in the config file and cs_helpers.pyin now +# +# Revision 1.2 2002/12/16 13:07:22 ghillie +# - finished queue processing +# +# Revision 1.1 2002/12/14 13:53:19 ghillie +# - idle.py and incoming.py are now auto-created from *.pyin +# + diff --git a/scripts/incoming.py b/scripts/incoming.py new file mode 100644 index 0000000..9b87dd9 --- /dev/null +++ b/scripts/incoming.py @@ -0,0 +1,464 @@ +# incoming.py - standard incoming script for capisuite +# ---------------------------------------------------- +# copyright : (C) 2002 by Gernot Hillier +# email : gernot@hillier.de +# version : $Revision: 1.1 $ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + +# general imports +import time,os,re,string,pwd +# CapiSuite imports +import capisuite,cs_helpers + +# @brief main function called by CapiSuite when an incoming call is received +# +# It will decide if this call should be accepted, with which service and for +# which user. The real call handling is done in faxIncoming and voiceIncoming. +# +# @param call reference to the call. Needed by all capisuite functions +# @param service one of SERVICE_FAXG3, SERVICE_VOICE, SERVICE_OTHER +# @param call_from string containing the number of the calling party +# @param call_to string containing the number of the called party +def callIncoming(call,service,call_from,call_to): + # read config file and search for call_to in the user sections + try: + config=cs_helpers.readConfig() + userlist=config.sections() + userlist.remove('GLOBAL') + curr_user="" + + for u in userlist: + if config.has_option(u,'voice_numbers'): + numbers=config.get(u,'voice_numbers') + if (call_to in numbers.split(',') or numbers=="*"): + if (service==capisuite.SERVICE_VOICE): + curr_user=u + curr_service=capisuite.SERVICE_VOICE + break + if (service==capisuite.SERVICE_FAXG3): + curr_user=u + curr_service=capisuite.SERVICE_FAXG3 + break + + if config.has_option(u,'fax_numbers'): + numbers=config.get(u,'fax_numbers') + if (call_to in numbers.split(',') or numbers=="*"): + if (service in (capisuite.SERVICE_FAXG3,capisuite.SERVICE_VOICE)): + curr_user=u + curr_service=capisuite.SERVICE_FAXG3 + break + + except IOError,e: + capisuite.error("Error occured during config file reading: "+e+" Disconnecting...") + capisuite.reject(call,0x34A9) + return + # setuid to the user and answer the call with the right service + if (curr_user==""): + capisuite.log("call from "+call_from+" to "+call_to+" ignoring",1,call) + capisuite.reject(call,1) + return + try: + try: + userdata=pwd.getpwnam(curr_user) + if (curr_service==capisuite.SERVICE_VOICE): + udir=config.get("GLOBAL","voice_user_dir")+curr_user+"/" + elif (curr_service==capisuite.SERVICE_FAXG3): + udir=config.get("GLOBAL","fax_user_dir")+curr_user+"/" + if (not os.access(udir,os.F_OK)): + os.mkdir(udir) + os.chown(udir,userdata[2],userdata[3]) + if (not os.access(udir+"received/",os.F_OK)): + os.mkdir(udir+"received/") + os.chown(udir+"received/",userdata[2],userdata[3]) + os.setuid(userdata[2]) + except KeyError: + capisuite.error("user "+curr_user+" is not a valid system user. Disconnecting",call) + capisuite.reject(call,0x34A9) + return + if (curr_service==capisuite.SERVICE_VOICE): + capisuite.log("call from "+call_from+" to "+call_to+" for "+curr_user+" connecting with voice",1,call) + capisuite.connect_voice(call,int(cs_helpers.getOption(config,curr_user,"voice_delay"))) + voiceIncoming(call,call_from,call_to,curr_user,config) + elif (curr_service==capisuite.SERVICE_FAXG3): + capisuite.log("call from "+call_from+" to "+call_to+" for "+curr_user+" connecting with fax",1,call) + capisuite.connect_faxG3(call,cs_helpers.getOption(config,curr_user,"fax_stationID"),cs_helpers.getOption(config,curr_user,"fax_headline"),0) + faxIncoming(call,call_from,call_to,curr_user,config) + except capisuite.CallGoneError: # catch exceptions from connect_* + (cause,causeB3)=capisuite.disconnect(call) + capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) + +# @brief called by callIncoming when an incoming fax call is received +# +# @param call reference to the call. Needed by all capisuite functions +# @param call_from string containing the number of the calling party +# @param call_to string containing the number of the called party +# @param curr_user name of the user who is responsible for this +# @param config ConfigParser instance holding the config data +def faxIncoming(call,call_from,call_to,curr_user,config): + filename=cs_helpers.uniqueName(config.get("GLOBAL","fax_user_dir")+curr_user+"/received/","fax","sff") + try: + capisuite.fax_receive(call,filename) + (cause,causeB3)=capisuite.disconnect(call) + capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call) + + except capisuite.CallGoneError: # catch this here to get the cause info in the mail + (cause,causeB3)=capisuite.disconnect(call) + capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) + + if (os.access(filename,os.R_OK)): + cs_helpers.writeDescription(filename, + "call_from=\""+call_from+"\"\ncall_to=\""+call_to+"\"\ntime=\"" + +time.ctime()+"\"\ncause=\"0x%x/0x%x\"\n" % (cause,causeB3)) + + mailaddress=cs_helpers.getOption(config,curr_user,"fax_email") + if (mailaddress=="" or mailaddress==None): + mailaddress=curr_user + if (cs_helpers.getOption(config,curr_user,"fax_action").lower()=="mailandsave"): + cs_helpers.sendMIMEMail(curr_user, mailaddress, "Fax received from "+call_from+" to "+call_to, "sff", + "You got a fax from "+call_from+" to "+call_to+"\nDate: "+time.ctime()+"\n\n" + +"See attached file.\nThe original file was saved to "+filename+"\n\n", filename) + +# @brief called by callIncoming when an incoming voice call is received +# +# @param call reference to the call. Needed by all capisuite functions +# @param call_from string containing the number of the calling party +# @param call_to string containing the number of the called party +# @param curr_user name of the user who is responsible for this +# @param config ConfigParser instance holding the config data +def voiceIncoming(call,call_from,call_to,curr_user,config): + userdir=config.get("GLOBAL","voice_user_dir")+curr_user+"/" + filename=cs_helpers.uniqueName(userdir+"received/","voice","la") + try: + capisuite.enable_DTMF(call) + userannouncement=userdir+cs_helpers.getOption(config,curr_user,"announcement") + pin=cs_helpers.getOption(config,curr_user,"pin") + if (os.access(userannouncement,os.R_OK)): + capisuite.audio_send(call,userannouncement,1) + else: + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"anrufbeantworter-von.la"),1) + cs_helpers.sayNumber(call,call_to,curr_user,config) + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-nachricht.la"),1) + + if (cs_helpers.getOption(config,curr_user,"voice_action").lower()!="none"): + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la"),1) + capisuite.audio_receive(call,filename,int(cs_helpers.getOption(config,curr_user,"record_length")), int(cs_helpers.getOption(config,curr_user,"record_silence_timeout")),1) + + dtmf_list=capisuite.read_DTMF(call,0) + if (dtmf_list=="X"): + if (os.access(filename,os.R_OK)): + os.unlink(filename) + capisuite.switch_to_faxG3(call,cs_helpers.getOption(config,curr_user,"fax_stationID"),cs_helpers.getOption(config,curr_user,"fax_headline")) + faxIncoming(call,call_from,call_to,curr_user,config) + elif (dtmf_list!="" and pin!=""): + dtmf_list+=capisuite.read_DTMF(call,3) # wait 5 seconds for input + count=1 + while (count<3 and pin!=dtmf_list): # try again if input was wrong + capisuite.log("wrong PIN entered...",1,call) + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) + dtmf_list=capisuite.read_DTMF(call,3) + count+=1 + if (pin==dtmf_list): + if (os.access(filename,os.R_OK)): + os.unlink(filename) + capisuite.log("Starting remote inquiry...",1,call) + remoteInquiry(call,userdir,curr_user,config) + + (cause,causeB3)=capisuite.disconnect(call) + capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call) + + except capisuite.CallGoneError: # catch this here to get the cause info in the mail + (cause,causeB3)=capisuite.disconnect(call) + capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) + + if (os.access(filename,os.R_OK)): + cs_helpers.writeDescription(filename, + "call_from=\""+call_from+"\"\ncall_to=\""+call_to+"\"\ntime=\"" + +time.ctime()+"\"\ncause=\"0x%x/0x%x\"\n" % (cause,causeB3)) + + mailaddress=cs_helpers.getOption(config,curr_user,"voice_email") + if (mailaddress=="" or mailaddress==None): + mailaddress=curr_user + if (cs_helpers.getOption(config,curr_user,"voice_action").lower()=="mailandsave"): + cs_helpers.sendMIMEMail(curr_user, mailaddress, "Voice call received from "+call_from+" to "+call_to, "la", + "You got a voice call from "+call_from+" to "+call_to+"\nDate: "+time.ctime()+"\n\n" + +"See attached file.\nThe original file was saved to "+filename+"\n\n", filename) + + +# @brief remote inquiry function (uses german wave snippets!) +# +# commands for remote inquiry +# delete message - 1 +# next message - 4 +# last message - 5 +# repeat current message - 6 +# +# @param call reference to the call. Needed by all capisuite functions +# @param userdir spool_dir of the current_user +# @param curr_user name of the user who is responsible for this +# @param config ConfigParser instance holding the config data +def remoteInquiry(call,userdir,curr_user,config): + import time,fcntl,errno,os + # acquire lock + lockfile=open(userdir+"received/inquiry_lock","w") + try: + try: + # read directory contents + fcntl.lockf(lockfile,fcntl.LOCK_EX | fcntl.LOCK_NB) # only one inquiry at a time! + + messages=os.listdir(userdir+"received/") + messages=filter (lambda s: re.match("voice-.*\.la",s),messages) # only use voice-* files + messages=map(lambda s: int(re.match("voice-([0-9]+)\.la",s).group(1)),messages) # filter out numbers + messages.sort() + + # read the number of the message heard last at the last inquiry + lastinquiry=-1 + if (os.access(userdir+"received/last_inquiry",os.W_OK)): + lastfile=open(userdir+"received/last_inquiry","r") + lastinquiry=int(lastfile.readline()) + lastfile.close() + print lastinquiry + + # sort out old messages + oldmessages=[] + i=0 + while (ilastinquiry): + lastinquiry=curr_msgs[i] + lastfile=open(userdir+"received/last_inquiry","w") + lastfile.write(str(curr_msgs[i])+"\n") + lastfile.close() + i+=1 + elif (cmd=="5"): + i-=1 + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"keine-weiteren-nachrichten.la")) + + except IOError,err: + if (err.errno in (errno.EACCES,errno.EAGAIN)): + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fernabfrage-aktiv.la")) + finally: + # unlock + fcntl.lockf(lockfile,fcntl.LOCK_UN) + lockfile.close() + os.unlink(userdir+"received/inquiry_lock") + +# @brief remote inquiry: record new announcement (uses german wave snippets!) +# +# @param call reference to the call. Needed by all capisuite functions +# @param userdir spool_dir of the current_user +# @param curr_user name of the user who is responsible for this +# @param config ConfigParser instance holding the config data +def newAnnouncement(call,userdir,curr_user,config): + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-komplett.la")) + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) + cmd="" + while (cmd!="1"): + capisuite.audio_receive(call,userdir+"announcement-tmp.la",60,3) + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-ansage-lautet.la")) + capisuite.audio_send(call,userdir+"announcement-tmp.la") + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"wenn-einverstanden-1.la")) + cmd=capisuite.read_DTMF(call,0,1) + if (cmd!="1"): + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-kurz.la")) + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) + userannouncement=userdir+cs_helpers.getOption(config,curr_user,"announcement") + os.rename(userdir+"announcement-tmp.la",userannouncement) + capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"ansage-gespeichert.la")) + +# +# History: +# +# $Log: incoming.py,v $ +# Revision 1.1 2003/02/19 08:19:54 gernot +# Initial revision +# +# Revision 1.11 2003/02/17 11:13:43 ghillie +# - remoteinquiry supports new and old messages now +# +# Revision 1.10 2003/02/03 14:50:08 ghillie +# - fixed small typo +# +# Revision 1.9 2003/01/31 16:32:41 ghillie +# - support "*" for "all numbers" +# - automatic switch voice->fax when SI says fax +# +# Revision 1.8 2003/01/31 11:24:41 ghillie +# - wrong user handling for more than one users fixed +# - creates user_dir/user and user_dir/user/received now separately as +# idle.py can also create user_dir/user now +# +# Revision 1.7 2003/01/27 21:57:54 ghillie +# - fax_numbers and voice_numbers may not exist (no fatal error any more) +# - accept missing email option +# - fixed typo +# +# Revision 1.6 2003/01/27 19:24:29 ghillie +# - updated to use new configuration files for fax & answering machine +# +# Revision 1.5 2003/01/19 12:03:27 ghillie +# - use capisuite log functions instead of stdout/stderr +# +# Revision 1.4 2003/01/17 15:09:49 ghillie +# - cs_helpers.sendMail was renamed to sendMIMEMail +# +# Revision 1.3 2003/01/16 12:58:34 ghillie +# - changed DTMF timeout for pin to 3 seconds +# - delete recorded wave if fax or remote inquiry is recognized +# - updates in remoteInquiry: added menu for recording own announcement +# - fixed some typos +# - remoteInquiry: delete description file together with call if requested +# - new function: newAnnouncement +# +# Revision 1.2 2003/01/15 15:55:12 ghillie +# - added exception handler in callIncoming +# - faxIncoming: small typo corrected +# - voiceIncoming & remoteInquiry: updated to new config file system +# +# Revision 1.1 2003/01/13 16:12:58 ghillie +# - renamed from incoming.pyin to incoming.py as all previously processed +# variables are moved to config and cs_helpers.pyin +# +# Revision 1.4 2002/12/18 14:34:56 ghillie +# - added some informational prints +# - accept voice calls to fax nr +# +# Revision 1.3 2002/12/16 15:04:51 ghillie +# - added missing path prefix to delete routing in remote inquiry +# +# Revision 1.2 2002/12/16 13:09:25 ghillie +# - added some comments about the conf_* vars +# - added conf_wavedir +# - added support for B3 cause now returned by disconnect() +# - corrected some dir entries to work in installed system +# +# Revision 1.1 2002/12/14 13:53:18 ghillie +# - idle.py and incoming.py are now auto-created from *.pyin +# +# Revision 1.4 2002/12/11 12:58:05 ghillie +# - read return value from disconnect() +# - added disconnect() to exception handler +# +# Revision 1.3 2002/12/09 15:18:35 ghillie +# - added disconnect() in exception handler +# +# Revision 1.2 2002/12/02 21:30:42 ghillie +# fixed some minor typos +# +# Revision 1.1 2002/12/02 21:15:55 ghillie +# - moved scripts to own directory +# - added remote-connect script to repository +# +# Revision 1.20 2002/12/02 20:59:44 ghillie +# another typo :-| +# +# Revision 1.19 2002/12/02 20:54:07 ghillie +# fixed small typo +# +# Revision 1.18 2002/12/02 16:51:32 ghillie +# nearly complete new script, supports answering machine, fax receiving and remote inquiry now +# +# Revision 1.17 2002/11/29 16:28:43 ghillie +# - updated syntax (connect_telephony -> connect_voice) +# +# Revision 1.16 2002/11/29 11:09:04 ghillie +# renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) +# +# Revision 1.15 2002/11/25 11:43:43 ghillie +# updated to new syntax +# +# Revision 1.14 2002/11/23 16:16:17 ghillie +# moved switch2fax after audio_receive() +# +# Revision 1.13 2002/11/22 15:48:58 ghillie +# renamed pcallcontrol module to capicom +# +# Revision 1.12 2002/11/22 15:02:39 ghillie +# - added automatic switch between speech and fax +# - some comments added +# +# Revision 1.11 2002/11/19 15:57:18 ghillie +# - Added missing throw() declarations +# - phew. Added error handling. All exceptions are caught now. +# +# Revision 1.10 2002/11/18 12:32:36 ghillie +# - callIncoming lives now in __main__, not necessarily in pcallcontrol any more +# - added some comments and header +# diff --git a/scripts/remote-connect.py b/scripts/remote-connect.py new file mode 100644 index 0000000..7c81327 --- /dev/null +++ b/scripts/remote-connect.py @@ -0,0 +1,59 @@ +import string,os,time +import pcallcontrol +cc=pcallcontrol + +def callWaiting(CIP,callingParty,calledParty): + if (calledParty=="23"): + print "nehme Anruf von",callingParty,"an",calledParty,"an." + cc.connect(16) # 16 = telephony + else: + print "nehme Anruf von",callingParty,"an",calledParty,"nicht an." + cc.reject(2) # 2 = normal call clearing + +def callConnected(): + try: + cc.enableDTMF() + cc.audio_send("send.la") + code=cc.getDTMF() + print "Bekommen habe ich",code + if code=="3008": + # establish connection + start_t=time.time() + os.spawnl(os.P_NOWAIT,"ping","ping","-w","1","www.hillier.de") + c_status="" + # wait 30 secs for connect + while ((c_status!="CONNECTED") and (time.time()^ž~Þ¾n²z*cïÏߧ'_ߟ¯³kJ¢òRnN>~Þ>ŽzêËÿ¯Ç¿g§ïoºZNFÆvöFæÎrã/_Ç÷ש)W÷wGߟï#Zî¾öV(hˆè訖†N2›Ï§Çw©©W—Çg›£Šº+ ‹;ïcc›Êú¦¨è¨¨VFÞ®J“§7W)))©W—‡‡gsƒ{r¦¾†–Æ–Ö(VvÖöf~.Ês'GWWW©÷Ç'Oó»:º¢*Ê;3ªª +⦞FV(¨¨–†ËÏç·—©)©)©·Ç翯ûkb®Ž¦&¶6–Öö6&Îâësg·—W©W—·'Ïs«ò’.N®â‚«Ã³/Ã#ËRŽ&Æv¨VV¶&ŽÿçÇ—W©W×7ÇSOÛj R’~&~v¶vV–V6v¶Þ>2k“Ÿg·×Wש—Ç_osê¢òî" +úƒ³3«£¢Ž¾F¶V(Ö¨vÆÞÂêS_÷—©)©W—·çŸÏÓ»ë.¾¦fF¶6–V–v6Æf>îzã¯ßgGww—÷Ç'¿³;R2îŽ"z:{#³3›{º.þ†¶¨VÖö†ž2«S§·—×WWwGGg?ŸS»[Ò¾&¦vöö¨¨–FF^Î’êƒO§‡7————7ǧëîÒŽ2Zšk£ÛCë+ÊξæÆöÖ6öF^ÎK/§‡·w×—w7‡''ÿ³;¢zŽþž†6–öv6Ff¦Ž¢ Ó?çÇ7—÷GgŸs ŠR¾n>.zë[£#[«:òΦ†F6¶ÆfÞ.úo§gÇ7÷w÷ÇÇß?ŸSË£:.Ž&F6öv–F†žîb+C¿§gG7777ç?ƒ›ë®‚’>òËŠƒÓkºëZ>Þæ¶ö¶¦NÂK/Ÿ‡·÷——w7G‡çßSÃ#*"îΆ¶6vö66¦N2*³Oç··Çç_O¯»ÊârîÎÎŽ®rbzjË›[#;ëºZ2R.n.’šŠ»[“ÓÓ“3³Cƒã#[{;»Ë+jŠ:Zâ‚BòòÒ’r²BâZ:Š«»ãÃsÓ¯¯s3ƒÛ;k* +:zšÚšzúººº:ºjjjê*ªjŠjŠŠêª«+++««+kkëë Ë»û{ÛÛ[Û›û ëëë«+*ÊJJúZ¢bâ""Úš:º + +Êê«kK{Û#ƒÃc£ûK+j:Ú¢¢"âZzºÊj+‹Ë»K»»KK»»»»»;K ‹ëªŠŠJúzššzzzúúúzZJ#Ûêkë*jË‹Û#›k«ë+ªëËk ‹Ë‹ª***jêjJ +jj +ÊÊÊŠŠÊºÊjj*j +ªëë++ ‹Kûû;ËK;ûK+«‹Ë +jêŠ +:úJ +*jJººÊŠêjŠÊÊ +Šª+kK;û;;»û{»ËË k+jJJŠ +ÊJº:ººÊj«+«+«ª+ëk Ë ‹ËË‹k++«**jŠŠÊºº +**ÊŠÛCÛÛó»‹{ +Â2ÎÎî.Zëª;SÏ¿Ï/›{»j‚2òBÂ2ÂâÂBâÚZzúúúÚ¢¢:ë›sÿ_§_ÿÏS‹"®~f†††¦>î‚«c¯ÿ¿Ïï¯/Ó³ãã#»ë+*b²ÒînnŽîî.²šŠªk»›ãÃ[ûó¯SoOo/û«J2ž¦††æ¦ž>r«c“Ï¿OﯯsÛ»{‹*júÚÚ"bÂbÂ2Ò2ò2B2²ÂbZ+§§ç_Ï/à +RŽž&æ&æ&~RÚ*;ãS/¯S¯/ïo¯SSCc‹ëºB2²ÒÒ®.î.®r²BZ +ë«‹[££³#C#ƒ³{{ûŠb2®NNÎ>¾îRš«kÃÓ/O//¯sƒckËëŠºÊ +Z:úÚZâ‚₲BBrb+#ÃS?§§?ÓK"Rî~ÞÞ¦&ž®+ sï/ï¯/¯C“sC[ K *šÚ’®’²rr‚ššÊ +zºjê*kË»[óSï¿¿ÏOoƒ; +®î>ÞÞžN®jË[[Ã3/¿O¯ÓsÓ/‹‹Š"zÚšzbÂÚú¢""‚â‚bšúZâZª3o/OŸ?ÿß?¯C‹²ŽnþÞ^¦ŽRš KÛ³³cSÿ¿sóC“SS»»{úêêÊš2râb22Z2²¢â¢ššš +º +{C“ï¿?Ï¿ÿÏ/ÃKÂn~þþ~¾.Új«Ë㓯o¯o¯3ãc› ;«êŠ +úzú¢"Ú‚²¢²"¢Ú⚊K3¯?OÏ??ó#ºòR¾¾¾~~Nî‚ +‹{ÃÃï¯/ïï3ãã{{k«êJŠŠÊúÚ²bbâââb‚bZz + +Ê ïOoo/ó;zbŽÎŽNîBj{£ƒóÓ/o/SC[»‹»K» « +JJʺÚZââ"¢âZšúºŠjšº£s¯³¯ocËK‚ \ No newline at end of file diff --git a/scripts/waves/70.la b/scripts/waves/70.la new file mode 100644 index 0000000..bd36d05 Binary files /dev/null and b/scripts/waves/70.la differ diff --git a/scripts/waves/8.la b/scripts/waves/8.la new file mode 100644 index 0000000..47bbd47 Binary files /dev/null and b/scripts/waves/8.la differ diff --git a/scripts/waves/80.la b/scripts/waves/80.la new file mode 100644 index 0000000..95040f8 Binary files /dev/null and b/scripts/waves/80.la differ diff --git a/scripts/waves/9.la b/scripts/waves/9.la new file mode 100644 index 0000000..aadb1af Binary files /dev/null and b/scripts/waves/9.la differ diff --git a/scripts/waves/90.la b/scripts/waves/90.la new file mode 100644 index 0000000..f60e75e Binary files /dev/null and b/scripts/waves/90.la differ diff --git a/scripts/waves/Makefile.am b/scripts/waves/Makefile.am new file mode 100644 index 0000000..e067d85 --- /dev/null +++ b/scripts/waves/Makefile.am @@ -0,0 +1,12 @@ +dist_pkgdata_DATA = 0.la 1.la 2.la 3.la 4.la 5.la 6.la 7.la 8.la 9.la ..la am.la \ + von.la beep.la erklaerung.la fuer.la keine-weiteren-nachrichten.la \ + nachricht-gelöscht.la nachricht.la nachrichten.la neue-nachricht.la \ + neue-nachrichten.la uhr.la um.la anrufbeantworter-von.la \ + bitte-nachricht.la fuer-neue-ansage-9.la zum-abhoeren-1.la \ + bitte-neue-ansage-komplett.la neue-ansage-lautet.la wenn-einverstanden-1.la \ + bitte-neue-ansage-kurz.la ansage-gespeichert.la und.la ein.la 10.la 11.la \ + 12.la 13.la 14.la 15.la 16.la 17.la 18.la 19.la 20.la 30.la 40.la 50.la 60.la \ + 70.la 80.la 90.la fernabfrage-aktiv.la README + +uninstall-hook: + -rmdir $(DESTDIR)$(pkgdatadir) diff --git a/scripts/waves/README b/scripts/waves/README new file mode 100644 index 0000000..3f41f68 --- /dev/null +++ b/scripts/waves/README @@ -0,0 +1,2 @@ +This directory holds the global audio snippets used by the scripts distributed +with CapiSuite. Please see the CapiSuite documentation for further details. diff --git a/scripts/waves/am.la b/scripts/waves/am.la new file mode 100644 index 0000000..09cbae7 Binary files /dev/null and b/scripts/waves/am.la differ diff --git a/scripts/waves/anrufbeantworter-von.la b/scripts/waves/anrufbeantworter-von.la new file mode 100644 index 0000000..170d6b5 Binary files /dev/null and b/scripts/waves/anrufbeantworter-von.la differ diff --git a/scripts/waves/ansage-gespeichert.la b/scripts/waves/ansage-gespeichert.la new file mode 100644 index 0000000..ee06c74 Binary files /dev/null and b/scripts/waves/ansage-gespeichert.la differ diff --git a/scripts/waves/beep.la b/scripts/waves/beep.la new file mode 100644 index 0000000..59903fa --- /dev/null +++ b/scripts/waves/beep.la @@ -0,0 +1 @@ +‡íÝÝí+ìÜÜì«íÝÝí«ìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíªìÜÜìªíÝÝíJìÜÜìÎ Í \ No newline at end of file diff --git a/scripts/waves/bitte-nachricht.la b/scripts/waves/bitte-nachricht.la new file mode 100644 index 0000000..c6eb8cd Binary files /dev/null and b/scripts/waves/bitte-nachricht.la differ diff --git a/scripts/waves/bitte-neue-ansage-komplett.la b/scripts/waves/bitte-neue-ansage-komplett.la new file mode 100644 index 0000000..0f07965 Binary files /dev/null and b/scripts/waves/bitte-neue-ansage-komplett.la differ diff --git a/scripts/waves/bitte-neue-ansage-kurz.la b/scripts/waves/bitte-neue-ansage-kurz.la new file mode 100644 index 0000000..4e23d5b Binary files /dev/null and b/scripts/waves/bitte-neue-ansage-kurz.la differ diff --git a/scripts/waves/ein.la b/scripts/waves/ein.la new file mode 100644 index 0000000..f3a5f71 Binary files /dev/null and b/scripts/waves/ein.la differ diff --git a/scripts/waves/erklaerung.la b/scripts/waves/erklaerung.la new file mode 100644 index 0000000..86295b2 Binary files /dev/null and b/scripts/waves/erklaerung.la differ diff --git a/scripts/waves/fernabfrage-aktiv.la b/scripts/waves/fernabfrage-aktiv.la new file mode 100644 index 0000000..e86c9a6 Binary files /dev/null and b/scripts/waves/fernabfrage-aktiv.la differ diff --git a/scripts/waves/fuer-neue-ansage-9.la b/scripts/waves/fuer-neue-ansage-9.la new file mode 100644 index 0000000..c09c383 Binary files /dev/null and b/scripts/waves/fuer-neue-ansage-9.la differ diff --git a/scripts/waves/fuer.la b/scripts/waves/fuer.la new file mode 100644 index 0000000..ca82acf Binary files /dev/null and b/scripts/waves/fuer.la differ diff --git a/scripts/waves/keine-weiteren-nachrichten.la b/scripts/waves/keine-weiteren-nachrichten.la new file mode 100644 index 0000000..f5c85c0 Binary files /dev/null and b/scripts/waves/keine-weiteren-nachrichten.la differ diff --git a/scripts/waves/nachricht-gelöscht.la b/scripts/waves/nachricht-gelöscht.la new file mode 100644 index 0000000..f9504b9 Binary files /dev/null and b/scripts/waves/nachricht-gelöscht.la differ diff --git a/scripts/waves/nachricht.la b/scripts/waves/nachricht.la new file mode 100644 index 0000000..7b53c2e Binary files /dev/null and b/scripts/waves/nachricht.la differ diff --git a/scripts/waves/nachrichten.la b/scripts/waves/nachrichten.la new file mode 100644 index 0000000..8254abd Binary files /dev/null and b/scripts/waves/nachrichten.la differ diff --git a/scripts/waves/neue-ansage-lautet.la b/scripts/waves/neue-ansage-lautet.la new file mode 100644 index 0000000..7dd125e Binary files /dev/null and b/scripts/waves/neue-ansage-lautet.la differ diff --git a/scripts/waves/neue-nachricht.la b/scripts/waves/neue-nachricht.la new file mode 100644 index 0000000..844fac7 Binary files /dev/null and b/scripts/waves/neue-nachricht.la differ diff --git a/scripts/waves/neue-nachrichten.la b/scripts/waves/neue-nachrichten.la new file mode 100644 index 0000000..54cf1c8 Binary files /dev/null and b/scripts/waves/neue-nachrichten.la differ diff --git a/scripts/waves/uhr.la b/scripts/waves/uhr.la new file mode 100644 index 0000000..1168fc8 Binary files /dev/null and b/scripts/waves/uhr.la differ diff --git a/scripts/waves/um.la b/scripts/waves/um.la new file mode 100644 index 0000000..2f4d988 Binary files /dev/null and b/scripts/waves/um.la differ diff --git a/scripts/waves/und.la b/scripts/waves/und.la new file mode 100644 index 0000000..12cdf42 --- /dev/null +++ b/scripts/waves/und.la @@ -0,0 +1,31 @@ +©‰¹y™¹é‡‹æ¨ȈV†n+Ó?ÿ?oC«²N¦&æÞ2 +Ë;K[óϧ?“z²¢£'©‰‰©OhØ àX¸v®Ã_‡w©‰¹yÙyÉ7Ûfèȸˆ¨f.«“oOÏÏoóK¢.~¾Ž’"J +Š‹[/oo¯ó¿G× É)÷Šv8à`€ ¸î³—)Éù™YÙ9駂öˆHHhöZc??ï¯c*.^†FƦ/oÓó#s///ÃÛJ¢C_w‰ iÇú–ø `àˆÆb¯§ÇW‰¹¡Ù9©¾VˆÈˆæ.J[³Ã¯oï/ÊÎf6–Æ~¢¿ÏSƒ{[Óï/Cj²Ãç)¹Iig¾hX`à ÈÖžªcÿ'× y¡¡‰ç"6èh(ö^îÊê{[oÏïëîfv6¦"#O3Ë+êoO#ZRwIÙ9éCø`` øÖ6¾òú/çiy!a¡IǶ¨¨öf¾Žîʯ_‡çJÆ–vÆ^‚ëûƒ£[S??ÿÛʾ~Ú÷‰™Ùé'h¸x¸¸èhVæ’i!ÙyigϪb®Þ^6¶†æ«/çOSú.Î.¾f¶&’K§?/++{#/+îŽ>Kw y9—sfhHˆˆHˆÈ¨Î¯©IùI)×7#†ÆfÎkÃ[ŠzúK;jR¦¶Æ^ÒÊ#c[ƒ[ƒsã;êÊ›³s;šú; I >–((È88ˆv‚ß·——©é©ÓÚÒBºëº².BKóï#Zž¾R‚î&ÆƦÚSÓ ‹ƒ[ëªê{{{{;'i é/²fvˆx8¨æ."k)i)wç'/;2RÚê;»k*ŠºšÚÒNξÞ&^΂ꛛ£“S/ó££o¿ÏkëêKSÏÃJJºÊêÚžîR榎BÊËûcOç§ß_?ï㛫ʺb’²âZÚ¢‚B"Z‚B2"ºJ:ŠŠªkk «‹›ûË+‹K£Ïß_ßOSóÛº®žfFvv6Ææ>òÊ[_gÇGÇçO¯C£K*JúúšÚ‚²n>žþNNnÒò"Š+Ë»›#ãƒCCcÃ/?Ÿ'ç?ÏÓ›z.^†¶ö66Fæ~îZK/ÿ§çg租O›Ëkëk+ +Êz²r.>>>Nn®2â:JJ«»ãCƒ£{›³ï?_§?“ûÊþ&¶¶F&>®šûÓŸ§§ßŸ?O/“Ã[K +«ªjÊz"B®ŽÎî.R‚âZºjkûÛ[{›C/¿§'§_ÿ¯cŠ2^fÆFÆæžN®‹ï¿ÿŸ¿ÏﯯÓ3ã›Ë +j +ºúÒînn®RÒrB⺊+KË ‹‹sß?S š’N^æ†f¦ž¾."ª[SÏ?ÿ?OïSÓó#û ë«ê +ºúzbÒ®Ò’2ÂòBBbÚÚ:Šª‹Ë‹k;c“o¿?O¯ó›+šòŽ>¦^ž¾Žò*{C/ÏO¿??Ïï“ÃÛ»ëjÊJ:š"²ÒîŽ.’²2ÂbbÚúJj‹K;Këkûc“/oï“c£{+J‚.N~ž~¾Îîò¢j»Ã¯OOOo¯óÛ»«Ê:úbB’®RòB2²‚b"¢ºŠ* »‹‹û[s¯oo/“c{ëênN>þþNîrZJ+{¯ooï/S“ãûË+Š:ÚBÒR’r2²²²Â‚âÚúʪ««ë K;›c3Óï¯ÓsãËêúâînŽn.²âz + ÛƒóS¯/¯SÓsC#›K+ê +:Úâ‚b"¢"bbâ"ZúÊŠ +ÊŠêª+k û[#ãÃ33³[û‹ª +ú"""‚Â":ʪ+ û[c#£ã#Û›Kk+«*jÊJÊ +Jº + + +Šjêj + +ººJÊŠŠê«ëë‹ kk‹Ë‹kK»ûû{»ë«*êŠ + +jŠjjjj*««*ªª*ª«ª*ªªªª+ª«+kë««ª««*««ª+këë+«*ê*êŠjê***jêê*ª+ë+ë+«ªªª+ë«««++ëë««««**ªªª«ªªªêꪫª«++ëë뫪*êêê*ê*ª+몪ª**«ªª+ëë++««+뫪«+ª«*****ê**«+++ë«******ªª««++«««ëë««ªª«+ª*ªªª*jª«««+«ªª«+ª**ª+««ª««ª++ªª««ª*ê*«+«+ë++k뫪**ª«ª*ê*ª*êª*ªªªë«««ëëª*ª****ª*ªëëë«+ë«++«ªª*«ª**ê*êª+«++««*ꪪ««+«ª+ë+ªª*ê*«ªª««ë+++«ª***ê*«++««ªª+«ªêjꪫª*«ª«+««««««**ª+ë+++««««ª*ªª«««+«**ª*jê**+++«««ª«««+«ª«««*ªªªª**ª++«««ª«*ê**ªª«+ë‹ë«ªêê**«ª++++««ªªª****ªªª«+ªª««+«*««++ªªªªª«««++««+ªªªª«ªªª«««+ªªªª«**ªª+ªªªª**ªªª«+++++«ª«««««««ªªªª*«+ªªªªª«ª***ª«ª«ª«+««ª*ª«+««««+««*ê*ª«ªª««++«**ªª««ªª«««««ªªª+++«ª««****ª«ë+++«êêê***++«ªª+««ªj** +bÎ’7I;æoö¨/.¾šJ3Ë['ëOOºËoÚ"Ó^‚Þ +‡n¾2/û:.ʯò?âzOzã:£{ӊ毺¾Ïþz¯NÚÿ" +/rcÃ:oK.«cš:£Îj«Žâ;Z/¢ªo®ZOâ:kŠ«ºóz§Š[³êÊBcnCz[ +z«{"o:Ûº>ÚznJjËŠ c‹º“oj³Kúª¢Ë®âãÒâëjËË‹£{óêk :ËJš+Ê«+Jkú:+ÊKKº¢Ê«òŠ‹ +‹ÛS**ãÛKŠ›‹úkÊë:¢BšëÛ£‹o{ºkzŠ"’ârªê[ƒÓ{sã›K¢»¢ +Ž:j+;»³{ëj{š +Â.KûÛÛk㪻ë2ËÛêJ:*«JËâZŠK+£#zëZzêZJÊK[êJë»;Ë£Kº«‹ê""ªJÊ›;k*+»«Kã+ªk«JJâš+Kc»ëkÊŠk +"‚ZŠªº+ëêË;k +ªëk*Ë{»Û‹kkêjJúJ‹Û‹ +ú"Z"‚*{›Û‹‹JººË»j*šÊêº +º+ã;Ë Kªj*zº +ÊJzê‹+«këk«+Kk«‹**k+ë +j««*jê+ë*몊*ªëkª«*Ê*+ªª*ªªë+*ªëk‹ë+*j*êjê*ª+k«*ªë++«ª+«««ªª+«ªª*êª+++«««+++ªêjêꪫ««««««««ª««+*ê++««««ª«ë«ê**ª««ª«+++ª***ª*ꪪ+«++«ªª+«ª«ªêŠ*ª**ª+‹ë++++ªª«**«+««ª««ª*«ªª«+«*ª«ªªª*ª*ª«ª««+«««ªªª«+*«+«++ªªª*«ë+ªª«ªª+«ê*ª«ªª«««««ªªª«ª*ª«+«+«ª«ªª«+«ª«+++ªêjªª***«««+++«ª++«*j*«««ª«+ \ No newline at end of file diff --git a/scripts/waves/von.la b/scripts/waves/von.la new file mode 100644 index 0000000..9730e61 Binary files /dev/null and b/scripts/waves/von.la differ diff --git a/scripts/waves/wenn-einverstanden-1.la b/scripts/waves/wenn-einverstanden-1.la new file mode 100644 index 0000000..fe34524 Binary files /dev/null and b/scripts/waves/wenn-einverstanden-1.la differ diff --git a/scripts/waves/zum-abhoeren-1.la b/scripts/waves/zum-abhoeren-1.la new file mode 100644 index 0000000..ed3498d Binary files /dev/null and b/scripts/waves/zum-abhoeren-1.la differ diff --git a/src/.cvsignore b/src/.cvsignore new file mode 100644 index 0000000..de38626 --- /dev/null +++ b/src/.cvsignore @@ -0,0 +1,7 @@ +*.o +capisuite +capisuite.conf +.deps +.deps/* +Makefile.in +Makefile diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..988b1d2 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,20 @@ +pkgsysconfdir = @sysconfdir@/capisuite + +bin_PROGRAMS = capisuite +capisuite_LDADD=application/libccapplication.a modules/libccmodules.a \ + backend/libccbackend.a +capisuite_SOURCES=main.cpp +SUBDIRS = application backend modules + +pkgsysconf_DATA = capisuite.conf +EXTRA_DIST = capisuite.conf.in + +capisuite.conf: capisuite.conf.in + rm -f $@ + sed -e 's,@pkgdatadir\@,$(pkgdatadir),g' \ + -e 's,@pkglibdir\@,$(pkglibdir),g' \ + -e 's,@localstatedir\@,$(localstatedir),g' \ + -e 's,@spooldir\@,$(spooldir),g' $< >$@ + +clean-local: + rm -f capisuite.conf diff --git a/src/application/.cvsignore b/src/application/.cvsignore new file mode 100644 index 0000000..e995588 --- /dev/null +++ b/src/application/.cvsignore @@ -0,0 +1,3 @@ +.deps +Makefile +Makefile.in diff --git a/src/application/Makefile.am b/src/application/Makefile.am new file mode 100644 index 0000000..2c213fc --- /dev/null +++ b/src/application/Makefile.am @@ -0,0 +1,5 @@ +noinst_LIBRARIES = libccapplication.a +libccapplication_a_SOURCES = capisuite.cpp capisuite.h capisuitemodule.h \ + capisuitemodule.cpp incomingscript.cpp incomingscript.h pythonscript.h \ + pythonscript.cpp idlescript.h idlescript.cpp applicationexception.h + diff --git a/src/application/applicationexception.h b/src/application/applicationexception.h new file mode 100644 index 0000000..f4342c6 --- /dev/null +++ b/src/application/applicationexception.h @@ -0,0 +1,121 @@ +/** @file applicationexception.h + @brief Contains ApplicationError - Exception class for errors in the application layer + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef APPLICATIONEXCEPTION_H +#define APPLICATIONEXCEPTION_H + +#include +#include + +using namespace std; + +/** @brief Exception class for errors in the application layer + + Each exception gets a severity (Warning, Error or Fatal), a message and the name of the function where it occurred. + + @author Gernot Hillier +*/ +class ApplicationError +{ + public: + + /** @brief Constructor. Create an object, print error message and abort if severity FATAL was chosen. + + @param errormsg some informal message describing the error + @param function_name name of the function which throws this exception + */ + ApplicationError(string errormsg,string function_name): + errormsg(errormsg),function_name(function_name) + {} + + /** @brief Return nice formatted error message + + Returns the string "Classname: error message occured in function()" + + @return error message + */ + virtual string message() + { + return ("ApplicationError: "+errormsg+" occured in "+function_name); + } + + + protected: + string errormsg; ///< textual error message + string function_name; ///< function/method where this error occured +}; + +/** @brief Overloaded operator for output of error classes +*/ +inline ostream& operator<<(ostream &s, ApplicationError &e) +{ + s << e.message(); + return s; +} + +#endif + +/* History + +$Log: applicationexception.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.12 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.11 2002/12/13 09:57:10 ghillie +- error message formatting done by exception classes now + +Revision 1.10 2002/12/10 15:03:04 ghillie +- added missing include, using namespace std + +Revision 1.9 2002/12/09 15:19:53 ghillie +- removed severity WARNING +- removed printing of error message in ERROR severity + +Revision 1.8 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.7 2002/11/27 15:54:02 ghillie +updated docu for doxygen + +Revision 1.6 2002/11/25 21:00:53 ghillie +- improved documentation, now doxygen-readabl + +Revision 1.5 2002/11/18 14:21:07 ghillie +- moved global severity_t to ApplicationError::severity_t +- added throw() declarations to header files + +Revision 1.4 2002/11/17 14:34:17 ghillie +small change in header description + +Revision 1.3 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.2 2002/10/27 12:47:20 ghillie +- added multithread support for python +- changed callcontrol reference to stay in the python namespace +- changed ApplicationError to support differen severity + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.1 2002/10/24 09:58:12 ghillie +definition of application exceptions will stay here + +*/ diff --git a/src/application/capisuite.cpp b/src/application/capisuite.cpp new file mode 100644 index 0000000..8f8b299 --- /dev/null +++ b/src/application/capisuite.cpp @@ -0,0 +1,590 @@ +/* @file capisuite.cpp + @brief Contains CapiSuite - Main application class, implements ApplicationInterface + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "../../config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include "../backend/capi.h" +#include "../backend/connection.h" +#include "incomingscript.h" +#include "idlescript.h" +#include "capisuite.h" + +/** @brief Global Pointer to current CapiSuite instance +*/ +CapiSuite* capisuiteInstance=NULL; + +void exit_handler(int) +{ + if (capisuiteInstance) + capisuiteInstance->finish(); +} + +void hup_handler(int) +{ + if (capisuiteInstance) + capisuiteInstance->reload(); + signal(SIGHUP,hup_handler); +} + +CapiSuite::CapiSuite(int argc,char **argv) +:capi(NULL),waiting(),instances(),config(),idle(NULL),py_state(NULL),debug(NULL),error(NULL),finish_flag(false),custom_configfile(),daemonmode(false) +{ + if (capisuiteInstance!=NULL) { + cerr << "FATAL error: More than one instances of CapiSuite created"; + exit(1); + } + capisuiteInstance=this; + + readCommandline(argc,argv); + readConfiguration(); + + try { + if (daemonmode) + // set daemon mode + if (daemon(0,0)!=0) { + (*error) << prefix() << "FATAL error: Can't fork off daemon" << endl; + exit(1); + } + + debug_level=atoi(config["log_level"].c_str()); + + (*debug) << prefix() << "CapiSuite " << VERSION << " started." << endl; + string info; + if (debug_level>=2) + info=Capi::getInfo(true); + else + info=Capi::getInfo(false); + (*debug) << prefix(); + for (int i=0;iregisterApplicationInterface(this); + + capi->setListenTelephony(0); // TODO: 0 = all, evtl. einstellbar? + capi->setListenFaxG3(0); // TODO: 0 = all, evtl. einstellbar? + + // initialization of the Python interpreter to be thread safe (taken out of PyApache 4.26) + Py_Initialize(); + PycString_IMPORT; // initialize cStringIO module + if (!(save_cStringIO=PycStringIO)) { // save it as it will be overwritten by each #include with NULL (sic) + (*error) << prefix() << "FATAL error: error during Python interpreter initialization (cString)"; + exit(1); + } + PyEval_InitThreads(); // init and acquire lock + py_state=PyEval_SaveThread(); // release lock, save thread context + if (!py_state) { + (*error) << prefix() << "FATAL error: can't release python lock"; + exit(1); + } + + // idle script object + int interval=atoi(config["idle_script_interval"].c_str()); + if (interval && config["idle_script"]!="") + idle=new IdleScript(*debug,debug_level,*error,capi,config["idle_script"],interval,py_state,save_cStringIO); + + // signal handling + signal(SIGTERM,exit_handler); + signal(SIGINT,exit_handler); // this must be located after pyhton initialization + signal(SIGHUP,hup_handler); + } + catch (CapiError e) { + capisuiteInstance=NULL; + if (idle) { + idle->requestTerminate(); + } + if (py_state) { + PyEval_RestoreThread(py_state); // switch to right thread context, acquire lock + py_state=NULL; + Py_Finalize(); + } + if (capi) + delete capi; + (*error) << prefix() << "Can't start Capi abstraction. The given error message was: " << e << endl << endl; + exit(1); + } + catch (ApplicationError e) { + capisuiteInstance=NULL; + if (idle) { + idle->requestTerminate(); + } + if (py_state) { + PyEval_RestoreThread(py_state); // switch to right thread context, acquire lock + py_state=NULL; + Py_Finalize(); + } + if (capi) + delete capi; + (*error) << prefix() << "Can't start application. The given error message was: " << e << endl; + exit(1); + } +} + +CapiSuite::~CapiSuite() +{ + if (idle) + idle->requestTerminate(); // will self-delete! + + // thread-safe shutdown of the Python interpreter (taken out of PyApache 4.26) + if (py_state) { + PyEval_RestoreThread(py_state); // switch to right thread context, acquire lock + py_state=NULL; + Py_Finalize(); + } + + delete capi; + + (*debug) << prefix() << "CapiSuite finished." << endl; + (*error) << prefix() << "CapiSuite finished." << endl; + + capisuiteInstance=NULL; +} + + +void +CapiSuite::finish() +{ + if (debug_level >= 2) + (*debug) << prefix() << "requested finish" << endl; + finish_flag=true; +} + +void CapiSuite::reload() +{ + if (debug_level >= 2) + (*debug) << prefix() << "requested reload" << endl; + if (idle) + idle->activate(); +} + +void +CapiSuite::callWaiting (Connection *conn) +{ + waiting.push(conn); +} + +void +CapiSuite::mainLoop() +{ + timespec delay_time; + delay_time.tv_sec=0; delay_time.tv_nsec=100000000; // 100 msec + int count,errorcount=0; + while (!finish_flag) { + nanosleep(&delay_time,NULL); + count++; + while (waiting.size()) { + Connection* conn=waiting.front(); + waiting.pop(); + if (instances.count(conn)) + throw ApplicationError("double used connection reference","CapiSuite::mainLoop()"); + + IncomingScript *instance; + try { + instance=new IncomingScript(*debug,debug_level,*error,conn,config["incoming_script"],save_cStringIO); + } + catch (ApplicationError e) + { + (*error) << prefix() << "ERROR: can't start CallControl thread, message was: " << e << endl; + delete instance; + } + // otherwise it will self-delete! + } + } +} + +string +CapiSuite::prefix() +{ + time_t t=time(NULL); + char* ct=ctime(&t); + ct[24]='\0'; + stringstream s; + s << ct << " CapiSuite " << hex << this << ": "; + return (s.str()); +} + +void +CapiSuite::parseConfigFile(ifstream &configfile) +{ + while (!configfile.eof()) { + string l; + getline(configfile,l); + if (l.size() && l[0]!='#') { + int pos=l.find("="); + if (pos>0) { + int key_f=l.find_first_not_of("\" \t"); // strip blanks and " chars... + int key_l=l.find_last_not_of("\" \t",pos-1); + int value_f=l.find_first_not_of("\" \t",pos+1); + int value_l=l.find_last_not_of("\" \t"); + string key,value; + if (key_f>=0 && key_f<=key_l) { // if we have a valid key + key=l.substr(key_f,key_l-key_f+1); + if (value_f>0 && value_f<=value_l) + value=l.substr(value_f,value_l-value_f+1); + else + value=""; + config[key]=value; + } + } + } + } +} + +void +CapiSuite::checkOption(string key, string value) +{ + if (!config.count(key)) { + cerr << "Warning: Can't find " << key << " variable. Using default (" << value << ")." << endl; + config[key]=value; + } +} + +void +CapiSuite::logMessage(string message, int level) +{ + if (debug_level >= level) + (*debug) << prefix() << message << endl; +} + +void +CapiSuite::errorMessage(string message) +{ + (*error) << prefix() << message << endl; +} + +void +CapiSuite::readConfiguration() +{ + ifstream configf; + + if (custom_configfile.size()) { + configf.open(custom_configfile.c_str()); + if (configf) + parseConfigFile(configf); + else + cerr << "Warning: Can't open custom file " << custom_configfile << "."<< endl; + configf.close(); + } else { + configf.open((string(PKGSYSCONFDIR)+"/capisuite.conf").c_str()); + if (configf) + parseConfigFile(configf); + else + cerr << "Warning: Can't open " << PKGSYSCONFDIR <<"/capisuite.conf." << endl; + configf.close(); + } + + checkOption("incoming_script",string(PKGLIBDIR)+"/incoming.py"); + checkOption("idle_script",string(PKGLIBDIR)+"idle.py"); + checkOption("idle_script_interval","60"); + checkOption("log_file",string(LOCALSTATEDIR)+"/log/capisuite.log"); + checkOption("log_level","2"); + checkOption("log_error",string(LOCALSTATEDIR)+"/log/capisuite.error"); + + string t(config["idle_script_interval"]); + for (int i=0;i'9') + throw ApplicationError("Invalid idle_script_interval given.","main()"); + + if (config["log_file"]!="" and config["log_file"]!="-") { + debug = new ofstream(config["log_file"].c_str(),ios::app); + if (! (*debug)) { + cerr << "Can't open log file. Writing to stdout." << endl; + delete debug; + debug = &cout; + } + } else + debug=&cout; + + t=config["log_level"]; + if (t.size()!=1 && (t[0]<'0' || t[0]>'3')) + throw ApplicationError("Invalid log_level given.","main()"); + + if (config["log_error"]!="" and config["log_error"]!="-") { + error = new ofstream(config["log_error"].c_str(),ios::app); + if (! (*error)) { + cerr << "Can't open error log file. Writing to stderr." << endl; + delete error; + error = &cerr; + } + } else + error=&cerr; + + if (daemonmode) { + if (debug==&cout) { + cerr << "FATAL error: not allowed to write to stdout in daemon mode."; + exit(1); + } + if (error==&cerr) { + cerr << "FATAL error: not allowed to write to stderr in daemon mode."; + exit(1); + } + } +} + + +void +CapiSuite::readCommandline(int argc, char** argv) +{ + struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"config", required_argument, NULL, 'c'}, + {"daemon", no_argument, NULL, 'd'}, + {0, 0, 0, 0} + }; + + int result=0; + do { + result=getopt_long(argc,argv,"hdc:",long_options,NULL); + switch (result) { + case -1: // end + break; + + case 'c': + custom_configfile=optarg; + break; + + case 'd': + daemonmode=true; + break; + case 'h': + default: + help(); + exit(1); + break; + } + } while (result!=-1); +} + +void +CapiSuite::help() +{ + cout << "CapiSuite " << VERSION << " (c) by Gernot Hillier " << endl << endl; + cout << "CapiSuite is an python-scriptable ISDN Telecommunication Suite providing some" << endl; + cout << "ISDN services like fax, voice recording/playing, etc. For further documentation" << endl; + cout << "please have a look at the HTML documents provided with it." << endl << endl; + cout << "syntax: capisuite [-h] [-c file]" << endl << endl; + cout << "-h, --help show this help" << endl; + cout << "-c file, --config=file use a custom configuration file" << endl; + cout << " (default: " << PKGSYSCONFDIR <<"/capisuite.conf)" << endl; + cout << "-d, --daemon run as daemon" << endl; +} + + +/* History + +$Log: capisuite.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.14 2003/02/17 16:49:24 ghillie +- cosmetic... + +Revision 1.13 2003/02/10 14:14:39 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.12.2.1 2003/02/09 14:58:13 ghillie +- rewritten parseConfigFile to use STL strings instead of CommonC++ strtokenizer +- no need to call detach on thread classes any more (because of their new + usage of native pthreads) + +Revision 1.12 2003/02/05 15:57:57 ghillie +- improved error handling, replaced some outputs to cerr with + correct error stream + +Revision 1.11 2003/01/31 11:25:53 ghillie +- moved capisuiteInstance from header to cpp (mustn't be defined in + each file including capisuite.h, use extern there instead!) + +Revision 1.10 2003/01/27 19:25:14 ghillie +- config moved to etc/capisuite/* + +Revision 1.9 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.8 2003/01/19 12:06:05 ghillie +- support starting as daemon with "-d" (resolving TODO) +- nicer formatting for getInfo output at startup (resolves TODO) +- new methods logMessage() and errorMessage() for use in python scripts + (resolves TODO) + +Revision 1.7 2003/01/18 12:51:08 ghillie +- initialization of cStringIO needed for printing tracebacks to error log + (solved one TODO item), pass on reference to *Script + +Revision 1.6 2003/01/13 21:24:17 ghillie +- corrected typos +- reverted change to config options + +Revision 1.5 2003/01/08 15:59:05 ghillie +- updated for new configuration variables for pathes, new method checkOption() + +Revision 1.4 2003/01/07 14:51:19 ghillie +- added commandline parsing (new methods help(), readCommandLine(): + -h/--help and -c/--config (other configuration file) + +Revision 1.3 2003/01/06 21:00:24 ghillie +- added SIGHUP support (new functions hup_handler, CapiSuite::reload) + +Revision 1.2 2003/01/06 16:20:36 ghillie +- added error check for idle->detach() +- removed superfluous delete idle calls (SEGV) + +Revision 1.1 2003/01/05 12:28:09 ghillie +- renamed FlowControl to CapiSuite +- the code from main() was moved to this class + +Revision 1.23 2003/01/04 15:57:03 ghillie +- added exit handler for SIGINT and SIGTERM +- added static FlowControl pointer +- added finish method for exit handler +- added timestamp in log files +- log_level support + +Revision 1.22 2002/12/14 14:01:06 ghillie +- just terminate() IdleScript instance, don't delete it (will delete self!) + +Revision 1.21 2002/12/11 13:01:59 ghillie +- executeIdleScript() removed, its function is now done by IdleScript + object (changes in constructor and mainLoop()) +- removed getCapi() + +Revision 1.20 2002/12/10 15:52:18 ghillie +- begin changes for moving functionality from executeIdleScript() to + IdleScript class + +Revision 1.19 2002/12/10 15:05:14 ghillie +- changed CallControl to IncomingScript +- added some missing =NULL statements in executeIdleScript() + +Revision 1.18 2002/12/09 15:26:11 ghillie +- callWaiting does add Connection to queue only, real handling moved + to mainLoop() as it may block in error handling +- error output/exception improvements (WARNING -> ERROR severity, ...) +- removed obsolete debug() method + +Revision 1.17 2002/12/07 22:37:56 ghillie +- moved capisuitemodule_init() call from executeIdleScript() to constructor +- get __main__ as it's not returned by capisuitemodule_init() any more +- MEMORY FIX: added missing fclose call + +Revision 1.16 2002/12/05 15:55:10 ghillie +- removed callCompleted() as now exception will be thrown in python script any more +- removed instances attribute + +Revision 1.15 2002/12/05 14:52:31 ghillie +- in executeIdleScript(): removed ugly strncpy, used const_cast() instead to satisfy PyRun_SimpleFile() +- cleaned up python reference counting +- new method getCapi() + +Revision 1.14 2002/12/02 12:28:09 ghillie +- FlowControl constructor now takes 3 additional parameters for the scripts to be used +- added support for regular execution of an idle script in mainLoop(), added executeIdleScript() + +Revision 1.13 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.12 2002/11/27 15:57:25 ghillie +added missing throw() declaration + +Revision 1.11 2002/11/23 16:40:19 ghillie +removed unnecessary include + +Revision 1.10 2002/11/21 11:35:54 ghillie +- in case of call Completion just call instance->callCompleted() instead of deleting it. + This allows us to continue w/o waiting for the thread to finish. + +Revision 1.9 2002/11/20 17:17:46 ghillie +- FIX: instances weren't erased when exception occurred -> this lead to double used PLCI +- in mainLoop(): changed sleep to nanosleep + +Revision 1.8 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.7 2002/11/18 14:21:07 ghillie +- moved global severity_t to ApplicationError::severity_t +- added throw() declarations to header files + +Revision 1.6 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.5 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.4 2002/11/10 17:02:54 ghillie +mainLoop prepared for Python execution + +Revision 1.3 2002/11/07 08:19:04 ghillie +some improvements and fixes in Python global lock and thread state handling + +Revision 1.2 2002/10/27 12:47:20 ghillie +- added multithread support for python +- changed callcontrol reference to stay in the python namespace +- changed ApplicationError to support differen severity + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.8 2002/10/24 09:55:52 ghillie +many fixes. Works for one call now + +Revision 1.7 2002/10/23 15:40:15 ghillie +added python integration... + +Revision 1.6 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.5 2002/10/05 20:43:32 gernot +quick'n'dirty, but WORKS + +Revision 1.4 2002/10/05 13:53:00 gernot +changed to use thread class of CommonC++ instead of the threads-package +some cosmetic improvements (indentation...) + +Revision 1.3 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.2 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.1 2002/10/02 14:10:07 gernot +first version + +*/ diff --git a/src/application/capisuite.h b/src/application/capisuite.h new file mode 100644 index 0000000..662c117 --- /dev/null +++ b/src/application/capisuite.h @@ -0,0 +1,277 @@ +/** @file capisuite.h + @brief Contains CapiSuite - Main application class, implements ApplicationInterface + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CAPISUITE_H +#define CAPISUITE_H + +#include +#include +#include +#include "../backend/applicationinterface.h" +#include "applicationexception.h" +#include "capisuitemodule.h" +class Capi; +class IdleScript; +class PycStringIO_CAPI; + +/** @brief Main application class, implements ApplicationInterface + + This class realizes the main application and thus implements the + ApplicationInterface. It firstly generates the necessary objects from the CAPI + abstraction layer (one object of class Capi) and enables call listening. + + It contains the mainLoop() and creates handling objects of IncomingScript for + all incoming calls and one object of IdleScript which will start the idle script + at regular intervals. The scripts are informed about disconnection / program termination + but will delete themselves. + + main() should create one CapiSuite object and then call mainLoop(). + + @author Gernot Hillier +*/ +class CapiSuite: public ApplicationInterface +{ + public: + /** @brief Constructor. General initializations. + + Creates a Capi object, enables listening, calls readConfiguration, + and initializes the Python interpreter in multithreading mode. + It immediately releases the global Python lock after doing initialization. + + Also an IdleScript object is created and will regularly call the given script. + + @param argc commandline argument count as given to main() + @param argv commandline arguments as given to main() + */ + CapiSuite(int argc, char** argv); + + /** @brief Destructor. Kill Python interpreter. + + Takes lock and calls CallControl::callCompleted(). + */ + ~CapiSuite(); + + /** @brief Callback: enqueue Connection in waiting + */ + virtual void callWaiting (Connection *conn); + + /** @brief Main Loop. Event Loop (handling incoming connections) + + For each incoming connection, an object of IncomingScript is created + which handles this call in an own thread. + + This loop will run until the program is finished. + */ + void mainLoop(); + + /** @brief Request finish of mainLoop + */ + void finish(); + + /** @brief Parse a given configuration file + + This function reads the given configuration file. It must consist of key=value pairs + separated on different lines. + + Lines beginning with "#" are treated as comments. Leading and trailing whitespaces + and quotation marks (") surrounding the values will be ignored. + */ + void parseConfigFile(ifstream &configfile); + + /** @brief Read configuration and set default values for options not found + + The configuration is read from PREFIX/etc/capisuite.conf (should exist), ~/.capisuite.conf (optional) and perhaps + a given custom config file (optional), while the latter has higher priority. After that all configuration options + are checked and set to default values if not found. + */ + void readConfiguration(); + + /** @brief Read commandline options + */ + void readCommandline(int argc, char**argv); + + /** @brief Print help message + */ + void help(); + + /** @brief restart some aspects if the process gets a SIGHUP + + Currently, this only reactivates the idle script if it was deactivated by too much errors in a row. + */ + void reload(); + + /** @brief print a message to the log + + Prints message to the log if it's level is high enough. + + @param message the message + @param level level of the message + */ + void logMessage(string message, int level); + + /** @brief print a message to the error log + + Prints message to the error log + + @param message the message + */ + void errorMessage(string message); + + private: + /** @brief return a prefix containing this pointer and date for log messages + + @return constructed prefix as stringstream + */ + string prefix(); + + /** @brief Test a configuration variable and set default if undefined + + @param key name of the config variable + @param value default value to set if key is not defined in config map + */ + void checkOption(string key, string value); + + + map instances; ///< saving pointers to all created CallControl instances indexed by Connection pointers + queue waiting; ///< queue for waiting connection instances + IdleScript *idle; ///< reference to the IdleScript object created + + PyThreadState *py_state; ///< saves the created thread state of the main python interpreter + PycStringIO_CAPI* save_cStringIO; ///< holds a pointer to the Python cStringIO C API + Capi* capi; ///< reference to Capi object to use, set in constructor + ostream *debug, ///< debug stream + *error; ///< stream for error messages + + unsigned short debug_level; ///< verbosity level for debug stream + + bool finish_flag; ///< flag to finish mainLoop() + + bool daemonmode; ///< flag set when we're running as daemon + + map config; ///< holds the configuration read from the configfile + string custom_configfile; ///< holds the name of the custom config file if given + +}; + +#endif + +/* History + +$Log: capisuite.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.8 2003/01/31 11:25:53 ghillie +- moved capisuiteInstance from header to cpp (mustn't be defined in + each file including capisuite.h, use extern there instead!) + +Revision 1.7 2003/01/19 12:06:25 ghillie +- new methods logMessage() and errorMessage() + +Revision 1.6 2003/01/18 12:51:48 ghillie +- added save_cStringIO attribute for Python cStringIO C API + +Revision 1.5 2003/01/13 21:24:47 ghillie +- added new method checkOption + +Revision 1.4 2003/01/07 14:52:36 ghillie +- added support for custom config files +- added support for parsing commandline options + +Revision 1.3 2003/01/06 21:00:48 ghillie +- added SIGHUP support (new method reload) + +Revision 1.2 2003/01/06 16:20:51 ghillie +- updated comment + +Revision 1.1 2003/01/05 12:28:09 ghillie +- renamed FlowControl to CapiSuite +- the code from main() was moved to this class + +Revision 1.13 2003/01/04 15:58:38 ghillie +- log improvements: log_level, timestamp +- added finish() method +- added static FlowControl pointer + +Revision 1.12 2002/12/11 13:02:56 ghillie +- executeIdleScript() removed, its function is now done by IdleScript + object (changes in constructor and mainLoop()) +- removed getCapi() +- minor docu bugs fixed + +Revision 1.11 2002/12/09 15:29:13 ghillie +- debug stream given in constructor +- doc update for callWaiting() and mainLoop() +- obsolete debug() method removed + +Revision 1.10 2002/12/06 12:54:30 ghillie +-removed callCompleted() + +Revision 1.9 2002/12/05 14:54:15 ghillie +- constructor gets Capi* now +- new method getCapi() +- python idle script gets called with pointer to FlowControl now + +Revision 1.8 2002/12/02 12:30:30 ghillie +- constructor now takes 3 additional arguments for the scripts to use +- added support for an idle script which is started in regular intervals + +Revision 1.7 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.6 2002/11/27 15:58:13 ghillie +updated comments for doxygen + +Revision 1.5 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.4 2002/11/18 14:21:07 ghillie +- moved global severity_t to ApplicationError::severity_t +- added throw() declarations to header files + +Revision 1.3 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.2 2002/10/27 12:47:20 ghillie +- added multithread support for python +- changed callcontrol reference to stay in the python namespace +- changed ApplicationError to support differen severity + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.6 2002/10/24 09:55:52 ghillie +many fixes. Works for one call now + +Revision 1.5 2002/10/23 15:40:15 ghillie +added python integration... + +Revision 1.4 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.3 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.2 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.1 2002/10/02 14:10:07 gernot +first version + +*/ diff --git a/src/application/capisuitemodule.cpp b/src/application/capisuitemodule.cpp new file mode 100644 index 0000000..ff2fb7c --- /dev/null +++ b/src/application/capisuitemodule.cpp @@ -0,0 +1,1158 @@ +/* @file capisuitemodule.cpp + @brief Contains the Python module and integration routines + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +// IMPORTANT: every python function MUST call PyErr_Occured() before using the associated +// Connection object! (connection can be already deleted while the python script is still running + +#include +#include "../backend/connection.h" +#include "../modules/audiosend.h" +#include "../modules/audioreceive.h" +#include "../modules/faxreceive.h" +#include "../modules/faxsend.h" +#include "../modules/connectmodule.h" +#include "../modules/disconnectmodule.h" +#include "../modules/switch2faxG3.h" +#include "../modules/readDTMF.h" +#include "../modules/calloutgoing.h" +#include "capisuitemodule.h" +#include "capisuite.h" + +#define TEMPORARY_FAILURE 0x34A9 // see ETS 300 102-1, Table 4.13 (cause information element) + +extern CapiSuite* capisuiteInstance; + +static PyObject* CallGoneError=NULL; +static PyObject* BackendError=NULL; + +/** @defgroup python C/Python wrapper functions + @brief These functions define the python commands you can use in your scripts. + + All CapiSuite-commands available in Python will stay in a python + module called capisuite. This module and all its functions are defined here. + + There's a general scheme for mapping the names of the C wrapper functions + to python names: + + Python command "capisuite.command()" will be defined in the wrapper function + "capisuite_command()". + + So you can use this document as reference to all available CapiSuite Python + commands. For example, if you read the documentation for the + capisuite_audio_send function here, you can use it as capisuite.audio_send in + your Python scripts. + +*/ + +void +capisuitemodule_destruct_connection(void* ptr) +{ + Connection *conn=(static_cast(ptr)); + conn->debugMessage("Python: deleting connection object",2); + if (conn->getState()!=Connection::DOWN) { + try { + conn->errorMessage("Warning: Connection still established in capisuitemodule_desctruct_conn(). Disconnecting."); + DisconnectModule active(conn,TEMPORARY_FAILURE,true); + active.mainLoop(); + } + catch (CapiError e) { + conn->errorMessage("ERROR: disconnection also failed. Too bad..."); + } + } + delete conn; +} + +/** @brief Private converter function to extract the contained Connection* from a PyCObject + + This function is defined for the use in PyArg_ParseTuple() calls. + + @param conn_ref - PyCObject pointer + @param conn address of the Connection pointer where the result will be stored + @return 1=successful, 0=error +*/ +bool +convertConnRef(PyObject *conn_ref, Connection** conn) +{ + if (!PyCObject_Check(conn_ref)) { + PyErr_SetString(PyExc_TypeError,"First parameter must be the call reference."); + return 0; + } + + if (! ( *conn=static_cast(PyCObject_AsVoidPtr(conn_ref)) ) ) { + PyErr_SetString(PyExc_TypeError,"Call reference is NULL. This is not allowed."); + return 0; + } + return 1; +} + +/** @brief Private converter function to extract the contained Capi* from a PyCObject + + This function is defined for the use in PyArg_ParseTuple() calls. + + @param capi_ref - PyCObject pointer + @param capi address of the Capi pointer where the result will be stored + @return 1=successful, 0=error +*/ +bool +convertCapiRef(PyObject *capi_ref, Capi** capi) +{ + if (!PyCObject_Check(capi_ref)) { + PyErr_SetString(PyExc_TypeError,"First parameter must be the Capi reference."); + return 0; + } + + if (! ( *capi=static_cast(PyCObject_AsVoidPtr(capi_ref)) ) ) { + PyErr_SetString(PyExc_TypeError,"Capi reference is NULL. This is not allowed."); + return 0; + } + return 1; +} + +/** @brief Write an informational message to the CapiSuite log. + @ingroup python + + This function writes a message to the CapiSuite log. It's helpful if you want to write + messages in the debug log in your scripts. + + The message can be either logged with the general CapiSuite prefix if they are of global + nature or with the Connection prefix if they're associated with a special connection. + + @param args Contains the python parameters. These are: + - message (string) the log message + - level (integer) parameter for log_level + - call (optional) call reference - if given, the message is logged with Connection prefix + @return None +*/ +static PyObject* +capisuite_log(PyObject*, PyObject *args) +{ + + Connection* conn=NULL; + char *message; + int level; + + if (!PyArg_ParseTuple(args,"si|O&:log",&message,&level,convertConnRef,&conn)) + return NULL; + + if (conn) + conn->debugMessage(message,level); + else if (capisuiteInstance) + capisuiteInstance->logMessage(message,level); + + Py_XINCREF(Py_None); + return (Py_None); +} + +/** @brief Write an error message to the CapiSuite error log. + @ingroup python + + This function writes a message to the CapiSuite error log. It + should be used to output error messages so they appear in the + normal error log. + + @param args Contains the python parameter: + - message (string) the log message + @return None +*/ +static PyObject* +capisuite_error(PyObject*, PyObject *args) +{ + + Connection* conn=NULL; + char *message; + + if (!PyArg_ParseTuple(args,"s|O&:error",&message,convertConnRef,&conn)) + return NULL; + + if (conn) + conn->errorMessage(message); + else if (capisuiteInstance) + capisuiteInstance->errorMessage(message); + + Py_XINCREF(Py_None); + return (Py_None); +} + +/** @brief Send an audio file in a speech mode connection. + @ingroup python + + This function sends an audio file. The audio file must be in bit-inversed A-Law format. It can be created for example + with sox using the suffix ".la". It supports abortion if DTMF signal is received. + + If DTMF abort is enabled, the command will also abort immediately if DTMF was received before it is called. That allows + you to abort subsequent audio receive and send commands with one DTMF signal w/o needing to check for received DTMF + after each command. + + The connction must be in audio mode (use capisuite_connect_voice()), otherwise an exception will be caused. + + @param args Contains the python parameters. These are: + - call Reference to the current call + - filename (string) file to send + - exit_DTMF (integer, optional) if set to 1, sending is aborted when a DTMF signal is received (0=off, default) + @return int containing duration of send in seconds +*/ +static PyObject* +capisuite_audio_send(PyObject*, PyObject *args) +{ + Connection* conn; + char *filename; + PyThreadState *_save; + int exit_DTMF=0; + long duration=0; + + if (!PyArg_ParseTuple(args,"O&s|i:audio_send",convertConnRef,&conn,&filename,&exit_DTMF)) + return NULL; + + try { + Py_UNBLOCK_THREADS + AudioSend active(conn,filename,exit_DTMF); + active.mainLoop(); + duration=active.duration(); + Py_BLOCK_THREADS + } + catch (CapiWrongState e) { + Py_BLOCK_THREADS + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + catch (CapiMsgError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + catch (CapiExternalError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + PyObject *r=PyInt_FromLong(duration); + return (r); +} + +/** @brief Receive an audio file in a speech mode connection. + @ingroup python + + This functions receives an audio file. It can recognize silence in the signal and timeout after + a given period of silence, after a general timeout or after the reception of a DTMF signal. + + If the recording was finished because of silence_timeout, the silence will be truncated away. + + If DTMF abort is enabled, the command will also abort immediately if DTMF was received before it is called. That allows + you to abort subsequent audio receive and send commands with one DTMF signal w/o needing to check for received DTMF + after each command. + + The connction must be in audio mode (use capisuite_connect_voice()), otherwise an exception will be caused. + + The created file will be saved in bit-reversed A-Law format, 8 kHz mono. Use sox to convert it to a normal wav file. + + @param args Contains the python parameters. These are: + - call Reference to the current call + - filename (string) where to save received file + - timeout (integer) receive length in seconds (-1 = infinite) + - silence_timeout (integer, optional) abort after x seconds of silence (0=off, default) + - exit_DTMF (integer, optional) if set to 1, sending is aborted when a DTMF signal is received (0=off, default) + @return int containing duration of receive in seconds +*/ +static PyObject* +capisuite_audio_receive(PyObject *, PyObject *args) +{ + Connection* conn; + char *filename; + int timeout, silence_timeout=0; + PyThreadState *_save; + int exit_DTMF=0; + long duration=0; + + if (!PyArg_ParseTuple(args,"O&si|ii:audio_receive",convertConnRef,&conn,&filename, &timeout, &silence_timeout,&exit_DTMF)) + return NULL; + + try { + Py_UNBLOCK_THREADS + AudioReceive active(conn,filename,timeout,silence_timeout,exit_DTMF); + active.mainLoop(); + duration=active.duration(); + Py_BLOCK_THREADS + } + catch (CapiWrongState e) { + Py_BLOCK_THREADS + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + catch (CapiExternalError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + PyObject *r=PyInt_FromLong(duration); + return (r); +} + +/** @brief Receive a fax in a fax mode connection + @ingroup python + + This command receives an analog fax (fax group 3). It starts the reception and waits for the end of the connection. + So it should be the last command before capisuite_disconnect. + + The connction must be in fax mode (use capisuite_connect_faxG3 or capisuite_switch_to_faxG3), otherwise an exception will be caused. + + The created file will be saved in the Structured Fax File (SFF) format. + + @param args Contains the python parameters. These are: + - call Reference to the current call + - filename (string) where to save received fax + @return None +*/ +static PyObject* +capisuite_fax_receive(PyObject *, PyObject *args) +{ + Connection *conn; + char *filename; + PyThreadState *_save; + + if (!PyArg_ParseTuple(args,"O&s:fax_receive",convertConnRef,&conn,&filename)) + return NULL; + + try { + Py_UNBLOCK_THREADS + FaxReceive active(conn,filename); + active.mainLoop(); + Py_BLOCK_THREADS + } + catch (CapiWrongState e) { + Py_BLOCK_THREADS + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + catch (CapiExternalError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + Py_XINCREF(Py_None); + return (Py_None); +} + +/** @brief Send a fax in a fax mode connection + @ingroup python + + This command sends an analog fax (fax group 3). It starts the send and waits for the end of the connection. + So it should be the last command before capisuite_disconnect. + + The connction must be in fax mode (use capisuite_call_faxG3 or capisuite_switch_to_faxG3), otherwise an exception will be caused. + + The created file will be saved in the Structured Fax File (SFF) format. + + @param args Contains the python parameters. These are: + - call Reference to the current call + - filename (string) file to send + @return None +*/ +static PyObject* +capisuite_fax_send(PyObject *, PyObject *args) +{ + Connection *conn; + char *filename; + PyThreadState *_save; + + if (!PyArg_ParseTuple(args,"O&s:fax_send",convertConnRef,&conn,&filename)) + return NULL; + + try { + Py_UNBLOCK_THREADS + FaxSend active(conn,filename); + active.mainLoop(); + Py_BLOCK_THREADS + } + catch (CapiWrongState e) { + Py_BLOCK_THREADS + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + catch (CapiError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + Py_XINCREF(Py_None); + return (Py_None); +} + +/** @brief Disconnect connection. + @ingroup python + + This will cause an immediate disconnection. It should be always the last command in every flow of a script. + It will return a tuple of two result values. The first is the disconnect cause of the physical connection, + the second the disconnect cause of the logical connection. See CAPI spec for the logical causes and + ETS 300 102-01 for the physical causes. + + @param args Contains the python parameters. These are: + - call Reference to the current call + @return Tuple containing (ReasonPhysical,ReasonLogical) +*/ +static PyObject* +capisuite_disconnect(PyObject *, PyObject *args) +{ + Connection *conn; + PyThreadState *_save; + + if (!PyArg_ParseTuple(args,"O&:disconnect",convertConnRef,&conn)) + return NULL; + + try { + Py_UNBLOCK_THREADS + DisconnectModule active(conn); + active.mainLoop(); + Py_BLOCK_THREADS + } + catch (CapiMsgError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + catch (CapiExternalError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + PyObject *r=Py_BuildValue("ii",conn->getCause(),conn->getCauseB3()); + return (r); +} + +/** @brief Reject an incoming call. + @ingroup python + + If you don't want to accept an incoming call for any reason (e.g. if it has a service or comes from a number + you don't want to accept), use this command. There are several reasons you can give when rejecting a call. + Some important ones are: + - 1=ignore call + - 2=normal call clearing + - 3=user busy + - 7=incompatible destination + - 8=destination out of order + - 0x34A9=temporary failure + + You can find many more reasons in the ETS 300 201-01 specification or on the web (search for ISDN cause) + if you really need them. + + @param args Contains the python parameters. These are: + - call Reference to the current call + - rejectCause (integer) which cause to signal when rejecting call (see above) + @return None +*/ +static PyObject* +capisuite_reject(PyObject *, PyObject *args) +{ + Connection *conn; + int rejectCause; + PyThreadState *_save; + + if (!PyArg_ParseTuple(args,"O&i:reject",convertConnRef,&conn,&rejectCause) ) + return NULL; + + try { + Py_UNBLOCK_THREADS + DisconnectModule active(conn,rejectCause); + active.mainLoop(); + Py_BLOCK_THREADS + } + catch (CapiMsgError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + catch (CapiExternalError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + Py_XINCREF(Py_None); + return (Py_None); +} + + +/** @brief helper function for capisuite_connect_voice() and capisuite_connect_faxG3() + + @param delay delay in seconds before connection will be established + @param service with which service we should connect + @param faxStationID only used for fax connections + @param faxHeadline only used for fax connections +*/ +static PyObject* +capisuite_connect(Connection *conn, int delay, Connection::service_t service, string faxStationID, string faxHeadline) +{ + PyThreadState *_save; + try { + Py_UNBLOCK_THREADS + if (delay) { + conn->acceptWaiting(); // so that connection doesn't timeout + sleep(delay); + } + ConnectModule active(conn,service, faxStationID, faxHeadline); + active.mainLoop(); + Py_BLOCK_THREADS + } + catch (CapiMsgError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + catch (CapiWrongState e) { + Py_BLOCK_THREADS + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + catch (CapiExternalError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + Py_XINCREF(Py_None); + return (Py_None); +} + +/** @brief Accept an incoming call and connect with voice service. + @ingroup python + + This will accept an incoming call and choose voice as service, so you can use the audio commands + (like audio_receive and audio_send) with this connection. After this command has finished, the call + is connected successfully. + + It's also possible to accept a call with some delay. This is for example useful for an answering + machine if you want to have the chance to get a call with your phone before your computer answers it. + + @param args Contains the python parameters. These are: + - call Reference to the current call + - delay (integer, optional) delay in seconds _before_ connection will be established (default: 0=immediate connect) + @return None +*/ +static PyObject* +capisuite_connect_voice(PyObject *, PyObject *args) +{ + Connection *conn; + int delay=0; + + if (!PyArg_ParseTuple(args,"O&|i:connect_voice",convertConnRef,&conn,&delay)) + return NULL; + + return capisuite_connect(conn,delay,Connection::VOICE,"",""); +} + +/** @brief Accept an incoming call and connect with fax (analog, group 3) service. + @ingroup python + + This will accept an incoming call and choose fax group 3 as service, so you can use the fax commands + (like fax_receive) with this connection. After this command has finished, the call is connected successfully. + + It's also possible to accept a call with some delay. This is for example useful if you want to have the chance + to get a call with your phone before your computer answers it. + + @param args Contains the python parameters. These are: + - call Reference to the current call + - faxStationID (string) the station ID to use + - faxHeadline (string) the fax headline to use + - delay (integer, optional) delay in seconds _before_ connection will be established (default: 0=immediate connect) + @return None +*/ +static PyObject* +capisuite_connect_faxG3(PyObject *, PyObject *args) +{ + Connection *conn; + int delay=0; + char *faxStationID, *faxHeadline; + + if (!PyArg_ParseTuple(args,"O&ss|i:connect_faxG3",convertConnRef,&conn,&faxStationID,&faxHeadline,&delay)) + return NULL; + + return capisuite_connect(conn,delay,Connection::FAXG3,faxStationID,faxHeadline); +} + +/** @brief helper function for capisuite_call_voice() and capisuite_call_faxG3() + + @param capi reference to object of Capi to use + @param controller controller number to use + @param call_from string containing the own number to use + @param call_to string containing the number to call + @param service service to call with as described in Connection::service_t + @param timeout timeout to wait for connection establishment + @param faxStationID fax station ID, only necessary when connecting in FAXG3 mode + @param faxHeadline fax headline, only necessary when connecting in FAXG3 mode + @param clir set to true to disable sending of own number + @return tuple (call,result) - call=reference to the created call object / result(int)=result of the call establishment +*/ +static PyObject* +capisuite_call(Capi *capi, unsigned short controller, string call_from, string call_to, Connection::service_t service, int timeout, string faxStationID, string faxHeadline, bool clir) +{ + PyThreadState *_save; + Connection* conn=NULL; + int result; + try { + Py_UNBLOCK_THREADS + CallOutgoing active(capi,controller,call_from,call_to,service,timeout,faxStationID,faxHeadline,clir); + active.mainLoop(); + conn=active.getConnection(); + result=active.getResult(); + Py_BLOCK_THREADS + } + catch (CapiMsgError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + catch (CapiExternalError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + PyObject *r=Py_BuildValue("Ni",PyCObject_FromVoidPtr(conn,capisuitemodule_destruct_connection),result); + return (r); +} + +/** @brief Initiate an outgoing call with service voice and wait for successful connection + @ingroup python + + This will initiate an outgoing call and choose voice as service, so you can use the audio commands + (like audio_receive and audio_send) with this connection. After this command has finished, the call + is connected successfully or the given timeout has exceeded. See the return value. + + A python tuple (call,result) is returned by this function. It contains of 2 values: + - call reference to the created call object - use this for subsequent calls like audio_send + - result (int) result of the call establishment process. + - 0 = connection established + - 1 = connection timeout exceeded, no connection was established + - 2 = connection wasn't successful and no reason for this failure is available + - 0x3301-0x34FF: Error reported by CAPI. For a complete description see the CAPI specification + and ETS 300102-1 from http://www.etsi.org or search for "ISDN cause" in Google ;-). + Here are some important values: + - 0x3301 Protocol error, Layer 1 + - 0x3302 Protocol error, Layer 2 + - 0x3303 Protocol error, Layer 3 + - 0x3480 normal call termination + - 0x3481 Unallocated (unassigned) number + - 0x3490 Normal call clearing + - 0x3491 User busy + - 0x3492 No user responding + - 0x3493 No answer from user (user alerted) + - 0x3495 Call rejected + - 0x3496 Number changed + - 0x349B Destination out of order + - 0x349C Invalid number format + - 0x34A2 No circuit / channel available + - 0x34A9 Temporary failure + - 0x34D8 Incompatible destination + + @param args Contains the python parameters. These are: + - capi reference to object of Capi to use (given to the idle function as parameter) + - controller (int) ISDN controller ID to use + - call_from (string)own number to use + - call_to (string)the number to call + - timeout (int)timeout to wait for connection establishment in seconds + - clir (int, optional)set to 1 to disable sending of own number (0=default) + @return tuple (call,result) - see above. +*/ +static PyObject* +capisuite_call_voice(PyObject *, PyObject *args) +{ + Capi *capi; + int controller, timeout, clir=0; + char *call_from,*call_to; + + if (!PyArg_ParseTuple(args,"O&issi|i:call_voice",convertCapiRef,&capi,&controller,&call_from,&call_to,&timeout,&clir)) + return NULL; + + return capisuite_call(capi,controller,call_from,call_to,Connection::VOICE,timeout,"","",clir); +} + +/** @brief Initiate an outgoing call with service faxG3 and wait for successful connection + @ingroup python + + This will initiate an outgoing call and choose fax group 3 as service, so you can use the fax commands + (like fax_send and fax_receive) with this connection. After this command has finished, the call + is connected successfully or the given timeout has exceeded. See the return value. + + A python tuple (call,result) is returned by this function. It contains of 2 values: + - call reference to the created call object - use this for subsequent calls like audio_send + - result (int) result of the call establishment process. + - 0 = connection established + - 1 = connection timeout exceeded, no connection was established + - 2 = connection wasn't successful and no reason for this failure is available + - 0x3301-0x34FF: Error reported by CAPI. For a complete description see the CAPI specification + and ETS 300102-1 from http://www.etsi.org or search for "ISDN cause" in Google ;-). + Here are some important values: + - 0x3301 Protocol error, Layer 1 + - 0x3302 Protocol error, Layer 2 + - 0x3303 Protocol error, Layer 3 + - 0x3480 normal call termination + - 0x3481 Unallocated (unassigned) number + - 0x3490 Normal call clearing + - 0x3491 User busy + - 0x3492 No user responding + - 0x3493 No answer from user (user alerted) + - 0x3495 Call rejected + - 0x3496 Number changed + - 0x349B Destination out of order + - 0x349C Invalid number format + - 0x34A2 No circuit / channel available + - 0x34A9 Temporary failure + - 0x34D8 Incompatible destination + + @param args Contains the python parameters. These are: + - capi reference to object of Capi to use (given to the idle function as parameter) + - controller (int) ISDN controller ID to use + - call_from (string)own number to use + - call_to (string)the number to call + - timeout (int)timeout to wait for connection establishment in seconds + - faxStationID (string)fax station ID + - faxHeadline (string) fax headline to print on every page + - clir (int, optional)set to 1 to disable sending of own number (0=default) + @return tuple (call,result) - see above. +*/ +static PyObject* +capisuite_call_faxG3(PyObject *, PyObject *args) +{ + Capi *capi; + int controller, timeout, clir=0; + char *call_from,*call_to,*faxStationID,*faxHeadline; + + if (!PyArg_ParseTuple(args,"O&ississ|i:call_faxG3",convertCapiRef,&capi,&controller,&call_from,&call_to,&timeout,&faxStationID,&faxHeadline,&clir)) + return NULL; + + return capisuite_call(capi,controller,call_from,call_to,Connection::FAXG3,timeout,faxStationID,faxHeadline,clir); +} + +/** @brief Switch a connection from voice mode to fax mode. + @ingroup python + + This will switch from voice mode to fax group 3 after you have connected, so you can use the fax commands afterwards. + + Attention: This command isn't supported by every ISDN card / CAPI driver! + + @param args Contains the python parameters. These are: + - call Reference to the current call + - faxStationID (string) the station ID to use + - faxHeadline (string) the fax headline to use + @return None +*/ +static PyObject* +capisuite_switch_to_faxG3(PyObject *, PyObject *args) +{ + Connection *conn; + char *faxStationID, *faxHeadline; + PyThreadState *_save; + + if (!PyArg_ParseTuple(args,"O&ss:switch_to_faxG3",convertConnRef,&conn,&faxStationID,&faxHeadline)) + return NULL; + + try { + Py_UNBLOCK_THREADS + Switch2FaxG3 active(conn,faxStationID,faxHeadline); + active.mainLoop(); + Py_BLOCK_THREADS + } + catch (CapiWrongState e) { + Py_BLOCK_THREADS + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + catch (CapiExternalError e) { + Py_BLOCK_THREADS + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + Py_XINCREF(Py_None); + return (Py_None); +} + +/** @brief Enable recognition of DTMF tones. + @ingroup python + + You have to enable the recognition of DTMF tones if you want to use them in your script. + + @param args Contains the python parameters. These are: + - call Reference to the current call + @return None +*/ +static PyObject* +capisuite_enable_DTMF(PyObject *, PyObject *args) +{ + Connection *conn; + + if (!PyArg_ParseTuple(args,"O&:enable_DTMF",convertConnRef,&conn) ) + return NULL; + + try { + conn->enableDTMF(); + } + catch (CapiWrongState) { // issued when we have no connection + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + catch (CapiMsgError e) { + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + Py_XINCREF(Py_None); + return (Py_None); +} + +/** @brief Disable recognition of DTMF tones. + @ingroup python + + You can disable the recognition of DTMF tones again if you want to. + + @param args Contains the python parameters. These are: + - call Reference to the current call + @return None +*/ +static PyObject* +capisuite_disable_DTMF(PyObject *, PyObject *args) +{ + Connection *conn; + + if (!PyArg_ParseTuple(args,"O&:disable_DTMF",convertConnRef,&conn) ) + return NULL; + + try { + conn->disableDTMF(); + } + catch (CapiWrongState) { // issued when we have no connection + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + catch (CapiMsgError e) { + PyErr_SetString(BackendError,(e.message()).c_str()); + return NULL; + } + + Py_XINCREF(Py_None); + return (Py_None); +} + +/** @brief Read the received DTMF tones or wait for a certain amount of them. + @ingroup python + + This function allows to just read in the DTMF tones which were already received. But it also supports to + wait for a certain amount of DTMF tones if you want the user to always input some digits at a certain + step in your script. + + You can specify how much DTMF tones you want in several ways - see the parameter description. To just + see if something was entered before, use capisuite.read_DTMF(0). If you want to get at least 1 and mostly 4 digits + and want to wait 5 seconds for additional digits, you'll use capisuite.read_DTMF(5,1,4). + + Valid DTMF characters are '0'...'9','A'...'D' and two special fax tones: 'X' (CNG), 'Y' (CED) + + @param args Contains the python parameters. These are: + - call Reference to the current call + - timeout (integer) timeout in seconds after which reading is terminated, only applied when min_digits are reached! (-1 = infinite) + - min_digits (integer, optional) minimum number of digits which must be read in ANY case, i.e. timout doesn't count here (default: 0) + - max_digits (integer, optional) maximum number of digits to read (aborts immediately if this number is reached) (0=infinite, i.e. only timeout counts, default) + @return python string containing the received DTMF characters +*/ +static PyObject* +capisuite_read_DTMF(PyObject *, PyObject *args) +{ + Connection *conn; + PyThreadState *_save; + int timeout, min_digits=0, max_digits=0; + + if (!PyArg_ParseTuple(args,"O&i|ii:read_DTMF",convertConnRef,&conn, &timeout, &min_digits, &max_digits) ) + return NULL; + + string dtmf_received; + try { + Py_UNBLOCK_THREADS + ReadDTMF active(conn,timeout,min_digits,max_digits); + active.mainLoop(); + dtmf_received=conn->getDTMF(); + conn->clearDTMF(); + Py_BLOCK_THREADS + } + catch (CapiWrongState e) { + Py_BLOCK_THREADS + PyErr_SetString(CallGoneError,"Call was finished from partner."); + return NULL; + } + + PyObject* result=Py_BuildValue("s",dtmf_received.c_str()); + return (result); +} + + +/** PCallControlMethods - array of functions in module capisuite +*/ +static PyMethodDef PCallControlMethods[] = { + {"audio_receive", capisuite_audio_receive, METH_VARARGS, "Receive audio. For further details see capisuite module reference."}, + {"audio_send", capisuite_audio_send, METH_VARARGS, "Send audio. For further details see capisuite module reference."}, + {"fax_receive", capisuite_fax_receive, METH_VARARGS, "Receive fax. For further details see capisuite module reference."}, + {"fax_send", capisuite_fax_send, METH_VARARGS, "Send fax. For further details see capisuite module reference."}, + {"disconnect", capisuite_disconnect, METH_VARARGS, "Disconnect call. For further details see capisuite module reference."}, + {"connect_voice", capisuite_connect_voice, METH_VARARGS, "Connect pending call with Telephony services. Arguments: call, delay"}, + {"connect_faxG3", capisuite_connect_faxG3, METH_VARARGS, "Connect pending call with FaxG3 services. For further details see capisuite module reference."}, + {"call_voice", capisuite_call_voice, METH_VARARGS, "Initiate an outgoing call with service voice. For further details see capisuite module reference."}, + {"call_faxG3", capisuite_call_faxG3, METH_VARARGS, "Initiate an outgoing call with service FaxG3. For further details see capisuite module reference."}, + {"switch_to_faxG3", capisuite_switch_to_faxG3, METH_VARARGS, "Switch from telephony to FaxG3 services. For further details see capisuite module reference."}, + {"reject", capisuite_reject, METH_VARARGS, "Reject waiting call. For further details see capisuite module reference."}, + {"enable_DTMF", capisuite_enable_DTMF, METH_VARARGS, "Enable DTMF recognition. For further details see capisuite module reference."}, + {"disable_DTMF", capisuite_disable_DTMF, METH_VARARGS, "Disable DTMF recognition. For further details see capisuite module reference."}, + {"read_DTMF", capisuite_read_DTMF, METH_VARARGS, "Read and clear received DTMF. For further details see capisuite module reference."}, + {"log", capisuite_log, METH_VARARGS, "Write log message. For further details see capisuite module reference."}, + {"error", capisuite_error, METH_VARARGS, "Write error message. For further details see capisuite module reference."}, + {NULL,NULL,0,NULL} +}; + +void +capisuitemodule_init () throw (ApplicationError) +{ + PyObject *mod,*d; + try { + if ( ! ( mod=Py_InitModule3("capisuite", PCallControlMethods, "Python module for controlling CapiSuite") ) ) // m=borrowed ref + throw ApplicationError("unable to init python module capisuite (InitModule failed)","capisuite_init()"); + + if ( ! ( d=PyModule_GetDict(mod) ) ) // d=borrowed ref + throw ApplicationError("unable to init python module capisuite (GetDict(mod) failed)","capisuite_init()"); + + if (!CallGoneError) + if (! (CallGoneError=PyErr_NewException("capisuite.CallGoneError", NULL, NULL) ) ) + throw ApplicationError("unable to init python module capisuite (NewException for CallGoneError failed)","capisuite_init()"); + if (!BackendError) + if (! (BackendError=PyErr_NewException("capisuite.BackendError", NULL, NULL) ) ) + throw ApplicationError("unable to init python module capisuite (NewException for BackendError failed)","capisuite_init()"); + + if (PyDict_SetItemString(d, "CallGoneError", CallGoneError)!=0) + throw ApplicationError("unable to init python module capisuite (SetItemString for CallGoneError failed)","capisuite_init()"); + if (PyDict_SetItemString(d, "BackendError", BackendError)!=0) + throw ApplicationError("unable to init python module capisuite (SetItemString for BackendError failed)","capisuite_init()"); + + if (PyModule_AddIntConstant(mod, "SERVICE_VOICE", Connection::VOICE) != 0) + throw ApplicationError("unable to init python module capisuite (adding constant SERVICE_VOICE failed)","capisuite_init()"); + if (PyModule_AddIntConstant(mod, "SERVICE_FAXG3", Connection::FAXG3) != 0) + throw ApplicationError("unable to init python module capisuite (adding constant SERVICE_FAXG3 failed)","capisuite_init()"); + if (PyModule_AddIntConstant(mod, "SERVICE_OTHER", Connection::OTHER) != 0) + throw ApplicationError("unable to init python module capisuite (adding constant SERVICE_OTHER failed)","capisuite_init()"); + } + catch(ApplicationError) { + if (CallGoneError) + Py_DECREF(CallGoneError); + if (BackendError) + Py_DECREF(BackendError); + throw; + } +} + + +/* History + +$Log: capisuitemodule.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.21 2003/01/31 11:26:46 ghillie +- add "extern capisuiteInstance" +- add two missing Py_BLOCK_THREADS in capisuite_reject() + +Revision 1.20 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.19 2003/01/19 12:07:47 ghillie +- new functions capisuite_log and capisuite_error (resolves TODO) + +Revision 1.18 2003/01/16 13:01:21 ghillie +- updated comment of audio_receive: truncates silence now + +Revision 1.17 2003/01/04 15:54:58 ghillie +- use new *Message functions of Connection + +Revision 1.16 2002/12/16 13:12:06 ghillie +- destruct_connection now uses correct debug stream +- changed disconnect() to return cause and causeB3 + +Revision 1.15 2002/12/13 11:44:34 ghillie +added support for fax send + +Revision 1.14 2002/12/13 09:57:10 ghillie +- error message formatting done by exception classes now + +Revision 1.13 2002/12/11 13:37:25 ghillie +- use quick_disconnect in destruct when connection is still established + because this is the result of some error + +Revision 1.12 2002/12/11 13:00:19 ghillie +- modified capisuitemodule_call_* and convertFlowControlRef (now named + convertCapiRef) to use Capi ptr instead of FlowControl ptr + +Revision 1.11 2002/12/10 15:03:53 ghillie +- capisuitemodule_destruct_connection does nice disconnection now + +Revision 1.10 2002/12/07 22:35:46 ghillie +- capisuite_connect(): removed PyErr_Occured() check +- capisuitemodule_init: doesn't return __main__ namespace any more +- FIX in capisuitemodule_init: don't double create exceptions any more! +- capisuitemodule_init: use shorter PyModule_AddIntConstant() instead of + manual creation and registration + +Revision 1.9 2002/12/06 15:24:25 ghillie +- reject uses DisconnectModule, too, now and waits for disconnection + +Revision 1.8 2002/12/06 12:53:08 ghillie +- added destruction function for Connection objects +- removed capisuitemodule_call_gone() +- changed capisuite_disconnect(): use DisconnectModule, wait for disconnect, + return ISDN disconnect cause +- fixed some typos in PyArg_ParseTuple() calls +- changed capisuite_call* to return a tuple (call,result) + +Revision 1.7 2002/12/05 15:54:22 ghillie +- removed checks for PyErr_Occured() as exceptions won't be thrown in from outside any more + +Revision 1.6 2002/12/05 14:49:47 ghillie +- added convertFlowControlRef() +- new python functions: call_voice and call_faxG3 for initiating outgoing calls + +Revision 1.5 2002/12/04 10:38:14 ghillie +- audio_send and audio_receive do return duration now + +Revision 1.4 2002/12/02 16:53:23 ghillie +- some minor fixes in the docu strings +- typo (SERVICE_SPEECH->SERVICE_VOICE) + +Revision 1.3 2002/12/02 12:26:31 ghillie +- renamed Connection::SPEECH to Connection::VOICE +- 3 new constants defined in init function: SERVICE_VOICE, SERVICE_FAXG3, SERVICE_OTHER +- moved DECREF's to exception handler where appropriate + +Revision 1.2 2002/11/29 11:37:39 ghillie +- missed some changes from CapiCom to CapiSuite + +Revision 1.1 2002/11/29 11:06:22 ghillie +renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) + +Revision 1.5 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.4 2002/11/27 15:56:56 ghillie +renamed connect_telephony to connect_voice + +Revision 1.3 2002/11/25 11:48:48 ghillie +- improved parameter descriptions for python functions +- made more python parameter optional +- removed CIPvalue from application layer, use service type instead +- renamed get_DTMF() to read_DTMF(), added additional parameters (timeout, min_/max_digits), uses ReadDTMF-module now + +Revision 1.2 2002/11/23 15:55:51 ghillie +changed some misleading function names + +Revision 1.1 2002/11/22 15:44:54 ghillie +renamed pcallcontrol.* to capicommodule.* + +Revision 1.18 2002/11/22 15:07:09 ghillie +- new python function switch_to_faxG3 +- new parameter exit_DTMF for audio_send() and audio_receive() +- new parameters faxStationID and faxHeadline for connect_faxG3 +- get_DTMF calls conn->clearDTMF() now + +Revision 1.17 2002/11/21 15:26:19 ghillie +- removed some unnecessary commands (we don't need result of mainLoop() any more, no need to use pointers for call modules any more) +- removed python command connect(call,CIP), added connect_telephony() and connect_faxG3() +- connect_telephony() and connect_faxG3 take delay in seconds before connect as parameter now +- unified python function names (enableDTMF -> enable_DTMF, disableDTMF -> disable_DTMF, getDTMF -> get_DTMF + +Revision 1.16 2002/11/20 17:23:04 ghillie +- fixed locking in the case of an exception. Python locks weren't set-up again when an exception occurred +- removed unnecessary conn->isUp() calls (handled via exceptions now) +- CapiWrongState exception causes CallGoneError to be issued in Python now +- removed unnecessary PyErr_Occured() calls at the end of each function +- added missing Py_XDECREF in _init() + +Revision 1.15 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.14 2002/11/18 14:21:07 ghillie +- moved global severity_t to ApplicationError::severity_t +- added throw() declarations to header files + +Revision 1.13 2002/11/18 12:08:46 ghillie +return reference to GetDict(__main__) instead of GetDict(pcallcontrol), so that callIncoming can live in __main__ + +Revision 1.12 2002/11/17 14:36:32 ghillie +improved error handling: script will now terminate if B3 connection is lost +(each function checks for PyErr_Occurred() and conn->isUp() now) + +Revision 1.11 2002/11/14 17:04:05 ghillie +* major structural changes - much is easier, nicer and better prepared for the future now: + - added DisconnectLogical handler to CallInterface + - DTMF handling moved from CallControl to Connection + - new call module ConnectModule for establishing connection + - python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) + - call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +* converter function for PyCObject -> Connection pointer introduced + -> this saves us many identical code lines in all Python/C integration + functions + +Revision 1.10 2002/11/13 15:23:21 ghillie +added new parameter to audio_receive (silence_timeout) +fixed deadlock: in deletion of call modules must happen w/o holding python global lock + +Revision 1.9 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.8 2002/11/10 17:03:45 ghillie +now CallControl reference is passed directly to the called Pyhton functions + +Revision 1.7 2002/11/06 16:16:07 ghillie +added code to raise CallGoneError in any case so the script is cancelled when the call is gone surely + +Revision 1.6 2002/10/31 12:35:58 ghillie +added DTMF support + +Revision 1.5 2002/10/30 16:05:20 ghillie +cosmetic fixes... + +Revision 1.4 2002/10/30 14:25:54 ghillie +added connect,disconnect,reject functions, changed init function to return the module dictionary + +Revision 1.3 2002/10/29 14:07:38 ghillie +changed to implecete pass call parameter so the user doesn't have to type it in the script... + +Revision 1.2 2002/10/27 12:47:20 ghillie +- added multithread support for python +- changed callcontrol reference to stay in the python namespace +- changed ApplicationError to support differen severity + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.3 2002/10/24 09:55:52 ghillie +many fixes. Works for one call now + +Revision 1.2 2002/10/23 15:42:11 ghillie +- added standard headers +- changed initialization code (object references now set in extra function) +- added some missing Py_None + +*/ diff --git a/src/application/capisuitemodule.h b/src/application/capisuitemodule.h new file mode 100644 index 0000000..c045fb3 --- /dev/null +++ b/src/application/capisuitemodule.h @@ -0,0 +1,124 @@ +/** @file capisuitemodule.h + @brief Contains the Python module integration routines + + This file contains the implementation of thy python module + capisuite which contains all commands available in python scripts + for programming capisuite. + + There are two groups of functions: functions used from C++ to init + and access the python module and functions used from python implementing + the functions of the python module. + + Here you'll only find the functions used from C++. If you're interested + in the commands usable from python, please have a look at the documentation + found in @ref python. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef PCAPICOMMODULE_H +#define PCAPICOMMODULE_H + +#include +#include "applicationexception.h" + +class Connection; +class CallControl; + +/** @brief Initializes and registers C implementation of python module capisuite + + This function creates a new python module named "capisuite" containing the + functions for the control of capisuite and two exception types: CallGoneError + and BackendError (see @ref python). Also there are three constants defined: + SERVICE_VOICE, SERVICE_FAXG3, SERVICE_OTHER, see also Connection::service_t. + + @return borrowed reference to the __main__-Dictionary of the created python interpreter + @throw ApplicationError Thrown if some step of the module initialization fails. See errormsg for details. +*/ +void capisuitemodule_init() throw (ApplicationError); + +/** @brief Destructor function for Connection reference given to Python scripts. + + This function will be called by Python if the given connection reference is not used any + more in the script. This will lead to the destruction of the Connection object. + + This function has the right signature to pass as destructor function for PyCCobject_FromVoidPtr() calls. + + @param conn Connection reference +*/ +void capisuitemodule_destruct_connection(void* conn); + +#endif + +/* History + +$Log: capisuitemodule.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.5 2002/12/07 22:36:21 ghillie +- capisuitemodule_init: doesn't return __main__ any more + +Revision 1.4 2002/12/06 12:54:11 ghillie +- removed capisuitemodule_call_gone() (CallGoneException won't be thrown in + from somewhere any more) +- added destruction function for Connection objects + +Revision 1.3 2002/12/05 14:50:05 ghillie +- comment improvement + +Revision 1.2 2002/12/02 12:26:51 ghillie +- update description to new behaviour of service parameter + +Revision 1.1 2002/11/29 11:06:22 ghillie +renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) + +Revision 1.2 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.1 2002/11/22 15:44:54 ghillie +renamed pcallcontrol.* to capicommodule.* + +Revision 1.7 2002/11/18 14:21:07 ghillie +- moved global severity_t to ApplicationError::severity_t +- added throw() declarations to header files + +Revision 1.6 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.5 2002/11/10 17:03:45 ghillie +now CallControl reference is passed directly to the called Pyhton functions + +Revision 1.4 2002/11/06 16:16:07 ghillie +added code to raise CallGoneError in any case so the script is cancelled when the call is gone surely + +Revision 1.3 2002/10/30 14:25:54 ghillie +added connect,disconnect,reject functions, changed init function to return the module dictionary + +Revision 1.2 2002/10/27 12:47:20 ghillie +- added multithread support for python +- changed callcontrol reference to stay in the python namespace +- changed ApplicationError to support differen severity + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.3 2002/10/24 09:55:52 ghillie +many fixes. Works for one call now + +Revision 1.2 2002/10/23 15:42:11 ghillie +- added standard headers +- changed initialization code (object references now set in extra function) +- added some missing Py_None + +*/ diff --git a/src/application/idlescript.cpp b/src/application/idlescript.cpp new file mode 100644 index 0000000..544ef92 --- /dev/null +++ b/src/application/idlescript.cpp @@ -0,0 +1,186 @@ +/* @file incomingscript.cpp + @brief Contains IncomingScript - Incoming call handling. One object for each incoming call is created. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "idlescript.h" +#include "capisuitemodule.h" + +void* idlescript_exec_handler(void* arg) +{ + if (!arg) { + cerr << "FATAL ERROR: no IdleScript reference given in idlescript_exec_handler" << endl; + exit(1); + } + pthread_cleanup_push(idlescript_cleanup_handler,arg); + IdleScript *instance=static_cast(arg); + instance->run(); + pthread_cleanup_pop(1); // run the cleanup_handler and then deregister it +} + +void idlescript_cleanup_handler(void* arg) +{ + if (!arg) { + cerr << "FATAL ERROR: no IdleScript reference given in idlescript_exec_handler" << endl; + exit(1); + } + IdleScript *instance=static_cast(arg); + instance->final(); +} + +IdleScript::IdleScript(ostream &debug, unsigned short debug_level, ostream &error, Capi *capi, string idlescript, int idlescript_interval, PyThreadState *py_state, PycStringIO_CAPI* cStringIO) throw (ApplicationError) +:PythonScript(debug,debug_level,error,idlescript,"idle",cStringIO),idlescript_interval(idlescript_interval),py_state(py_state),capi(capi),active(true) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); + int ret=pthread_create(&thread_handle, &attr, idlescript_exec_handler, this); // start thread as detached + if (ret) + throw ApplicationError("error while creating thread","IdleScript::IdleScript()"); + + if (debug_level>=3) + debug << prefix() << "IdleScript created." << endl; +} + +IdleScript::~IdleScript() +{ + if (debug_level>=3) + debug << prefix() << "IdleScript deleted" << endl; +} + +void +IdleScript::run() throw() +{ + int count=0,errorcount=0; + timespec delay_time; + delay_time.tv_sec=0; delay_time.tv_nsec=100000000; // 100 msec + while (1) { + pthread_testcancel(); // cancellation point + nanosleep(&delay_time,NULL); + count++; + if (active && (count>=idlescript_interval*10)) { + count=0; + PyObject *capi_ref=NULL; + try { + if (debug_level>=3) + debug << prefix() << "executing idlescript..." << endl; + PyEval_RestoreThread(py_state); // acquire lock, switch to right thread context + + capisuitemodule_init(); + + capi_ref=PyCObject_FromVoidPtr(capi,NULL); // new ref + if (!capi_ref) + throw ApplicationError("unable to create CObject from Capi reference","IdleScript::run()"); + + args=Py_BuildValue("(O)",capi_ref); // args = new ref + if (!args) + throw ApplicationError("can't build arguments","IdleScript::run()"); + + PythonScript::run(); + + Py_DECREF(args); + args=NULL; + + Py_DECREF(capi_ref); + capi_ref=NULL; + + if (PyEval_SaveThread()!=py_state) // release lock + throw ApplicationError("can't release thread lock","IdleScript::run()"); + errorcount=0; + if (debug_level>=3) + debug << prefix() << "idlescript finished..." << endl; + } + catch (ApplicationError e) { + errorcount++; + error << prefix() << "IdleScript " << this << " Error occured. " << endl; + error << prefix() << "message was: " << e << endl; + if (errorcount>9) { + error << prefix() << "Too much subsequent errors. Disabling idle script." << endl; + active=false; + } + + if (args) + Py_DECREF(args); + if (capi_ref) + Py_DECREF(capi_ref); + if (PyEval_SaveThread()!=py_state) // release lock + throw ApplicationError("can't release thread lock","IdleScript::run()"); + } + } + } +} + +void +IdleScript::requestTerminate() +{ + pthread_cancel(thread_handle); +} + +void +IdleScript::activate() +{ + active=true; +} + +/* History + +$Log: idlescript.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.12 2003/02/10 14:17:09 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.11.2.1 2003/02/09 15:03:41 ghillie +- rewritten to use native pthread_* calls instead of CommonC++ Thread + +Revision 1.11 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.10 2003/01/18 12:52:50 ghillie +- pass on reference to Python C API to PythonScript + +Revision 1.9 2003/01/17 15:11:34 ghillie +- added debug output for finish of idlescript + +Revision 1.8 2003/01/06 21:02:01 ghillie +- won't exit if script causes too much errors - only temporarily deactivate + script execution, can be re-enabled with activate() +- added debug output for script execution + +Revision 1.7 2003/01/06 16:21:58 ghillie +- renamed terminate() to requestTerminate() to avoid endless recursion +- use finish flag instead of call to terminate() in requestTerminate() and run() + +Revision 1.6 2003/01/04 16:00:53 ghillie +- log improvements: log_level, timestamp + +Revision 1.5 2002/12/16 13:12:44 ghillie +- removed double output of error message + +Revision 1.4 2002/12/14 14:02:22 ghillie +- added terminate() method (make terminate public) +- added error counting code to de-activate idle-script after 10 errors + +Revision 1.3 2002/12/13 09:57:10 ghillie +- error message formatting done by exception classes now + +Revision 1.2 2002/12/11 13:03:50 ghillie +- finished (use Thred::sleep() instead of nanosleep(), fix in if condition) + +Revision 1.1 2002/12/10 15:54:08 ghillie +- initial checkin, will take over functionality from FlowControl::executeIdleScript() + +*/ diff --git a/src/application/idlescript.h b/src/application/idlescript.h new file mode 100644 index 0000000..5f6a78f --- /dev/null +++ b/src/application/idlescript.h @@ -0,0 +1,160 @@ +/** @file idlescript.h + @brief Contains IdleScript - Implements calling of python script in regular intervals for user defined activity (e.g. sending faxes). + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef IDLESCRIPT_H +#define IDLESCRIPT_H + +#include +#include "applicationexception.h" +#include "pythonscript.h" + +class Capi; +class PycStringIO_CAPI; + +/** @brief Thread exec handler for IdleScript class + + This is a handler which will call this->run() for the use in pthread_create(). + It will also register idlescript_cleanup_handler +*/ +void* idlescript_exec_handler(void* arg); + +/** @brief Thread clean handler for IdleScript class + + This is a handler which is called by pthreads at cleanup. + It will call this->final(). +*/ +void idlescript_cleanup_handler(void* arg); + +/** @brief Implements calling of python script in regular intervals for user defined activity (e.g. sending faxes). + + Executes a given idle script at regular intervals thus giving the user the ability to + do arbitrary things. The main use is surely initiating outgoing calls, e.g. to send faxes. + + It creates one new thread which will execute the idle script over and over... + + If the script fails too often, it's deactivated. After fixing the script, it can be reactivated + with activate(). + + @author Gernot Hillier +*/ + +class IdleScript: public PythonScript +{ + friend void* idlescript_exec_handler(void*); + friend void idlescript_cleanup_handler(void*); + public: + /** @brief Constructor. Create Object and start a detached thread + + @param debug stream for debugging info + @param debug_level verbosity level for debug messages + @param error stream for error messages + @param capi reference to Capi object + @param idlescript file name of the python script to use as incoming script + @param idlescript_interval interval between two subsequent calls to the idle script in seconds + @param py_state thread state of the main python interpreter which must be initialized an Py_SaveThread()'d before. + @param cStringIO pointer to the Python cStringIO C API + @throw ApplicationError Thrown if thread can't be started + */ + IdleScript(ostream &debug, unsigned short debug_level, ostream &error, Capi *capi, string idlescript, int idlescript_interval, PyThreadState *py_state, PycStringIO_CAPI* cStringIO) throw (ApplicationError); + + /** @brief Destructor. Destruct object. + */ + virtual ~IdleScript(); + + /** @brief terminate thread + */ + void requestTerminate(void); + + /** @brief reactivate the script execution in the case it was deactivated by too much errors + */ + void activate(void); + + private: + /** @brief Thread body. Calls the python function idle(). + + The read Python idle script must provide a function named idle with the following signature: + + def idle(capi): + # function body + + The parameters given to the python function are: + - capi: reference to the capi providing the interface to the ISDN hardware (meeded for the call_*() function class) + + The script is responsible for clearing each call it initiates, even in the exception handlers! + + If the call is disconnected by the other party, the Python exception CallGoneError is raised and should be caught + by the script (don't forget to call disconnect() there). + + If the script produces too much errors in a row, it will be deactivated. Use activate() to re-enable. + + The python global lock will be acquired while the function runs. + */ + virtual void run(void) throw(); + + PyThreadState *py_state; ///< py_state of the main python interpreter used for run(). + string idlescript; ///< name of the python script which is called at regular intervals + int idlescript_interval; ///< interval between subsequent executions of idle script + Capi *capi; ///< reference to Capi object + bool active; ///< used to disable IdleScript in case of too much errors + + pthread_t thread_handle; ///< handle for the created pthread thread +}; + +#endif + +/* History + +$Log: idlescript.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.9 2003/02/10 14:17:09 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.8.2.2 2003/02/10 14:04:57 ghillie +- made destructors virtual, otherwise wrong destructor is called! + +Revision 1.8.2.1 2003/02/09 15:03:41 ghillie +- rewritten to use native pthread_* calls instead of CommonC++ Thread + +Revision 1.8 2003/01/18 12:53:06 ghillie +- pass on reference to Python C API to PythonScript + +Revision 1.7 2003/01/13 21:25:13 ghillie +- improved comment for finish flag + +Revision 1.6 2003/01/06 21:02:56 ghillie +- added support for deactivating/activating script execution w/o exiting the + thread (new method activate, comment changes) + +Revision 1.5 2003/01/06 16:22:24 ghillie +- renamed terminate() to requestTerminate() to avoid name-conflict +- added finish flag + +Revision 1.4 2003/01/04 16:00:53 ghillie +- log improvements: log_level, timestamp + +Revision 1.3 2002/12/14 14:02:51 ghillie +- added terminate() method +- added error counting code to run() to deactivate after 10 errors + +Revision 1.2 2002/12/11 13:04:35 ghillie +- minor improvements in comments, ... + +Revision 1.1 2002/12/10 15:54:08 ghillie +- initial checkin, will take over functionality from FlowControl::executeIdleScript() + +*/ diff --git a/src/application/incomingscript.cpp b/src/application/incomingscript.cpp new file mode 100644 index 0000000..464977a --- /dev/null +++ b/src/application/incomingscript.cpp @@ -0,0 +1,318 @@ +/* @file incomingscript.cpp + @brief Contains IncomingScript - Incoming call handling. One object for each incoming call is created. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "incomingscript.h" +#include "../modules/disconnectmodule.h" +#include "capisuitemodule.h" + +#define TEMPORARY_FAILURE 0x34A9 // see ETS 300 102-1, Table 4.13 (cause information element) + +void* incomingscript_exec_handler(void* arg) +{ + if (!arg) { + cerr << "FATAL ERROR: no IncomingScript reference given in incomingscript_exec_handler" << endl; + exit(1); + } + pthread_cleanup_push(incomingscript_cleanup_handler,arg); + IncomingScript *instance=static_cast(arg); + instance->run(); + pthread_cleanup_pop(1); // run the cleanup_handler and then deregister it +} + +void incomingscript_cleanup_handler(void* arg) +{ + if (!arg) { + cerr << "FATAL ERROR: no IncomingScript reference given in incomingscript_exec_handler" << endl; + exit(1); + } + IncomingScript *instance=static_cast(arg); + instance->final(); +} + +IncomingScript::IncomingScript(ostream &debug, unsigned short debug_level, ostream &error, Connection *conn, string incoming_script, PycStringIO_CAPI* cStringIO) throw (ApplicationError) +:PythonScript(debug,debug_level,error,incoming_script,"callIncoming",cStringIO),conn(conn) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); + int ret=pthread_create(&thread_handle, &attr, incomingscript_exec_handler, this); // start thread as detached + if (ret) + throw ApplicationError("error while creating thread","PythonScript::PythonScript()"); + + if (debug_level>=2) + debug << prefix() << "Connection " << conn << " created IncomingScript" << endl; +} + +IncomingScript::~IncomingScript() +{ + if (conn) { + error << prefix() << "Warning: Connection still established in IncomingScript destructor. Disconnecting." << endl; + try { + DisconnectModule active(conn,TEMPORARY_FAILURE); + active.mainLoop(); + } + catch (CapiError e) { + error << prefix() << "ERROR: disconnection also failed. Too bad..." << endl; + } + delete conn; + } + if (debug_level>=2) + debug << prefix() << "IncomingScript deleted" << endl; +} + +void +IncomingScript::run() throw() +{ + PyObject *conn_ref=NULL; + PyThreadState *py_state=NULL; + + try { + // thread safe Python init, taken out of PyApache 4.26 + PyEval_AcquireLock(); + + if (!(py_state=Py_NewInterpreter() )) { + PyEval_ReleaseLock(); + capisuitemodule_destruct_connection(conn); + throw ApplicationError("error while creating new python interpreter","IncomingScript::run()"); + } + + capisuitemodule_init(); + + conn_ref=PyCObject_FromVoidPtr(conn,capisuitemodule_destruct_connection); // new ref + if (!conn_ref) { + capisuitemodule_destruct_connection(conn); + throw ApplicationError("unable to create CObject from Connection reference","IncomingScript::run()"); + } + + args=Py_BuildValue("Oiss",conn_ref,conn->getService(),conn->getCallingPartyNumber().c_str(),conn->getCalledPartyNumber().c_str()); + if (!args) + throw ApplicationError("error during argument building","IncomingScript::run()"); + + PythonScript::run(); + + Py_DECREF(args); + args=NULL; + + Py_DECREF(conn_ref); + conn_ref=NULL; + conn=NULL; // Connection object will be deleted by Python destruction handler... + + Py_EndInterpreter(py_state); + py_state=NULL; + PyEval_ReleaseLock(); // release lock + } + catch(ApplicationError e) { + error << prefix() << "Error occured. message was: " << e << endl; + + if (args) + Py_DECREF(args); + if (conn_ref) { + Py_DECREF(conn_ref); + conn=NULL; + } + if (py_state) { + Py_EndInterpreter(py_state); + py_state=NULL; + PyEval_ReleaseLock(); + } + } +} + +/* History + +$Log: incomingscript.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.9 2003/02/10 14:17:09 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.8.2.1 2003/02/09 15:03:41 ghillie +- rewritten to use native pthread_* calls instead of CommonC++ Thread + +Revision 1.8 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.7 2003/01/19 12:08:47 ghillie +- changed some debug_levels + +Revision 1.6 2003/01/18 12:53:06 ghillie +- pass on reference to Python C API to PythonScript + +Revision 1.5 2003/01/04 16:00:53 ghillie +- log improvements: log_level, timestamp + +Revision 1.4 2002/12/14 14:03:27 ghillie +- added throw() declaration to run() method + +Revision 1.3 2002/12/13 09:57:10 ghillie +- error message formatting done by exception classes now + +Revision 1.2 2002/12/10 15:52:32 ghillie +- removed debug output + +Revision 1.1 2002/12/10 15:01:08 ghillie +- class IncomingScript now takes over the functionality of the old CallControl + class defined in callcontrol.*, but uses a base class now + +Revision 1.29 2002/12/09 15:23:49 ghillie +- moved start() out of constructor in creator (was unspecified this way!) +- moved disconnection to destructor so it's assured it happens +- exception severity cleanup (no more WARNING exceptions) +- python reference counting cleanup +- added debug stream as constructor parameter, debug output improvement + +Revision 1.28 2002/12/07 22:30:48 ghillie +- removed copying of filename to new char*, used const_cast from (const char*) + to (char*) instead +- moved python initialization code from constructor to run(), makes some + attributes obsolete +- getting __main__ namespace now taken out of capisuitemodule_init(), done + here instead +- use DisconnectModule for error handling now + +Revision 1.27 2002/12/06 15:23:14 ghillie +- wait for successful disconnection when an error in the script occured + +Revision 1.26 2002/12/06 12:50:02 ghillie +- passed the destruction function capisuitemodule_destruct_connection to the PyCObject containing connection reference + +Revision 1.25 2002/12/05 15:52:48 ghillie +- begin restructuring for self deletion of Connection object after it gets its OK from CallControl/FlowControl + +Revision 1.24 2002/12/05 14:48:25 ghillie +- cleaned up some python reference counting + +Revision 1.23 2002/12/02 12:21:56 ghillie +- incoming script name is now a parameter to constructor, not #define'd any more +- service parameter to python script now uses constants defined in Connection::service_t +- SEGV FIX: isRunning is now set to false before ending Python interpreter in run(), callCompleted() acquires + python global lock _before_ reading isRunning -> race condition fixed +- exception handler in run() ends python interpreter correctly now + +Revision 1.22 2002/11/29 11:09:04 ghillie +renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) + +Revision 1.21 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.20 2002/11/25 11:44:48 ghillie +removed CIP value from application, use service type instead + +Revision 1.19 2002/11/23 15:54:40 ghillie +pcallcontrol was renamed to capicommodule + +Revision 1.18 2002/11/21 11:34:08 ghillie +- changed Reject cause when we have a problem from "Destination Out Of Order" to "Temporary Failure" +- moved Py_EndInterpreter from destructor to run() +- new method callCompleted() which throws CallGoneError into Python +- new method final() which is called automatically after thread has finished, now CallControl objects will delete themselves + to allow cleanup routines in Python scripts which may take some time to finish w/o freezing the whole application + +Revision 1.17 2002/11/20 17:16:24 ghillie +- SEGV-Fix: CallGoneError only triggered if python script is still running in CallControl::~CallControl +- changed sleep in mainLoop() to nanosleep +- small typo fixed + +Revision 1.16 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.15 2002/11/18 14:21:07 ghillie +- moved global severity_t to ApplicationError::severity_t +- added throw() declarations to header files + +Revision 1.14 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.13 2002/11/13 15:21:22 ghillie +added some error handling for python states + +Revision 1.12 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.11 2002/11/12 15:47:27 ghillie +added dataIn-handler + +Revision 1.10 2002/11/10 17:02:22 ghillie +changed to pass CallControl reference to the called python functions + +Revision 1.9 2002/11/07 08:19:04 ghillie +some improvements and fixes in Python global lock and thread state handling + +Revision 1.8 2002/11/06 16:16:07 ghillie +added code to raise CallGoneError in any case so the script is cancelled when the call is gone surely + +Revision 1.7 2002/10/31 12:35:58 ghillie +added DTMF support + +Revision 1.6 2002/10/30 16:05:20 ghillie +cosmetic fixes... + +Revision 1.5 2002/10/30 14:24:41 ghillie +added support for python call handling before call is connected + +Revision 1.4 2002/10/30 10:45:51 ghillie +added #define for value which should go to config file later + +Revision 1.3 2002/10/29 14:06:36 ghillie +several fixes in run method + +Revision 1.2 2002/10/27 12:47:20 ghillie +- added multithread support for python +- changed callcontrol reference to stay in the python namespace +- changed ApplicationError to support differen severity + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.9 2002/10/24 09:55:52 ghillie +many fixes. Works for one call now + +Revision 1.8 2002/10/23 15:40:15 ghillie +added python integration... + +Revision 1.7 2002/10/10 12:45:40 gernot +added AudioReceive module, some small details changed + +Revision 1.6 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.5 2002/10/09 11:18:59 gernot +cosmetic changes (again...) and changed info function of CAPI class + +Revision 1.4 2002/10/05 20:43:32 gernot +quick'n'dirty, but WORKS + +Revision 1.3 2002/10/05 13:53:00 gernot +changed to use thread class of CommonC++ instead of the threads-package +some cosmetic improvements (indentation...) + +Revision 1.2 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.1 2002/10/04 13:28:43 gernot +CallControll class added + +*/ diff --git a/src/application/incomingscript.h b/src/application/incomingscript.h new file mode 100644 index 0000000..34d0134 --- /dev/null +++ b/src/application/incomingscript.h @@ -0,0 +1,216 @@ +/** @file incomingscript.h + @brief Contains IncomingScript - Incoming call handling. One object for each incoming call is created. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef INCOMINGSCRIPT_H +#define INCOMINGSCRIPT_H + +#include "applicationexception.h" +#include "pythonscript.h" + +class Connection; +class PycStringIO_CAPI; + +/** @brief Thread exec handler for IncomingScript class + + This is a handler which will call this->run() for the use in pthread_create(). + It will also register incomingscript_cleanup_handler +*/ +void* incomingscript_exec_handler(void* arg); + +/** @brief Thread clean handler for IncomingScript class + + This is a handler which is called by pthreads at cleanup. + It will call this->final(). +*/ +void incomingscript_cleanup_handler(void* arg); + +/** @brief Incoming call handling. One object for each incoming call is created. + + IncomingScript handels an incoming connection. For each connection, one object + of it is created by FlowControl. It mainly creates a new thread with an own + python subinterpreter, initializes the capisuitemodule, and calls run() of + PythonScript which will execute the defined function in the script. + + @author Gernot Hillier +*/ +class IncomingScript: public PythonScript +{ + friend void* incomingscript_exec_handler(void*); + friend void incomingscript_cleanup_handler(void*); + + public: + /** @brief Constructor. Create Object and start detached thread + + @param debug stream for debugging info + @param debug_level verbosity level for debug messages + @param error stream for error messages + @param conn reference to according connection (disconnected if error occurs) + @param incoming_script file name of the python script to use as incoming script + @param cStringIO pointer to the Python cStringIO C API + @throw ApplicationError Thrown if thread can't be started + */ + IncomingScript(ostream &debug, unsigned short debug_level, ostream &error, Connection *conn, string incoming_script, PycStringIO_CAPI* cStringIO) throw (ApplicationError); + + /** @brief Destructor. Destruct object and assure the call is disconnected. + */ + virtual ~IncomingScript(); + + private: + /** @brief Thread body. Calls the python function callIncoming() which will handle the call. + + Create python sub-interpreter, read script for incoming calls, + + The read Python script for incoming calls must provide a function named callIncoming with the following signature: + + def callIncoming(call, service, callingParty, calledParty): + # function body + + The parameters given to the python function are: + - call: reference to the incoming call. Must be given to all capisuite-provided python functions as first parameter. + - service (integer): service as signalled by ISDN, set to one of the SERVICE_* constants defined in capisuitemodule_init + - callingParty (string): the number of the calling party (source of the call) + - calledParty (string): the number of the called party (destination of the call) + + At the moment callIncoming() is called, the call is waiting for an answer, so the first thing the script must do + is to call connect_*() or reject(). It must also disconnect the call in any case (even in the exception handlers!) + before finishing using disconnect(). + + If the call is disconnected by the other party, the Python exception CallGoneError is raised and should be caught + by the script (but even there you must call disconnect()). + + The python global lock will be acquired while the function runs. + */ + virtual void run(void) throw(); + + Connection *conn; ///< reference to according connection object + + pthread_t thread_handle; ///< handle for the created pthread thread +}; + +#endif + +/* History + +$Log: incomingscript.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.5 2003/02/10 14:17:09 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.4.2.2 2003/02/10 14:04:57 ghillie +- made destructors virtual, otherwise wrong destructor is called! + +Revision 1.4.2.1 2003/02/09 15:03:41 ghillie +- rewritten to use native pthread_* calls instead of CommonC++ Thread + +Revision 1.4 2003/01/18 12:53:06 ghillie +- pass on reference to Python C API to PythonScript + +Revision 1.3 2003/01/04 16:00:53 ghillie +- log improvements: log_level, timestamp + +Revision 1.2 2002/12/14 14:03:27 ghillie +- added throw() declaration to run() method + +Revision 1.1 2002/12/10 15:01:08 ghillie +- class IncomingScript now takes over the functionality of the old CallControl + class defined in callcontrol.*, but uses a base class now + +Revision 1.17 2002/12/09 15:24:21 ghillie +- new parameter debug to constructor +- doc changes + +Revision 1.16 2002/12/07 22:31:37 ghillie +- remove unnecessary attributes py_state, py_dict, isRunning +- added attribute incoming_script + +Revision 1.15 2002/12/05 15:53:41 ghillie +- began restructuring for COnnection to self-delete after getting OK from FlowControl / CallControl +- callCompleted() removed, not needed any more + +Revision 1.14 2002/12/02 12:23:06 ghillie +- incoming_script is now a parameter to constructor +- service parameter now uses constants from Connection::service_t + +Revision 1.13 2002/11/29 11:09:04 ghillie +renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) + +Revision 1.12 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.11 2002/11/27 15:56:14 ghillie +updated comments for doxygen + +Revision 1.10 2002/11/23 15:55:09 ghillie +added missing (?) include + +Revision 1.9 2002/11/21 11:34:33 ghillie +- new methods final() and callCompleted() + +Revision 1.8 2002/11/18 14:21:07 ghillie +- moved global severity_t to ApplicationError::severity_t +- added throw() declarations to header files + +Revision 1.7 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.6 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.5 2002/11/12 15:48:07 ghillie +added data in handler + +Revision 1.4 2002/10/31 12:35:58 ghillie +added DTMF support + +Revision 1.3 2002/10/30 14:24:41 ghillie +added support for python call handling before call is connected + +Revision 1.2 2002/10/27 12:47:20 ghillie +- added multithread support for python +- changed callcontrol reference to stay in the python namespace +- changed ApplicationError to support differen severity + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.6 2002/10/23 15:40:15 ghillie +added python integration... + +Revision 1.5 2002/10/23 14:17:41 ghillie +added registerCallModule() + +Revision 1.4 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.3 2002/10/05 20:43:32 gernot +quick'n'dirty, but WORKS + +Revision 1.2 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.1 2002/10/04 13:28:43 gernot +CallControll class added + +*/ diff --git a/src/application/pythonscript.cpp b/src/application/pythonscript.cpp new file mode 100644 index 0000000..a712cf2 --- /dev/null +++ b/src/application/pythonscript.cpp @@ -0,0 +1,138 @@ +/* @file pythonscript.cpp + @brief Contains PythonScript - Read a python script and call a function in own thread + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "pythonscript.h" +#include +#include + +PythonScript::PythonScript(ostream &debug, unsigned short debug_level, ostream &error, string filename, string functionname, PycStringIO_CAPI* cStringIO) +:debug(debug),debug_level(debug_level),error(error),filename(filename),functionname(functionname),args(NULL), cStringIO(cStringIO) +{ + if (debug_level>=3) + debug << prefix() << "PythonScript created." << endl; +} + +PythonScript::~PythonScript() +{ + if (debug_level>=3) + debug << prefix() << "PythonScript deleted." << endl; +} + +string +PythonScript::prefix() +{ + stringstream s; + time_t t=time(NULL); + char* ct=ctime(&t); + ct[24]='\0'; + s << ct << " Pythonscript " << filename << "," << functionname << "," << hex << this << ": "; + return (s.str()); +} + +void +PythonScript::run() throw (ApplicationError) +{ + PyObject *module=NULL, *module_dict=NULL, *function_ref=NULL, *result=NULL; + + FILE* scriptfile=NULL; + try { + if (!(scriptfile=fopen(filename.c_str(),"r") ) ) + throw ApplicationError("unable to open "+filename,"PythonScript::run()"); + + // get __main__ + if ( ! ( module=PyImport_AddModule("__main__"))) // module = borrowed ref + throw ApplicationError("unable to get __main__ namespace","PythonScript::run()"); + if ( ! ( module_dict=PyModule_GetDict(module) ) ) // module_dict = borrowed ref + throw ApplicationError("unable to get __main__ dictionary","PythonScript::run()"); + + // read control script. It must define a function callIncoming. For description see run() + if (PyRun_SimpleFile(scriptfile,const_cast(filename.c_str()))==-1) + throw ApplicationError("syntax error while executing python script","PythonScript::run()"); + + fclose(scriptfile); + scriptfile=NULL; + + // now let's get the user defined function + PyObject* function_ref=PyDict_GetItemString(module_dict,const_cast(functionname.c_str())); // borrowed ref + if (! function_ref || !PyCallable_Check(function_ref) ) + throw ApplicationError("control script does not define function "+functionname,"PythonScript::run()"); + + if (!args) + throw ApplicationError("no arguments given","PythonScript::run()"); + if (!PyTuple_Check(args)) + throw ApplicationError("args must be a tuple","PythonScript::run()"); + + result=PyObject_CallObject(function_ref,args); + if (!result) { + PyObject *catch_stderr; + // redirect sys.stderr and then print exception + if ( ! ( module=PyImport_AddModule("sys"))) // module = borrowed ref + throw ApplicationError("unable to get sys namespace","PythonScript::run()"); + if ( ! ( module_dict=PyModule_GetDict(module) ) ) // module_dict = borrowed ref + throw ApplicationError("unable to get sys dictionary","PythonScript::run()"); + + catch_stderr=cStringIO->NewOutput(128); // create StringIO object for collecting stderr messages + if ( PyDict_SetItemString(module_dict,"stderr",catch_stderr)!=0 ) { + Py_DECREF(catch_stderr); + throw ApplicationError("unable to redirect sys.stderr","PythonScript::run()"); + } + Py_DECREF(catch_stderr); + + PyErr_Print(); + PyObject *py_traceback; + if ( !(py_traceback=cStringIO->cgetvalue(catch_stderr)) ) + throw ApplicationError("unable to get traceback","PythonScript::run()"); + + int length; + char *traceback; + if (PyString_AsStringAndSize(py_traceback, &traceback, &length)) + throw ApplicationError("unable to convert traceback to char*","PythonScript::run()"); + + error << prefix() << "A python error occured. See traceback below." << endl; + error << prefix() << "Python traceback: "; + for (int i=0;i + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef PYTHONSCRIPT_H +#define PYTHONSCRIPT_H + +#include +#include +#include "applicationexception.h" +class PycStringIO_CAPI; + +using namespace std; + +/** @brief Read a python script and call a function + + This class reads a given python script which + must define one function with given name. This function is called + with arbitrary parameters. + + @author Gernot Hillier +*/ +class PythonScript +{ + public: + /** @brief Constructor. Create Object. + + @param debug stream for debugging info + @param debug_level verbosity level for debug messages + @param error stream for error messages + @param filename file name of the python script to read + @param functionname name of the function to call + @param cStringIO pointer to the Python cStringIO C API + */ + PythonScript(ostream &debug, unsigned short debug_level, ostream &error, string filename, string functionname, PycStringIO_CAPI* cStringIO); + + /** @brief Destructor. + */ + virtual ~PythonScript(); + + protected: + /** @brief Reads the given python script and calls the given function. + + The arguments for the function must be given in the constructor. + + @throw ApplicationError Thrown when script can't be executed for any reason. + */ + virtual void run() throw (ApplicationError); + + /** @brief Called by pscript_cleanup_handler(), will delete the current object. + */ + virtual void final(); + + /** @brief return a prefix containing this pointer and date for log messages + + @return constructed prefix as stringstream + */ + string prefix(); + + string filename, ///< name of the python script to read + functionname; ///< name of the function to call + PyObject *args; ///< python tuple containing the args for the called python function + ostream &debug, ///< debug stream + &error; ///< error stream + unsigned short debug_level; ///< debug level + PycStringIO_CAPI* cStringIO; ///< holds a pointer to the Python cStringIO C API +}; + +#endif + +/* History + +$Log: pythonscript.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.6 2003/02/10 14:17:09 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.5.2.2 2003/02/10 14:04:57 ghillie +- made destructors virtual, otherwise wrong destructor is called! + +Revision 1.5.2.1 2003/02/09 15:03:42 ghillie +- rewritten to use native pthread_* calls instead of CommonC++ Thread + +Revision 1.5 2003/01/18 12:55:39 ghillie +- run handles python script errors now on its own and prints tracebacks + to error log file correctly (solves TODO) + +Revision 1.4 2003/01/04 16:00:53 ghillie +- log improvements: log_level, timestamp + +Revision 1.3 2002/12/14 14:04:20 ghillie +- run throws ApplicationError now so that derived classes can catch + and handle it on their behalf + +Revision 1.2 2002/12/10 15:05:45 ghillie +- finished pythonscript class definition + +Revision 1.1 2002/12/09 18:07:59 ghillie +- initial checkin, not finished! + +*/ diff --git a/src/backend/.cvsignore b/src/backend/.cvsignore new file mode 100644 index 0000000..e995588 --- /dev/null +++ b/src/backend/.cvsignore @@ -0,0 +1,3 @@ +.deps +Makefile +Makefile.in diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am new file mode 100644 index 0000000..6f19e65 --- /dev/null +++ b/src/backend/Makefile.am @@ -0,0 +1,3 @@ +noinst_LIBRARIES = libccbackend.a +libccbackend_a_SOURCES = capi.cpp capi.h applicationinterface.h connection.h \ + connection.cpp callinterface.h capiexception.h diff --git a/src/backend/applicationinterface.h b/src/backend/applicationinterface.h new file mode 100644 index 0000000..fe3cf5f --- /dev/null +++ b/src/backend/applicationinterface.h @@ -0,0 +1,104 @@ +/** @file applicationinterface.h + @brief Contains ApplicationInterface - Interface class which is implemented by the main application. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef APPLICATIONINTERFACE_H +#define APPLICATIONINTERFACE_H + +using namespace std; + +#include + +class Connection; + +/** @brief Interface class which is implemented by the main application. + + This interface exposes methods for general communication between the Capi abstraction layer and the application. These methods + are mainly used for telling the application the begin and end of an incoming call, so it can start and finish call handling + procedures + + For special events during call handling, see the CallInterface. + + @author Gernot Hillier +*/ +class ApplicationInterface +{ + public: + /** @brief Called by Capi if we get a CONNECT_IND (in case of an incoming call) + + @param conn pointer to the according connection object + */ + virtual void callWaiting (Connection *conn) = 0; +}; + +#endif + +/* History + +$Log: applicationinterface.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.7 2002/12/05 15:55:34 ghillie +- removed callCompleted(), application will self-determine when call is completed + +Revision 1.6 2002/12/05 14:54:52 ghillie +- thrown away debug() method, debug stream is given in constructor now + +Revision 1.5 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.4 2002/11/25 21:00:53 ghillie +- improved documentation, now doxygen-readabl + +Revision 1.3 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.2 2002/10/29 14:10:42 ghillie +updated description of callCompleted: now the given reference is valid + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.9 2002/10/23 15:37:50 ghillie +typo... + +Revision 1.8 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.7 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.6 2002/10/02 14:10:07 gernot +first version + +Revision 1.5 2002/10/01 08:56:09 gernot +some cosmetic improvements, changes for gcc3.2 + +Revision 1.4 2002/09/22 14:22:53 gernot +some cosmetic comment improvements ;-) + +Revision 1.3 2002/09/19 12:08:19 gernot +added magic CVS strings + +* Sun Sep 15 2002 - gernot@hillier.de +- put under CVS, cvs changelog follows + +* Sun May 20 2002 - gernot@hillier.de +- first version + +*/ diff --git a/src/backend/callinterface.h b/src/backend/callinterface.h new file mode 100644 index 0000000..9c89fb9 --- /dev/null +++ b/src/backend/callinterface.h @@ -0,0 +1,142 @@ +/** @file callinterface.h + @brief Contains CallInterface - Interface class for all signals specific to a certain call. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CALLINTERFACE_H +#define CALLINTERFACE_H + +#include + +using namespace std; + +/** @brief Interface class for all signals specific to a certain call. + + While ApplicationInterface contains the methods to inform the application about general events, this + interface has all the signals which describe events of a certain connection like DTMF signal received, + call is disconnected logical, etc. + + The application is supposed to create objects for each call which implement this interface and register + them with Connection::registerCallInterface(). It's possible to use different modules for different tasks + during one connection and to dynamically register/unregister them. If no object is registered, the callbacks + are simply not called. However, there are certain events which need a registered CallInterface implementing + object - otherwise Connection will throw exceptions. + + @author Gernot Hillier +*/ +class CallInterface +{ + public: + /** @brief Called if the connection is completely established (physical + logical) + */ + virtual void callConnected (void) = 0; + + /** @brief called if logical connection is finished + */ + virtual void callDisconnectedLogical (void) = 0; + + /** @brief called if physical connection is finished. + + This is called when the connection has been cleared down completely. + + Attention: You must delete the Connection object yourself if you don't need + it any more! + */ + virtual void callDisconnectedPhysical (void) = 0; + + /** @brief called if the file requested for sending is sent completely + */ + virtual void transmissionComplete (void) = 0; + + /** @brief called by Connection object if DMTF characters were received. + + It is necessary to enable DTMF receiving with Connection::enableDTMF + before any DTMFs are signalled. DTMF chars can be read with Connection::getDTMF(). + */ + virtual void gotDTMF (void) = 0; + + /** @brief called by Connection object for each received data packet. + + You can either use this to save your data manually and/or tell connection + to save it to a file (with start_file_reception)() ) + + But please not that this is a performance issue: calling an application function + for each received function should only be done if really necessary. + + @param data pointer to data as received by CAPI + @param length length of data in bytes + */ + virtual void dataIn (unsigned char* data, unsigned length) = 0; +}; + +#endif + +/* History + +$Log: callinterface.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.9 2002/12/06 12:55:04 ghillie +- updated docs + +Revision 1.8 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.7 2002/11/27 15:58:13 ghillie +updated comments for doxygen + +Revision 1.6 2002/11/15 13:49:10 ghillie +fix: callmodule wasn't aborted when call was only connected/disconnected physically + +Revision 1.5 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.4 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.3 2002/11/12 15:48:54 ghillie +added data in handler + +Revision 1.2 2002/10/31 12:37:34 ghillie +added DTMF support + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.5 2002/10/23 15:40:51 ghillie +typo... + +Revision 1.4 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.3 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.2 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.1 2002/10/02 14:10:07 gernot +first version + +*/ diff --git a/src/backend/capi.cpp b/src/backend/capi.cpp new file mode 100644 index 0000000..18473b6 --- /dev/null +++ b/src/backend/capi.cpp @@ -0,0 +1,1031 @@ +/* @file capi.h + @brief Contains Capi - Main Class for communication with CAPI + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include "connection.h" +#include "applicationinterface.h" +#include "capi.h" + +short Capi::numControllers=0; + +void* capi_exec_handler(void* arg) +{ + if (!arg) { + cerr << "FATAL ERROR: no Capi reference given in capi_exec_handler" << endl; + exit(1); + } + + Capi *instance=static_cast(arg); + instance->run(); +} + +Capi::Capi (ostream& debug, unsigned short debug_level, ostream &error, unsigned maxLogicalConnection, unsigned maxBDataBlocks,unsigned maxBDataLen) throw (CapiMsgError, CapiError) +:debug(debug),debug_level(debug_level),error(error),messageNumber(0),usedInfoMask(0),usedCIPMask(0) +{ + if (debug_level >= 2) + debug << prefix() << "Capi object created" << endl; + getInfo(); // can throw CapiMsgError. Just propagate... + + if (Capi::numControllers==0) + throw (CapiError("No ISDN-Controller installed","Capi::Capi()")); + + unsigned info = capi20_register(maxLogicalConnection, maxBDataBlocks, maxBDataLen, &applId); + if (applId == 0 || info!=0) + throw (CapiMsgError(info,"Error while registering application: "+describeParamInfo(info),"Capi::Capi()")); + + int erg=pthread_create(&thread_handle, NULL, capi_exec_handler, this); // create a normal thread + if (erg!=0) + throw (CapiMsgError(erg,"Error while starting message thread","Capi::Capi()")); +} + +Capi::~Capi () +{ + int ret=pthread_cancel(thread_handle); // tell run() to end + if (ret) + throw (CapiMsgError(ret,"Error while cancelling Capi thread","Capi::~Capi()")); + ret=pthread_join(thread_handle,NULL); + if (ret) + throw (CapiMsgError(ret,"Error while joining Capi thread","Capi::~Capi()")); + + unsigned info = capi20_release(applId); // this will abort capi20_waitformessage + if (info != 0) + throw (CapiMsgError(info,"Error while unregistering application: "+describeParamInfo(info),"Capi::~Capi()")); + + if (debug_level >= 2) + debug << prefix() << "Capi object deleted. Let's go to bed..." << endl; +} + +void +Capi::registerApplicationInterface(ApplicationInterface* application_in) +{ + application=application_in; + if (debug_level >= 1) { + debug << prefix() << "Registered successful at CAPI with ApplId " << applId << endl; + } +} + +void +Capi::unregisterConnection(_cdword plci) +{ + connections.erase(plci); +} + +void +Capi::listen_req(_cdword Controller, _cdword InfoMask, _cdword CIPMask) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + usedInfoMask=InfoMask; + usedCIPMask=CIPMask; + + if (debug_level >= 2) { + debug << prefix() << ">LISTEN_REQ ApplID 0x" << hex << applId << " msgNum 0x" << messageNumber << " Controller 0x" << Controller << " InfoMask 0x" + << InfoMask << " CIPMask 0x" << CIPMask << " 0x0 NULL NULL" << endl; + } + unsigned info=LISTEN_REQ(&CMSG, applId, messageNumber++, Controller, InfoMask,CIPMask,0,NULL,NULL); + if (debug_level >= 2) { + debug << prefix() << "info: " << info << endl; + } + + if (info != 0) + throw(CapiMsgError(info,"Error while LISTEN_REQ: "+Capi::describeParamInfo(info),"Capi::listen_req()")); +} + +void +Capi::alert_req(_cdword plci) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + + if (debug_level >= 2) { + debug << prefix() << ">ALERT_REQ: ApplId 0x" << hex << applId << ", MsgNr 0x" << messageNumber << ", PLCI 0x" << plci << endl; + } + unsigned info=ALERT_REQ(&CMSG, applId, messageNumber++, plci, NULL, NULL, NULL, NULL); + if (debug_level >= 2) { + debug << prefix() << "info: " << info << endl; + } + + if (info != 0) + throw(CapiMsgError(info,"Error while ALERT_REQ: "+Capi::describeParamInfo(info),"Capi::alert_req()")); +} + + +void +Capi::connect_req(Connection *conn, _cdword controller, _cword CIPValue, _cstruct calledPartyNumber, _cstruct callingPartyNumber, _cword B1protocol, _cword B2protocol, _cword B3protocol, _cstruct B1configuration, _cstruct B2configuration, _cstruct B3configuration) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + + messageNumber++; + + connections[0xFACE0000 | messageNumber]=conn; // pseudo PLCI to see which CONNECT_CONF corresponds to which CONNECT_REQ + // this can't be a valid NCCI as a valid ncci & 0xFF000000 = 0 + + if (debug_level >= 2) { + debug << prefix() << ">CONNECT_REQ: ApplId 0x" << hex << applId << ", MsgNr 0x" << messageNumber << ", Controller 0x" << controller + << " CIPValue 0x" << CIPValue << ", B1proto 0x" << B1protocol << ", B2proto 0x" << B2protocol <<", B3proto 0x" << B3protocol << endl; + } + unsigned info=CONNECT_REQ(&CMSG, applId, messageNumber, controller, CIPValue, calledPartyNumber, callingPartyNumber, NULL, NULL, + B1protocol, B2protocol, B3protocol, B1configuration, B2configuration, B3configuration, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + if (debug_level >= 2) { + debug << prefix() << "info: " << info << endl; + } + + if (info != 0) + throw(CapiMsgError(info,"Error while CONNECT_REQ: "+Capi::describeParamInfo(info),"Capi::connect_req()")); +} + +void +Capi::connect_b3_req(_cdword plci) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + + if (debug_level >= 2) { + debug << prefix() << ">CONNECT_B3_REQ: ApplId 0x" << hex << applId << ", MsgNr 0x" << messageNumber << ", PLCI 0x" << plci << endl; + } + unsigned info=CONNECT_B3_REQ(&CMSG, applId, messageNumber++, plci, NULL); + if (debug_level >= 2) { + debug << prefix() << "info: " << info << endl; + } + + if (info != 0) + throw(CapiMsgError(info,"Error while CONNECT_B3_REQ: "+Capi::describeParamInfo(info),"Capi::connect_b3_req()")); +} + +void +Capi::select_b_protocol_req (_cdword plci, _cword B1protocol, _cword B2protocol, _cword B3protocol, _cstruct B1configuration, _cstruct B2configuration, _cstruct B3configuration) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + + if (debug_level >= 2) debug << prefix() << ">SELECT_B_PROTOCOL_REQ: ApplId 0x" << hex << applId << ", MsgNr 0x" << messageNumber << ", PLCI 0x" << plci + << ", B1protocol " << B1protocol << ", B2protocol " << B2protocol << ", B3protocol " << B3protocol << endl; + unsigned info=SELECT_B_PROTOCOL_REQ(&CMSG, applId, messageNumber++, plci, B1protocol, B2protocol, B3protocol, B1configuration, B2configuration, B3configuration); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while SELECT_B_PROTOCOL_REQ: "+Capi::describeParamInfo(info),"Capi::select_b_protocol_req()")); +} + +void +Capi::data_b3_req (_cdword ncci, void* Data, _cword DataLength,_cword DataHandle,_cword Flags) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + if (debug_level >= 3) + debug << prefix() << ">DATA_B3_REQ ApplId 0x" << hex << applId << ", msgNum 0x" << messageNumber << ", NCCI 0x" << ncci << dec + << ", DataLen " << DataLength << ", DataHandle " << DataHandle << hex << ", Flags 0x" << Flags << endl; + unsigned info=DATA_B3_REQ(&CMSG, applId, messageNumber++, ncci, Data, DataLength, DataHandle, Flags); + if (debug_level >= 3) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while DATA_B3_REQ: "+Capi::describeParamInfo(info),"Capi::data_b3_req()")); +} + +void +Capi::disconnect_b3_req (_cdword ncci, _cstruct ncpi) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + if (debug_level >= 2) + debug << prefix() << ">DISCONNECT_B3_REQ ApplId 0x" << hex << applId << " MsgNum 0x" << messageNumber << " NCCI 0x" << ncci << endl; + unsigned info=DISCONNECT_B3_REQ(&CMSG, applId, messageNumber++, ncci, ncpi); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while DISCONNECT_B3_REQ: "+Capi::describeParamInfo(info),"Capi::disconnect_b3_req()")); +} + +void +Capi::disconnect_req (_cdword plci, _cstruct Keypadfacility, _cstruct Useruserdata, _cstruct Facilitydataarray) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + if (debug_level >= 2) { + debug << prefix() << ">DISCONNECT_REQ ApplId 0x" << hex << applId << " MsgNum 0x" << messageNumber << " PLCI 0x" << plci << endl; + } + unsigned info=DISCONNECT_REQ(&CMSG, applId, messageNumber++, plci, NULL, Keypadfacility, Useruserdata, Facilitydataarray); + if (debug_level >= 2) { + debug << prefix() << "info: " << info << endl; + } + + if (info != 0) + throw(CapiMsgError(info,"Error while DISCONNECT_REQ: "+Capi::describeParamInfo(info),"Capi::disconnect_req()")); +} + +void +Capi::facility_req (_cdword address, _cword FacilitySelector, _cstruct FacilityRequestParameter) throw (CapiMsgError) +{ + _cmsg CMSG; // Nachrichten-Struktur + if (debug_level >= 2) { + debug << prefix() << ">FACILITY_REQ ApplId 0x" << hex << applId << ", MsgNr 0x" << messageNumber << ", Address 0x" << address << ", FacilitySelector 0x" << FacilitySelector << endl; + } + unsigned info=FACILITY_REQ(&CMSG, applId, messageNumber++, address, FacilitySelector, FacilityRequestParameter); + if (debug_level >= 2) { + debug << prefix() << "info: " << info << endl; + } + + if (info != 0) + throw(CapiMsgError(info,"Error while FACILITY_REQ: "+Capi::describeParamInfo(info),"Capi::facility_req()")); +} + + +void +Capi::setListenFaxG3 (_cdword Controller) throw (CapiMsgError) +{ + usedCIPMask|=0x00020010; + + if (!Controller) { + unsigned char buf[64]; + for (int i=1;i<=numControllers;i++) { + unsigned info = CAPI20_GET_PROFILE(i, buf); + if (info!=0) + throw (CapiMsgError(info,"Error in CAPI20_GET_PROFILE: "+Capi::describeParamInfo(info),"Capi::setListenFaxG3()")); + + if ((buf[8] & 0x10 && buf[12] & 0x10 && buf[16] & 0x10) // is controller able to handle faxG3? + || (buf[8] & 0x10 && buf[12] & 0x10 && buf[16] & 0x20)) //faxG3ext + listen_req(i, usedInfoMask, usedCIPMask); // can throw CapiMsgError + } + } + else + listen_req(Controller, usedInfoMask, usedCIPMask); // can throw CapiMsgError +} + +void +Capi::setListenTelephony (_cdword Controller) throw (CapiMsgError) +{ + usedCIPMask|=0x00010012; + if (!Controller) { + unsigned char buf[64]; + for (int i=1;i<=numControllers;i++) { + unsigned info = CAPI20_GET_PROFILE(i, buf); + if (info!=0) + throw (CapiMsgError(info,"Error in CAPI20_GET_PROFILE: "+Capi::describeParamInfo(info),"Capi::setListenTelephony()")); + + if (buf[8] & 0x02 && buf[12] & 0x02 && buf[16] & 0x01) // is controller able to handle transparent? + listen_req(i, usedInfoMask, usedCIPMask); // can throw CapiMsgError + } + } + else + listen_req(Controller, usedInfoMask, usedCIPMask); // can throw CapiMsgError +} + +void +Capi::connect_resp (_cword messageNumber, _cdword plci, _cword reject, _cword B1protocol, _cword B2protocol, _cword B3protocol, _cstruct B1configuration, _cstruct B2configuration, _cstruct B3configuration) throw (CapiMsgError) +{ + if (debug_level >= 2) + debug << prefix() << ">CONNECT_RESP ApplId 0x" << hex << applId << ", msgNum 0x" << messageNumber << ", PLCI 0x" << plci << ", Reject 0x" + << reject << ", B1proto 0x" << B1protocol << ", B2proto 0x" << B2protocol << ", B3proto 0x" << B3protocol << endl; + + _cmsg new_message; + unsigned info=CONNECT_RESP(&new_message, applId, messageNumber, plci, reject, B1protocol, B2protocol, B3protocol, B1configuration, B2configuration, B3configuration, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while CONNECT_REQ: "+Capi::describeParamInfo(info),"Capi::connect_resp()")); + +} + +void +Capi::connect_active_resp (_cword messageNumber, _cdword plci) throw (CapiMsgError) +{ + if (debug_level >= 2) + debug << prefix() << ">CONNECT_ACTIVE_RESP ApplId 0x" << hex << applId << " MsgNum 0x" << messageNumber << " PLCI 0x" << plci << endl; + + _cmsg new_message; + unsigned info=CONNECT_ACTIVE_RESP(&new_message, applId, messageNumber, plci); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while CONNECT_ACTIVE_RESP: "+Capi::describeParamInfo(info),"Capi::connect_active_resp()")); +} + +void +Capi::connect_b3_resp (_cword messageNumber, _cdword ncci, _cword reject, _cstruct ncpi) throw (CapiMsgError) +{ + if (debug_level >= 2) + debug << prefix() << ">CONNECT_B3_RESP ApplId 0x" << hex << applId << " MsgNum 0x" << messageNumber << " NCCI 0x" << ncci << " Reject 0x" << reject << endl; + + _cmsg new_message; + unsigned info=CONNECT_B3_RESP(&new_message, applId, messageNumber, ncci, reject, ncpi); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while CONNECT_B3_RESP: "+Capi::describeParamInfo(info),"Capi::connect_b3_resp()")); +} + +void +Capi::connect_b3_active_resp (_cword messageNumber, _cdword ncci) throw (CapiMsgError) +{ + if (debug_level >= 2) + debug << prefix() << ">CONNECT_B3_ACTIVE_RESP ApplId 0x" << hex << applId << " MsgNum 0x" << messageNumber << " NCCI 0x" << ncci << endl; + + _cmsg new_message; + unsigned info=CONNECT_B3_ACTIVE_RESP(&new_message, applId, messageNumber, ncci); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while CONNECT_B3_ACTIVE_RESP: "+Capi::describeParamInfo(info),"Capi::connect_b3_active_resp()")); +} + + +void +Capi::data_b3_resp (_cword messageNumber, _cdword ncci, _cword dataHandle) throw (CapiMsgError) +{ + if (debug_level >= 3) + debug << prefix() << ">DATA_B3_RESP, ApplId 0x" << hex << applId << ", msgNum 0x" << messageNumber << ", NCCI 0x" << ncci << ", DataHandle 0x" << dataHandle << endl; + + _cmsg new_message; + unsigned info=DATA_B3_RESP(&new_message, applId, messageNumber, ncci, dataHandle); + if (debug_level >= 3) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while DATA_B3_RESP: "+Capi::describeParamInfo(info),"Capi::data_b3_resp()")); +} + +void +Capi::facility_resp (_cword messageNumber, _cdword address, _cword facilitySelector, _cstruct facilityResponseParameter) throw (CapiMsgError) +{ + if (debug_level >= 2) + debug << prefix() << ">FACILITY_RESP ApplId 0x" << hex << applId << ", MsgNr 0x" << messageNumber << ", Address 0x" << address + << ", FacilitySelector 0x" << facilitySelector << endl; + + _cmsg new_message; + unsigned info=FACILITY_RESP(&new_message, applId, messageNumber, address, facilitySelector, facilityResponseParameter); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while FACILITY_RESP: "+Capi::describeParamInfo(info),"Capi::facility_resp()")); +} + + +void +Capi::disconnect_b3_resp (_cword messageNumber, _cdword ncci) throw (CapiMsgError) +{ + if (debug_level >= 2) + debug << prefix() << ">DISCONNECT_B3_RESP ApplId 0x" << hex << applId << " MsgNum 0x" << messageNumber << " NCCI 0x" << ncci << endl; + + _cmsg new_message; + unsigned info=DISCONNECT_B3_RESP(&new_message, applId, messageNumber, ncci); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while DISCONNECT_B3_RESP: "+Capi::describeParamInfo(info),"Capi::disconnect_b3_resp()")); +} + + +void +Capi::disconnect_resp (_cword messageNumber, _cdword plci) throw (CapiMsgError) +{ + if (debug_level >= 2) + debug << prefix() << ">DISCONNECT_RESP ApplId 0x" << hex << applId << " MsgNum 0x" << messageNumber << " PLCI 0x" << plci << endl; + + _cmsg new_message; + unsigned info=DISCONNECT_RESP(&new_message, applId, messageNumber, plci); + if (debug_level >= 2) + debug << prefix() << "info: " << info << endl; + + if (info != 0) + throw(CapiMsgError(info,"Error while DISCONNECT_RESP: "+Capi::describeParamInfo(info),"Capi::disconnect_resp()")); +} + + + +void +Capi::readMessage (void) throw (CapiMsgError, CapiError, CapiWrongState, CapiExternalError) +{ + _cmsg nachricht; + unsigned info=CAPI_GET_CMSG(&nachricht, applId); // don't use capi20_get_message here as CAPI_GET_CMSG does disassembling of message parameters for us + switch (info) { + case CapiNoError: //----- a message has been read ----- + switch (nachricht.Subcommand) { + case CAPI_CONF: // confirmation + switch (nachricht.Command) { + case CAPI_ALERT: { + _cdword plci=ALERT_CONF_PLCI(&nachricht); + if (debug_level >= 2) + debug << prefix() << "alert_conf(nachricht); + } break; + + case CAPI_CONNECT: { + _cdword pseudoPLCI=0xFACE0000 | nachricht.Messagenumber; // pseudo PLCI as set by connect_req + _cdword plci=CONNECT_CONF_PLCI(&nachricht); + if (debug_level >= 2) + debug << prefix() << "connect_conf(nachricht); + } + } break; + + case CAPI_CONNECT_B3: { + _cdword plci=CONNECT_B3_CONF_NCCI(&nachricht) & 0xFFFF; // PLCI is coded in the least 2 octets of NCCI + if (debug_level >= 2) + debug << prefix() << "connect_b3_conf(nachricht); + } break; + + case CAPI_SELECT_B_PROTOCOL: { + _cdword plci=SELECT_B_PROTOCOL_CONF_PLCI(&nachricht); + if (debug_level >= 2) + debug << prefix() << "select_b_protocol_conf(nachricht); + } break; + + case CAPI_LISTEN: + if (debug_level >= 2) + debug << prefix() << "= 3) + debug << prefix() << "data_b3_conf(nachricht); + } break; + + + case CAPI_FACILITY: + switch (FACILITY_CONF_FACILITYSELECTOR(&nachricht)) { + case 1: { // DTMF + _cdword plci=FACILITY_CONF_PLCI(&nachricht) & 0xFFFF; // this *should* be PLCI but who knows, so let's mask it to be sure + if (debug_level >= 2) + debug << prefix() << "facility_conf_DTMF(nachricht); + } break; + + default: + error << prefix() << "WARNING: PLCI " << hex << FACILITY_CONF_PLCI(&nachricht) << ": unsupported facility selector " << FACILITY_CONF_FACILITYSELECTOR(&nachricht) << " in FACILITY_CONF" << endl; + break; + } + break; + + case CAPI_DISCONNECT_B3: { + _cdword plci=DISCONNECT_B3_CONF_NCCI(&nachricht) & 0xFFFF; // PLCI is coded in the least 2 octets of NCCI + if (debug_level >= 2) + debug << prefix() << "disconnect_b3_conf(nachricht); + } break; + + case CAPI_DISCONNECT: { // TODO: perhaps we should handle NCPI telling us fax infos here?? + _cdword plci=DISCONNECT_CONF_PLCI(&nachricht); + if (debug_level >= 2) + debug << prefix() << "disconnect_conf(nachricht); + } break; + } + break; + + case CAPI_IND: // indication + switch (nachricht.Command) { + case CAPI_CONNECT: { // call for us + _cdword plci=CONNECT_IND_PLCI(&nachricht); + if (debug_level >= 2) + debug << prefix() << "0) + throw(CapiError("PLCI used twice from CAPI in CONNECT_IND","Capi::readMessage()")); + else { + Connection *c=new Connection(nachricht,this); + connections[plci]=c; + application->callWaiting(c); + } + } break; + + case CAPI_CONNECT_ACTIVE: { + _cdword plci=CONNECT_IND_PLCI(&nachricht); + if (debug_level >= 2) + debug << prefix() << "connect_active_ind(nachricht); + } break; + + case CAPI_CONNECT_B3: { + _cdword plci=CONNECT_B3_IND_NCCI(&nachricht) & 0xFFFF; // PLCI is coded in the least 2 octets of NCCI + if (debug_level >= 2) + debug << prefix() << "connect_b3_ind(nachricht); + } break; + + case CAPI_CONNECT_B3_ACTIVE: { + _cdword plci=CONNECT_B3_ACTIVE_IND_NCCI(&nachricht) & 0xFFFF; // PLCI is coded in the least 2 octets of NCCI + if (debug_level >= 2) + debug << prefix() << "connect_b3_active_ind(nachricht); + } break; + + case CAPI_DISCONNECT: { // call gone, we'll confirm to CAPI + _cdword plci=DISCONNECT_IND_PLCI(&nachricht); + if (debug_level >= 2) + debug << prefix() << "disconnect_ind(nachricht); + } + } break; + + case CAPI_DISCONNECT_B3: { + _cdword plci=DISCONNECT_B3_IND_NCCI(&nachricht) & 0xFFFF; // PLCI is coded in the least 2 octets of NCCI + if (debug_level >= 2) + debug << prefix() << "disconnect_b3_ind(nachricht); + } break; + + case CAPI_DATA_B3: { + _cdword plci=DATA_B3_IND_NCCI(&nachricht) & 0xFFFF; // PLCI is coded in the least 2 octets of NCCI + if (debug_level >= 3) + debug << prefix() << "data_b3_ind(nachricht); + } break; + + case CAPI_FACILITY: + switch (FACILITY_IND_FACILITYSELECTOR(&nachricht)) { + case 1: { // DTMF + _cdword plci=FACILITY_IND_PLCI(&nachricht) & 0xFFFF; // we *should* get PLCI but just to be sure we mask the NCCI-part out... + if (debug_level >= 2) + debug << prefix() << "facility_ind_DTMF(nachricht); + } break; + + default: + error << prefix() << "WARNING: PLCI " << hex << (FACILITY_IND_PLCI(&nachricht) & 0xFFFF) << ": Unsupported facility selector " << FACILITY_IND_FACILITYSELECTOR(&nachricht) << " in FACILITY_IND" << endl; + break; + } + break; + + + default: + stringstream err; + err << "Indication 0x" << hex << nachricht.Command << " not handled" << ends; + throw (CapiError(err.str(),"Capi::readMessage()")); + break; + } + break; + + default: //----- neither indication nor confirmation ???? ----- + throw(CapiError("Unknown subcommand in function Handle_CAPI_Msg","Capi::readMessage()")); + break; + } + break; + case CapiReceiveQueueEmpty: + throw (CapiError("readMessage called but no message available?","Capi::readMessage()")); + break; + + default: + throw (CapiMsgError(info,"Error while CAPI_GET_CMSG: "+Capi::describeParamInfo(info),"Capi::readMessage()")); + break; + } +} + +void +Capi::run() +{ + while (1) { + pthread_testcancel(); + unsigned info=capi20_waitformessage (applId, NULL); // will block until message is available or capi20_release called + try { + if (info==CapiNoError) { + if (debug_level >= 3) + debug << prefix() << "*" << endl; + readMessage(); // trigger message reading + if (debug_level >= 3) + debug << prefix() << "**" << endl; + } + } + catch (CapiError e) { + error << prefix() << "ERROR: Connection " << this << ": Error in readMessage(), messagge: " << e << endl; + } + } +} + +string +Capi::describeParamInfo (unsigned int info) +{ + switch (info) + { + // class 0x00xx: Informative values, no error (see CAPI 2.0 4th ed. PartI, chapter 6.1.26) + case 0x0000: + return "request accepted."; + case 0x0001: + return "warning: NCPI not supported by current protocol, NCPI ignored."; + case 0x0002: + return "warnung: Flags not supported by current protocol, flags ignored."; + case 0x0003: + return "warnung: Alert already sent by another application."; + // class 0x10xx: Error information concerning CAPI_REGISTER + case 0x1001: + return "Too many applications."; + case 0x1002: + return "Logical Block size too small; must be at least 128 bytes."; + case 0x1003: + return "Buffer exceeds 64 kbytes."; + case 0x1004: + return "Message buffer size too small, must be at least 1024 bytes."; + case 0x1005: + return "Max. number of logical connections not supported."; + case 0x1006: + return "reserved (unknown error)."; + case 0x1007: + return "The message could not be accepted because of an internal busy condition."; + case 0x1008: + return "OS Resource error (out of memory?)."; + case 0x1009: + return "CAPI not installed."; + case 0x100A: + return "Controller does not support external equipment."; + case 0x100B: + return "Controller does only support external equipment."; + // class 0x11xx: Error information concerning message exchange functions + case 0x1101: + return "Illegal application number."; + case 0x1102: + return "Illegal command or subcommand, or message length less than 12 octets."; + case 0x1103: + return "The message could not be accepted because of a queue full condition."; + case 0x1104: + return "Queue is empty."; + case 0x1105: + return "Queue overflow: a message was lost!!"; + case 0x1106: + return "Unknown notification parameter."; + case 0x1107: + return "The message could not be accepted because on an internal busy condition."; + case 0x1108: + return "OS resource error (out of memory?)."; + case 0x1109: + return "CAPI not installed."; + case 0x110A: + return "Controller does not support external equipment."; + case 0x110B: + return "Controller does only support external equipment."; + // class 0x20xx: Error information concerning resource/coding problems + case 0x2001: + return "Message not supported in current state."; + case 0x2002: + return "Illegal Controller/PLCI/NCCI."; + case 0x2003: + return "No PLCI available."; + case 0x2004: + return "No NCCI available."; + case 0x2005: + return "No Listen resources available."; + case 0x2006: + return "No fax resources available (protocol T.30 not supported)."; + case 0x2007: + return "Illegal message parameter coding."; + case 0x2008: + return "No interconnection resources available."; + // class 0x30xx: Error information concerning requested services + case 0x3001: + return "B1 protocol not supported."; + case 0x3002: + return "B2 protocol not supported."; + case 0x3003: + return "B3 protocol not supported."; + case 0x3004: + return "B1 protocol parameter not supported."; + case 0x3005: + return "B2 protocol parameter not supported."; + case 0x3006: + return "B3 protocol parameter not supported."; + case 0x3007: + return "B protocol combination not supported."; + case 0x3008: + return "NCPI not supported."; + case 0x3009: + return "CIP Value unknown."; + case 0x300A: + return "Flags nor supported (reserved bits used!)."; + case 0x300B: + return "Facility not supported."; + case 0x300C: + return "Data length not supported by current protocol."; + case 0x300D: + return "Reset procedure not supported by current protocol."; + case 0x300E: + return "TEI assignment failed / overlapping channel masks."; + case 0x300F: + return "Unsupported interoperability (see CAPI 2.0 specification, Part IV)."; + case 0x3010: + return "Request not allowed in this state."; + case 0x3011: + return "Facility specific function not supported."; + + default: + stringstream message; + message << "CAPI returned an unknown error! Please ask your manufacturer for assistance (error code=0x" << hex << info << ")"; + return message.str(); + } +} + +string +Capi::prefix() +{ + stringstream s; + time_t t=time(NULL); + char* ct=ctime(&t); + ct[24]='\0'; + s << ct << " Capi " << hex << this << ": "; + return (s.str()); +} + +string +Capi::getInfo(bool verbose) throw (CapiMsgError) +{ + unsigned char buf[64]; + _cdword buf2[4]; + stringstream tmp; + + // is CAPI correctly installed? + unsigned info=CAPI20_ISINSTALLED(); + if (info!=0) + throw (CapiMsgError(info,"Error in CAPI20_ISINSTALLED: "+describeParamInfo(info),"Capi::getCapiInfo()")); + + // retrieve number of installed controllers and create array for storing the descriptions + info=CAPI20_GET_PROFILE (0, buf); + if (info!=0) + throw (CapiMsgError(info,"Error in CAPI20_GET_PROFILE: "+describeParamInfo(info),"Capi::getCapiInfo()")); + + numControllers=buf[0]+(buf[1] << 8); + + tmp << numControllers << " controllers found" << endl; + + if (verbose) + { + // retrieve general information (kernel driver manufacturer, version of kernel driver) + if (capi20_get_manufacturer(0,buf)) + tmp << reinterpret_cast (buf); + else + tmp << "unknown"; + + if (capi20_get_version(0, reinterpret_cast(buf2))) { + tmp << ", version " << buf2[0] << "." << buf2[1] << "/" << buf2[2] << "." << buf2[3] << endl; + } else + tmp << ", version unknown" << endl; + + // retrieve controller specific information (manufacturer, version) + for (unsigned i=1;i<=numControllers;i++) { + tmp << "Controller " << i << ": "; + + if (capi20_get_manufacturer(i,buf)) + tmp << reinterpret_cast (buf); + else + tmp << "unknown"; + + info = CAPI20_GET_PROFILE(i, buf); + if (info!=0) + throw (CapiMsgError(info,"Error in CAPI20_GET_PROFILE/2: "+Capi::describeParamInfo(info),"Capi::getCapiInfo()")); + + tmp << " (" << buf[2] + (buf[3]<<8) << " B channels"; + + if (buf[4] & 0x08) + tmp << ", DTMF"; + if (buf[4] & 0x10) + tmp << ", SuppServ"; + if (buf[8] & 0x02 && buf[12] & 0x02 && buf[16] & 0x01) + tmp << ", transparent"; + if (buf[8] & 0x10 && buf[12] & 0x10 && buf[16] & 0x10) + tmp << ", FaxG3"; + if (buf[8] & 0x10 && buf[12] & 0x10 && buf[16] & 0x20) + tmp << ", FaxG3ext"; + + if (capi20_get_version(i,reinterpret_cast(buf2))) + tmp << "), driver version " << buf2[0] << "." << buf2[1] << "/" << buf2[2] << "." << buf2[3]; + else + tmp << "), unknown driver version"; + + tmp << endl; + } + } + return tmp.str(); +} + +/* History + +$Log: capi.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.28 2003/02/10 14:20:52 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.27.2.1 2003/02/09 15:05:36 ghillie +- rewritten to use native pthread_* calls instead of CommonC++ Thread + +Revision 1.27 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.26 2003/01/06 16:29:25 ghillie +- added debug output for constructor/destructor +- added call to terminate() in destructor +- missing break added in readMessage (oops... :-|) +- consider CapiReceiveQueueEmpty as error in readMessage() +- support finishing in run() +- only call readMessage() when waitformessage returned CapiNoError + +Revision 1.25 2003/01/04 16:07:42 ghillie +- log improvements: log_level, timestamp + +Revision 1.24 2002/12/18 14:40:23 ghillie +- removed this nasty listen_state. It had no sense than making problems. +- small change in getInfo() +- added check for abilities in setListen* if you say 0 for all controllers. + Do nothing if this ability isn't provided for this controller + +Revision 1.23 2002/12/13 09:57:10 ghillie +- error message formatting done by exception classes now + +Revision 1.22 2002/12/09 15:32:58 ghillie +- debug stream now given in constructor +- debug output improvements +- exception cleanup (removed WARNING exceptions, output messages, ...) + +Revision 1.21 2002/12/06 12:55:28 ghillie +- don't delete Connection objects any more + +Revision 1.20 2002/12/05 15:00:06 ghillie +- new methods registerApplicationInterface(), unregisterConnection() +- use debug stream given in constructor, not from ApplicationInterface any more +- connect_req gets Connection* now, enters it with pseudoPLCI in connections map +- readMessage(): when receiving CONNECT_CONF: searches for pseudoPLCI in connections and moves this entry to real plci +- readMessage(): when receiving DISCONNECT_INF: don't erase Connection object from connections map, this will be done in Connection::~Connection + +Revision 1.19 2002/12/02 16:53:51 ghillie +- minor debug output change + +Revision 1.18 2002/11/29 11:11:12 ghillie +- moved communication thread from own class (CapiCommThread) to Capi class + +Revision 1.17 2002/11/29 10:22:19 ghillie +- updated comments, use doxygen format now +- fixed small typo in setListen* methods + +Revision 1.16 2002/11/27 15:58:52 ghillie +caught exceptions from disconnect_ind() + +Revision 1.15 2002/11/25 20:57:21 ghillie +setListen* methods can now set listen state on all found controllers + +Revision 1.14 2002/11/22 15:08:22 ghillie +- new method select_b_protocol_req() +- added SELECT_B_PROTOCOL_CONF case in readMessage() + +Revision 1.13 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.12 2002/11/18 14:24:09 ghillie +- moved global severity_t to CapiError::severity_t +- added throw() declarations + +Revision 1.11 2002/11/17 14:38:34 ghillie +- improved exception throwing: using different exception types with severity parameter (FATAL,ERROR,WARNING) now +- changed getInfo() to use macros from /usr/include/capiutils.h now + +Revision 1.10 2002/11/15 15:25:53 ghillie +added ALERT_REQ so we don't loose a call when we wait before connection establishment + +Revision 1.9 2002/11/15 13:49:10 ghillie +fix: callmodule wasn't aborted when call was only connected/disconnected physically + +Revision 1.8 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.7 2002/11/10 17:04:21 ghillie +improved some debug output + +Revision 1.6 2002/11/08 07:57:07 ghillie +added functions to initiate a call +corrected FACILITY calls to use PLCI instead of NCCI in DTMF processing as told by Mr. Ortmann on comp.dcom.isdn.capi + +Revision 1.5 2002/10/31 15:39:04 ghillie +added missing FACILITY_RESP message (oops...) + +Revision 1.4 2002/10/31 12:37:34 ghillie +added DTMF support + +Revision 1.3 2002/10/29 15:42:29 ghillie +typos... + +Revision 1.2 2002/10/29 14:09:12 ghillie +fixed order in DISCONNECT_ROUTINE: first call callCompleted, then delete connection object! +cosmetic fixes + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.12 2002/10/09 11:18:59 gernot +cosmetic changes (again...) and changed info function of CAPI class + +Revision 1.11 2002/10/05 20:43:32 gernot +quick'n'dirty, but WORKS + +Revision 1.10 2002/10/05 13:53:00 gernot +changed to use thread class of CommonC++ instead of the threads-package +some cosmetic improvements (indentation...) + +Revision 1.9 2002/10/04 15:56:34 gernot +reactivated error handling for Capi::getInfo() + +Revision 1.8 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.7 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.6 2002/10/02 14:10:07 gernot +first version + +Revision 1.5 2002/10/01 09:02:04 gernot +changes for compilation with gcc3.2 + +Revision 1.4 2002/09/22 14:22:53 gernot +some cosmetic comment improvements ;-) + +Revision 1.3 2002/09/19 12:08:19 gernot +added magic CVS strings + +* Sun Sep 15 2002 - gernot@hillier.de +- put under CVS, cvs changelog follows + +* Sun May 19 2002 - gernot@hillier.de +- changed to not using QT libs any more + +* Sun Apr 1 2002 - gernot@hillier.de +- first version + +*/ diff --git a/src/backend/capi.h b/src/backend/capi.h new file mode 100644 index 0000000..ed08e64 --- /dev/null +++ b/src/backend/capi.h @@ -0,0 +1,500 @@ +/** @file capi.h + @brief Contains Capi - Main Class for communication with CAPI + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +***************************************************************************/ + +#ifndef CAPI_H +#define CAPI_H + +#include +#include +#include +#include "capiexception.h" + +using namespace std; + +class Connection; +class ApplicationInterface; + +/** @brief Thread exec handler for Capi class + + This is a handler which will call this->run() for the use in pthread_create(). +*/ +void* capi_exec_handler(void* args); + +/** @brief Main Class for communication with CAPI + + This class is the main encapsulation to use the CAPI ISDN interface. + + There are only a small subset of methods which are of use for the application layer. These are for general purposes like enabling + listening to calls and getting some nice formatted info strings. + + The biggest amount are shadow methods for nearly all CAPI messages, which do the dumb stuff like increasing message numbers, + testing for errors, building message structures and so on. Not each parameter of these is described in every detail here. + For more details please have a look in the CAPI 2.0 specification, available from http://www.capi.org + + There's also a big message handling routine (readMessage()) which calls special handlers for incoming messages of the CAPI. + + A Capi object creates a new thread (with body run()) which waits for incoming messages in an endless loop and hands them to readMessage(). + + This class only does the general things - for handling single connections see Connection. Connection objects will be automatically created + by this class for incoming connections and can be created manually to initiate an outgoing connection. + + The application is supposed to create one single object of this class. + + To communicate with the application via callback functions, the application must provide an implementation of the ApplicationInterface. + The methods of this interface are called when some special events are received. + + @author Gernot Hillier +*/ +class Capi { + friend class Connection; + friend void* capi_exec_handler(void*); + + public: + /** @brief Constructor. Registers our App at CAPI and start the communication thread. + + @param debug reference to a ostream object where debug info should be written to + @param debug_level verbosity level for debug messages + @param error reference to a ostream object where errors should be written to + @param maxLogicalConnection max. number of logical connections we will handle + @param maxBDataBlocks max. number of unconfirmed B3-datablocks, 7 is the maximum supported by CAPI + @param maxBDataLen max. B3-Datablocksize, 2048 is the maximum supported by CAPI + @throw CapiError Thrown if no ISDN controller is reported by CAPI + @throw CapiMsgError Thrown if registration at CAPI wasn't successful. + */ + Capi (ostream &debug, unsigned short debug_level, ostream &error, unsigned maxLogicalConnection=2, unsigned maxBDataBlocks=7,unsigned maxBDataLen=2048) throw (CapiError, CapiMsgError); + + /** @brief Destructor. Unregister App at CAPI + + @throw CapiMsgError Thrown if deregistration at CAPI failed. + */ + ~Capi(); + + /** @brief Register the instance implementing the ApplicationInterface + + @param application_in pointer to a class implementing (derived from) ApplicationInterface + */ + void registerApplicationInterface(ApplicationInterface* application_in); + + /** @brief Tell capi that we want to _additionally_ listen to Fax G3 calls + + This method enables listening to fax calls from the analog network (coded as Bearer Capability 3.1kHz audio and + from ISDN coded as fax group 3. The previously set listen mask is not cleared, so that the application + can call this method after another listen request w/o loosing the other services. + + @param Controller Nr. of Controller (0 = all available controllers) + @throw CapiMsgError Thrown by listen_req, see there for details + */ + void setListenFaxG3 (_cdword Controller=0) throw (CapiMsgError); + + /** @brief Tell capi that we want to _additionally_ listen to Telephony calls + + This method enables listening to speech calls from the analog network (coded as Bearer Capability 3.1kHz audio) and + from ISDN (coded as Bearer Capability Speech or High Layer Compatibility telephony). The previously set listen mask + is not cleared, so that the application can call this method after another listen request w/o loosing the other services. + + @param Controller Nr. of Controller (0 = all available controllers) + @throw CapiMsgError Thrown by listen_req, see there for details + */ + void setListenTelephony (_cdword Controller=0) throw (CapiMsgError); + + /**  @brief Static function which returns some info about the installed Controllers + + The returned string has the following format (UPPERCASE words replaced): + + "NUM controllers found. + CAPI_MANUFACTURER, version VERSION_OF_CAPI/SUBVERSION_OF_CAPI + Controller X: CONTROLLER_MANUFACTURER (NUM B channels, SERVICE1, SERVICE2, ..., driver version DRIVER_VERSION/DRIVER_SUBVERSION + Controller X+1: CONTROLLER_MANUFACTURER (NUM B channels, SERVICE1, SERVICE2, ..., driver version DRIVER_VERSION/DRIVER_SUBVERSION + ..." + + If verbose is set to false, only the first line is returned. + + The following services are checked currently and printed as SERVICEX if found: DTMF, SuppServ, transparent, FaxG3, FaxG3ext + + @param verbose controls verbosity of output (see above). + @return string containing details (see above). + */ + static string getInfo(bool verbose=false) throw (CapiMsgError); + + private: + + /** @brief erase Connection object in connections map + + This method is used by Connection::~Connection() + */ + void unregisterConnection (_cdword plci); + + /********************************************************************************/ + /* methods to send CAPI messages - called by the Connection class */ + /********************************************************************************/ + + /*************************** REQUESTS *******************************************/ + + /** @brief Send LISTEN_REQ to CAPI + + @param Controller Nr. of Controller + @param InfoMask see CAPI 2.0 spec, ch 5.37, default = 0x03FF -> all available info elements + @param CIPMask see CAPI 2.0 spec, ch 5.37, default = 0x1FFF03FF -> all available services + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void listen_req (_cdword Controller, _cdword InfoMask=0x03FF, _cdword CIPMask=0x1FFF03FF) throw (CapiMsgError); + + /** @brief Send ALERT_REQ to CAPI + + @param plci reference to physical connection + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void alert_req (_cdword plci) throw (CapiMsgError); + + /** @brief Send CONNECT_REQ to CAPI + + To be able to see which CONNECT_CONF corresponds to this CONNECT_REQ, the Connection object + will be saved in the connections map under a pseudo PLCI (0xFACE & messageNumber). This will + be corrected at the moment the CONNECT_CONF is received. + + @param conn reference to the Connection object which calls connect_req() + @param Controller Nr. of controller to use for connection establishment + @param CIPvalue CIP (service indicator) to use for the connection + @param calledPartyNumber The number of the party which is called (i.e. the destination of the call) + @param callingPartyNumber The number of the party which calls (i.e. the source of the call) + @param B1protocol see CAPI spec for details + @param B2protocol see CAPI spec for details + @param B3protocol see CAPI spec for details + @param B1configuration see CAPI spec for details + @param B2configuration see CAPI spec for details + @param B3configuration see CAPI spec for details + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void connect_req (Connection *conn, _cdword Controller, _cword CIPvalue, _cstruct calledPartyNumber, _cstruct callingPartyNumber, _cword B1protocol, _cword B2protocol, _cword B3protocol, _cstruct B1configuration, _cstruct B2configuration, _cstruct B3configuration) throw (CapiMsgError); + + /** @brief send SELECT_B_PROTOCOL_REQ to CAPI + + @param plci reference to physical connection + @param B1protocol see CAPI spec for details + @param B2protocol see CAPI spec for details + @param B3protocol see CAPI spec for details + @param B1configuration see CAPI spec for details + @param B2configuration see CAPI spec for details + @param B3configuration see CAPI spec for details + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void select_b_protocol_req (_cdword plci, _cword B1protocol, _cword B2protocol, _cword B3protocol, _cstruct B1configuration, _cstruct B2configuration, _cstruct B3configuration) throw (CapiMsgError); + + /** @brief send CONNECT_B3_REQ to CAPI + + @param plci reference to physical connection + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void connect_b3_req (_cdword plci) throw (CapiMsgError); + + /** @brief send DATA_B3_REQ to CAPI + + @param ncci reference to physical connection + @param Data pointer to transmission data + @param DataLength length of transmission data + @param DataHandle some word value which will be referred to in DATA_B3_CONF (to see which data packet was sent successful) + @param Flags see CAPI 2.0 spec + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void data_b3_req (_cdword ncci, void* Data, _cword DataLength,_cword DataHandle,_cword Flags) throw (CapiMsgError); + + /** @brief send DISCONNECT_B3_REQ to CAPI + + @param ncci reference to physical connection + @param ncpi protocol specific info + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void disconnect_b3_req (_cdword ncci, _cstruct ncpi=NULL) throw (CapiMsgError); + + /** @brief send DISCONNECT_REQ to CAPI + + @param plci reference to physical connection + @param Keypadfacility see CAPI spec + @param Useruserdata see CAPI spec + @param Facilitydataarray see CAPI spec + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void disconnect_req (_cdword plci, _cstruct Keypadfacility=NULL, _cstruct Useruserdata=NULL, _cstruct Facilitydataarray=NULL) throw (CapiMsgError); + + /** @brief send FACILITY_REQ to CAPI + + @param address Nr. of connection (Controller/PLCI/NCCI) + @param FacilitySelector see CAPI spec (1=DTMF) + @param FacilityRequestParameter see CAPI spec (too long to describe it here...) + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void facility_req (_cdword address, _cword FacilitySelector, _cstruct FacilityRequestParameter) throw (CapiMsgError); + + /*************************** RESPONSES *******************************************/ + + /** @brief send CONNECT_RESP to CAPI + + @param messageNumber number of the referred INDICATION message + @param plci reference to physical connection + @param reject tell CAPI if we want to accept (0) or reject (!=0, for details see CAPI spec) the incoming call + @param B1protocol see CAPI spec for details + @param B2protocol see CAPI spec for details + @param B3protocol see CAPI spec for details + @param B1configuration see CAPI spec for details + @param B2configuration see CAPI spec for details + @param B3configuration see CAPI spec for details + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void connect_resp (_cword messageNumber, _cdword plci, _cword reject, _cword B1protocol, _cword B2protocol, _cword B3protocol, _cstruct B1configuration, _cstruct B2configuration, _cstruct B3configuration) throw (CapiMsgError); + + /** @brief send CONNECT_ACTIVE_RESP to CAPI + + @param messageNumber number of the referred INDICATION message + @param plci reference to physical connection + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void connect_active_resp (_cword messageNumber, _cdword plci) throw (CapiMsgError); + + /** @brief send CONNECT_B3_RESP to CAPI + + @param messageNumber number of the referred INDICATION message + @param ncci reference to physical connection + @param reject tell CAPI if we want to accept (0) or reject (2) the incoming call + @param ncpi protocol specific info + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void connect_b3_resp (_cword messageNumber, _cdword ncci, _cword reject, _cstruct ncpi) throw (CapiMsgError); + + /** @brief send CONNECT_B3_ACTIVE_RESP to CAPI + + @param messageNumber number of the referred INDICATION message + @param ncci reference to physical connection + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void connect_b3_active_resp (_cword messageNumber, _cdword ncci) throw (CapiMsgError); + + /** @brief send DATA_B3_RESP to CAPI + + @param messageNumber number of the referred INDICATION message + @param ncci reference to physical connection + @param dataHandle Data Handle given by the referred DATA_B3_IND + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void data_b3_resp (_cword messageNumber, _cdword ncci, _cword dataHandle) throw (CapiMsgError); + + /** @brief send FACILITY_RESP to CAPI + + @param messageNumber number of the referred INDICATION message + @param address Nr. of connection (Controller/PLCI/NCCI) + @param facilitySelector see CAPI spec (1=DTMF) + @param facilityResponseParameter see CAPI spec + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void facility_resp (_cword messageNumber, _cdword address, _cword facilitySelector, _cstruct facilityResponseParameter=NULL) throw (CapiMsgError); + + /** @brief send DISCONNECT_RESP to CAPI + + @param messageNumber number of the referred INDICATION message + @param plci reference to physical connection + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void disconnect_resp (_cword messageNumber, _cdword plci) throw (CapiMsgError); + + /** @brief send DISCONNECT_B3_RESP to CAPI + + @param messageNumber number of the referred INDICATION message + @param ncci reference to physical connection + @throw CapiMsgError Thrown when CAPI_PUT_MESSAGE returned an error. + */ + void disconnect_b3_resp (_cword messageNumber, _cdword ncci) throw (CapiMsgError); + + /********************************************************************************/ + /* main message handling method for incoming msgs */ + /********************************************************************************/ + + /** @brief read Message from CAPI and process it accordingly + + This method handles all incoming messages. It is called by run() and will call + special handler methods of Connection mainly. Prints messages for debug purposes. + + @throw CapiMsgError directly raised when CAPI_GET_MESSAGE or LISTEN_REQ fails, may also be raised by all called *_ind, *_conf handlers + @throw CapiError directly raised when general error occurs (unknown call references, unknown message, ... received) + @throw CapiWrongState may be raised by all called *_ind(), *_conf() handlers + @throw CapiExternalError may be raised by some called *_ind(), *_conf() handlers + */ + void readMessage (void) throw (CapiMsgError, CapiError, CapiWrongState, CapiExternalError); + + /********************************************************************************/ + /* methods for internal use */ + /********************************************************************************/ + + /** @brief textual description for Parameter Info + + This method returns an error string for the given info parameter. The strings were + taken out of the CAPI 2.0 spec + + @param info errorcode as given by CAPI + @return textual description of error + */ + static string describeParamInfo (unsigned int info); + + /** @brief getApplId returns the application id we got from CAPI + + @return Application ID from CAPI + */ + unsigned short getApplId(void) {return applId;} + + /** @brief Thread body - endless loop, will be blocked until message is received and then call readMessage() + */ + virtual void run(void); + + /** @brief return a prefix containing this pointer and date for log messages + + @return constructed prefix as string + */ + string prefix(); + + /********************************************************************************/ + /* attributes */ + /********************************************************************************/ + + map <_cdword,Connection*> connections; ///< @brief containing pointers to the currently active Connection + ///< objects, referenced by PLCI (or 0xFACE & messageNum when Connection is in plci_state Connection::P01 + + static short numControllers; ///< number of installed controllers, set by getInfo() method + + _cword messageNumber; ///< sequencial message number, must be increased for every sent message + _cdword usedInfoMask; ///< InfoMask currently used (in last listen_req) + _cdword usedCIPMask; ///< CIPMask currently used (in last listen_req) + + unsigned applId; ///< containing the ID we got from CAPI after successful registration + + ApplicationInterface *application; ///< pointer to the application object implementing ApplicationInterface + ostream &debug, ///< stream to write debug info to + &error; ///< stream for error messages + unsigned short debug_level; ///< debug level + + pthread_t thread_handle; ///< handle for the created message reading thread +}; + +#endif + +/* History + +$Log: capi.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.22 2003/02/10 14:20:52 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.21.2.1 2003/02/09 15:05:36 ghillie +- rewritten to use native pthread_* calls instead of CommonC++ Thread + +Revision 1.21 2003/01/06 16:29:52 ghillie +- destructor doesn't throw any exceptions any more + +Revision 1.20 2003/01/04 16:07:42 ghillie +- log improvements: log_level, timestamp + +Revision 1.19 2002/12/18 14:40:44 ghillie +- removed this nasty listen_state. Made nothing than problems + +Revision 1.18 2002/12/11 13:05:34 ghillie +- minor comment improvements + +Revision 1.17 2002/12/09 15:33:23 ghillie +- debug and error stream now given in constructor + +Revision 1.16 2002/12/05 15:02:36 ghillie +- constructor: removed param application (pointer to ApplicationInterface, now given by registerApplInterface()), added param debug giving debug stream +- new methods registerApplicationInterface(), unregisterConnection() +- connect_req gets COnnection* now + +Revision 1.15 2002/11/29 11:38:13 ghillie +- missed some changes because CapiCommThread was deleted + +Revision 1.14 2002/11/29 11:11:12 ghillie +- moved communication thread from own class (CapiCommThread) to Capi class + +Revision 1.13 2002/11/29 10:23:07 ghillie +- updated comments, use doxygen format now + +Revision 1.12 2002/11/27 15:58:13 ghillie +updated comments for doxygen + +Revision 1.11 2002/11/25 20:58:47 ghillie +- improved documentation, is now readable by doxygen +- setListen* can now set listen state for all available controllers + +Revision 1.10 2002/11/22 15:08:22 ghillie +- new method select_b_protocol_req() +- added SELECT_B_PROTOCOL_CONF case in readMessage() + +Revision 1.9 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.8 2002/11/18 14:24:09 ghillie +- moved global severity_t to CapiError::severity_t +- added throw() declarations + +Revision 1.7 2002/11/17 14:39:23 ghillie +removed CapiError from this header -> exceptions are now defined in capiexception.h + +Revision 1.6 2002/11/15 15:25:53 ghillie +added ALERT_REQ so we don't loose a call when we wait before connection establishment + +Revision 1.5 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.4 2002/11/08 07:57:07 ghillie +added functions to initiate a call +corrected FACILITY calls to use PLCI instead of NCCI in DTMF processing as told by Mr. Ortmann on comp.dcom.isdn.capi + +Revision 1.3 2002/10/31 15:39:04 ghillie +added missing FACILITY_RESP message (oops...) + +Revision 1.2 2002/10/31 12:37:35 ghillie +added DTMF support + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.8 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.7 2002/10/09 11:18:59 gernot +cosmetic changes (again...) and changed info function of CAPI class + +Revision 1.6 2002/10/08 12:01:26 gernot +cosmetic... (indentation) + +Revision 1.5 2002/10/01 09:02:04 gernot +changes for compilation with gcc3.2 + +Revision 1.4 2002/09/22 14:22:53 gernot +some cosmetic comment improvements ;-) + +Revision 1.3 2002/09/19 12:08:19 gernot +added magic CVS strings + +* Sun Sep 15 2002 - gernot@hillier.de +- put under CVS, cvs changelog follows above + +* Sun May 19 2002 - gernot@hillier.de +- changed to not using QT libs any more +- modified to conform to CAPI20-Spec, 4th edition (parameter names, ...) + +* Sun Apr 1 2002 - gernot@hillier.de +- first version + +*/ diff --git a/src/backend/capiexception.h b/src/backend/capiexception.h new file mode 100644 index 0000000..b48266b --- /dev/null +++ b/src/backend/capiexception.h @@ -0,0 +1,209 @@ +/** @file capiexception.h + @brief Contains exception classes for errors in the CAPI abstraction layer + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CAPIEXCEPTION_H +#define CAPIEXCEPTION_H + +#include +#include +#include + +using namespace std; + +/** @brief General and base class for errors in the Capi abstraction layer. + + This is the general class for all Capi errors. It serves as base class for the more specific exceptions + and also as one-size-fits-all throwable object if the other errors doesn't fit. ;-) + + Each exception gets a severity (Warning, Error or Fatal), a message and the name of the function where it occurred. + If you need further data, please derive a sub-class or format it into the errormsg. + + @author Gernot Hillier +*/ +class CapiError +{ + public: + /** @brief Constructor. Create an object, print error message and abort if severity FATAL was chosen. + + @param errormsg some informal message describing the error + @param function_name name of the function which throws this exception + */ + CapiError(string errormsg,string function_name): + errormsg(errormsg),function_name(function_name) + {} + + /** @brief Return nice formatted error message + + Returns the string "Classname: error message occured in function()" + @return error message + */ + virtual string message() + { + return ("CapiError: "+errormsg+" occured in "+function_name); + } + + protected: + string errormsg; ///< textual error message + string function_name; ///< function/method where this error occured +}; + +/** @brief Capi Abstraction Layer exception class thrown if something should be done in a wrong state. + + This exception is thrown if the Connection is in a wrong state (see Connection::ncci_state and Connection::plci_state) + when something connection related should be done. This usually means the call was disconnected by the other party + and should be handled by clearing the ressources controlling the call. + + @author Gernot Hillier +*/ +class CapiWrongState : public CapiError +{ + public: + /** @brief Constructor. Create an object, print error message and abort if severity FATAL was chosen. + + @param errormsg some informal message describing the error + @param function_name name of the function which throws this exception + */ + CapiWrongState(string errormsg,string function_name): + CapiError("CapiWrongstate: "+errormsg,function_name) + {} + + /** @brief Return nice formatted error message + + Returns the string "Classname: error message occured in function()" + @return error message + */ + virtual string message() + { + return ("CapiWrongState: "+errormsg+" occured in "+function_name); + } +}; + +/** @brief Capi Abstraction Layer exception class thrown if an error is indicated by Capi + + This exception serves as a way to communicate errors indicated by CAPI to the application. These + are mostly errors indicated from CAPI_PUT_MESSAGE or *_CONF messages. + + It includes also the error code given by CAPI in the info attribute. + + @author Gernot Hillier +*/ +class CapiMsgError : public CapiError +{ + public: + /** @brief Constructor. Create an object, print error message and abort if severity FATAL was chosen. + + @param info error code given by CAPI + @param errormsg informal message describing the error + @param function_name name of the function which throws this exception + */ + CapiMsgError(unsigned info, string errormsg ,string function_name): + CapiError(errormsg,function_name),info(info) + {} + + /** @brief Return nice formatted error message + + Returns the string "Classname: error message occured in function()" + @return error message + */ + virtual string message() + { + stringstream m; + m << "CapiMsgError: " << errormsg << " (error code 0x" << hex << info << ") occured in " << function_name; + return (m.str()); + } + + protected: + unsigned info; ///< error code given by CAPI +}; + +/** @brief Capi Abstraction Layer exception class thrown if an error was caused by the application. + + This ecxeption should be raised by methods in the CAPI abstraction layer when an error was detected + that was clearly caused by the application (e.g. giving an invalid file to send, ...). + + @author Gernot Hillier +*/ +class CapiExternalError : public CapiError +{ + public: + /** @brief Constructor. Create an object, print error message and abort if severity FATAL was chosen. + + @param errormsg informal message describing the error + @param function_name name of the function which throws this exception + */ + CapiExternalError(string errormsg,string function_name): + CapiError("CapiExternalError: "+errormsg,function_name) + {} + + /** @brief Return nice formatted error message + + Returns the string "Classname: error message occured in function()" + @return error message + */ + virtual string message() + { + return ("CapiExternalError: "+errormsg+" occured in "+function_name); + } +}; + +/** @brief Overloaded operator for output of error classes +*/ +inline ostream& operator<<(ostream &s, CapiError &e) +{ + s << e.message(); + return s; +} + +#endif + +/* History + +$Log: capiexception.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.9 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.8 2002/12/13 09:57:10 ghillie +- error message formatting done by exception classes now + +Revision 1.7 2002/12/11 13:05:34 ghillie +- minor comment improvements + +Revision 1.6 2002/12/09 15:39:01 ghillie +- removed severity WARNING +- exception class doesn't print error message any more + +Revision 1.5 2002/11/29 10:24:09 ghillie +- updated comments, use doxygen format now +- changed some parameter names in constructor of CapiMsgError + +Revision 1.4 2002/11/27 16:00:02 ghillie +updated comments for doxygen + +Revision 1.3 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.2 2002/11/18 14:24:09 ghillie +- moved global severity_t to CapiError::severity_t +- added throw() declarations + +Revision 1.1 2002/11/17 14:42:22 ghillie +initial checkin + +*/ diff --git a/src/backend/connection.cpp b/src/backend/connection.cpp new file mode 100644 index 0000000..6da5c27 --- /dev/null +++ b/src/backend/connection.cpp @@ -0,0 +1,1204 @@ +/* @file connection.cpp + @brief Contains Connection - Encapsulates a CAPI connection with all its states and methods. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include "capi.h" +#include "callinterface.h" +#include "connection.h" + +#define conf_send_buffers 4 + +// TODO NCPI handling für Fax +// TODO Bconfiguration für Fax überprüfen + +using namespace std; + +Connection::Connection (_cmsg& message, Capi* capi_in): + call_if(NULL),capi(capi_in),plci_state(P2),ncci_state(N0), buffer_start(0), buffers_used(0), + file_for_reception(NULL), file_to_send(NULL), received_dtmf(""), keepPhysicalConnection(false), + disconnect_cause(0),debug(capi->debug), debug_level(capi->debug_level), error(capi->error), + our_call(false), disconnect_cause_b3(0) +{ + pthread_mutex_init(&send_mutex, NULL); + pthread_mutex_init(&receive_mutex, NULL); + + plci=CONNECT_IND_PLCI(&message); // Physical Link Connection Identifier + call_from = getNumber(CONNECT_IND_CALLINGPARTYNUMBER(&message),true); + call_to = getNumber(CONNECT_IND_CALLEDPARTYNUMBER(&message),false); + if (debug_level >= 1) { + debug << prefix() << "Connection object created for incoming call PLCI " << plci << endl; + debug << prefix() << "from " << call_from << " to " << call_to << " CIP 0x" << hex << CONNECT_IND_CIPVALUE(&message) << endl; + } + switch (CONNECT_IND_CIPVALUE(&message)) { + case 1: + case 4: + case 16: + service=VOICE; + break; + case 17: + service=FAXG3; + break; + default: + service=OTHER; + break; + } + connect_ind_msg_nr=message.Messagenumber; // this is needed as connect_resp is given later +} + +Connection::Connection (Capi* capi, _cdword controller, string call_from_in, bool clir, string call_to_in, service_t service, string faxStationID, string faxHeadline) throw (CapiExternalError, CapiMsgError) + :call_if(NULL),capi(capi),plci_state(P01),ncci_state(N0),plci(0),service(service), buffer_start(0), buffers_used(0), + file_for_reception(NULL), file_to_send(NULL), call_from(call_from_in), call_to(call_to_in), connect_ind_msg_nr(0), + disconnect_cause(0), debug(capi->debug), debug_level(capi->debug_level), error(capi->error), keepPhysicalConnection(false), + our_call(true), disconnect_cause_b3(0) +{ + pthread_mutex_init(&send_mutex, NULL); + pthread_mutex_init(&receive_mutex, NULL); + + if (debug_level >= 1) { + debug << prefix() << "Connection object created for outgoing call from " << call_from << " to " << call_to + << " service " << dec << service << endl; + } + if (debug_level >= 2) { + debug << prefix() << "using faxStationID " << faxStationID << " faxHeadline " << faxHeadline << " CLIR " << clir << endl; + } + _cstruct B1config=NULL, B2config=NULL, B3config=NULL, calledPartyNumber=NULL, callingPartyNumber=NULL; + _cword B1proto,B2proto,B3proto; + + try { + _cword CIPvalue; + switch (service) { + case VOICE: + CIPvalue=16; + break; + case FAXG3: + CIPvalue=17; + break; + default: + throw CapiExternalError("unsupported service given","Connection::Connection()"); + break; + } + + buildBconfiguration(service, faxStationID, faxHeadline, B1proto, B2proto, B3proto, B1config, B2config, B3config); + + if (!call_to.size()) + throw CapiExternalError("calledPartyNumber is required","Connection::Connection()"); + + calledPartyNumber=new unsigned char [1+1+call_to.size()]; //struct length, number type/number plan, number + calledPartyNumber[0]=1+call_to.size(); // length + calledPartyNumber[1]=0x80; // as suggested by CAPI spec (unknown number type, unknown number plan, see ETS 300 102-1) + for (unsigned j=0;jconnect_req(this,controller,CIPvalue, calledPartyNumber, callingPartyNumber,B1proto,B2proto,B3proto,B1config, B2config, B3config); + } catch (...) { + if (B1config) + delete[] B1config; + if (B2config) + delete[] B2config; + if (B3config) + delete[] B3config; + if (calledPartyNumber) + delete[] calledPartyNumber; + if (callingPartyNumber) + delete[] callingPartyNumber; + throw; + } + if (B1config) + delete[] B1config; + if (B2config) + delete[] B2config; + if (B3config) + delete[] B3config; + if (calledPartyNumber) + delete[] calledPartyNumber; + if (callingPartyNumber) + delete[] callingPartyNumber; +} + +Connection::~Connection() +{ + stop_file_transmission(); + stop_file_reception(); + + if (getState()!=DOWN) { + error << prefix() << "WARNING: please disconnect yourself before deleting connection object!!" << endl; + disconnectCall(PHYSICAL_ONLY); + while (getState()!=DOWN) + ; + } + plci_state=P0; + + pthread_mutex_lock(&send_mutex); // assure the lock is free before destroying it + pthread_mutex_unlock(&send_mutex); + pthread_mutex_destroy(&send_mutex); + + pthread_mutex_lock(&receive_mutex); // assure the lock is free before destroying it + pthread_mutex_unlock(&receive_mutex); + pthread_mutex_destroy(&receive_mutex); + + if (debug_level >= 1) { + debug << prefix() << "Connection object deleted" << endl; + } +} + +void +Connection::registerCallInterface(CallInterface *call_if_in) +{ + call_if=call_if_in; +} + +void +Connection::changeProtocol(service_t desired_service, string faxStationID, string faxHeadline) throw (CapiMsgError, CapiExternalError, CapiWrongState) +{ + if (debug_level >= 2) { + debug << prefix() << "Protocol change to " << desired_service << " requested" << endl; + } + + if (ncci_state!=N0 || plci_state!=PACT) + throw CapiWrongState("wrong state for changeProtocol","Connection::changeProtocol()"); + + if (desired_service!=service) { + _cstruct B1config=NULL, B2config=NULL, B3config=NULL; + _cword B1proto,B2proto,B3proto; + + try { + buildBconfiguration(desired_service, faxStationID, faxHeadline, B1proto, B2proto, B3proto, B1config, B2config, B3config); + + capi->select_b_protocol_req(plci,B1proto,B2proto,B3proto,B1config, B2config, B3config); + } catch (...) { + if (B1config) + delete[] B1config; + if (B2config) + delete[] B2config; + if (B3config) + delete[] B3config; + throw; + } + if (B1config) + delete[] B1config; + if (B2config) + delete[] B2config; + if (B3config) + delete[] B3config; + + service=desired_service; + } +} + +void +Connection::connectWaiting(service_t desired_service, string faxStationID, string faxHeadline) throw (CapiWrongState, CapiExternalError, CapiMsgError) +{ + if (debug_level >= 1) { + debug << prefix() << "accepting with service " << desired_service << endl; + } + if (debug_level >= 2) { + debug << prefix() << "using faxStationID " << faxStationID << " faxHeadline " << faxHeadline << endl; + } + if (plci_state!=P2) + throw CapiWrongState("wrong state for connectWaiting","Connection::connectWaiting()"); + + if (our_call) + throw (CapiError("can't accept an outgoing call","Connection::connectWaiting()")); + + _cstruct B1config=NULL, B2config=NULL, B3config=NULL; + _cword B1proto,B2proto,B3proto; + + try { + buildBconfiguration(desired_service, faxStationID, faxHeadline, B1proto, B2proto, B3proto, B1config, B2config, B3config); + + plci_state=P4; + capi->connect_resp(connect_ind_msg_nr,plci,0,B1proto,B2proto,B3proto,B1config, B2config, B3config); + } catch (...) { + if (B1config) + delete[] B1config; + if (B2config) + delete[] B2config; + if (B3config) + delete[] B3config; + throw; + } + if (B1config) + delete[] B1config; + if (B2config) + delete[] B2config; + if (B3config) + delete[] B3config; + service=desired_service; +} + +void +Connection::rejectWaiting(_cword reject) throw (CapiWrongState, CapiMsgError, CapiExternalError) +{ + if (debug_level >= 1) { + debug << prefix() << "rejecting with cause " << reject << endl; + } + if (plci_state!=P2) + throw CapiWrongState("wrong state for reject","Connection::reject()"); + if (our_call) + throw (CapiError("can't accept an outgoing call","Connection::connectWaiting()")); + if (!reject) + throw CapiExternalError("reject cause must not be zero","Connection::reject()"); + + plci_state=P5; + capi->connect_resp(connect_ind_msg_nr,plci,reject,0,0,0,NULL,NULL,NULL); // can throw CapiMsgError. Propagate +} + +void +Connection::acceptWaiting() throw (CapiMsgError, CapiWrongState) +{ + if (plci_state!=P2) + throw CapiWrongState("wrong state for acceptWaiting","Connection::acceptWaiting()"); + capi->alert_req(plci); +} + +string +Connection::getCalledPartyNumber() +{ + return call_to; +} + +string +Connection::getCallingPartyNumber() +{ + return call_from; +} + +string +Connection::prefix() +{ + stringstream s; + time_t t=time(NULL); + char* ct=ctime(&t); + ct[24]='\0'; + s << ct << " Connection " << hex << this << ": "; + return (s.str()); +} + +void +Connection::debugMessage(string message, unsigned short level) +{ + if (debug_level >= level) + debug << prefix() << message << endl; +} + +void +Connection::errorMessage(string message) +{ + error << prefix() << message << endl; +} + +Connection::service_t +Connection::getService() +{ + return service; +} + +Connection::connection_state_t +Connection::getState() +{ + if (plci_state==PACT && ncci_state==NACT) + return UP; + else if (plci_state==P2 && ncci_state==N0) + return WAITING; + else if (plci_state==P0 && ncci_state==N0) + return DOWN; + else + return OTHER_STATE; +} + +_cword +Connection::getCause() +{ + return disconnect_cause; +} + +_cword +Connection::getCauseB3() +{ + return disconnect_cause_b3; +} + +void +Connection::connect_active_ind(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (plci_state!=P4 && plci_state!=P1) { + throw CapiWrongState("CONNECT_ACTIVE_IND received in wrong state","Connection::connect_active_ind()"); + } else { + try { + capi->connect_active_resp(message.Messagenumber,plci); + } + catch (CapiMsgError e) { + error << prefix() << "WARNING: error detected when trying to send connect_active_resp. Message was:" << e << endl; + } + + if (plci_state==P1) { // this is an outgoing call, so we have to initiate B3 connection + ncci_state=N01; + try { + capi->connect_b3_req(plci); + } + catch (CapiMsgError) { + plci_state=PACT; + ncci_state=N0; + throw; // this is critical, so propagate + } + } + plci_state=PACT; + } +} + +void +Connection::connect_b3_ind(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (ncci_state!=N0) { + throw CapiWrongState("CONNECT_B3_IND received in wrong state","Connection::connect_b3_ind()"); + } else { + ncci=CONNECT_B3_IND_NCCI(&message); + + // 0 = we'll accept any call, NULL=no NCPI necessary + // this can throw CapiMsgError. Propagate. + ncci_state=N2; + capi->connect_b3_resp(message.Messagenumber,ncci,0,NULL); + } +} + +void +Connection::connect_b3_active_ind(_cmsg& message) throw (CapiWrongState, CapiExternalError) +{ + if (ncci_state!=N2) { + throw CapiWrongState("CONNECT_B3_ACTIVE_IND received in wrong state","Connection::connect_active_b3_ind()"); + } else { + if (ncci!=CONNECT_B3_IND_NCCI(&message)) + throw CapiError("CONNECT_B3_ACTIVE_IND received with wrong NCCI","Connection::connect_active_b3_ind()"); + try { + capi->connect_b3_active_resp(message.Messagenumber,ncci); + } + catch (CapiMsgError e) { + error << prefix() << "WARNING: Error deteced when sending connect_b3_active_resp. Message was: " << e << endl; + } + ncci_state=NACT; + if (call_if) + call_if->callConnected(); + else + throw CapiExternalError("no call control interface registered!","Connection::connect_b3_active_ind()"); + } +} + +void +Connection::disconnect_b3_ind(_cmsg& message) throw (CapiWrongState) +{ + if (ncci_state!=NACT && ncci_state!=N1 && ncci_state!=N2 && ncci_state!=N3 && ncci_state!=N4) { + throw CapiWrongState("DISCONNECT_B3_IND received in wrong state","Connection::disconnect_b3_ind()"); + } else { + if (ncci!=DISCONNECT_B3_IND_NCCI(&message)) + throw CapiError("DISCONNECT_B3_IND received with wrong NCCI","Connection::disconnect_b3_ind()"); + + disconnect_cause_b3=DISCONNECT_B3_IND_REASON_B3(&message); + + pthread_mutex_lock(&send_mutex); + buffers_used=0; // we'll get no DATA_B3_CONF's after DISCONNECT_B3_IND, see Capi 2.0 spec, 5.18, note for DATA_B3_CONF + pthread_mutex_unlock(&send_mutex); + + stop_file_transmission(); + stop_file_reception(); + + bool our_disconnect_req= (ncci_state==N4) ? true : false; + + ncci_state=N5; + + if (call_if) + call_if->callDisconnectedLogical(); + + try { + ncci_state=N0; + capi->disconnect_b3_resp(message.Messagenumber,ncci); + } + catch (CapiMsgError e) { + error << prefix() << "WARNING: Can't send disconnect_b3_resp. Message was: " << e << endl; + } + + if (our_disconnect_req && !keepPhysicalConnection) { // this means *we* initiated disconnect of logical connection with DISCONNECT_B3_REQ before + try { + plci_state=P5; + capi->disconnect_req(plci); // so we'll continue with the disconnect of physical connection + } + catch (CapiMsgError e) { + // in this application this is fatal. Panic please. + throw CapiError("Can't disconnect. Please file a bug report. Error message: "+e.message(),"Connection::disconnect_b3_ind()"); + } + } else + keepPhysicalConnection=false; + } +} + +void +Connection::disconnect_ind(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (ncci_state!=N0 || (plci_state!=P1 && plci_state!=P2 && plci_state!=P3 && plci_state!=P4 && plci_state!=P5 && plci_state!=PACT)) { + throw CapiWrongState("DISCONNECT_IND received in wrong state","Connection::disconnect_ind()"); + } else { + if (plci!=DISCONNECT_IND_PLCI(&message)) + throw CapiError("DISCONNECT_IND received with wrong PLCI","Connection::disconnect_ind()"); + + disconnect_cause=DISCONNECT_IND_REASON(&message); + + plci_state=P0; + capi->disconnect_resp(message.Messagenumber,plci); + capi->unregisterConnection(plci); + + if (call_if) + call_if->callDisconnectedPhysical(); + } +} + +void +Connection::data_b3_ind(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (ncci_state!=NACT && ncci_state!=N4) + throw CapiWrongState("DATA_B3_IND received in wrong state","Connection::data_b3_ind()"); + + if (ncci!=CONNECT_B3_IND_NCCI(&message)) + throw CapiError("DATA_B3_IND received with wrong NCCI","Connection::data_b3_ind()"); + + pthread_mutex_lock(&receive_mutex); + if (file_for_reception) { + for (int i=0;idataIn(DATA_B3_IND_DATA(&message),DATA_B3_IND_DATALENGTH(&message)); + + capi->data_b3_resp(message.Messagenumber,ncci,DATA_B3_IND_DATAHANDLE(&message)); +} + +void +Connection::facility_ind_DTMF(_cmsg &message) throw (CapiWrongState) +{ + if (plci_state!=PACT) + throw CapiWrongState("FACILITY_IND received in wrong state","Connection::facility_ind_DTMF()"); + + if (plci!=(FACILITY_IND_PLCI(&message) & 0xFFFF) ) // this *should* be PLCI, but who knows - so mask NCCI part if it's there... + throw CapiError("FACILITY_IND received with wrong PLCI","Connection::facility_ind_DTMF()"); + + try { + capi->facility_resp(message.Messagenumber,plci,1); + } + catch (CapiMsgError e) { + error << prefix() << "WARNING: Can't send facility_resp. Message was: " << e << endl; + } + + _cstruct facilityIndParam=FACILITY_IND_FACILITYINDICATIONPARAMETER(&message); + received_dtmf.append(reinterpret_cast(facilityIndParam+1),static_cast(facilityIndParam[0])); //string, length + if (debug_level >= 2) { + debug << prefix() << "received DTMF buffer " << received_dtmf << endl; + } + + if (call_if) + call_if->gotDTMF(); +} + +void +Connection::connect_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (plci_state!=P01) + throw CapiWrongState("CONNECT_CONF received in wrong state","Connection::connect_conf()"); + + if (CONNECT_CONF_INFO(&message)) + throw CapiMsgError(CONNECT_CONF_INFO(&message),"CONNECT_CONF received with Error (Info)","Connection::connect_conf()"); + // TODO: do we have to delete Connection here if Info!=0 or is a DISCONNECT_IND initiated then (think not ...) + + plci=CONNECT_CONF_PLCI(&message); + if (debug_level >= 2) { + debug << prefix() << "got PLCI " << plci << endl; + } + + plci_state=P1; +} + +void +Connection::connect_b3_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (ncci_state!=N01) + throw CapiWrongState("CONNECT_B3_CONF received in wrong state","Connection::connect_b3_conf()"); + + if (CONNECT_B3_CONF_INFO(&message)) { + ncci_state=N0; + throw CapiMsgError(CONNECT_B3_CONF_INFO(&message),"CONNECT_B3_CONF received with Error (Info)","Connection::connect_b3_conf()"); + } + + ncci=CONNECT_B3_CONF_NCCI(&message); + + ncci_state=N2; +} + +void +Connection::select_b_protocol_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (plci_state!=PACT || ncci_state!=N0) + throw CapiWrongState("SELECT_B_PROTOCOL_CONF received in wrong state","Connection::select_b_protocol_conf()"); + + if (plci!=SELECT_B_PROTOCOL_CONF_PLCI(&message)) + throw CapiError("SELECT_B_PROTOCOL_CONF received with wrong PLCI","Connection::select_b_protocol_conf()"); + + if (SELECT_B_PROTOCOL_CONF_INFO(&message)) + throw CapiMsgError(SELECT_B_PROTOCOL_CONF_INFO(&message),"SELECT_B_PROTOCOL_CONF received with Error (Info)","Connection::select_b_protocol_conf()"); + + if (our_call) { + try { + ncci_state=N01; + capi->connect_b3_req(plci); + } + catch (CapiMsgError) { + ncci_state=N0; + throw; // this is critical, so propagate + } + } +} + +void +Connection::alert_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (plci_state!=P2 && plci_state!=P5) + throw CapiWrongState("ALERT_CONF received in wrong state","Connection::alert_conf()"); + + if (plci!=ALERT_CONF_PLCI(&message)) + throw CapiError("ALERT_CONF received with wrong PLCI","Connection::alert_conf()"); + + if (ALERT_CONF_INFO(&message) && ALERT_CONF_INFO(&message)!=0x0003) // 0x0003 = another application sent ALERT_REQ earlier -> no problem for us + throw CapiMsgError(ALERT_CONF_INFO(&message),"ALERT_CONF received with Error (Info)","Connection::alert_conf()"); +} + +void +Connection::data_b3_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError, CapiExternalError) +{ + if (ncci_state!=NACT) + throw CapiWrongState("DATA_B3_CONF received in wrong state","Connection::data_b3_conf()"); + + if (ncci!=DATA_B3_CONF_NCCI(&message)) + throw CapiError("DATA_B3_CONF received with wrong NCCI","Connection::data_b3_conf()"); + + if (DATA_B3_CONF_INFO(&message)) + throw CapiMsgError(DATA_B3_CONF_INFO(&message),"DATA_B3_CONF received with Error (Info)","Connection::data_b3_conf()"); + + if ( (!buffers_used) || (DATA_B3_CONF_DATAHANDLE(&message)!=buffer_start) ) + throw CapiError("DATA_B3_CONF received with invalid data handle","Connection::data_b3_conf()"); + + pthread_mutex_lock(&send_mutex); + // free one buffer + buffers_used--; + buffer_start=(buffer_start+1)%7; + + try { + while (file_to_send && (buffers_used < conf_send_buffers) ) + send_block(); + } + catch (...) { + pthread_mutex_unlock(&send_mutex); + throw; + } + pthread_mutex_unlock(&send_mutex); +} + +void +Connection::facility_conf_DTMF(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (plci_state!=PACT) + throw CapiWrongState("FACILITY_CONF for DTMF received in wrong state","Connection::facility_conf_DTMF()"); + + if (plci!=(FACILITY_CONF_PLCI(&message) & 0xFFFF)) // this *should* be the PLCI but to be sure we mask out NCCI part + throw CapiError("FACILITY_CONF received with wrong PLCI","Connection::facility_conf_DTMF()"); + + if (FACILITY_CONF_INFO(&message)) + throw CapiMsgError(FACILITY_CONF_INFO(&message),"FACILITY_CONF received with Error (Info)","Connection::facility_conf_DTMF()"); + + _cstruct facilityConfParameter=FACILITY_CONF_FACILITYCONFIRMATIONPARAMETER(&message); + if ((facilityConfParameter[0]==2) && facilityConfParameter[1]) + throw CapiMsgError(FACILITY_CONF_INFO(&message),"FACILITY_CONF received with DTMF Error (DTMF information)","Connection::facility_conf_DTMF()"); +} + +void +Connection::disconnect_b3_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (ncci_state!=N4) + throw CapiWrongState("DISCONNECT_B3_CONF received in wrong state","Connection::disconnect_b3_conf()"); + + if (ncci!=DISCONNECT_B3_CONF_NCCI(&message)) + throw CapiError("DISCONNECT_B3_CONF received with wrong NCCI","Connection::disconnect_b3_conf()"); + + if (DISCONNECT_B3_CONF_INFO(&message)) + throw CapiMsgError(DISCONNECT_B3_CONF_INFO(&message),"DISCONNECT_B3_CONF received with Error (Info)","Connection::disconnect_b3_conf()"); +} + +void +Connection::disconnect_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError) +{ + if (plci_state!=P5) + throw CapiWrongState("DISCONNECT_CONF received in wrong state","Connection::disconnect_conf()"); + + if (plci!=DISCONNECT_CONF_PLCI(&message)) + throw CapiError("DISCONNECT_CONF received with wrong PLCI","Connection::disconnect_conf()"); + + if (DISCONNECT_CONF_INFO(&message)) + throw CapiMsgError(DISCONNECT_CONF_INFO(&message),"DISCONNECT_CONF received with Error (Info)","Connection::disconnect_conf()"); +} + +void +Connection::disconnectCall(disconnect_mode_t disconnect_mode) throw (CapiMsgError) +{ + if (debug_level >= 1) { + debug << prefix() << "disconnect initiated" << endl; + } + if ((ncci_state==N1 || ncci_state==N2 || ncci_state==N3 || ncci_state==NACT) && (disconnect_mode==ALL || disconnect_mode==LOGICAL_ONLY) ) { // logical connection up + ncci_state=N4; + capi->disconnect_b3_req(ncci); // can throw CapiMsgError. Fatal here. Propagate + if (disconnect_mode==LOGICAL_ONLY) + keepPhysicalConnection=true; + } else if ((plci_state==PACT || plci_state==P1 || plci_state==P2 || plci_state==P3 || plci_state==P4) && (disconnect_mode==ALL || disconnect_mode==PHYSICAL_ONLY) ) { // physical connection up + plci_state=P5; + capi->disconnect_req(plci); // can throw CapiMsgError. Fatal here. Propagate + } + // otherwise do nothing +} + +void +Connection::send_block() throw (CapiWrongState, CapiExternalError, CapiMsgError) +{ + if (ncci_state!=NACT) + throw CapiWrongState("unable to send file because connection is not established","Connection::send_block()"); + + if (!file_to_send) + throw CapiError("unable to play file because no input file is open","Connection::send_block()"); + + if (buffers_used>=7) + throw CapiError("unable to send file snippet because buffers are full","Connection::send_block()"); + + bool file_completed=false; + + unsigned short buff_num=(buffer_start+buffers_used)%7; // buffer to store the next item + + int i=0; + while (i<2048 && !file_completed) { + if (!file_to_send->get(send_buffer[buff_num][i])) + file_completed=true; + else + i++; + } + + try { + if (i>0) { + capi->data_b3_req(ncci,send_buffer[buff_num],i,buff_num,0); // can throw CapiMsgError. Propagate. + buffers_used++; + } + } + catch (CapiMsgError e) { + error << prefix() << "WARNING: Can't send data_b3_req. Message was: " << e << endl; + if (file_completed) { + file_to_send->close(); + delete file_to_send; + file_to_send=NULL; + } + } + + if (file_completed) { + file_to_send->close(); + delete file_to_send; + file_to_send=NULL; + if (call_if) + call_if->transmissionComplete(); + else + throw CapiExternalError("no call control interface registered!","Connection::send_block()"); + } +} + +void +Connection::start_file_transmission(string filename) throw (CapiWrongState, CapiExternalError, CapiMsgError) +{ + if (debug_level >= 2) { + debug << prefix() << "start_file_transmission " << filename << endl; + } + if (ncci_state!=NACT) + throw CapiWrongState("unable to send file because connection is not established","Connection::start_file_transmission()"); + + if (file_to_send) + throw CapiExternalError("unable to send file because transmission is already in progress","Connection::start_file_transmission()"); + + file_to_send=new ifstream(filename.c_str()); + + if (! (*file_to_send)) { // we can't open the file + delete file_to_send; + file_to_send=NULL; + throw CapiExternalError("unable to open file to send ("+filename+")","Connection::start_file_transmission()"); + } else { + pthread_mutex_lock(&send_mutex); + try { + while (file_to_send && buffers_used= 2) { + debug << prefix() << "stop_file_transmission initiated" << endl; + } + pthread_mutex_lock(&send_mutex); + if (file_to_send) { + file_to_send->close(); + delete file_to_send; + file_to_send=NULL; + } + pthread_mutex_unlock(&send_mutex); + + timespec delay_time; + delay_time.tv_sec=0; delay_time.tv_nsec=100000000; // 100 msec + while (buffers_used) // wait until all packages are transmitted + nanosleep(&delay_time,NULL); + if (debug_level >= 2) { + debug << prefix() << "stop_file_transmission finished" << endl; + } +} + +void +Connection::start_file_reception(string filename) throw (CapiWrongState, CapiExternalError) +{ + if (debug_level >= 2) { + debug << prefix() << "start_file_reception " << filename << endl; + } + if (ncci_state!=NACT) + throw CapiWrongState("unable to receive file because connection is not established","Connection::start_file_reception()"); + + if (file_for_reception) + throw CapiExternalError("file reception is already active","Connection::start_file_reception()"); + + file_for_reception=new ofstream(filename.c_str()); + if (! (*file_for_reception)) { // we can't open the file + delete file_for_reception; + file_for_reception=NULL; + throw CapiExternalError("unable to open file for reception ("+filename+")","Connection::start_file_reception()"); + } +} + +void +Connection::stop_file_reception() +{ + pthread_mutex_lock(&receive_mutex); + + if (file_for_reception) { + file_for_reception->close(); + delete file_for_reception; + file_for_reception=NULL; + } + + pthread_mutex_unlock(&receive_mutex); + if (debug_level >= 2) { + debug << prefix() << "stop_file_reception finished" << endl; + } +} + +void +Connection::enableDTMF() throw (CapiWrongState, CapiMsgError) +{ + if (plci_state!=PACT) + throw CapiWrongState("unable to enable DTMF because connection is not established","Connection::enableDTMF()"); + + _cstruct facilityRequestParameter=new unsigned char[1+2+2+2+1+3]; + int i=0; + facilityRequestParameter[i++]=2+2+2+1+3; // total length + facilityRequestParameter[i++]=1; facilityRequestParameter[i++]=0; // start DTMF listen + facilityRequestParameter[i++]=40; facilityRequestParameter[i++]=0; // default value for tone-duration + facilityRequestParameter[i++]=40; facilityRequestParameter[i++]=0; // default value for gap-duration + facilityRequestParameter[i++]=0; // we don't want to send DTMF now (=empty struct) + facilityRequestParameter[i++]=2; // now let's start substruct DTMF Characteristics (length) + facilityRequestParameter[i++]=0; facilityRequestParameter[i++]=0; // default value for DTMF Selectivity + + try { + capi->facility_req(plci,1,facilityRequestParameter); + } + catch (CapiMsgError) { + delete[] facilityRequestParameter; + throw; + } + delete[] facilityRequestParameter; +} + +void +Connection::disableDTMF() throw (CapiWrongState, CapiMsgError) +{ + if (plci_state!=PACT) + throw CapiWrongState("unable to disable DTMF because connection is not established","Connection::disableDTMF()"); + + _cstruct facilityRequestParameter=new unsigned char[1+2+2+2+1+1]; + int i=0; + facilityRequestParameter[i++]=2+2+2+1+1; // total length + facilityRequestParameter[i++]=2; facilityRequestParameter[i++]=0; // stop DTMF listen + facilityRequestParameter[i++]=40; facilityRequestParameter[i++]=0; // default value for tone-duration + facilityRequestParameter[i++]=40; facilityRequestParameter[i++]=0; // default value for gap-duration + facilityRequestParameter[i++]=0; // we don't want to send DTMF now (=empty struct) + facilityRequestParameter[i++]=0; // no DTMF Characteristics + + try { + capi->facility_req(plci,1,facilityRequestParameter); + } + catch (CapiMsgError) { + delete[] facilityRequestParameter; + throw; + } + delete[] facilityRequestParameter; +} + +string +Connection::getDTMF() +{ + return received_dtmf; +} + +void +Connection::clearDTMF() +{ + received_dtmf.clear(); +} + +string +Connection::getNumber(_cstruct capi_input, bool isCallingNr) +{ +// CallingNr: byte 0: length (w/o byte 0), Byte 1+2 see ETS 300 102-1, Chapter 4.5, byte 3-end: number (w/o leading "0" or "00") +// CalledNr: byte 0: length (w/o byte 0), Byte 1 see ETS 300 102-1, Chapter 4, byte 2-end: number w/o leading "0" or "00" + int length=capi_input[0]; + + if (!length) // no info element given + return "??"; + + char *nr=new char[length]; + memcpy (nr,&capi_input[2],length-1); // copy only number + nr[length-1]='\0'; // add \0 + string a(nr); + if (isCallingNr) + a=a.substr(1); + + // if we are looking at a CallingPartyNumber and it is an international number or a national number + // (see ETS 300 102-1, chapter 4.5), we'll add the prefix "0" or "+" + + if (a.empty()) { + a="??"; + } else if (isCallingNr && ((capi_input[1] & 0x70) == 0x20)) { //  national number + a='0'+a; + } else if (isCallingNr && ((capi_input[1] & 0x70) == 0x10)) { // international number + a='+'+a; + } + return a; +} + +void +Connection::buildBconfiguration(service_t service, string faxStationID, string faxHeadline, _cword& B1proto, _cword& B2proto, _cword& B3proto, _cstruct& B1config, _cstruct& B2config, _cstruct& B3config) throw (CapiExternalError) +{ + switch (service) { + case VOICE: + B1proto=1; // bit-transparent + B2proto=1; // Transparent + B3proto=0; // Transparent + B1config=NULL; // no configuration for bit-transparent available + B2config=NULL; // no configuration for transparent available + B3config=NULL; // no configuration for transparent available + break; + + case FAXG3: { + B1proto=4; // T.30 modem for Fax G3 + B2proto=4; // T.30 for Fax G3 + B3proto=4; // T.30 for Fax G3 TODO: should be changed to 5 if necessary!! + B1config=NULL; // default configuration (adaptive maximum baud rate, default transmit level) + B2config=NULL; // no configuration available + + if (faxStationID.size()>254) // if the string would be longer the struct must be coded different, but I think a ID > 254 bytes has no sence anyway + faxStationID=faxStationID.substr(0,254); + if (faxHeadline.size()>254) // if the string would be longer the struct must be coded different, but I think a header > 254 bytes has no sence + faxHeadline=faxHeadline.substr(0,254); + _cstruct B3config=new unsigned char [1+2+2+1+faxStationID.size()+1+faxHeadline.size()]; // length + 1 byte for the length itself + int i=0; + B3config[i++]=2+2+1+faxStationID.size()+1+faxHeadline.size(); // length + B3config[i++]=0; B3config[i++]=0; // resolution = standard + B3config[i++]=0; B3config[i++]=0; // format: SFF + B3config[i++]=faxStationID.size(); + for (unsigned j=0;junregisterConnection(plci) in destructor +- connect_conf() sets plci attribute +- connect_b3_conf() sets ncci attribute + +Revision 1.25 2002/12/04 10:43:43 ghillie +- small FIX in getNumber(): added missing parantheses in if condition -> national number & international number work now + +Revision 1.24 2002/12/02 12:31:10 ghillie +- renamed Connection::SPEECH to Connection::VOICE + +Revision 1.23 2002/11/29 10:25:01 ghillie +- updated comments, use doxygen format now + +Revision 1.22 2002/11/27 16:02:54 ghillie +- added missing throw() declaration in changeProtocol() +- added missing state check in acceptWaiting() +- data_b3_ind and disconnect_ind propagate CapiMsgError now +- DTMF handling routines and select_b_protocol_conf test for state of physical connection instead of logical connection now + +Revision 1.21 2002/11/25 11:51:45 ghillie +- removed the unhandy CIP parameters from the interface to the application layer, use service type instead +- rejectWaiting() tests against cause!=0 now +- removed isUp() method + +Revision 1.20 2002/11/22 15:13:44 ghillie +- new attribute keepPhysicalConnection which prevents disconnect_b3_ind() from sending disconnect_req() +- moved the ugly B*configuration, B*protocol settings from some methods to private method buildBconfiguration +- new methods changeProtocol(), select_b_protocol_conf(), clearDTMF() +- disconnect_b3_ind sets ncci_state to N0 before calling the callbacks +- added parameter disconnect_mode to disconnectCall() +- getDTMF() does non-destructive read now + +Revision 1.19 2002/11/21 15:28:12 ghillie +- removed ALERT_REQ sending from constructor - this is now done by the python functions connect_*() +- new method Connection::acceptWaiting() - sends ALERT_REQ for use by the above mentioned python functions +- connectWaiting changes cipValue now + +Revision 1.18 2002/11/20 17:24:58 ghillie +- added check if call_if is set in data_b3_ind before it's called (ouch!) +- changed impossible error to ::FATAL in send_block() + +Revision 1.17 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.16 2002/11/18 14:24:09 ghillie +- moved global severity_t to CapiError::severity_t +- added throw() declarations + +Revision 1.15 2002/11/18 12:23:17 ghillie +- fix: set buffers_used to 0 in critical section in Connection::disconnect_b3_ind() +- disconnectCall() doesn't throw exception any more (does nothing if we have wrong state), + so we can call it w/o knowledge if connection is still up + +Revision 1.14 2002/11/17 14:40:47 ghillie +- improved exception throwing, different exception kinds are used now +- added isUp() + +Revision 1.13 2002/11/15 15:25:53 ghillie +added ALERT_REQ so we don't loose a call when we wait before connection establishment + +Revision 1.12 2002/11/15 13:49:10 ghillie +fix: callmodule wasn't aborted when call was only connected/disconnected physically + +Revision 1.11 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.10 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.9 2002/11/12 15:51:12 ghillie +minor fixes (avoid deadlock, don't wait for DATA_B3_CONF after DISCONNECT_B3_IND) in file_transmission code +added dataIn handler +minor fixes (and reformatting) in getNumber() + +Revision 1.8 2002/11/10 17:05:18 ghillie +changed to support multiple buffers -> deadlock in stop_file_transmission!! + +Revision 1.7 2002/11/08 07:57:06 ghillie +added functions to initiate a call +corrected FACILITY calls to use PLCI instead of NCCI in DTMF processing as told by Mr. Ortmann on comp.dcom.isdn.capi + +Revision 1.6 2002/10/31 15:39:04 ghillie +added missing FACILITY_RESP message (oops...) + +Revision 1.5 2002/10/31 12:40:06 ghillie +added DTMF support +small fixes like making some unnecessary global variables local, removed some unnecessary else cases + +Revision 1.4 2002/10/30 14:29:25 ghillie +added getCIPvalue + +Revision 1.3 2002/10/30 10:47:13 ghillie +added debug output + +Revision 1.2 2002/10/29 14:27:09 ghillie +added stop_file_*, added semaphore calls to guarantee right order of execution (I hope ;-) ) + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.15 2002/10/24 09:55:52 ghillie +many fixes. Works for one call now + +Revision 1.14 2002/10/23 09:43:05 ghillie +small variable name change (stationID->faxStationID) + +Revision 1.13 2002/10/10 12:45:40 gernot +added AudioReceive module, some small details changed + +Revision 1.12 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.11 2002/10/09 11:18:59 gernot +cosmetic changes (again...) and changed info function of CAPI class + +Revision 1.10 2002/10/05 13:53:00 gernot +changed to use thread class of CommonC++ instead of the threads-package +some cosmetic improvements (indentation...) + +Revision 1.9 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.8 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) +does not do anything useful yet nor does it even compile... + +Revision 1.7 2002/10/01 09:02:04 gernot +changes for compilation with gcc3.2 + +Revision 1.6 2002/09/22 14:22:53 gernot +some cosmetic comment improvements ;-) + +Revision 1.5 2002/09/19 12:08:19 gernot +added magic CVS strings + +Revision 1.4 2002/09/18 16:59:48 gernot +added version info + +* Sun Sep 15 2002 - gernot@hillier.de +- put under CVS, cvs log follows above + +* Sun May 20 2002 - gernot@hillier.de +- first version + +*/ diff --git a/src/backend/connection.h b/src/backend/connection.h new file mode 100644 index 0000000..89d73ab --- /dev/null +++ b/src/backend/connection.h @@ -0,0 +1,774 @@ +/** @file connection.h + @brief Contains Connection - Encapsulates a CAPI connection with all its states and methods. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#include +#include +#include +#include "capiexception.h" + +class CallInterface; +class Capi; + +using namespace std; + +/** @brief Encapsulates a CAPI connection with all its states and methods. + + This class encapsulates one ISDN connection (physical and logical). It has two groups of methods: + methods which are supposed to be called from the application (declared public) which serve to + access the connection, send data, clear it etc. Also there are many message handlers implemented + (declared as private) which are called by Capi::readMessage() and do the most message handling stuff. + + Only one logical connection per physical connection is supported. + + To communicate with the application by callback functions, the application must implement the CallInterface. + The methods of this class are called if different events occur like call completion or a finished data transfer. + + There are two ways how Connection objects are created: + - automatically for each incoming connection + - manually to initiate a outgoing connection + + @author Gernot Hillier +*/ +class Connection +{ + friend class Capi; + + public: + /** @brief Type for describing the service of incoming and outgoing calls. + + Several similar services are grouped together so the application doesn't have to distinct between speech calls + originating from the analog or digital network, between devices providing a High Layer compatibility and devices + which only provide bearer capabilities and so on. + + For outgoing calls, the most specific value will be used. + + For exact details concerning the different services see the parameter CIP in the CAPI spec. + */ + enum service_t { + VOICE, ///< connections for speech services originated from the analog or digital network (CIP values 1,4,16) + FAXG3, ///< connections for fax Group2/3 services originating from the digital network (as analog networks don't provide service indicator) (CIP value 17) + OTHER ///< all other service types not included in the above values + }; + + /** @brief Constructor. Create an object to initiate an outgoing call + + This constructor is used when the application creates a Connection object manually to initiate an outgoing connection. + + It constructs the necessary information elements (B channel protocol settings, Number elements) and calls Capi::connect_req + to initiate a call. + + @param capi pointer to the Capi Object + @param controller number of the controller which should initiate the call + @param call_from our number + @param clir set to TRUE to hide our number (so that the called party can't see it) + @param call_to the number which we want to call + @param service service to use (currently only VOICE and FAXG3 are supported), see service_t + @param faxStationID fax station ID, only used with service faxG3 + @param faxHeadline fax headline (written on each fax page), only used with service faxG3 + @throw CapiExternalError thrown when parameters are missing or wrong + @throw CapiMsgError thrown by Capi::connect_req, see there + */ + Connection (Capi* capi, _cdword controller, string call_from, bool clir, string call_to, service_t service, string faxStationID, string faxHeadline) throw (CapiExternalError, CapiMsgError); + + /** @brief. Destructor. Deletes the connection object. + + Can block if file transmission is still in progress and/or if connection is not cleared already. + + Please call as soon as you don't need the object any more as this will also free some + CAPI resources associated to the call. + + Please disconnect before deleting a Connection object! The auto-disconnect here is only supported + to prevent fatal errors and won't work very nicely!! + */ + ~Connection(); + + /** @brief Register the instance implementing the CallInterface + + @param call_if - pointer to the instance to use + */ + void registerCallInterface(CallInterface *call_if); + + /** @brief Change the used B protcols (e.g. switch from speech to faxG3) + + You have to disconnect the logical connection before calling this method. So to change from speech to fax do: + - disconnect(LOGICAL_ONLY); + - wait for CallInterface::CallDisconnectedLogical() to be called + - changeProtocol(FAXG3,"stationID","headline") + - wait for CallInterface::callConnected() to be called + + Does nothing if the requested service is already active. Otherwise the necessary information elements are built + and Capi::select_b_protocol_req is called. + + @param desired_service service to switch to + @param faxStationID Only needed when switching to FaxG3. The fax station ID to use. + @param faxHeadline Only needed when switching to FaxG3. The fax headline to use. + @throw CapiExternalError Thrown by Connection::buildBconfiguration. See there. + @throw CapiMsgError Thrown by Connection::select_b_protocol_req. See there. + @throw CapiWrongState Connection is in wrong state. Either it was finished by the partner or you didn't disconnect the logical connection before calling changeProtocol + */ + void changeProtocol (service_t desired_service, string faxStationID, string faxHeadline) throw (CapiMsgError, CapiExternalError, CapiWrongState); + + /** @brief called to start sending of a file + + The transmission will be controlled automatically by Connection - no further user interaction is necessary + if the whole file should be sent. + + The file has to be in the correct format expected by CAPI, i.e. bit-reversed A-Law, 8 khz, mono (".la" for sox) for speech, SFF for faxG3 + + @param filename the name of the file which should be sent + @throw CapiWrongState Thrown if Connection isn't up completely (physical & logical) + @throw CapiExternalError Thrown if file transmission is already in progress or the file couldn't be opened + @throw CapiMsgError Thrown by send_block(). See there. + */ + void start_file_transmission(string filename) throw (CapiWrongState, CapiExternalError, CapiMsgError); + + /** @brief called to stop sending of the current file, will block until file is really finished + + If you stop the file transmission manually, CallInterface::transferCompleted won't be called. + */ + void stop_file_transmission(); + + /** @brief called to activate receive mode + + This method doen't do anything active, it will only set the receive mode for this connection + If you don't call this, all received data is discarded. If called, the data B3 stream received + is written to this file w/o changes. So it's in the native format given by CAPI (i.e. inserved A-Law + for speech, SFF for FaxG3). + + @param filename name of the file to which to save the incoming data + @throw CapiWrongState Thrown if Connection isn't up completely (physical & logical) + @throw CapiExternalError Thrown if file reception is already in progress or the file couldn't be opened + */ + void start_file_reception(string filename) throw (CapiWrongState, CapiExternalError); + + /** @brief called to stop receive mode + + This closes the reception file and tells us to ignore further incoming B3 data. + */ + void stop_file_reception(); + + /** @brief Tells disconnectCall() method how to disconnect. + */ + enum disconnect_mode_t { + ALL, ///< do the necessary steps to finish the call nicely (first logical, then physical) + PHYSICAL_ONLY, ///< quick'n'dirty disconnect (physical only, logical will auto-disconnect with protocol error) + LOGICAL_ONLY ///< only disconnect logical connection, physical will stay up + }; + + /** @brief Terminate this connection + + According to the current state and the given mode, the needed steps to disconnect will be executed. + + If you don't specify a mode, the connection will be completely cleared (first logical, then physical). + + It doesn't throw any error if you call it for an already cleared connection, so it's safe to call it more + than one time. + + To reject a waiting call you haven't accepted earlier, use rejectWaiting(), not disconnectCall(). + + Attention: Connection objects will not be deleted after the disconnection. You must delete the Connection + object explicitly. The reason for this behavior is to allow you to get connection related information + (like disconnect cause, ...) after the connection has cleared. See also CallInterface::callDisconnectedPhysical(). + + @param disconnect_mode see description of disconnect_mode_t + @throw CapiMsgError Thrown by Capi::disconnect_b3_req() or Capi::disconnect_req(). See there. + */ + void disconnectCall(disconnect_mode_t disconnect_mode=ALL) throw (CapiMsgError); + + /** @brief Accept a waiting incoming call. + + Will update the saved service to the desired_service. Calls Capi::connect_resp(). + + @param desired_service to determine with which service we should connect (e.g. VOICE or FAXG3) + @param faxStationID my fax station ID - only needed in FAXG3 mode + @param faxHeadline my fax headline - only needed in FAXG3 mode + @throw CapiWrongState Thrown if call is not in necessary state (waiting for acception or rejection) + @throw CapiExternalError Thrown by buildBconfiguration. See there. + @throw CapiMsgError Thrown by Capi::connect_resp(). See there. + */ + void connectWaiting(service_t desired_service, string faxStationID="", string faxHeadline="") throw (CapiWrongState, CapiExternalError, CapiMsgError); + + /** @brief Reject a waiting incoming call + + Only use for waiting calls - to disconnect an already connected call use disconnectCall(). + + @param reject reject cause, see CAPI spec ch 5.6 for details + @throw CapiWrongState Thrown if call is not in necessary state (waiting for acception or rejection) + @throw CapiExternalError Thrown if reject cause is not set (cause 0 not allowed). + @throw CapiMsgError Thrown by Capi::connect_resp(). See there. + */ + void rejectWaiting(_cword reject) throw (CapiWrongState, CapiMsgError, CapiExternalError); + + /** @brief disable timeout for a pending call (i.e. send ALERT) + + Normally, a call is indicated from ISDN and timeouts after 4 (or 8) seconds if no answer is received. + If you want to accept the call, not immediately, but some secods later, you must call acceptWaiting(). + This tells ISDN that we will accept it some times later (i.e. send ALERT_REQ) + + @throw CapiWrongState Thrown if call is not in necessary state (waiting for acception or rejection) + @throw CapiMsgError Thrown by Capi::calert_req(). See there. + */ + void acceptWaiting() throw (CapiMsgError, CapiWrongState); + + /** @brief Enable indication for DTMF signals + + Normally you get no indication when the CAPI recognizes a DTMF signal. With enableDTMF() + you enable these indications. DTMF signals will be saved locally and signalled by CallInterface::gotDTMF(). + You can read the saved DTMF signales with getDTMF(). + + @throw CapiWrongState Thrown if Connection isn't up completely (physical & logical) + @throw CapiMsgError Thrown by Capi::facility_req(). See there. + */ + void enableDTMF() throw (CapiWrongState, CapiMsgError); + + /** @brief Disable indication for DTMF signals + + @throw CapiWrongState Thrown if Connection isn't up completely (physical & logical) + @throw CapiMsgError Thrown by Capi::facility_req(). See there. + */ + void disableDTMF() throw (CapiWrongState, CapiMsgError); + + /** @brief read the saved DTMF characters + + DTMF characters will only be saved if you called enableDTMF() before. + +  @return string containing received DTMF coded as characters ('0'..'9','A'..'D','*','#','X'=fax CNG,'Y'=fax CED) + */ + string getDTMF(); + + /** @brief Delete the saved DTMF characters + */ + void clearDTMF(); + + /** @brief Return number of the called party (the source of the call) + + @return CalledPartyNumber + */ + string getCalledPartyNumber(); + + /** @brief Return number of calling party (the destination of the call) + + @return callingPartyNumber + */ + string getCallingPartyNumber(); + + /** @brief Return currently used service mode + + @return service as described in Connection::service_t + */ + service_t getService(); + + /** @brief Return disconnection cause given by the CAPI + + 0x33xx=see CAPI spec + 0x34xx=ISDN cause, for coding of xx see ETS 300102-1. + + @return Returns the disconnection cause, 0=no cause available + */ + _cword getCause(); + + /** @brief Return disconnection cause given by the CAPI + + 0x33xx=see CAPI spec + 0x34xx=ISDN cause, for coding of xx see ETS 300102-1. + + @return Returns the disconnection cause of Layer3, 0=no cause available or normal clearing + */ + _cword getCauseB3(); + + + /** @brief Represents the current connection state in an easy way + */ + enum connection_state_t { + DOWN, ///< this means the connection is completely down, i.e. physical and logical down + WAITING, ///< this means an incoming connection is waiting for our decision (accept or reject) + UP, ///< this means the connection is completely up, i.e. physical and logical up + OTHER_STATE ///< connection is in some other state (e.g. physical up, logical down, ...) + }; + + /** @brief Return the connection state + + @return current connection state as specified in Connection::connection_state_t + */ + connection_state_t getState(); + + /** @brief Output error message + + This is intended for external use if some other part of the application wants to make a error-log entry. + For internal use in this class just output to the stream "error". + + @param message the message to print + */ + void errorMessage(string message); + + + /** @brief Output debug message + + This is intended for external use if some other part of the application wants to make a log entry. + For internal use in this class just output to the stream "debug". + + @param message the message to print + @param level the debug level of the given message (see capisuite.conf for explanation) + */ + void debugMessage(string message, unsigned short level); + + protected: + + /** @brief Constructor. Create an object for an incoming call + + This one is used by Capi when an incoming connection triggers the automatic creation of a Connection object, i.e. + when we receive a CONNECT_IND message. + + It only extracts some data (numbers, services, etc.) from the message and saves it in private attributes. + + @param message the received CONNECT_IND message + @param capi pointer to the Capi Object + */ + Connection (_cmsg& message, Capi *capi); + + /********************************************************************************/ + /* methods handling CAPI messages - called by the Capi class */ + /********************************************************************************/ + + /*************************** INDICATIONS ****************************************/ + + /** @brief Called when we get CONNECT_ACTIVE_IND from CAPI + + This method will also send a response to Capi and initiate + a B3 connection if necessary. + + @param message the received CONNECT_ACTIVE_IND message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong plci_state) + @throw CapiMsgError Thrown by Capi::connect_b3_req + */ + void connect_active_ind(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /** @brief called when we get CONNECT_B3_IND from CAPI + + This method will also send a response to Capi. + + @param message the received CONNECT_B3_IND message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong ncci_state) + @throw CapiMsgError Thrown by Capi::connect_b3_resp() + */ + void connect_b3_ind(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /** @brief called when we get CONNECT_B3_ACTIVE_IND from CAPI + + This method will also send a response to Capi and call CallInterface::callConnected(). + + @param message the received CONNECT_B3_ACTIVE_IND message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong ncci_state) + @throw CapiExternalError Thrown if no CallInterface is registered + */ + void connect_b3_active_ind(_cmsg& message) throw (CapiWrongState, CapiExternalError); + + /** @brief called when we get DATA_B3_IND from CAPI + + This method will also save the received data, send a response to Capi and call CallInterface::dataIn(). + + @param message the received DATA_B3_IND message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong ncci_state) + @throw CapiMsgError Thrown by Capi::data_b3_resp() + */ + void data_b3_ind(_cmsg& message) throw (CapiWrongState,CapiMsgError); + + /** @brief called when we get FACILITY_IND from CAPI with facility selector saying it's DTMF + + This method will save the received DTMF to received_dtmf, send a response to Capi and call + CallInterface::gotDTMF(). + + @param message the received FACILITY_IND message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong ncci_state) + */ + void facility_ind_DTMF(_cmsg& message) throw (CapiWrongState); + + /** @brief called when we get DISCONNECT_B3_IND from CAPI + + This method will also send a response to Capi and stop_file_transmission and stop_file_reception(). + It will call CallInterface::callDisconnectedLogical() and initiate termination of physical connection. + + @param message the received DISCONNECT_B3_IND message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong ncci_state) + */ + void disconnect_b3_ind(_cmsg& message) throw (CapiWrongState); + + /** @brief called when we get DISCONNECT_IND from CAPI + + This method will also send a response to Capi and call CallInterface::callDisconnectedPhysical(). + + @param message the received DISCONNECT_IND message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong ncci_state or plci_state) + @throw CapiMsgError Thrown by Capi::disconnect_resp() + */ + void disconnect_ind(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /*************************** CONFIRMATIONS **************************************/ + + /** @brief called when we get CONNECT_CONF from CAPI + + @param message the received CONNECT_CONF message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong plci_state) + @throw CapiMsgError Thrown if the info InfoElement indicates an error + */ + void connect_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /** @brief called when we get CONNECT_B3_CONF from CAPI + + @param message the received CONNECT_B3_CONF message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong ncci_state) + @throw CapiMsgError Thrown if the info InfoElement indicates an error + */ + void connect_b3_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /** @brief called when we get SELECT_B_PROTOCOL_CONF from CAPI + + @param message the received SELECT_B_PROTOCOL_CONF message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong plci_state) + @throw CapiMsgError Thrown if the info InfoElement indicates an error + */ + void select_b_protocol_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /** @brief called when we get ALERT_CONF from CAPI + + @param message the received ALERT_CONF message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong plci_state) + @throw CapiMsgError Thrown if the info InfoElement indicates an error + */ + void alert_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /** @brief called when we get DATA_B3_CONF from CAPI + + This will trigger new send_block(). + + @param message the received DATA_B3_CONF message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong plci_state) + @throw CapiMsgError Thrown if the info InfoElement indicates an error + @throw CapiExternalError Thrown by Connection::send_block() + */ + void data_b3_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError, CapiExternalError); + + /** @brief called when we get FACILITY_CONF from CAPI with facility selector saying it's DTMF + + @param message the received FACILITY_CONF message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong plci_state) + @throw CapiMsgError Thrown if the info InfoElement indicates an error + */ + void facility_conf_DTMF(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /** @brief called when we get DISCONNECT_B3_CONF from CAPI + + @param message the received DISCONNECT_B3_CONF message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong plci_state) + @throw CapiMsgError Thrown if the info InfoElement indicates an error + */ + void disconnect_b3_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /** @brief called when we get DISCONNECT_CONF from CAPI + + @param message the received DISCONNECT_CONF message + @throw CapiWrongState Thrown when the message is received unexpected (i.e. in a wrong plci_state) + @throw CapiMsgError Thrown if the info InfoElement indicates an error + */ + void disconnect_conf(_cmsg& message) throw (CapiWrongState, CapiMsgError); + + /********************************************************************************/ + /* internal methods */ + /********************************************************************************/ + + /** @brief return a prefix containing this pointer and date for log messages + + @return constructed prefix as stringstream + */ + string prefix(); + + /** @brief format the CallingPartyNr or CalledPartyNr to readable string + + @param capi_input the CallingPartyNumber resp. CalledPartyNumber struct + @param isCallingNr true = CallingPartyNr / false = CalledPartyNr + @return the number formatted as string + */ + string getNumber (_cstruct capi_input, bool isCallingNr); + + /** @brief called to send next block (2048 bytes) of file + + The transmission will be controlled automatically by Connection, so you don't + need to call this method directly. send_block() will automatically send as much + packets as the configured window size (conf_send_buffers) permits. + + Will call CallInterface::transmissionComplete() if the file was transferred completely. + + @throw CapiWrongState Thrown when the the connection is not up completely (physical & logical) + @throw CapiExternalError Thrown when no CallInterface is registered + @throw CapiMsgError Thrown by Capi::data_b3_req(). + */ + void send_block() throw (CapiWrongState, CapiExternalError, CapiMsgError); + + /** @brief called to build the B Configuration info elements out of given service + + This is a convenience functin to do the quite annoying enconding stuff for the 6 B configuration parameters. + + @param service value indicating service to be used as described in service_t + @param faxStationID my fax station ID + @param faxHeadline the fax headline + @param B1proto return value: B1protocol value for CAPI, see CAPI spec + @param B2proto return value: B2protocol value for CAPI, see CAPI spec + @param B3proto return value: B3protocol value for CAPI, see CAPI spec + @param B1config return value: B1configuration element for CAPI, see CAPI spec + @param B2config return value: B2configuration element for CAPI, see CAPI spec + @param B3config return value: B3configuration element for CAPI, see CAPI spec + */ + void buildBconfiguration(service_t service, string faxStationID, string faxHeadline, _cword& B1proto, _cword& B2proto, _cword& B3proto, _cstruct& B1config, _cstruct& B2config, _cstruct& B3config) throw (CapiExternalError); + + /********************************************************************************/ + /* attributes */ + /********************************************************************************/ + + /** @brief State for the physical connection as defined in the CAPI spec. + + For complete diagrams and state definition see CAPI 2.0 spec, chapter 7.2 + + */ + enum plci_state_t { + P0, ///< default state. no connection. + P01, ///< CONNECT_REQ sent, waiting for CONNECT_CONF + P1, ///< CONNECT_CONF received, waiting for CONNECT_ACTIVE_IND + P2, ///< CONNECT_IND received, no CONNECT_RESP given yet + P3, ///< only needed for handsets + P4, ///< CONNECT_RESP sent, waiting for CONNECT_ACTIVE_IND + P5, ///< DISCONNECT_REQ sent, waiting for DISCONNECT_IND + P6, ///< DISCONNECT_IND received, DISCONNECT_RESP not sent yet + PACT ///< active physical connection + } plci_state; + + /** @brief State for logical connection as defined in the CAPI spec + + For complete diagrams and state definition see CAPI 2.0 spec, chapter 7.2 + */ + enum ncci_state_t { + N0, ///< default state, no connection. + N01, ///< CONNECT_B3_REQ sent, waiting for CONNECT_B3_CONF + N1, ///< CONNECT_B3_IND received, CONNECT_B3_RESP not sent yet + N2, ///< CONNECT_B3_CONF received / CONNECT_B3_RESP sent, waiting for CONNECT_B3_ACTIVE_IND + N3, ///< RESET_B3_REQ sent, waiting for RESET_B3_IND + N4, ///< DISCONNECT_B3_REQ sent, waiting for DISCONNECT_B3_IND + N5, ///< DISCONNECT_B3_IND received + NACT ///< active logical connection + } ncci_state; + + _cdword plci; ///< CAPI id for call + _cdword ncci; ///< id for logical connection + + service_t service; ///< as described in Connection::service_t, set to the last known service (either got from ISDN or set explicitly) + + _cword connect_ind_msg_nr; ///< this is needed as connect_resp is given in another function as connect_ind + + _cword disconnect_cause, ///< the disconnect cause as given by the CAPI in DISCONNECT_IND + disconnect_cause_b3; ///< the disconnect cause as given by the CAPI in DISCONNECT_B3_IND + + string call_from; ///< CallingPartyNumber, formatted as string with leading '0' or '+' prefix + string call_to; ///< CalledPartyNumber, formatted as string + + string received_dtmf; ///< accumulates the received DTMF data, see readDTMF() + + bool keepPhysicalConnection, ///< set to true to disable auto-physical disconnect after logical disconnect for one time + our_call; ///< set to true if we initiated the call (needed to know as some messages must be sent if we initiated the call) + + CallInterface *call_if; ///< pointer to the object implementing CallInterface + Capi *capi; ///< pointer to the Capi object + + pthread_mutex_t send_mutex, ///< to realize critical sections in transmission code + receive_mutex; ///< to realize critical sections in reception code + + ofstream *file_for_reception; ///< NULL if no file is received, pointer to the file otherwise + ifstream *file_to_send; ///< NULL if no file is sent, pointer to the file otherwise + + ostream &debug, ///< debug stream + &error; ///< stream for error messages + unsigned short debug_level; ///< debug level + + /** @brief ring buffer for sending + + 7 buffers a 2048 byte mark the maximal window size handled by CAPI + + is empty: buffer_used==0 / is full: buffers_used==7 + to forget item: buffers_used--; buffer_start++; + to remember item: send_buffer[ (buffer_start+buffers_used)%8 ]=item; buffers_used++ + */ + char send_buffer[7][2048]; + + unsigned short buffer_start, ///< holds the index for the first buffer currently used + buffers_used; ///< holds the number of currently used buffers +}; + +#endif + +/* History + +$Log: connection.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.30 2003/02/10 14:20:52 ghillie +merged from NATIVE_PTHREADS to HEAD + +Revision 1.29.2.1 2003/02/10 14:07:54 ghillie +- use pthread_mutex_* instead of CommonC++ Semaphore + +Revision 1.29 2003/01/04 16:08:22 ghillie +- log improvements: log_level, timestamp +- added methods debugMessage(), errorMessage(), removed get*Stream() +- added some additional debug output for connection setup / finish + +Revision 1.28 2002/12/16 13:13:47 ghillie +- added getCauseB3 to return B3 cause + +Revision 1.27 2002/12/13 11:46:59 ghillie +- added attribute our_call to differ outgoing and incoming calls + +Revision 1.26 2002/12/11 13:39:05 ghillie +- added support for PHYSICAL_ONLY disconnect in disconnectCall() + +Revision 1.25 2002/12/10 15:06:15 ghillie +- new methods get*Stream() for use in capisuitemodule + +Revision 1.24 2002/12/09 15:42:24 ghillie +- save debug and error stream in own attributes + +Revision 1.23 2002/12/06 15:25:47 ghillie +- new return value for getState(): WAITING + +Revision 1.22 2002/12/06 13:07:36 ghillie +- update docs because application is now responsible to delete + Connection object +- new methods getCause() and getState() + +Revision 1.21 2002/12/05 15:05:12 ghillie +- moved constructor for incoming calls to "private:" + +Revision 1.20 2002/12/02 12:31:36 ghillie +renamed Connection::SPEECH to Connection::VOICE + +Revision 1.19 2002/11/29 10:25:01 ghillie +- updated comments, use doxygen format now + +Revision 1.18 2002/11/27 16:03:20 ghillie +updated comments for doxygen + +Revision 1.17 2002/11/25 11:51:54 ghillie +- removed the unhandy CIP parameters from the interface to the application layer, use service type instead +- rejectWaiting() tests against cause!=0 now +- removed isUp() method + +Revision 1.16 2002/11/22 15:13:44 ghillie +- new attribute keepPhysicalConnection which prevents disconnect_b3_ind() from sending disconnect_req() +- moved the ugly B*configuration, B*protocol settings from some methods to private method buildBconfiguration +- new methods changeProtocol(), select_b_protocol_conf(), clearDTMF() +- disconnect_b3_ind sets ncci_state to N0 before calling the callbacks +- added parameter disconnect_mode to disconnectCall() +- getDTMF() does non-destructive read now + +Revision 1.15 2002/11/21 15:30:28 ghillie +- added new method Connection::acceptWaiting() - sends ALERT_REQ +- updated description of Connection::connectWaiting() + +Revision 1.14 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.13 2002/11/18 14:24:09 ghillie +- moved global severity_t to CapiError::severity_t +- added throw() declarations + +Revision 1.12 2002/11/18 12:24:33 ghillie +- changed disconnectCall() so that it doesn't throw exceptions any more, + so that we can call it in any state + +Revision 1.11 2002/11/17 14:40:55 ghillie +added isUp() + +Revision 1.10 2002/11/15 15:25:53 ghillie +added ALERT_REQ so we don't loose a call when we wait before connection establishment + +Revision 1.9 2002/11/15 13:49:10 ghillie +fix: callmodule wasn't aborted when call was only connected/disconnected physically + +Revision 1.8 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.7 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.6 2002/11/10 17:05:18 ghillie +changed to support multiple buffers -> deadlock in stop_file_transmission!! + +Revision 1.5 2002/11/08 07:57:07 ghillie +added functions to initiate a call +corrected FACILITY calls to use PLCI instead of NCCI in DTMF processing as told by Mr. Ortmann on comp.dcom.isdn.capi + +Revision 1.4 2002/10/31 12:40:06 ghillie +added DTMF support +small fixes like making some unnecessary global variables local, removed some unnecessary else cases + +Revision 1.3 2002/10/30 14:29:25 ghillie +added getCIPvalue + +Revision 1.2 2002/10/29 14:27:42 ghillie +added stop_file_*, added semaphores + +Revision 1.1 2002/10/25 13:29:38 ghillie +grouped files into subdirectories + +Revision 1.10 2002/10/10 12:45:40 gernot +added AudioReceive module, some small details changed + +Revision 1.9 2002/10/09 11:18:59 gernot +cosmetic changes (again...) and changed info function of CAPI class + +Revision 1.8 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.7 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.6 2002/10/01 09:02:04 gernot +changes for compilation with gcc3.2 + +Revision 1.5 2002/09/22 14:22:53 gernot +some cosmetic comment improvements ;-) + +Revision 1.4 2002/09/19 12:08:19 gernot +added magic CVS strings + +* Sun Sep 15 2002 - gernot@hillier.de +- put under CVS, cvs changelog follows above + +* Sun May 20 2002 - gernot@hillier.de +- first version + +*/ diff --git a/src/capisuite.conf.in b/src/capisuite.conf.in new file mode 100644 index 0000000..f454d6d --- /dev/null +++ b/src/capisuite.conf.in @@ -0,0 +1,70 @@ +# $Id: capisuite.conf.in,v 1.1 2003/02/19 08:19:52 gernot Exp $ +# +# This is the global configuration file for CapiSuite +# +# Some general options will be set here, which define which scripts +# to use, when to start them and what to log and where. +# +# All other options are defined in the python scripts given here +# or (preferably) are read from them from some other configuration +# files. +# +# As usual, lines starting with # or empty lines will be ignored +# +# The rest must be key value pairs written as key=value. +# +# Additional whitespaces and quotation marks (") surrounding +# the values will be ignored. + +# incoming_script +# +# This python script will be called when an incoming call occurs. +# +# It must define a python function named "callIncoming". +# +incoming_script="@pkglibdir@/incoming.py" + +# idle_script +# +# This python script will be called in regular intervals giving +# you the ability to search for needed actions (like send fax) +# in a queue (or somewhere else) and initiate the calls. +# +# It must define a python function named "idle". It's allowed +# to use the same script for incoming_script and idle_script +# +idle_script="@pkglibdir@/idle.py" + +# idle_script_interval +# +# The given idle_script will be called in regular intervals. +# +# The length of the intervals in seconds is given here. If you +# don't want to use an idle script, set it to "0" +# +idle_script_interval="60" + +# log_file +# +# The file given here is used for writing normal log messages to. +# +log_file="@localstatedir@/log/capisuite.log" + +# log_level +# +# Selects how chatty CapiSuite will be: +# +# 0 = only very few messages (e.g. system start/stop) +# 1 = some important messages (e.g. connection start/end) +# 2 = many debug messages (nearly every CAPI message logged) +# 3 = all debug messages (all CAPI messages - include every transmitted data package, every script execution) +log_level="1" + +# log_error +# +# The file given here is used for writing error messages to. +# +# Error messages are separated so you can easily find them. +# +log_error="@localstatedir@/log/capisuite.error" + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9741359 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,126 @@ +/** @file main.cpp + @brief Contains main(). + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "application/capisuite.h" +#include "application/applicationexception.h" +#include "backend/capiexception.h" + +/** @brief main application function + + This is the main() of CapiSuite. +*/ +int main(int argc, char** argv) +{ + CapiSuite cs(argc,argv); + cs.mainLoop(); +} + +/* History + +$Log: main.cpp,v $ +Revision 1.1 2003/02/19 08:19:52 gernot +Initial revision + +Revision 1.13 2003/02/05 15:59:38 ghillie +- moved error handling to capisuite.cpp, so it's logged + +Revision 1.12 2003/01/19 16:36:07 ghillie +- added catch blocks + +Revision 1.11 2003/01/07 14:48:44 ghillie +- added support for commandline parsing (only --help and --config yet) + +Revision 1.10 2003/01/05 12:26:19 ghillie +- renamed fro capisuite.cpp to main.cpp +- moved all code to the CapiSuite class (application/capisuite.*) + +Revision 1.9 2003/01/04 15:54:18 ghillie +- append log files, don't recreate them on every new start +- support for log_level +- added timestamp to log messages + +Revision 1.8 2002/12/18 14:35:39 ghillie +- added setListenFaxG3(). Oops ;) + +Revision 1.7 2002/12/16 13:10:41 ghillie +- added support for using log files + +Revision 1.6 2002/12/14 14:00:04 ghillie +- added code for configfile parsing + +Revision 1.5 2002/12/13 09:22:19 ghillie +- use config.h from automake/autoconf +- error classes support direct output now using operator<< +- began implementing support for config file + +Revision 1.4 2002/12/09 15:19:08 ghillie +- given debug and error streams to FlowControl and Capi objects + +Revision 1.3 2002/12/05 14:47:42 ghillie +- small changes in the constructor parameters for Capi and FlowControl, added call to capi->registerApplicationInterface() + +Revision 1.2 2002/12/02 12:12:56 ghillie +added script names to FlowControl contructor parameters + +Revision 1.1 2002/11/29 11:06:22 ghillie +renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) + +Revision 1.5 2002/11/29 10:20:44 ghillie +- updated docs, use doxygen format now + +Revision 1.4 2002/11/25 20:55:27 ghillie +-changed to set Listen on all controllers + +Revision 1.3 2002/11/20 17:14:03 ghillie +fixed small scope problem + +Revision 1.2 2002/11/19 15:57:18 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.1 2002/10/25 13:23:38 ghillie +renamed main.cpp + +Revision 1.8 2002/10/09 11:18:59 gernot +cosmetic changes (again...) and changed info function of CAPI class + +Revision 1.7 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.6 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.5 2002/09/22 20:06:36 gernot +deleted autocreated file + +Revision 1.4 2002/09/22 14:55:21 gernot +adding audio send module + +Revision 1.3 2002/09/22 14:22:53 gernot +some cosmetic comment improvements ;-) + +Revision 1.2 2002/09/19 12:08:19 gernot +added magic CVS strings + +* Sun Sep 15 2002 - gernot@hillier.de +- put under CVS, cvs log follows above + +* Sun May 20 2002 - gernot@hillier.de +- first version + +*/ diff --git a/src/modules/.cvsignore b/src/modules/.cvsignore new file mode 100644 index 0000000..e995588 --- /dev/null +++ b/src/modules/.cvsignore @@ -0,0 +1,3 @@ +.deps +Makefile +Makefile.in diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am new file mode 100644 index 0000000..b67329c --- /dev/null +++ b/src/modules/Makefile.am @@ -0,0 +1,7 @@ +noinst_LIBRARIES = libccmodules.a +libccmodules_a_SOURCES = audiosend.cpp audiosend.h callmodule.h callmodule.cpp\ + audioreceive.h audioreceive.cpp faxreceive.h faxreceive.cpp connectmodule.cpp\ + connectmodule.h switch2faxG3.cpp switch2faxG3.h readDTMF.cpp readDTMF.h \ + calloutgoing.cpp calloutgoing.h disconnectmodule.cpp disconnectmodule.h \ + faxsend.cpp faxsend.h + diff --git a/src/modules/audioreceive.cpp b/src/modules/audioreceive.cpp new file mode 100644 index 0000000..b1b663a --- /dev/null +++ b/src/modules/audioreceive.cpp @@ -0,0 +1,182 @@ +/* @file audioreceive.cpp + @brief Contains AudioReceive - Call Module for receiving audio. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#define conf_silence_limit 10 + +#include "../backend/connection.h" +#include "audioreceive.h" +#include +#include +#include + +/* Lookup table to reverse the bit order of a byte. ie MSB become LSB */ +/* taken from Sox 12.17.3 */ +unsigned char cswap[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, + 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, + 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, + 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, + 0x3C, 0xBC, 0x7C, 0xFC, 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, + 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, 0x4A, 0xCA, + 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, + 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, + 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, + 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, + 0x39, 0xB9, 0x79, 0xF9, 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, + 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, + 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, + 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, + 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, 0x47, 0xC7, + 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, + 0x3F, 0xBF, 0x7F, 0xFF +}; + + +AudioReceive::AudioReceive(Connection *conn, string file, int timeout, int silence_timeout, bool DTMF_exit) throw (CapiExternalError) + :CallModule(conn, timeout, DTMF_exit),silence_count(0),file(file),start_time(0),end_time(0), + silence_timeout(silence_timeout*8000) // ISDN audio sample rate = 8000Hz +{ + if (conn->getService()!=Connection::VOICE) + throw CapiExternalError("Connection not in speech mode","AudioReceive::AudioReceive()"); +} + +void +AudioReceive::mainLoop() throw (CapiWrongState, CapiExternalError) +{ + start_time=getTime(); + if (!(DTMF_exit && (!conn->getDTMF().empty()) ) ) { + conn->start_file_reception(file); + CallModule::mainLoop(); + conn->stop_file_reception(); + // truncate the silence away if it's more than one second + if (silence_timeout>8000 && silence_count > silence_timeout) { + struct stat filestat; + if (stat(file.c_str(),&filestat)==-1) + throw CapiExternalError("can't stat output file","AudioReceive::mainLoop"); + if (truncate(file.c_str(),filestat.st_size-silence_timeout+8000)==-1) + throw CapiExternalError("can't truncate output file","AudioReceive::mainLoop"); + } + } + end_time=getTime(); +} + +void +AudioReceive::dataIn(unsigned char* data, unsigned length) +{ + if (silence_timeout) { + unsigned int sum=0; + for (int i=0;idebugMessage("silence",3); + silence_count+=length; + if (silence_count > silence_timeout) + finish=true; + } else + silence_count=0; + } +} + +long +AudioReceive::duration() +{ + return end_time-start_time; +} + +/* History + +$Log: audioreceive.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.18 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.17 2003/01/16 13:02:30 ghillie +- truncate silence away if silence_timeout enabled +- improve duration() to only include real duration of recording + +Revision 1.16 2003/01/04 16:08:48 ghillie +- use new debugMessage method of Connection + +Revision 1.15 2002/12/16 15:06:14 ghillie +use right debug stream now + +Revision 1.14 2002/12/04 11:38:50 ghillie +- added time measurement: save time in start_time at the begin of mainLoop() and return difference to getTime() in duration() + +Revision 1.13 2002/12/02 12:32:21 ghillie +renamed Connection::SPEECH to Connection::VOICE + +Revision 1.12 2002/11/29 10:26:10 ghillie +- updated comments, use doxygen format now +- removed transmissionComplete() method as this makes no sense in receiving! + +Revision 1.11 2002/11/25 11:54:21 ghillie +- tests for speech mode before receiving now +- small performance improvement (use string::empty() instead of comparison to "") + +Revision 1.10 2002/11/22 15:16:20 ghillie +added support for finishing when DTMF is received + +Revision 1.9 2002/11/21 15:32:40 ghillie +- moved code from constructor/destructor to overwritten mainLoop() method + +Revision 1.8 2002/11/21 11:37:09 ghillie +make sure that we don't use Connection object after call was finished + +Revision 1.7 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.6 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.5 2002/11/13 15:24:25 ghillie +finished silence detection code + +Revision 1.4 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.3 2002/11/12 15:52:08 ghillie +added data in handler + +Revision 1.2 2002/10/29 14:28:22 ghillie +added stop_file_* calls to make sure transmission is cancelled when it's time... + +Revision 1.1 2002/10/25 13:29:39 ghillie +grouped files into subdirectories + +Revision 1.2 2002/10/24 09:55:52 ghillie +many fixes. Works for one call now + +Revision 1.1 2002/10/23 09:47:30 ghillie +added audioreceive module + +*/ diff --git a/src/modules/audioreceive.h b/src/modules/audioreceive.h new file mode 100644 index 0000000..76c6124 --- /dev/null +++ b/src/modules/audioreceive.h @@ -0,0 +1,158 @@ +/** @file audioreceive.h + @brief Contains AudioReceive - Call Module for receiving audio. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AUDIORECEIVE_H +#define AUDIORECEIVE_H + +#include +#include "callmodule.h" + +class Connection; + +using namespace std; + +/** @brief Call Module for receiving audio. + + This module handles the reception of an audio wave file. It can recognize silence in the signal and timeout after + a given period of silence, after a general timeout or after the reception of a DTMF signal. + + If DTMF abort is enabled, the module will abort immediately if the DTMF receiving buffer (see Connection::getDTMF) + isn't empty when it is created. That allows the user to abort subsequent audio receive and send commands with one + DTMF signal w/o needing to check for received DTMF after each command. + + The call must be in audio mode (by connecting with service VOICE), otherwise an exception will be caused. + + The created file will be saved in the format given by Capi, that is bit-reversed A-Law (or u-Law), 8 kHz mono. + + @author Gernot Hillier +*/ +class AudioReceive: public CallModule +{ + public: + /** @brief Constructor. Create an object and test for audio mode. + + The constructor also converts the given silence_timeout from seconds to number of samples (bytes). + + @param conn reference to Connection object + @param file name of file to save received audio stream to. + @param timeout timeout in seconds after which record is finished, 0=record forever (until call is finished) + @param silence_timeout duration of silence in seconds after which record is finished, 0=no silence detection + @param DTMF_exit true: abort if we receive DTMF during mainLoop() or if DTMF was received before + @throw CapiExternalError Thrown if connection is not in speech mode + */ + AudioReceive(Connection *conn, string file, int timeout, int silence_timeout, bool DTMF_exit) throw (CapiExternalError); + + /** @brief Start file reception, wait for one of the timeouts or disconnection and stop the reception. + + If the recording was finished because of silence, the silence is truncated away from the recorded file + + @throw CapiWrongState Thrown if disconnect is recognized + @throw CapiExternalError Thrown by Connection::start_file_reception(). + */ + void mainLoop() throw (CapiWrongState, CapiExternalError); + + /** @brief Test all received audio packets for silence and count silent packets + + All bytes of a received packages (i.e. 2048 bytes) are partly A-Law decoded, added and + compared to a threshhold. If silence is found, silence_count is increased, otherwise the + counter is reset to 0. + + If the silence_timeout value is reached, the mainLoop is signalled to finish. + */ + void dataIn(unsigned char* data, unsigned length); + + /** @brief Return the time in seconds since start of mainLoop() + + @return time in seconds since start of mainLoop() + */ + long duration(); + + private: + unsigned int silence_count; ///< counter how many consecutive samples (bytes) have been silent + unsigned int silence_timeout; ///< amount of silence samples after which record is finished + string file; ///< file name to save audio data to + long start_time, ///< time in seconds since the epoch when the recording was started + end_time; ///< time in seconds since the epoch when the recording was finished +}; + +#endif + +/* History + +$Log: audioreceive.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.14 2003/01/16 13:03:07 ghillie +- added attribute end_time +- updated comment to reflect new truncation of silence + +Revision 1.13 2002/12/04 11:38:50 ghillie +- added time measurement: save time in start_time at the begin of mainLoop() and return difference to getTime() in duration() + +Revision 1.12 2002/12/02 12:32:21 ghillie +renamed Connection::SPEECH to Connection::VOICE + +Revision 1.11 2002/11/29 10:26:10 ghillie +- updated comments, use doxygen format now +- removed transmissionComplete() method as this makes no sense in receiving! + +Revision 1.10 2002/11/25 11:54:21 ghillie +- tests for speech mode before receiving now +- small performance improvement (use string::empty() instead of comparison to "") + +Revision 1.9 2002/11/22 15:16:20 ghillie +added support for finishing when DTMF is received + +Revision 1.8 2002/11/21 15:32:40 ghillie +- moved code from constructor/destructor to overwritten mainLoop() method + +Revision 1.7 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.6 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.5 2002/11/13 15:24:25 ghillie +finished silence detection code + +Revision 1.4 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.3 2002/11/12 15:52:08 ghillie +added data in handler + +Revision 1.2 2002/10/29 14:28:22 ghillie +added stop_file_* calls to make sure transmission is cancelled when it's time... + +Revision 1.1 2002/10/25 13:29:39 ghillie +grouped files into subdirectories + +Revision 1.2 2002/10/24 09:55:52 ghillie +many fixes. Works for one call now + +Revision 1.1 2002/10/23 09:47:30 ghillie +added audioreceive module + +*/ diff --git a/src/modules/audiosend.cpp b/src/modules/audiosend.cpp new file mode 100644 index 0000000..3509c41 --- /dev/null +++ b/src/modules/audiosend.cpp @@ -0,0 +1,136 @@ +/* @file audiosend.cpp + @brief Contains AudioSend - Call Module for sending an A-Law file + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "../backend/connection.h" +#include "audiosend.h" + +AudioSend::AudioSend(Connection *conn, string file, bool DTMF_exit) throw (CapiExternalError) +:CallModule(conn,-1,DTMF_exit),file(file) +{ + if (conn->getService()!=Connection::VOICE) + throw CapiExternalError("Connection not in speech mode","AudioSend::AudioSend()"); +} + +void +AudioSend::mainLoop() throw (CapiWrongState, CapiExternalError, CapiMsgError) +{ + start_time=getTime(); + if (!(DTMF_exit && (!conn->getDTMF().empty()) ) ) { + conn->start_file_transmission(file); + CallModule::mainLoop(); + conn->stop_file_transmission(); + } +} + +void +AudioSend::transmissionComplete() +{ + finish=true; +} + +long +AudioSend::duration() +{ + return getTime()-start_time; +} + + +/* History + +$Log: audiosend.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.14 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.13 2002/12/04 11:38:50 ghillie +- added time measurement: save time in start_time at the begin of mainLoop() and return difference to getTime() in duration() + +Revision 1.12 2002/12/02 12:32:21 ghillie +renamed Connection::SPEECH to Connection::VOICE + +Revision 1.11 2002/11/29 10:27:44 ghillie +- updated comments, use doxygen format now + +Revision 1.10 2002/11/25 11:54:35 ghillie +- tests for speech mode before receiving now +- small performance improvement (use string::empty() instead of comparison to "") + +Revision 1.9 2002/11/22 15:16:20 ghillie +added support for finishing when DTMF is received + +Revision 1.8 2002/11/21 15:32:40 ghillie +- moved code from constructor/destructor to overwritten mainLoop() method + +Revision 1.7 2002/11/21 11:37:09 ghillie +make sure that we don't use Connection object after call was finished + +Revision 1.6 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.5 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.4 2002/11/13 15:26:28 ghillie +removed unnecessary member attribute filename + +Revision 1.3 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.2 2002/10/29 14:28:22 ghillie +added stop_file_* calls to make sure transmission is cancelled when it's time... + +Revision 1.1 2002/10/25 13:29:39 ghillie +grouped files into subdirectories + +Revision 1.8 2002/10/23 15:37:50 ghillie +typo... + +Revision 1.7 2002/10/23 14:10:27 ghillie +callmodules must register itself at connection class now + +Revision 1.6 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.5 2002/10/05 20:43:32 gernot +quick'n'dirty, but WORKS + +Revision 1.4 2002/10/05 13:53:00 gernot +changed to use thread class of CommonC++ instead of the threads-package +some cosmetic improvements (indentation...) + +Revision 1.3 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.2 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.1 2002/09/22 14:55:21 gernot +adding audio send module + +*/ diff --git a/src/modules/audiosend.h b/src/modules/audiosend.h new file mode 100644 index 0000000..379925b --- /dev/null +++ b/src/modules/audiosend.h @@ -0,0 +1,164 @@ +/** @file audiosend.h + @brief Contains AudioSend - Call Module for sending an A-Law file + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef AUDIOSEND_H +#define AUDIOSEND_H + +#include +#include "callmodule.h" + +class Connection; + +using namespace std; + +/** @brief Call Module for sending an A-Law file. + + This module handles the sending of an audio file. The audio file must be in bit-inversed A-Law format. It can be created for example + with sox using the suffix ".la". It supports abortion if DTMF signal is received. + + If DTMF abort is enabled, the module will abort immediately if the DTMF receiving buffer (see Connection::getDTMF) + isn't empty when it is created. That allows the user to abort subsequent audio receive and send commands with one + DTMF signal w/o needing to check for received DTMF after each command. + + The connction must be in audio mode (by connecting with service VOICE), otherwise an exception will be caused. + + @author Gernot Hillier +*/ +class AudioSend: public CallModule +{ + public: + /** @brief Constructor. Test if we are in speech mode and create an object. + + @param conn reference to Connection object + @param file name of file to send + @param DTMF_exit set to true, if you want to finish when DTMF signal is received + @throw CapiExternalError Thrown if speech mode isn't established before. + */ + AudioSend(Connection *conn, string file, bool DTMF_exit) throw (CapiExternalError); + + /** @brief Start file transmission, wait for the end of the file or the connection, stop file transmission + + @throw CapiWrongState Thrown when disconnection takes place. + @throw CapiExternalError Thrown by Connection::start_file_transmission, see there for explanation. + @throw CapiMsgError Thrown by Connection::start_file_transmission, see there for explanation. + */ + void mainLoop() throw (CapiWrongState, CapiExternalError, CapiMsgError); + + /** @brief finish main loop if file is completely received + + */ + void transmissionComplete(); + + /** @brief Return the time in seconds since start of mainLoop() + + @return time in seconds since start of mainLoop() + */ + long duration(); + + private: + string file; ///< name of the file to send + long start_time; ///< time in seconds since the epoch when the module was started +}; + +#endif + +/* History + +$Log: audiosend.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.13 2002/12/04 11:38:50 ghillie +- added time measurement: save time in start_time at the begin of mainLoop() and return difference to getTime() in duration() + +Revision 1.12 2002/12/02 12:32:21 ghillie +renamed Connection::SPEECH to Connection::VOICE + +Revision 1.11 2002/11/29 10:27:44 ghillie +- updated comments, use doxygen format now + +Revision 1.10 2002/11/25 21:00:53 ghillie +- improved documentation, now doxygen-readabl + +Revision 1.9 2002/11/25 11:54:35 ghillie +- tests for speech mode before receiving now +- small performance improvement (use string::empty() instead of comparison to "") + +Revision 1.8 2002/11/22 15:16:20 ghillie +added support for finishing when DTMF is received + +Revision 1.7 2002/11/21 15:32:40 ghillie +- moved code from constructor/destructor to overwritten mainLoop() method + +Revision 1.6 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.5 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.4 2002/11/13 15:26:28 ghillie +removed unnecessary member attribute filename + +Revision 1.3 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.2 2002/10/29 14:28:22 ghillie +added stop_file_* calls to make sure transmission is cancelled when it's time... + +Revision 1.1 2002/10/25 13:29:39 ghillie +grouped files into subdirectories + +Revision 1.10 2002/10/23 15:37:50 ghillie +typo... + +Revision 1.9 2002/10/23 14:10:27 ghillie +callmodules must register itself at connection class now + +Revision 1.8 2002/10/10 12:45:40 gernot +added AudioReceive module, some small details changed + +Revision 1.7 2002/10/09 14:36:22 gernot +added CallModule base class for all call handling modules + +Revision 1.6 2002/10/05 20:43:32 gernot +quick'n'dirty, but WORKS + +Revision 1.5 2002/10/04 15:48:03 gernot +structure changes completed & compiles now! + +Revision 1.4 2002/10/04 13:27:15 gernot +some restructuring to get it to a working state ;-) + +does not do anything useful yet nor does it even compile... + +Revision 1.3 2002/10/02 14:10:07 gernot +first version + +Revision 1.2 2002/10/01 09:02:04 gernot +changes for compilation with gcc3.2 + +Revision 1.1 2002/09/22 14:55:21 gernot +adding audio send module + +*/ diff --git a/src/modules/callmodule.cpp b/src/modules/callmodule.cpp new file mode 100644 index 0000000..395f623 --- /dev/null +++ b/src/modules/callmodule.cpp @@ -0,0 +1,160 @@ +/* @file callmodule.cpp + @brief Contains CallModule - Base class for all call handling modules + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include "../backend/connection.h" +#include "callmodule.h" + +CallModule::CallModule(Connection *connection, int timeout, bool DTMF_exit) +:finish(false),abort(false),timeout(timeout),conn(connection),DTMF_exit(DTMF_exit) +{ + if (conn) + conn->registerCallInterface(this); // Connection needs to know who we are... +} + +CallModule::~CallModule() +{ + if (conn) + conn->registerCallInterface(NULL); // tell Connection that we've finished... +} + +void +CallModule::callDisconnectedPhysical() +{ + abort=true; +} + +void +CallModule::callDisconnectedLogical() +{ + abort=true; +} + +void +CallModule::mainLoop() throw (CapiWrongState, CapiMsgError, CapiExternalError) +{ + if (! (DTMF_exit && (conn->getDTMF()!="") ) ) { + exit_time=getTime()+timeout; + timespec delay_time; + delay_time.tv_sec=0; delay_time.tv_nsec=100000000; // 100 msec + while(!finish && !abort && ( (timeout==-1) || (getTime() <= exit_time) ) ) + nanosleep(&delay_time,NULL); + } + if (abort) + throw CapiWrongState("call abort detected","CallModule::mainLoop()"); +} + +long +CallModule::getTime() +{ + struct timeval curr_time; + gettimeofday(&curr_time,NULL); + return curr_time.tv_sec; +} + +// will be overloaded by subclasses if necessary + +void +CallModule::transmissionComplete() +{ +} + +void +CallModule::callConnected() +{ +} + +void +CallModule::dataIn(unsigned char* data, unsigned length) +{ +} + +void +CallModule::gotDTMF() +{ + if (DTMF_exit) + finish=true; +} + +/* History + +$Log: callmodule.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.14 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.13 2002/12/09 15:43:10 ghillie +- changed WARNING to ERROR severity in exception + +Revision 1.12 2002/12/05 15:06:21 ghillie +- do registering and unregistering only if conn pointer is set (used for CallOutgoing) + +Revision 1.11 2002/11/29 10:27:44 ghillie +- updated comments, use doxygen format now + +Revision 1.10 2002/11/25 11:56:21 ghillie +- changed semantics of timeout parameter: -1 = infinite now, 0 = 0 seconds (i.e. abort immediately) + +Revision 1.9 2002/11/22 15:18:06 ghillie +- added support for DTMF_exit +- de-register Connection object uncondionally in destructor (checking for abort removed) + +Revision 1.8 2002/11/21 15:34:50 ghillie +- mainLoop() doesn't return any value any more, but throws CapiWrongState when connection is lost + +Revision 1.7 2002/11/21 11:37:09 ghillie +make sure that we don't use Connection object after call was finished + +Revision 1.6 2002/11/15 13:51:49 ghillie +fix: call module wasn't finished when call was only connected/disconnected physically + +Revision 1.5 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.4 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.3 2002/11/12 15:52:08 ghillie +added data in handler + +Revision 1.2 2002/10/31 12:40:57 ghillie +changed sleep to nanosleep to make module destruction more quick + +Revision 1.1 2002/10/25 13:29:39 ghillie +grouped files into subdirectories + +Revision 1.3 2002/10/23 14:25:29 ghillie +- a callmodule has to register itself at CallControl so it can be aborted if call is gone +- added distinction between "exited normally" and "aborted because call is gone" -> different results of mainLoop() + +Revision 1.2 2002/10/10 12:45:40 gernot +added AudioReceive module, some small details changed + +Revision 1.1 2002/10/09 14:34:22 gernot +added CallModule class as base class for all call handling modules + +*/ diff --git a/src/modules/callmodule.h b/src/modules/callmodule.h new file mode 100644 index 0000000..e2737fe --- /dev/null +++ b/src/modules/callmodule.h @@ -0,0 +1,180 @@ +/** @file callmodule.h + @brief Contains CallModule - Base class for all call handling modules + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CALLMODULE_H +#define CALLMODULE_H + +#include "../backend/callinterface.h" +#include "../backend/capiexception.h" + +class Connection; + +/** @brief Base class for all call handling modules + + This class implements the CallInterface. It is the base class for all special call handling modules like FaxReceive/FaxSend, AudioReceive/AudioSend, etc. + It contains basic code for registering with the according Connection object, realizing exits because of timeouts, received DTMF and call clearing from the other party. + + The general usage is: create a CallModule object, then run mainLoop(). mainLoop() will exit if necessary. + + To be able to recognize call clearing of the other party, the CapiWrongState exception is used. It is triggered by mainLoop(). + + Sub classes will mainly overwrite mainLoop() and the other signals they need for their tasks. + + If you don't change the semantics in the sub classes, the module will terminate when at least one of the following events occurs: + - logical connection is ended + - physical connection is ended + - DTMF code is received (depending on the value of DTMF_exit given in the constructor) + + @author Gernot Hillier +*/ +class CallModule: public CallInterface +{ + public: + /** @brief Constructor. Register this module at the according Connection object + + @param connection reference to Connection object + @param timeout timeout for this module in seconds (only considered in mainLoop!), -1=infinite (default) + @param DTMF_exit if this is set to true, then the current module is exited if we receive a DTMF tone + */ + CallModule(Connection* connection, int timeout=-1, bool DTMF_exit=false); + + /** @brief Destructor. Deregister this module at the according Connection object. + */ + ~CallModule(); + + /** @brief Waits in busy loop until the module is completed. + + Waits in a busy loop (sleeping 100 msecs between each iteration) until a DTMF signal is reached (if enabled) or the timeout is reached (if enabled). + + This method will likely be overwritten in each sub class. You can call CallModule::mainLoop() there to implement busy loops. + @throw CapiWrongState Something is tried in a wrong connection state. This usually means our call was finished (raised directly). + @throw CapiMsgError A CAPI function hasn't succeeded for some reason (not thrown by CallModule, but may be thrown in subclasses). + @throw CapiExternalError A given command didn't succeed for a reason not caused by the CAPI (not thrown by CallModule, but may be thrown in subclasses) + */ + virtual void mainLoop() throw (CapiWrongState, CapiMsgError, CapiExternalError); + + /** @brief empty here. + + empty function to overwrite if necessary + */ + virtual void transmissionComplete(void); + + /** @brief empty here. + + empty function to overwrite if necessary + */ + virtual void callConnected (void); + + /** @brief abort current modul if logical connection is lost. + + will abort currently running module. May be overwritten with complete new behaviour if needed. + */ + virtual void callDisconnectedLogical (void); + + /** @brief abort current module if physical connection is lost. + + will abort currently running module. Only overwrite if you know what you do! + */ + virtual void callDisconnectedPhysical (void); + + /** @brief finish current module if DTMF is received. + + finish current module if DTMF_exit was enabled + */ + virtual void gotDTMF (void); + + /** @brief empty here. + + empty function to overwrite if necessary + */ + virtual void dataIn (unsigned char* data, unsigned lentgh); + + protected: + /** @brief get the current time in # of seconds sinc 1/1/1970 + */ + virtual long getTime(); + + bool DTMF_exit; ///< if set to true, we will finish when we receive a DTMF signal + bool finish; ///< set this if the module should exit nicely for any reason + bool abort; ///< set this for hard exit because connection is lost, causes CapiWrongState to be throwed in mainLoop + Connection* conn; ///< reference to the according Connection object + long exit_time; ///< time when the timeout should occur + int timeout; ///< timeout period in seconds +}; + +#endif + +/* History + +$Log: callmodule.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.12 2002/12/06 13:08:30 ghillie +minor doc change + +Revision 1.11 2002/11/29 10:27:44 ghillie +- updated comments, use doxygen format now + +Revision 1.10 2002/11/25 21:00:53 ghillie +- improved documentation, now doxygen-readabl + +Revision 1.9 2002/11/25 11:56:21 ghillie +- changed semantics of timeout parameter: -1 = infinite now, 0 = 0 seconds (i.e. abort immediately) + +Revision 1.8 2002/11/22 15:18:06 ghillie +- added support for DTMF_exit +- de-register Connection object uncondionally in destructor (checking for abort removed) + +Revision 1.7 2002/11/21 15:34:50 ghillie +- mainLoop() doesn't return any value any more, but throws CapiWrongState when connection is lost + +Revision 1.6 2002/11/15 13:51:49 ghillie +fix: call module wasn't finished when call was only connected/disconnected physically + +Revision 1.5 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.4 2002/11/13 15:25:08 ghillie +fixed small typo + +Revision 1.3 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.2 2002/11/12 15:52:08 ghillie +added data in handler + +Revision 1.1 2002/10/25 13:29:39 ghillie +grouped files into subdirectories + +Revision 1.3 2002/10/23 14:25:29 ghillie +- a callmodule has to register itself at CallControl so it can be aborted if call is gone +- added distinction between "exited normally" and "aborted because call is gone" -> different results of mainLoop() + +Revision 1.2 2002/10/10 12:45:40 gernot +added AudioReceive module, some small details changed + +Revision 1.1 2002/10/09 14:34:22 gernot +added CallModule class as base class for all call handling modules + +*/ diff --git a/src/modules/calloutgoing.cpp b/src/modules/calloutgoing.cpp new file mode 100644 index 0000000..4ba8601 --- /dev/null +++ b/src/modules/calloutgoing.cpp @@ -0,0 +1,86 @@ +/* @file calloutgoing.cpp + @brief Contains CallOutgoingModule - Call Module for establishment of an outgoing connection and wait for successful connect + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "calloutgoing.h" + +CallOutgoing::CallOutgoing(Capi *capi, _cdword controller, string call_from, string call_to, Connection::service_t service, int timeout, string faxStationID, string faxHeadline, bool clir) +:CallModule(NULL,timeout,false),capi(capi),controller(controller),call_from(call_from),call_to(call_to),service(service),faxStationID(faxStationID),faxHeadline(faxHeadline),clir(clir) +{} + +void +CallOutgoing::mainLoop() throw (CapiExternalError, CapiMsgError) +{ + conn=new Connection(capi,controller,call_from,clir,call_to,service,faxStationID,faxHeadline); + conn->registerCallInterface(this); + + try { + CallModule::mainLoop(); + } + catch (CapiWrongState) {} // filter abort exception + + if (finish) // connection up + result=0; + else if (abort) { // error during connection setup + result=conn->getCause(); + if (!result) + result=2; // no reason available + } else { // timeout exceeded + result=1; + conn->disconnectCall(); + timespec delay_time; + delay_time.tv_sec=0; delay_time.tv_nsec=100000000; // 100 msec + while(conn->getState()!=Connection::DOWN) + nanosleep(&delay_time,NULL); + } +} + +void +CallOutgoing::callConnected() +{ + finish=true; +} + +Connection* +CallOutgoing::getConnection() +{ + return conn; +} + +int +CallOutgoing::getResult() +{ + return result; +} + +/* History + +$Log: calloutgoing.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.3 2002/12/06 13:10:34 ghillie +- corrected some wrong semantics, don't throw CapiWrongState any more +- added waiting for disconnection after timeout +- added result value for reason of abortion or success (getResult() added) +- always return Connection object, don't delete it in any case + +Revision 1.2 2002/12/05 15:55:54 ghillie +- small typo fixed + +Revision 1.1 2002/12/05 15:07:44 ghillie +- initial checking + +*/ diff --git a/src/modules/calloutgoing.h b/src/modules/calloutgoing.h new file mode 100644 index 0000000..fabb683 --- /dev/null +++ b/src/modules/calloutgoing.h @@ -0,0 +1,112 @@ +/** @file calloutgoing.h + @brief Contains CallOutgoing - Call Module for establishment of an outgoing connection and wait for successful connect + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CALLOUTGOINGMODULE_H +#define CALLOUTGOINGMODULE_H + +#include "callmodule.h" +#include "../backend/connection.h" + + +using namespace std; + +/** @brief Call Module for establishment of an outgoing connection and wait for successful connect + + This module serves to initiate an outgoing call and wait for the connection + establishment. The module can finish for three reasons: + - timeout for connecting exceeded + - error during call setup + - successful connect + + You can get the reason for exiting with getResult(). + + @author Gernot Hillier +*/ +class CallOutgoing: public CallModule +{ + public: + /** @brief Constructor. Create object. + + @param capi reference to object of Capi to use + @param controller controller number to use + @param call_from string containing the number to call + @param call_to string containing the own number to use + @param service service to call with as described in Connection::service_t + @param timeout timeout to wait for connection establishment + @param faxStationID fax station ID, only necessary when connecting in FAXG3 mode + @param faxHeadline fax headline, only necessary when connecting in FAXG3 mode + @param clir set to true to disable sending of own number + */ + CallOutgoing(Capi *capi, _cdword controller, string call_from, string call_to, Connection::service_t service, int timeout, string faxStationID, string faxHeadline, bool clir); + + /** @brief Initiate connection, wait for it to succeed + + This call module does never throw CapiWrongState! see getResult() if you need to know if conneciton succeeded. + + @throw CapiExternalError Thrown by Connection::Connection(Capi*,_cdword,string,bool,string,service_t,string,string) + @throw CapiMsgError Thrown by Connection::Connection(Capi*,_cdword,string,bool,string,service_t,string,string) + */ + void mainLoop() throw (CapiExternalError, CapiMsgError); + + /** @brief Finish if we got connection + + */ + void callConnected(); + + /** @brief return reference to the established connection + + @return reference to the established connection, NULL if timout exceeded w/o successful connection + */ + Connection* getConnection(); + + /** @brief return result of connection establishment + + 0: connection is up, call was successful + 1: call timeout exceeded + 2: connection not successful, no reason available + 0x3301-0x34FF: connection not successful, reason given in CAPI coding. See CAPI spec for details. + @return result, see above + */ + int getResult(); + + private: + Connection::service_t service; ///< service with which we should connect + string call_from, ///< CallingPartyNumber + call_to, ///< CalledPartyNumber + faxStationID, ///< fax Station ID to use + faxHeadline; ///< fax headlint to use + Capi *capi; ///< reference to object of Capi to use + _cdword controller; ///< controller to use + bool clir; ///< enable CLIR? (don't show own number to called party) + int result; ///< result of the call establishment process (0=success, 1=timeout exceeded, 2=aborted w/o reason, 0x3301-0x34FF=CAPI errors) +}; + +#endif + +/* History + +$Log: calloutgoing.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.2 2002/12/06 13:12:23 ghillie +- mainLoop() doesn't throw CapiWrongState any more +- added getResult() + +Revision 1.1 2002/12/05 15:07:44 ghillie +- initial checking + +*/ diff --git a/src/modules/connectmodule.cpp b/src/modules/connectmodule.cpp new file mode 100644 index 0000000..5269d71 --- /dev/null +++ b/src/modules/connectmodule.cpp @@ -0,0 +1,61 @@ +/* @file connectmodule.cpp + @brief Contains ConnectModule - Call Module for connection establishment at incoming connection + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "connectmodule.h" + +ConnectModule::ConnectModule(Connection *conn_in, Connection::service_t service, string faxStationID, string faxHeadline) +:CallModule(conn_in),service(service),faxStationID(faxStationID),faxHeadline(faxHeadline) +{} + +void +ConnectModule::mainLoop() throw (CapiWrongState, CapiExternalError, CapiMsgError) +{ + conn->connectWaiting(service,faxStationID,faxHeadline); + CallModule::mainLoop(); +} + +void +ConnectModule::callConnected() +{ + finish=true; +} + +/* History + +$Log: connectmodule.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.6 2002/11/29 10:27:44 ghillie +- updated comments, use doxygen format now + +Revision 1.5 2002/11/25 11:57:19 ghillie +- use service_type instead of CIP value in application layer + +Revision 1.4 2002/11/22 15:18:56 ghillie +added faxStationID, faxHeadline parameters + +Revision 1.3 2002/11/21 15:33:44 ghillie +- moved code from constructor/destructor to overwritten mainLoop() method + +Revision 1.2 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.1 2002/11/14 17:05:58 ghillie +initial checkin + +*/ diff --git a/src/modules/connectmodule.h b/src/modules/connectmodule.h new file mode 100644 index 0000000..c133f6b --- /dev/null +++ b/src/modules/connectmodule.h @@ -0,0 +1,94 @@ +/** @file connectmodule.h + @brief Contains ConnectModule - Call Module for connection establishment at incoming connection + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CONNECTMODULE_H +#define CONNECTMODULE_H + +#include "callmodule.h" +#include "../backend/connection.h" + + +using namespace std; + +/** @brief Call Module for connection establishment at incoming connection + + This module serves to accept an incoming call and wait for the connection + establishment. It is the first module you should call when an incoming + call is signalled and you want to accept it. + + @author Gernot Hillier +*/ +class ConnectModule: public CallModule +{ + public: + /** @brief Constructor. Create object. + + @param conn reference to Connection object + @param service service to connect with as described in Connection::service_t + @param faxStationID fax station ID, only necessary when connecting in FAXG3 mode + @param faxHeadline fax headline, only necessary when connecting in FAXG3 mode + */ + ConnectModule(Connection *conn, Connection::service_t service, string faxStationID, string faxHeadline); + + /** @brief Accept connection and wait for complete establishment + + @throw CapiWrongState Thrown by CallModule::mainLoop() + @throw CapiExternalError Thrown by Connection::connectWaiting() + @throw CapiMsgError Thrown by Connection::connectWaiting() + */ + void mainLoop() throw (CapiWrongState, CapiExternalError, CapiMsgError); + + /** @brief Finish mainLoop() if call is completely established + */ + void callConnected(); + + private: + Connection::service_t service; ///< service with which we should connect + string faxStationID, ///< fax Station ID to use + faxHeadline; ///< fax headlint to use +}; + +#endif + +/* History + +$Log: connectmodule.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.7 2002/11/29 10:27:44 ghillie +- updated comments, use doxygen format now + +Revision 1.6 2002/11/25 11:57:19 ghillie +- use service_type instead of CIP value in application layer + +Revision 1.5 2002/11/22 15:18:56 ghillie +added faxStationID, faxHeadline parameters + +Revision 1.4 2002/11/21 15:33:44 ghillie +- moved code from constructor/destructor to overwritten mainLoop() method + +Revision 1.3 2002/11/20 17:25:29 ghillie +added missing throw() declaration + +Revision 1.2 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.1 2002/11/14 17:05:58 ghillie +initial checkin + +*/ diff --git a/src/modules/disconnectmodule.cpp b/src/modules/disconnectmodule.cpp new file mode 100644 index 0000000..4c04739 --- /dev/null +++ b/src/modules/disconnectmodule.cpp @@ -0,0 +1,62 @@ +/** @file disconnectmodule.cpp + @brief Contains DisconnectModule - Call Module for call clearing + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "disconnectmodule.h" + +DisconnectModule::DisconnectModule(Connection *conn, int reject_reason, bool quick_disconnect) +:CallModule(conn),reject_reason(reject_reason),quick_disconnect(quick_disconnect) +{} + +void +DisconnectModule::mainLoop() throw (CapiMsgError,CapiExternalError) +{ + Connection::connection_state_t state=conn->getState(); + if (state!=Connection::DOWN) { + try { + if (state==Connection::WAITING) + conn->rejectWaiting(reject_reason); + else + conn->disconnectCall(quick_disconnect ? Connection::PHYSICAL_ONLY : Connection::ALL); + CallModule::mainLoop(); + } + catch (CapiWrongState) {} // shouldn't throw CapiWrongState as abort won't get true, but who knows... + } +} + +void DisconnectModule::callDisconnectedLogical() +{} + +void DisconnectModule::callDisconnectedPhysical() +{ + finish=true; +} + +/* History + +$Log: disconnectmodule.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.3 2002/12/11 13:40:22 ghillie +- added support for quick disconnect (immediate physical disconnect) + +Revision 1.2 2002/12/06 15:26:30 ghillie +- supports rejecting of call now, too + +Revision 1.1 2002/12/06 12:48:38 ghillie +inital checkin + +*/ diff --git a/src/modules/disconnectmodule.h b/src/modules/disconnectmodule.h new file mode 100644 index 0000000..c54d0f7 --- /dev/null +++ b/src/modules/disconnectmodule.h @@ -0,0 +1,86 @@ +/** @file disconnectmodule.h + @brief Contains DisconnectModule - Call Module for call clearing + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef DISCONNECTMODULE_H +#define DISCONNECTMODULE_H + +#include "callmodule.h" +#include "../backend/connection.h" + + +using namespace std; + +/** @brief Call Module for call clearing + + This module initiates disconnection or rejection of connection and + waits until the physical connection is cleared completely. It's + no problem to call it when the connection is already (partly or completely) + cleared. + + @author Gernot Hillier +*/ +class DisconnectModule: public CallModule +{ + public: + /** @brief Constructor. Create object. + + @param conn reference to Connection object + @param reject_reason reason to give for rejecting a waiting call (1=ignore call, default; + for other values see Connection::rejectWaiting()), ignored for normal disconnect + @param quick_disconnect disconnect physical immediately, this will lead to a protocol error in Layer 3. Use for error cases. + */ + DisconnectModule(Connection *conn, int reject_reason=1, bool quick_disconnect=false); + + /** @brief Initiate call clearing and wait for successful physical disconnection. + + @throw CapiMsgError Thrown by Connection::disconnectCall() or Connection::rejectWaiting() + @throw CapiExternalError Thrown by Connection::rejectWaiting(). + */ + void mainLoop() throw (CapiMsgError,CapiExternalError); + + /** @brief Do nothing as we're waiting for physical disconnection. + */ + void callDisconnectedLogical (); + + /** @brief Finish current module if physical connection is cleared. + + This is overwritten here because we don't trigger an exception at call clearing. + */ + void callDisconnectedPhysical (); + + private: + int reject_reason; ///< saving reject reason given in constructor + bool quick_disconnect; ///< disconnect physical immediately w/o disconnection logical before +}; + +#endif + +/* History + +$Log: disconnectmodule.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.3 2002/12/11 13:40:22 ghillie +- added support for quick disconnect (immediate physical disconnect) + +Revision 1.2 2002/12/06 15:26:30 ghillie +- supports rejecting of call now, too + +Revision 1.1 2002/12/06 12:48:38 ghillie +inital checkin + +*/ diff --git a/src/modules/faxreceive.cpp b/src/modules/faxreceive.cpp new file mode 100644 index 0000000..929d2ad --- /dev/null +++ b/src/modules/faxreceive.cpp @@ -0,0 +1,100 @@ +/* @file faxreceive.cpp + @brief Contains FaxReceive - Call Module for receiving an analog fax (group 3) + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "../backend/connection.h" +#include "faxreceive.h" + + +FaxReceive::FaxReceive(Connection *conn, string file) throw (CapiExternalError) +:CallModule(conn),file(file) +{ + if (conn->getService()!=Connection::FAXG3) + throw CapiExternalError("Connection not in fax mode","FaxReceive::FaxReceive()"); +} + + +void +FaxReceive::mainLoop() throw (CapiWrongState, CapiExternalError) +{ + conn->start_file_reception(file); + CallModule::mainLoop(); + conn->stop_file_reception(); +} + +void +FaxReceive::transmissionComplete() +{ + finish=true; +} + +/* History + +$Log: faxreceive.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.12 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.11 2002/12/09 15:43:45 ghillie +- small typo... + +Revision 1.10 2002/11/29 10:27:44 ghillie +- updated comments, use doxygen format now + +Revision 1.9 2002/11/25 11:58:04 ghillie +- test for fax mode before receiving now + +Revision 1.8 2002/11/21 15:32:40 ghillie +- moved code from constructor/destructor to overwritten mainLoop() method + +Revision 1.7 2002/11/21 11:37:09 ghillie +make sure that we don't use Connection object after call was finished + +Revision 1.6 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.5 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.4 2002/11/13 15:26:28 ghillie +removed unnecessary member attribute filename + +Revision 1.3 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.2 2002/10/29 14:28:22 ghillie +added stop_file_* calls to make sure transmission is cancelled when it's time... + +Revision 1.1 2002/10/25 13:29:39 ghillie +grouped files into subdirectories + +Revision 1.7 2002/10/23 14:34:26 ghillie +call modules must register itself at CallControl now + +Revision 1.6 2002/10/23 09:45:00 ghillie +changed to fit into new architecture + +*/ diff --git a/src/modules/faxreceive.h b/src/modules/faxreceive.h new file mode 100644 index 0000000..196ed5a --- /dev/null +++ b/src/modules/faxreceive.h @@ -0,0 +1,121 @@ +/** @file faxreceive.h + @brief Contains FaxReceive - Call Module for receiving an analog fax (group 3) + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef FAXRECEIVE_H +#define FAXRECEIVE_H + +#include +#include "callmodule.h" + +class Connection; + +using namespace std; + +/** @brief Call Module for receiving an analog fax (group 3). + + This module handles the reception of an analog fax (fax group 3). It starts the reception and waits for the end of the connection. + + Fax polling isn't supported yet. + + Fax mode must have been established before using this (by connecting in fax mode or switching to fax with Switch2FaxG3), + otherwise an exception is caused. + + The created file will be saved in the format received by Capi, i.e. as Structured Fax File (SFF). + + @author Gernot Hillier +*/ +class FaxReceive: public CallModule +{ + public: + /** @brief Constructor. Test if we are in fax mode and create an object. + + @param conn reference to Connection object + @param file name of file to save recorded stream to + @throw CapiExternalError Thrown if we are not in fax mode. + */ + FaxReceive(Connection *conn, string file) throw (CapiExternalError); + + /** @brief Start file reception, wait for disconnect and stop the reception afterwards + + @throw CapiWrongState Thrown when disconnection takes place. + @throw CapiExternalError Thrown by Connection::start_file_reception. See there for explanation. + */ + void mainLoop() throw (CapiWrongState, CapiExternalError); + + /** @brief finish main loop if file is completely received + */ + void transmissionComplete(); + + private: + string file; ///< file name to save file to +}; + +#endif + +/* History + +$Log: faxreceive.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.11 2002/12/13 11:47:40 ghillie +- added comment about fax polling + +Revision 1.10 2002/11/29 10:27:44 ghillie +- updated comments, use doxygen format now + +Revision 1.9 2002/11/25 21:00:53 ghillie +- improved documentation, now doxygen-readabl + +Revision 1.8 2002/11/25 11:58:04 ghillie +- test for fax mode before receiving now + +Revision 1.7 2002/11/21 15:32:40 ghillie +- moved code from constructor/destructor to overwritten mainLoop() method + +Revision 1.6 2002/11/19 15:57:19 ghillie +- Added missing throw() declarations +- phew. Added error handling. All exceptions are caught now. + +Revision 1.5 2002/11/14 17:05:19 ghillie +major structural changes - much is easier, nicer and better prepared for the future now: +- added DisconnectLogical handler to CallInterface +- DTMF handling moved from CallControl to Connection +- new call module ConnectModule for establishing connection +- python script reduced from 2 functions to one (callWaiting, callConnected + merged to callIncoming) +- call modules implement the CallInterface now, not CallControl any more + => this freed CallControl from nearly all communication stuff + +Revision 1.4 2002/11/13 15:26:28 ghillie +removed unnecessary member attribute filename + +Revision 1.3 2002/11/13 08:34:54 ghillie +moved history to the bottom + +Revision 1.2 2002/10/29 14:28:22 ghillie +added stop_file_* calls to make sure transmission is cancelled when it's time... + +Revision 1.1 2002/10/25 13:29:39 ghillie +grouped files into subdirectories + +Revision 1.7 2002/10/23 14:34:26 ghillie +call modules must register itself at CallControl now + +Revision 1.6 2002/10/23 09:46:08 ghillie +changed to fit into new architecture + +*/ diff --git a/src/modules/faxsend.cpp b/src/modules/faxsend.cpp new file mode 100644 index 0000000..cdc0eaa --- /dev/null +++ b/src/modules/faxsend.cpp @@ -0,0 +1,56 @@ +/* @file faxsend.cpp + @brief Contains FaxSend - Call Module for sending an analog fax (group 3) + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "../backend/connection.h" +#include "faxsend.h" + + +FaxSend::FaxSend(Connection *conn, string file) throw (CapiExternalError) +:CallModule(conn),file(file) +{ + if (conn->getService()!=Connection::FAXG3) + throw CapiExternalError("Connection not in fax mode","FaxSend::FaxSend()"); +} + + +void +FaxSend::mainLoop() throw (CapiWrongState, CapiExternalError, CapiMsgError) +{ + conn->start_file_transmission(file); + CallModule::mainLoop(); + conn->stop_file_transmission(); +} + +void +FaxSend::transmissionComplete() +{ + finish=true; +} + +/* History + +$Log: faxsend.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.2 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.1 2002/12/13 11:44:34 ghillie +added support for fax send + +*/ diff --git a/src/modules/faxsend.h b/src/modules/faxsend.h new file mode 100644 index 0000000..1867105 --- /dev/null +++ b/src/modules/faxsend.h @@ -0,0 +1,78 @@ +/** @file faxsend.h + @brief Contains FaxSend - Call Module for sending an analog fax (group 3) + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef FAXSEND_H +#define FAXSEND_H + +#include +#include "callmodule.h" + +class Connection; + +using namespace std; + +/** @brief Call Module for sending an analog fax (group 3). + + This module handles the send of an analog fax (fax group 3). It starts the send and waits for the end of the connection. + + Fax polling isn't supported yet. + + Fax mode must have been established before using this (by connecting in fax mode or switching to fax with Switch2FaxG3), + otherwise an exception is caused. + + The given file must be in the format used by Capi, i.e. Structured Fax File (SFF). + + @author Gernot Hillier +*/ +class FaxSend: public CallModule +{ + public: + /** @brief Constructor. Test if we are in fax mode and create an object. + + @param conn reference to Connection object + @param file name of file to send + @throw CapiExternalError Thrown if we are not in fax mode. + */ + FaxSend(Connection *conn, string file) throw (CapiExternalError); + + /** @brief Start file send, wait for disconnect and stop the send afterwards + + @throw CapiWrongState Thrown when disconnection takes place. + @throw CapiExternalError Thrown by Connection::start_file_transmission. See there for explanation. + @throw CapiMsgError Thrown by Connection::start_file_transmission. See there for explanation. + */ + void mainLoop() throw (CapiWrongState, CapiExternalError, CapiMsgError); + + /** @brief finish main loop if file is completely sent + */ + void transmissionComplete(); + + private: + string file; ///< file name to send +}; + +#endif + +/* History + +$Log: faxsend.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.1 2002/12/13 11:44:34 ghillie +added support for fax send + +*/ diff --git a/src/modules/readDTMF.cpp b/src/modules/readDTMF.cpp new file mode 100644 index 0000000..92ca13e --- /dev/null +++ b/src/modules/readDTMF.cpp @@ -0,0 +1,78 @@ +/* @file readDTMF.cpp + @brief Contains ReadDTMF - Call Module for waiting for DTMF signals + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "../backend/connection.h" +#include "readDTMF.h" + +ReadDTMF::ReadDTMF(Connection *conn, int timeout, int min_digits, int max_digits) + :CallModule(conn, timeout, false),min_digits(min_digits),max_digits(max_digits) +{ + if (conn->getState()!=Connection::UP) + throw CapiWrongState("Disconnection occured.","ReadDTMF::ReadDTMF()"); + digit_count=conn->getDTMF().size(); +} + +void +ReadDTMF::mainLoop() throw (CapiWrongState) +{ + if (!max_digits || (digit_count < max_digits)) { + do { + finish=false; + CallModule::mainLoop(); + } while (!abort && (digit_countgetDTMF().size(); + if (max_digits && (digit_count >= max_digits)) + finish=true; + else + exit_time=getTime()+timeout; +} + +/* History + +$Log: readDTMF.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.7 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.6 2002/12/06 13:13:06 ghillie +- use Connection::getState() insted of Connection::isUp() + +Revision 1.5 2002/12/05 15:56:24 ghillie +- added checks for connection state to throw exception if connection is down + +Revision 1.4 2002/12/02 21:03:46 ghillie +assured that while loop is entered even if digit_count>=min_digits + +Revision 1.3 2002/11/29 10:28:33 ghillie +- updated comments, use doxygen format now +- removed unnecessary attribute again + +Revision 1.2 2002/11/25 21:01:55 ghillie +- simplified timeout handling (end point is changed now, no new iteration of mainLoop for every DTMF signal) + +Revision 1.1 2002/11/25 11:42:07 ghillie +initial checkin + +*/ diff --git a/src/modules/readDTMF.h b/src/modules/readDTMF.h new file mode 100644 index 0000000..ad09ca8 --- /dev/null +++ b/src/modules/readDTMF.h @@ -0,0 +1,83 @@ +/** @file readDTMF.h + @brief Contains ReadDTMF - Call Module for waiting for DTMF signals + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef READDTMF_H +#define READDTMF_H + +#include "callmodule.h" + +class Connection; + +/** @brief Call Module for waiting for DTMF signals + + This module allows the user to specify how much DTMF digits he wants to read and how long to + wait for them. It doesn't do the actual read, just waits for the given conditions to be fulfilled. + + To use it, create an object and call mainLoop(). After mainLoop() finished, call Connection::getDTMF() to read + the received signals. + + @author Gernot Hillier + +*/ +class ReadDTMF: public CallModule +{ + public: + /** @brief Constructor. Create Object and read the current digit count from Connection. + + @param conn reference to Connection object + @param timeout timeout in seconds after which reading is terminated (only terminates when min_digits are reached!), restarts after each digit + @param min_digits minimum number of digits which must be read in ANY case without respect to timout. Only set to value >0 if you're sure the user will input a digit. + @param max_digits maximum number of digits to read, we abort immediately if this number is reached (0=infinite, only timeout counts) + */ + ReadDTMF(Connection *conn, int timeout, int min_digits, int max_digits); + + /** @brief mainLoop: Waits until the given conditions (see constructor) have been fulfilled + + The module will finish if one of these conditions are true: + + - max_digits is fulfilled + - timeout was reached AND min_digits is fulfilled + + @throw CapiWrongState Thrown if disconnection is recognized + */ + void mainLoop() throw (CapiWrongState); + + /** @brief finish if max_digits is reached, otherwise restart timeout when DTMF signal is received + */ + void gotDTMF(); + + private: + int digit_count, ///< save the current number of digits in receive buffer + min_digits, ///< save min_digits parameter + max_digits; ///< save max_digits parameter +}; + +#endif + +/* History + +$Log: readDTMF.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.2 2002/11/29 10:28:34 ghillie +- updated comments, use doxygen format now +- removed unnecessary attribute again + +Revision 1.1 2002/11/25 11:42:07 ghillie +initial checkin + +*/ diff --git a/src/modules/switch2faxG3.cpp b/src/modules/switch2faxG3.cpp new file mode 100644 index 0000000..cedf931 --- /dev/null +++ b/src/modules/switch2faxG3.cpp @@ -0,0 +1,81 @@ +/* @file switch2faxG3.cpp + @brief Contains Switch2FaxG3 - Call Module for switching to FAXG3 service from another one. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "../backend/connection.h" +#include "switch2faxG3.h" + +Switch2FaxG3::Switch2FaxG3(Connection *conn_in, string faxStationID, string faxHeadline) +:CallModule(conn_in), faxStationID(faxStationID), faxHeadline(faxHeadline) +{} + +void +Switch2FaxG3::mainLoop() throw (CapiWrongState, CapiExternalError, CapiMsgError) +{ + if (conn->getState()!=Connection::UP) + throw CapiWrongState("Disconnection detected","Switch2FaxG3::mainLoop()"); + conn->debugMessage("switching to fax protocol",1); + conn->disconnectCall(Connection::LOGICAL_ONLY); + CallModule::mainLoop(); // wait for DISCONNECT_B3_IND + finish=false; + conn->changeProtocol(Connection::FAXG3,faxStationID,faxHeadline); // change to FaxG3 + CallModule::mainLoop(); // wait for CONNECT_B3_IND + conn->debugMessage("connection re-established, switching to fax protocol finished",1); +} + +void +Switch2FaxG3::callDisconnectedLogical() +{ + finish=true; +} + +void +Switch2FaxG3::callConnected() +{ + finish=true; +} + +/* History + +$Log: switch2faxG3.cpp,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.8 2003/02/10 14:08:21 ghillie +- cosmetical log improvement + +Revision 1.7 2003/01/19 16:50:27 ghillie +- removed severity in exceptions. No FATAL-automatic-exit any more. + Removed many FATAL conditions, other ones are exiting now by themselves + +Revision 1.6 2002/12/18 14:46:23 ghillie +- removed done TODO + +Revision 1.5 2002/12/06 13:13:06 ghillie +- use Connection::getState() insted of Connection::isUp() + +Revision 1.4 2002/12/05 15:56:24 ghillie +- added checks for connection state to throw exception if connection is down + +Revision 1.3 2002/11/29 10:29:12 ghillie +- updated comments, use doxygen format now + +Revision 1.2 2002/11/25 11:58:53 ghillie +use service_type instead of hardcoded CIP values now + +Revision 1.1 2002/11/22 14:59:36 ghillie +initial checkin + +*/ diff --git a/src/modules/switch2faxG3.h b/src/modules/switch2faxG3.h new file mode 100644 index 0000000..dc5dfc9 --- /dev/null +++ b/src/modules/switch2faxG3.h @@ -0,0 +1,86 @@ +/** @file switch2faxG3.h + @brief Contains Switch2FaxG3 - Call Module for switching to FAXG3 service from another one. + + @author Gernot Hillier + $Revision: 1.1 $ +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef SWITCH2FAXG3_H +#define SWITCH2FAXG3_H + +#include "callmodule.h" + +class Connection; + +using namespace std; + +/** @brief Call Module for switching to FAXG3 service from another one. + + This module does all the necessary steps to switch from another service (mostly VOICE) + to FaxG3 service (see Connection::service_t). The steps are: + + - disconnect logical connection + - wait for completion of disconnect + - call Connection::changeProtocol() to switch to faxG3 + - wait for logical connection to re-establish +*/ +class Switch2FaxG3: public CallModule +{ + public: + /** @brief Constructor. Create object. + + @param conn reference to Connection object + @param faxStationID fax station ID to use + @param faxHeadline fax headline to use + */ + Switch2FaxG3(Connection *conn, string faxStationID, string faxHeadline); + + /** @brief Do all needed steps (disconnect logical, wait, switch to fax, wait). + + @throw CapiWrongState Thrown by CallModule::mainLoop, Connection::changeProtocol + @throw CapiExternalError Thrown by Connection::changeProtocol + @throw CapiMsgError Thrown by Connection::changeProtocol, Connection::disconnectCall + */ + void mainLoop() throw (CapiWrongState, CapiExternalError, CapiMsgError); + + /** @brief Finish first wait if the logical disconnection succeeded. + */ + void callDisconnectedLogical(); + + /** @brief Finish second wait if logical connection has been re-established + */ + void callConnected(); + + + private: + string faxStationID, ///< fax station ID to use + faxHeadline; ///< fax headline to use +}; + +#endif + +/* History + +$Log: switch2faxG3.h,v $ +Revision 1.1 2003/02/19 08:19:53 gernot +Initial revision + +Revision 1.3 2002/12/02 12:32:54 ghillie +renamed Connection::SPEECH to Connection::VOICE + +Revision 1.2 2002/11/29 10:29:12 ghillie +- updated comments, use doxygen format now + +Revision 1.1 2002/11/22 14:59:36 ghillie +initial checkin + +*/