[vdr-plugin-satip] 01/03: Imported Upstream version 1.0.2

Tobias Grimm tiber-guest at moszumanska.debian.org
Sat Feb 14 17:45:48 UTC 2015


This is an automated email from the git hooks/post-receive script.

tiber-guest pushed a commit to branch master
in repository vdr-plugin-satip.

commit bdcecb7be40fafe90df74f17d244bc5c84eea076
Author: etobi <git at e-tobi.net>
Date:   Sat Feb 14 18:22:56 2015 +0100

    Imported Upstream version 1.0.2
---
 COPYING         | 340 ++++++++++++++++++++++++++++++++
 HISTORY         | 110 +++++++++++
 Makefile        | 171 ++++++++++++++++
 README          | 155 +++++++++++++++
 common.c        | 150 +++++++++++++++
 common.h        | 116 +++++++++++
 config.c        |  78 ++++++++
 config.h        |  90 +++++++++
 device.c        | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++
 device.h        | 115 +++++++++++
 deviceif.h      |  25 +++
 discover.c      | 335 ++++++++++++++++++++++++++++++++
 discover.h      |  91 +++++++++
 discoverif.h    |  22 +++
 log.h           |  49 +++++
 msearch.c       | 104 ++++++++++
 msearch.h       |  40 ++++
 param.c         | 200 +++++++++++++++++++
 param.h         |  15 ++
 po/ca_ES.po     | 185 ++++++++++++++++++
 po/de_DE.po     | 185 ++++++++++++++++++
 po/es_ES.po     | 185 ++++++++++++++++++
 po/fi_FI.po     | 184 ++++++++++++++++++
 poller.c        | 122 ++++++++++++
 poller.h        |  44 +++++
 pollerif.h      |  24 +++
 rtcp.c          | 100 ++++++++++
 rtcp.h          |  36 ++++
 rtp.c           | 151 +++++++++++++++
 rtp.h           |  42 ++++
 rtsp.c          | 335 ++++++++++++++++++++++++++++++++
 rtsp.h          |  58 ++++++
 satip.c         | 466 ++++++++++++++++++++++++++++++++++++++++++++
 sectionfilter.c | 424 ++++++++++++++++++++++++++++++++++++++++
 sectionfilter.h |  89 +++++++++
 server.c        | 227 ++++++++++++++++++++++
 server.h        |  90 +++++++++
 setup.c         | 561 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 setup.h         |  49 +++++
 socket.c        | 195 +++++++++++++++++++
 socket.h        |  34 ++++
 statistics.c    | 222 +++++++++++++++++++++
 statistics.h    |  87 +++++++++
 tuner.c         | 589 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tuner.h         | 161 ++++++++++++++++
 tunerif.h       |  26 +++
 46 files changed, 7604 insertions(+)

diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f90922e
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..0a1a70b
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,110 @@
+===================================
+VDR Plugin 'satip' Revision History
+===================================
+
+2014-03-08: Version 0.0.1
+
+- Initial revision.
+- Added German translation (Thanks to Frank Neumann).
+
+2014-03-15: Version 0.1.0
+
+- Switched to the standard S/T/C source identifiers.
+- Added a new operation mode setup parameter.
+- Added new SVDRP commands.
+
+2014-03-16: Version 0.1.1
+
+- Changed code to utilize a proper XML library.
+- Refactored the session code.
+- Fixed EIT scan functionality.
+- Updated for vdr-2.1.6.
+
+2014-03-28: Version 0.2.0
+
+- Added support for cDevice::Ready().
+- Fixed pid leaking while disabling section filters.
+- Fixed keepalive heartbeat.
+
+2014-04-01: Version 0.2.1
+
+- Changed implementation to report about RTP packet
+  errors on 5 minutes interval only.
+- Added a check to write new sections only if there
+  is no data in the read socket.
+- Fixed keepalive heartbeat again.
+
+2014-04-05: Version 0.2.2
+
+- Fixed the default keepalive interval.
+- Refactored the section filtering.
+- Added Catalan translation (Thanks to Gabriel Bonich).
+
+2014-04-12: Version 0.2.3
+
+- Added Spanish translation (Thanks to Gabriel Bonich).
+- Fixed parameters of the OPTIONS command.
+- Added a device identication into the user agent string.
+- Removed unnecessary PLAY commands and header callbacks.
+
+2014-04-20: Version 0.3.0
+
+- Tweaked the pid update mechanism.
+
+2014-04-27: Version 0.3.1
+
+- Fixed the device discovery.
+
+2014-05-10: Version 0.3.2
+
+- Fixed model detection and OctopusNet DVB-C model quirks.
+- Added a session id quirk for GSSBOX.
+
+2014-05-18: Version 0.3.3
+
+- Added a validity check for the session member.
+- Added a session id quirk for Triax TSS 400.
+
+2014-12-24: Version 1.0.0
+
+- Fixed the cable only device detection.
+- Added support for blacklisted sources.
+- Fixed server reuse for active transponders.
+- Added a preliminary support for Fritz!WLAN
+  Repeater DVB-C (Thanks to Christian Wick).
+- Added a preliminary support for Telestar
+  Digibit R1 (Thanks to Dirk Wagner).
+- Added a new device status menu.
+- Added support for SAT>IP frontend selection via
+  Radio ID.
+- Added command-line switches for manually defining
+  used SAT>IP servers and setting used tracing mode.
+- Added new STAT and TRAC commands into the SVDRP
+  interface.
+- Refactored the tuner implementation.
+- Updated against SAT>IP protocol specification
+  version 1.2.1.
+- Refactored input thread to increase performance.
+- Added plenty of performance tweaks (Thanks to
+  Stefan Schallenberg).
+- Fixed EIT scan (Thanks to Stefan Schallenberg).
+
+2015-01-10: Version 1.0.1
+
+- Updated the command-line help and README.
+- Fixed the server teardown.
+- Removed the unnecessary config directory definition.
+- Added a fallback for older glibc libraries.
+- Improved pid selection performance.
+- Added support for Digital Devices CI extension.
+
+2015-01-18: Version 1.0.2
+
+- Added configurable CI slots.
+- Fixed parsing of the setup values.
+- Added an option to disable sources via sources.conf.
+- Added a command-line option to disable all the
+  SAT>IP server quirks.
+- Updated Spanish and Catalan translations (Thanks to
+  Gabriel Bonich).
+- Updated German translations (Thanks to Frank Neumann).
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2ff1695
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,171 @@
+#
+# Makefile for SAT>IP plugin
+#
+
+# Debugging on/off
+
+#SATIP_DEBUG = 1
+
+# Use TinyXML instead of PugiXML
+
+#SATIP_USE_TINYXML = 1
+
+# Strip debug symbols?  Set eg. to /bin/true if not
+
+STRIP = strip
+
+# The official name of this plugin.
+# This name will be used in the '-P...' option of VDR to load the plugin.
+# By default the main source file also carries this name.
+
+PLUGIN = satip
+
+### The version number of this plugin (taken from the main source file):
+
+VERSION = $(shell grep 'const char VERSION\[\] *=' $(PLUGIN).c | awk '{ print $$5 }' | sed -e 's/[";]//g')
+GITTAG  = $(shell git describe --always 2>/dev/null)
+
+### The directory environment:
+
+# Use package data if installed...otherwise assume we're under the VDR source directory:
+PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell PKG_CONFIG_PATH="$$PKG_CONFIG_PATH:../../.." pkg-config --variable=$(1) vdr))
+LIBDIR = $(call PKGCFG,libdir)
+LOCDIR = $(call PKGCFG,locdir)
+PLGCFG = $(call PKGCFG,plgcfg)
+CFGDIR = $(call PKGCFG,configdir)
+#
+TMPDIR ?= /tmp
+
+### The compiler options:
+
+export CFLAGS   = $(call PKGCFG,cflags)
+export CXXFLAGS = $(call PKGCFG,cxxflags)
+
+### The version number of VDR's plugin API:
+
+APIVERSION = $(call PKGCFG,apiversion)
+
+### Allow user defined options to overwrite defaults:
+
+-include $(PLGCFG)
+
+### The name of the distribution archive:
+
+ARCHIVE = $(PLUGIN)-$(VERSION)
+PACKAGE = vdr-$(ARCHIVE)
+
+### The name of the shared object file:
+
+SOFILE = libvdr-$(PLUGIN).so
+
+### Libraries
+
+LIBS = $(shell curl-config --libs)
+
+### Includes and Defines (add further entries here):
+
+INCLUDES +=
+
+DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"'
+
+ifdef SATIP_USE_TINYXML
+DEFINES += -DUSE_TINYXML
+LIBS += -ltinyxml
+else
+LIBS += -lpugixml
+endif
+
+ifdef SATIP_DEBUG
+ifeq ($(SATIP_DEBUG),1)
+DEFINES += -DDEBUG
+endif
+endif
+
+ifneq ($(strip $(GITTAG)),)
+DEFINES += -DGITVERSION='"-GIT-$(GITTAG)"'
+endif
+
+.PHONY: all all-redirect
+all-redirect: all
+
+### The object files (add further files here):
+
+OBJS = $(PLUGIN).o common.o config.o device.o discover.o msearch.o param.o \
+	poller.o rtp.o rtcp.o rtsp.o sectionfilter.o server.o setup.o socket.o \
+	statistics.o tuner.o
+
+### The main target:
+
+all: $(SOFILE) i18n
+
+### Implicit rules:
+
+%.o: %.c
+	$(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $<
+
+### Dependencies:
+
+MAKEDEP = $(CXX) -MM -MG
+DEPFILE = .dependencies
+$(DEPFILE): Makefile
+	@$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@
+
+-include $(DEPFILE)
+
+### Internationalization (I18N):
+
+PODIR     = po
+I18Npo    = $(wildcard $(PODIR)/*.po)
+I18Nmo    = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file))))
+I18Nmsgs  = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file))))))
+I18Npot   = $(PODIR)/$(PLUGIN).pot
+
+%.mo: %.po
+	msgfmt -c -o $@ $<
+
+$(I18Npot): $(wildcard *.c)
+	xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='<see README>' -o $@ `ls $^`
+
+%.po: $(I18Npot)
+	msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $<
+	@touch $@
+
+$(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo
+	install -D -m644 $< $@
+
+.PHONY: i18n
+i18n: $(I18Nmo) $(I18Npot)
+
+install-i18n: $(I18Nmsgs)
+
+### Targets:
+
+$(SOFILE): $(OBJS)
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@
+ifndef SATIP_DEBUG
+	@$(STRIP) $@
+endif
+
+install-lib: $(SOFILE)
+	install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION)
+
+install-conf:
+	@mkdir -p $(DESTDIR)$(CFGDIR)/plugins/$(PLUGIN)
+
+install: install-lib install-i18n install-conf
+
+dist: $(I18Npo) clean
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@mkdir $(TMPDIR)/$(ARCHIVE)
+	@cp -a * $(TMPDIR)/$(ARCHIVE)
+	@tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE)
+	@-rm -rf $(TMPDIR)/$(ARCHIVE)
+	@echo Distribution package created as $(PACKAGE).tgz
+
+clean:
+	@-rm -f $(PODIR)/*.mo $(PODIR)/*.pot
+	@-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~
+
+.PHONY: cppcheck
+cppcheck:
+	@cppcheck --language=c++ --enable=all -v -f $(OBJS:%.o=%.c)
diff --git a/README b/README
new file mode 100644
index 0000000..07139d9
--- /dev/null
+++ b/README
@@ -0,0 +1,155 @@
+This is an SAT>IP plugin for the Video Disk Recorder (VDR).
+
+Written by:                  Rolf Ahrenberg
+                             < R o l f . A h r e n b e r g @ s c i . f i >
+
+Project's homepage:          http://www.saunalahti.fi/~rahrenbe/vdr/satip/
+
+Latest version available at: http://www.saunalahti.fi/~rahrenbe/vdr/satip/
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 as
+published by the Free Software Foundation.
+See the file COPYING for more information.
+
+Requirements:
+
+- Libcurl >= 7.36.0 - the multiprotocol file transfer library with RTSP support
+  http://curl.haxx.se/libcurl/
+
+- PugiXML - Light-weight, simple and fast XML parser for C++
+  http://pugixml.org/
+  or
+  TinyXML - a simple, small, C++ XML parser
+  http://www.grinninglizard.com/tinyxml/
+
+- Glibc >= 2.12 - the GNU C library (recvmmsg)
+  http://www.gnu.org/software/libc/
+
+- VDR >= 2.1.4 for scrambled channels
+
+- VDR >= 2.1.7 for external CI
+
+Description:
+
+This plugin integrates SAT>IP network devices seamlessly into VDR.
+You can use any SAT>IP channel like any other normal DVB channel for
+live viewing, recording, etc. The plugin also features full section
+filtering capabilities which allow for example EIT information to be
+extracted from the incoming stream.
+
+Installation:
+
+tar -xzf /put/your/path/here/vdr-satip-X.Y.Z.tgz
+make -C satip-X.Y.Z install
+
+Configuration:
+
+The plugin accepts a "--devices" (-d) command-line parameter defaulting
+to one. This parameter defines how many simultaneous transponders can
+be received, if there are available SAT>IP tuners.
+
+The plugin accepts also a "--server" (-s) command-line parameter, that
+can be used to manually configure static SAT>IP servers if autodetection
+via UPnP somehow can't be used. The parameter string is a semicolon
+separated list of "<ipaddress>|<model>|<description>" entries. The model
+consists of a DVB system (DVBS2,DVBT2,DVBT,DVBC) and number of available
+frontends separated by a hyphen:
+
+vdr -P 'satip -s <ipaddress>|<model>|<description>;...'
+vdr -P 'satip -s 192.168.0.1|DVBS2-2,DVBT2-2|Octo1'
+vdr -P 'satip -s 192.168.0.1|DVBS2-4|Octo1;192.168.0.2|DVBT2-4|Octo2'
+
+SAT>IP satellite positions (aka. signal sources) shall be defined via
+sources.conf. If the source description begins with a number, it's used
+as SAT>IP signal source selection parameter. A special number zero can
+be used to disable the source. Otherwise, the default parameter is one:
+
+S19.2E  Astra 1KR/1L/1M/2C
+=> Signal source = 1
+
+S19.2E  2
+=> Signal source = 2
+
+S19.2E  3 Astra 1KR/1L/1M/2C
+=> Signal source = 3
+
+S19.2E 0 Astra 1KR/1L/1M/2C
+=> Source is disabled
+
+A channel can be assigned into a specific SAT>IP frontend by giving the
+identifier number in RID field of a channels.conf entry:
+FE = RID % 100
+Valid range: 1 ... 99
+
+Setup menu:
+
+- Operating mode = off       If you want exclude all SAT>IP devices
+                   low       from VDR's device handling, set this
+                   normal    option to "off". Otherwise, if you want
+                   high      to keep SAT>IP at a low priority when
+                             selecting available devices, set this
+                             option to "low". Similarly, the "high"
+                             value prefers the SAT>IP over the local
+                             DVB cards when selecting available devices.
+- Use CI extension = no      If you want to use the CI extension found
+                             in some SAT>IP hardware (e.g. Digital
+                             Devices OctopusNet), set this option to
+                             "yes".
+- CICAM #<slot> = <system>   If you want to assign a CA system into
+                             a specific CI slot, set this option to
+                             a named one. Use "---" for autoselection.
+- Enable EPG scanning = yes  If you want exclude all SAT>IP devices
+                             from VDR's EIT background scanning, set
+                             this option to "no".
+- Disabled sources = none    If your SAT>IP servers don't have certain
+                             satellite positions available you can
+                             disable them via this option.
+- Disabled filters = none    Certain section filters might cause some
+                             unwanted behaviour to VDR such as time
+                             being falsely synchronized etc. This option
+                             allows creation of blacklists of ill-behaving
+                             filters. If this option is set to a non-zero
+                             value, the menu page will contain that many
+                             "Disable filter" options which allow you
+                             to disable the individual section filters.
+                             Valid range: "none" = 0 ... 7
+- [Red:Scan]                 Forces network scanning of SAT>IP hardware.
+- [Yellow:Devices]           Opens SAT>IP device status menu.
+- [Blue:Info]                Opens SAT>IP information/statistics menu.
+- [Ok]                       Opens information menu of selected SAT>IP
+                             device.
+
+Information menu:
+
+- [Red:General]              Opens the general information page.
+- [Green:Pids]               Opens the pid statistics page.
+- [Yellow:Filters]           Opens the section filter statistics page.
+- [Blue:Bits/bytes]          Toggles between bits and bytes mode.
+
+Notes:
+
+- The stream id "-1" states about unsuccessful tuning. This might be a
+  result of invalid channel parameters or lack of free SAT>IP tuners.
+
+- If the plugin doesn't detect your SAT>IP network device, make sure
+  your setup doesn't have firewalled the UDP port 1900.
+
+- Stream decryption requires a separate CAM plugin that works without
+  direct access to any DVB card devices. The integrated CAM slot in
+  Octopus Net devices isn't supported.
+
+- Tracing can be set on/off dynamically via command-line switch or
+  SVDRP command.
+
+- OctopusNet firmware 1.0.40 or greater recommended.
+
+- Inverto OEM firmware 1.17.0.120 or greater recommended.
+  The firmware 1.16.0.120 can be downloaded and installed
+  from their webpage: http://www.inverto.tv/support/
+  An update to a newer firmware should be offered afterwards.
+
+Acknowledgements:
+
+- Big thanks to Digital Devices GmbH for providing the Octopus Net
+  hardware for development!
diff --git a/common.c b/common.c
new file mode 100644
index 0000000..02892ca
--- /dev/null
+++ b/common.c
@@ -0,0 +1,150 @@
+/*
+ * common.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <ctype.h>
+#include <vdr/tools.h>
+#include "common.h"
+
+uint16_t ts_pid(const uint8_t *bufP)
+{
+  return (uint16_t)(((bufP[1] & 0x1f) << 8) + bufP[2]);
+}
+
+uint8_t payload(const uint8_t *bufP)
+{
+  if (!(bufP[3] & 0x10)) // no payload?
+     return 0;
+
+  if (bufP[3] & 0x20) {  // adaptation field?
+     if (bufP[4] > 183)  // corrupted data?
+        return 0;
+     else
+        return (uint8_t)((184 - 1) - bufP[4]);
+     }
+
+  return 184;
+}
+
+const char *id_pid(const u_short pidP)
+{
+  for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) {
+      if (pidP == section_filter_table[i].pid)
+         return section_filter_table[i].tag;
+      }
+  return "---";
+}
+
+char *StripTags(char *strP)
+{
+  if (strP) {
+     char *c = strP, *r = strP, t = 0;
+     while (*strP) {
+           if (*strP == '<')
+              ++t;
+           else if (*strP == '>')
+              --t;
+           else if (t < 1)
+              *(c++) = *strP;
+           ++strP;
+           }
+     *c = 0;
+     return r;
+     }
+  return NULL;
+}
+
+char *SkipZeroes(const char *strP)
+{
+  if ((uchar)*strP != '0')
+     return (char *)strP;
+  while (*strP && (uchar)*strP == '0')
+        strP++;
+  return (char *)strP;
+}
+
+cString ChangeCase(const cString &strP, bool upperP)
+{
+  cString res(strP);
+  char *p = (char *)*res;
+  while (p && *p) {
+        *p = upperP ? toupper(*p) : tolower(*p);
+        ++p;
+        }
+  return res;
+}
+
+const section_filter_table_type section_filter_table[SECTION_FILTER_TABLE_SIZE] =
+{
+  // description                        tag    pid   tid   mask
+  {trNOOP("PAT (0x00)"),                "PAT", 0x00, 0x00, 0xFF},
+  {trNOOP("NIT (0x40)"),                "NIT", 0x10, 0x40, 0xFF},
+  {trNOOP("SDT (0x42)"),                "SDT", 0x11, 0x42, 0xFF},
+  {trNOOP("EIT (0x4E/0x4F/0x5X/0x6X)"), "EIT", 0x12, 0x40, 0xC0},
+  {trNOOP("TDT (0x70)"),                "TDT", 0x14, 0x70, 0xFF},
+};
+
+const ca_systems_table_type ca_systems_table[CA_SYSTEMS_TABLE_SIZE] =
+{
+  // http://www.dvb.org/index.php?id=174
+  // http://en.wikipedia.org/wiki/Conditional_access_system
+  // start end     description
+  {0x0000, 0x0000, "---"                       }, // 0
+  {0x0100, 0x01FF, "SECA Mediaguard (100..1FF)"}, // 1
+  {0x0464, 0x0464, "EuroDec (464)"             }, // 2
+  {0x0500, 0x05FF, "Viaccess (500..5FF)"       }, // 3
+  {0x0600, 0x06FF, "Irdeto (600..6FF)"         }, // 4
+  {0x0700, 0x07FF, "DigiCipher 2 (700..7FF)"   }, // 5
+  {0x0900, 0x09FF, "NDS Videoguard (900..9FF)" }, // 6
+  {0x0B00, 0x0BFF, "Conax (B00..BFF)"          }, // 7
+  {0x0D00, 0x0DFF, "CryptoWorks (D00..DFF)"    }, // 8
+  {0x0E00, 0x0EFF, "PowerVu (E00..EFF)"        }, // 9
+  {0x1000, 0x10FF, "RAS (1000..10FF)"          }, // 10
+  {0x1200, 0x12FF, "NagraVision (1200..12FF)"  }, // 11
+  {0x1700, 0x17FF, "VCAS (1700..17FF)"         }, // 12
+  {0x1800, 0x18FF, "NagraVision (1800..18FF)"  }, // 13
+  {0x22F0, 0x22F0, "Codicrypt (22F0)"          }, // 14
+  {0x2600, 0x2600, "BISS (2600)"               }, // 15
+  {0x2719, 0x2719, "VanyaCas (2719)"           }, // 16
+  {0x4347, 0x4347, "CryptOn (4347)"            }, // 17
+  {0x4800, 0x4800, "Accessgate (4800)"         }, // 18
+  {0x4900, 0x4900, "China Crypt (4900)"        }, // 19
+  {0x4A02, 0x4A02, "Tongfang (4A02)"           }, // 20
+  {0x4A10, 0x4A10, "EasyCas (4A10)"            }, // 21
+  {0x4A20, 0x4A20, "AlphaCrypt (4A20)"         }, // 22
+  {0x4A60, 0x4A60, "SkyCrypt (4A60)"           }, // 23
+  {0x4A61, 0x4A61, "Neotioncrypt (4A61)"       }, // 24
+  {0x4A62, 0x4A62, "SkyCrypt (4A62)"           }, // 25
+  {0x4A63, 0x4A63, "Neotion SHL (4A63)"        }, // 26
+  {0x4A64, 0x4A6F, "SkyCrypt (4A64)"           }, // 27
+  {0x4A70, 0x4A70, "DreamCrypt (4A70)"         }, // 28
+  {0x4A80, 0x4A80, "ThalesCrypt (4A80)"        }, // 29
+  {0x4AA1, 0x4AA1, "KeyFly (4AA1)"             }, // 30
+  {0x4ABF, 0x4ABF, "CTI-CAS (4ABF)"            }, // 31
+  {0x4AC1, 0x4AC1, "Latens (4AC1)"             }, // 32
+  {0x4AD0, 0x4AD1, "X-Crypt (4AD0)"            }, // 33
+  {0x4AD4, 0x4AD4, "OmniCrypt (4AD4)"          }, // 34
+  {0x4AE0, 0x4AE1, "Z-Crypt (4AE0)"            }, // 35
+  {0x4AE4, 0x4AE4, "CoreCrypt (4AE4)"          }, // 36
+  {0x4AE5, 0x4AE5, "PRO-Crypt (4AE5)"          }, // 37
+  {0x4AEA, 0x4AEA, "Cryptoguard (4AEA)"        }, // 38
+  {0x4AEB, 0x4AEB, "Abel Quintic (4AEB)"       }, // 39
+  {0x4AF0, 0x4AF0, "ABV (4AF0)"                }, // 40
+  {0x5500, 0x5500, "Z-Crypt (5500)"            }, // 41
+  {0x5501, 0x5501, "Griffin (5501)"            }, // 42
+  {0x5581, 0x5581, "Bulcrypt (5581)"           }, // 43
+  {0x7BE1, 0x7BE1, "DRE-Crypt (7BE1)"          }, // 44
+  {0xA101, 0xA101, "RosCrypt-M (A101)"         }, // 45
+  {0xEAD0, 0xEAD0, "VanyaCas (EAD0)"           }, // 46
+};
+
+bool checkCASystem(unsigned int cicamP, int caidP)
+{
+  // always skip the first row
+  if ((cicamP > 0) && (cicamP < ELEMENTS(ca_systems_table)))
+     return ((caidP >= ca_systems_table[cicamP].start) && (caidP <= ca_systems_table[cicamP].end));
+  return false;
+}
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..1cc713b
--- /dev/null
+++ b/common.h
@@ -0,0 +1,116 @@
+/*
+ * common.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_COMMON_H
+#define __SATIP_COMMON_H
+
+#include <vdr/device.h>
+#include <vdr/tools.h>
+#include <vdr/config.h>
+#include <vdr/i18n.h>
+
+#define SATIP_MAX_DEVICES                MAXDEVICES
+
+#define SATIP_BUFFER_SIZE                KILOBYTE(1024)
+
+#define SATIP_DEVICE_INFO_ALL            0
+#define SATIP_DEVICE_INFO_GENERAL        1
+#define SATIP_DEVICE_INFO_PIDS           2
+#define SATIP_DEVICE_INFO_FILTERS        3
+#define SATIP_DEVICE_INFO_PROTOCOL       4
+#define SATIP_DEVICE_INFO_BITRATE        5
+
+#define SATIP_STATS_ACTIVE_PIDS_COUNT    10
+#define SATIP_STATS_ACTIVE_FILTERS_COUNT 10
+
+#define MAX_DISABLED_SOURCES_COUNT       25
+#define SECTION_FILTER_TABLE_SIZE        5
+
+#define MAX_CICAM_COUNT                  2
+#define CA_SYSTEMS_TABLE_SIZE            47
+
+#define SATIP_CURL_EASY_GETINFO(X, Y, Z) \
+  if ((res = curl_easy_getinfo((X), (Y), (Z))) != CURLE_OK) { \
+     error("curl_easy_getinfo(%s) [%s,%d] failed: %s (%d)", #Y,  __FILE__, __LINE__, curl_easy_strerror(res), res); \
+     }
+
+#define SATIP_CURL_EASY_SETOPT(X, Y, Z) \
+  if ((res = curl_easy_setopt((X), (Y), (Z))) != CURLE_OK) { \
+     esyslog("curl_easy_setopt(%s, %s) [%s,%d] failed: %s (%d)", #Y, #Z, __FILE__, __LINE__, curl_easy_strerror(res), res); \
+     }
+
+#define SATIP_CURL_EASY_PERFORM(X) \
+  if ((res = curl_easy_perform((X))) != CURLE_OK) { \
+     esyslog("curl_easy_perform() [%s,%d] failed: %s (%d)",  __FILE__, __LINE__, curl_easy_strerror(res), res); \
+     }
+
+#define ERROR_IF_FUNC(exp, errstr, func, ret)              \
+  do {                                                     \
+     if (exp) {                                            \
+        char tmp[64];                                      \
+        esyslog("[%s,%d]: "errstr": %s", __FILE__, __LINE__, \
+              strerror_r(errno, tmp, sizeof(tmp)));        \
+        func;                                              \
+        ret;                                               \
+        }                                                  \
+  } while (0)
+
+
+#define ERROR_IF_RET(exp, errstr, ret) ERROR_IF_FUNC(exp, errstr, ,ret);
+
+#define ERROR_IF(exp, errstr) ERROR_IF_FUNC(exp, errstr, , );
+
+#define DELETE_POINTER(ptr)      \
+  do {                           \
+     if (ptr) {                  \
+        typeof(*ptr) *tmp = ptr; \
+        ptr = NULL;              \
+        delete(tmp);             \
+        }                        \
+  } while (0)
+
+#define FREE_POINTER(ptr)        \
+  do {                           \
+     if (ptr) {                  \
+        typeof(*ptr) *tmp = ptr; \
+        ptr = NULL;              \
+        free(tmp);               \
+        }                        \
+  } while (0)
+
+#define ELEMENTS(x) (sizeof(x) / sizeof(x[0]))
+
+uint16_t ts_pid(const uint8_t *bufP);
+uint8_t payload(const uint8_t *bufP);
+const char *id_pid(const u_short pidP);
+char *StripTags(char *strP);
+char *SkipZeroes(const char *strP);
+cString ChangeCase(const cString &strP, bool upperP);
+
+struct section_filter_table_type {
+  const char *description;
+  const char *tag;
+  u_short pid;
+  u_char tid;
+  u_char mask;
+};
+
+extern const section_filter_table_type section_filter_table[SECTION_FILTER_TABLE_SIZE];
+
+struct ca_systems_table_type {
+  int start;
+  int end;
+  const char *description;
+};
+
+extern const ca_systems_table_type ca_systems_table[CA_SYSTEMS_TABLE_SIZE];
+extern bool checkCASystem(unsigned int cicamP, int caidP);
+
+extern const char VERSION[];
+
+#endif // __SATIP_COMMON_H
+
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..a0688cc
--- /dev/null
+++ b/config.c
@@ -0,0 +1,78 @@
+/*
+ * config.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "discover.h"
+#include "log.h"
+#include "config.h"
+
+cSatipConfig SatipConfig;
+
+cSatipConfig::cSatipConfig(void)
+: operatingModeM(eOperatingModeLow),
+  traceModeM(eTraceModeNormal),
+  ciExtensionM(0),
+  eitScanM(1),
+  useBytesM(1),
+  disableServerQuirksM(false),
+  useSingleModelServersM(false)
+{
+  for (unsigned int i = 0; i < ELEMENTS(cicamsM); ++i)
+      cicamsM[i] = 0;
+  for (unsigned int i = 0; i < ELEMENTS(disabledSourcesM); ++i)
+      disabledSourcesM[i] = cSource::stNone;
+  for (unsigned int i = 0; i < ELEMENTS(disabledFiltersM); ++i)
+      disabledFiltersM[i] = -1;
+}
+
+int cSatipConfig::GetCICAM(unsigned int indexP) const
+{
+  return (indexP < ELEMENTS(cicamsM)) ? cicamsM[indexP] : -1;
+}
+
+void cSatipConfig::SetCICAM(unsigned int indexP, int cicamP)
+{
+  if (indexP < ELEMENTS(cicamsM))
+     cicamsM[indexP] = cicamP;
+}
+
+unsigned int cSatipConfig::GetDisabledSourcesCount(void) const
+{
+  unsigned int n = 0;
+  while ((n < ELEMENTS(disabledSourcesM) && (disabledSourcesM[n] != cSource::stNone)))
+        n++;
+  return n;
+}
+
+int cSatipConfig::GetDisabledSources(unsigned int indexP) const
+{
+  return (indexP < ELEMENTS(disabledSourcesM)) ? disabledSourcesM[indexP] : cSource::stNone;
+}
+
+void cSatipConfig::SetDisabledSources(unsigned int indexP, int sourceP)
+{
+  if (indexP < ELEMENTS(disabledSourcesM))
+     disabledSourcesM[indexP] = sourceP;
+}
+
+unsigned int cSatipConfig::GetDisabledFiltersCount(void) const
+{
+  unsigned int n = 0;
+  while ((n < ELEMENTS(disabledFiltersM) && (disabledFiltersM[n] != -1)))
+        n++;
+  return n;
+}
+
+int cSatipConfig::GetDisabledFilters(unsigned int indexP) const
+{
+  return (indexP < ELEMENTS(disabledFiltersM)) ? disabledFiltersM[indexP] : -1;
+}
+
+void cSatipConfig::SetDisabledFilters(unsigned int indexP, int numberP)
+{
+  if (indexP < ELEMENTS(disabledFiltersM))
+     disabledFiltersM[indexP] = numberP;
+}
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..9dcd4fd
--- /dev/null
+++ b/config.h
@@ -0,0 +1,90 @@
+/*
+ * config.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_CONFIG_H
+#define __SATIP_CONFIG_H
+
+#include <vdr/menuitems.h>
+#include "common.h"
+
+class cSatipConfig
+{
+private:
+  unsigned int operatingModeM;
+  unsigned int traceModeM;
+  unsigned int ciExtensionM;
+  unsigned int eitScanM;
+  unsigned int useBytesM;
+  bool disableServerQuirksM;
+  bool useSingleModelServersM;
+  int cicamsM[MAX_CICAM_COUNT];
+  int disabledSourcesM[MAX_DISABLED_SOURCES_COUNT];
+  int disabledFiltersM[SECTION_FILTER_TABLE_SIZE];
+
+public:
+  enum eOperatingMode {
+    eOperatingModeOff = 0,
+    eOperatingModeLow,
+    eOperatingModeNormal,
+    eOperatingModeHigh,
+    eOperatingModeCount
+  };
+  enum eTraceMode {
+   eTraceModeNormal  = 0x0000,
+   eTraceModeDebug1  = 0x0001,
+   eTraceModeDebug2  = 0x0002,
+   eTraceModeDebug3  = 0x0004,
+   eTraceModeDebug4  = 0x0008,
+   eTraceModeDebug5  = 0x0010,
+   eTraceModeDebug6  = 0x0020,
+   eTraceModeDebug7  = 0x0040,
+   eTraceModeDebug8  = 0x0080,
+   eTraceModeDebug9  = 0x0100,
+   eTraceModeDebug10 = 0x0200,
+   eTraceModeDebug11 = 0x0400,
+   eTraceModeDebug12 = 0x0800,
+   eTraceModeDebug13 = 0x1000,
+   eTraceModeDebug14 = 0x2000,
+   eTraceModeDebug15 = 0x4000,
+   eTraceModeDebug16 = 0x8000,
+   eTraceModeMask    = 0xFFFF
+  };
+  cSatipConfig();
+  unsigned int GetOperatingMode(void) const { return operatingModeM; }
+  bool IsOperatingModeOff(void) const { return (operatingModeM == eOperatingModeOff); }
+  bool IsOperatingModeLow(void) const { return (operatingModeM == eOperatingModeLow); }
+  bool IsOperatingModeNormal(void) const { return (operatingModeM == eOperatingModeNormal); }
+  bool IsOperatingModeHigh(void) const { return (operatingModeM == eOperatingModeHigh); }
+  void ToggleOperatingMode(void) { operatingModeM = (operatingModeM + 1) % eOperatingModeCount; }
+  unsigned int GetTraceMode(void) const { return traceModeM; }
+  bool IsTraceMode(eTraceMode modeP) const { return (traceModeM & modeP); }
+  unsigned int GetCIExtension(void) const { return ciExtensionM; }
+  int GetCICAM(unsigned int indexP) const;
+  unsigned int GetEITScan(void) const { return eitScanM; }
+  unsigned int GetUseBytes(void) const { return useBytesM; }
+  bool GetDisableServerQuirks(void) const { return disableServerQuirksM; }
+  bool GetUseSingleModelServers(void) const { return useSingleModelServersM; }
+  unsigned int GetDisabledSourcesCount(void) const;
+  int GetDisabledSources(unsigned int indexP) const;
+  unsigned int GetDisabledFiltersCount(void) const;
+  int GetDisabledFilters(unsigned int indexP) const;
+
+  void SetOperatingMode(unsigned int operatingModeP) { operatingModeM = operatingModeP; }
+  void SetTraceMode(unsigned int modeP) { traceModeM = (modeP & eTraceModeMask); }
+  void SetCIExtension(unsigned int onOffP) { ciExtensionM = onOffP; }
+  void SetCICAM(unsigned int indexP, int cicamP);
+  void SetEITScan(unsigned int onOffP) { eitScanM = onOffP; }
+  void SetUseBytes(unsigned int onOffP) { useBytesM = onOffP; }
+  void SetDisableServerQuirks(bool onOffP) { disableServerQuirksM = onOffP; }
+  void SetUseSingleModelServers(bool onOffP) { useSingleModelServersM = onOffP; }
+  void SetDisabledSources(unsigned int indexP, int sourceP);
+  void SetDisabledFilters(unsigned int indexP, int numberP);
+};
+
+extern cSatipConfig SatipConfig;
+
+#endif // __SATIP_CONFIG_H
diff --git a/device.c b/device.c
new file mode 100644
index 0000000..cfaee19
--- /dev/null
+++ b/device.c
@@ -0,0 +1,527 @@
+/*
+ * device.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <vdr/menu.h> // cRecordControl
+
+#include "config.h"
+#include "discover.h"
+#include "log.h"
+#include "param.h"
+#include "device.h"
+
+static cSatipDevice * SatipDevicesS[SATIP_MAX_DEVICES] = { NULL };
+
+cSatipDevice::cSatipDevice(unsigned int indexP)
+: deviceIndexM(indexP),
+  isPacketDeliveredM(false),
+  isOpenDvrM(false),
+  deviceNameM(*cString::sprintf("%s %d", *DeviceType(), deviceIndexM)),
+  channelM(),
+  createdM(0),
+  mutexM()
+{
+  unsigned int bufsize = (unsigned int)SATIP_BUFFER_SIZE;
+  bufsize -= (bufsize % TS_SIZE);
+  info("Creating device CardIndex=%d DeviceNumber=%d [device %u]", CardIndex(), DeviceNumber(), deviceIndexM);
+  tsBufferM = new cRingBufferLinear(bufsize + 1, TS_SIZE, false,
+                                   *cString::sprintf("SATIP#%d TS", deviceIndexM));
+  if (tsBufferM) {
+     tsBufferM->SetTimeouts(10, 10);
+     tsBufferM->SetIoThrottle();
+     pTunerM = new cSatipTuner(*this, tsBufferM->Free());
+     }
+  // Start section handler
+  pSectionFilterHandlerM = new cSatipSectionFilterHandler(deviceIndexM, bufsize + 1);
+  StartSectionHandler();
+}
+
+cSatipDevice::~cSatipDevice()
+{
+  debug1("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  // Stop section handler
+  StopSectionHandler();
+  DELETE_POINTER(pSectionFilterHandlerM);
+  DELETE_POINTER(pTunerM);
+  DELETE_POINTER(tsBufferM);
+}
+
+bool cSatipDevice::Initialize(unsigned int deviceCountP)
+{
+  debug1("%s (%u)", __PRETTY_FUNCTION__, deviceCountP);
+  if (deviceCountP > SATIP_MAX_DEVICES)
+     deviceCountP = SATIP_MAX_DEVICES;
+  for (unsigned int i = 0; i < deviceCountP; ++i)
+      SatipDevicesS[i] = new cSatipDevice(i);
+  for (unsigned int i = deviceCountP; i < SATIP_MAX_DEVICES; ++i)
+      SatipDevicesS[i] = NULL;
+  return true;
+}
+
+void cSatipDevice::Shutdown(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  for (int i = 0; i < SATIP_MAX_DEVICES; ++i) {
+      if (SatipDevicesS[i])
+         SatipDevicesS[i]->CloseDvr();
+      }
+}
+
+unsigned int cSatipDevice::Count(void)
+{
+  unsigned int count = 0;
+  debug1("%s", __PRETTY_FUNCTION__);
+  for (unsigned int i = 0; i < SATIP_MAX_DEVICES; ++i) {
+      if (SatipDevicesS[i] != NULL)
+         count++;
+      }
+  return count;
+}
+
+cSatipDevice *cSatipDevice::GetSatipDevice(int cardIndexP)
+{
+  debug16("%s (%d)", __PRETTY_FUNCTION__, cardIndexP);
+  for (unsigned int i = 0; i < SATIP_MAX_DEVICES; ++i) {
+      if (SatipDevicesS[i] && (SatipDevicesS[i]->CardIndex() == cardIndexP)) {
+         debug16("%s (%d): Found!", __PRETTY_FUNCTION__, cardIndexP);
+         return SatipDevicesS[i];
+         }
+      }
+  return NULL;
+}
+
+cString cSatipDevice::GetSatipStatus(void)
+{
+  cString info = "";
+  for (int i = 0; i < cDevice::NumDevices(); i++) {
+      const cDevice *device = cDevice::GetDevice(i);
+      if (device && strstr(device->DeviceType(), "SAT>IP")) {
+         int timers = 0;
+         bool live = (device == cDevice::ActualDevice());
+         bool lock = device->HasLock();
+         const cChannel *channel = device->GetCurrentlyTunedTransponder();
+         for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
+             if (timer->Recording()) {
+                cRecordControl *control = cRecordControls::GetRecordControl(timer);
+                if (control && control->Device() == device)
+                   timers++;
+                }
+            }
+         info = cString::sprintf("%sDevice: %s\n", *info, *device->DeviceName());
+         if (lock)
+            info = cString::sprintf("%sCardIndex: %d  HasLock: yes  Strength: %d  Quality: %d%s\n", *info, device->CardIndex(), device->SignalStrength(), device->SignalQuality(), live ? "  Live: yes" : "");
+         else
+            info = cString::sprintf("%sCardIndex: %d  HasLock: no\n", *info, device->CardIndex());
+         if (channel && channel->Number() > 0)
+            info = cString::sprintf("%sTransponder: %d  Channel: %s\n", *info, (channel && channel->Number() > 0) ? channel->Transponder() : 0, (channel && channel->Number() > 0) ? channel->Name() : "---");
+         if (timers)
+            info = cString::sprintf("%sRecording: %d timer%s\n", *info, timers, (timers > 1) ? "s" : "");
+         info = cString::sprintf("%s\n", *info);
+         }
+      }
+  return isempty(*info) ? cString(tr("SAT>IP information not available!")) : info;
+}
+
+cString cSatipDevice::GetGeneralInformation(void)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return cString::sprintf("SAT>IP device: %d\nCardIndex: %d\nStream: %s\nSignal: %s\nStream bitrate: %s\n%sChannel: %s",
+                          deviceIndexM, CardIndex(),
+                          pTunerM ? *pTunerM->GetInformation() : "",
+                          pTunerM ? *pTunerM->GetSignalStatus() : "",
+                          pTunerM ? *pTunerM->GetTunerStatistic() : "",
+                          *GetBufferStatistic(),
+                          *Channels.GetByNumber(cDevice::CurrentChannel())->ToText());
+}
+
+cString cSatipDevice::GetPidsInformation(void)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return GetPidStatistic();
+}
+
+cString cSatipDevice::GetFiltersInformation(void)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return cString::sprintf("Active section filters:\n%s", pSectionFilterHandlerM ? *pSectionFilterHandlerM->GetInformation() : "");
+}
+
+cString cSatipDevice::GetInformation(unsigned int pageP)
+{
+  // generate information string
+  cString s;
+  switch (pageP) {
+    case SATIP_DEVICE_INFO_GENERAL:
+         s = GetGeneralInformation();
+         break;
+    case SATIP_DEVICE_INFO_PIDS:
+         s = GetPidsInformation();
+         break;
+    case SATIP_DEVICE_INFO_FILTERS:
+         s = GetFiltersInformation();
+         break;
+    case SATIP_DEVICE_INFO_PROTOCOL:
+         s = pTunerM ? *pTunerM->GetInformation() : "";
+         break;
+    case SATIP_DEVICE_INFO_BITRATE:
+         s = pTunerM ? *pTunerM->GetTunerStatistic() : "";
+         break;
+    default:
+         s = cString::sprintf("%s%s%s",
+                              *GetGeneralInformation(),
+                              *GetPidsInformation(),
+                              *GetFiltersInformation());
+         break;
+    }
+  return s;
+}
+
+bool cSatipDevice::Ready(void)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return ((cSatipDiscover::GetInstance()->GetServerCount() > 0) || (createdM.Elapsed() > eReadyTimeoutMs));
+}
+
+cString cSatipDevice::DeviceType(void) const
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return "SAT>IP";
+}
+
+cString cSatipDevice::DeviceName(void) const
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return deviceNameM;
+}
+
+bool cSatipDevice::AvoidRecording(void) const
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return SatipConfig.IsOperatingModeLow();
+}
+
+int cSatipDevice::SignalStrength(void) const
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return (pTunerM ? pTunerM->SignalStrength() : -1);
+}
+
+int cSatipDevice::SignalQuality(void) const
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return (pTunerM ? pTunerM->SignalQuality() : -1);
+}
+
+bool cSatipDevice::ProvidesSource(int sourceP) const
+{
+  cSource *s = Sources.Get(sourceP);
+  debug9("%s (%c) desc='%s' [device %u]", __PRETTY_FUNCTION__, cSource::ToChar(sourceP), s ? s->Description() : "", deviceIndexM);
+  // source descriptions starting with '0' are disabled
+  if (s && s->Description() && (*(s->Description()) == '0'))
+     return false;
+  if (!SatipConfig.IsOperatingModeOff() && !!cSatipDiscover::GetInstance()->GetServer(sourceP)) {
+     int numDisabledSourcesM = SatipConfig.GetDisabledSourcesCount();
+     for (int i = 0; i < numDisabledSourcesM; ++i) {
+         if (sourceP == SatipConfig.GetDisabledSources(i))
+            return false;
+         }
+     return true;
+     }
+  return false;
+}
+
+bool cSatipDevice::ProvidesTransponder(const cChannel *channelP) const
+{
+  debug9("%s (%d) transponder=%d source=%c [device %u]", __PRETTY_FUNCTION__, channelP ? channelP->Number() : -1, channelP ? channelP->Transponder() : -1, channelP ? cSource::ToChar(channelP->Source()) : '?', deviceIndexM);
+  if (!ProvidesSource(channelP->Source()))
+     return false;
+  return DeviceHooksProvidesTransponder(channelP);
+}
+
+bool cSatipDevice::ProvidesChannel(const cChannel *channelP, int priorityP, bool *needsDetachReceiversP) const
+{
+  bool result = false;
+  bool hasPriority = (priorityP == IDLEPRIORITY) || (priorityP > this->Priority());
+  bool needsDetachReceivers = false;
+
+  debug9("%s (%d, %d, %d) [device %u]", __PRETTY_FUNCTION__, channelP ? channelP->Number() : -1, priorityP, !!needsDetachReceiversP, deviceIndexM);
+
+  if (channelP && ProvidesTransponder(channelP)) {
+     result = hasPriority;
+     if (priorityP > IDLEPRIORITY) {
+        if (Receiving()) {
+           if (IsTunedToTransponder(channelP)) {
+              if (channelP->Vpid() && !HasPid(channelP->Vpid()) || channelP->Apid(0) && !HasPid(channelP->Apid(0)) || channelP->Dpid(0) && !HasPid(channelP->Dpid(0))) {
+                 if (CamSlot() && channelP->Ca() >= CA_ENCRYPTED_MIN) {
+                    if (CamSlot()->CanDecrypt(channelP))
+                       result = true;
+                    else
+                       needsDetachReceivers = true;
+                    }
+                 else
+                    result = true;
+                 }
+              else
+                 result = true;
+              }
+           else
+              needsDetachReceivers = Receiving();
+           }
+        }
+     }
+  if (needsDetachReceiversP)
+     *needsDetachReceiversP = needsDetachReceivers;
+  return result;
+}
+
+bool cSatipDevice::ProvidesEIT(void) const
+{
+  return (SatipConfig.GetEITScan());
+}
+
+int cSatipDevice::NumProvidedSystems(void) const
+{
+  int count = cSatipDiscover::GetInstance()->NumProvidedSystems();
+  // Tweak the count according to operation mode
+  if (SatipConfig.IsOperatingModeLow())
+     count = 15;
+  else if (SatipConfig.IsOperatingModeHigh())
+     count = 1;
+  // Clamp the count between 1 and 15
+  if (count > 15)
+     count = 15;
+  else if (count < 1)
+     count = 1;
+  return count;
+}
+
+const cChannel *cSatipDevice::GetCurrentlyTunedTransponder(void) const
+{
+  return &channelM;
+}
+
+bool cSatipDevice::IsTunedToTransponder(const cChannel *channelP) const
+{
+  if (pTunerM && !pTunerM->IsTuned())
+     return false;
+  if ((channelM.Source() != channelP->Source()) || (channelM.Transponder() != channelP->Transponder()))
+     return false;
+  return (strcmp(channelM.Parameters(), channelP->Parameters()) == 0);
+}
+
+bool cSatipDevice::MaySwitchTransponder(const cChannel *channelP) const
+{
+  return cDevice::MaySwitchTransponder(channelP);
+}
+
+bool cSatipDevice::SetChannelDevice(const cChannel *channelP, bool liveViewP)
+{
+  debug9("%s (%d, %d) [device %u]", __PRETTY_FUNCTION__, channelP ? channelP->Number() : -1, liveViewP, deviceIndexM);
+  if (channelP) {
+     cDvbTransponderParameters dtp(channelP->Parameters());
+     cString params = GetTransponderUrlParameters(channelP);
+     if (isempty(params)) {
+        error("Unrecognized channel parameters: %s [device %u]", channelP->Parameters(), deviceIndexM);
+        return false;
+        }
+     cString address;
+     cSatipServer *server = cSatipDiscover::GetInstance()->GetServer(channelP->Source(), channelP->Transponder(), dtp.System());
+     if (!server) {
+        debug9("%s No suitable server found [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+        return false;
+        }
+     cSatipDiscover::GetInstance()->SetTransponder(server, channelP->Transponder());
+     if (pTunerM && pTunerM->SetSource(server, *params, deviceIndexM)) {
+        channelM = *channelP;
+        deviceNameM = cString::sprintf("%s %d %s", *DeviceType(), deviceIndexM, *cSatipDiscover::GetInstance()->GetServerString(server));
+        return true;
+        }
+     }
+  else if (pTunerM) {
+     pTunerM->SetSource(NULL, NULL, deviceIndexM);
+     return true;
+     }
+  return false;
+}
+
+bool cSatipDevice::SetPid(cPidHandle *handleP, int typeP, bool onP)
+{
+  debug12("%s (%d, %d, %d) [device %u]", __PRETTY_FUNCTION__, handleP->pid, typeP, onP, deviceIndexM);
+  if (pTunerM && handleP && handleP->pid >= 0) {
+     if (onP)
+        return pTunerM->SetPid(handleP->pid, typeP, true);
+     else if (!handleP->used)
+        return pTunerM->SetPid(handleP->pid, typeP, false);
+     }
+  return true;
+}
+
+int cSatipDevice::OpenFilter(u_short pidP, u_char tidP, u_char maskP)
+{
+  debug12("%s (%d, %02X, %02X) [device %d]", __PRETTY_FUNCTION__, pidP, tidP, maskP, deviceIndexM);
+  if (pSectionFilterHandlerM) {
+     int handle = pSectionFilterHandlerM->Open(pidP, tidP, maskP);
+     if (pTunerM && (handle >= 0))
+        pTunerM->SetPid(pidP, ptOther, true);
+     return handle;
+     }
+  return -1;
+}
+
+void cSatipDevice::CloseFilter(int handleP)
+{
+  if (pSectionFilterHandlerM) {
+     int pid = pSectionFilterHandlerM->GetPid(handleP);
+     debug12("%s (%d) [device %u]", __PRETTY_FUNCTION__, pid, deviceIndexM);
+     if (pTunerM)
+        pTunerM->SetPid(pid, ptOther, false);
+     pSectionFilterHandlerM->Close(handleP);
+     }
+}
+
+bool cSatipDevice::OpenDvr(void)
+{
+  debug9("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  isPacketDeliveredM = false;
+  tsBufferM->Clear();
+  if (pTunerM)
+     pTunerM->Open();
+  isOpenDvrM = true;
+  return true;
+}
+
+void cSatipDevice::CloseDvr(void)
+{
+  debug9("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  if (pTunerM)
+     pTunerM->Close();
+  isOpenDvrM = false;
+}
+
+bool cSatipDevice::HasLock(int timeoutMsP) const
+{
+  debug16("%s (%d) [device %d]", __PRETTY_FUNCTION__, timeoutMsP, deviceIndexM);
+  return (pTunerM && pTunerM->HasLock());
+}
+
+bool cSatipDevice::HasInternalCam(void)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  return SatipConfig.GetCIExtension();
+}
+
+void cSatipDevice::WriteData(uchar *bufferP, int lengthP)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  // Fill up TS buffer
+  if (isOpenDvrM && tsBufferM) {
+     int len = tsBufferM->Put(bufferP, lengthP);
+     if (len != lengthP)
+        tsBufferM->ReportOverflow(lengthP - len);
+     }
+  // Filter the sections
+  if (pSectionFilterHandlerM)
+     pSectionFilterHandlerM->Write(bufferP, lengthP);
+}
+
+int cSatipDevice::GetId(void)
+{
+  return deviceIndexM;
+}
+
+int cSatipDevice::GetPmtPid(void)
+{
+  int pid = 0;
+#if defined(APIVERSNUM) && APIVERSNUM >= 20107
+  pid = channelM.Ca() ? ::GetPmtPid(channelM.Source(), channelM.Transponder(), channelM.Sid()) : 0;
+#endif
+  debug11("%s pmtpid=%d source=%c transponder=%d sid=%d name=%s [device %u]", __PRETTY_FUNCTION__, pid, cSource::ToChar(channelM.Source()), channelM.Transponder(), channelM.Sid(), channelM.Name(), deviceIndexM);
+  return pid;
+}
+
+int cSatipDevice::GetCISlot(void)
+{
+  int slot = 0;
+  int ca = 0;
+  for (const int *id = channelM.Caids(); *id; ++id) {
+      if (checkCASystem(SatipConfig.GetCICAM(0), *id)) {
+         ca = *id;
+         slot = 1;
+         break;
+         }
+      else if (checkCASystem(SatipConfig.GetCICAM(1), *id)) {
+         ca = *id;
+         slot = 2;
+         break;
+         }
+      }
+  debug11("%s slot=%d ca=%X name=%s [device %u]", __PRETTY_FUNCTION__, slot, ca, channelM.Name(), deviceIndexM);
+  return slot;
+}
+
+uchar *cSatipDevice::GetData(int *availableP)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  if (isOpenDvrM && tsBufferM) {
+     int count = 0;
+     if (isPacketDeliveredM)
+        SkipData(TS_SIZE);
+     uchar *p = tsBufferM->Get(count);
+     if (p && count >= TS_SIZE) {
+        if (*p != TS_SYNC_BYTE) {
+           for (int i = 1; i < count; i++) {
+               if (p[i] == TS_SYNC_BYTE) {
+                  count = i;
+                  break;
+                  }
+               }
+           tsBufferM->Del(count);
+           info("Skipped %d bytes to sync on TS packet", count);
+           return NULL;
+           }
+        isPacketDeliveredM = true;
+        if (availableP)
+           *availableP = count;
+        // Update pid statistics
+        AddPidStatistic(ts_pid(p), payload(p));
+        return p;
+        }
+     }
+  return NULL;
+}
+
+void cSatipDevice::SkipData(int countP)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  tsBufferM->Del(countP);
+  isPacketDeliveredM = false;
+  // Update buffer statistics
+  AddBufferStatistic(countP, tsBufferM->Available());
+}
+
+bool cSatipDevice::GetTSPacket(uchar *&dataP)
+{
+  debug16("%s [device %u]", __PRETTY_FUNCTION__, deviceIndexM);
+  if (tsBufferM) {
+#if defined(APIVERSNUM) && APIVERSNUM >= 20104
+     if (cCamSlot *cs = CamSlot()) {
+        if (cs->WantsTsData()) {
+           int available;
+           dataP = GetData(&available);
+           if (dataP) {
+              dataP = cs->Decrypt(dataP, available);
+              SkipData(available);
+              }
+           return true;
+           }
+        }
+#endif
+     dataP = GetData();
+     return true;
+     }
+  dataP = NULL;
+  return true;
+}
diff --git a/device.h b/device.h
new file mode 100644
index 0000000..bfb7a08
--- /dev/null
+++ b/device.h
@@ -0,0 +1,115 @@
+/*
+ * device.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_DEVICE_H
+#define __SATIP_DEVICE_H
+
+#include <vdr/device.h>
+#include "common.h"
+#include "deviceif.h"
+#include "tuner.h"
+#include "sectionfilter.h"
+#include "statistics.h"
+
+class cSatipDevice : public cDevice, public cSatipPidStatistics, public cSatipBufferStatistics, public cSatipDeviceIf {
+  // static ones
+public:
+  static unsigned int deviceCount;
+  static bool Initialize(unsigned int DeviceCount);
+  static void Shutdown(void);
+  static unsigned int Count(void);
+  static cSatipDevice *GetSatipDevice(int CardIndex);
+  static cString GetSatipStatus(void);
+
+  // private parts
+private:
+  enum {
+    eReadyTimeoutMs = 2000 // in milliseconds
+  };
+  unsigned int deviceIndexM;
+  bool isPacketDeliveredM;
+  bool isOpenDvrM;
+  cString deviceNameM;
+  cChannel channelM;
+  cRingBufferLinear *tsBufferM;
+  cSatipTuner *pTunerM;
+  cSatipSectionFilterHandler *pSectionFilterHandlerM;
+  cTimeMs createdM;
+  cMutex mutexM;
+
+  // constructor & destructor
+public:
+  cSatipDevice(unsigned int deviceIndexP);
+  virtual ~cSatipDevice();
+  cString GetInformation(unsigned int pageP = SATIP_DEVICE_INFO_ALL);
+
+  // copy and assignment constructors
+private:
+  cSatipDevice(const cSatipDevice&);
+  cSatipDevice& operator=(const cSatipDevice&);
+
+  // for statistics and general information
+  cString GetGeneralInformation(void);
+  cString GetPidsInformation(void);
+  cString GetFiltersInformation(void);
+
+  // for channel info
+public:
+  virtual bool Ready(void);
+  virtual cString DeviceType(void) const;
+  virtual cString DeviceName(void) const;
+  virtual bool AvoidRecording(void) const;
+  virtual int SignalStrength(void) const;
+  virtual int SignalQuality(void) const;
+
+  // for channel selection
+public:
+  virtual bool ProvidesSource(int sourceP) const;
+  virtual bool ProvidesTransponder(const cChannel *channelP) const;
+  virtual bool ProvidesChannel(const cChannel *channelP, int priorityP = -1, bool *needsDetachReceiversP = NULL) const;
+  virtual bool ProvidesEIT(void) const;
+  virtual int NumProvidedSystems(void) const;
+  virtual const cChannel *GetCurrentlyTunedTransponder(void) const;
+  virtual bool IsTunedToTransponder(const cChannel *channelP) const;
+  virtual bool MaySwitchTransponder(const cChannel *channelP) const;
+
+protected:
+  virtual bool SetChannelDevice(const cChannel *channelP, bool liveViewP);
+
+  // for recording
+private:
+  uchar *GetData(int *availableP = NULL);
+  void SkipData(int countP);
+
+protected:
+  virtual bool SetPid(cPidHandle *handleP, int typeP, bool onP);
+  virtual bool OpenDvr(void);
+  virtual void CloseDvr(void);
+  virtual bool GetTSPacket(uchar *&dataP);
+
+  // for section filtering
+public:
+  virtual int OpenFilter(u_short pidP, u_char tidP, u_char maskP);
+  virtual void CloseFilter(int handleP);
+
+  // for transponder lock
+public:
+  virtual bool HasLock(int timeoutMsP) const;
+
+  // for common interface
+public:
+  virtual bool HasInternalCam(void);
+
+  // for internal device interface
+public:
+  virtual void WriteData(u_char *bufferP, int lengthP);
+  virtual int GetId(void);
+  virtual int GetPmtPid(void);
+  virtual int GetCISlot(void);
+};
+
+#endif // __SATIP_DEVICE_H
diff --git a/deviceif.h b/deviceif.h
new file mode 100644
index 0000000..719f4c2
--- /dev/null
+++ b/deviceif.h
@@ -0,0 +1,25 @@
+/*
+ * deviceif.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_DEVICEIF_H
+#define __SATIP_DEVICEIF_H
+
+class cSatipDeviceIf {
+public:
+  cSatipDeviceIf() {}
+  virtual ~cSatipDeviceIf() {}
+  virtual void WriteData(u_char *bufferP, int lengthP) = 0;
+  virtual int GetId(void) = 0;
+  virtual int GetPmtPid(void) = 0;
+  virtual int GetCISlot(void) = 0;
+
+private:
+  cSatipDeviceIf(const cSatipDeviceIf&);
+  cSatipDeviceIf& operator=(const cSatipDeviceIf&);
+};
+
+#endif // __SATIP_DEVICEIF_H
diff --git a/discover.c b/discover.c
new file mode 100644
index 0000000..91735bd
--- /dev/null
+++ b/discover.c
@@ -0,0 +1,335 @@
+/*
+ * discover.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <string.h>
+#ifdef USE_TINYXML
+ #include <tinyxml.h>
+#else
+ #include <pugixml.hpp>
+#endif
+#include "common.h"
+#include "config.h"
+#include "log.h"
+#include "socket.h"
+#include "discover.h"
+
+cSatipDiscover *cSatipDiscover::instanceS = NULL;
+
+cSatipDiscover *cSatipDiscover::GetInstance(void)
+{
+  if (!instanceS)
+     instanceS = new cSatipDiscover();
+  return instanceS;
+}
+
+bool cSatipDiscover::Initialize(cSatipDiscoverServers *serversP)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (instanceS) {
+       if (serversP) {
+          for (cSatipDiscoverServer *s = serversP->First(); s; s = serversP->Next(s))
+              instanceS->AddServer(s->IpAddress(), s->Model(), s->Description());
+          }
+     else
+        instanceS->Activate();
+     }
+  return true;
+}
+
+void cSatipDiscover::Destroy(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (instanceS)
+     instanceS->Deactivate();
+}
+
+size_t cSatipDiscover::WriteCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP)
+{
+  cSatipDiscover *obj = reinterpret_cast<cSatipDiscover *>(dataP);
+  size_t len = sizeP * nmembP;
+  debug16("%s len=%zu", __PRETTY_FUNCTION__, len);
+
+  if (obj) {
+     CURLcode res = CURLE_OK;
+     const char *desc = NULL, *model = NULL, *addr = NULL;
+#ifdef USE_TINYXML
+     TiXmlDocument doc;
+     char *xml = MALLOC(char, len + 1);
+     memcpy(xml, ptrP, len);
+     *(xml + len + 1) = 0;
+     doc.Parse((const char *)xml);
+     TiXmlHandle docHandle(&doc);
+     TiXmlElement *descElement = docHandle.FirstChild("root").FirstChild("device").FirstChild("friendlyName").ToElement();
+     if (descElement)
+        desc = descElement->GetText() ? descElement->GetText() : "MyBrokenHardware";
+     TiXmlElement *modelElement = docHandle.FirstChild("root").FirstChild("device").FirstChild("satip:X_SATIPCAP").ToElement();
+     if (modelElement)
+        model = modelElement->GetText() ? modelElement->GetText() : "DVBS2-1";
+#else
+     pugi::xml_document doc;
+     pugi::xml_parse_result result = doc.load_buffer(ptrP, len);
+     if (result) {
+        pugi::xml_node descNode = doc.first_element_by_path("root/device/friendlyName");
+        if (descNode)
+           desc = descNode.text().as_string("MyBrokenHardware");
+        pugi::xml_node modelNode = doc.first_element_by_path("root/device/satip:X_SATIPCAP");
+        if (modelNode)
+           model = modelNode.text().as_string("DVBS2-1");
+        }
+#endif
+     SATIP_CURL_EASY_GETINFO(obj->handleM, CURLINFO_PRIMARY_IP, &addr);
+     obj->AddServer(addr, model, desc);
+     }
+
+  return len;
+}
+
+int cSatipDiscover::DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP)
+{
+  cSatipDiscover *obj = reinterpret_cast<cSatipDiscover *>(userPtrP);
+
+  if (obj) {
+     switch (typeP) {
+       case CURLINFO_TEXT:
+            debug2("%s HTTP INFO %.*s", __PRETTY_FUNCTION__, (int)sizeP, dataP);
+            break;
+       case CURLINFO_HEADER_IN:
+            debug2("%s HTTP HEAD <<< %.*s", __PRETTY_FUNCTION__, (int)sizeP, dataP);
+            break;
+       case CURLINFO_HEADER_OUT:
+            debug2("%s HTTP HEAD >>>\n%.*s", __PRETTY_FUNCTION__, (int)sizeP, dataP);
+            break;
+       case CURLINFO_DATA_IN:
+            debug2("%s HTTP DATA <<< %.*s", __PRETTY_FUNCTION__, (int)sizeP, dataP);
+            break;
+       case CURLINFO_DATA_OUT:
+            debug2("%s HTTP DATA >>>\n%.*s", __PRETTY_FUNCTION__, (int)sizeP, dataP);
+            break;
+       default:
+            break;
+       }
+     }
+
+  return 0;
+}
+
+cSatipDiscover::cSatipDiscover()
+: cThread("SATIP discover"),
+  mutexM(),
+  msearchM(*this),
+  probeUrlListM(),
+  handleM(curl_easy_init()),
+  sleepM(),
+  probeIntervalM(0),
+  serversM()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+}
+
+cSatipDiscover::~cSatipDiscover()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  Deactivate();
+  cMutexLock MutexLock(&mutexM);
+  // Free allocated memory
+  if (handleM)
+     curl_easy_cleanup(handleM);
+  handleM = NULL;
+  probeUrlListM.Clear();
+}
+
+void cSatipDiscover::Activate(void)
+{
+  // Start the thread
+  Start();
+}
+
+void cSatipDiscover::Deactivate(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  sleepM.Signal();
+  if (Running())
+     Cancel(3);
+}
+
+void cSatipDiscover::Action(void)
+{
+  debug1("%s Entering", __PRETTY_FUNCTION__);
+  probeIntervalM.Set(eProbeIntervalMs);
+  msearchM.Probe();
+  // Do the thread loop
+  while (Running()) {
+        cStringList tmp;
+
+        if (probeIntervalM.TimedOut()) {
+           probeIntervalM.Set(eProbeIntervalMs);
+           msearchM.Probe();
+           mutexM.Lock();
+           serversM.Cleanup(eProbeIntervalMs * 2);
+           mutexM.Unlock();
+           }
+        mutexM.Lock();
+        if (probeUrlListM.Size()) {
+           for (int i = 0; i < probeUrlListM.Size(); ++i)
+               tmp.Insert(strdup(probeUrlListM.At(i)));
+           probeUrlListM.Clear();
+           }
+        mutexM.Unlock();
+        if (tmp.Size()) {
+           for (int i = 0; i < tmp.Size(); ++i)
+               Fetch(tmp.At(i));
+           tmp.Clear();
+           }
+        // to avoid busy loop and reduce cpu load
+        sleepM.Wait(eSleepTimeoutMs);
+        }
+  debug1("%s Exiting", __PRETTY_FUNCTION__);
+}
+
+void cSatipDiscover::Fetch(const char *urlP)
+{
+  debug1("%s (%s)", __PRETTY_FUNCTION__, urlP);
+  if (handleM && !isempty(urlP)) {
+     long rc = 0;
+     CURLcode res = CURLE_OK;
+
+     // Verbose output
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_VERBOSE, 1L);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGFUNCTION, cSatipDiscover::DebugCallback);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGDATA, this);
+
+     // Set callback
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, cSatipDiscover::WriteCallback);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, this);
+
+     // No progress meter and no signaling
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_NOPROGRESS, 1L);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_NOSIGNAL, 1L);
+
+     // Set timeouts
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_TIMEOUT_MS, (long)eConnectTimeoutMs);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_CONNECTTIMEOUT_MS, (long)eConnectTimeoutMs);
+
+     // Set user-agent
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_USERAGENT, *cString::sprintf("vdr-%s/%s", PLUGIN_NAME_I18N, VERSION));
+
+     // Set URL
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_URL, urlP);
+
+     // Fetch the data
+     SATIP_CURL_EASY_PERFORM(handleM);
+     SATIP_CURL_EASY_GETINFO(handleM, CURLINFO_RESPONSE_CODE, &rc);
+     if (rc != 200)
+        error("Discovery detected invalid status code: %ld", rc);
+     }
+}
+
+void cSatipDiscover::AddServer(const char *addrP, const char *modelP, const char * descP)
+{
+  debug1("%s (%s, %s, %s)", __PRETTY_FUNCTION__, addrP, modelP, descP);
+  cMutexLock MutexLock(&mutexM);
+  if (SatipConfig.GetUseSingleModelServers()) {
+     int n = 0;
+     char *s, *p = strdup(modelP);
+     char *r = strtok_r(p, ",", &s);
+     while (r) {
+           r = skipspace(r);
+           cString desc = cString::sprintf("%s #%d", !isempty(descP) ? descP : "MyBrokenHardware", n++);
+           cSatipServer *tmp = new cSatipServer(addrP, r, desc);
+           if (!serversM.Update(tmp)) {
+              info("Adding server '%s|%s|%s'", tmp->Address(), tmp->Model(), tmp->Description());
+              serversM.Add(tmp);
+              }
+           else
+              DELETENULL(tmp);
+           r = strtok_r(NULL, ",", &s);
+           }
+     FREE_POINTER(p);
+     }
+  else {
+     cSatipServer *tmp = new cSatipServer(addrP, modelP, descP);
+     if (!serversM.Update(tmp)) {
+        info("Adding server '%s|%s|%s'", tmp->Address(), tmp->Model(), tmp->Description());
+        serversM.Add(tmp);
+        }
+     else
+        DELETENULL(tmp);
+     }
+}
+
+int cSatipDiscover::GetServerCount(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  return serversM.Count();
+}
+
+cSatipServer *cSatipDiscover::GetServer(int sourceP, int transponderP, int systemP)
+{
+  debug16("%s (%d, %d, %d)", __PRETTY_FUNCTION__, sourceP, transponderP, systemP);
+  cMutexLock MutexLock(&mutexM);
+  return serversM.Find(sourceP, transponderP, systemP);
+}
+
+cSatipServer *cSatipDiscover::GetServer(cSatipServer *serverP)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  return serversM.Find(serverP);
+}
+
+cSatipServers *cSatipDiscover::GetServers(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  return &serversM;
+}
+
+cString cSatipDiscover::GetServerString(cSatipServer *serverP)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  return serversM.GetString(serverP);
+}
+
+cString cSatipDiscover::GetServerList(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  return serversM.List();
+}
+
+void cSatipDiscover::SetTransponder(cSatipServer *serverP, int transponderP)
+{
+  debug16("%s (, %d)", __PRETTY_FUNCTION__, transponderP);
+  cMutexLock MutexLock(&mutexM);
+  serversM.SetTransponder(serverP, transponderP);
+}
+
+void cSatipDiscover::UseServer(cSatipServer *serverP, bool onOffP)
+{
+  debug16("%s (, %d)", __PRETTY_FUNCTION__, onOffP);
+  cMutexLock MutexLock(&mutexM);
+  serversM.Use(serverP, onOffP);
+}
+
+int cSatipDiscover::NumProvidedSystems(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  return serversM.NumProvidedSystems();
+}
+
+void cSatipDiscover::SetUrl(const char *urlP)
+{
+  debug16("%s (%s)", __PRETTY_FUNCTION__, urlP);
+  mutexM.Lock();
+  probeUrlListM.Insert(strdup(urlP));
+  mutexM.Unlock();
+  sleepM.Signal();
+}
diff --git a/discover.h b/discover.h
new file mode 100644
index 0000000..74e72b5
--- /dev/null
+++ b/discover.h
@@ -0,0 +1,91 @@
+/*
+ * discover.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_DISCOVER_H
+#define __SATIP_DISCOVER_H
+
+#include <curl/curl.h>
+
+#include <vdr/thread.h>
+#include <vdr/tools.h>
+
+#include "discoverif.h"
+#include "msearch.h"
+#include "server.h"
+#include "socket.h"
+
+class cSatipDiscoverServer : public cListObject {
+private:
+  cString ipAddressM;
+  cString descriptionM;
+  cString modelM;
+public:
+  cSatipDiscoverServer(const char *ipAddressP, const char *modelP, const char *descriptionP)
+  {
+    ipAddressM = ipAddressP; modelM = modelP; descriptionM = descriptionP;
+  }
+  const char *IpAddress(void)   { return *ipAddressM; }
+  const char *Model(void)       { return *modelM; }
+  const char *Description(void) { return *descriptionM; }
+};
+
+class cSatipDiscoverServers : public cList<cSatipDiscoverServer> {
+};
+
+class cSatipDiscover : public cThread, public cSatipDiscoverIf {
+private:
+  enum {
+    eSleepTimeoutMs   = 500,  // in milliseconds
+    eConnectTimeoutMs = 1500, // in milliseconds
+    eProbeTimeoutMs   = 2000, // in milliseconds
+    eProbeIntervalMs  = 60000 // in milliseconds
+  };
+  static cSatipDiscover *instanceS;
+  static size_t WriteCallback(char *ptrP, size_t sizeP, size_t nmembP, void *dataP);
+  static int    DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP);
+  cMutex mutexM;
+  cSatipMsearch msearchM;
+  cStringList probeUrlListM;
+  CURL *handleM;
+  cCondWait sleepM;
+  cTimeMs probeIntervalM;
+  cSatipServers serversM;
+  void Activate(void);
+  void Deactivate(void);
+  void AddServer(const char *addrP, const char *modelP, const char *descP);
+  void Fetch(const char *urlP);
+  // constructor
+  cSatipDiscover();
+  // to prevent copy constructor and assignment
+  cSatipDiscover(const cSatipDiscover&);
+  cSatipDiscover& operator=(const cSatipDiscover&);
+
+protected:
+  virtual void Action(void);
+
+public:
+  static cSatipDiscover *GetInstance(void);
+  static bool Initialize(cSatipDiscoverServers *serversP);
+  static void Destroy(void);
+  virtual ~cSatipDiscover();
+  void TriggerScan(void) { probeIntervalM.Set(0); }
+  int GetServerCount(void);
+  cSatipServer *GetServer(int sourceP, int transponderP = 0, int systemP = -1);
+  cSatipServer *GetServer(cSatipServer *serverP);
+  cSatipServers *GetServers(void);
+  cString GetServerString(cSatipServer *serverP);
+  void SetTransponder(cSatipServer *serverP, int transponderP);
+  void UseServer(cSatipServer *serverP, bool onOffP);
+  cString GetServerList(void);
+  int NumProvidedSystems(void);
+
+  // for internal discover interface
+public:
+  virtual void SetUrl(const char *urlP);
+};
+
+#endif // __SATIP_DISCOVER_H
diff --git a/discoverif.h b/discoverif.h
new file mode 100644
index 0000000..b6218c5
--- /dev/null
+++ b/discoverif.h
@@ -0,0 +1,22 @@
+/*
+ * discoverif.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_DISCOVERIF_H
+#define __SATIP_DISCOVERIF_H
+
+class cSatipDiscoverIf {
+public:
+  cSatipDiscoverIf() {}
+  virtual ~cSatipDiscoverIf() {}
+  virtual void SetUrl(const char *urlP) = 0;
+
+private:
+  cSatipDiscoverIf(const cSatipDiscoverIf&);
+  cSatipDiscoverIf& operator=(const cSatipDiscoverIf&);
+};
+
+#endif // __SATIP_DISCOVERIF_H
diff --git a/log.h b/log.h
new file mode 100644
index 0000000..4d5ee67
--- /dev/null
+++ b/log.h
@@ -0,0 +1,49 @@
+/*
+ * log.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_LOG_H
+#define __SATIP_LOG_H
+
+#include "config.h"
+
+#define error(x...)   esyslog("SATIP-ERROR: " x)
+#define info(x...)    isyslog("SATIP: " x)
+// 0x0001: Generic call stack
+#define debug1(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug1)  ? dsyslog("SATIP1: " x)  : void() )
+// 0x0002: CURL data flow
+#define debug2(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug2)  ? dsyslog("SATIP2: " x)  : void() )
+// 0x0004: Data parsing
+#define debug3(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug3)  ? dsyslog("SATIP3: " x)  : void() )
+// 0x0008: Tuner state machine
+#define debug4(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug4)  ? dsyslog("SATIP4: " x)  : void() )
+// 0x0010: RTSP responses
+#define debug5(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug5)  ? dsyslog("SATIP5: " x)  : void() )
+// 0x0020: RTP throughput performance
+#define debug6(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug6)  ? dsyslog("SATIP6: " x)  : void() )
+// 0x0040: RTP packet internals
+#define debug7(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug7)  ? dsyslog("SATIP7: " x)  : void() )
+// 0x0080: Section filtering
+#define debug8(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug8)  ? dsyslog("SATIP8: " x)  : void() )
+// 0x0100: Channel switching
+#define debug9(x...)  void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug9)  ? dsyslog("SATIP9: " x)  : void() )
+// 0x0200: RTCP packets
+#define debug10(x...) void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug10) ? dsyslog("SATIP10: " x) : void() )
+// 0x0400: CI
+#define debug11(x...) void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug11) ? dsyslog("SATIP11: " x) : void() )
+// 0x0800: Discovery
+#define debug12(x...) void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug12) ? dsyslog("SATIP12: " x) : void() )
+// 0x1000: Pids
+#define debug13(x...) void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug13) ? dsyslog("SATIP13: " x) : void() )
+// 0x2000: TBD
+#define debug14(x...) void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug14) ? dsyslog("SATIP14: " x) : void() )
+// 0x4000: TBD
+#define debug15(x...) void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug15) ? dsyslog("SATIP15: " x) : void() )
+// 0x8000; Extra call stack
+#define debug16(x...) void( SatipConfig.IsTraceMode(cSatipConfig::eTraceModeDebug16) ? dsyslog("SATIP16: " x) : void() )
+
+#endif // __SATIP_LOG_H
+
diff --git a/msearch.c b/msearch.c
new file mode 100644
index 0000000..9f28d32
--- /dev/null
+++ b/msearch.c
@@ -0,0 +1,104 @@
+/*
+ * msearch.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "config.h"
+#include "common.h"
+#include "discover.h"
+#include "log.h"
+#include "poller.h"
+#include "msearch.h"
+
+const char *cSatipMsearch::bcastAddressS = "239.255.255.250";
+const char *cSatipMsearch::bcastMessageS = "M-SEARCH * HTTP/1.1\r\n"                  \
+                                           "HOST: 239.255.255.250:1900\r\n"           \
+                                           "MAN: \"ssdp:discover\"\r\n"               \
+                                           "ST: urn:ses-com:device:SatIPServer:1\r\n" \
+                                           "MX: 2\r\n\r\n";
+
+cSatipMsearch::cSatipMsearch(cSatipDiscoverIf &discoverP)
+: discoverM(discoverP),
+  bufferLenM(eProbeBufferSize),
+  bufferM(MALLOC(unsigned char, bufferLenM)),
+  registeredM(false)
+{
+  if (bufferM)
+     memset(bufferM, 0, bufferLenM);
+  else
+     error("Cannot create Msearch buffer!");
+  if (!Open(eDiscoveryPort))
+     error("Cannot open Msearch port!");
+}
+
+cSatipMsearch::~cSatipMsearch()
+{
+}
+
+void cSatipMsearch::Probe(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (!registeredM) {
+     cSatipPoller::GetInstance()->Register(*this);
+     registeredM = true;
+     }
+  Write(bcastAddressS, reinterpret_cast<const unsigned char *>(bcastMessageS), strlen(bcastMessageS));
+}
+
+int cSatipMsearch::GetFd(void)
+{
+  return Fd();
+}
+
+void cSatipMsearch::Process(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  if (bufferM) {
+     int length;
+     while ((length = Read(bufferM, bufferLenM)) > 0) {
+           bufferM[min(length, int(bufferLenM - 1))] = 0;
+           debug12("%s len=%d buf=%s", __PRETTY_FUNCTION__, length, bufferM);
+           bool status = false, valid = false;
+           char *s, *p = reinterpret_cast<char *>(bufferM), *location = NULL;
+           char *r = strtok_r(p, "\r\n", &s);
+           while (r) {
+                 debug12("%s r=%s", __PRETTY_FUNCTION__, r);
+                 // Check the status code
+                 // HTTP/1.1 200 OK
+                 if (!status && startswith(r, "HTTP/1.1 200 OK"))
+                    status = true;
+                 if (status) {
+                    // Check the location data
+                    // LOCATION: http://192.168.0.115:8888/octonet.xml
+                    if (startswith(r, "LOCATION:")) {
+                       location = compactspace(r + 9);
+                       debug1("%s location='%s'", __PRETTY_FUNCTION__, location);
+                       }
+                    // Check the source type
+                    // ST: urn:ses-com:device:SatIPServer:1
+                    else if (startswith(r, "ST:")) {
+                       char *st = compactspace(r + 3);
+                       if (strstr(st, "urn:ses-com:device:SatIPServer:1"))
+                          valid = true;
+                       debug1("%s st='%s'", __PRETTY_FUNCTION__, st);
+                       }
+                    // Check whether all the required data is found
+                    if (valid && !isempty(location)) {
+                       discoverM.SetUrl(location);
+                       break;
+                       }
+                    }
+                 r = strtok_r(NULL, "\r\n", &s);
+                 }
+           }
+     if (errno != EAGAIN && errno != EWOULDBLOCK)
+        error("Error %d reading in %s", errno, *ToString());
+     }
+}
+
+cString cSatipMsearch::ToString(void) const
+{
+  return "MSearch";
+}
diff --git a/msearch.h b/msearch.h
new file mode 100644
index 0000000..b27a008
--- /dev/null
+++ b/msearch.h
@@ -0,0 +1,40 @@
+/*
+ * msearch.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_MSEARCH_H_
+#define __SATIP_MSEARCH_H_
+
+#include "discoverif.h"
+#include "socket.h"
+#include "pollerif.h"
+
+class cSatipMsearch : public cSatipSocket, public cSatipPollerIf {
+private:
+  enum {
+    eProbeBufferSize  = 1024, // in bytes
+    eDiscoveryPort    = 1900,
+  };
+  static const char *bcastAddressS;
+  static const char *bcastMessageS;
+  cSatipDiscoverIf &discoverM;
+  unsigned int bufferLenM;
+  unsigned char *bufferM;
+  bool registeredM;
+
+public:
+  cSatipMsearch(cSatipDiscoverIf &discoverP);
+  virtual ~cSatipMsearch();
+  void Probe(void);
+
+  // for internal poller interface
+public:
+  virtual int GetFd(void);
+  virtual void Process(void);
+  virtual cString ToString(void) const;
+};
+
+#endif /* __SATIP_MSEARCH_H_ */
diff --git a/param.c b/param.c
new file mode 100644
index 0000000..d999a8f
--- /dev/null
+++ b/param.c
@@ -0,0 +1,200 @@
+/*
+ * param.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <ctype.h>
+#include <vdr/dvbdevice.h>
+#include "common.h"
+#include "param.h"
+
+// --- cSatipParameterMaps ----------------------------------------------------
+
+struct tSatipParameterMap {
+  int         driverValue;
+  const char *satipString;
+};
+
+static const tSatipParameterMap SatipBandwidthValues[] = {
+  {  5000000, "&bw=5"     },
+  {  6000000, "&bw=6"     },
+  {  7000000, "&bw=7"     },
+  {  8000000, "&bw=8"     },
+  { 10000000, "&bw=10"    },
+  {  1712000, "&bw=1.712" },
+  {       -1, NULL        }
+};
+
+static const tSatipParameterMap SatipPilotValues[] = {
+  {  PILOT_OFF, "&plts=off" },
+  {   PILOT_ON, "&plts=on"  },
+  { PILOT_AUTO, ""          },
+  {         -1, NULL        }
+};
+
+static const tSatipParameterMap SatipSisoMisoValues[] = {
+  {   0, "&sm=0" },
+  {   1, "&sm=1" },
+  {  -1, NULL    }
+};
+
+static const tSatipParameterMap SatipCodeRateValues[] = {
+  { FEC_NONE, ""         },
+  {  FEC_1_2, "&fec=12"  },
+  {  FEC_2_3, "&fec=23"  },
+  {  FEC_3_4, "&fec=34"  },
+  {  FEC_3_5, "&fec=35"  },
+  {  FEC_4_5, "&fec=45"  },
+  {  FEC_5_6, "&fec=56"  },
+  {  FEC_6_7, "&fec=67"  },
+  {  FEC_7_8, "&fec=78"  },
+  {  FEC_8_9, "&fec=89"  },
+  { FEC_9_10, "&fec=910" },
+  { FEC_AUTO, ""         },
+  {       -1, NULL       }
+};
+
+static const tSatipParameterMap SatipModulationValues[] = {
+  {     QPSK, "&mtype=qpsk"   },
+  {    PSK_8, "&mtype=8psk"   },
+  {   QAM_16, "&mtype=16qam"  },
+  {   QAM_64, "&mtype=64qam"  },
+  {  QAM_128, "&mtype=128qam" },
+  {  QAM_256, "&mtype=256qam" },
+  { QAM_AUTO, ""              },
+  {       -1, NULL            }
+};
+
+static const tSatipParameterMap SatipSystemValuesSat[] = {
+  {   0, "&msys=dvbs"  },
+  {   1, "&msys=dvbs2" },
+  {  -1, NULL          }
+};
+
+static const tSatipParameterMap SatipSystemValuesTerrestrial[] = {
+  {   0, "&msys=dvbt"  },
+  {   1, "&msys=dvbt2" },
+  {  -1, NULL          }
+};
+
+static const tSatipParameterMap SatipSystemValuesCable[] = {
+  {   0, "&msys=dvbc"  },
+  {   1, "&msys=dvbc2" },
+  {  -1, NULL          }
+};
+
+static const tSatipParameterMap SatipTransmissionValues[] = {
+  {   TRANSMISSION_MODE_1K, "&tmode=1k"  },
+  {   TRANSMISSION_MODE_2K, "&tmode=2k"  },
+  {   TRANSMISSION_MODE_4K, "&tmode=4k"  },
+  {   TRANSMISSION_MODE_8K, "&tmode=8k"  },
+  {  TRANSMISSION_MODE_16K, "&tmode=16k" },
+  {  TRANSMISSION_MODE_32K, "&tmode=32k" },
+  { TRANSMISSION_MODE_AUTO, ""           },
+  {                     -1, NULL         }
+};
+
+static const tSatipParameterMap SatipGuardValues[] = {
+  {    GUARD_INTERVAL_1_4, "&gi=14"    },
+  {    GUARD_INTERVAL_1_8, "&gi=18"    },
+  {   GUARD_INTERVAL_1_16, "&gi=116"   },
+  {   GUARD_INTERVAL_1_32, "&gi=132"   },
+  {  GUARD_INTERVAL_1_128, "&gi=1128"  },
+  { GUARD_INTERVAL_19_128, "&gi=19128" },
+  { GUARD_INTERVAL_19_256, "&gi=19256" },
+  {   GUARD_INTERVAL_AUTO, ""          },
+  {                    -1, NULL        }
+};
+
+static const tSatipParameterMap SatipRollOffValues[] = {
+  { ROLLOFF_AUTO, ""         },
+  {   ROLLOFF_20, "&ro=0.20" },
+  {   ROLLOFF_25, "&ro=0.25" },
+  {   ROLLOFF_35, "&ro=0.35" },
+  {           -1, NULL       }
+};
+
+static const tSatipParameterMap SatipInversionValues[] = {
+  { INVERSION_AUTO, ""           },
+  {  INVERSION_OFF, "&specinv=0" },
+  {   INVERSION_ON, "&specinv=1" },
+  {             -1, NULL         }
+};
+
+static int SatipUserIndex(int valueP, const tSatipParameterMap *mapP)
+{
+  const tSatipParameterMap *map = mapP;
+  while (map && map->driverValue != -1) {
+        if (map->driverValue == valueP)
+           return map - mapP;
+        map++;
+        }
+  return -1;
+}
+
+static int PrintUrlString(char *bufP, int lenP, int valueP, const tSatipParameterMap *mapP)
+{
+  int n = SatipUserIndex(valueP, mapP);
+  return ((n >= 0) && (lenP > 0)) ? snprintf(bufP, lenP, "%s", mapP[n].satipString) : 0;
+}
+
+cString GetTransponderUrlParameters(const cChannel *channelP)
+{
+  if (channelP) {
+     char buffer[255];
+     cDvbTransponderParameters dtp(channelP->Parameters());
+     int DataSlice = 0;
+     int C2TuningFrequencyType = 0;
+#if defined(APIVERSNUM) && APIVERSNUM < 20106
+     int Pilot = PILOT_AUTO;
+     int T2SystemId = 0;
+     int SisoMiso = 0;
+#else
+     int Pilot = dtp.Pilot();
+     int T2SystemId = dtp.T2SystemId();
+     int SisoMiso = dtp.SisoMiso();
+#endif
+     float freq = channelP->Frequency();
+     char type = cSource::ToChar(channelP->Source());
+     cSource *source = Sources.Get(channelP->Source());
+     int src = (strchr("S", type) && source) ? atoi(source->Description()) : 1;
+     char *q = buffer;
+     *q = 0;
+     // Scale down frequencies to MHz
+     while (freq > 20000L)
+           freq /= 1000L;
+#define ST(s) if (strchr(s, type) && (strchr(s, '0' + dtp.System() + 1) || strchr(s, '*')))
+#define STBUFLEFT (sizeof(buffer) - (q - buffer))
+                q += snprintf(q,       STBUFLEFT, "freq=%s",          *dtoa(freq, "%lg"));
+     ST(" S *") q += snprintf(q,       STBUFLEFT, "&src=%d",          ((src > 0) && (src <= 255)) ? src : 1);
+     ST(" S *") q += snprintf(q,       STBUFLEFT, "&sr=%d",           channelP->Srate());
+     ST("C  1") q += snprintf(q,       STBUFLEFT, "&sr=%d",           channelP->Srate());
+     ST(" S *") q += snprintf(q,       STBUFLEFT, "&pol=%c",          tolower(dtp.Polarization()));
+     ST("C T2") q += snprintf(q,       STBUFLEFT, "&plp=%d",          dtp.StreamId());
+     ST("  T2") q += snprintf(q,       STBUFLEFT, "&t2id=%d",         T2SystemId);
+     ST("C  2") q += snprintf(q,       STBUFLEFT, "&c2tft=%d",        C2TuningFrequencyType);
+     ST("C  2") q += snprintf(q,       STBUFLEFT, "&ds=%d",           DataSlice);
+     ST("C  1") q += PrintUrlString(q, STBUFLEFT, dtp.Inversion(),    SatipInversionValues);
+     ST("  T2") q += PrintUrlString(q, STBUFLEFT, SisoMiso,           SatipSisoMisoValues);
+     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Bandwidth(),    SatipBandwidthValues);
+     ST("C  2") q += PrintUrlString(q, STBUFLEFT, dtp.Bandwidth(),    SatipBandwidthValues);
+     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Guard(),        SatipGuardValues);
+     ST("CST*") q += PrintUrlString(q, STBUFLEFT, dtp.CoderateH(),    SatipCodeRateValues);
+     ST(" S 2") q += PrintUrlString(q, STBUFLEFT, Pilot,              SatipPilotValues);
+     ST(" S 2") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
+     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
+     ST("C  1") q += PrintUrlString(q, STBUFLEFT, dtp.Modulation(),   SatipModulationValues);
+     ST(" S 2") q += PrintUrlString(q, STBUFLEFT, dtp.RollOff(),      SatipRollOffValues);
+     ST(" S *") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesSat);
+     ST("C  *") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesCable);
+     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.System(),       SatipSystemValuesTerrestrial);
+     ST("  T*") q += PrintUrlString(q, STBUFLEFT, dtp.Transmission(), SatipTransmissionValues);
+     if ((channelP->Rid() % 100) > 0)
+                q += snprintf(q,       STBUFLEFT, "&fe=%d",           channelP->Rid() % 100);
+#undef ST
+     return buffer;
+     }
+  return NULL;
+}
diff --git a/param.h b/param.h
new file mode 100644
index 0000000..ae68578
--- /dev/null
+++ b/param.h
@@ -0,0 +1,15 @@
+/*
+ * param.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_PARAM_H
+#define __SATIP_PARAM_H
+
+#include "common.h"
+
+cString GetTransponderUrlParameters(const cChannel *channelP);
+
+#endif // __SATIP_PARAM_H
diff --git a/po/ca_ES.po b/po/ca_ES.po
new file mode 100644
index 0000000..05c01a1
--- /dev/null
+++ b/po/ca_ES.po
@@ -0,0 +1,185 @@
+# VDR plugin language source file.
+# Copyright (C) 2007-2015 Rolf Ahrenberg
+# This file is distributed under the same license as the satip package.
+# Gabriel Bonich, 2014-2015
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-satip 1.0.2\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2015-01-18 01:18+0200\n"
+"PO-Revision-Date: 2015-01-18 01:18+0200\n"
+"Last-Translator: Gabriel Bonich <gbonich at gmail.com>\n"
+"Language-Team: Catalan <vdr at linuxtv.org>\n"
+"Language: ca\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "PAT (0x00)"
+msgstr "PAT (0x00)"
+
+msgid "NIT (0x40)"
+msgstr "NIT (0x40)"
+
+msgid "SDT (0x42)"
+msgstr "SDT (0x42)"
+
+msgid "EIT (0x4E/0x4F/0x5X/0x6X)"
+msgstr "EIT (0x4E/0x4F/0x5X/0x6X)"
+
+msgid "TDT (0x70)"
+msgstr "TDT (0x70)"
+
+msgid "SAT>IP information not available!"
+msgstr "SAT>IP Informació no disponible!"
+
+msgid "SAT>IP Devices"
+msgstr "SAT>IP Dispositius"
+
+msgid "SAT>IP Server"
+msgstr "SAT>IP Server"
+
+msgid "Address"
+msgstr "Adressa"
+
+msgid "Model"
+msgstr "Model"
+
+msgid "Description"
+msgstr "Descripció"
+
+msgid "CI extension"
+msgstr "Extensió CI"
+
+msgid "Creation date"
+msgstr "Creació de data"
+
+msgid "SAT>IP Device Status"
+msgstr "SAT>IP Estat Dispositiu"
+
+msgid "SAT>IP Information"
+msgstr "SAT>IP Informació"
+
+msgid "General"
+msgstr "General"
+
+msgid "Pids"
+msgstr "Pids"
+
+msgid "Filters"
+msgstr "Filtres"
+
+msgid "Bits/bytes"
+msgstr "Bits/Bytes"
+
+msgid "off"
+msgstr "Apagat"
+
+msgid "low"
+msgstr "Baix"
+
+msgid "normal"
+msgstr "Normal"
+
+msgid "high"
+msgstr "Alt"
+
+msgid "Button$Devices"
+msgstr "Dispositius"
+
+msgid "Operating mode"
+msgstr "Mode de operació"
+
+msgid ""
+"Define the used operating mode for all SAT>IP devices:\n"
+"\n"
+"off - devices are disabled\n"
+"low - devices are working at the lowest priority\n"
+"normal - devices are working within normal parameters\n"
+"high - devices are working at the highest priority"
+msgstr ""
+"Defineig la manera de operar els Dispositius SAT>IP:\n"
+"\n"
+"Apagat - Dispositius desactivats\n"
+"Baix - Dispositius treballan a baixa prioritat\n"
+"Normal - Dispositius treballan en parametres normals\n"
+"Alta - Dispositius treballan a prioritat Alta"
+
+msgid "Enable CI extension"
+msgstr "Habilita la extenció CI"
+
+msgid ""
+"Define whether a CI extension shall be used.\n"
+"\n"
+"This setting enables integrated CI/CAM handling found in some SAT>IP hardware (e.g. Digital Devices OctopusNet)."
+msgstr ""
+"Definir si s'utilitzarà una extensió de CI.\n"
+"\n"
+"Aquesta configuració permet utilitzar CI/CAM integrat que es troba en alguns equips SAT>IP (ex. Digital Devices OctopusNet)."
+
+msgid "CI/CAM"
+msgstr "CI/CAM"
+
+msgid ""
+"Define a desired CAM type for the CI slot.\n"
+"\n"
+"The '---' option lets SAT>IP hardware do the auto-selection."
+msgstr ""
+"Definir quin tipus de CAM vols per a la ranura CI.\n"
+"\n"
+"L'opció '---' permet l'equip SAT>IP fer la selecció automàtica."
+
+msgid "Enable EPG scanning"
+msgstr "Activa Escanneig EPG"
+
+msgid ""
+"Define whether the EPG background scanning shall be used.\n"
+"\n"
+"This setting disables the automatic EIT scanning functionality for all SAT>IP devices."
+msgstr ""
+"Definir si s'utilitzarà l'anàlisi en segon pla del EPG.\n"
+"\n"
+"Aquesta configuració desactiva la funcionalitat d'escaneig EIT automàtica per a tots els dispositius SAT>IP."
+
+msgid "Disabled sources"
+msgstr "Desactiva entrades"
+
+msgid "none"
+msgstr "no"
+
+msgid ""
+"Define number of sources to be disabled.\n"
+"\n"
+"SAT>IP servers might not have all satellite positions available and such sources can be blacklisted here."
+msgstr ""
+"Definir nombre de entrades que es desactiven.\n"
+"\n"
+"SAT>IP els servidors podrien no tenir totes les posicions dels satèl·lits disponibles i aquestes entrades poden ser la llista negra."
+
+msgid "Define a source to be blacklisted."
+msgstr "Definir una entrada a la llista negra"
+
+msgid "Disabled filters"
+msgstr "Desactiva filtres"
+
+msgid ""
+"Define number of section filters to be disabled.\n"
+"\n"
+"Certain section filters might cause some unwanted behaviour to VDR such as time being falsely synchronized. By blacklisting the filters here, useful section data can be left intact for VDR to process."
+msgstr ""
+"Defineix el numero de filtres de secció que seran deshabilitats.\n"
+"\n"
+"Alguns filtres de secció podrien provocar un comportament no desitjat a VDR, com sincronitzar malament l'hora. Posant aquests filtres a la llista negra, aqui, la secció de dades útil pot ser deixada intacta pel seu procès en el VDR."
+
+msgid "Filter"
+msgstr "Filtra"
+
+msgid "Define an ill-behaving filter to be blacklisted."
+msgstr "Definir un filtre mal comportar a la llista negra."
+
+msgid "Active SAT>IP servers:"
+msgstr "Activa SAT>IP servers:"
+
+msgid "Help"
+msgstr "Ajuda"
diff --git a/po/de_DE.po b/po/de_DE.po
new file mode 100644
index 0000000..0489acf
--- /dev/null
+++ b/po/de_DE.po
@@ -0,0 +1,185 @@
+# VDR plugin language source file.
+# Copyright (C) 2007-2015 Rolf Ahrenberg
+# This file is distributed under the same license as the satip package.
+# Frank Neumann, 2014-2015
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-satip 1.0.2\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2015-01-18 01:18+0200\n"
+"PO-Revision-Date: 2015-01-18 01:18+0200\n"
+"Last-Translator: Frank Neumann <fnu at yavdr.org>\n"
+"Language-Team: German <vdr at linuxtv.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "PAT (0x00)"
+msgstr "PAT (0x00)"
+
+msgid "NIT (0x40)"
+msgstr "NIT (0x40)"
+
+msgid "SDT (0x42)"
+msgstr "SDT (0x42)"
+
+msgid "EIT (0x4E/0x4F/0x5X/0x6X)"
+msgstr "EIT (0x4E/0x4F/0x5X/0x6X)"
+
+msgid "TDT (0x70)"
+msgstr "TDT (0x70)"
+
+msgid "SAT>IP information not available!"
+msgstr "Keine SAT>IP Informationen verfügbar!"
+
+msgid "SAT>IP Devices"
+msgstr "SAT>IP Geräte"
+
+msgid "SAT>IP Server"
+msgstr "SAT>IP Server"
+
+msgid "Address"
+msgstr "Adresse"
+
+msgid "Model"
+msgstr "Modell"
+
+msgid "Description"
+msgstr "Beschreibung"
+
+msgid "CI extension"
+msgstr "CI Erweiterung"
+
+msgid "Creation date"
+msgstr "Zeitpunkt der Erstellung"
+
+msgid "SAT>IP Device Status"
+msgstr "SAT>IP Geräte Status"
+
+msgid "SAT>IP Information"
+msgstr "SAT>IP Informationen"
+
+msgid "General"
+msgstr "Allgemein"
+
+msgid "Pids"
+msgstr "Pids"
+
+msgid "Filters"
+msgstr "Filter"
+
+msgid "Bits/bytes"
+msgstr "Bits/Bytes"
+
+msgid "off"
+msgstr "aus"
+
+msgid "low"
+msgstr "niedrig"
+
+msgid "normal"
+msgstr "normal"
+
+msgid "high"
+msgstr "hoch"
+
+msgid "Button$Devices"
+msgstr "Geräte"
+
+msgid "Operating mode"
+msgstr "Betriebsmodus"
+
+msgid ""
+"Define the used operating mode for all SAT>IP devices:\n"
+"\n"
+"off - devices are disabled\n"
+"low - devices are working at the lowest priority\n"
+"normal - devices are working within normal parameters\n"
+"high - devices are working at the highest priority"
+msgstr ""
+"Bestimme den Betriebsmodus für alle SAT>IP Geräte:\n"
+"\n"
+"aus - Geräte sind abgeschaltet\n"
+"niedrig - Geräte arbeiten mit geringster Priorität\n"
+"normal - Geräte arbeiten innerhalb der gewöhnlichen Parameter\n"
+"hoch - Geräte arbeiten mit höchste Priorität"
+
+msgid "Enable CI extension"
+msgstr "Aktiviere CI Erweiterung"
+
+msgid ""
+"Define whether a CI extension shall be used.\n"
+"\n"
+"This setting enables integrated CI/CAM handling found in some SAT>IP hardware (e.g. Digital Devices OctopusNet)."
+msgstr ""
+"Legt fest ob eine CI Erweiterung genutzt werden soll.\n"
+"\n"
+"Diese Einstellung aktiviert die Nutzung des integrierten CI/CAM einiger SAT>IP Geräte (z.B. Digital Devices OctopusNet)."
+
+msgid "CI/CAM"
+msgstr "CI/CAM"
+
+msgid ""
+"Define a desired CAM type for the CI slot.\n"
+"\n"
+"The '---' option lets SAT>IP hardware do the auto-selection."
+msgstr ""
+"Bestimmt welcher CI Einschub für ein CAM genutzt werden soll.\n"
+"\n"
+"Die Option '---' überlässt der SAT>IP Hardware die automatische Auswahl."
+
+msgid "Enable EPG scanning"
+msgstr "Aktiviere EPG Aktualisierung"
+
+msgid ""
+"Define whether the EPG background scanning shall be used.\n"
+"\n"
+"This setting disables the automatic EIT scanning functionality for all SAT>IP devices."
+msgstr ""
+"Legt fest ob EPG im Hintergrund aktualisiert werden soll oder nicht.\n"
+"\n"
+"Diese Einstellung schaltet die automatische EIT Aktualisierung für alle SAT>IP Geräte."
+
+msgid "Disabled sources"
+msgstr "Deaktivierte Quellen"
+
+msgid "none"
+msgstr "keine"
+
+msgid ""
+"Define number of sources to be disabled.\n"
+"\n"
+"SAT>IP servers might not have all satellite positions available and such sources can be blacklisted here."
+msgstr ""
+"Legt die Anzahl der deaktivierten Quellen fest.\n"
+"\n"
+"Für einige SAT>IP server sind nicht alle Satellitenpositionen verfügbar, nicht verfügbare Quellen können hier ausgeblendet werden"
+
+msgid "Define a source to be blacklisted."
+msgstr "Bestimme eine Quelle, die ausgeblendet wird"
+
+msgid "Disabled filters"
+msgstr "Deaktivierte Filter"
+
+msgid ""
+"Define number of section filters to be disabled.\n"
+"\n"
+"Certain section filters might cause some unwanted behaviour to VDR such as time being falsely synchronized. By blacklisting the filters here, useful section data can be left intact for VDR to process."
+msgstr ""
+"Bestimme die Anzahl der Abschnittsfilter die deaktiviert werden sollen.\n"
+"\n"
+"Bestimmte Abschnittsfilter können unerwünschtes Verhalten mit VDR, z.B. falsche Zeit-Synchronisation, verursachen. Durch das Ausblenden einzelner Filter können nützliche Daten dieser Abschnitte für den VDR erhalten werden."
+
+msgid "Filter"
+msgstr "Filter"
+
+msgid "Define an ill-behaving filter to be blacklisted."
+msgstr "Bestimme einen fehlerhaften Filter der ausgeblendet werden soll."
+
+msgid "Active SAT>IP servers:"
+msgstr "Aktive SAT>IP Server:"
+
+msgid "Help"
+msgstr "Hilfe"
diff --git a/po/es_ES.po b/po/es_ES.po
new file mode 100644
index 0000000..2dcf9eb
--- /dev/null
+++ b/po/es_ES.po
@@ -0,0 +1,185 @@
+# VDR plugin language source file.
+# Copyright (C) 2007-2015 Rolf Ahrenberg
+# This file is distributed under the same license as the satip package.
+# Gabriel Bonich, 2014-2015
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-satip 1.0.2\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2015-01-18 01:18+0200\n"
+"PO-Revision-Date: 2015-01-18 01:18+0200\n"
+"Last-Translator: Gabriel Bonich <gbonich at gmail.com>\n"
+"Language-Team: Spanish <vdr at linuxtv.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "PAT (0x00)"
+msgstr "PAT (0x00)"
+
+msgid "NIT (0x40)"
+msgstr "NIT (0x40)"
+
+msgid "SDT (0x42)"
+msgstr "SDT (0x42)"
+
+msgid "EIT (0x4E/0x4F/0x5X/0x6X)"
+msgstr "EIT (0x4E/0x4F/0x5X/0x6X)"
+
+msgid "TDT (0x70)"
+msgstr "TDT (0x70)"
+
+msgid "SAT>IP information not available!"
+msgstr "SAT>IP Información no disponible!"
+
+msgid "SAT>IP Devices"
+msgstr "SAT>IP Dispositivos"
+
+msgid "SAT>IP Server"
+msgstr "SAT>IP Server"
+
+msgid "Address"
+msgstr "Dirección"
+
+msgid "Model"
+msgstr "Modelo"
+
+msgid "Description"
+msgstr "Descripción"
+
+msgid "CI extension"
+msgstr "Extensión CI"
+
+msgid "Creation date"
+msgstr "Fecha creación"
+
+msgid "SAT>IP Device Status"
+msgstr "SAT>IP Estado del Dispositivo"
+
+msgid "SAT>IP Information"
+msgstr "SAT>IP Información"
+
+msgid "General"
+msgstr "General"
+
+msgid "Pids"
+msgstr "Pids"
+
+msgid "Filters"
+msgstr "Filtros"
+
+msgid "Bits/bytes"
+msgstr "Bits/Bytes"
+
+msgid "off"
+msgstr "Apagado"
+
+msgid "low"
+msgstr "Bajo"
+
+msgid "normal"
+msgstr "Normal"
+
+msgid "high"
+msgstr "Alto"
+
+msgid "Button$Devices"
+msgstr "Dispositivos"
+
+msgid "Operating mode"
+msgstr "Modo de operación"
+
+msgid ""
+"Define the used operating mode for all SAT>IP devices:\n"
+"\n"
+"off - devices are disabled\n"
+"low - devices are working at the lowest priority\n"
+"normal - devices are working within normal parameters\n"
+"high - devices are working at the highest priority"
+msgstr ""
+"Define la manera que trabajan los Dispositivos SAT>IP:\n"
+"\n"
+"Apagat - Dispositivos desactivados\n"
+"Baix - Dispositivos trabajando con prioridad Baja\n"
+"Normal - Dispositivos trabajando con prioridad Normal\n"
+"Alta - Dispositivos trabajando con prioridad Alta"
+
+msgid "Enable CI extension"
+msgstr "Habilitar extensión CI"
+
+msgid ""
+"Define whether a CI extension shall be used.\n"
+"\n"
+"This setting enables integrated CI/CAM handling found in some SAT>IP hardware (e.g. Digital Devices OctopusNet)."
+msgstr ""
+"Definir si se utilizará una extensión de CI.\n"
+"\n"
+"Esto permite la configuración CI/CAM integrado que se encuentra en algunos equipos SAT>IP (ej. Digital Devices OctopusNet)."
+
+msgid "CI/CAM"
+msgstr "CI/CAM"
+
+msgid ""
+"Define a desired CAM type for the CI slot.\n"
+"\n"
+"The '---' option lets SAT>IP hardware do the auto-selection."
+msgstr ""
+"Definir el tipo de CAM para la ranura CI.\n"
+"\n"
+"La opción '---' permite al equipo SAT>IP hacer la selección automática."
+
+msgid "Enable EPG scanning"
+msgstr "Activa Escaneo EPG"
+
+msgid ""
+"Define whether the EPG background scanning shall be used.\n"
+"\n"
+"This setting disables the automatic EIT scanning functionality for all SAT>IP devices."
+msgstr ""
+"Definir si se utilitzará el analisí en segundo plano del EPG.\n"
+"\n"
+"Esta configuración desactiva la funcionalidad del escaneo EIT automática para todos los Dispositivos SAT>IP."
+
+msgid "Disabled sources"
+msgstr "Desactiva fuentes"
+
+msgid "none"
+msgstr "no"
+
+msgid ""
+"Define number of sources to be disabled.\n"
+"\n"
+"SAT>IP servers might not have all satellite positions available and such sources can be blacklisted here."
+msgstr ""
+"Definir número de fuentes desactivadas.\n"
+"\n"
+"SAT>IP servidores que no tenga todas las posiciones de los satélites disponibles y estas se ponen en la lista negra."
+
+msgid "Define a source to be blacklisted."
+msgstr "Define fuentes de la lista negra"
+
+msgid "Disabled filters"
+msgstr "Desactiva filtros"
+
+msgid ""
+"Define number of section filters to be disabled.\n"
+"\n"
+"Certain section filters might cause some unwanted behaviour to VDR such as time being falsely synchronized. By blacklisting the filters here, useful section data can be left intact for VDR to process."
+msgstr ""
+"Define el numero de filtros de sección que seran deshabilitados.\n"
+"\n"
+"Algunos filtros de sección podrian causar un comportamiento no deseado a VDR, como sincronizar mal la hora. Poniendo estos filtros en la lista negra, aqui, la sección de datos útiles se puede dejar intacta para su proceso con el VDR."
+
+msgid "Filter"
+msgstr "Filtra"
+
+msgid "Define an ill-behaving filter to be blacklisted."
+msgstr "Define un filtro para poner en la lista negra."
+
+msgid "Active SAT>IP servers:"
+msgstr "Activa SAT>IP servers:"
+
+msgid "Help"
+msgstr "Ayuda"
diff --git a/po/fi_FI.po b/po/fi_FI.po
new file mode 100644
index 0000000..3694cee
--- /dev/null
+++ b/po/fi_FI.po
@@ -0,0 +1,184 @@
+# VDR plugin language source file.
+# Copyright (C) 2007-2015 Rolf Ahrenberg
+# This file is distributed under the same license as the satip package.
+# Rolf Ahrenberg, 2015
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: vdr-satip 1.0.2\n"
+"Report-Msgid-Bugs-To: <see README>\n"
+"POT-Creation-Date: 2015-01-18 01:18+0200\n"
+"PO-Revision-Date: 2015-01-18 01:18+0200\n"
+"Last-Translator: Rolf Ahrenberg\n"
+"Language-Team: Finnish <vdr at linuxtv.org>\n"
+"Language: fi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "PAT (0x00)"
+msgstr "PAT (0x00)"
+
+msgid "NIT (0x40)"
+msgstr "NIT (0x40)"
+
+msgid "SDT (0x42)"
+msgstr "SDT (0x42)"
+
+msgid "EIT (0x4E/0x4F/0x5X/0x6X)"
+msgstr "EIT (0x4E/0x4F/0x5X/0x6X)"
+
+msgid "TDT (0x70)"
+msgstr "TDT (0x70)"
+
+msgid "SAT>IP information not available!"
+msgstr "SAT>IP-tietoja ei saatavilla!"
+
+msgid "SAT>IP Devices"
+msgstr "SAT>IP-laitteet"
+
+msgid "SAT>IP Server"
+msgstr "SAT>IP-palvelin"
+
+msgid "Address"
+msgstr "Osoite"
+
+msgid "Model"
+msgstr "Malli"
+
+msgid "Description"
+msgstr "Kuvaus"
+
+msgid "CI extension"
+msgstr "CI-laajennos"
+
+msgid "Creation date"
+msgstr "Luontiajankohta"
+
+msgid "SAT>IP Device Status"
+msgstr "SAT>IP-laitteiden tiedot"
+
+msgid "SAT>IP Information"
+msgstr "SAT>IP-tiedot"
+
+msgid "General"
+msgstr "Yleiset"
+
+msgid "Pids"
+msgstr "Pidit"
+
+msgid "Filters"
+msgstr "Suodattimet"
+
+msgid "Bits/bytes"
+msgstr "Bitit/tavut"
+
+msgid "off"
+msgstr "ei käytössä"
+
+msgid "low"
+msgstr "matala"
+
+msgid "normal"
+msgstr "normaali"
+
+msgid "high"
+msgstr "korkea"
+
+msgid "Button$Devices"
+msgstr "Laitteet"
+
+msgid "Operating mode"
+msgstr "Laitteiden toimintatapa"
+
+msgid ""
+"Define the used operating mode for all SAT>IP devices:\n"
+"\n"
+"off - devices are disabled\n"
+"low - devices are working at the lowest priority\n"
+"normal - devices are working within normal parameters\n"
+"high - devices are working at the highest priority"
+msgstr ""
+"Määrittele toimintamoodi SAT>IP-laitteille:\n"
+"ei käytössä - laitteet ovat pois käytöstä\n"
+"matala - laitteet toimivat matalalla prioriteetilla\n"
+"normaali - laitteet toimivat normaalilla prioriteetilla\n"
+"korkea - laitteet toimivat korkealla prioriteetilla"
+
+msgid "Enable CI extension"
+msgstr "Käytä CI-laajennosta"
+
+msgid ""
+"Define whether a CI extension shall be used.\n"
+"\n"
+"This setting enables integrated CI/CAM handling found in some SAT>IP hardware (e.g. Digital Devices OctopusNet)."
+msgstr ""
+"Määrittele CI-laajennoksen käyttöönotto\n"
+"\n"
+"Tällä asetuksella saadaan otettua käyttöön SAT>IP-laitteiden sisäinen CI-paikka (esim. Digital Devices OctopusNet)."
+
+msgid "CI/CAM"
+msgstr "CI/CAM"
+
+msgid ""
+"Define a desired CAM type for the CI slot.\n"
+"\n"
+"The '---' option lets SAT>IP hardware do the auto-selection."
+msgstr ""
+"Määrittele haluttu CAM-tyyppi CI-paikalle.\n"
+"\n"
+"Vaihtoehto '---' antaa SAT>IP-laitteen valita automaattisesti käytetyn CI-paikan."
+
+msgid "Enable EPG scanning"
+msgstr "Käytä ohjelmaoppaan taustapäivitystä"
+
+msgid ""
+"Define whether the EPG background scanning shall be used.\n"
+"\n"
+"This setting disables the automatic EIT scanning functionality for all SAT>IP devices."
+msgstr ""
+"Määrittele ohjelmaoppaan taustapäivityksen olemassaolo.\n"
+"\n"
+"Tällä asetuksella saadaan otettua automaattinen EIT-datan päivitys pois päältä kaikilta SAT>IP-laitteilta."
+
+msgid "Disabled sources"
+msgstr "Käytöstä poistetut lähteet"
+
+msgid "none"
+msgstr "tyhjä"
+
+msgid ""
+"Define number of sources to be disabled.\n"
+"\n"
+"SAT>IP servers might not have all satellite positions available and such sources can be blacklisted here."
+msgstr ""
+"Määrittele käytöstä poistettavien lähteiden lukumäärä.\n"
+"\n"
+"SAT>IP-palvelimilla ei välttämättä ole kaikkia ohjelmalähteitä tarjolla."
+
+msgid "Define a source to be blacklisted."
+msgstr "Määrittele käytöstä"
+
+msgid "Disabled filters"
+msgstr "Käytöstä poistetut suodattimet"
+
+msgid ""
+"Define number of section filters to be disabled.\n"
+"\n"
+"Certain section filters might cause some unwanted behaviour to VDR such as time being falsely synchronized. By blacklisting the filters here, useful section data can be left intact for VDR to process."
+msgstr ""
+"Määrittele käytöstä poistettavien suodattimien lukumäärä sektioille.\n"
+"\n"
+"Tietyt sektiot saattavat aiheuttaa virheellistä toimintaa VDR:ssä, esimerkiksi asettavat väärän kellonajan, ja näiden poistaminen auttaa VDR:ää toimimaan kunnolla jäljelle jäävien sektioiden kanssa."
+
+msgid "Filter"
+msgstr "Suodatin"
+
+msgid "Define an ill-behaving filter to be blacklisted."
+msgstr "Määrittele käytöstä poistettava suodatin, joka lisätään mustalle listalle."
+
+msgid "Active SAT>IP servers:"
+msgstr "Aktiiviset SAT>IP-palvelimet:"
+
+msgid "Help"
+msgstr "Opaste"
diff --git a/poller.c b/poller.c
new file mode 100644
index 0000000..4058321
--- /dev/null
+++ b/poller.c
@@ -0,0 +1,122 @@
+/*
+ * poller.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#define __STDC_FORMAT_MACROS // Required for format specifiers
+#include <inttypes.h>
+#include <sys/epoll.h>
+
+#include "config.h"
+#include "common.h"
+#include "log.h"
+#include "poller.h"
+
+cSatipPoller *cSatipPoller::instanceS = NULL;
+
+cSatipPoller *cSatipPoller::GetInstance(void)
+{
+  if (!instanceS)
+     instanceS = new cSatipPoller();
+  return instanceS;
+}
+
+bool cSatipPoller::Initialize(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (instanceS)
+     instanceS->Activate();
+  return true;
+}
+
+void cSatipPoller::Destroy(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (instanceS)
+     instanceS->Deactivate();
+}
+
+cSatipPoller::cSatipPoller()
+: cThread("SATIP poller"),
+  mutexM(),
+  fdM(epoll_create(eMaxFileDescriptors))
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+}
+
+cSatipPoller::~cSatipPoller()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  Deactivate();
+  cMutexLock MutexLock(&mutexM);
+  close(fdM);
+  // Free allocated memory
+}
+
+void cSatipPoller::Activate(void)
+{
+  // Start the thread
+  Start();
+}
+
+void cSatipPoller::Deactivate(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  if (Running())
+     Cancel(3);
+}
+
+void cSatipPoller::Action(void)
+{
+  debug1("%s Entering", __PRETTY_FUNCTION__);
+  struct epoll_event events[eMaxFileDescriptors];
+  uint64_t maxElapsed = 0;
+  // Increase priority
+  SetPriority(-1);
+  // Do the thread loop
+  while (Running()) {
+        int nfds = epoll_wait(fdM, events, eMaxFileDescriptors, -1);
+        ERROR_IF_FUNC((nfds == -1), "epoll_wait() failed", break, ;);
+        for (int i = 0; i < nfds; ++i) {
+            cSatipPollerIf* poll = reinterpret_cast<cSatipPollerIf *>(events[i].data.ptr);
+            if (poll) {
+               uint64_t elapsed;
+               cTimeMs processing(0);
+               poll->Process();
+               elapsed = processing.Elapsed();
+               if (elapsed > maxElapsed) {
+                  maxElapsed = elapsed;
+                  debug1("%s Processing %s took %" PRIu64 " ms", __PRETTY_FUNCTION__, *(poll->ToString()), maxElapsed);
+                  }
+               }
+           }
+        }
+  debug1("%s Exiting", __PRETTY_FUNCTION__);
+}
+
+bool cSatipPoller::Register(cSatipPollerIf &pollerP)
+{
+  debug1("%s fd=%d", __PRETTY_FUNCTION__, pollerP.GetFd());
+  cMutexLock MutexLock(&mutexM);
+
+  struct epoll_event ev;
+  ev.events = EPOLLIN | EPOLLET;
+  ev.data.ptr = &pollerP;
+  ERROR_IF_RET(epoll_ctl(fdM, EPOLL_CTL_ADD, pollerP.GetFd(), &ev) == -1, "epoll_ctl(EPOLL_CTL_ADD) failed", return false);
+  debug1("%s Added interface fd=%d", __PRETTY_FUNCTION__, pollerP.GetFd());
+
+  return true;
+}
+
+bool cSatipPoller::Unregister(cSatipPollerIf &pollerP)
+{
+  debug1("%s fd=%d", __PRETTY_FUNCTION__, pollerP.GetFd());
+  cMutexLock MutexLock(&mutexM);
+  ERROR_IF_RET((epoll_ctl(fdM, EPOLL_CTL_DEL, pollerP.GetFd(), NULL) == -1), "epoll_ctl(EPOLL_CTL_DEL) failed", return false);
+  debug1("%s Removed interface fd=%d", __PRETTY_FUNCTION__, pollerP.GetFd());
+
+  return true;
+}
diff --git a/poller.h b/poller.h
new file mode 100644
index 0000000..32216e9
--- /dev/null
+++ b/poller.h
@@ -0,0 +1,44 @@
+/*
+ * poller.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_POLLER_H
+#define __SATIP_POLLER_H
+
+#include <vdr/thread.h>
+#include <vdr/tools.h>
+
+#include "pollerif.h"
+
+class cSatipPoller : public cThread {
+private:
+  enum {
+    eMaxFileDescriptors = SATIP_MAX_DEVICES * 2, // Data + Application
+  };
+  static cSatipPoller *instanceS;
+  cMutex mutexM;
+  int fdM;
+  void Activate(void);
+  void Deactivate(void);
+  // constructor
+  cSatipPoller();
+  // to prevent copy constructor and assignment
+  cSatipPoller(const cSatipPoller&);
+  cSatipPoller& operator=(const cSatipPoller&);
+
+protected:
+  virtual void Action(void);
+
+public:
+  static cSatipPoller *GetInstance(void);
+  static bool Initialize(void);
+  static void Destroy(void);
+  virtual ~cSatipPoller();
+  bool Register(cSatipPollerIf &pollerP);
+  bool Unregister(cSatipPollerIf &pollerP);
+};
+
+#endif // __SATIP_POLLER_H
diff --git a/pollerif.h b/pollerif.h
new file mode 100644
index 0000000..79d4a96
--- /dev/null
+++ b/pollerif.h
@@ -0,0 +1,24 @@
+/*
+ * pollerif.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_POLLERIF_H
+#define __SATIP_POLLERIF_H
+
+class cSatipPollerIf {
+public:
+  cSatipPollerIf() {}
+  virtual ~cSatipPollerIf() {}
+  virtual int GetFd(void) = 0;
+  virtual void Process(void) = 0;
+  virtual cString ToString(void) const = 0;
+
+private:
+  cSatipPollerIf(const cSatipPollerIf&);
+  cSatipPollerIf& operator=(const cSatipPollerIf&);
+};
+
+#endif // __SATIP_POLLERIF_H
diff --git a/rtcp.c b/rtcp.c
new file mode 100644
index 0000000..4008633
--- /dev/null
+++ b/rtcp.c
@@ -0,0 +1,100 @@
+/*
+ * rtcp.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "config.h"
+#include "common.h"
+#include "log.h"
+#include "rtcp.h"
+
+cSatipRtcp::cSatipRtcp(cSatipTunerIf &tunerP)
+: tunerM(tunerP),
+  bufferLenM(eApplicationMaxSizeB),
+  bufferM(MALLOC(unsigned char, bufferLenM))
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  if (bufferM)
+     memset(bufferM, 0, bufferLenM);
+  else
+     error("Cannot create RTCP buffer! [device %d]", tunerM.GetId());
+}
+
+cSatipRtcp::~cSatipRtcp()
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  DELETE_POINTER(bufferM);
+}
+
+int cSatipRtcp::GetFd(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  return Fd();
+}
+
+int cSatipRtcp::GetApplicationOffset(int *lengthP)
+{
+  debug16("%s (%d) [device %d]", __PRETTY_FUNCTION__, *lengthP, tunerM.GetId());
+  if (!lengthP)
+     return -1;
+  int offset = 0;
+  int total = *lengthP;
+  while (total > 0) {
+        // Version
+        unsigned int v = (bufferM[offset] >> 6) & 0x03;
+         // Padding
+        //unsigned int p = (bufferM[offset] >> 5) & 0x01;
+        // Subtype
+        //unsigned int st = bufferM[offset] & 0x1F;
+        // Payload type
+        unsigned int pt = bufferM[offset + 1] & 0xFF;
+        // Length
+        unsigned int length = ((bufferM[offset + 2] & 0xFF) << 8) | (bufferM[offset + 3] & 0xFF);
+        // Convert it to bytes
+        length = (length + 1) * 4;
+        // V=2, APP = 204
+        if ((v == 2) && (pt == 204)) {
+           // SSCR/CSCR
+           //unsigned int ssrc = ((bufferM[offset + 4] & 0xFF) << 24) | ((bufferM[offset + 5] & 0xFF) << 16) |
+           //                     ((bufferM[offset + 6] & 0xFF) << 8) | (bufferM[offset + 7] & 0xFF);
+           // Name
+           if ((bufferM[offset +  8] == 'S') && (bufferM[offset +  9] == 'E') &&
+               (bufferM[offset + 10] == 'S') && (bufferM[offset + 11] == '1')) {
+              // Identifier
+              //unsigned int id = ((bufferM[offset + 12] & 0xFF) << 8) | (bufferM[offset + 13] & 0xFF);
+              // String length
+              int string_length = ((bufferM[offset + 14] & 0xFF) << 8) | (bufferM[offset + 15] & 0xFF);
+              if (string_length > 0) {
+                 *lengthP = string_length;
+                 return (offset + 16);
+                 }
+              }
+           }
+        offset += length;
+        total -= length;
+        }
+  *lengthP = 0;
+  return -1;
+}
+
+void cSatipRtcp::Process(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  if (bufferM) {
+     int length;
+     while ((length = Read(bufferM, bufferLenM)) > 0) {
+           int offset = GetApplicationOffset(&length);
+           if (offset >= 0)
+              tunerM.ProcessApplicationData(bufferM + offset, length);
+           }
+     if (errno != EAGAIN && errno != EWOULDBLOCK)
+        error("Error %d reading in %s", errno, *ToString());
+     }
+}
+
+cString cSatipRtcp::ToString(void) const
+{
+  return cString::sprintf("RTCP [device %d]", tunerM.GetId());
+}
diff --git a/rtcp.h b/rtcp.h
new file mode 100644
index 0000000..9b31241
--- /dev/null
+++ b/rtcp.h
@@ -0,0 +1,36 @@
+/*
+ * rtcp.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_RTCP_H_
+#define __SATIP_RTCP_H_
+
+#include "socket.h"
+#include "tunerif.h"
+#include "pollerif.h"
+
+class cSatipRtcp : public cSatipSocket, public cSatipPollerIf {
+private:
+  enum {
+    eApplicationMaxSizeB = 1500,
+  };
+  cSatipTunerIf &tunerM;
+  unsigned int bufferLenM;
+  unsigned char *bufferM;
+  int GetApplicationOffset(int *lengthP);
+
+public:
+  cSatipRtcp(cSatipTunerIf &tunerP);
+  virtual ~cSatipRtcp();
+
+  // for internal poller interface
+public:
+  virtual int GetFd(void);
+  virtual void Process(void);
+  virtual cString ToString(void) const;
+};
+
+#endif /* __SATIP_RTCP_H_ */
diff --git a/rtp.c b/rtp.c
new file mode 100644
index 0000000..3d033f9
--- /dev/null
+++ b/rtp.c
@@ -0,0 +1,151 @@
+/*
+ * rtp.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#define __STDC_FORMAT_MACROS // Required for format specifiers
+#include <inttypes.h>
+
+#include "config.h"
+#include "common.h"
+#include "log.h"
+#include "rtp.h"
+
+cSatipRtp::cSatipRtp(cSatipTunerIf &tunerP)
+: tunerM(tunerP),
+  bufferLenM(eRtpPacketReadCount * eMaxUdpPacketSizeB),
+  bufferM(MALLOC(unsigned char, bufferLenM)),
+  lastErrorReportM(0),
+  packetErrorsM(0),
+  sequenceNumberM(-1)
+{
+  debug1("%s () [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  if (!bufferM)
+     error("Cannot create RTP buffer! [device %d]", tunerM.GetId());
+}
+
+cSatipRtp::~cSatipRtp()
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  DELETE_POINTER(bufferM);
+}
+
+int cSatipRtp::GetFd(void)
+{
+  return Fd();
+}
+
+void cSatipRtp::Close(void)
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+
+  cSatipSocket::Close();
+
+  sequenceNumberM = -1;
+  if (packetErrorsM) {
+     info("Detected %d RTP packet errors [device %d]", packetErrorsM, tunerM.GetId());
+     packetErrorsM = 0;
+     lastErrorReportM = time(NULL);
+     }
+}
+
+int cSatipRtp::GetHeaderLength(unsigned char *bufferP, unsigned int lengthP)
+{
+  debug16("%s (, %d) [device %d]", __PRETTY_FUNCTION__, lengthP, tunerM.GetId());
+  unsigned int headerlen = 0;
+
+  if (lengthP > 0) {
+     if (bufferP[0] == TS_SYNC_BYTE)
+        return headerlen;
+     else if (lengthP > 3) {
+        // http://tools.ietf.org/html/rfc3550
+        // http://tools.ietf.org/html/rfc2250
+        // Version
+        unsigned int v = (bufferP[0] >> 6) & 0x03;
+        // Extension bit
+        unsigned int x = (bufferP[0] >> 4) & 0x01;
+        // CSCR count
+        unsigned int cc = bufferP[0] & 0x0F;
+        // Payload type: MPEG2 TS = 33
+        unsigned int pt = bufferP[1] & 0x7F;
+        if (pt != 33)
+           debug7("%s (%d) Received invalid RTP payload type %d - v=%d [device %d]",
+                    __PRETTY_FUNCTION__, lengthP, pt, v, tunerM.GetId());
+        // Sequence number
+        int seq = ((bufferP[2] & 0xFF) << 8) | (bufferP[3] & 0xFF);
+        if ((((sequenceNumberM + 1) % 0xFFFF) == 0) && (seq == 0xFFFF))
+           sequenceNumberM = -1;
+        else if ((sequenceNumberM >= 0) && (((sequenceNumberM + 1) % 0xFFFF) != seq)) {
+           packetErrorsM++;
+           if (time(NULL) - lastErrorReportM > eReportIntervalS) {
+              info("Detected %d RTP packet errors [device %d]", packetErrorsM, tunerM.GetId());
+              packetErrorsM = 0;
+              lastErrorReportM = time(NULL);
+              }
+           sequenceNumberM = seq;
+           }
+        else
+           sequenceNumberM = seq;
+        // Header length
+        headerlen = (3 + cc) * (unsigned int)sizeof(uint32_t);
+        // Check if extension
+        if (x) {
+           // Extension header length
+           unsigned int ehl = (((bufferP[headerlen + 2] & 0xFF) << 8) | (bufferP[headerlen + 3] & 0xFF));
+           // Update header length
+           headerlen += (ehl + 1) * (unsigned int)sizeof(uint32_t);
+           }
+        // Check for empty payload
+        if (lengthP == headerlen) {
+           debug7("%s (%d) Received empty RTP packet #%d [device %d]", __PRETTY_FUNCTION__, lengthP, seq, tunerM.GetId());
+           headerlen = -1;
+           }
+        // Check that rtp is version 2 and payload contains multiple of TS packet data
+        else if ((v != 2) || (((lengthP - headerlen) % TS_SIZE) != 0) || (bufferP[headerlen] != TS_SYNC_BYTE)) {
+           debug7("%s (%d) Received incorrect RTP packet #%d v=%d len=%d sync=0x%02X [device %d]", __PRETTY_FUNCTION__,
+                   lengthP, seq, v, headerlen, bufferP[headerlen], tunerM.GetId());
+           headerlen = -1;
+           }
+        else
+           debug7("%s (%d) Received RTP packet #%d v=%d len=%d sync=0x%02X [device %d]", __PRETTY_FUNCTION__,
+                   lengthP, seq, v, headerlen, bufferP[headerlen], tunerM.GetId());
+        }
+     }
+
+  return headerlen;
+}
+
+void cSatipRtp::Process(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  if (bufferM) {
+     unsigned int lenMsg[eRtpPacketReadCount];
+     uint64_t elapsed;
+     int count = 0;
+     cTimeMs processing(0);
+
+     do {
+       count = ReadMulti(bufferM, lenMsg, eRtpPacketReadCount, eMaxUdpPacketSizeB);
+       for (int i = 0; i < count; ++i) {
+           unsigned char *p = &bufferM[i * eMaxUdpPacketSizeB];
+           int headerlen = GetHeaderLength(p, lenMsg[i]);
+           if ((headerlen >= 0) && (headerlen < (int)lenMsg[i]))
+              tunerM.ProcessVideoData(p + headerlen, lenMsg[i] - headerlen);
+           }
+       } while (count >= eRtpPacketReadCount);
+
+     if (errno != EAGAIN && errno != EWOULDBLOCK)
+        error("Error %d reading in %s [device %d]", errno, *ToString(), tunerM.GetId());
+
+     elapsed = processing.Elapsed();
+     if (elapsed > 1)
+        debug6("%s %d read(s) took %" PRIu64 " ms [device %d]", __PRETTY_FUNCTION__, count, elapsed, tunerM.GetId());
+     }
+}
+
+cString cSatipRtp::ToString(void) const
+{
+  return cString::sprintf("RTP [device %d]", tunerM.GetId());
+}
diff --git a/rtp.h b/rtp.h
new file mode 100644
index 0000000..5080e37
--- /dev/null
+++ b/rtp.h
@@ -0,0 +1,42 @@
+/*
+ * rtp.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_RTP_H_
+#define __SATIP_RTP_H_
+
+#include "socket.h"
+#include "tunerif.h"
+#include "pollerif.h"
+
+class cSatipRtp : public cSatipSocket, public cSatipPollerIf {
+private:
+  enum {
+    eRtpPacketReadCount = 50,
+    eMaxUdpPacketSizeB  = TS_SIZE * 7 + 12,
+    eReportIntervalS    = 300 // in seconds
+  };
+  cSatipTunerIf &tunerM;
+  unsigned int bufferLenM;
+  unsigned char *bufferM;
+  time_t lastErrorReportM;
+  int packetErrorsM;
+  int sequenceNumberM;
+  int GetHeaderLength(unsigned char *bufferP, unsigned int lengthP);
+
+public:
+  cSatipRtp(cSatipTunerIf &tunerP);
+  virtual ~cSatipRtp();
+  virtual void Close(void);
+
+  // for internal poller interface
+public:
+  virtual int GetFd(void);
+  virtual void Process(void);
+  virtual cString ToString(void) const;
+};
+
+#endif /* __SATIP_RTP_H_ */
diff --git a/rtsp.c b/rtsp.c
new file mode 100644
index 0000000..e437d6b
--- /dev/null
+++ b/rtsp.c
@@ -0,0 +1,335 @@
+/*
+ * rtsp.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#define __STDC_FORMAT_MACROS // Required for format specifiers
+#include <inttypes.h>
+
+#include "config.h"
+#include "common.h"
+#include "log.h"
+#include "rtsp.h"
+
+cSatipRtsp::cSatipRtsp(cSatipTunerIf &tunerP)
+: tunerM(tunerP),
+  modeM(cmUnicast),
+  handleM(NULL),
+  headerListM(NULL)
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  Create();
+}
+
+cSatipRtsp::~cSatipRtsp()
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  Destroy();
+}
+
+size_t cSatipRtsp::HeaderCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP)
+{
+  cSatipRtsp *obj = reinterpret_cast<cSatipRtsp *>(dataP);
+  size_t len = sizeP * nmembP;
+  debug16("%s len=%zu", __PRETTY_FUNCTION__, len);
+
+  char *s, *p = (char *)ptrP;
+  char *r = strtok_r(p, "\r\n", &s);
+
+  while (obj && r) {
+        debug16("%s (%zu): %s", __PRETTY_FUNCTION__, len, r);
+        r = skipspace(r);
+        if (strstr(r, "com.ses.streamID")) {
+           int streamid = -1;
+           if (sscanf(r, "com.ses.streamID:%11d", &streamid) == 1)
+              obj->tunerM.SetStreamId(streamid);
+           }
+        else if (strstr(r, "Session:")) {
+           int timeout = -1;
+           char *session = NULL;
+           if (sscanf(r, "Session:%m[^;];timeout=%11d", &session, &timeout) == 2)
+              obj->tunerM.SetSessionTimeout(skipspace(session), timeout * 1000);
+           else if (sscanf(r, "Session:%m[^;]", &session) == 1)
+              obj->tunerM.SetSessionTimeout(skipspace(session), -1);
+           FREE_POINTER(session);
+           }
+        r = strtok_r(NULL, "\r\n", &s);
+        }
+
+  return len;
+}
+
+size_t cSatipRtsp::WriteCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP)
+{
+  cSatipRtsp *obj = reinterpret_cast<cSatipRtsp *>(dataP);
+  size_t len = sizeP * nmembP;
+  debug16("%s len=%zu", __PRETTY_FUNCTION__, len);
+
+  if (obj && (len > 0))
+     obj->tunerM.ProcessApplicationData((u_char*)ptrP, len);
+
+  return len;
+}
+
+int cSatipRtsp::DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP)
+{
+  cSatipRtsp *obj = reinterpret_cast<cSatipRtsp *>(userPtrP);
+
+  if (obj) {
+     switch (typeP) {
+       case CURLINFO_TEXT:
+            debug2("%s [device %d] RTSP INFO %.*s", __PRETTY_FUNCTION__, obj->tunerM.GetId(), (int)sizeP, dataP);
+            break;
+       case CURLINFO_HEADER_IN:
+            debug2("%s [device %d] RTSP HEAD <<< %.*s", __PRETTY_FUNCTION__, obj->tunerM.GetId(), (int)sizeP, dataP);
+            break;
+       case CURLINFO_HEADER_OUT:
+            debug2("%s [device %d] RTSP HEAD >>>\n%.*s", __PRETTY_FUNCTION__, obj->tunerM.GetId(), (int)sizeP, dataP);
+            break;
+       case CURLINFO_DATA_IN:
+            debug2("%s [device %d] RTSP DATA <<< %.*s", __PRETTY_FUNCTION__, obj->tunerM.GetId(), (int)sizeP, dataP);
+            break;
+       case CURLINFO_DATA_OUT:
+            debug2("%s [device %d] RTSP DATA >>>\n%.*s", __PRETTY_FUNCTION__, obj->tunerM.GetId(), (int)sizeP, dataP);
+            break;
+       default:
+            break;
+       }
+     }
+
+  return 0;
+}
+
+cString cSatipRtsp::RtspUnescapeString(const char *strP)
+{
+  debug1("%s (%s) [device %d]", __PRETTY_FUNCTION__, strP, tunerM.GetId());
+  if (handleM) {
+     char *p = curl_easy_unescape(handleM, strP, 0, NULL);
+     cString s = p;
+     curl_free(p);
+
+     return s;
+     }
+
+  return cString(strP);
+}
+
+void cSatipRtsp::Create(void)
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  if (!handleM)
+     handleM = curl_easy_init();
+
+  if (handleM) {
+     CURLcode res = CURLE_OK;
+
+     // Verbose output
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_VERBOSE, 1L);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGFUNCTION, cSatipRtsp::DebugCallback);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_DEBUGDATA, this);
+
+     // No progress meter and no signaling
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_NOPROGRESS, 1L);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_NOSIGNAL, 1L);
+
+     // Set timeouts
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_TIMEOUT_MS, (long)eConnectTimeoutMs);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_CONNECTTIMEOUT_MS, (long)eConnectTimeoutMs);
+
+     // Set user-agent
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_USERAGENT, *cString::sprintf("vdr-%s/%s (device %d)", PLUGIN_NAME_I18N, VERSION, tunerM.GetId()));
+     }
+}
+
+void cSatipRtsp::Destroy(void)
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  if (handleM) {
+     // Cleanup curl stuff
+     if (headerListM) {
+        curl_slist_free_all(headerListM);
+        headerListM = NULL;
+        }
+     curl_easy_cleanup(handleM);
+     handleM = NULL;
+     }
+}
+
+void cSatipRtsp::Reset(void)
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+  Destroy();
+  Create();
+}
+
+bool cSatipRtsp::Options(const char *uriP)
+{
+  debug1("%s (%s) [device %d]", __PRETTY_FUNCTION__, uriP, tunerM.GetId());
+  bool result = false;
+
+  if (handleM && !isempty(uriP)) {
+     long rc = 0;
+     cTimeMs processing(0);
+     CURLcode res = CURLE_OK;
+
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_URL, uriP);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_OPTIONS);
+     SATIP_CURL_EASY_PERFORM(handleM);
+
+     result = ValidateLatestResponse(&rc);
+     debug5("%s (%s) Response %ld in %" PRIu64 " ms [device %d]", __PRETTY_FUNCTION__, uriP, rc, processing.Elapsed(), tunerM.GetId());
+     }
+
+  return result;
+}
+
+bool cSatipRtsp::Setup(const char *uriP, int rtpPortP, int rtcpPortP)
+{
+  debug1("%s (%s, %d, %d) [device %d]", __PRETTY_FUNCTION__, uriP, rtpPortP, rtcpPortP, tunerM.GetId());
+  bool result = false;
+
+  if (handleM && !isempty(uriP)) {
+     cString transport;
+     long rc = 0;
+     cTimeMs processing(0);
+     CURLcode res = CURLE_OK;
+
+     switch (modeM) {
+       case cmMulticast:
+            // RTP/AVP;multicast;destination=<IP multicast address>;port=<RTP port>-<RTCP port>;ttl=<ttl>
+            transport = cString::sprintf("RTP/AVP;multicast;port=%d-%d", rtpPortP, rtcpPortP);
+            break;
+       default:
+       case cmUnicast:
+            // RTP/AVP;unicast;client_port=<client RTP port>-<client RTCP port>
+            transport = cString::sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPortP, rtcpPortP);
+            break;
+       }
+
+     // Setup media stream
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_TRANSPORT, *transport);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_SETUP);
+     // Set header callback for catching the session and timeout
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_HEADERFUNCTION, cSatipRtsp::HeaderCallback);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEHEADER, this);
+     SATIP_CURL_EASY_PERFORM(handleM);
+     // Session id is now known - disable header parsing
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_HEADERFUNCTION, NULL);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEHEADER, NULL);
+
+     result = ValidateLatestResponse(&rc);
+     debug5("%s (%s, %d, %d) Response %ld in %" PRIu64 " ms [device %d]", __PRETTY_FUNCTION__, uriP, rtpPortP, rtcpPortP, rc, processing.Elapsed(), tunerM.GetId());
+     }
+
+  return result;
+}
+
+bool cSatipRtsp::SetSession(const char *sessionP)
+{
+  debug1("%s (%s) [device %d]", __PRETTY_FUNCTION__, sessionP, tunerM.GetId());
+  if (handleM) {
+     CURLcode res = CURLE_OK;
+
+     debug1("%s: session id quirk enabled [device %d]", __PRETTY_FUNCTION__, tunerM.GetId());
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_SESSION_ID, sessionP);
+     }
+
+  return true;
+}
+
+bool cSatipRtsp::Describe(const char *uriP)
+{
+  debug1("%s (%s) [device %d]", __PRETTY_FUNCTION__, uriP, tunerM.GetId());
+  bool result = false;
+
+  if (handleM && !isempty(uriP)) {
+     long rc = 0;
+     cTimeMs processing(0);
+     CURLcode res = CURLE_OK;
+
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_DESCRIBE);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, cSatipRtsp::WriteCallback);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, this);
+     SATIP_CURL_EASY_PERFORM(handleM);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEFUNCTION, NULL);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_WRITEDATA, NULL);
+
+     result = ValidateLatestResponse(&rc);
+     debug5("%s (%s) Response %ld in %" PRIu64 " ms [device %d]", __PRETTY_FUNCTION__, uriP, rc, processing.Elapsed(), tunerM.GetId());
+     }
+
+  return result;
+}
+
+bool cSatipRtsp::Play(const char *uriP)
+{
+  debug1("%s (%s) [device %d]", __PRETTY_FUNCTION__, uriP, tunerM.GetId());
+  bool result = false;
+
+  if (handleM && !isempty(uriP)) {
+     long rc = 0;
+     cTimeMs processing(0);
+     CURLcode res = CURLE_OK;
+
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_PLAY);
+     SATIP_CURL_EASY_PERFORM(handleM);
+
+     result = ValidateLatestResponse(&rc);
+     debug5("%s (%s) Response %ld in %" PRIu64 " ms [device %d]", __PRETTY_FUNCTION__, uriP, rc, processing.Elapsed(), tunerM.GetId());
+     }
+
+  return result;
+}
+
+bool cSatipRtsp::Teardown(const char *uriP)
+{
+  debug1("%s (%s) [device %d]", __PRETTY_FUNCTION__, uriP, tunerM.GetId());
+  bool result = false;
+
+  if (handleM && !isempty(uriP)) {
+     long rc = 0;
+     cTimeMs processing(0);
+     CURLcode res = CURLE_OK;
+
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_STREAM_URI, uriP);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_REQUEST, (long)CURL_RTSPREQ_TEARDOWN);
+     SATIP_CURL_EASY_PERFORM(handleM);
+
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_CLIENT_CSEQ, 1L);
+     SATIP_CURL_EASY_SETOPT(handleM, CURLOPT_RTSP_SESSION_ID, NULL);
+
+     result = ValidateLatestResponse(&rc);
+     debug5("%s (%s) Response %ld in %" PRIu64 " ms [device %d]", __PRETTY_FUNCTION__, uriP, rc, processing.Elapsed(), tunerM.GetId());
+     }
+
+  return result;
+}
+
+bool cSatipRtsp::ValidateLatestResponse(long *rcP)
+{
+  bool result = false;
+
+  if (handleM) {
+     long rc = 0;
+     CURLcode res = CURLE_OK;
+     SATIP_CURL_EASY_GETINFO(handleM, CURLINFO_RESPONSE_CODE, &rc);
+     if (rc == 200)
+        result = true;
+     else if (rc != 0) {
+        char *url = NULL;
+        SATIP_CURL_EASY_GETINFO(handleM, CURLINFO_EFFECTIVE_URL, &url);
+        error("Detected invalid status code %ld: %s [device %d]", rc, url, tunerM.GetId());
+        }
+     if (rcP)
+        *rcP = rc;
+     }
+  debug1("%s result=%s [device %d]", __PRETTY_FUNCTION__, result ? "ok" : "failed", tunerM.GetId());
+
+  return result;
+}
diff --git a/rtsp.h b/rtsp.h
new file mode 100644
index 0000000..1d1be4d
--- /dev/null
+++ b/rtsp.h
@@ -0,0 +1,58 @@
+/*
+ * rtsp.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_RTSP_H
+#define __SATIP_RTSP_H
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#ifndef CURLOPT_RTSPHEADER
+#error "libcurl is missing required RTSP support"
+#endif
+
+#include "tunerif.h"
+
+class cSatipRtsp {
+private:
+  static size_t HeaderCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP);
+  static size_t WriteCallback(void *ptrP, size_t sizeP, size_t nmembP, void *dataP);
+  static int    DebugCallback(CURL *handleP, curl_infotype typeP, char *dataP, size_t sizeP, void *userPtrP);
+
+  enum {
+    eConnectTimeoutMs = 1500,  // in milliseconds
+  };
+  enum eCommunicationMode { cmUnicast, cmMulticast };
+
+  cSatipTunerIf &tunerM;
+  eCommunicationMode modeM;
+  CURL *handleM;
+  struct curl_slist *headerListM;
+
+  void Create(void);
+  void Destroy(void);
+  bool ValidateLatestResponse(long *rcP);
+
+  // to prevent copy constructor and assignment
+  cSatipRtsp(const cSatipRtsp&);
+  cSatipRtsp& operator=(const cSatipRtsp&);
+
+public:
+  cSatipRtsp(cSatipTunerIf &tunerP);
+  virtual ~cSatipRtsp();
+
+  cString RtspUnescapeString(const char *strP);
+  void Reset(void);
+  bool Options(const char *uriP);
+  bool Setup(const char *uriP, int rtpPortP, int rtcpPortP);
+  bool SetSession(const char *sessionP);
+  bool Describe(const char *uriP);
+  bool Play(const char *uriP);
+  bool Teardown(const char *uriP);
+};
+
+#endif // __SATIP_RTSP_H
diff --git a/satip.c b/satip.c
new file mode 100644
index 0000000..7ae3faa
--- /dev/null
+++ b/satip.c
@@ -0,0 +1,466 @@
+/*
+ * satip.c: A plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <getopt.h>
+#include <vdr/plugin.h>
+#include "common.h"
+#include "config.h"
+#include "device.h"
+#include "discover.h"
+#include "log.h"
+#include "poller.h"
+#include "setup.h"
+
+#if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM < 0x072400
+#warning "CURL version >= 7.36.0 is recommended"
+#endif
+
+#if defined(APIVERSNUM) && APIVERSNUM < 20000
+#error "VDR-2.0.0 API version or greater is required!"
+#endif
+
+#ifndef GITVERSION
+#define GITVERSION ""
+#endif
+
+       const char VERSION[]     = "1.0.2" GITVERSION;
+static const char DESCRIPTION[] = trNOOP("SAT>IP Devices");
+
+class cPluginSatip : public cPlugin {
+private:
+  unsigned int deviceCountM;
+  cSatipDiscoverServers *serversM;
+  void ParseServer(const char *paramP);
+  int ParseCicams(const char *valueP, int *cicamsP);
+  int ParseSources(const char *valueP, int *sourcesP);
+  int ParseFilters(const char *valueP, int *filtersP);
+public:
+  cPluginSatip(void);
+  virtual ~cPluginSatip();
+  virtual const char *Version(void) { return VERSION; }
+  virtual const char *Description(void) { return tr(DESCRIPTION); }
+  virtual const char *CommandLineHelp(void);
+  virtual bool ProcessArgs(int argc, char *argv[]);
+  virtual bool Initialize(void);
+  virtual bool Start(void);
+  virtual void Stop(void);
+  virtual void Housekeeping(void);
+  virtual void MainThreadHook(void);
+  virtual cString Active(void);
+  virtual time_t WakeupTime(void);
+  virtual const char *MainMenuEntry(void) { return NULL; }
+  virtual cOsdObject *MainMenuAction(void);
+  virtual cMenuSetupPage *SetupMenu(void);
+  virtual bool SetupParse(const char *Name, const char *Value);
+  virtual bool Service(const char *Id, void *Data = NULL);
+  virtual const char **SVDRPHelpPages(void);
+  virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
+  };
+
+cPluginSatip::cPluginSatip(void)
+: deviceCountM(1),
+  serversM(NULL)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  // Initialize any member variables here.
+  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
+  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
+}
+
+cPluginSatip::~cPluginSatip()
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  // Clean up after yourself!
+}
+
+const char *cPluginSatip::CommandLineHelp(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Return a string that describes all known command line options.
+  return "  -d <num>, --devices=<number>  set number of devices to be created\n"
+         "  -t <mode>, --trace=<mode>     set the tracing mode\n"
+         "  -s <ipaddr>|<model>|<desc>, --server=<ipaddr1>|<model1>|<desc1>;<ipaddr2>|<model2>|<desc2>\n"
+         "                                define hard-coded SAT>IP server(s)"
+         "  -S, --single                  set the single model server mode on\n"
+         "  -n, --noquirks                disable all the server quirks\n";
+}
+
+bool cPluginSatip::ProcessArgs(int argc, char *argv[])
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Implement command line argument processing here if applicable.
+  static const struct option long_options[] = {
+    { "devices",  required_argument, NULL, 'd' },
+    { "trace",    required_argument, NULL, 't' },
+    { "server",   required_argument, NULL, 's' },
+    { "single",   no_argument,       NULL, 'S' },
+    { "noquirks", no_argument,       NULL, 'n' },
+    { NULL,       no_argument,       NULL,  0  }
+    };
+
+  cString server;
+  int c;
+  while ((c = getopt_long(argc, argv, "d:t:s:Sn", long_options, NULL)) != -1) {
+    switch (c) {
+      case 'd':
+           deviceCountM = strtol(optarg, NULL, 0);
+           break;
+      case 't':
+           SatipConfig.SetTraceMode(strtol(optarg, NULL, 0));
+           break;
+      case 's':
+           server = optarg;
+           break;
+      case 'S':
+           SatipConfig.SetUseSingleModelServers(true);
+           break;
+      case 'n':
+           SatipConfig.SetDisableServerQuirks(true);
+           break;
+      default:
+           return false;
+      }
+    }
+  // this must be done after all parameters are parsed
+  if (!isempty(*server))
+     ParseServer(*server);
+  return true;
+}
+
+bool cPluginSatip::Initialize(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Initialize any background activities the plugin shall perform.
+  if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
+     error("Unable to initialize CURL");
+  cSatipPoller::GetInstance()->Initialize();
+  cSatipDiscover::GetInstance()->Initialize(serversM);
+  return cSatipDevice::Initialize(deviceCountM);
+}
+
+bool cPluginSatip::Start(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Start any background activities the plugin shall perform.
+  curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
+  cString info = cString::sprintf("Using CURL %s", data->version);
+  for (int i = 0; data->protocols[i]; ++i) {
+      // Supported protocols: HTTP(S), RTSP, FILE
+      if (startswith(data->protocols[i], "rtsp"))
+         info = cString::sprintf("%s %s", *info, data->protocols[i]);
+      }
+  info("%s", *info);
+  return true;
+}
+
+void cPluginSatip::Stop(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Stop any background activities the plugin is performing.
+  cSatipDevice::Shutdown();
+  cSatipDiscover::GetInstance()->Destroy();
+  cSatipPoller::GetInstance()->Destroy();
+  curl_global_cleanup();
+}
+
+void cPluginSatip::Housekeeping(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  // Perform any cleanup or other regular tasks.
+}
+
+void cPluginSatip::MainThreadHook(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  // Perform actions in the context of the main program thread.
+  // WARNING: Use with great care - see PLUGINS.html!
+}
+
+cString cPluginSatip::Active(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  // Return a message string if shutdown should be postponed
+  return NULL;
+}
+
+time_t cPluginSatip::WakeupTime(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  // Return custom wakeup time for shutdown script
+  return 0;
+}
+
+cOsdObject *cPluginSatip::MainMenuAction(void)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  // Perform the action when selected from the main VDR menu.
+  return NULL;
+}
+
+cMenuSetupPage *cPluginSatip::SetupMenu(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Return a setup menu in case the plugin supports one.
+  return new cSatipPluginSetup();
+}
+
+void cPluginSatip::ParseServer(const char *paramP)
+{
+  debug1("%s (%s)", __PRETTY_FUNCTION__, paramP);
+  int n = 0;
+  char *s, *p = strdup(paramP);
+  char *r = strtok_r(p, ";", &s);
+  while (r) {
+        r = skipspace(r);
+        debug3("%s server[%d]=%s", __PRETTY_FUNCTION__, n, r);
+        cString serverAddr, serverModel, serverDescription;
+        int n2 = 0;
+        char *s2, *p2 = r;
+        char *r2 = strtok_r(p2, "|", &s2);
+        while (r2) {
+              debug3("%s param[%d]=%s", __PRETTY_FUNCTION__, n2, r2);
+              switch (n2++) {
+                     case 0:
+                          serverAddr = r2;
+                          break;
+                     case 1:
+                          serverModel = r2;
+                          break;
+                     case 2:
+                          serverDescription = r2;
+                          break;
+                     default:
+                          break;
+                     }
+              r2 = strtok_r(NULL, "|", &s2);
+              }
+        if (*serverAddr && *serverModel && *serverDescription) {
+           debug1("%s ipaddr=%s model=%s desc=%s", __PRETTY_FUNCTION__, *serverAddr, *serverModel, *serverDescription);
+           if (!serversM)
+              serversM = new cSatipDiscoverServers();
+           serversM->Add(new cSatipDiscoverServer(*serverAddr, *serverModel, *serverDescription));
+           }
+        ++n;
+        r = strtok_r(NULL, ";", &s);
+        }
+  FREE_POINTER(p);
+}
+
+int cPluginSatip::ParseCicams(const char *valueP, int *cicamsP)
+{
+  debug1("%s (%s,)", __PRETTY_FUNCTION__, valueP);
+  int n = 0;
+  char *s, *p = strdup(valueP);
+  char *r = strtok_r(p, " ", &s);
+  while (r) {
+        r = skipspace(r);
+        debug3("%s cicams[%d]=%s", __PRETTY_FUNCTION__, n, r);
+        if (n < MAX_CICAM_COUNT) {
+           cicamsP[n++] = atoi(r);
+           }
+        r = strtok_r(NULL, " ", &s);
+        }
+  FREE_POINTER(p);
+  return n;
+}
+
+int cPluginSatip::ParseSources(const char *valueP, int *sourcesP)
+{
+  debug1("%s (%s,)", __PRETTY_FUNCTION__, valueP);
+  int n = 0;
+  char *s, *p = strdup(valueP);
+  char *r = strtok_r(p, " ", &s);
+  while (r) {
+        r = skipspace(r);
+        debug3("%s sources[%d]=%s", __PRETTY_FUNCTION__, n, r);
+        if (n < MAX_DISABLED_SOURCES_COUNT) {
+           sourcesP[n++] = cSource::FromString(r);
+           }
+        r = strtok_r(NULL, " ", &s);
+        }
+  FREE_POINTER(p);
+  return n;
+}
+
+int cPluginSatip::ParseFilters(const char *valueP, int *filtersP)
+{
+  debug1("%s (%s,)", __PRETTY_FUNCTION__, valueP);
+  char buffer[256];
+  int n = 0;
+  while (valueP && *valueP && (n < SECTION_FILTER_TABLE_SIZE)) {
+    strn0cpy(buffer, valueP, sizeof(buffer));
+    int i = atoi(buffer);
+    debug3("%s filters[%d]=%d", __PRETTY_FUNCTION__, n, i);
+    if (i >= 0)
+       filtersP[n++] = i;
+    if ((valueP = strchr(valueP, ' ')) != NULL)
+       valueP++;
+    }
+  return n;
+}
+
+bool cPluginSatip::SetupParse(const char *nameP, const char *valueP)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Parse your own setup parameters and store their values.
+  if (!strcasecmp(nameP, "OperatingMode"))
+     SatipConfig.SetOperatingMode(atoi(valueP));
+  else if (!strcasecmp(nameP, "EnableCIExtension"))
+     SatipConfig.SetCIExtension(atoi(valueP));
+  else if (!strcasecmp(nameP, "CICAM")) {
+     int Cicams[MAX_CICAM_COUNT];
+     for (unsigned int i = 0; i < ELEMENTS(Cicams); ++i)
+         Cicams[i] = 0;
+     unsigned int CicamsCount = ParseCicams(valueP, Cicams);
+     for (unsigned int i = 0; i < CicamsCount; ++i)
+         SatipConfig.SetCICAM(i, Cicams[i]);
+     }
+  else if (!strcasecmp(nameP, "EnableEITScan"))
+     SatipConfig.SetEITScan(atoi(valueP));
+  else if (!strcasecmp(nameP, "DisabledSources")) {
+     int DisabledSources[MAX_DISABLED_SOURCES_COUNT];
+     for (unsigned int i = 0; i < ELEMENTS(DisabledSources); ++i)
+         DisabledSources[i] = cSource::stNone;
+     unsigned int DisabledSourcesCount = ParseSources(valueP, DisabledSources);
+     for (unsigned int i = 0; i < DisabledSourcesCount; ++i)
+         SatipConfig.SetDisabledSources(i, DisabledSources[i]);
+     }
+  else if (!strcasecmp(nameP, "DisabledFilters")) {
+     int DisabledFilters[SECTION_FILTER_TABLE_SIZE];
+     for (unsigned int i = 0; i < ELEMENTS(DisabledFilters); ++i)
+         DisabledFilters[i] = -1;
+     unsigned int DisabledFiltersCount = ParseFilters(valueP, DisabledFilters);
+     for (unsigned int i = 0; i < DisabledFiltersCount; ++i)
+         SatipConfig.SetDisabledFilters(i, DisabledFilters[i]);
+     }
+  else
+     return false;
+  return true;
+}
+
+bool cPluginSatip::Service(const char *idP, void *dataP)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  return false;
+}
+
+const char **cPluginSatip::SVDRPHelpPages(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  static const char *HelpPages[] = {
+    "INFO [ <page> ] [ <card index> ]\n"
+    "    Prints SAT>IP device information and statistics.\n"
+    "    The output can be narrowed using optional \"page\""
+    "    option: 1=general 2=pids 3=section filters.\n",
+    "MODE\n"
+    "    Toggles between bit or byte information mode.\n",
+    "LIST\n"
+    "    Lists active SAT>IP servers.\n",
+    "SCAN\n"
+    "    Scans active SAT>IP servers.\n",
+    "STAT\n"
+    "    Lists status information of SAT>IP devices.\n",
+    "CONT\n"
+    "    Shows SAT>IP device count.\n",
+    "OPER\n"
+    "    Toggles operating mode of SAT>IP devices.\n",
+    "TRAC [ <mode> ]\n"
+    "    Gets and/or sets used tracing mode.\n",
+    NULL
+    };
+  return HelpPages;
+}
+
+cString cPluginSatip::SVDRPCommand(const char *commandP, const char *optionP, int &replyCodeP)
+{
+  debug1("%s (%s, %s,)", __PRETTY_FUNCTION__, commandP, optionP);
+  if (strcasecmp(commandP, "INFO") == 0) {
+     int index = cDevice::ActualDevice()->CardIndex();
+     int page = SATIP_DEVICE_INFO_ALL;
+     char *opt = strdup(optionP);
+     char *num = skipspace(opt);
+     char *option = num;
+     while (*option && !isspace(*option))
+           ++option;
+     if (*option) {
+        *option = 0;
+        option = skipspace(++option);
+        if (isnumber(option))
+           index = atoi(option);
+        }
+     if (isnumber(num)) {
+        page = atoi(num);
+        if ((page < SATIP_DEVICE_INFO_ALL) || (page > SATIP_DEVICE_INFO_FILTERS))
+           page = SATIP_DEVICE_INFO_ALL;
+        }
+     free(opt);
+     cSatipDevice *device = cSatipDevice::GetSatipDevice(index);
+     if (device) {
+        return device->GetInformation(page);
+        }
+     else {
+        replyCodeP = 550; // Requested action not taken
+        return cString("SATIP information not available!");
+        }
+     }
+  else if (strcasecmp(commandP, "MODE") == 0) {
+     unsigned int mode = !SatipConfig.GetUseBytes();
+     SatipConfig.SetUseBytes(mode);
+     return cString::sprintf("SATIP information mode: %s\n", mode ? "bytes" : "bits");
+     }
+  else if (strcasecmp(commandP, "LIST") == 0) {
+     cString list = cSatipDiscover::GetInstance()->GetServerList();
+     if (!isempty(list)) {
+        return list;
+        }
+     else {
+        replyCodeP = 550; // Requested action not taken
+        return cString("No SATIP servers detected!");
+        }
+     }
+  else if (strcasecmp(commandP, "SCAN") == 0) {
+     cSatipDiscover::GetInstance()->TriggerScan();
+     return cString("SATIP server scan requested");
+     }
+  else if (strcasecmp(commandP, "STAT") == 0) {
+     return cSatipDevice::GetSatipStatus();
+     }
+  else if (strcasecmp(commandP, "CONT") == 0) {
+     return cString::sprintf("SATIP device count: %u", cSatipDevice::Count());
+     }
+  else if (strcasecmp(commandP, "OPER") == 0) {
+     cString mode;
+     SatipConfig.ToggleOperatingMode();
+     switch (SatipConfig.GetOperatingMode()) {
+       case cSatipConfig::eOperatingModeOff:
+            mode = "off";
+            break;
+       case cSatipConfig::eOperatingModeLow:
+            mode = "low";
+            break;
+       case cSatipConfig::eOperatingModeNormal:
+            mode = "normal";
+            break;
+       case cSatipConfig::eOperatingModeHigh:
+            mode = "high";
+            break;
+       default:
+            mode = "unknown";
+            break;
+       }
+     return cString::sprintf("SATIP operating mode: %s\n", *mode);
+     }
+  else if (strcasecmp(commandP, "TRAC") == 0) {
+     if (optionP && *optionP)
+        SatipConfig.SetTraceMode(strtol(optionP, NULL, 0));
+     return cString::sprintf("SATIP tracing mode: 0x%04X\n", SatipConfig.GetTraceMode());
+     }
+
+  return NULL;
+}
+
+VDRPLUGINCREATOR(cPluginSatip); // Don't touch this!
diff --git a/sectionfilter.c b/sectionfilter.c
new file mode 100644
index 0000000..edf0382
--- /dev/null
+++ b/sectionfilter.c
@@ -0,0 +1,424 @@
+/*
+ * sectionfilter.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include "config.h"
+#include "log.h"
+#include "sectionfilter.h"
+
+cSatipSectionFilter::cSatipSectionFilter(int deviceIndexP, uint16_t pidP, uint8_t tidP, uint8_t maskP)
+: pusiSeenM(0),
+  feedCcM(0),
+  doneqM(0),
+  secBufM(NULL),
+  secBufpM(0),
+  secLenM(0),
+  tsFeedpM(0),
+  pidM(pidP),
+  ringBufferM(new cRingBufferFrame(eDmxMaxSectionCount * eDmxMaxSectionSize)),
+  deviceIndexM(deviceIndexP)
+{
+  debug16("%s (%d, %d, %d, %d) [device %d]", __PRETTY_FUNCTION__, deviceIndexM, pidM, tidP, maskP, deviceIndexM);
+  int i;
+
+  memset(secBufBaseM,     0, sizeof(secBufBaseM));
+  memset(filterValueM,    0, sizeof(filterValueM));
+  memset(filterMaskM,     0, sizeof(filterMaskM));
+  memset(filterModeM,     0, sizeof(filterModeM));
+  memset(maskAndModeM,    0, sizeof(maskAndModeM));
+  memset(maskAndNotModeM, 0, sizeof(maskAndNotModeM));
+
+  filterValueM[0] = tidP;
+  filterMaskM[0] = maskP;
+
+  // Invert the filter
+  for (i = 0; i < eDmxMaxFilterSize; ++i)
+      filterValueM[i] ^= 0xFF;
+
+  uint8_t doneq = 0;
+  for (i = 0; i < eDmxMaxFilterSize; ++i) {
+      uint8_t mode = filterModeM[i];
+      uint8_t mask = filterMaskM[i];
+      maskAndModeM[i] = (uint8_t)(mask & mode);
+      maskAndNotModeM[i] = (uint8_t)(mask & ~mode);
+      doneq |= maskAndNotModeM[i];
+      }
+  doneqM = doneq ? 1 : 0;
+
+  // Create sockets
+  socketM[0] = socketM[1] = -1;
+  if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socketM) != 0) {
+     char tmp[64];
+     error("Opening section filter sockets failed (device=%d pid=%d): %s", deviceIndexM, pidM, strerror_r(errno, tmp, sizeof(tmp)));
+     }
+  else if ((fcntl(socketM[0], F_SETFL, O_NONBLOCK) != 0) || (fcntl(socketM[1], F_SETFL, O_NONBLOCK) != 0)) {
+     char tmp[64];
+     error("Setting section filter socket to non-blocking mode failed (device=%d pid=%d): %s", deviceIndexM, pidM, strerror_r(errno, tmp, sizeof(tmp)));
+     }
+}
+
+cSatipSectionFilter::~cSatipSectionFilter()
+{
+  debug16("%s pid=%d [device %d]", __PRETTY_FUNCTION__, pidM, deviceIndexM);
+  int tmp = socketM[1];
+  socketM[1] = -1;
+  if (tmp >= 0)
+     close(tmp);
+  tmp = socketM[0];
+  socketM[0] = -1;
+  if (tmp >= 0)
+     close(tmp);
+  secBufM = NULL;
+  DELETENULL(ringBufferM);
+}
+
+inline uint16_t cSatipSectionFilter::GetLength(const uint8_t *dataP)
+{
+  return (uint16_t)(3 + ((dataP[1] & 0x0f) << 8) + dataP[2]);
+}
+
+void cSatipSectionFilter::New(void)
+{
+  tsFeedpM = secBufpM = secLenM = 0;
+  secBufM = secBufBaseM;
+}
+
+int cSatipSectionFilter::Filter(void)
+{
+  if (secBufM) {
+     int i;
+     uint8_t neq = 0;
+
+     for (i = 0; i < eDmxMaxFilterSize; ++i) {
+         uint8_t calcxor = (uint8_t)(filterValueM[i] ^ secBufM[i]);
+         if (maskAndModeM[i] & calcxor)
+            return 0;
+         neq |= (maskAndNotModeM[i] & calcxor);
+         }
+
+     if (doneqM && !neq)
+        return 0;
+
+     if (ringBufferM && (secLenM > 0))
+        ringBufferM->Put(new cFrame(secBufM, secLenM));
+     }
+  return 0;
+}
+
+inline int cSatipSectionFilter::Feed(void)
+{
+  if (Filter() < 0)
+     return -1;
+  secLenM = 0;
+  return 0;
+}
+
+int cSatipSectionFilter::CopyDump(const uint8_t *bufP, uint8_t lenP)
+{
+  uint16_t limit, n;
+
+  if (tsFeedpM >= eDmxMaxSectionFeedSize)
+     return 0;
+
+  if (tsFeedpM + lenP > eDmxMaxSectionFeedSize)
+     lenP = (uint8_t)(eDmxMaxSectionFeedSize - tsFeedpM);
+
+  if (lenP <= 0)
+     return 0;
+
+  memcpy(secBufBaseM + tsFeedpM, bufP, lenP);
+  tsFeedpM = uint16_t(tsFeedpM + lenP);
+
+  limit = tsFeedpM;
+  if (limit > eDmxMaxSectionFeedSize)
+     return -1; // internal error should never happen
+
+  // Always set secbuf
+  secBufM = secBufBaseM + secBufpM;
+
+  for (n = 0; secBufpM + 2 < limit; ++n) {
+      uint16_t seclen = GetLength(secBufM);
+      if ((seclen <= 0) || (seclen > eDmxMaxSectionSize) || ((seclen + secBufpM) > limit))
+         return 0;
+      secLenM = seclen;
+      if (pusiSeenM)
+         Feed();
+      secBufpM = uint16_t(secBufpM + seclen);
+      secBufM += seclen;
+      }
+  return 0;
+}
+
+void cSatipSectionFilter::Process(const uint8_t* dataP)
+{
+  if (dataP[0] != TS_SYNC_BYTE)
+     return;
+
+  // Stop if not the PID this filter is looking for
+  if (ts_pid(dataP) != pidM)
+     return;
+
+  uint8_t count = payload(dataP);
+
+  // Check if no payload or out of range
+  if (count == 0)
+     return;
+
+  // Payload start
+  uint8_t p = (uint8_t)(TS_SIZE - count);
+
+  uint8_t cc = (uint8_t)(dataP[3] & 0x0f);
+  int ccok = ((feedCcM + 1) & 0x0f) == cc;
+  feedCcM = cc;
+
+  int dc_i = 0;
+  if (dataP[3] & 0x20) {
+     // Adaption field present, check for discontinuity_indicator
+     if ((dataP[4] > 0) && (dataP[5] & 0x80))
+        dc_i = 1;
+     }
+
+  if (!ccok || dc_i) {
+     // Discontinuity detected. Reset pusiSeenM = 0 to
+     // stop feeding of suspicious data until next PUSI=1 arrives
+     pusiSeenM = 0;
+     New();
+     }
+
+  if (dataP[1] & 0x40) {
+     // PUSI=1 (is set), section boundary is here
+     if (count > 1 && dataP[p] < count) {
+        const uint8_t *before = &dataP[p + 1];
+        uint8_t before_len = dataP[p];
+        const uint8_t *after = &before[before_len];
+        uint8_t after_len = (uint8_t)(count - 1 - before_len);
+        CopyDump(before, before_len);
+
+        // Before start of new section, set pusiSeenM = 1
+        pusiSeenM = 1;
+        New();
+        CopyDump(after, after_len);
+        }
+     }
+  else {
+     // PUSI=0 (is not set), no section boundary
+     CopyDump(&dataP[p], count);
+     }
+}
+
+bool cSatipSectionFilter::Send(void)
+{
+  bool result = false;
+  cFrame *section = ringBufferM->Get();
+  if (section) {
+     uchar *data = section->Data();
+     int count = section->Count();
+     if (data && (count > 0) && (socketM[1] >= 0) && (socketM[0] >= 0)) {
+        ssize_t len = send(socketM[1], data, count, MSG_EOR);
+        ERROR_IF(len < 0 && errno != EAGAIN, "send()");
+        if (len > 0) {
+           ringBufferM->Drop(section);
+           result = !!ringBufferM->Available();
+           // Update statistics
+           AddSectionStatistic(len, 1);
+           }
+        }
+     }
+  return result;
+}
+
+
+cSatipSectionFilterHandler::cSatipSectionFilterHandler(int deviceIndexP, unsigned int bufferLenP)
+: cThread(cString::sprintf("SATIP#%d section handler", deviceIndexP)),
+  ringBufferM(new cRingBufferLinear(bufferLenP, TS_SIZE, false, *cString::sprintf("SATIP %d section handler", deviceIndexP))),
+  mutexM(),
+  deviceIndexM(deviceIndexP)
+{
+  debug1("%s (%d, %d) [device %d]", __PRETTY_FUNCTION__, deviceIndexM, bufferLenP, deviceIndexM);
+
+  // Initialize filter pointers
+  memset(filtersM, 0, sizeof(filtersM));
+
+  // Create input buffer
+  if (ringBufferM) {
+     ringBufferM->SetTimeouts(100, 100);
+     ringBufferM->SetIoThrottle();
+     }
+  else
+     error("Failed to allocate buffer for section filter handler [device=%d]", deviceIndexM);
+
+  Start();
+}
+
+cSatipSectionFilterHandler::~cSatipSectionFilterHandler()
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIndexM);
+  // Stop thread
+  if (Running())
+     Cancel(3);
+  DELETE_POINTER(ringBufferM);
+
+  // Destroy all filters
+  cMutexLock MutexLock(&mutexM);
+  for (int i = 0; i < eMaxSecFilterCount; ++i)
+      Delete(i);
+}
+
+void cSatipSectionFilterHandler::Action(void)
+{
+  debug1("%s Entering [device %d]", __PRETTY_FUNCTION__, deviceIndexM);
+  bool processed = false;
+  // Do the thread loop
+  while (Running()) {
+        // Send demuxed section packets through all filters
+        bool retry = false;
+        mutexM.Lock();
+        for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
+            if (filtersM[i] && filtersM[i]->Send())
+               retry = true;
+            }
+        mutexM.Unlock();
+        if (retry)
+           continue;
+        // Read one TS packet
+        if (ringBufferM) {
+           int len = 0;
+           if (processed) {
+              ringBufferM->Del(TS_SIZE);
+              processed = false;
+              }
+           uchar *p = ringBufferM->Get(len);
+           if (p && (len >= TS_SIZE)) {
+              if (*p != TS_SYNC_BYTE) {
+                 for (int i = 1; i < len; ++i) {
+                     if (p[i] == TS_SYNC_BYTE) {
+                        len = i;
+                        break;
+                        }
+                     }
+                 ringBufferM->Del(len);
+                 debug1("%s Skipped %d bytes to sync on TS packet [device %d]", __PRETTY_FUNCTION__, len, deviceIndexM);
+                 continue;
+                 }
+              // Process TS packet through all filters
+              mutexM.Lock();
+              for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
+                  if (filtersM[i])
+                     filtersM[i]->Process(p);
+                  }
+              mutexM.Unlock();
+              processed = true;
+              continue;
+              }
+           }
+        cCondWait::SleepMs(10); // to avoid busy loop and reduce cpu load
+        }
+  debug1("%s Exiting [device %d]", __PRETTY_FUNCTION__, deviceIndexM);
+}
+
+cString cSatipSectionFilterHandler::GetInformation(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIndexM);
+  // loop through active section filters
+  cMutexLock MutexLock(&mutexM);
+  cString s = "";
+  unsigned int count = 0;
+  for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
+      if (filtersM[i]) {
+         s = cString::sprintf("%sFilter %d: %s Pid=0x%02X (%s)\n", *s, i,
+                              *filtersM[i]->GetSectionStatistic(), filtersM[i]->GetPid(),
+                              id_pid(filtersM[i]->GetPid()));
+         if (++count > SATIP_STATS_ACTIVE_FILTERS_COUNT)
+            break;
+         }
+      }
+  return s;
+}
+
+bool cSatipSectionFilterHandler::Delete(unsigned int indexP)
+{
+  debug16("%s (%d) [device %d]", __PRETTY_FUNCTION__, indexP, deviceIndexM);
+  if ((indexP < eMaxSecFilterCount) && filtersM[indexP]) {
+     debug8("%s (%d) Found [device %d]", __PRETTY_FUNCTION__, indexP, deviceIndexM);
+     cSatipSectionFilter *tmp = filtersM[indexP];
+     filtersM[indexP] = NULL;
+     delete tmp;
+     return true;
+     }
+  return false;
+}
+
+bool cSatipSectionFilterHandler::IsBlackListed(u_short pidP, u_char tidP, u_char maskP) const
+{
+  debug16("%s (%d, %02X, %02X) [device %d]", __PRETTY_FUNCTION__, pidP, tidP, maskP, deviceIndexM);
+  // loop through section filter table
+  for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) {
+      int index = SatipConfig.GetDisabledFilters(i);
+      // Check if matches
+      if ((index >= 0) && (index < SECTION_FILTER_TABLE_SIZE) &&
+          (section_filter_table[index].pid == pidP) && (section_filter_table[index].tid == tidP) &&
+          (section_filter_table[index].mask == maskP)) {
+         debug8("%s Found %s [device %d]", __PRETTY_FUNCTION__, section_filter_table[index].description, deviceIndexM);
+         return true;
+         }
+      }
+  return false;
+}
+
+int cSatipSectionFilterHandler::Open(u_short pidP, u_char tidP, u_char maskP)
+{
+  cMutexLock MutexLock(&mutexM);
+  // Blacklist check, refuse certain filters
+  if (IsBlackListed(pidP, tidP, maskP))
+     return -1;
+  // Search the next free filter slot
+  for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
+      if (!filtersM[i]) {
+         filtersM[i] = new cSatipSectionFilter(deviceIndexM, pidP, tidP, maskP);
+         debug16("%s (%d, %02X, %02X) handle=%d index=%u [device %d]", __PRETTY_FUNCTION__, pidP, tidP, maskP, filtersM[i]->GetFd(), i, deviceIndexM);
+         return filtersM[i]->GetFd();
+         }
+      }
+  // No free filter slot found
+  return -1;
+}
+
+void cSatipSectionFilterHandler::Close(int handleP)
+{
+  cMutexLock MutexLock(&mutexM);
+  // Search the filter for deletion
+  for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
+      if (filtersM[i] && (handleP == filtersM[i]->GetFd())) {
+         debug8("%s (%d) pid=%d index=%u [device %d]", __PRETTY_FUNCTION__, handleP, filtersM[i]->GetPid(), i, deviceIndexM);
+         Delete(i);
+         break;
+         }
+      }
+}
+
+int cSatipSectionFilterHandler::GetPid(int handleP)
+{
+  cMutexLock MutexLock(&mutexM);
+  // Search the filter for data
+  for (unsigned int i = 0; i < eMaxSecFilterCount; ++i) {
+      if (filtersM[i] && (handleP == filtersM[i]->GetFd())) {
+         debug8("%s (%d) pid=%d index=%u [device %d]", __PRETTY_FUNCTION__, handleP, filtersM[i]->GetPid(), i, deviceIndexM);
+         return filtersM[i]->GetPid();
+         }
+      }
+  return -1;
+}
+
+void cSatipSectionFilterHandler::Write(uchar *bufferP, int lengthP)
+{
+  debug16("%s (, %d) [device %d]", __PRETTY_FUNCTION__, lengthP, deviceIndexM);
+  // Fill up the buffer
+  if (ringBufferM) {
+     int len = ringBufferM->Put(bufferP, lengthP);
+     if (len != lengthP)
+        ringBufferM->ReportOverflow(lengthP - len);
+     }
+}
diff --git a/sectionfilter.h b/sectionfilter.h
new file mode 100644
index 0000000..e25c897
--- /dev/null
+++ b/sectionfilter.h
@@ -0,0 +1,89 @@
+/*
+ * sectionfilter.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_SECTIONFILTER_H
+#define __SATIP_SECTIONFILTER_H
+
+#include <vdr/device.h>
+
+#include "common.h"
+#include "statistics.h"
+
+class cSatipSectionFilter : public cSatipSectionStatistics {
+private:
+  enum {
+    eDmxMaxFilterSize      = 18,
+    eDmxMaxSectionCount    = 64,
+    eDmxMaxSectionSize     = 4096,
+    eDmxMaxSectionFeedSize = (eDmxMaxSectionSize + TS_SIZE)
+  };
+
+  int pusiSeenM;
+  int feedCcM;
+  int doneqM;
+
+  uint8_t *secBufM;
+  uint8_t secBufBaseM[eDmxMaxSectionFeedSize];
+  uint16_t secBufpM;
+  uint16_t secLenM;
+  uint16_t tsFeedpM;
+  uint16_t pidM;
+
+  cRingBufferFrame *ringBufferM;
+  int deviceIndexM;
+  int socketM[2];
+
+  uint8_t filterValueM[eDmxMaxFilterSize];
+  uint8_t filterMaskM[eDmxMaxFilterSize];
+  uint8_t filterModeM[eDmxMaxFilterSize];
+
+  uint8_t maskAndModeM[eDmxMaxFilterSize];
+  uint8_t maskAndNotModeM[eDmxMaxFilterSize];
+
+  inline uint16_t GetLength(const uint8_t *dataP);
+  void New(void);
+  int Filter(void);
+  inline int Feed(void);
+  int CopyDump(const uint8_t *bufP, uint8_t lenP);
+
+public:
+  // constructor & destructor
+  cSatipSectionFilter(int deviceIndexP, uint16_t pidP, uint8_t tidP, uint8_t maskP);
+  virtual ~cSatipSectionFilter();
+  void Process(const uint8_t* dataP);
+  bool Send(void);
+  int GetFd(void) { return socketM[0]; }
+  uint16_t GetPid(void) const { return pidM; }
+};
+
+class cSatipSectionFilterHandler : public cThread {
+private:
+  enum {
+    eMaxSecFilterCount = 32
+  };
+  cRingBufferLinear *ringBufferM;
+  cMutex mutexM;
+  int deviceIndexM;
+  cSatipSectionFilter *filtersM[eMaxSecFilterCount];
+
+  bool Delete(unsigned int indexP);
+  bool IsBlackListed(u_short pidP, u_char tidP, u_char maskP) const;
+
+protected:
+  virtual void Action(void);
+
+public:
+  cSatipSectionFilterHandler(int deviceIndexP, unsigned int bufferLenP);
+  virtual ~cSatipSectionFilterHandler();
+  cString GetInformation(void);
+  int Open(u_short pidP, u_char tidP, u_char maskP);
+  void Close(int handleP);
+  int GetPid(int handleP);
+  void Write(u_char *bufferP, int lengthP);
+};
+
+#endif // __SATIP_SECTIONFILTER_H
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..65113ab
--- /dev/null
+++ b/server.c
@@ -0,0 +1,227 @@
+/*
+ * server.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <vdr/sources.h>
+
+#include "config.h"
+#include "common.h"
+#include "log.h"
+#include "server.h"
+
+// --- cSatipServer -----------------------------------------------------------
+
+cSatipServer::cSatipServer(const char *addressP, const char *modelP, const char *descriptionP)
+: addressM((addressP && *addressP) ? addressP : "0.0.0.0"),
+  modelM((modelP && *modelP) ? modelP : "DVBS-1"),
+  descriptionM(!isempty(descriptionP) ? descriptionP : "MyBrokenHardware"),
+  modelTypeM(eSatipModelTypeNone),
+  quirkM(eSatipQuirkNone),
+  useCountM(0),
+  transponderM(0),
+  createdM(time(NULL)),
+  lastSeenM(0)
+{
+  memset(modelCountM, 0, sizeof(modelCountM));
+  if (!SatipConfig.GetDisableServerQuirks()) {
+     debug3("%s quirks=%s", __PRETTY_FUNCTION__, *descriptionM);
+     // These devices contain a session id bug:
+     // Inverto Airscreen Server IDL 400 ?
+     // Elgato EyeTV Netstream 4Sat ?
+     if (strstr(*descriptionM, "GSSBOX") ||             // Grundig Sat Systems GSS.box DSI 400
+         strstr(*descriptionM, "DIGIBIT") ||            // Telestar Digibit R1
+         strstr(*descriptionM, "Triax SatIP Converter") // Triax TSS 400
+        )
+        quirkM |= eSatipQuirkSessionId;
+     // These devices contain a play (add/delpids) parameter bug:
+     if (strstr(*descriptionM, "fritzdvbc"))            // Fritz!WLAN Repeater DVB-C
+        quirkM |= eSatipQuirkPlayPids;
+     // These devices contain a frontend locking bug:
+     if (strstr(*descriptionM, "fritzdvbc"))            // Fritz!WLAN Repeater DVB-C
+        quirkM |= eSatipQuirkForceLock;
+     if (quirkM != eSatipQuirkNone)
+        info("Malfunctioning '%s' server detected! Please, fix the firmware.", *descriptionM);
+     }
+  // These devices support the X_PMT protocol extension
+  if (strstr(*descriptionM, "OctopusNet"))           // Digital Devices OctopusNet
+     quirkM |= eSatipQuirkUseXCI;
+  char *s, *p = strdup(*modelM);
+  char *r = strtok_r(p, ",", &s);
+  while (r) {
+        if (strstr(r, "DVBS2-")) {
+           modelTypeM |= eSatipModelTypeDVBS2;
+           if (char *c = strstr(r, "-"))
+              modelCountM[eSatipModuleDVBS2] = atoi(++c);
+           }
+        else if (strstr(r, "DVBT2-")) {
+           modelTypeM |= eSatipModelTypeDVBT2;
+           if (char *c = strstr(r, "-"))
+              modelCountM[eSatipModuleDVBT2] = atoi(++c);
+           modelTypeM |= eSatipModelTypeDVBT;
+           modelCountM[eSatipModuleDVBT] = modelCountM[eSatipModuleDVBT2];
+           }
+        else if (strstr(r, "DVBT-")) {
+           modelTypeM |= eSatipModelTypeDVBT;
+           if (char *c = strstr(r, "-"))
+              modelCountM[eSatipModuleDVBT] = atoi(++c);
+           }
+        else if (strstr(r, "DVBC2-")) {
+           modelTypeM |= eSatipModelTypeDVBC2;
+           if (char *c = strstr(r, "-"))
+              modelCountM[eSatipModuleDVBC2] = atoi(++c);
+           modelTypeM |= eSatipModelTypeDVBC;
+           modelCountM[eSatipModuleDVBC] = modelCountM[eSatipModuleDVBC2];
+           }
+        else if (strstr(r, "DVBC-")) {
+           modelTypeM |= eSatipModelTypeDVBC;
+           if (char *c = strstr(r, "-"))
+              modelCountM[eSatipModuleDVBC] = atoi(++c);
+           }
+        r = strtok_r(NULL, ",", &s);
+        }
+  free(p);
+}
+
+cSatipServer::~cSatipServer()
+{
+}
+
+int cSatipServer::Compare(const cListObject &listObjectP) const
+{
+  const cSatipServer *s = (const cSatipServer *)&listObjectP;
+  int result = strcasecmp(*addressM, *s->addressM);
+  if (!result) {
+     result = strcasecmp(*modelM, *s->modelM);
+     if (!result)
+        result = strcasecmp(*descriptionM, *s->descriptionM);
+     }
+  return result;
+}
+
+void cSatipServer::Use(bool onOffP)
+{
+  if (onOffP)
+     ++useCountM;
+  else
+     --useCountM;
+}
+
+// --- cSatipServers ----------------------------------------------------------
+
+cSatipServer *cSatipServers::Find(cSatipServer *serverP)
+{
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s == serverP)
+         return s;
+      }
+  return NULL;
+}
+
+cSatipServer *cSatipServers::Find(int sourceP, int transponderP, int systemP)
+{
+  cSatipServer *result = NULL;
+  int model = 0;
+  if (cSource::IsType(sourceP, 'S'))
+     model |= cSatipServer::eSatipModelTypeDVBS2;
+  else if (cSource::IsType(sourceP, 'T')) {
+     if (systemP < 0)
+        model |= cSatipServer::eSatipModelTypeDVBT2 | cSatipServer::eSatipModelTypeDVBT;
+     else
+        model |= systemP ? cSatipServer::eSatipModelTypeDVBT2 : cSatipServer::eSatipModelTypeDVBT;
+     }
+  else if (cSource::IsType(sourceP, 'C'))
+     model |= cSatipServer::eSatipModelTypeDVBC;
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s->Match(model) && s->Used() && (s->Transponder() == transponderP))
+         return s;
+      }
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s->Match(model)) {
+         result = s;
+         if (!s->Used()) {
+            break;
+            }
+         }
+      }
+  return result;
+}
+
+void cSatipServers::SetTransponder(cSatipServer *serverP, int transponderP)
+{
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s == serverP) {
+         s->SetTransponder(transponderP);
+         break;
+         }
+      }
+}
+
+cSatipServer *cSatipServers::Update(cSatipServer *serverP)
+{
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s->Compare(*serverP) == 0) {
+         s->Update();
+         return s;
+         }
+      }
+  return NULL;
+}
+
+void cSatipServers::Use(cSatipServer *serverP, bool onOffP)
+{
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s == serverP) {
+         s->Use(onOffP);
+         break;
+         }
+      }
+}
+
+void cSatipServers::Cleanup(uint64_t intervalMsP)
+{
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (!intervalMsP || (s->LastSeen() > intervalMsP)) {
+         info("Removing server %s (%s %s)", s->Description(), s->Address(), s->Model());
+         Del(s);
+         }
+      }
+}
+
+cString cSatipServers::GetString(cSatipServer *serverP)
+{
+  cString list = "";
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      if (s == serverP) {
+         list = cString::sprintf("%s|%s|%s", s->Address(), s->Model(), s->Description());
+         break;
+         }
+      }
+  return list;
+}
+
+cString cSatipServers::List(void)
+{
+  cString list = "";
+  for (cSatipServer *s = First(); s; s = Next(s))
+      list = cString::sprintf("%s%s|%s|%s\n", *list, s->Address(), s->Model(), s->Description());
+  return list;
+}
+
+int cSatipServers::NumProvidedSystems(void)
+{
+  int count = 0;
+  for (cSatipServer *s = First(); s; s = Next(s)) {
+      // DVB-S2: qpsk, 8psk, 16apsk, 32apsk
+      count += s->Satellite() * 4;
+      // DVB-T2: qpsk, qam16, qam64, qam256
+      // DVB-T: qpsk, qam16, qam64
+      count += s->Terrestrial2() ? s->Terrestrial2() * 4 : s->Terrestrial() * 3;
+      // DVB-C2: qam16, qam32, qam64, qam128, qam256
+      // DVB-C: qam64, qam128, qam256
+      count += s->Cable2() ? s->Cable2() * 5 : s->Cable() * 3;
+      }
+  return count;
+}
diff --git a/server.h b/server.h
new file mode 100644
index 0000000..2a21b73
--- /dev/null
+++ b/server.h
@@ -0,0 +1,90 @@
+/*
+ * server.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_SERVER_H
+#define __SATIP_SERVER_H
+
+// --- cSatipServer -----------------------------------------------------------
+
+class cSatipServer : public cListObject {
+private:
+  enum eSatipModule {
+    eSatipModuleDVBS2 = 0,
+    eSatipModuleDVBT,
+    eSatipModuleDVBT2,
+    eSatipModuleDVBC,
+    eSatipModuleDVBC2,
+    eSatipModuleCount
+  };
+  cString addressM;
+  cString modelM;
+  cString descriptionM;
+  int modelCountM[eSatipModuleCount];
+  int modelTypeM;
+  int quirkM;
+  int useCountM;
+  int transponderM;
+  time_t createdM;
+  cTimeMs lastSeenM;
+
+public:
+  enum eSatipQuirk {
+    eSatipQuirkNone      = 0x00,
+    eSatipQuirkSessionId = 0x01,
+    eSatipQuirkPlayPids  = 0x02,
+    eSatipQuirkForceLock = 0x04,
+    eSatipQuirkUseXCI    = 0x08,
+    eSatipQuirkMask      = 0x0F
+  };
+  enum eSatipModelType {
+    eSatipModelTypeNone  = 0x00,
+    eSatipModelTypeDVBS2 = 0x01,
+    eSatipModelTypeDVBT  = 0x02,
+    eSatipModelTypeDVBT2 = 0x04,
+    eSatipModelTypeDVBC  = 0x08,
+    eSatipModelTypeDVBC2 = 0x10,
+    eSatipModelTypeMask  = 0xFF
+  };
+  cSatipServer(const char *addressP, const char *modelP, const char *descriptionP);
+  virtual ~cSatipServer();
+  virtual int Compare(const cListObject &listObjectP) const;
+  void Use(bool onOffP);
+  void SetTransponder(const int transponderP) { transponderM = transponderP; }
+  int Transponder(void)     { return transponderM; }
+  bool Used(void)           { return !!useCountM; }
+  const char *Address()     { return *addressM; }
+  const char *Model(void)   { return *modelM; }
+  const char *Description() { return *descriptionM; }
+  bool Quirk(int quirkP)    { return ((quirkP & eSatipQuirkMask) & quirkM); }
+  int ModelType(void)       { return modelTypeM; }
+  bool Match(int modelP)    { return ((modelP & eSatipModelTypeMask) & modelTypeM); }
+  int Cable()               { return Match(eSatipModelTypeDVBC)  ? modelCountM[eSatipModuleDVBC]  : 0; }
+  int Cable2()              { return Match(eSatipModelTypeDVBC2) ? modelCountM[eSatipModuleDVBC2] : 0; }
+  int Satellite()           { return Match(eSatipModelTypeDVBS2) ? modelCountM[eSatipModuleDVBS2] : 0; }
+  int Terrestrial()         { return Match(eSatipModelTypeDVBT)  ? modelCountM[eSatipModuleDVBT]  : 0; }
+  int Terrestrial2()        { return Match(eSatipModelTypeDVBT2) ? modelCountM[eSatipModuleDVBT2] : 0; }
+  void Update(void)         { lastSeenM.Set(); }
+  uint64_t LastSeen(void)   { return lastSeenM.Elapsed(); }
+  time_t Created(void)      { return createdM; }
+};
+
+// --- cSatipServers ----------------------------------------------------------
+
+class cSatipServers : public cList<cSatipServer> {
+public:
+  cSatipServer *Find(cSatipServer *serverP);
+  cSatipServer *Find(int sourceP, int transponderP, int systemP);
+  void SetTransponder(cSatipServer *serverP, int transponderP);
+  cSatipServer *Update(cSatipServer *serverP);
+  void Use(cSatipServer *serverP, bool onOffP);
+  void Cleanup(uint64_t intervalMsP = 0);
+  cString GetString(cSatipServer *serverP);
+  cString List(void);
+  int NumProvidedSystems(void);
+};
+
+#endif // __SATIP_SERVER_H
diff --git a/setup.c b/setup.c
new file mode 100644
index 0000000..9753f10
--- /dev/null
+++ b/setup.c
@@ -0,0 +1,561 @@
+/*
+ * setup.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <vdr/status.h>
+#include <vdr/menu.h>
+
+#include "common.h"
+#include "config.h"
+#include "device.h"
+#include "discover.h"
+#include "log.h"
+#include "setup.h"
+
+// --- cSatipEditSrcItem ------------------------------------------------------
+// This class is a 99% copy of cMenuEditSrcItem() taken from VDR's menu.c
+
+class cSatipEditSrcItem : public cMenuEditIntItem {
+private:
+  const cSource *source;
+protected:
+  virtual void Set(void);
+public:
+  cSatipEditSrcItem(const char *Name, int *Value);
+  eOSState ProcessKey(eKeys Key);
+  };
+
+cSatipEditSrcItem::cSatipEditSrcItem(const char *Name, int *Value)
+:cMenuEditIntItem(Name, Value, 0)
+{
+  source = Sources.Get(*Value);
+  if (!source)
+     source = Sources.First();
+  Set();
+}
+
+void cSatipEditSrcItem::Set(void)
+{
+  if (source)
+     SetValue(cString::sprintf("%s - %s", *cSource::ToString(source->Code()), source->Description()));
+  else
+     cMenuEditIntItem::Set();
+}
+
+eOSState cSatipEditSrcItem::ProcessKey(eKeys Key)
+{
+  eOSState state = cMenuEditItem::ProcessKey(Key);
+
+  if (state == osUnknown) {
+     bool IsRepeat = Key & k_Repeat;
+     Key = NORMALKEY(Key);
+     if (Key == kLeft) { // TODO might want to increase the delta if repeated quickly?
+        if (source) {
+           if (source->Prev())
+              source = (cSource *)source->Prev();
+           else if (!IsRepeat)
+              source = Sources.Last();
+           *value = source->Code();
+           }
+        }
+     else if (Key == kRight) {
+        if (source) {
+           if (source->Next())
+              source = (cSource *)source->Next();
+           else if (!IsRepeat)
+              source = Sources.First();
+           }
+        else
+           source = Sources.First();
+        if (source)
+           *value = source->Code();
+        }
+     else
+        return state; // we don't call cMenuEditIntItem::ProcessKey(Key) here since we don't accept numerical input
+     Set();
+     state = osContinue;
+     }
+  return state;
+}
+
+// --- cSatipServerInfo -------------------------------------------------------
+
+class cSatipServerInfo : public cOsdMenu
+{
+private:
+  cString addressM;
+  cString modelM;
+  cString descriptionM;
+  cString ciExtensionM;
+  uint64_t createdM;
+  void Setup(void);
+
+public:
+  cSatipServerInfo(cSatipServer *serverP);
+  virtual ~cSatipServerInfo();
+  virtual eOSState ProcessKey(eKeys keyP);
+};
+
+cSatipServerInfo::cSatipServerInfo(cSatipServer *serverP)
+: cOsdMenu(tr("SAT>IP Server"), 20),
+  addressM(serverP ? serverP->Address() : "---"),
+  modelM(serverP ? serverP->Model() : "---"),
+  descriptionM(serverP ? serverP->Description() : "---"),
+  ciExtensionM(serverP && serverP->Quirk(cSatipServer::eSatipQuirkUseXCI) ? trVDR("yes") : trVDR("no")),
+  createdM(serverP ? serverP->Created() : 0)
+{
+  SetMenuCategory(mcSetupPlugins);
+  Setup();
+  SetHelp(NULL, NULL, NULL, NULL);
+}
+
+cSatipServerInfo::~cSatipServerInfo()
+{
+}
+
+void cSatipServerInfo::Setup(void)
+{
+  Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Address"),       *addressM),              osUnknown, false));
+  Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Model"),         *modelM),                osUnknown, false));
+  Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Description"),   *descriptionM),          osUnknown, false));
+  Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("CI extension"),  *ciExtensionM),          osUnknown, false));
+  Add(new cOsdItem(cString::sprintf("%s:\t%s", tr("Creation date"), *DayDateTime(createdM)), osUnknown, false));
+}
+
+eOSState cSatipServerInfo::ProcessKey(eKeys keyP)
+{
+  eOSState state = cOsdMenu::ProcessKey(keyP);
+
+  if (state == osUnknown) {
+     switch (keyP) {
+       case kOk: state = osBack;     break;
+       default:  state = osContinue; break;
+       }
+     }
+  return state;
+}
+
+// --- cSatipServerItem -------------------------------------------------------
+
+class cSatipServerItem : public cOsdItem {
+private:
+  cSatipServer *serverM;
+
+public:
+  cSatipServerItem(cSatipServer *serverP);
+  cSatipServer *Server(void) { return serverM; }
+  virtual void SetMenuItem(cSkinDisplayMenu *displayMenuP, int indexP, bool currentP, bool selectableP);
+  };
+
+cSatipServerItem::cSatipServerItem(cSatipServer *serverP)
+: serverM(serverP)
+{
+  SetSelectable(true);
+  // Must begin with a '#' character!
+  SetText(*cString::sprintf("# %s (%s)\t%s", serverM->Address(), serverM->Model(), serverM->Description()));
+}
+
+void cSatipServerItem::SetMenuItem(cSkinDisplayMenu *displayMenuP, int indexP, bool currentP, bool selectableP)
+{
+  if (displayMenuP)
+     displayMenuP->SetItem(Text(), indexP, currentP, selectableP);
+}
+
+// --- cSatipMenuDeviceStatus -------------------------------------------------
+
+class cSatipMenuDeviceStatus : public cOsdMenu
+{
+private:
+  enum {
+    eInfoTimeoutMs = 15000
+  };
+  cString textM;
+  cTimeMs timeoutM;
+  void UpdateInfo();
+
+public:
+  cSatipMenuDeviceStatus();
+  virtual ~cSatipMenuDeviceStatus();
+  virtual void Display(void);
+  virtual eOSState ProcessKey(eKeys keyP);
+};
+
+cSatipMenuDeviceStatus::cSatipMenuDeviceStatus()
+: cOsdMenu(tr("SAT>IP Device Status")),
+  textM(""),
+  timeoutM()
+{
+  SetMenuCategory(mcText);
+  timeoutM.Set(eInfoTimeoutMs);
+  UpdateInfo();
+  SetHelp(NULL, NULL, NULL, NULL);
+}
+
+cSatipMenuDeviceStatus::~cSatipMenuDeviceStatus()
+{
+}
+
+void cSatipMenuDeviceStatus::UpdateInfo()
+{
+  textM = cSatipDevice::GetSatipStatus();
+  Display();
+  timeoutM.Set(eInfoTimeoutMs);
+}
+
+void cSatipMenuDeviceStatus::Display(void)
+{
+  cOsdMenu::Display();
+  DisplayMenu()->SetText(textM, true);
+  if (*textM)
+     cStatus::MsgOsdTextItem(textM);
+}
+
+eOSState cSatipMenuDeviceStatus::ProcessKey(eKeys keyP)
+{
+  eOSState state = cOsdMenu::ProcessKey(keyP);
+
+  if (state == osUnknown) {
+     switch (keyP) {
+       case kOk: state = osBack;     break;
+       default:  if (timeoutM.TimedOut())
+                    UpdateInfo();
+                 state = osContinue;
+                 break;
+       }
+     }
+  return state;
+}
+
+// --- cSatipMenuInfo ---------------------------------------------------------
+
+class cSatipMenuInfo : public cOsdMenu
+{
+private:
+  enum {
+    eInfoTimeoutMs = 2000
+  };
+  cString textM;
+  cTimeMs timeoutM;
+  unsigned int pageM;
+  void UpdateInfo();
+
+public:
+  cSatipMenuInfo();
+  virtual ~cSatipMenuInfo();
+  virtual void Display(void);
+  virtual eOSState ProcessKey(eKeys keyP);
+};
+
+cSatipMenuInfo::cSatipMenuInfo()
+: cOsdMenu(tr("SAT>IP Information")),
+  textM(""),
+  timeoutM(),
+  pageM(SATIP_DEVICE_INFO_GENERAL)
+{
+  SetMenuCategory(mcText);
+  timeoutM.Set(eInfoTimeoutMs);
+  UpdateInfo();
+  SetHelp(tr("General"), tr("Pids"), tr("Filters"), tr("Bits/bytes"));
+}
+
+cSatipMenuInfo::~cSatipMenuInfo()
+{
+}
+
+void cSatipMenuInfo::UpdateInfo()
+{
+  cSatipDevice *device = cSatipDevice::GetSatipDevice(cDevice::ActualDevice()->CardIndex());
+  if (device)
+     textM = device->GetInformation(pageM);
+  else
+     textM = cString(tr("SAT>IP information not available!"));
+  Display();
+  timeoutM.Set(eInfoTimeoutMs);
+}
+
+void cSatipMenuInfo::Display(void)
+{
+  cOsdMenu::Display();
+  DisplayMenu()->SetText(textM, true);
+  if (*textM)
+     cStatus::MsgOsdTextItem(textM);
+}
+
+eOSState cSatipMenuInfo::ProcessKey(eKeys keyP)
+{
+  switch (int(keyP)) {
+    case kUp|k_Repeat:
+    case kUp:
+    case kDown|k_Repeat:
+    case kDown:
+    case kLeft|k_Repeat:
+    case kLeft:
+    case kRight|k_Repeat:
+    case kRight:
+                  DisplayMenu()->Scroll(NORMALKEY(keyP) == kUp || NORMALKEY(keyP) == kLeft, NORMALKEY(keyP) == kLeft || NORMALKEY(keyP) == kRight);
+                  cStatus::MsgOsdTextItem(NULL, NORMALKEY(keyP) == kUp || NORMALKEY(keyP) == kLeft);
+                  return osContinue;
+    default: break;
+    }
+
+  eOSState state = cOsdMenu::ProcessKey(keyP);
+
+  if (state == osUnknown) {
+     switch (keyP) {
+       case kOk:     return osBack;
+       case kRed:    pageM = SATIP_DEVICE_INFO_GENERAL;
+                     UpdateInfo();
+                     break;
+       case kGreen:  pageM = SATIP_DEVICE_INFO_PIDS;
+                     UpdateInfo();
+                     break;
+       case kYellow: pageM = SATIP_DEVICE_INFO_FILTERS;
+                     UpdateInfo();
+                     break;
+       case kBlue:   SatipConfig.SetUseBytes(SatipConfig.GetUseBytes() ? 0 : 1);
+                     UpdateInfo();
+                     break;
+       default:      if (timeoutM.TimedOut())
+                        UpdateInfo();
+                     state = osContinue;
+                     break;
+       }
+     }
+  return state;
+}
+
+// --- cSatipPluginSetup ------------------------------------------------------
+
+cSatipPluginSetup::cSatipPluginSetup()
+: deviceCountM(0),
+  operatingModeM(SatipConfig.GetOperatingMode()),
+  ciExtensionM(SatipConfig.GetCIExtension()),
+  eitScanM(SatipConfig.GetEITScan()),
+  numDisabledSourcesM(SatipConfig.GetDisabledSourcesCount()),
+  numDisabledFiltersM(SatipConfig.GetDisabledFiltersCount())
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  operatingModeTextsM[cSatipConfig::eOperatingModeOff]    = tr("off");
+  operatingModeTextsM[cSatipConfig::eOperatingModeLow]    = tr("low");
+  operatingModeTextsM[cSatipConfig::eOperatingModeNormal] = tr("normal");
+  operatingModeTextsM[cSatipConfig::eOperatingModeHigh]   = tr("high");
+  for (unsigned int i = 0; i < ELEMENTS(cicamsM); ++i)
+      cicamsM[i] = SatipConfig.GetCICAM(i);
+  for (unsigned int i = 0; i < ELEMENTS(ca_systems_table); ++i)
+      cicamTextsM[i] = ca_systems_table[i].description;
+  if (numDisabledSourcesM > MAX_DISABLED_SOURCES_COUNT)
+     numDisabledSourcesM = MAX_DISABLED_SOURCES_COUNT;
+  for (int i = 0; i < MAX_DISABLED_SOURCES_COUNT; ++i)
+      disabledSourcesM[i] = SatipConfig.GetDisabledSources(i);
+  if (numDisabledFiltersM > SECTION_FILTER_TABLE_SIZE)
+     numDisabledFiltersM = SECTION_FILTER_TABLE_SIZE;
+  for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) {
+      disabledFilterIndexesM[i] = SatipConfig.GetDisabledFilters(i);
+      disabledFilterNamesM[i] = tr(section_filter_table[i].description);
+      }
+  SetMenuCategory(mcSetupPlugins);
+  Setup();
+  SetHelp(trVDR("Button$Scan"), NULL, tr("Button$Devices"), trVDR("Button$Info"));
+}
+
+void cSatipPluginSetup::Setup(void)
+{
+  int current = Current();
+
+  Clear();
+  helpM.Clear();
+
+  Add(new cMenuEditStraItem(tr("Operating mode"), &operatingModeM, ELEMENTS(operatingModeTextsM), operatingModeTextsM));
+  helpM.Append(tr("Define the used operating mode for all SAT>IP devices:\n\noff - devices are disabled\nlow - devices are working at the lowest priority\nnormal - devices are working within normal parameters\nhigh - devices are working at the highest priority"));
+
+  if (operatingModeM) {
+     Add(new cMenuEditBoolItem(tr("Enable CI extension"), &ciExtensionM));
+     helpM.Append(tr("Define whether a CI extension shall be used.\n\nThis setting enables integrated CI/CAM handling found in some SAT>IP hardware (e.g. Digital Devices OctopusNet)."));
+
+     for (unsigned int i = 0; ciExtensionM && i < ELEMENTS(cicamsM); ++i) {
+         Add(new cMenuEditStraItem(*cString::sprintf(" %s #%d", tr("CI/CAM"), i + 1), &cicamsM[i], ELEMENTS(cicamTextsM), cicamTextsM));
+         helpM.Append(tr("Define a desired CAM type for the CI slot.\n\nThe '---' option lets SAT>IP hardware do the auto-selection."));
+         }
+
+     Add(new cMenuEditBoolItem(tr("Enable EPG scanning"), &eitScanM));
+     helpM.Append(tr("Define whether the EPG background scanning shall be used.\n\nThis setting disables the automatic EIT scanning functionality for all SAT>IP devices."));
+
+     Add(new cMenuEditIntItem(tr("Disabled sources"), &numDisabledSourcesM, 0, MAX_DISABLED_SOURCES_COUNT, tr("none")));
+     helpM.Append(tr("Define number of sources to be disabled.\n\nSAT>IP servers might not have all satellite positions available and such sources can be blacklisted here."));
+
+     for (int i = 0; i < numDisabledSourcesM; ++i) {
+         Add(new cSatipEditSrcItem(*cString::sprintf(" %s %d", trVDR("Source"), i + 1), &disabledSourcesM[i]));
+         helpM.Append(tr("Define a source to be blacklisted."));
+         }
+
+     Add(new cMenuEditIntItem(tr("Disabled filters"), &numDisabledFiltersM, 0, SECTION_FILTER_TABLE_SIZE, tr("none")));
+     helpM.Append(tr("Define number of section filters to be disabled.\n\nCertain section filters might cause some unwanted behaviour to VDR such as time being falsely synchronized. By blacklisting the filters here, useful section data can be left intact for VDR to process."));
+
+     for (int i = 0; i < numDisabledFiltersM; ++i) {
+         Add(new cMenuEditStraItem(*cString::sprintf(" %s %d", tr("Filter"), i + 1), &disabledFilterIndexesM[i], SECTION_FILTER_TABLE_SIZE, disabledFilterNamesM));
+         helpM.Append(tr("Define an ill-behaving filter to be blacklisted."));
+         }
+     }
+  Add(new cOsdItem(tr("Active SAT>IP servers:"), osUnknown, false));
+  helpM.Append("");
+
+  cSatipServers *servers = cSatipDiscover::GetInstance()->GetServers();
+  deviceCountM = servers->Count();
+  for (cSatipServer *s = servers->First(); s; s = servers->Next(s)) {
+      Add(new cSatipServerItem(s));
+      helpM.Append("");
+      }
+
+  SetCurrent(Get(current));
+  Display();
+}
+
+eOSState cSatipPluginSetup::DeviceScan(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  cSatipDiscover::GetInstance()->TriggerScan();
+
+  return osContinue;
+}
+
+eOSState cSatipPluginSetup::DeviceInfo(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (HasSubMenu() || Count() == 0)
+     return osContinue;
+
+  cSatipServerItem *item = reinterpret_cast<cSatipServerItem *>(Get(Current()));
+  if (item && !!cSatipDiscover::GetInstance()->GetServer(item->Server()))
+     return AddSubMenu(new cSatipServerInfo(item->Server()));
+
+  return osContinue;
+}
+
+eOSState cSatipPluginSetup::ShowDeviceStatus(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (HasSubMenu() || Count() == 0)
+     return osContinue;
+
+  return AddSubMenu(new cSatipMenuDeviceStatus());
+}
+
+eOSState cSatipPluginSetup::ShowInfo(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (HasSubMenu() || Count() == 0)
+     return osContinue;
+
+  return AddSubMenu(new cSatipMenuInfo());
+}
+
+eOSState cSatipPluginSetup::ProcessKey(eKeys keyP)
+{
+  bool hadSubMenu = HasSubMenu();
+  int oldOperatingMode = operatingModeM;
+  int oldCiExtension = ciExtensionM;
+  int oldNumDisabledSources = numDisabledSourcesM;
+  int oldNumDisabledFilters = numDisabledFiltersM;
+  eOSState state = cMenuSetupPage::ProcessKey(keyP);
+
+  // Ugly hack with hardcoded '#' character :(
+  const char *p = Get(Current())->Text();
+  if (!hadSubMenu && !HasSubMenu() && (*p == '#') && (keyP == kOk))
+     return DeviceInfo();
+
+  if (state == osUnknown) {
+     switch (keyP) {
+       case kRed:    return DeviceScan();
+       case kYellow: return ShowDeviceStatus();
+       case kBlue:   return ShowInfo();
+       case kInfo:   if (Current() < helpM.Size())
+                        return AddSubMenu(new cMenuText(cString::sprintf("%s - %s '%s'", tr("Help"), trVDR("Plugin"), PLUGIN_NAME_I18N), helpM[Current()]));
+       default:      state = osContinue; break;
+       }
+     }
+
+  if ((keyP == kNone) && (cSatipDiscover::GetInstance()->GetServers()->Count() != deviceCountM))
+     Setup();
+
+  if ((keyP != kNone) && ((numDisabledSourcesM != oldNumDisabledSources) || (numDisabledFiltersM != oldNumDisabledFilters) || (operatingModeM != oldOperatingMode) || (ciExtensionM != oldCiExtension))) {
+     while ((numDisabledSourcesM < oldNumDisabledSources) && (oldNumDisabledSources > 0))
+           disabledSourcesM[--oldNumDisabledSources] = cSource::stNone;
+     while ((numDisabledFiltersM < oldNumDisabledFilters) && (oldNumDisabledFilters > 0))
+           disabledFilterIndexesM[--oldNumDisabledFilters] = -1;
+     Setup();
+     }
+
+  return state;
+}
+
+void cSatipPluginSetup::StoreCicams(const char *nameP, int *cicamsP)
+{
+  cString buffer = "";
+  int n = 0;
+  for (int i = 0; i < MAX_CICAM_COUNT; ++i) {
+      if (cicamsP[i] < 0)
+         break;
+      if (n++ > 0)
+         buffer = cString::sprintf("%s %d", *buffer, cicamsP[i]);
+      else
+         buffer = cString::sprintf("%d", cicamsP[i]);
+      }
+  debug3("%s (%s, %s)", __PRETTY_FUNCTION__, nameP, *buffer);
+  SetupStore(nameP, *buffer);
+}
+
+void cSatipPluginSetup::StoreSources(const char *nameP, int *sourcesP)
+{
+  cString buffer = "";
+  int n = 0;
+  for (int i = 0; i < MAX_DISABLED_SOURCES_COUNT; ++i) {
+      if (sourcesP[i] == cSource::stNone)
+         break;
+      if (n++ > 0)
+         buffer = cString::sprintf("%s %s", *buffer, *cSource::ToString(sourcesP[i]));
+      else
+         buffer = cString::sprintf("%s", *cSource::ToString(sourcesP[i]));
+      }
+  debug3("%s (%s, %s)", __PRETTY_FUNCTION__, nameP, *buffer);
+  SetupStore(nameP, *buffer);
+}
+
+void cSatipPluginSetup::StoreFilters(const char *nameP, int *valuesP)
+{
+  cString buffer = "";
+  int n = 0;
+  for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i) {
+      if (valuesP[i] < 0)
+         break;
+      if (n++ > 0)
+         buffer = cString::sprintf("%s %d", *buffer, valuesP[i]);
+      else
+         buffer = cString::sprintf("%d", valuesP[i]);
+      }
+  debug3("%s (%s, %s)", __PRETTY_FUNCTION__, nameP, *buffer);
+  SetupStore(nameP, *buffer);
+}
+
+void cSatipPluginSetup::Store(void)
+{
+  // Store values into setup.conf
+  SetupStore("OperatingMode", operatingModeM);
+  SetupStore("EnableCIExtension", ciExtensionM);
+  SetupStore("EnableEITScan", eitScanM);
+  StoreCicams("CICAM", cicamsM);
+  StoreSources("DisabledSources", disabledSourcesM);
+  StoreFilters("DisabledFilters", disabledFilterIndexesM);
+  // Update global config
+  SatipConfig.SetOperatingMode(operatingModeM);
+  SatipConfig.SetCIExtension(ciExtensionM);
+  SatipConfig.SetEITScan(eitScanM);
+  for (int i = 0; i < MAX_CICAM_COUNT; ++i)
+      SatipConfig.SetCICAM(i, cicamsM[i]);
+  for (int i = 0; i < MAX_DISABLED_SOURCES_COUNT; ++i)
+      SatipConfig.SetDisabledSources(i, disabledSourcesM[i]);
+  for (int i = 0; i < SECTION_FILTER_TABLE_SIZE; ++i)
+      SatipConfig.SetDisabledFilters(i, disabledFilterIndexesM[i]);
+}
diff --git a/setup.h b/setup.h
new file mode 100644
index 0000000..f2489da
--- /dev/null
+++ b/setup.h
@@ -0,0 +1,49 @@
+/*
+ * setup.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_SETUP_H
+#define __SATIP_SETUP_H
+
+#include <vdr/menuitems.h>
+#include <vdr/sourceparams.h>
+#include "common.h"
+
+class cSatipPluginSetup : public cMenuSetupPage
+{
+private:
+  int deviceCountM;
+  int operatingModeM;
+  const char *operatingModeTextsM[cSatipConfig::eOperatingModeCount];
+  int ciExtensionM;
+  int cicamsM[MAX_CICAM_COUNT];
+  const char *cicamTextsM[CA_SYSTEMS_TABLE_SIZE];
+  int eitScanM;
+  int numDisabledSourcesM;
+  int disabledSourcesM[MAX_DISABLED_SOURCES_COUNT];
+  int numDisabledFiltersM;
+  int disabledFilterIndexesM[SECTION_FILTER_TABLE_SIZE];
+  const char *disabledFilterNamesM[SECTION_FILTER_TABLE_SIZE];
+  cVector<const char*> helpM;
+
+  eOSState DeviceScan(void);
+  eOSState DeviceInfo(void);
+  eOSState ShowDeviceStatus(void);
+  eOSState ShowInfo(void);
+  void Setup(void);
+  void StoreCicams(const char *nameP, int *cicamsP);
+  void StoreSources(const char *nameP, int *sourcesP);
+  void StoreFilters(const char *nameP, int *valuesP);
+
+protected:
+  virtual eOSState ProcessKey(eKeys keyP);
+  virtual void Store(void);
+
+public:
+  cSatipPluginSetup();
+};
+
+#endif // __SATIP_SETUP_H
diff --git a/socket.c b/socket.c
new file mode 100644
index 0000000..358d506
--- /dev/null
+++ b/socket.c
@@ -0,0 +1,195 @@
+/*
+ * socket.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <vdr/device.h>
+
+#include "common.h"
+#include "config.h"
+#include "log.h"
+#include "socket.h"
+
+cSatipSocket::cSatipSocket()
+: socketPortM(0),
+  socketDescM(-1)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  memset(&sockAddrM, 0, sizeof(sockAddrM));
+}
+
+cSatipSocket::~cSatipSocket()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  // Close the socket
+  Close();
+}
+
+bool cSatipSocket::Open(const int portP)
+{
+  // Bind to the socket if it is not active already
+  if (socketDescM < 0) {
+     socklen_t len = sizeof(sockAddrM);
+     // Create socket
+     socketDescM = socket(PF_INET, SOCK_DGRAM, 0);
+     ERROR_IF_RET(socketDescM < 0, "socket()", return false);
+     // Make it use non-blocking I/O to avoid stuck read calls
+     ERROR_IF_FUNC(fcntl(socketDescM, F_SETFL, O_NONBLOCK), "fcntl(O_NONBLOCK)",
+                   Close(), return false);
+     // Allow multiple sockets to use the same PORT number
+     int yes = 1;
+     ERROR_IF_FUNC(setsockopt(socketDescM, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0,
+                   "setsockopt(SO_REUSEADDR)", Close(), return false);
+     // Bind socket
+     memset(&sockAddrM, 0, sizeof(sockAddrM));
+     sockAddrM.sin_family = AF_INET;
+     sockAddrM.sin_port = htons((uint16_t)(portP & 0xFFFF));
+     sockAddrM.sin_addr.s_addr = htonl(INADDR_ANY);
+     ERROR_IF_FUNC(bind(socketDescM, (struct sockaddr *)&sockAddrM, sizeof(sockAddrM)) < 0,
+                   "bind()", Close(), return false);
+     // Update socket port
+     ERROR_IF_FUNC(getsockname(socketDescM,(struct sockaddr*)&sockAddrM, &len) < 0,
+                   "getsockname()", Close(), return false);
+     socketPortM = ntohs(sockAddrM.sin_port);
+     }
+  debug1("%s (%d) socketPort=%d", __PRETTY_FUNCTION__, portP, socketPortM);
+  return true;
+}
+
+void cSatipSocket::Close(void)
+{
+  debug1("%s sockerPort=%d", __PRETTY_FUNCTION__, socketPortM);
+  // Check if socket exists
+  if (socketDescM >= 0) {
+     close(socketDescM);
+     socketDescM = -1;
+     socketPortM = 0;
+     memset(&sockAddrM, 0, sizeof(sockAddrM));
+     }
+}
+
+bool cSatipSocket::Flush(void)
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  if (socketDescM < 0) {
+     const unsigned int len = 65535;
+     unsigned char *buf = MALLOC(unsigned char, len);
+     if (buf) {
+        int i = 0;
+        do {
+           // Sanity check
+           if (++i > 10)
+              break;
+        } while (Read(buf, len));
+        return true;
+        }
+     }
+  return false;
+}
+
+int cSatipSocket::Read(unsigned char *bufferAddrP, unsigned int bufferLenP)
+{
+  debug16("%s (, %d)", __PRETTY_FUNCTION__, bufferLenP);
+  // Error out if socket not initialized
+  if (socketDescM <= 0) {
+     error("%s Invalid socket", __PRETTY_FUNCTION__);
+     return -1;
+     }
+  int len = 0;
+  // Read data from socket in a loop
+  do {
+    socklen_t addrlen = sizeof(sockAddrM);
+    struct msghdr msgh;
+    struct iovec iov;
+    char cbuf[256];
+    len = 0;
+    // Initialize iov and msgh structures
+    memset(&msgh, 0, sizeof(struct msghdr));
+    iov.iov_base = bufferAddrP;
+    iov.iov_len = bufferLenP;
+    msgh.msg_control = cbuf;
+    msgh.msg_controllen = sizeof(cbuf);
+    msgh.msg_name = &sockAddrM;
+    msgh.msg_namelen = addrlen;
+    msgh.msg_iov = &iov;
+    msgh.msg_iovlen = 1;
+    msgh.msg_flags = 0;
+
+    if (socketDescM && bufferAddrP && (bufferLenP > 0))
+       len = (int)recvmsg(socketDescM, &msgh, MSG_DONTWAIT);
+    if (len > 0)
+       return len;
+    } while (len > 0);
+  ERROR_IF_RET(len < 0 && errno != EAGAIN, "recvmsg()", return -1);
+  return 0;
+}
+
+int cSatipSocket::ReadMulti(unsigned char *bufferAddrP, unsigned int *elementRecvSizeP, unsigned int elementCountP, unsigned int elementBufferSizeP)
+{
+  debug16("%s (, , %d, %d)", __PRETTY_FUNCTION__, elementCountP, elementBufferSizeP);
+#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2,12)
+  // Error out if socket not initialized
+  if (socketDescM <= 0) {
+     error("%s Invalid socket", __PRETTY_FUNCTION__);
+     return -1;
+     }
+  // Initialize iov and msgh structures
+  struct mmsghdr mmsgh[elementCountP];
+  struct iovec iov[elementCountP];
+  memset(mmsgh, 0, sizeof(mmsgh[0]) * elementCountP);
+  for (unsigned int i = 0; i < elementCountP; ++i) {
+      iov[i].iov_base = bufferAddrP + i * elementBufferSizeP;
+      iov[i].iov_len = elementBufferSizeP;
+      mmsgh[i].msg_hdr.msg_iov = &iov[i];
+      mmsgh[i].msg_hdr.msg_iovlen = 1;
+      }
+
+  // Read data from socket as a set
+  int count = -1;
+  if (socketDescM && bufferAddrP && elementRecvSizeP && (elementCountP > 0) && (elementBufferSizeP > 0))
+     count = (int)recvmmsg(socketDescM, mmsgh, elementCountP, MSG_DONTWAIT, NULL);
+  ERROR_IF_RET(count < 0 && errno != EAGAIN && errno != EWOULDBLOCK, "recvmmsg()", return -1);
+  for (int i = 0; i < count; ++i)
+      elementRecvSizeP[i] = mmsgh[i].msg_len;
+#else
+  int count = 0;
+  while (count < (int)elementCountP) {
+        int len = Read(bufferAddrP + count * elementBufferSizeP, elementBufferSizeP);
+        if (len < 0)
+           return -1;
+        else if (len == 0)
+           break;
+        elementRecvSizeP[count++] = len;
+        }
+#endif
+  debug16("%s Received %d packets size[0]=%d", __PRETTY_FUNCTION__, count, elementRecvSizeP[0]);
+
+  return count;
+}
+
+
+bool cSatipSocket::Write(const char *addrP, const unsigned char *bufferAddrP, unsigned int bufferLenP)
+{
+  debug1("%s (%s, , %d)", __PRETTY_FUNCTION__, addrP, bufferLenP);
+  // Error out if socket not initialized
+  if (socketDescM <= 0) {
+     error("%s Invalid socket", __PRETTY_FUNCTION__);
+     return false;
+     }
+  struct sockaddr_in sockAddr;
+  memset(&sockAddr, 0, sizeof(sockAddr));
+  sockAddr.sin_family = AF_INET;
+  sockAddr.sin_port = htons((uint16_t)(socketPortM & 0xFFFF));
+  sockAddr.sin_addr.s_addr = inet_addr(addrP);
+  ERROR_IF_RET(sendto(socketDescM, bufferAddrP, bufferLenP, 0, (struct sockaddr *)&sockAddr, sizeof(sockAddr)) < 0, "sendto()", return false);
+  return true;
+}
diff --git a/socket.h b/socket.h
new file mode 100644
index 0000000..f9a93d8
--- /dev/null
+++ b/socket.h
@@ -0,0 +1,34 @@
+/*
+ * socket.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_SOCKET_H
+#define __SATIP_SOCKET_H
+
+#include <arpa/inet.h>
+
+class cSatipSocket {
+private:
+  int socketPortM;
+  int socketDescM;
+  struct sockaddr_in sockAddrM;
+
+public:
+  cSatipSocket();
+  virtual ~cSatipSocket();
+  bool Open(const int portP = 0);
+  virtual void Close(void);
+  int Fd(void) { return socketDescM; }
+  int Port(void) { return socketPortM; }
+  bool IsOpen(void) { return (socketDescM >= 0); }
+  bool Flush(void);
+  int Read(unsigned char *bufferAddrP, unsigned int bufferLenP);
+  int ReadMulti(unsigned char *bufferAddrP, unsigned int *elementRecvSizeP, unsigned int elementCountP, unsigned int elementBufferSizeP);
+  bool Write(const char *addrP, const unsigned char *bufferAddrP, unsigned int bufferLenP);
+};
+
+#endif // __SATIP_SOCKET_H
+
diff --git a/statistics.c b/statistics.c
new file mode 100644
index 0000000..2133e01
--- /dev/null
+++ b/statistics.c
@@ -0,0 +1,222 @@
+/*
+ * statistics.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#include <limits.h>
+
+#include "common.h"
+#include "statistics.h"
+#include "log.h"
+#include "config.h"
+
+// Section statistics class
+cSatipSectionStatistics::cSatipSectionStatistics()
+: filteredDataM(0),
+  numberOfCallsM(0),
+  timerM(),
+  mutexM()
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+}
+
+cSatipSectionStatistics::~cSatipSectionStatistics()
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+}
+
+cString cSatipSectionStatistics::GetSectionStatistic()
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  uint64_t elapsed = timerM.Elapsed(); /* in milliseconds */
+  timerM.Set();
+  long bitrate = elapsed ? (long)(1000.0L * filteredDataM / KILOBYTE(1) / elapsed) : 0L;
+  if (!SatipConfig.GetUseBytes())
+     bitrate *= 8;
+  // no trailing linefeed here!
+  cString s = cString::sprintf("%4ld (%4ld k%s/s)", numberOfCallsM, bitrate,
+                               SatipConfig.GetUseBytes() ? "B" : "bit");
+  filteredDataM = numberOfCallsM = 0;
+  return s;
+}
+
+void cSatipSectionStatistics::AddSectionStatistic(long bytesP, long callsP)
+{
+  debug16("%s (%ld, %ld)", __PRETTY_FUNCTION__, bytesP, callsP);
+  cMutexLock MutexLock(&mutexM);
+  filteredDataM += bytesP;
+  numberOfCallsM += callsP;
+}
+
+// --- cSatipPidStatistics ----------------------------------------------------
+
+// Device statistics class
+cSatipPidStatistics::cSatipPidStatistics()
+: timerM(),
+  mutexM()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+  const int numberOfElements = sizeof(mostActivePidsM) / sizeof(pidStruct);
+  for (int i = 0; i < numberOfElements; ++i) {
+      mostActivePidsM[i].pid = -1;
+      mostActivePidsM[i].dataAmount = 0L;
+      }
+}
+
+cSatipPidStatistics::~cSatipPidStatistics()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+}
+
+cString cSatipPidStatistics::GetPidStatistic()
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  const int numberOfElements = sizeof(mostActivePidsM) / sizeof(pidStruct);
+  uint64_t elapsed = timerM.Elapsed(); /* in milliseconds */
+  timerM.Set();
+  cString s("Active pids:\n");
+  for (int i = 0; i < numberOfElements; ++i) {
+      if (mostActivePidsM[i].pid >= 0) {
+         long bitrate = elapsed ? (long)(1000.0L * mostActivePidsM[i].dataAmount / KILOBYTE(1) / elapsed) : 0L;
+         if (!SatipConfig.GetUseBytes())
+            bitrate *= 8;
+         s = cString::sprintf("%sPid %d: %4d (%4ld k%s/s)\n", *s, i,
+                              mostActivePidsM[i].pid, bitrate,
+                              SatipConfig.GetUseBytes() ? "B" : "bit");
+         }
+      }
+  for (int i = 0; i < numberOfElements; ++i) {
+      mostActivePidsM[i].pid = -1;
+      mostActivePidsM[i].dataAmount = 0L;
+      }
+  return s;
+}
+
+int cSatipPidStatistics::SortPids(const void* data1P, const void* data2P)
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  const pidStruct *comp1 = reinterpret_cast<const pidStruct*>(data1P);
+  const pidStruct *comp2 = reinterpret_cast<const pidStruct*>(data2P);
+  if (comp1->dataAmount > comp2->dataAmount)
+     return -1;
+  if (comp1->dataAmount < comp2->dataAmount)
+     return 1;
+  return 0;
+}
+
+void cSatipPidStatistics::AddPidStatistic(int pidP, long payloadP)
+{
+  debug16("%s (%d, %ld)", __PRETTY_FUNCTION__, pidP, payloadP);
+  cMutexLock MutexLock(&mutexM);
+  const int numberOfElements = sizeof(mostActivePidsM) / sizeof(pidStruct);
+  // If our statistic already is in the array, update it and quit
+  for (int i = 0; i < numberOfElements; ++i) {
+      if (mostActivePidsM[i].pid == pidP) {
+         mostActivePidsM[i].dataAmount += payloadP;
+         // Now re-sort the array and quit
+         qsort(mostActivePidsM, numberOfElements, sizeof(pidStruct), SortPids);
+         return;
+         }
+      }
+  // Apparently our pid isn't in the array. Replace the last element with this
+  // one if new payload is greater
+  if (mostActivePidsM[numberOfElements - 1].dataAmount < payloadP) {
+      mostActivePidsM[numberOfElements - 1].pid = pidP;
+      mostActivePidsM[numberOfElements - 1].dataAmount = payloadP;
+     // Re-sort
+     qsort(mostActivePidsM, numberOfElements, sizeof(pidStruct), SortPids);
+     }
+}
+
+// --- cSatipTunerStatistics --------------------------------------------------
+
+// Tuner statistics class
+cSatipTunerStatistics::cSatipTunerStatistics()
+: dataBytesM(0),
+  timerM(),
+  mutexM()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+}
+
+cSatipTunerStatistics::~cSatipTunerStatistics()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+}
+
+cString cSatipTunerStatistics::GetTunerStatistic()
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  mutexM.Lock();
+  uint64_t elapsed = timerM.Elapsed(); /* in milliseconds */
+  timerM.Set();
+  long bitrate = elapsed ? (long)(1000.0L * dataBytesM / KILOBYTE(1) / elapsed) : 0L;
+  dataBytesM = 0;
+  mutexM.Unlock();
+
+  if (!SatipConfig.GetUseBytes())
+     bitrate *= 8;
+  cString s = cString::sprintf("%ld k%s/s", bitrate, SatipConfig.GetUseBytes() ? "B" : "bit");
+  return s;
+}
+
+void cSatipTunerStatistics::AddTunerStatistic(long bytesP)
+{
+  debug16("%s (%ld)", __PRETTY_FUNCTION__, bytesP);
+  cMutexLock MutexLock(&mutexM);
+  dataBytesM += bytesP;
+}
+
+
+// Buffer statistics class
+cSatipBufferStatistics::cSatipBufferStatistics()
+: dataBytesM(0),
+  freeSpaceM(0),
+  usedSpaceM(0),
+  timerM(),
+  mutexM()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+}
+
+cSatipBufferStatistics::~cSatipBufferStatistics()
+{
+  debug1("%s", __PRETTY_FUNCTION__);
+}
+
+cString cSatipBufferStatistics::GetBufferStatistic()
+{
+  debug16("%s", __PRETTY_FUNCTION__);
+  cMutexLock MutexLock(&mutexM);
+  uint64_t elapsed = timerM.Elapsed(); /* in milliseconds */
+  timerM.Set();
+  long bitrate = elapsed ? (long)(1000.0L * dataBytesM / KILOBYTE(1) / elapsed) : 0L;
+  long totalSpace = SATIP_BUFFER_SIZE;
+  float percentage = (float)((float)usedSpaceM / (float)totalSpace * 100.0);
+  long totalKilos = totalSpace / KILOBYTE(1);
+  long usedKilos = usedSpaceM / KILOBYTE(1);
+  if (!SatipConfig.GetUseBytes()) {
+     bitrate *= 8;
+     totalKilos *= 8;
+     usedKilos *= 8;
+     }
+  cString s = cString::sprintf("Buffer bitrate: %ld k%s/s\nBuffer usage: %ld/%ld k%s (%2.1f%%)\n", bitrate,
+                               SatipConfig.GetUseBytes() ? "B" : "bit", usedKilos, totalKilos,
+                               SatipConfig.GetUseBytes() ? "B" : "bit", percentage);
+  dataBytesM = 0;
+  usedSpaceM = 0;
+  return s;
+}
+
+void cSatipBufferStatistics::AddBufferStatistic(long bytesP, long usedP)
+{
+  debug16("%s (%ld, %ld)", __PRETTY_FUNCTION__, bytesP, usedP);
+  cMutexLock MutexLock(&mutexM);
+  dataBytesM += bytesP;
+  if (usedP > usedSpaceM)
+     usedSpaceM = usedP;
+}
diff --git a/statistics.h b/statistics.h
new file mode 100644
index 0000000..f7c4907
--- /dev/null
+++ b/statistics.h
@@ -0,0 +1,87 @@
+/*
+ * statistics.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_STATISTICS_H
+#define __SATIP_STATISTICS_H
+
+#include <vdr/thread.h>
+
+// Section statistics
+class cSatipSectionStatistics {
+public:
+  cSatipSectionStatistics();
+  virtual ~cSatipSectionStatistics();
+  cString GetSectionStatistic();
+
+protected:
+  void AddSectionStatistic(long bytesP, long callsP);
+
+private:
+  long filteredDataM;
+  long numberOfCallsM;
+  cTimeMs timerM;
+  cMutex mutexM;
+};
+
+// Pid statistics
+class cSatipPidStatistics {
+public:
+  cSatipPidStatistics();
+  virtual ~cSatipPidStatistics();
+  cString GetPidStatistic();
+
+protected:
+  void AddPidStatistic(int pidP, long payloadP);
+
+private:
+  struct pidStruct {
+    int  pid;
+    long dataAmount;
+  };
+  pidStruct mostActivePidsM[SATIP_STATS_ACTIVE_PIDS_COUNT];
+  cTimeMs timerM;
+  cMutex mutexM;
+
+private:
+  static int SortPids(const void* data1P, const void* data2P);
+};
+
+// Tuner statistics
+class cSatipTunerStatistics {
+public:
+  cSatipTunerStatistics();
+  virtual ~cSatipTunerStatistics();
+  cString GetTunerStatistic();
+
+protected:
+  void AddTunerStatistic(long bytesP);
+
+private:
+  long dataBytesM;
+  cTimeMs timerM;
+  cMutex mutexM;
+};
+
+// Buffer statistics
+class cSatipBufferStatistics {
+public:
+  cSatipBufferStatistics();
+  virtual ~cSatipBufferStatistics();
+  cString GetBufferStatistic();
+
+protected:
+  void AddBufferStatistic(long bytesP, long usedP);
+
+private:
+  long dataBytesM;
+  long freeSpaceM;
+  long usedSpaceM;
+  cTimeMs timerM;
+  cMutex mutexM;
+};
+
+#endif // __SATIP_STATISTICS_H
diff --git a/tuner.c b/tuner.c
new file mode 100644
index 0000000..5c770bf
--- /dev/null
+++ b/tuner.c
@@ -0,0 +1,589 @@
+/*
+ * tuner.c: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#define __STDC_FORMAT_MACROS // Required for format specifiers
+#include <inttypes.h>
+
+#include "common.h"
+#include "config.h"
+#include "discover.h"
+#include "log.h"
+#include "poller.h"
+#include "tuner.h"
+
+cSatipTuner::cSatipTuner(cSatipDeviceIf &deviceP, unsigned int packetLenP)
+: cThread(cString::sprintf("SATIP#%d tuner", deviceP.GetId())),
+  sleepM(),
+  deviceM(&deviceP),
+  deviceIdM(deviceP.GetId()),
+  rtspM(*this),
+  rtpM(*this),
+  rtcpM(*this),
+  streamAddrM(""),
+  streamParamM(""),
+  currentServerM(NULL),
+  nextServerM(NULL),
+  mutexM(),
+  reConnectM(),
+  keepAliveM(),
+  statusUpdateM(),
+  pidUpdateCacheM(),
+  sessionM(""),
+  currentStateM(tsIdle),
+  internalStateM(),
+  externalStateM(),
+  timeoutM(eMinKeepAliveIntervalMs),
+  hasLockM(false),
+  signalStrengthM(-1),
+  signalQualityM(-1),
+  streamIdM(-1),
+  pmtPidM(-1),
+  addPidsM(),
+  delPidsM(),
+  pidsM()
+{
+  debug1("%s (, %d) [device %d]", __PRETTY_FUNCTION__, packetLenP, deviceIdM);
+
+  // Open sockets
+  int i = 100;
+  while (i-- > 0) {
+        if (rtpM.Open(0) && rtcpM.Open(rtpM.Port() + 1))
+           break;
+        rtpM.Close();
+        rtcpM.Close();
+        }
+  if ((rtpM.Port() <= 0) || (rtcpM.Port() <= 0)) {
+     error("Cannot open required RTP/RTCP ports [device %d]", deviceIdM);
+     }
+  // Must be done after socket initialization!
+  cSatipPoller::GetInstance()->Register(rtpM);
+  cSatipPoller::GetInstance()->Register(rtcpM);
+
+  // Start thread
+  Start();
+}
+
+cSatipTuner::~cSatipTuner()
+{
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+
+  // Stop thread
+  sleepM.Signal();
+  if (Running())
+     Cancel(3);
+  Close();
+  currentStateM = tsIdle;
+  internalStateM.Clear();
+  externalStateM.Clear();
+
+  // Close the listening sockets
+  cSatipPoller::GetInstance()->Unregister(rtcpM);
+  cSatipPoller::GetInstance()->Unregister(rtpM);
+  rtcpM.Close();
+  rtpM.Close();
+}
+
+void cSatipTuner::Action(void)
+{
+  debug1("%s Entering [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+  reConnectM.Set(eConnectTimeoutMs);
+  // Do the thread loop
+  while (Running()) {
+        UpdateCurrentState();
+        switch (currentStateM) {
+          case tsIdle:
+               debug4("%s: tsIdle [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+               break;
+          case tsRelease:
+               debug4("%s: tsRelease [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+               Disconnect();
+               RequestState(tsIdle, smInternal);
+               break;
+          case tsSet:
+               debug4("%s: tsSet [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+               if (Connect()) {
+                  RequestState(tsTuned, smInternal);
+                  UpdatePids(true);
+                  }
+               else
+                  Disconnect();
+               break;
+          case tsTuned:
+               debug4("%s: tsTuned [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+               reConnectM.Set(eConnectTimeoutMs);
+               // Read reception statistics via DESCRIBE and RTCP
+               if (hasLockM || ReadReceptionStatus()) {
+                  // Quirk for devices without valid reception data
+                  if (currentServerM && currentServerM->Quirk(cSatipServer::eSatipQuirkForceLock)) {
+                     hasLockM = true;
+                     signalStrengthM = eDefaultSignalStrength;
+                     signalQualityM = eDefaultSignalQuality;
+                     }
+                  if (hasLockM)
+                     RequestState(tsLocked, smInternal);
+                  }
+               break;
+          case tsLocked:
+               debug4("%s: tsLocked [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+               if (!UpdatePids()) {
+                  error("Pid update failed - retuning [device %d]", deviceIdM);
+                  RequestState(tsSet, smInternal);
+                  break;
+                  }
+               if (!KeepAlive()) {
+                  error("Keep-alive failed - retuning [device %d]", deviceIdM);
+                  RequestState(tsSet, smInternal);
+                  break;
+                  }
+               if (reConnectM.TimedOut()) {
+                  error("Connection timeout - retuning [device %d]", deviceIdM);
+                  RequestState(tsSet, smInternal);
+                  break;
+                  }
+               break;
+          default:
+               error("Unknown tuner status %d [device %d]", currentStateM, deviceIdM);
+               break;
+          }
+        if (!StateRequested())
+           sleepM.Wait(eSleepTimeoutMs); // to avoid busy loop and reduce cpu load
+        }
+  debug1("%s Exiting [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+}
+
+bool cSatipTuner::Open(void)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+
+  RequestState(tsSet, smExternal);
+
+  // return always true
+  return true;
+}
+
+bool cSatipTuner::Close(void)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+
+  RequestState(tsRelease, smExternal);
+
+  // return always true
+  return true;
+}
+
+bool cSatipTuner::Connect(void)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+
+  if (!isempty(*streamAddrM)) {
+     cString connectionUri = cString::sprintf("rtsp://%s/", *streamAddrM);
+     // Just retune
+     if (streamIdM >= 0) {
+        cString uri = cString::sprintf("%sstream=%d?%s", *connectionUri, streamIdM, *streamParamM);
+        debug1("%s Retuning [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+        if (rtspM.Play(*uri)) {
+           keepAliveM.Set(timeoutM);
+           return true;
+           }
+        }
+     else if (rtspM.Options(*connectionUri)) {
+        cString uri = cString::sprintf("%s?%s", *connectionUri, *streamParamM);
+        // Flush any old content
+        rtpM.Flush();
+        rtcpM.Flush();
+        if (rtspM.Setup(*uri, rtpM.Port(), rtcpM.Port())) {
+           keepAliveM.Set(timeoutM);
+           if (nextServerM) {
+              cSatipDiscover::GetInstance()->UseServer(nextServerM, true);
+              currentServerM = nextServerM;
+              nextServerM = NULL;
+              }
+           return true;
+           }
+        }
+     else
+        rtspM.Reset();
+     streamIdM = -1;
+     error("Connect failed [device %d]", deviceIdM);
+     }
+
+  return false;
+}
+
+bool cSatipTuner::Disconnect(void)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug1("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+
+  if (!isempty(*streamAddrM) && (streamIdM >= 0)) {
+     cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM);
+     rtspM.Teardown(*uri);
+     // some devices requires a teardown for TCP connection also
+     rtspM.Reset();
+     streamIdM = -1;
+     }
+
+  // Reset signal parameters
+  hasLockM = false;
+  signalStrengthM = -1;
+  signalQualityM = -1;
+
+  if (currentServerM)
+     cSatipDiscover::GetInstance()->UseServer(currentServerM, false);
+  statusUpdateM.Set(0);
+  timeoutM = eMinKeepAliveIntervalMs;
+  pmtPidM = -1;
+  addPidsM.Clear();
+  delPidsM.Clear();
+
+  // return always true
+  return true;
+}
+
+void cSatipTuner::ProcessVideoData(u_char *bufferP, int lengthP)
+{
+  debug16("%s (, %d) [device %d]", __PRETTY_FUNCTION__, lengthP, deviceIdM);
+  if (lengthP > 0) {
+     uint64_t elapsed;
+     cTimeMs processing(0);
+
+     AddTunerStatistic(lengthP);
+     elapsed = processing.Elapsed();
+     if (elapsed > 1)
+        debug6("%s AddTunerStatistic() took %" PRIu64 " ms [device %d]", __PRETTY_FUNCTION__, elapsed, deviceIdM);
+
+     processing.Set(0);
+     deviceM->WriteData(bufferP, lengthP);
+     elapsed = processing.Elapsed();
+     if (elapsed > 1)
+        debug6("%s WriteData() took %" PRIu64 " ms [device %d]", __FUNCTION__, elapsed, deviceIdM);
+     }
+  reConnectM.Set(eConnectTimeoutMs);
+}
+
+void cSatipTuner::ProcessApplicationData(u_char *bufferP, int lengthP)
+{
+  debug16("%s (%d) [device %d]", __PRETTY_FUNCTION__, lengthP, deviceIdM);
+  // DVB-S2:
+  // ver=<major>.<minor>;src=<srcID>;tuner=<feID>,<level>,<lock>,<quality>,<frequency>,<polarisation>,<system>,<type>,<pilots>,<roll_off>,<symbol_rate>,<fec_inner>;pids=<pid0>,...,<pidn>
+  // DVB-T2:
+  // ver=1.1;tuner=<feID>,<level>,<lock>,<quality>,<freq>,<bw>,<msys>,<tmode>,<mtype>,<gi>,<fec>,<plp>,<t2id>,<sm>;pids=<pid0>,...,<pidn>
+  // DVB-C2:
+  // ver=1.2;tuner=<feID>,<level>,<lock>,<quality>,<freq>,<bw>,<msys>,<mtype>,<sr>,<c2tft>,<ds>,<plp>,<specinv>;pids=<pid0>,...,<pidn>
+  if (lengthP > 0) {
+     char s[lengthP];
+     memcpy(s, (char *)bufferP, lengthP);
+     debug10("%s (%s) [device %d]", __PRETTY_FUNCTION__, s, deviceIdM);
+     char *c = strstr(s, ";tuner=");
+     if (c)  {
+        int value;
+
+        // level:
+        // Numerical value between 0 and 255
+        // An incoming L-band satellite signal of
+        // -25dBm corresponds to 224
+        // -65dBm corresponds to 32
+        // No signal corresponds to 0
+        c = strstr(c, ",");
+        value = min(atoi(++c), 255);
+        // Scale value to 0-100
+        signalStrengthM = (value >= 0) ? (value * 100 / 255) : -1;
+
+        // lock:
+        // lock Set to one of the following values:
+        // "0" the frontend is not locked
+        // "1" the frontend is locked
+        c = strstr(c, ",");
+        hasLockM = !!atoi(++c);
+
+        // quality:
+        // Numerical value between 0 and 15
+        // Lowest value corresponds to highest error rate
+        // The value 15 shall correspond to
+        // -a BER lower than 2x10-4 after Viterbi for DVB-S
+        // -a PER lower than 10-7 for DVB-S2
+        c = strstr(c, ",");
+        value = min(atoi(++c), 15);
+        // Scale value to 0-100
+        signalQualityM = (hasLockM && (value >= 0)) ? (value * 100 / 15) : 0;
+        }
+     }
+  reConnectM.Set(eConnectTimeoutMs);
+}
+
+void cSatipTuner::SetStreamId(int streamIdP)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug1("%s (%d) [device %d]", __PRETTY_FUNCTION__, streamIdP, deviceIdM);
+  streamIdM = streamIdP;
+}
+
+void cSatipTuner::SetSessionTimeout(const char *sessionP, int timeoutP)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug1("%s (%s, %d) [device %d]", __PRETTY_FUNCTION__, sessionP, timeoutP, deviceIdM);
+  sessionM = sessionP;
+  if (nextServerM && nextServerM->Quirk(cSatipServer::eSatipQuirkSessionId) && !isempty(*sessionM) && startswith(*sessionM, "0"))
+     rtspM.SetSession(SkipZeroes(*sessionM));
+  timeoutM = (timeoutP > eMinKeepAliveIntervalMs) ? timeoutP : eMinKeepAliveIntervalMs;
+}
+
+int cSatipTuner::GetId(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+  return deviceIdM;
+}
+
+bool cSatipTuner::SetSource(cSatipServer *serverP, const char *parameterP, const int indexP)
+{
+  debug1("%s (%s, %d) [device %d]", __PRETTY_FUNCTION__, parameterP, indexP, deviceIdM);
+  cMutexLock MutexLock(&mutexM);
+  if (serverP) {
+     nextServerM = cSatipDiscover::GetInstance()->GetServer(serverP);
+     if (nextServerM && !isempty(nextServerM->Address()) && !isempty(parameterP)) {
+        // Update stream address and parameter
+        streamAddrM = rtspM.RtspUnescapeString(nextServerM->Address());
+        streamParamM = rtspM.RtspUnescapeString(parameterP);
+        // Reconnect
+        RequestState(tsSet, smExternal);
+        }
+     }
+  else {
+     streamAddrM = "";
+     streamParamM = "";
+     }
+
+  return true;
+}
+
+bool cSatipTuner::SetPid(int pidP, int typeP, bool onP)
+{
+  debug16("%s (%d, %d, %d) [device %d]", __PRETTY_FUNCTION__, pidP, typeP, onP, deviceIdM);
+  cMutexLock MutexLock(&mutexM);
+  if (onP) {
+     pidsM.AddPid(pidP);
+     addPidsM.AddPid(pidP);
+     delPidsM.RemovePid(pidP);
+     }
+  else {
+     pidsM.RemovePid(pidP);
+     delPidsM.AddPid(pidP);
+     addPidsM.RemovePid(pidP);
+     }
+  debug12("%s (%d, %d, %d) pids=%s [device %d]", __PRETTY_FUNCTION__, pidP, typeP, onP, *pidsM.ListPids(), deviceIdM);
+  sleepM.Signal();
+
+  return true;
+}
+
+bool cSatipTuner::UpdatePids(bool forceP)
+{
+  debug16("%s (%d) tunerState=%s [device %d]", __PRETTY_FUNCTION__, forceP, TunerStateString(currentStateM), deviceIdM);
+  cMutexLock MutexLock(&mutexM);
+  if (((forceP && pidsM.Size()) || (pidUpdateCacheM.TimedOut() && (addPidsM.Size() || delPidsM.Size()))) &&
+      !isempty(*streamAddrM) && (streamIdM > 0)) {
+     cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM);
+     bool useci = (SatipConfig.GetCIExtension() && !!(currentServerM && currentServerM->Quirk(cSatipServer::eSatipQuirkUseXCI)));
+     bool usedummy = !!(currentServerM && currentServerM->Quirk(cSatipServer::eSatipQuirkPlayPids));
+     if (forceP || usedummy) {
+        if (pidsM.Size())
+           uri = cString::sprintf("%s?pids=%s", *uri, *pidsM.ListPids());
+        if (usedummy && (pidsM.Size() == 1) && (pidsM[0] < 0x20))
+           uri = cString::sprintf("%s,%d", *uri, eDummyPid);
+        }
+     else {
+        if (addPidsM.Size())
+           uri = cString::sprintf("%s?addpids=%s", *uri, *addPidsM.ListPids());
+        if (delPidsM.Size())
+           uri = cString::sprintf("%s%sdelpids=%s", *uri, addPidsM.Size() ? "&" : "?", *delPidsM.ListPids());
+        }
+     if (useci) {
+        // CI extension parameters:
+        // - x_pmt : specifies the PMT of the service you want the CI to decode
+        // - x_ci  : specfies which CI slot (1..n) to use
+        //           value 0 releases the CI slot
+        //           CI slot released automatically if the stream is released,
+        //           but not when used retuning to another channel
+        int pid = deviceM->GetPmtPid();
+        if ((pid > 0) && (pid != pmtPidM)) {
+           int slot = deviceM->GetCISlot();
+           uri = cString::sprintf("%s&x_pmt=%d", *uri, pid);
+           if (slot > 0)
+              uri = cString::sprintf("%s&x_ci=%d", *uri, slot);
+           }
+        pmtPidM = pid;
+        }
+     pidUpdateCacheM.Set(ePidUpdateIntervalMs);
+     if (!rtspM.Play(*uri))
+        return false;
+     addPidsM.Clear();
+     delPidsM.Clear();
+     }
+
+  return true;
+}
+
+bool cSatipTuner::KeepAlive(bool forceP)
+{
+  debug16("%s (%d) tunerState=%s [device %d]", __PRETTY_FUNCTION__, forceP, TunerStateString(currentStateM), deviceIdM);
+  cMutexLock MutexLock(&mutexM);
+  if (keepAliveM.TimedOut()) {
+     keepAliveM.Set(timeoutM);
+     forceP = true;
+     }
+  if (forceP && !isempty(*streamAddrM)) {
+     cString uri = cString::sprintf("rtsp://%s/", *streamAddrM);
+     if (!rtspM.Options(*uri))
+        return false;
+     }
+
+  return true;
+}
+
+bool cSatipTuner::ReadReceptionStatus(bool forceP)
+{
+  debug16("%s (%d) tunerState=%s [device %d]", __PRETTY_FUNCTION__, forceP, TunerStateString(currentStateM), deviceIdM);
+  cMutexLock MutexLock(&mutexM);
+  if (statusUpdateM.TimedOut()) {
+     statusUpdateM.Set(eStatusUpdateTimeoutMs);
+     forceP = true;
+     }
+  if (forceP && !isempty(*streamAddrM) && (streamIdM > 0)) {
+     cString uri = cString::sprintf("rtsp://%s/stream=%d", *streamAddrM, streamIdM);
+     if (rtspM.Describe(*uri))
+        return true;
+     }
+
+  return false;
+}
+
+void cSatipTuner::UpdateCurrentState(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+  cMutexLock MutexLock(&mutexM);
+  eTunerState state = currentStateM;
+
+  if (internalStateM.Size()) {
+     state = internalStateM.At(0);
+     internalStateM.Remove(0);
+     }
+  else if (externalStateM.Size()) {
+     state = externalStateM.At(0);
+     externalStateM.Remove(0);
+     }
+
+  if (currentStateM != state) {
+     debug1("%s: Switching from %s to %s [device %d]", __PRETTY_FUNCTION__, TunerStateString(currentStateM), TunerStateString(state), deviceIdM);
+     currentStateM = state;
+     }
+}
+
+bool cSatipTuner::StateRequested(void)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug16("%s current=%s internal=%d external=%d [device %d]", __PRETTY_FUNCTION__, TunerStateString(currentStateM), internalStateM.Size(), externalStateM.Size(), deviceIdM);
+
+  return (internalStateM.Size() || externalStateM.Size());
+}
+
+bool cSatipTuner::RequestState(eTunerState stateP, eStateMode modeP)
+{
+  cMutexLock MutexLock(&mutexM);
+  debug1("%s (%s, %s) current=%s internal=%d external=%d [device %d]", __PRETTY_FUNCTION__, TunerStateString(stateP), StateModeString(modeP), TunerStateString(currentStateM), internalStateM.Size(), externalStateM.Size(), deviceIdM);
+
+  if (modeP == smExternal)
+     externalStateM.Append(stateP);
+  else if (modeP == smInternal) {
+     eTunerState state = internalStateM.Size() ? internalStateM.At(internalStateM.Size() - 1) : currentStateM;
+
+     // validate legal state changes
+     switch (state) {
+       case tsIdle:
+            if (stateP == tsRelease)
+               return false;
+       case tsRelease:
+       case tsSet:
+       case tsLocked:
+       case tsTuned:
+       default:
+            break;
+       }
+
+     internalStateM.Append(stateP);
+     }
+  else
+     return false;
+
+  return true;
+}
+
+const char *cSatipTuner::StateModeString(eStateMode modeP)
+{
+  switch (modeP) {
+    case smInternal:
+         return "smInternal";
+    case smExternal:
+         return "smExternal";
+     default:
+         break;
+    }
+
+   return "---";
+}
+
+const char *cSatipTuner::TunerStateString(eTunerState stateP)
+{
+  switch (stateP) {
+    case tsIdle:
+         return "tsIdle";
+    case tsRelease:
+         return "tsRelease";
+    case tsSet:
+         return "tsSet";
+    case tsLocked:
+         return "tsLocked";
+    case tsTuned:
+         return "tsTuned";
+    default:
+         break;
+    }
+
+  return "---";
+}
+
+int cSatipTuner::SignalStrength(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+  return signalStrengthM;
+}
+
+int cSatipTuner::SignalQuality(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+  return signalQualityM;
+}
+
+bool cSatipTuner::HasLock(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+  return (currentStateM >= tsTuned) && hasLockM;
+}
+
+cString cSatipTuner::GetSignalStatus(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+  return cString::sprintf("lock=%d strength=%d quality=%d", HasLock(), SignalStrength(), SignalQuality());
+}
+
+cString cSatipTuner::GetInformation(void)
+{
+  debug16("%s [device %d]", __PRETTY_FUNCTION__, deviceIdM);
+  return (currentStateM >= tsTuned) ? cString::sprintf("rtsp://%s/?%s [stream=%d]", *streamAddrM, *streamParamM, streamIdM) : "connection failed";
+}
diff --git a/tuner.h b/tuner.h
new file mode 100644
index 0000000..aa753f6
--- /dev/null
+++ b/tuner.h
@@ -0,0 +1,161 @@
+/*
+ * tuner.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_TUNER_H
+#define __SATIP_TUNER_H
+
+#include <vdr/config.h> // APIVERSNUM
+#include <vdr/thread.h>
+#include <vdr/tools.h>
+
+#include "deviceif.h"
+#include "rtp.h"
+#include "rtcp.h"
+#include "rtsp.h"
+#include "server.h"
+#include "statistics.h"
+
+class cSatipPid : public cVector<int> {
+private:
+  static int PidCompare(const void *aPidP, const void *bPidP)
+  {
+    return (*(int*)aPidP - *(int*)bPidP);
+  }
+
+public:
+#if defined(APIVERSNUM) && APIVERSNUM < 20107
+  int IndexOf(const int &pidP)
+  {
+    for (int i = 0; i < Size(); ++i) {
+        if (pidP == At(i))
+           return i;
+        }
+    return -1;
+  }
+  bool RemoveElement(const int &pidP)
+  {
+    int i = IndexOf(pidP);
+    if (i >= 0) {
+       Remove(i);
+       return true;
+       }
+    return false;
+  }
+  bool AppendUnique(int pidP)
+  {
+    if (IndexOf(pidP) < 0) {
+       Append(pidP);
+       return true;
+       }
+    return false;
+  }
+#endif
+  void RemovePid(const int &pidP)
+  {
+    if (RemoveElement(pidP))
+       Sort(PidCompare);
+  }
+  void AddPid(int pidP)
+  {
+    if (AppendUnique(pidP))
+       Sort(PidCompare);
+  }
+  cString ListPids(void)
+  {
+    cString list = "";
+    if (Size()) {
+       for (int i = 0; i < Size(); ++i)
+           list = cString::sprintf("%s%d,", *list, At(i));
+       list = list.Truncate(-1);
+       }
+    return list;
+  }
+};
+
+class cSatipTuner : public cThread, public cSatipTunerStatistics, public cSatipTunerIf
+{
+private:
+  enum {
+    eDummyPid               = 100,
+    eDefaultSignalStrength  = 15,
+    eDefaultSignalQuality   = 224,
+    eSleepTimeoutMs         = 250,   // in milliseconds
+    eStatusUpdateTimeoutMs  = 1000,  // in milliseconds
+    ePidUpdateIntervalMs    = 250,   // in milliseconds
+    eConnectTimeoutMs       = 5000,  // in milliseconds
+    eMinKeepAliveIntervalMs = 30000  // in milliseconds
+  };
+  enum eTunerState { tsIdle, tsRelease, tsSet, tsTuned, tsLocked };
+  enum eStateMode { smInternal, smExternal };
+
+  cCondWait sleepM;
+  cSatipDeviceIf* deviceM;
+  int deviceIdM;
+  cSatipRtsp rtspM;
+  cSatipRtp rtpM;
+  cSatipRtcp rtcpM;
+  cString streamAddrM;
+  cString streamParamM;
+  cSatipServer *currentServerM;
+  cSatipServer *nextServerM;
+  cMutex mutexM;
+  cTimeMs reConnectM;
+  cTimeMs keepAliveM;
+  cTimeMs statusUpdateM;
+  cTimeMs pidUpdateCacheM;
+  cString sessionM;
+  eTunerState currentStateM;
+  cVector<eTunerState> internalStateM;
+  cVector<eTunerState> externalStateM;
+  int timeoutM;
+  bool hasLockM;
+  int signalStrengthM;
+  int signalQualityM;
+  int streamIdM;
+  int pmtPidM;
+  cSatipPid addPidsM;
+  cSatipPid delPidsM;
+  cSatipPid pidsM;
+
+  bool Connect(void);
+  bool Disconnect(void);
+  bool KeepAlive(bool forceP = false);
+  bool ReadReceptionStatus(bool forceP = false);
+  bool UpdatePids(bool forceP = false);
+  void UpdateCurrentState(void);
+  bool StateRequested(void);
+  bool RequestState(eTunerState stateP, eStateMode modeP);
+  const char *StateModeString(eStateMode modeP);
+  const char *TunerStateString(eTunerState stateP);
+
+protected:
+  virtual void Action(void);
+
+public:
+  cSatipTuner(cSatipDeviceIf &deviceP, unsigned int packetLenP);
+  virtual ~cSatipTuner();
+  bool IsTuned(void) const { return (currentStateM >= tsTuned); }
+  bool SetSource(cSatipServer *serverP, const char *parameterP, const int indexP);
+  bool SetPid(int pidP, int typeP, bool onP);
+  bool Open(void);
+  bool Close(void);
+  int SignalStrength(void);
+  int SignalQuality(void);
+  bool HasLock(void);
+  cString GetSignalStatus(void);
+  cString GetInformation(void);
+
+  // for internal tuner interface
+public:
+  virtual void ProcessVideoData(u_char *bufferP, int lengthP);
+  virtual void ProcessApplicationData(u_char *bufferP, int lengthP);
+  virtual void SetStreamId(int streamIdP);
+  virtual void SetSessionTimeout(const char *sessionP, int timeoutP);
+  virtual int GetId(void);
+};
+
+#endif // __SATIP_TUNER_H
diff --git a/tunerif.h b/tunerif.h
new file mode 100644
index 0000000..3790bcd
--- /dev/null
+++ b/tunerif.h
@@ -0,0 +1,26 @@
+/*
+ * tunerif.h: SAT>IP plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ */
+
+#ifndef __SATIP_TUNERIF_H
+#define __SATIP_TUNERIF_H
+
+class cSatipTunerIf {
+public:
+  cSatipTunerIf() {}
+  virtual ~cSatipTunerIf() {}
+  virtual void ProcessVideoData(u_char *bufferP, int lengthP) = 0;
+  virtual void ProcessApplicationData(u_char *bufferP, int lengthP) = 0;
+  virtual void SetStreamId(int streamIdP) = 0;
+  virtual void SetSessionTimeout(const char *sessionP, int timeoutP) = 0;
+  virtual int GetId(void) = 0;
+
+private:
+  cSatipTunerIf(const cSatipTunerIf&);
+  cSatipTunerIf& operator=(const cSatipTunerIf&);
+};
+
+#endif // __SATIP_TUNERIF_H

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-vdr-dvb/vdr-plugin-satip.git



More information about the pkg-vdr-dvb-changes mailing list