diff --git a/HISTORY.LNBshare b/HISTORY.LNBshare new file mode 100644 index 0000000..d6eeb7f --- /dev/null +++ b/HISTORY.LNBshare @@ -0,0 +1,42 @@ +configurableLNBshare Revision History + +Version for VDR 1.4.3 + +- configurableLNBshare-VDR_1.4.3.patch from Matthias Lötzke (http://lötzke.de/vdr.html) + +2010-04-10: Version for VDR 1.7.14 +- Created HISTORY.LNBshare +- Created README.LNBshare +- Fixed possible buffer overflow in cMenuSetupLNB::Setup(void) +- Ignore all non-DVB-S devices when checking for possible conflicts. +- Use the 'actual' device instead of the 'primary' device as device that is used as reciever for live viewing. This adds support for 'other' output devices than a FF card like xine. Based on a patch from CR7 at vdr-portal.de +- Adept to VDR 1.7.14. Support the new diseqc feature introduced with VDR 1.7.13: 'Added device definitions to the diseqc.conf file format, so that certain satellite positions can be limited to a given list of devices'. + +2010-11-13: +- Fix: Devices sharing same LNB don't switch channel on main channel switch (thanks to hivdr @ vdr-portal.de for reporting the bug and spockele @ vdr-portal.de and hopsi @ vdr-portal.de for testing the correction) +- Fix: In LOG, device numbering is now as in VDR: Starting from 1, ... +- Fix: Encoding of german umlaut in "DVB-Empfänger %d nutzt LNB Nr" + +2010-11-28: +- Fix: Device numbering in message 'LNB or DiSEq conflict with device ...'. +- Fix: Numbering of LNBs not using SAT + +2010-12-25: Version 0.1.0 for VDR version 1.7.16 +- Rename the patch to include patch version number (vdr-lnb-sharing-0.1.0-1.7.16.patch) +- Write patch version number to log file (if logging is switched on). +- Adept to VDR 1.7.16 + +2011-02-06 Version 0.1.1 for VDR version 1.7.16 +- Fix: If several DVB devices share one LNB, only the first of these devises will send signals like 22kHz. See README.LNBshare for details. + Thanks to Uwe (Licherkette @ vdr-portal.de) for finding and analyzing the error and testing the correction. +- README.LNBshare: Include warning about 1.3 FF cards with buggy Loop-Through that might result in destruction of LNBP-Chips if the Loop-Through is used +- Fix: Log file: Numbering of 'LNBs' not connected to a SAT DVB card + +2011-02-08 Version 0.1.2 for VDR version 1.7.16 +- Change: Make it easier to apply this patch together with the dynamite-Plugin patch. Note: this is mostly for maintainers of distributions and multi-patches. It will (must probably) not be possible for devices added with dynamite to share LNBs. Some concept work is required for this: How can VDR uniquely identify a device? How can a user identify such a device on OSD? + +2011-02-16 Version 0.1.3 for VDR version 1.7.16 +- Fix an error that can occur if devices are explizitly specified (-D or --device) during start of VDR. Thanks to Lars (mini73 @ vdr-portal.de) for reporting this error. + +2011-02-18 Version 0.1.4 for VDR version 1.7.16 +- Fix: Short before a recording starts, the system switches between black screen and channel. If VPS is used, the recording will not even start. Thanks to urknall @ vdr-portal.de for reporting this error and Uwe (Licherkette @ vdr-portal.de) for providing a Log diff --git a/README.LNBshare b/README.LNBshare new file mode 100644 index 0000000..1d624bf --- /dev/null +++ b/README.LNBshare @@ -0,0 +1,32 @@ +Warning +- At some 1.3 FF-Cards a voltage at the Loop-Through will destroy the LNBP-Chips of these cards. Please check the manual. If you are unsure or your card is affected, don't use the Loop-Through! Even if this patch avoids switching on the voltage in such a case, the patch might be buggy or wrong configured or ... + +Required hardware: +- sat splitter like SVE 2-01 (reichelt.de). Make sure to have a sat splitter with built in diodes preventing any current in case of different voltage on the connected DVB cards. + +Settings (OSD) +In VDR, if you select Settings -> LNB and have two or more devices that can recieve SAT channels, you can set the 'LNB Nr.' for each of these devices. Note: +- All devices with an identical 'LNB Nr.' share the same LNB +- The patch assumes that a device has always the same number. If you have more than two devices, you must make sure to load the drivers in a given order to make sure that each of the listed devices belongs always to the same card. If during setup, for example, your first DVB-S card is device 2 and your second DVB-S card is device 4 (device 1 and 3 are, for example, DVB-T cards) you must make sure that after reboot these stay the same: your first DVB-S card is device 2 and your second DVB-S card is device 4. +- Signals (like DiSEqC, 22kHz, Voltage) are only sent by the first device if several devices share the same LNB. VDR will even switch on the LNB Voltage only on the first device. Note: This is not guarantied: If you use a Loop-Through, please see the warning in the README and make sure that the FF Card is the first card. + +DiSEqC Support: +DiSEqC is supported, including the feature added in VDR 1.7.13: Added device definitions to the diseqc.conf file format, so that certain satellite positions can be limited to a given list of devices +Note: Please ensure in diseqc.conf that all devices sharing one LNB have exactly the same satellite positions. The system will not check this, but, otherwise, the patch will not work correctly. This is not a restriction: obviously, if two devices share the same LNB, they can also receive the same satellite positions. + +Primary limit (Setup.PrimaryLimit): +See MANUAL for a description: 'This is mainly useful for recordings that should take place only when there is nothing else to do, but should never keep the user from viewing stuff on the primary interface' +In unpatched VDR, this seems to work with FF cards. For other output devices (for example vdr-xine), it probably won't work. +Not fully supported by LNB sharing: If the primary device shares an LNB with device2, device2 might start a recording with priority < Setup.PrimaryLimit and force the primary device to an other channel. + +TODO / Missing: +Intelligent assigning of timers to devices. +Example: If you have 3 DVB-S cards and 2 of them share the same LNB, you can, in principle, record 2 channels in 'high' band and one in 'low' band. But, with this patch, this is not guaranteed. The DVB-S card with an own LNB might record one of the channels in high band. So, one of the other channels will not be recorded. +Note: +- Such intelligent scheduling is generally missing in VDR and would be also useful for 'mixed' systems with DVB-S and DVB-T cards as well as systems in which not all DVB-S cards can receive the same satellites. +- I don't plan to implement this feature. Any volunteers? + +Hompage: http://projects.vdr-developer.org/projects/patch-lnbsharing . Please use this for bug reports. You can also create a new thread in http://www.vdr-portal.de , Developer->Patches for any questions. +Git: The patch is available in http://projects.vdr-developer.org/git/?p=vdr-patch-lnbsharing.git + +License: The same license as for VDR applies. See file COPYING (in VDR sources) for details. diff --git a/ci.h b/ci.h index c31dccf..daa18f6 100644 --- a/ci.h +++ b/ci.h @@ -115,6 +115,8 @@ public: ///< The derived class must call Cancel(3) in its destructor. virtual bool Ready(void); ///< Returns 'true' if all present CAMs in this adapter are ready. + virtual bool SetIdle(bool Idle, bool TestOnly) { return false; } + virtual bool IsIdle(void) const { return false; } }; class cTPDU; diff --git a/config.c b/config.c index 73bb00d..66b67e1 100644 --- a/config.c +++ b/config.c @@ -399,6 +399,10 @@ cSetup::cSetup(void) InitialVolume = -1; ChannelsWrap = 0; EmergencyExit = 1; +//ML + VerboseLNBlog = 0; + for (int i = 0; i < MAXDEVICES; i++) CardUsesLnbNr[i] = i + 1; +//ML-Ende } cSetup& cSetup::operator= (const cSetup &s) @@ -592,7 +596,23 @@ bool cSetup::Parse(const char *Name, const char *Value) else if (!strcasecmp(Name, "ChannelsWrap")) ChannelsWrap = atoi(Value); else if (!strcasecmp(Name, "EmergencyExit")) EmergencyExit = atoi(Value); else - return false; + +//ML + if (!strcasecmp(Name, "VerboseLNBlog")) VerboseLNBlog = atoi(Value); + else { + char tmp[20]; + bool result = false; + for (int i = 1; i <= MAXDEVICES; i++) { + sprintf(tmp, "Card%dusesLNBnr", i); + if (!strcasecmp(Name, tmp)) { + CardUsesLnbNr[i - 1] = atoi(Value); + result = true; + } + } + return result; + } +//ML-Ende + return true; } @@ -688,6 +708,17 @@ bool cSetup::Save(void) Store("ChannelsWrap", ChannelsWrap); Store("EmergencyExit", EmergencyExit); +//ML + Store("VerboseLNBlog", VerboseLNBlog); + char tmp[20]; + if (cDevice::NumDevices() > 1) { + for (int i = 1; i <= cDevice::NumDevices(); i++) { + sprintf(tmp, "Card%dusesLNBnr", i); + Store(tmp, CardUsesLnbNr[i - 1]); + } + } +//ML-Ende + Sort(); if (cConfig::Save()) { diff --git a/config.h b/config.h index c51e3df..37ac85c 100644 --- a/config.h +++ b/config.h @@ -290,6 +290,16 @@ public: int InitialVolume; int ChannelsWrap; int EmergencyExit; + +//ML + #define LNB_SHARING_VERSION "0.1.4" + int VerboseLNBlog; + #define MAXDEVICES 16 // Since VDR 1.3.32 we can not #include "device.h" for MAXDEVICES anymore. + // With this workaround a warning will be shown during compilation if + // MAXDEVICES changes in device.h. + int CardUsesLnbNr[MAXDEVICES]; +//ML-Ende + int __EndData__; cString InitialChannel; cSetup(void); diff --git a/device.c b/device.c index ba098d8..859b2c3 100644 --- a/device.c +++ b/device.c @@ -72,12 +72,22 @@ cDevice *cDevice::device[MAXDEVICES] = { NULL }; cDevice *cDevice::primaryDevice = NULL; cDevice *cDevice::avoidDevice = NULL; cList cDevice::deviceHooks; +cDevice *cDevice::nextParentDevice = NULL; -cDevice::cDevice(void) +cDevice::cDevice(cDevice *ParentDevice) :patPmtParser(true) -{ - cardIndex = nextCardIndex++; - dsyslog("new device number %d", CardIndex() + 1); +,isIdle(false) +,parentDevice(ParentDevice) +,subDevice(NULL) +{ + if (!ParentDevice) + parentDevice = nextParentDevice; + cDevice::nextParentDevice = NULL; + if (parentDevice) + cardIndex = parentDevice->cardIndex; + else + cardIndex = nextCardIndex++; + dsyslog("new %sdevice number %d", parentDevice ? "sub-" : "", CardIndex() + 1); SetDescription("receiver on device %d", CardIndex() + 1); @@ -108,10 +118,14 @@ cDevice::cDevice(void) for (int i = 0; i < MAXRECEIVERS; i++) receiver[i] = NULL; - if (numDevices < MAXDEVICES) - device[numDevices++] = this; + if (!parentDevice) { + if (numDevices < MAXDEVICES) + device[numDevices++] = this; + else + esyslog("ERROR: too many devices or \"dynamite\"-unpatched device creator!"); + } else - esyslog("ERROR: too many devices!"); + parentDevice->subDevice = this; } cDevice::~cDevice() @@ -120,6 +134,29 @@ cDevice::~cDevice() DetachAllReceivers(); delete liveSubtitle; delete dvbSubtitleConverter; + if (parentDevice && (parentDevice->subDevice == this)) + parentDevice->subDevice = NULL; +} + +bool cDevice::SetIdle(bool Idle) +{ + if (parentDevice) + return parentDevice->SetIdle(Idle); + if (isIdle == Idle) + return true; + if (Receiving(false)) + return false; + if (Idle) { + Detach(player); + DetachAllReceivers(); + } + if (!SetIdleDevice(Idle, true)) + return false; + isIdle = Idle; + if (SetIdleDevice(Idle, false)) + return true; + isIdle = !Idle; + return false; } bool cDevice::WaitForAllDevicesReady(int Timeout) @@ -144,6 +181,28 @@ void cDevice::SetUseDevice(int n) useDevice |= (1 << n); } +//ML +void cDevice::SetLnbNr(void) +{ + for (int i = 0; i < numDevices; i++) + device[i]->SetLnbNrFromSetup(); +} + +bool cDevice::IsLnbSendSignals(void) +{ + if (parentDevice) + return parentDevice->IsLnbSendSignals(); + for (int i = 0; device[i] != this && i < numDevices; i++) { + if (device[i]->IsShareLnb(this)) { + isyslog("Device %d: will not send any signal (like 22kHz) to LNB as device %d will do this", cardIndex+1, device[i]->cardIndex + 1); + return false; + } + } + isyslog("Device %d: will send signals (like 22kHz) to LNB nr. = %d ", cardIndex+1, LnbNr()); + return true; +} +//ML-Ende + int cDevice::NextCardIndex(int n) { if (n > 0) { @@ -158,6 +217,8 @@ int cDevice::NextCardIndex(int n) int cDevice::DeviceNumber(void) const { + if (parentDevice) + return parentDevice->DeviceNumber(); for (int i = 0; i < numDevices; i++) { if (device[i] == this) return i; @@ -263,6 +324,10 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView for (int i = 0; i < numDevices; i++) { if (device[i] == AvoidDevice) continue; // this device shall be temporarily avoided + // LNB - Sharing + if (AvoidDevice && device[i]->IsShareAvoidDevice(Channel, AvoidDevice) ) + continue; // this device shall be temporarily avoided + // LNB - Sharing END if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device[i]->CardIndex() + 1) continue; // a specific card was requested, but not this one if (NumUsableSlots && !CamSlots.Get(j)->Assign(device[i], true)) @@ -283,9 +348,19 @@ cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool LiveView imp <<= 1; imp |= device[i]->Receiving(); // avoid devices that are receiving imp <<= 4; imp |= GetClippedNumProvidedSystems(4, device[i]) - 1; // avoid cards which support multiple delivery systems imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice(); // avoid the Transfer Mode receiver device - imp <<= 8; imp |= min(max(device[i]->Priority() + MAXPRIORITY, 0), 0xFF); // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) + // LNB - Sharing + int badPriority = device[i]->GetMaxBadPriority(Channel); + if (badPriority < 0) { // a device receiving with lower priority would need to be stopped + imp <<= 8; imp |= min(max(device[i]->Priority() + MAXPRIORITY, 0), 0xFF); // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) + } else { + imp <<= 8; imp |= min(max(max(device[i]->Priority(), badPriority) + MAXPRIORITY, 0), 0xFF); // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) + } + // LNB - Sharing End imp <<= 8; imp |= min(max((NumUsableSlots ? SlotPriority[j] : 0) + MAXPRIORITY, 0), 0xFF); // use the CAM slot with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) imp <<= 1; imp |= ndr; // avoid devices if we need to detach existing receivers + // LNB - Sharing + imp <<= 1; imp |= (badPriority == -1); // avoid cards where the actual device needs to be switched + // LNB - Sharing End imp <<= 1; imp |= NumUsableSlots ? 0 : device[i]->HasCi(); // avoid cards with Common Interface for FTA channels imp <<= 1; imp |= device[i]->AvoidRecording(); // avoid SD full featured cards imp <<= 1; imp |= NumUsableSlots ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel @@ -328,6 +403,8 @@ bool cDevice::HasCi(void) void cDevice::SetCamSlot(cCamSlot *CamSlot) { + if (parentDevice) + return parentDevice->SetCamSlot(CamSlot); camSlot = CamSlot; } @@ -531,6 +608,10 @@ bool cDevice::SetPid(cPidHandle *Handle, int Type, bool On) void cDevice::StartSectionHandler(void) { + if (parentDevice) { + parentDevice->StartSectionHandler(); + return; + } if (!sectionHandler) { sectionHandler = new cSectionHandler(this); AttachFilter(eitFilter = new cEitFilter); @@ -542,6 +623,10 @@ void cDevice::StartSectionHandler(void) void cDevice::StopSectionHandler(void) { + if (parentDevice) { + parentDevice->StopSectionHandler(); + return; + } if (sectionHandler) { delete nitFilter; delete sdtFilter; @@ -568,12 +653,17 @@ void cDevice::CloseFilter(int Handle) void cDevice::AttachFilter(cFilter *Filter) { + if (parentDevice) + return parentDevice->AttachFilter(Filter); + SetIdle(false); if (sectionHandler) sectionHandler->Attach(Filter); } void cDevice::Detach(cFilter *Filter) { + if (parentDevice) + return parentDevice->Detach(Filter); if (sectionHandler) sectionHandler->Detach(Filter); } @@ -602,7 +692,9 @@ bool cDevice::ProvidesTransponder(const cChannel *Channel) const bool cDevice::ProvidesTransponderExclusively(const cChannel *Channel) const { for (int i = 0; i < numDevices; i++) { - if (device[i] && device[i] != this && device[i]->ProvidesTransponder(Channel)) +//ML + if (device[i] && device[i] != this && device[i]->ProvidesTransponder(Channel) && device[i]->IsShareLnb(this)) +//ML-Ende return false; } return true; @@ -699,6 +791,70 @@ bool cDevice::SwitchChannel(int Direction) return result; } +// ML +cDevice *cDevice::GetBadDevice(const cChannel *Channel) +{ + if (parentDevice) + return parentDevice->GetBadDevice(Channel); + if (!cSource::IsSat(Channel->Source())) return NULL; // no conflict if the new channel is not on sat + if (!ProvidesSource(cSource::stSat)) return NULL; // no conflict if this device is not on sat + for (int i = 0; i < numDevices; i++) { + if (this != device[i] && device[i]->IsShareLnb(this) && device[i]->IsLnbConflict(Channel) ) { + // there is a conflict between device[i] and 'this' if we tune this to Channel + if (Setup.VerboseLNBlog) { + isyslog("LNB %d: Device check for channel %d on device %d. LNB or DiSEq conflict with device %d", LnbNr(), Channel->Number(), this->cardIndex + 1, device[i]->cardIndex + 1); + } + return device[i]; + } + } + if (Setup.VerboseLNBlog) { + isyslog("LNB %d: Device check for channel %d on device %d. OK", LnbNr(), Channel->Number(), this->cardIndex + 1); + } + return NULL; +} + +int cDevice::GetMaxBadPriority(const cChannel *Channel) const +{ + if (parentDevice) + return parentDevice->GetMaxBadPriority(Channel); + if (!cSource::IsSat(Channel->Source())) return -2; // no conflict if the new channel is not on sat + if (!ProvidesSource(cSource::stSat)) return -2; // no conflict if this device is not on sat + + int maxBadPriority = -2; + for (int i = 0; i < numDevices; i++) { + if (this != device[i] && device[i]->IsShareLnb(this) && device[i]->IsLnbConflict(Channel) ) { + // there is a conflict between device[i] and 'this' if we tune this to Channel + if (Setup.VerboseLNBlog) { + isyslog("LNB %d: Conflict for device %d, priority of conflicting device: %d", LnbNr(), device[i]->cardIndex + 1, device[i]->Priority()); + } + if (device[i]->Receiving() && device[i]->Priority() > maxBadPriority) maxBadPriority = device[i]->Priority(); + if (device[i] == ActualDevice() && maxBadPriority < -1) maxBadPriority = -1; + } + } + + if (Setup.VerboseLNBlog) { + isyslog("LNB %d: Request for channel %d on device %d. MaxBadPriority is %d", LnbNr(), Channel->Number(), this->cardIndex + 1, maxBadPriority); + } + return maxBadPriority; +} + +bool cDevice::IsShareAvoidDevice(const cChannel *Channel, const cDevice *AvoidDevice) const +{ + if (parentDevice) + return parentDevice->IsShareAvoidDevice(Channel, AvoidDevice); + if (!cSource::IsSat(Channel->Source())) return false; // no conflict if the new channel is not on sat + if (!ProvidesSource(cSource::stSat)) return false; // no conflict if this device is not on sat + + for (int i = 0; i < numDevices; i++) { + if (this != device[i] && device[i]->IsShareLnb(this) && device[i]->IsLnbConflict(Channel) ) { + // there is a conflict between device[i] and 'this' if we tune this to Channel + if(device[i] == AvoidDevice) return true; + } + } + return false; +} +// ML Ende + eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) { if (LiveView) { @@ -713,6 +869,13 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) eSetChannelResult Result = scrOk; +//ML + if (Setup.VerboseLNBlog) { + isyslog("LNB %d: Switching device %d to channel %d", LnbNr(), this->DeviceNumber() + 1, Channel->Number()); + } +//ML-Ende + + // If this DVB card can't receive this channel, let's see if we can // use the card that actually can receive it and transfer data from there: @@ -735,6 +898,7 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) sectionHandler->SetStatus(false); sectionHandler->SetChannel(NULL); } + SetIdle(false); // Tell the camSlot about the channel switch and add all PIDs of this // channel to it, for possible later decryption: if (camSlot) @@ -781,8 +945,10 @@ void cDevice::ForceTransferMode(void) { if (!cTransferControl::ReceiverDevice()) { cChannel *Channel = Channels.GetByNumber(CurrentChannel()); - if (Channel) + if (Channel) { + SetIdle(false); SetChannelDevice(Channel, false); // this implicitly starts Transfer Mode + } } } @@ -1153,7 +1319,10 @@ bool cDevice::Transferring(void) const bool cDevice::AttachPlayer(cPlayer *Player) { + if (parentDevice) + return parentDevice->AttachPlayer(Player); if (CanReplay()) { + SetIdle(false); if (player) Detach(player); DELETENULL(liveSubtitle); @@ -1172,6 +1341,8 @@ bool cDevice::AttachPlayer(cPlayer *Player) void cDevice::Detach(cPlayer *Player) { + if (parentDevice) + return parentDevice->Detach(Player); if (Player && player == Player) { cPlayer *p = player; player = NULL; // avoids recursive calls to Detach() @@ -1191,6 +1362,8 @@ void cDevice::Detach(cPlayer *Player) void cDevice::StopReplay(void) { + if (parentDevice) + return parentDevice->StopReplay(); if (player) { Detach(player); if (IsPrimaryDevice()) @@ -1473,6 +1646,8 @@ int cDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly) int cDevice::Priority(void) const { + if (parentDevice) + return parentDevice->Priority(); int priority = IsPrimaryDevice() ? Setup.PrimaryLimit - 1 : DEFAULTPRIORITY; for (int i = 0; i < MAXRECEIVERS; i++) { if (receiver[i]) @@ -1488,6 +1663,8 @@ bool cDevice::Ready(void) bool cDevice::Receiving(bool CheckAny) const { + if (parentDevice) + return parentDevice->Receiving(CheckAny); for (int i = 0; i < MAXRECEIVERS; i++) { if (receiver[i] && (CheckAny || receiver[i]->priority >= 0)) // cReceiver with priority < 0 doesn't count return true; @@ -1567,10 +1744,13 @@ bool cDevice::GetTSPacket(uchar *&Data) bool cDevice::AttachReceiver(cReceiver *Receiver) { + if (parentDevice) + return parentDevice->AttachReceiver(Receiver); if (!Receiver) return false; if (Receiver->device == this) return true; + SetIdle(false); // activate the following line if you need it - actually the driver should be fixed! //#define WAIT_FOR_TUNER_LOCK #ifdef WAIT_FOR_TUNER_LOCK @@ -1609,6 +1789,8 @@ bool cDevice::AttachReceiver(cReceiver *Receiver) void cDevice::Detach(cReceiver *Receiver) { + if (parentDevice) + return parentDevice->Detach(Receiver); if (!Receiver || Receiver->device != this) return; bool receiversLeft = false; @@ -1634,6 +1816,8 @@ void cDevice::Detach(cReceiver *Receiver) void cDevice::DetachAll(int Pid) { + if (parentDevice) + return parentDevice->DetachAll(Pid); if (Pid) { cMutexLock MutexLock(&mutexReceiver); for (int i = 0; i < MAXRECEIVERS; i++) { @@ -1646,6 +1830,8 @@ void cDevice::DetachAll(int Pid) void cDevice::DetachAllReceivers(void) { + if (parentDevice) + return parentDevice->DetachAllReceivers(); cMutexLock MutexLock(&mutexReceiver); for (int i = 0; i < MAXRECEIVERS; i++) Detach(receiver[i]); @@ -1717,3 +1903,25 @@ uchar *cTSBuffer::Get(void) } return NULL; } + +// --- cDynamicDeviceProbe ------------------------------------------------------- + +cList DynamicDeviceProbes; + +cList cDynamicDeviceProbe::commandQueue; + +void cDynamicDeviceProbe::QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath) +{ + if (DevPath) + commandQueue.Add(new cDynamicDeviceProbeItem(Cmd, new cString(DevPath))); +} + +cDynamicDeviceProbe::cDynamicDeviceProbe(void) +{ + DynamicDeviceProbes.Add(this); +} + +cDynamicDeviceProbe::~cDynamicDeviceProbe() +{ + DynamicDeviceProbes.Del(this, false); +} diff --git a/device.h b/device.h index fd587a8..1ecfc55 100644 --- a/device.h +++ b/device.h @@ -159,11 +159,50 @@ public: static void Shutdown(void); ///< Closes down all devices. ///< Must be called at the end of the program. +//ML +public: + static void SetLnbNr(void); + ///< Called after changes in setup + ///< call SetLNBNrFromSetup for each device + virtual void SetLnbNrFromSetup(void) {}; + ///< Called after changes in setup + ///< Read Setup.CardUsesLNBnr, write value to member variable of this class + ///< Only implemented in dvbdevice, other devices don't use LNBs + bool IsLnbSendSignals(void); + ///< If several devices share the same LNB, only the first of these + ///< must send signals (like 22 kHz) to the LNB + ///< check, whepher this device must send these signals + virtual int LnbNr(void) const { return ( cardIndex + 1 ) * -1; }; + ///< Number of LNB. This is -cardIndex for all non-DVB devices. + ///< So, there will be no LNB conflicts for non-DVB devices. + virtual bool IsShareLnb(const cDevice *Device) { return false; }; + ///< True if both devices differ (this != Device) and share the same LNB + virtual bool IsLnbConflict(const cChannel *Channel) { return false; }; + ///< false if 'Channel' can be recieved with the same + ///< LNB as 'this' device is using + ///< Otherwise, true + cDevice *GetBadDevice(const cChannel *Channel) ; + ///< Returns NULL if there is no device which uses the same LNB or if + ///< all of those devices are tuned to the same frequency band and + ///< polarization as of the requested channel. + ///< Otherwise returns the first device found. + int GetMaxBadPriority(const cChannel *Channel) const; + ///< Returns the highest priority of all receiving devices which use + ///< the same LNB and are tuned to a different frequency band or + ///< polarization as of the requested channel. + ///< Returns -1 if there are no such devices, but the 'actual' device + ///< (device recieving live view) would be affected by switching to the requested channel. + ///< Returns -2 if there are no such devices and the actual device + ///< would not be affected by switching to the requested channel. + bool IsShareAvoidDevice(const cChannel *Channel, const cDevice *AvoidDevice) const; + ///< Returns true if switching this device to Channel will result in a switch of AvoidDevice +//ML-Ende + + private: static int nextCardIndex; int cardIndex; protected: - cDevice(void); virtual ~cDevice(); virtual bool Ready(void); ///< Returns true if this device is ready. Devices with conditional @@ -190,9 +229,6 @@ protected: ///< A derived class must call the MakePrimaryDevice() function of its ///< base class. public: - bool IsPrimaryDevice(void) const { return this == primaryDevice; } - int CardIndex(void) const { return cardIndex; } - ///< Returns the card index of this device (0 ... MAXDEVICES - 1). int DeviceNumber(void) const; ///< Returns the number of this device (0 ... numDevices). virtual bool HasDecoder(void) const; @@ -382,9 +418,6 @@ public: ///< Returns true if this device has a Common Interface. void SetCamSlot(cCamSlot *CamSlot); ///< Sets the given CamSlot to be used with this device. - cCamSlot *CamSlot(void) const { return camSlot; } - ///< Returns the CAM slot that is currently used with this device, - ///< or NULL if no CAM slot is in use. // Image Grab facilities @@ -540,9 +573,6 @@ private: cTsToPes tsToPesSubtitle; bool isPlayingVideo; protected: - const cPatPmtParser *PatPmtParser(void) const { return &patPmtParser; } - ///< Returns a pointer to the patPmtParser, so that a derived device - ///< can use the stream information from it. virtual bool CanReplay(void) const; ///< Returns true if this device can currently start a replay session. virtual bool SetPlayMode(ePlayMode PlayMode); @@ -728,6 +758,38 @@ public: ///< Detaches all receivers from this device for this pid. void DetachAllReceivers(void); ///< Detaches all receivers from this device. + +// --- dynamite subdevice patch start --- + friend class cDynamicDevice; +private: + static cDevice *nextParentDevice; + ///< Holds the parent device for the next subdevice + ///< so the dynamite-plugin can work with unpatched plugins + bool isIdle; +protected: + cDevice *parentDevice; + cDevice *subDevice; + cDevice(cDevice *ParentDevice = NULL); + const cPatPmtParser *PatPmtParser(void) const { if (parentDevice) return parentDevice->PatPmtParser(); return &patPmtParser; } + ///< Returns a pointer to the patPmtParser, so that a derived device + ///< can use the stream information from it. +public: + bool IsPrimaryDevice(void) const { if (parentDevice) return parentDevice->IsPrimaryDevice(); return this == primaryDevice; } + int CardIndex(void) const { if (parentDevice) return parentDevice->cardIndex; return cardIndex; } + ///< Returns the card index of this device (0 ... MAXDEVICES - 1). + cCamSlot *CamSlot(void) const { if (parentDevice) return parentDevice->CamSlot(); return camSlot; } + ///< Returns the CAM slot that is currently used with this device, + ///< or NULL if no CAM slot is in use. + bool IsSubDevice(void) const { return (parentDevice != NULL); } + bool HasSubDevice(void) const { return (subDevice != NULL); } + cDevice *SubDevice(void) const { return subDevice; } + bool IsIdle(void) const { if (parentDevice) return parentDevice->IsIdle(); return isIdle; } + bool SetIdle(bool Idle); + virtual bool SetIdleDevice(bool Idle, bool TestOnly) { return false; } + ///< Called by SetIdle + ///< if TestOnly, don't do anything, just return, if the device + ///< can be set to the new idle state + // --- dynamite subdevice patch end --- }; /// Derived cDevice classes that can receive channels will have to provide @@ -751,4 +813,47 @@ public: uchar *Get(void); }; +/// A plugin that want to create devices handled by the dynamite-plugin needs to create +/// a cDynamicDeviceProbe derived object on the heap in order to have its Probe() +/// function called, where it can actually create the appropriate device. +/// The cDynamicDeviceProbe object must be created in the plugin's constructor, +/// and deleted in its destructor. +/// The "DevPath" hasn't to be a physical device or a path in the filesystem. +/// It can be any string a plugin may react on. + +#define __DYNAMIC_DEVICE_PROBE + +enum eDynamicDeviceProbeCommand { ddpcAttach, ddpcDetach, ddpcService }; + +class cDynamicDeviceProbe : public cListObject { + friend class cDynamicDevice; +private: + class cDynamicDeviceProbeItem : public cListObject { + public: + eDynamicDeviceProbeCommand cmd; + cString *devpath; + cDynamicDeviceProbeItem(eDynamicDeviceProbeCommand Cmd, cString *DevPath):cmd(Cmd),devpath(DevPath) {} + virtual ~cDynamicDeviceProbeItem() { if (devpath) delete devpath; } + }; + static cList commandQueue; + ///< A list where all attach/detach commands are queued + ///< so they can be processed in the MainThreadHook of + ///< the dynamite plugin. +public: + static void QueueDynamicDeviceCommand(eDynamicDeviceProbeCommand Cmd, const char *DevPath); + ///< Plugins which support cDynamicDeviceProbe must use this function + ///< to queue the devices they normally create in their Initialize method. + ///< These devices are created as subdevices in the Start-method of the dynamite-plugin. + cDynamicDeviceProbe(void); + virtual ~cDynamicDeviceProbe(); + virtual cDevice *Attach(cDevice *ParentDevice, const char *DevPath) = 0; + ///< Probes for a device at the given device-path like /dev/dvb/adapter0/frontend0 + ///< or /dev/video0 etc. and creates the appropriate + ///< object derived from cDevice if applicable. + ///< Returns the device that has been created or NULL if not. + ///< The dynamite-plugin will delete the device if it is detached. + }; + +extern cList DynamicDeviceProbes; + #endif //__DEVICE_H diff --git a/diseqc.c b/diseqc.c index d446299..ac38d60 100644 --- a/diseqc.c +++ b/diseqc.c @@ -24,6 +24,7 @@ cDiseqc::cDiseqc(void) commands = NULL; parsing = false; numCodes = 0; + unicable = -1; } cDiseqc::~cDiseqc() @@ -101,9 +102,10 @@ const char *cDiseqc::Codes(const char *s) const char *p; int n = strtol(t, &p, 16); if (!errno && p != t && 0 <= n && n <= 255) { - if (!parsing) { - codes[NumCodes++] = uchar(n); - numCodes = NumCodes; + // unicable: don't read codes again, since there are some "presets" + if ((unicable < 0) || (parsing && (unicable >= 0))) { + codes[NumCodes] = uchar(n); + ++NumCodes; } t = skipspace(p); } @@ -117,6 +119,8 @@ const char *cDiseqc::Codes(const char *s) const return NULL; } } + if (parsing || (unicable < 0)) + numCodes = NumCodes; return e + 1; } else @@ -124,6 +128,46 @@ const char *cDiseqc::Codes(const char *s) const return NULL; } +const char *cDiseqc::Unicable(const char *s) const +{ + char *p = NULL; + errno = 0; + int n = strtol(s, &p, 10); + if (!errno && p != s && n >= 0 && n < 8) { + if (parsing) + unicable = n; + return p; + } + esyslog("ERROR: invalid unicable sat in '%s'", s - 1); + return NULL; +} + +unsigned int cDiseqc::UnicableFreq(unsigned int frequency, int satcr, unsigned int bpf) const +{ + unsigned int t = frequency == 0 ? 0 : (frequency + bpf + 2) / 4 - 350; + if (t < 1024 && satcr >= 0 && satcr < 8) + { + codes[3] = t >> 8 | (t == 0 ? 0 : unicable << 2) | satcr << 5; + codes[4] = t; + return (t + 350) * 4 - frequency; + } + + return 0; +} + +void cDiseqc::UnicablePin(int pin) const +{ + if (pin >= 0 && pin <= 255) { + numCodes = 6; + codes[2] = 0x5c; + codes[5] = pin; + } + else { + numCodes = 5; + codes[2] = 0x5a; + } +} + cDiseqc::eDiseqcActions cDiseqc::Execute(const char **CurrentAction) const { if (!*CurrentAction) @@ -139,6 +183,7 @@ cDiseqc::eDiseqcActions cDiseqc::Execute(const char **CurrentAction) const case 'B': return daMiniB; case 'W': *CurrentAction = Wait(*CurrentAction); break; case '[': *CurrentAction = Codes(*CurrentAction); return *CurrentAction ? daCodes : daNone; + case 'U': *CurrentAction = Unicable(*CurrentAction); return *CurrentAction ? daUnicable : daNone; default: return daNone; } } @@ -164,3 +209,46 @@ const cDiseqc *cDiseqcs::Get(int Device, int Source, int Frequency, char Polariz } return NULL; } + +// --- cUnicable -------------------------------------------------------------- + +cUnicable::cUnicable() +{ + satcr = -1; + bpf = 0; + pin = -1; + unused = true; +} + +bool cUnicable::Parse(const char *s) +{ + bool result = false; + int fields = sscanf(s, "%d %u %d", &satcr, &bpf, &pin); + if (fields >= 2) { + if (satcr >= 0 && satcr < 8) + result = true; + else + esyslog("Error: invalid unicable channel '%d'", satcr); + if (result && fields == 3 && (pin < 0 || pin > 255)) { + esyslog("Error: invalid unicable pin '%d'", pin); + result = false; + } + } + return result; +} + + +// --- cUnicables -------------------------------------------------------------- + +cUnicables Unicables; + +cUnicable *cUnicables::GetUnused() +{ + for (cUnicable *p = First(); p; p = Next(p)) { + if (p->Unused()) { + p->Use(); + return p; + } + } + return NULL; +} diff --git a/diseqc.conf b/diseqc.conf index c9ab3ad..dad3fde 100644 --- a/diseqc.conf +++ b/diseqc.conf @@ -18,6 +18,7 @@ # V voltage high (18V) # A mini A # B mini B +# Un unicable code sequence for bank n follows # Wnn wait nn milliseconds (nn may be any positive integer number) # [xx ...] hex code sequence (max. 6) # @@ -75,3 +76,16 @@ S13.0E 99999 H 10600 t V W15 [E0 10 38 F7] W15 B W15 T # S19.2E 99999 H 10560 t v # S19.2E 12110 V 11080 t v # S19.2E 99999 V 10720 t v +# +# Unicable +# +# S19.2E 11700 V 9750 t V U0 W10 [E0 10 5A 00 00] W10 v +# S19.2E 99999 V 10600 t V U1 W10 [E0 10 5A 00 00] W10 v +# S19.2E 11700 H 9750 t V U2 W10 [E0 10 5A 00 00] W10 v +# S19.2E 99999 H 10600 t V U3 W10 [E0 10 5A 00 00] W10 v +# +# S13.0E 11700 V 9750 t V U4 W10 [E0 10 5A 00 00] W10 v +# S13.0E 99999 V 10600 t V U5 W10 [E0 10 5A 00 00] W10 v +# S13.0E 11700 H 9750 t V U6 W10 [E0 10 5A 00 00] W10 v +# S13.0E 99999 H 10600 t V U7 W10 [E0 10 5A 00 00] W10 v + diff --git a/diseqc.h b/diseqc.h index 413ed4d..552e3da 100644 --- a/diseqc.h +++ b/diseqc.h @@ -23,6 +23,7 @@ public: daMiniA, daMiniB, daCodes, + daUnicable, }; enum { MaxDiseqcCodes = 6 }; private: @@ -32,11 +33,13 @@ private: char polarization; int lof; char *commands; + mutable int unicable; bool parsing; mutable uchar codes[MaxDiseqcCodes]; mutable int numCodes; const char *Wait(const char *s) const; const char *Codes(const char *s) const; + const char *Unicable(const char *s) const; public: cDiseqc(void); ~cDiseqc(); @@ -56,6 +59,9 @@ public: int Lof(void) const { return lof; } const char *Commands(void) const { return commands; } const uchar *Codes(int &NumCodes) const { NumCodes = numCodes; return numCodes ? codes : NULL; } + bool Unicable() const { return unicable >= 0; } + unsigned int UnicableFreq(unsigned int frequency, int satcr, unsigned int bpf) const; + void UnicablePin(int pin) const; }; class cDiseqcs : public cConfig { @@ -65,4 +71,27 @@ public: extern cDiseqcs Diseqcs; +class cUnicable : public cListObject { +private: + int satcr; + unsigned int bpf; + int pin; + bool unused; +public: + cUnicable(); + bool Parse(const char *s); + int Satcr() const { return satcr; } + unsigned int Bpf() const { return bpf; } + int Pin() const { return pin; } + bool Unused() const { return unused; } + void Use() { unused = false; } + }; + +class cUnicables : public cConfig { +public: + cUnicable *GetUnused(); + }; + +extern cUnicables Unicables; + #endif //__DISEQC_H diff --git a/dvbci.c b/dvbci.c index 5289bbd..fea3a83 100644 --- a/dvbci.c +++ b/dvbci.c @@ -10,15 +10,18 @@ #include "dvbci.h" #include #include -#include "device.h" +#include "dvbdevice.h" // --- cDvbCiAdapter --------------------------------------------------------- -cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd) +cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend) { device = Device; SetDescription("CI adapter on device %d", device->DeviceNumber()); fd = Fd; + adapter = Adapter; + frontend = Frontend; + idle = false; ca_caps_t Caps; if (ioctl(fd, CA_GET_CAP, &Caps) == 0) { if ((Caps.slot_type & CA_CI_LINK) != 0) { @@ -41,10 +44,44 @@ cDvbCiAdapter::cDvbCiAdapter(cDevice *Device, int Fd) cDvbCiAdapter::~cDvbCiAdapter() { Cancel(3); + if (device->IsSubDevice() || device->HasSubDevice()) + CloseCa(); +} + +bool cDvbCiAdapter::OpenCa(void) +{ + if (fd >= 0) + return true; + fd = cDvbDevice::DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR); + return (fd >= 0); +} + +void cDvbCiAdapter::CloseCa(void) +{ + if (fd < 0) + return; + close(fd); + fd = -1; +} + +bool cDvbCiAdapter::SetIdle(bool Idle, bool TestOnly) +{ + if ((adapter < 0) || (frontend < 0)) + return false; + if (TestOnly || (idle == Idle)) + return true; + if (Idle) + CloseCa(); + else + OpenCa(); + idle = Idle; + return true; } int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength) { + if (idle || (fd < 0)) + return 0; if (Buffer && MaxLength > 0) { struct pollfd pfd[1]; pfd[0].fd = fd; @@ -61,6 +98,8 @@ int cDvbCiAdapter::Read(uint8_t *Buffer, int MaxLength) void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length) { + if (idle || (fd < 0)) + return; if (Buffer && Length > 0) { if (safe_write(fd, Buffer, Length) != Length) esyslog("ERROR: can't write to CI adapter on device %d: %m", device->DeviceNumber()); @@ -69,6 +108,8 @@ void cDvbCiAdapter::Write(const uint8_t *Buffer, int Length) bool cDvbCiAdapter::Reset(int Slot) { + if (idle || (fd < 0)) + return false; if (ioctl(fd, CA_RESET, 1 << Slot) != -1) return true; else @@ -78,6 +119,8 @@ bool cDvbCiAdapter::Reset(int Slot) eModuleStatus cDvbCiAdapter::ModuleStatus(int Slot) { + if (idle || (fd < 0)) + return msNone; ca_slot_info_t sinfo; sinfo.num = Slot; if (ioctl(fd, CA_GET_SLOT_INFO, &sinfo) != -1) { @@ -99,10 +142,10 @@ bool cDvbCiAdapter::Assign(cDevice *Device, bool Query) return true; } -cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd) +cDvbCiAdapter *cDvbCiAdapter::CreateCiAdapter(cDevice *Device, int Fd, int Adapter, int Frontend) { // TODO check whether a CI is actually present? if (Device) - return new cDvbCiAdapter(Device, Fd); + return new cDvbCiAdapter(Device, Fd, Adapter, Frontend); return NULL; } diff --git a/dvbci.h b/dvbci.h index adbe40d..6d117b2 100644 --- a/dvbci.h +++ b/dvbci.h @@ -16,16 +16,24 @@ class cDvbCiAdapter : public cCiAdapter { private: cDevice *device; int fd; + int adapter; + int frontend; + bool idle; + + bool OpenCa(void); + void CloseCa(void); protected: virtual int Read(uint8_t *Buffer, int MaxLength); virtual void Write(const uint8_t *Buffer, int Length); virtual bool Reset(int Slot); virtual eModuleStatus ModuleStatus(int Slot); virtual bool Assign(cDevice *Device, bool Query = false); - cDvbCiAdapter(cDevice *Device, int Fd); + cDvbCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1); public: virtual ~cDvbCiAdapter(); - static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd); + virtual bool SetIdle(bool Idle, bool TestOnly); + virtual bool IsIdle(void) const { return idle; } + static cDvbCiAdapter *CreateCiAdapter(cDevice *Device, int Fd, int Adapter = -1, int Frontend = -1); }; #endif //__DVBCI_H diff --git a/dvbdevice.c b/dvbdevice.c index a97f274..472d2a6 100644 --- a/dvbdevice.c +++ b/dvbdevice.c @@ -264,32 +264,52 @@ private: uint32_t subsystemId; int tuneTimeout; int lockTimeout; + bool lnbSendSignals; // LNB Sharing. false if this device must not send signals to the LNB (like 22 kHz, ...). time_t lastTimeoutReport; fe_delivery_system frontendType; cChannel channel; - const char *diseqcCommands; + cUnicable *unicable; + const cDiseqc *diseqcLast; eTunerStatus tunerStatus; cMutex mutex; cCondVar locked; cCondVar newSet; - void ClearEventQueue(void) const; - bool GetFrontendStatus(fe_status_t &Status) const; + void ClearEventQueue(void); + bool GetFrontendStatus(fe_status_t &Status); + void ExecuteDiseqc(const cDiseqc *diseqc, unsigned int &frequency); bool SetFrontend(void); virtual void Action(void); + + cDvbDevice *dvbdevice; + bool isIdle; + bool OpenFrontend(void); + bool CloseFrontend(void); public: - cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType); + cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType, cDvbDevice *Dvbdevice); virtual ~cDvbTuner(); const cChannel *GetTransponder(void) const { return &channel; } uint32_t SubsystemId(void) const { return subsystemId; } bool IsTunedTo(const cChannel *Channel) const; void Set(const cChannel *Channel); bool Locked(int TimeoutMs = 0); - int GetSignalStrength(void) const; - int GetSignalQuality(void) const; + int GetSignalStrength(void); + int GetSignalQuality(void); + + bool SetIdle(bool Idle); + bool IsIdle(void) const { return isIdle; } + + int UnicableSatcr() const { return unicable ? unicable->Satcr() : -1; } + int UnicableBpf() const { return unicable ? unicable->Bpf() : 0; } + int UnicablePin() const { return unicable ? unicable->Pin() : -1; } }; -cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType) +cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_delivery_system FrontendType, cDvbDevice *Dvbdevice) { +// LNB Sharing + if(Dvbdevice) { + lnbSendSignals = Dvbdevice->IsLnbSendSignals(); + } else lnbSendSignals = true; +// END LNB Sharing device = Device; fd_frontend = Fd_Frontend; adapter = Adapter; @@ -299,10 +319,15 @@ cDvbTuner::cDvbTuner(int Device, int Fd_Frontend, int Adapter, int Frontend, fe_ tuneTimeout = 0; lockTimeout = 0; lastTimeoutReport = 0; - diseqcCommands = NULL; + unicable = NULL; + diseqcLast = NULL; tunerStatus = tsIdle; - if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) - CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power + dvbdevice = Dvbdevice; + isIdle = false; + if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) { + if(lnbSendSignals) CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power + unicable = Unicables.GetUnused(); + } SetDescription("tuner on frontend %d/%d", adapter, frontend); Start(); } @@ -313,6 +338,12 @@ cDvbTuner::~cDvbTuner() newSet.Broadcast(); locked.Broadcast(); Cancel(3); + if (diseqcLast && diseqcLast->Unicable()) { + unsigned int frequency = 0; + ExecuteDiseqc(diseqcLast, frequency); + } + if (dvbdevice && dvbdevice->IsSubDevice()) + CloseFrontend(); } bool cDvbTuner::IsTunedTo(const cChannel *Channel) const @@ -347,8 +378,10 @@ bool cDvbTuner::Locked(int TimeoutMs) return tunerStatus >= tsLocked; } -void cDvbTuner::ClearEventQueue(void) const +void cDvbTuner::ClearEventQueue(void) { + if (!OpenFrontend()) + return; cPoller Poller(fd_frontend); if (Poller.Poll(TUNER_POLL_TIMEOUT)) { dvb_frontend_event Event; @@ -357,7 +390,7 @@ void cDvbTuner::ClearEventQueue(void) const } } -bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const +bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) { ClearEventQueue(); while (1) { @@ -372,7 +405,7 @@ bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const //#define DEBUG_SIGNALSTRENGTH //#define DEBUG_SIGNALQUALITY -int cDvbTuner::GetSignalStrength(void) const +int cDvbTuner::GetSignalStrength(void) { ClearEventQueue(); uint16_t Signal; @@ -399,7 +432,7 @@ int cDvbTuner::GetSignalStrength(void) const #define LOCK_THRESHOLD 5 // indicates that all 5 FE_HAS_* flags are set -int cDvbTuner::GetSignalQuality(void) const +int cDvbTuner::GetSignalQuality(void) { fe_status_t Status; if (GetFrontendStatus(Status)) { @@ -482,8 +515,51 @@ static unsigned int FrequencyToHz(unsigned int f) return f; } +void cDvbTuner::ExecuteDiseqc(const cDiseqc *diseqc, unsigned int &frequency) +{ + // LNB sharing + if (!lnbSendSignals) + return; + // LNB sharing + cDiseqc::eDiseqcActions da; + for (const char *CurrentAction = NULL; (da = diseqc->Execute(&CurrentAction)) != cDiseqc::daNone; ) { + switch (da) { + case cDiseqc::daNone: break; + case cDiseqc::daToneOff: CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF)); break; + case cDiseqc::daToneOn: CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_ON)); break; + case cDiseqc::daVoltage13: CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); break; + case cDiseqc::daVoltage18: CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_18)); break; + case cDiseqc::daMiniA: CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_BURST, SEC_MINI_A)); break; + case cDiseqc::daMiniB: CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_BURST, SEC_MINI_B)); break; + case cDiseqc::daCodes: { + int n = 0; + const uchar *codes = diseqc->Codes(n); + if (codes) { + struct dvb_diseqc_master_cmd cmd; + cmd.msg_len = min(n, int(sizeof(cmd.msg))); + memcpy(cmd.msg, codes, cmd.msg_len); + CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &cmd)); + } + } + break; + case cDiseqc::daUnicable: { + frequency = diseqc->UnicableFreq(frequency, UnicableSatcr(), UnicableBpf()); + diseqc->UnicablePin(UnicablePin()); + if (frequency == 0) { + CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); + esyslog("ERROR: unable to setup unicable frequency for channel %d (f=%u, s=%d, b=%d)", channel.Number(), frequency, UnicableSatcr(), UnicableBpf()); + } + } + break; + default: esyslog("ERROR: unknown diseqc command %d", da); + } + } +} + bool cDvbTuner::SetFrontend(void) { + if (!OpenFrontend()) + return false; #define MAXFRONTENDCMDS 16 #define SETCMD(c, d) { Frontend[CmdSeq.num].cmd = (c);\ Frontend[CmdSeq.num].u.data = (d);\ @@ -511,34 +587,14 @@ bool cDvbTuner::SetFrontend(void) if (Setup.DiSEqC) { const cDiseqc *diseqc = Diseqcs.Get(device, channel.Source(), channel.Frequency(), dtp.Polarization()); if (diseqc) { - if (diseqc->Commands() && (!diseqcCommands || strcmp(diseqcCommands, diseqc->Commands()) != 0)) { - cDiseqc::eDiseqcActions da; - for (const char *CurrentAction = NULL; (da = diseqc->Execute(&CurrentAction)) != cDiseqc::daNone; ) { - switch (da) { - case cDiseqc::daNone: break; - case cDiseqc::daToneOff: CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF)); break; - case cDiseqc::daToneOn: CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_ON)); break; - case cDiseqc::daVoltage13: CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); break; - case cDiseqc::daVoltage18: CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_18)); break; - case cDiseqc::daMiniA: CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_BURST, SEC_MINI_A)); break; - case cDiseqc::daMiniB: CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_BURST, SEC_MINI_B)); break; - case cDiseqc::daCodes: { - int n = 0; - const uchar *codes = diseqc->Codes(n); - if (codes) { - struct dvb_diseqc_master_cmd cmd; - cmd.msg_len = min(n, int(sizeof(cmd.msg))); - memcpy(cmd.msg, codes, cmd.msg_len); - CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &cmd)); - } - } - break; - default: esyslog("ERROR: unknown diseqc command %d", da); - } - } - diseqcCommands = diseqc->Commands(); - } frequency -= diseqc->Lof(); + if (diseqc->Commands() && (!diseqcLast || strcmp(diseqcLast->Commands(), diseqc->Commands()) != 0 || diseqc->Unicable())) { + ExecuteDiseqc(diseqc, frequency); + if (frequency == 0) { + return false; + } + diseqcLast = diseqc; + } } else { esyslog("ERROR: no DiSEqC parameters found for channel %d", channel.Number()); @@ -555,9 +611,11 @@ bool cDvbTuner::SetFrontend(void) frequency -= Setup.LnbFrequHi; tone = SEC_TONE_ON; } - int volt = (dtp.Polarization() == 'v' || dtp.Polarization() == 'V' || dtp.Polarization() == 'r' || dtp.Polarization() == 'R') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18; - CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, volt)); - CHECK(ioctl(fd_frontend, FE_SET_TONE, tone)); + if(lnbSendSignals) { // LNB sharing + int volt = (dtp.Polarization() == 'v' || dtp.Polarization() == 'V' || dtp.Polarization() == 'r' || dtp.Polarization() == 'R') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18; + CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, volt)); + CHECK(ioctl(fd_frontend, FE_SET_TONE, tone)); + } // LNB sharing } frequency = abs(frequency); // Allow for C-band, where the frequency is less than the LOF @@ -643,9 +701,11 @@ void cDvbTuner::Action(void) bool LostLock = false; fe_status_t Status = (fe_status_t)0; while (Running()) { - fe_status_t NewStatus; - if (GetFrontendStatus(NewStatus)) - Status = NewStatus; + if (!isIdle) { + fe_status_t NewStatus; + if (GetFrontendStatus(NewStatus)) + Status = NewStatus; + } cMutexLock MutexLock(&mutex); switch (tunerStatus) { case tsIdle: @@ -657,7 +717,7 @@ void cDvbTuner::Action(void) case tsTuned: if (Timer.TimedOut()) { tunerStatus = tsSet; - diseqcCommands = NULL; + diseqcLast = NULL; if (time(NULL) - lastTimeoutReport > 60) { // let's not get too many of these isyslog("frontend %d/%d timed out while tuning to channel %d, tp %d", adapter, frontend, channel.Number(), channel.Transponder()); lastTimeoutReport = time(NULL); @@ -667,7 +727,7 @@ void cDvbTuner::Action(void) case tsLocked: if (Status & FE_REINIT) { tunerStatus = tsSet; - diseqcCommands = NULL; + diseqcLast = NULL; isyslog("frontend %d/%d was reinitialized", adapter, frontend); lastTimeoutReport = 0; continue; @@ -698,6 +758,45 @@ void cDvbTuner::Action(void) } } +bool cDvbTuner::SetIdle(bool Idle) +{ + if (isIdle == Idle) + return true; + isIdle = Idle; + if (Idle) + return CloseFrontend(); + return OpenFrontend(); +} + +bool cDvbTuner::OpenFrontend(void) +{ + if (fd_frontend >= 0) + return true; + cMutexLock MutexLock(&mutex); + fd_frontend = cDvbDevice::DvbOpen(DEV_DVB_FRONTEND, adapter, frontend, O_RDWR | O_NONBLOCK); + if (fd_frontend < 0) + return false; + if (frontendType == SYS_DVBS || frontendType == SYS_DVBS2) +#ifdef LNB_SHARING_VERSION + if (lnbSendSignals) +#endif + CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13)); // must explicitly turn on LNB power + isIdle = false; + return true; +} + +bool cDvbTuner::CloseFrontend(void) +{ + if (fd_frontend < 0) + return true; + cMutexLock MutexLock(&mutex); + tunerStatus = tsIdle; + newSet.Broadcast(); + close(fd_frontend); + fd_frontend = -1; + return true; +} + // --- cDvbSourceParam ------------------------------------------------------- class cDvbSourceParam : public cSourceParam { @@ -778,7 +877,8 @@ const char *DeliverySystems[] = { NULL }; -cDvbDevice::cDvbDevice(int Adapter, int Frontend) +cDvbDevice::cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice) +:cDevice(ParentDevice) { adapter = Adapter; frontend = Frontend; @@ -795,7 +895,7 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend) fd_ca = DvbOpen(DEV_DVB_CA, adapter, frontend, O_RDWR); if (fd_ca >= 0) - ciAdapter = cDvbCiAdapter::CreateCiAdapter(this, fd_ca); + ciAdapter = cDvbCiAdapter::CreateCiAdapter(parentDevice ? parentDevice : this, fd_ca, Adapter, Frontend); // The DVR device (will be opened and closed as needed): @@ -835,7 +935,14 @@ cDvbDevice::cDvbDevice(int Adapter, int Frontend) else p = (char *)"unknown modulations"; isyslog("frontend %d/%d provides %s with %s (\"%s\")", adapter, frontend, DeliverySystems[frontendType], p, frontendInfo.name); - dvbTuner = new cDvbTuner(CardIndex() + 1, fd_frontend, adapter, frontend, frontendType); + +//ML + isyslog("LNB-sharing: patch version %s", LNB_SHARING_VERSION); + lnbState = -1; + SetLnbNrFromSetup(); + lnbSource = NULL; + dvbTuner = new cDvbTuner(CardIndex() + 1, fd_frontend, adapter, frontend, frontendType, this); +//ML-Ende } } else @@ -940,6 +1047,26 @@ bool cDvbDevice::Ready(void) return true; } +bool cDvbDevice::SetIdleDevice(bool Idle, bool TestOnly) +{ + if (TestOnly) { + if (ciAdapter) + return ciAdapter->SetIdle(Idle, true); + return true; + } + if (!dvbTuner->SetIdle(Idle)) + return false; + if (ciAdapter && !ciAdapter->SetIdle(Idle, false)) { + dvbTuner->SetIdle(!Idle); + return false; + } + if (Idle) + StopSectionHandler(); + else + StartSectionHandler(); + return true; +} + bool cDvbDevice::HasCi(void) { return ciAdapter; @@ -1051,6 +1178,12 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne { bool result = false; bool hasPriority = Priority < 0 || Priority > this->Priority(); + // LNB Sharing + if(hasPriority && GetMaxBadPriority(Channel) >= Priority) hasPriority = false; + if (Setup.VerboseLNBlog) { + isyslog("LNB %d: ProvidesChannel %d on device %d. Priority is %d, hasPriority is %d", LnbNr(), Channel->Number(), this->DeviceNumber() + 1, Priority, hasPriority); + } + // LNB Sharing END bool needsDetachReceivers = false; if (dvbTuner && ProvidesTransponder(Channel)) { @@ -1083,7 +1216,7 @@ bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *Ne bool cDvbDevice::ProvidesEIT(void) const { - return dvbTuner != NULL; + return !IsIdle() && (dvbTuner != NULL) && !dvbTuner->IsIdle() && ((ciAdapter == NULL) || !ciAdapter->IsIdle()); } int cDvbDevice::NumProvidedSystems(void) const @@ -1115,7 +1248,27 @@ bool cDvbDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) { if (dvbTuner) dvbTuner->Set(Channel); - return true; + // LNB Sharing + cDvbTransponderParameters dtp(Channel->Parameters()); + + if (Channel->Frequency() >= Setup.LnbSLOF) { + lnbState = 1; + } else { + lnbState = 0; + } + if (dtp.Polarization() == 'v' || dtp.Polarization() == 'V') lnbState += 2; + lnbSource = (int*) Diseqcs.Get(CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization()); + + cDevice *tmpDevice; + if (tmpDevice = GetBadDevice(Channel)){ + tmpDevice->DetachAllReceivers(); + if (tmpDevice->CamSlot() && !tmpDevice->CamSlot()->IsDecrypting()) + tmpDevice->CamSlot()->Assign(NULL); + + tmpDevice->SwitchChannel(Channel, false); + } + // LNB Sharing End + return true; } bool cDvbDevice::HasLock(int TimeoutMs) @@ -1156,6 +1309,45 @@ bool cDvbDevice::GetTSPacket(uchar *&Data) return false; } +// LNB - Sharing +void cDvbDevice::SetLnbNrFromSetup(void) +{ + lnbNr = Setup.CardUsesLnbNr[CardIndex()]; + isyslog("LNB-sharing: setting device %d to use LNB %d", CardIndex() + 1, lnbNr); +} + +bool cDvbDevice::IsShareLnb(const cDevice *Device) +{ + return this != Device && this->parentDevice != Device && LnbNr() == Device->LnbNr(); +} + +bool cDvbDevice::IsLnbConflict(const cChannel *Channel) +{ + if(!cSource::IsSat(Channel->Source())) return false; // no conflict if the new channel is not on sat + if(!ProvidesSource(cSource::stSat)) return false; // no conflict if this device is not on sat +//if(MaySwitchTransponder()) return false; // no conflict if this transponder may be switched + cDvbTransponderParameters dtp(Channel->Parameters()); + if (Setup.DiSEqC) { + const cDiseqc *diseqc; + diseqc = Diseqcs.Get(CardIndex() + 1, Channel->Source(), Channel->Frequency(), dtp.Polarization() ); + if (LnbSource() != (int*) diseqc) return true; + return false; + } else + { + char requiredState; + if (Channel->Frequency() >= Setup.LnbSLOF) { + requiredState = 1 ; + } else { + requiredState = 0; + } + if (dtp.Polarization() == 'v' || dtp.Polarization() == 'V') requiredState += 2; + if(lnbState != requiredState) return true; + return false; + } +} +// LNB - Sharing Ende + + // --- cDvbDeviceProbe ------------------------------------------------------- cList DvbDeviceProbes; diff --git a/dvbdevice.h b/dvbdevice.h index e1842b7..570c41c 100644 --- a/dvbdevice.h +++ b/dvbdevice.h @@ -102,7 +102,7 @@ class cDvbTuner; /// The cDvbDevice implements a DVB device which can be accessed through the Linux DVB driver API. class cDvbDevice : public cDevice { -protected: +public: static cString DvbName(const char *Name, int Adapter, int Frontend); static int DvbOpen(const char *Name, int Adapter, int Frontend, int Mode, bool ReportError = false); private: @@ -123,10 +123,27 @@ private: fe_delivery_system frontendType; int fd_dvr, fd_ca; public: - cDvbDevice(int Adapter, int Frontend); + cDvbDevice(int Adapter, int Frontend, cDevice *ParentDevice = NULL); virtual ~cDvbDevice(); virtual bool Ready(void); + virtual bool SetIdleDevice(bool Idle, bool TestOnly); + +// LNB Sharing +private: + char lnbState; // Current frequency band and polarization of the DVB-tuner + // cDiseqc *lnbSource; // can not #include "diseqc.h". A workaround follows: + int *lnbSource; // [DiSEqC] DiSEqC-Source + int lnbNr; // Number of LNB used + +public: + int *LnbSource(void) { return lnbSource; }; + virtual int LnbNr(void) const { if(ProvidesSource(cSource::stSat)) return lnbNr; return (CardIndex() + 1) * -1;}; + virtual void SetLnbNrFromSetup(void); + virtual bool IsLnbConflict(const cChannel *Channel); + virtual bool IsShareLnb(const cDevice *Device); +// LNB Sharing Ende + // Common Interface facilities: private: diff --git a/eitscan.c b/eitscan.c index 9d8ac99..8ae11ae 100644 --- a/eitscan.c +++ b/eitscan.c @@ -151,9 +151,11 @@ void cEITScanner::Process(void) if (Device->ProvidesTransponder(Channel)) { if (!Device->Receiving()) { bool MaySwitchTransponder = Device->MaySwitchTransponder(); - if (MaySwitchTransponder || Device->ProvidesTransponderExclusively(Channel) && now - lastActivity > Setup.EPGScanTimeout * 3600) { +//ML + if (MaySwitchTransponder && Device->GetMaxBadPriority(Channel) == -2 || (MaySwitchTransponder || Device->ProvidesTransponderExclusively(Channel)) && Device->GetMaxBadPriority(Channel) <= -1 && now - lastActivity > Setup.EPGScanTimeout * 3600) { if (!MaySwitchTransponder) { - if (Device == cDevice::ActualDevice() && !currentChannel) { + if ((Device == cDevice::ActualDevice() || Device->GetMaxBadPriority(Channel) == -1) && !currentChannel) { +//ML-Ende cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode currentChannel = Device->CurrentChannel(); Skins.Message(mtInfo, tr("Starting EPG scan")); diff --git a/menu.c b/menu.c index ef2bb46..aa2c43b 100644 --- a/menu.c +++ b/menu.c @@ -2903,6 +2903,23 @@ void cMenuSetupLNB::Setup(void) Clear(); +//ML + int numSatDevices = 0; + for (int i = 0; i < cDevice::NumDevices(); i++) { + if (cDevice::GetDevice(i)->ProvidesSource(cSource::stSat)) numSatDevices++; + } + if (numSatDevices > 1) { + char tmp[40]; + for (int i = 1; i <= cDevice::NumDevices(); i++) { + if (cDevice::GetDevice(i - 1)->ProvidesSource(cSource::stSat)) { + snprintf( tmp, 40, tr("Setup.LNB$DVB device %d uses LNB No."), i); + Add(new cMenuEditIntItem( tmp, &data.CardUsesLnbNr[i - 1], 1, numSatDevices )); + } + } + } + Add(new cMenuEditBoolItem(tr("Setup.LNB$Log LNB usage"), &data.VerboseLNBlog)); +//ML-Ende + Add(new cMenuEditBoolItem(tr("Setup.LNB$Use DiSEqC"), &data.DiSEqC)); if (!data.DiSEqC) { Add(new cMenuEditIntItem( tr("Setup.LNB$SLOF (MHz)"), &data.LnbSLOF)); @@ -2919,6 +2936,10 @@ eOSState cMenuSetupLNB::ProcessKey(eKeys Key) int oldDiSEqC = data.DiSEqC; eOSState state = cMenuSetupBase::ProcessKey(Key); +//ML + if (Key == kOk) cDevice::SetLnbNr(); +//ML-Ende + if (Key != kNone && data.DiSEqC != oldDiSEqC) Setup(); return state; diff --git a/unicable.conf b/unicable.conf new file mode 100644 index 0000000..e71d9fc --- /dev/null +++ b/unicable.conf @@ -0,0 +1,15 @@ +# Unicable configuration for VDR +# +# Format: +# +# channel frequency [pin] +# +# channel: unicable channel (0-7) +# frequency: frequency of the unicable channel +# pin: optional pin of the unicable channel (0-255) +# +# Examples: + +0 1400 +1 1516 + diff --git a/vdr.c b/vdr.c index c32e45f..1749c7d 100644 --- a/vdr.c +++ b/vdr.c @@ -598,6 +598,7 @@ int main(int argc, char *argv[]) Setup.Load(AddDirectory(ConfigDirectory, "setup.conf")); Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true); Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC); + Unicables.Load(AddDirectory(ConfigDirectory, "unicable.conf"), true); Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true); Timers.Load(AddDirectory(ConfigDirectory, "timers.conf")); Commands.Load(AddDirectory(ConfigDirectory, "commands.conf")); @@ -900,26 +901,29 @@ int main(int argc, char *argv[]) break; } bool timeout = Now - DeviceUsed[d->DeviceNumber()] > TIMERDEVICETIMEOUT; // only check other devices if they have been left alone for a while - if (d->MaySwitchTransponder()) { + if (d->MaySwitchTransponder() && d->GetMaxBadPriority(Timer->Channel()) == -2 ) { // LNB Sharing DeviceAvailable = true; // avoids using the actual device below if (timeout) Device = d; // only check other devices if they have been left alone for a while } else if (timeout && !Device && InVpsMargin && !d->Receiving() && d->ProvidesTransponderExclusively(Timer->Channel())) - Device = d; // use this one only if no other with less impact can be found - } + // Device = d; // use this one only if no other with less impact can be found + if( d->GetMaxBadPriority(Timer->Channel()) <= -1) Device = d; // LNB Sharing + } } if (!Device && InVpsMargin && !DeviceAvailable) { cDevice *d = cDevice::ActualDevice(); if (!d->Receiving() && d->ProvidesTransponder(Timer->Channel()) && Now - DeviceUsed[d->DeviceNumber()] > TIMERDEVICETIMEOUT) - Device = d; // use the actual device as a last resort + // Device = d; // use the actual device as a last resort + if( d->GetMaxBadPriority(Timer->Channel()) <= -1) Device = d; // LNB Sharing + } // Switch the device to the transponder: if (Device) { if (!Device->IsTunedToTransponder(Timer->Channel())) { if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice()) cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode - dsyslog("switching device %d to channel %d", Device->DeviceNumber() + 1, Timer->Channel()->Number()); + dsyslog("VDR: switching device %d to channel %d", Device->DeviceNumber() + 1, Timer->Channel()->Number()); Device->SwitchChannel(Timer->Channel(), false); DeviceUsed[Device->DeviceNumber()] = Now; }