Hi. Here you can find an updated version of the patch: https://github.com/glenvt18/vdr/commits/vdr-2.4.6 On Mon, Oct 11, 2021 at 04:02:43PM +0100, Richard F wrote: > Mon, Sep 13, 2021 at 08:51:05PM +0200, Martin Wache wrote: > > So, back to the plan with the Raspberry Pi 2B. One problem that I wanted > > to solve is the power consumption of the DVB-T dongle when it is not in > > use. I found https://github.com/mvp/uhubctl which can switch off power > > on individual USB ports. I wrote a VDR shutdown script that does the > > following: > > > > sudo service lircd stop > > uhubctl -p ... -a off > > sudo service vdr stop > > > > I did not refine the uhubctl invocation yet. The physical port could > > vary. > > > > An easy way to start up VDR could be a udev rule that would start up the > > lircd and vdr services when the USB DVB stick is plugged in. Obviously, > > if we power off the port during VDR shutdown, the adapter would have to > > be plugged into a different port. > > > > A simpler option for the occasional use of VDR might be to simply write > > udev rules that will start up lircd and vdr when the DVB adapter is > > plugged in, and shut down the services when the adapter is removed. > > That would be too risky if recording timers are being used. > > > > I am not yet sure whether powering off the USB port makes any > > difference, because the plastic case of the DVB stick feels slightly > > warm to the touch even when the port is supposedly powered off. It might > > be that some internal heat produced by the Rasberry is being dissipated > > via the USB port. The metal frame of the USB jacks feels a bit warm too. > > > > I think that I must measure the input power of the Raspberry Pi as well > > as the voltage on the USB port when it is supposedly powered off. > > > > Marko > > Powersaving for DVB receivers - try the attached patch from glenvt18 > <glenvt18@xxxxxxxxx> > > This has worked for me for a few years without noticeable problems on both > USB and PCI cards. I'm still on V2.20 by the way. There's a little bit of > log chatter as it powers up/down receivers when VDR scans the EPG etc, but > you can filter that I reckoned it saved around 3W at the mains for a > 2-receiver server setup when I tested it back in 2016 - which adds up over > time. > > usbhubctl is a bit of a sledgehammer ! > > HTH > > Richard > > From 656cce97750882fd945d9ba76c47cb93a74c3059 Mon Sep 17 00:00:00 2001 > From: glenvt18 <glenvt18@xxxxxxxxx> > 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<cDvbDeviceProbe> 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 > #/bin/sh > > # This script monitors if a dvb device's frontend is opened by VDR. > # Can be used with a single tuner or with two tuners. > # Output: > # 1 - adapter 0 is on > # 2 - adapter 1 is on > # 3 - both are on > # - - both are off > # . - VDR is not running > > # VDR executable. Change to vdr.bin for OpenELEC. > VDR=vdr > > # Uncomment this if you want to output timestamps of status changes > PRINT_TIMESTAMPS=yes > > old_status='*' > old_stamp=$(date +%s) > while true; do > vdrpid=$(pidof $VDR) > if [ -z "$vdrpid" ]; then > status='.' > else > mask=0 > for adapter in 0 1; do > if lsof -p $vdrpid 2>/dev/null | grep -q "adapter$adapter/frontend"; then > mask=$(($mask + $adapter + 1)) > fi > done > if [ $mask -eq 0 ]; then > status='-' > else > status=$mask > fi > fi > stamp=$(date +%s) > if [ -n "$PRINT_TIMESTAMPS" -a "$old_status" != "$status" ]; then > printf '\n[%s] %s>%s +%ds\n' $(date +%H:%M:%S) "$old_status" $status $(($stamp - $old_stamp)) > old_stamp=$stamp > fi > old_status=$status > printf "$status" > sleep 1 > done > _______________________________________________ > vdr mailing list > vdr@xxxxxxxxxxx > https://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr -- glenvt18 _______________________________________________ vdr mailing list vdr@xxxxxxxxxxx https://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr