>From 656cce97750882fd945d9ba76c47cb93a74c3059 Mon Sep 17 00:00:00 2001 From: glenvt18 Date: Tue, 24 May 2016 00:39:01 +0300 Subject: [PATCH] Device power saving feature --- config.c | 9 ++++++ config.h | 3 ++ device.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- device.h | 29 +++++++++++++++++++ dvbdevice.c | 39 +++++++++++++++++++++++++ dvbdevice.h | 7 +++++ eitscan.c | 7 ++++- menu.c | 9 +++++- vdr.c | 6 ++++ 9 files changed, 201 insertions(+), 4 deletions(-) diff --git a/config.c b/config.c index 9c6b71e..e196353 100644 --- a/config.c +++ b/config.c @@ -395,6 +395,9 @@ cSetup::cSetup(void) PositionerSpeed = 15; PositionerSwing = 650; PositionerLastLon = 0; + PowerdownEnabled = 0; + PowerdownTimeoutM = 15; + PowerdownWakeupH = 4; SetSystemTime = 0; TimeSource = 0; TimeTransponder = 0; @@ -617,6 +620,9 @@ bool cSetup::Parse(const char *Name, const char *Value) else if (!strcasecmp(Name, "PositionerSpeed")) PositionerSpeed = atoi(Value); else if (!strcasecmp(Name, "PositionerSwing")) PositionerSwing = atoi(Value); else if (!strcasecmp(Name, "PositionerLastLon")) PositionerLastLon = atoi(Value); + else if (!strcasecmp(Name, "PowerdownEnabled")) PowerdownEnabled = atoi(Value); + else if (!strcasecmp(Name, "PowerdownTimeoutM")) PowerdownTimeoutM = atoi(Value); + else if (!strcasecmp(Name, "PowerdownWakeupH")) PowerdownWakeupH = atoi(Value); else if (!strcasecmp(Name, "SetSystemTime")) SetSystemTime = atoi(Value); else if (!strcasecmp(Name, "TimeSource")) TimeSource = cSource::FromString(Value); else if (!strcasecmp(Name, "TimeTransponder")) TimeTransponder = atoi(Value); @@ -743,6 +749,9 @@ bool cSetup::Save(void) Store("PositionerSpeed", PositionerSpeed); Store("PositionerSwing", PositionerSwing); Store("PositionerLastLon", PositionerLastLon); + Store("PowerdownEnabled", PowerdownEnabled); + Store("PowerdownTimeoutM", PowerdownTimeoutM); + Store("PowerdownWakeupH", PowerdownWakeupH); Store("SetSystemTime", SetSystemTime); Store("TimeSource", cSource::ToString(TimeSource)); Store("TimeTransponder", TimeTransponder); diff --git a/config.h b/config.h index d1bae04..dbe84bb 100644 --- a/config.h +++ b/config.h @@ -273,6 +273,9 @@ public: int PositionerSpeed; int PositionerSwing; int PositionerLastLon; + int PowerdownEnabled; + int PowerdownTimeoutM; + int PowerdownWakeupH; int SetSystemTime; int TimeSource; int TimeTransponder; diff --git a/device.c b/device.c index 4db7cc2..1c29677 100644 --- a/device.c +++ b/device.c @@ -104,6 +104,9 @@ cDevice::cDevice(void) dvbSubtitleConverter = NULL; autoSelectPreferredSubtitleLanguage = true; + idleTimerExpires = time(NULL) + Setup.PowerdownTimeoutM * 60; + wakeupTimerExpires = 0; + for (int i = 0; i < MAXRECEIVERS; i++) receiver[i] = NULL; @@ -744,6 +747,11 @@ bool cDevice::SwitchChannel(int Direction) return result; } +// While switching to a channel, the device will be kept powered up +// for at least this number of seconds before a receiver is attached. +// Must be less than cEITScanner::ScanTimeout. +#define CHANNEL_SWITCH_POWERUP_TIMEOUT 10 + eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) { cStatus::MsgChannelSwitch(this, 0, LiveView); @@ -778,6 +786,8 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) } else { Channels.Lock(false); + // Power up the device + PowerUp(CHANNEL_SWITCH_POWERUP_TIMEOUT); // Stop section handling: if (sectionHandler) { sectionHandler->SetStatus(false); @@ -843,8 +853,11 @@ int cDevice::Occupied(void) const void cDevice::SetOccupied(int Seconds) { - if (Seconds >= 0) + if (Seconds >= 0) { occupiedTimeout = time(NULL) + min(Seconds, MAXOCCUPIEDTIMEOUT); + // avoid short power-down/power-up cycles + SetIdleTimer(true, Seconds + 30); + } } bool cDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) @@ -1675,6 +1688,7 @@ bool cDevice::AttachReceiver(cReceiver *Receiver) startScrambleDetection = time(NULL); } Start(); + SetIdleTimer(false); return true; } } @@ -1708,8 +1722,10 @@ void cDevice::Detach(cReceiver *Receiver) camSlot->Assign(NULL); } } - if (!receiversLeft) + if (!receiversLeft) { Cancel(-1); + SetIdleTimer(true); + } } void cDevice::DetachAll(int Pid) @@ -1731,6 +1747,82 @@ void cDevice::DetachAllReceivers(void) Detach(receiver[i]); } +void cDevice::CheckIdle(void) +{ + if (!SupportsPowerDown() || !Setup.PowerdownEnabled) + return; + cMutexLock MutexLock(&mutexPowerSaving); + if (idleTimerExpires != 0 && time(NULL) > idleTimerExpires) { + // idle, powered up + dsyslog("power saving: device %d idle timer expired", CardIndex() + 1); + SetIdleTimer(false); + if (Setup.PowerdownWakeupH != 0) + wakeupTimerExpires = time(NULL) + Setup.PowerdownWakeupH * 3600; + else + dsyslog("power saving: waking up is disabled"); + if (!IsPoweredDown()) { + dsyslog("power saving: powering device %d down", CardIndex() + 1); + if (sectionHandler) { + sectionHandler->SetStatus(false); + sectionHandler->SetChannel(NULL); + } + PowerDown(true); + } + } + if (wakeupTimerExpires != 0 && time(NULL) > wakeupTimerExpires) { + // idle, powered down + dsyslog("power saving: device %d wakeup timer expired", CardIndex() + 1); + SetIdleTimer(true); + if (IsPoweredDown()) { + dsyslog("power saving: waking up device %d", CardIndex() + 1); + PowerDown(false); + } + } +} + +void cDevice::SetIdleTimer(bool On, int ExtraTimeoutS) +{ + if (!SupportsPowerDown()) + return; + cMutexLock MutexLock(&mutexPowerSaving); + if (On) { + int Tout = Setup.PowerdownTimeoutM * 60; + time_t Now = time(NULL); + if (ExtraTimeoutS > 0) { + if (idleTimerExpires >= Now + ExtraTimeoutS) + return; + Tout = ExtraTimeoutS; + } + idleTimerExpires = Now + Tout; + if (Setup.PowerdownEnabled) + dsyslog("power saving: set device %d idle timer to %d sec", CardIndex() + 1, Tout); + } + else { + idleTimerExpires = 0; + if (Setup.PowerdownEnabled) + dsyslog("power saving: disable device %d idle timer", CardIndex() + 1); + } + wakeupTimerExpires = 0; +} + +bool cDevice::PoweredDown(void) +{ + if (SupportsPowerDown() && Setup.PowerdownEnabled) { + cMutexLock MutexLock(&mutexPowerSaving); + return IsPoweredDown(); + } + else + return false; +} + +void cDevice::PowerUp(int ExtraTimeoutS) +{ + cMutexLock MutexLock(&mutexPowerSaving); + SetIdleTimer(true, ExtraTimeoutS); + if (SupportsPowerDown() && IsPoweredDown()) + PowerDown(false); +} + // --- cTSBuffer ------------------------------------------------------------- cTSBuffer::cTSBuffer(int File, int Size, int CardIndex) diff --git a/device.h b/device.h index b06d977..56c4878 100644 --- a/device.h +++ b/device.h @@ -821,6 +821,35 @@ public: ///< Detaches all receivers from this device for this pid. virtual void DetachAllReceivers(void); ///< Detaches all receivers from this device. + +// Power saving facilities + +private: + cMutex mutexPowerSaving; + time_t idleTimerExpires, wakeupTimerExpires; + void PowerUp(int ExtraTimeoutS); + ///< If the device is powered down, powers it up and keeps it + ///< powered up for at least ExtraTimeoutS seconds (see + ///< cDevice::SetIdleTimer()). +public: + void CheckIdle(void); + ///< Should be called periodically in the main loop. + bool PoweredDown(void); + ///< Returns true if the device is powered down "logically", that is, + ///< idle tasks like EPG scanning are disabled. + void SetIdleTimer(bool On, int ExtraTimeoutS = 0); + ///< Starts/disables the idle timer. This timer must be started when + ///< a device gets idle and must be disabled when it is receiving. + ///< If ExtraTimeoutS is greater than zero and On is true, a new timer + ///< won't be set, but the device will be kept powered up for at least + ///< ExtraTimeoutS seconds. +protected: + virtual bool IsPoweredDown(void) {return false;} + ///< Returns true if the device is powered down "physically". + virtual void PowerDown(bool On) {}; + ///< Actually powers the device down/up. + virtual bool SupportsPowerDown() {return false;} + ///< Returns true if a derived device supports power saving. }; /// Derived cDevice classes that can receive channels will have to provide diff --git a/dvbdevice.c b/dvbdevice.c index 9321f16..2a0dad1 100644 --- a/dvbdevice.c +++ b/dvbdevice.c @@ -348,6 +348,8 @@ public: const cPositioner *Positioner(void) const { return positioner; } int GetSignalStrength(void) const; int GetSignalQuality(void) const; + bool IsPoweredDown(void) {return fd_frontend < 0;} + void PowerDown(bool On); }; cMutex cDvbTuner::bondMutex; @@ -544,6 +546,8 @@ void cDvbTuner::ClearEventQueue(void) const bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const { + if (fd_frontend < 0) + return false; ClearEventQueue(); while (1) { if (ioctl(fd_frontend, FE_READ_STATUS, &Status) != -1) @@ -559,6 +563,8 @@ bool cDvbTuner::GetFrontendStatus(fe_status_t &Status) const int cDvbTuner::GetSignalStrength(void) const { + if (fd_frontend < 0) + return -1; ClearEventQueue(); uint16_t Signal; while (1) { @@ -1001,6 +1007,26 @@ void cDvbTuner::Action(void) } } +void cDvbTuner::PowerDown(bool On) +{ + cMutexLock MutexLock(&mutex); + if (On && fd_frontend >= 0) { + isyslog("dvb tuner: power-down - closing frontend %d/%d", adapter, frontend); + tunerStatus = tsIdle; + close(fd_frontend); + fd_frontend = -1; + } + if (!On && fd_frontend < 0) { + cString Filename = cString::sprintf("%s/%s%d/%s%d", + DEV_DVB_BASE, DEV_DVB_ADAPTER, adapter, DEV_DVB_FRONTEND, frontend); + isyslog("dvb tuner: power-up - opening frontend %d/%d", adapter, frontend); + fd_frontend = open(Filename, O_RDWR | O_NONBLOCK); + if (fd_frontend < 0) + esyslog("ERROR: can't open DVB device frontend %d/%d", adapter, frontend); + tunerStatus = tsIdle; + } +} + // --- cDvbSourceParam ------------------------------------------------------- class cDvbSourceParam : public cSourceParam { @@ -1711,6 +1737,19 @@ void cDvbDevice::DetachAllReceivers(void) needsDetachBondedReceivers = false; } +bool cDvbDevice::IsPoweredDown(void) +{ + if (dvbTuner) + return dvbTuner->IsPoweredDown(); + return false; +} + +void cDvbDevice::PowerDown(bool On) +{ + if (dvbTuner) + dvbTuner->PowerDown(On); +} + // --- cDvbDeviceProbe ------------------------------------------------------- cList DvbDeviceProbes; diff --git a/dvbdevice.h b/dvbdevice.h index 0a148ce..a156de6 100644 --- a/dvbdevice.h +++ b/dvbdevice.h @@ -289,6 +289,13 @@ protected: virtual void CloseDvr(void); virtual bool GetTSPacket(uchar *&Data); virtual void DetachAllReceivers(void); + +// Power saving facilities + +protected: + virtual bool IsPoweredDown(void); + virtual void PowerDown(bool On); + virtual bool SupportsPowerDown() {return true;} }; // A plugin that implements a DVB device derived from cDvbDevice needs to create diff --git a/eitscan.c b/eitscan.c index 77f15c6..3899e00 100644 --- a/eitscan.c +++ b/eitscan.c @@ -142,7 +142,8 @@ void cEITScanner::Process(void) bool AnyDeviceSwitched = false; for (int i = 0; i < cDevice::NumDevices(); i++) { cDevice *Device = cDevice::GetDevice(i); - if (Device && Device->ProvidesEIT()) { + if (Device && Device->ProvidesEIT() + && (!Device->PoweredDown() || lastActivity == 0)) { // powered up or forced scan for (cScanData *ScanData = scanList->First(); ScanData; ScanData = scanList->Next(ScanData)) { const cChannel *Channel = ScanData->GetChannel(); if (Channel) { @@ -159,6 +160,10 @@ void cEITScanner::Process(void) } } //dsyslog("EIT scan: device %d source %-8s tp %5d", Device->DeviceNumber() + 1, *cSource::ToString(Channel->Source()), Channel->Transponder()); + if (lastActivity == 0) + // forced scan - set idle timer for each channel switch; + // this prevents powering down while scanning a transponder + Device->SetIdleTimer(true, ScanTimeout + 5); Device->SwitchChannel(Channel, false); scanList->Del(ScanData); AnyDeviceSwitched = true; diff --git a/menu.c b/menu.c index ae61c64..c469ab0 100644 --- a/menu.c +++ b/menu.c @@ -3464,6 +3464,12 @@ void cMenuSetupLNB::Setup(void) Add(new cMenuEditIntxItem(tr("Setup.LNB$Positioner speed (degrees/s)"), &data.PositionerSpeed, 1, 1800, 10)); } + Add(new cMenuEditBoolItem(tr("Setup.LNB$Enable power saving"), &data.PowerdownEnabled)); + if (data.PowerdownEnabled) { + Add(new cMenuEditIntItem(tr("Setup.LNB$Power down an idle device after (min)"), &data.PowerdownTimeoutM)); + Add(new cMenuEditIntItem(tr("Setup.LNB$Wake up from power-down after (h)"), &data.PowerdownWakeupH)); + } + SetCurrent(Get(current)); Display(); } @@ -3472,6 +3478,7 @@ eOSState cMenuSetupLNB::ProcessKey(eKeys Key) { int oldDiSEqC = data.DiSEqC; int oldUsePositioner = data.UsePositioner; + int oldPowerdownEnabled = data.PowerdownEnabled; bool DeviceBondingsChanged = false; if (Key == kOk) { cString NewDeviceBondings = satCableNumbers.ToString(); @@ -3480,7 +3487,7 @@ eOSState cMenuSetupLNB::ProcessKey(eKeys Key) } eOSState state = cMenuSetupBase::ProcessKey(Key); - if (Key != kNone && (data.DiSEqC != oldDiSEqC || data.UsePositioner != oldUsePositioner)) + if (Key != kNone && (data.DiSEqC != oldDiSEqC || data.UsePositioner != oldUsePositioner || data.PowerdownEnabled != oldPowerdownEnabled)) Setup(); else if (DeviceBondingsChanged) cDvbDevice::BondDevices(data.DeviceBondings); diff --git a/vdr.c b/vdr.c index 71a72f2..c12ad2c 100644 --- a/vdr.c +++ b/vdr.c @@ -1444,6 +1444,12 @@ int main(int argc, char *argv[]) ReportEpgBugFixStats(); + for (int i = 0; i < cDevice::NumDevices(); i++) { + cDevice *d = cDevice::GetDevice(i); + if (d) + d->CheckIdle(); + } + // Main thread hooks of plugins: PluginManager.MainThreadHook(); } -- 1.9.1