Re: Making VDR run under Systemd

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux Media]     [Asterisk]     [DCCP]     [Netdev]     [Xorg]     [Util Linux NG]     [Xfree86]     [Big List of Linux Books]     [Fedora Users]     [Fedora Women]     [ALSA Devel]     [Linux USB]

  Powered by Linux