GOTOXX patch for vdr-1.7.22

Message ID 4EE0A4FD.2020603@quick.cz
State New
Headers

Commit Message

Ales Jurik Dec. 8, 2011, 11:52 a.m. UTC
  Hi all,

here is patch for using motor with vdr-1.7.22. As the diseqc subsystem
changed quite a lot in 1.7.22 please report any problem.

I'm using/testing it together with vtuner devices (NessieDVB) and
cascade of diseqc switches:

1. DVB device: vtuner(PC) <-> LAN <-> NessieDVB <-> motor <->
uncomm.switch <-> comm. switch)
2. DVB device: vtuner(PC) <-> LAN <-> NessieDVB <-> uncomm.switch <->
comm. switch)

I'm using it together with VDR capability of dedicated and undedicated
sources to some cards and it works for me without problems.

I do not have possibility to test it together with real DVB cards inside
PC, so any remarks are welcomed.

My diseqc.conf looks like:

# ONLY SWITCHES

1 2:
S19.2E  11700 V  9750  t v W30 [E0 10 39 F0] W30 [E0 10 38 F0] W30 t
S19.2E  99999 V 10600  t v W30 [E0 10 39 F0] W30 [E0 10 38 F1] W30 T
S19.2E  11700 H  9750  t V W30 [E0 10 39 F0] W30 [E0 10 38 F2] W30 t
S19.2E  99999 H 10600  t V W30 [E0 10 39 F0] W30 [E0 10 38 F3] W30 T

S13.0E  11700 V  9750  t v W30 [E0 10 39 F0] W30 [E0 10 38 F4] W30 t
S13.0E  99999 V 10600  t v W30 [E0 10 39 F0] W30 [E0 10 38 F5] W30 T
S13.0E  11700 H  9750  t V W30 [E0 10 39 F0] W30 [E0 10 38 F6] W30 t
S13.0E  99999 H 10600  t V W30 [E0 10 39 F0] W30 [E0 10 38 F7] W30 T

....

# MOTOR + SWITCHES

1:
S1.0W   11700 V  9750  t V G [E0 10 39 F1] v W30 [E0 10 38 FC] W30 v t
S1.0W   99999 V 10600  t V G [E0 10 39 F1] v W30 [E0 10 38 FD] W30 v T
S1.0W   11700 H  9750  t V G [E0 10 39 F1] V W30 [E0 10 38 FE] W30 V t
S1.0W   99999 H 10600  t V G [E0 10 39 F1] V W30 [E0 10 38 FF] W30 V T

S9.0E   11700 V  9750  t V G [E0 10 39 F1] v W30 [E0 10 38 FC] W30 v t
S9.0E   99999 V 10600  t V G [E0 10 39 F1] v W30 [E0 10 38 FD] W30 v T
S9.0E   11700 H  9750  t V G [E0 10 39 F1] V W30 [E0 10 38 FE] W30 V t
S9.0E   99999 H 10600  t V G [E0 10 39 F1] V W30 [E0 10 38 FF] W30 V T

....

Enjoy,

Ales
  

Patch

diff -rupN vdr-1.7.22//config.c vdr-1.7.22_gotoxx_patched//config.c
--- vdr-1.7.22//config.c	2011-12-03 16:21:30.000000000 +0100
+++ vdr-1.7.22_gotoxx_patched//config.c	2011-12-08 12:08:20.482922647 +0100
@@ -389,6 +389,12 @@  cSetup::cSetup(void)
   LnbFrequLo =  9750;
   LnbFrequHi = 10600;
   DiSEqC = 0;
+  UseGotox = 0;
+  GotoxSpeed = 100;
+  GotoxRepeat = 0;
+  GotoxSN = 0; GotoxLat = 613; GotoxEW = 1; GotoxLong = 236; // Somewhere at Tampere, Finland :^)
+  GotoxMaxSwing = 60; 
+  GotoxPrevSource = 0;
   SetSystemTime = 0;
   TimeSource = 0;
   TimeTransponder = 0;
@@ -583,6 +589,15 @@  bool cSetup::Parse(const char *Name, con
   else if (!strcasecmp(Name, "LnbFrequLo"))          LnbFrequLo         = atoi(Value);
   else if (!strcasecmp(Name, "LnbFrequHi"))          LnbFrequHi         = atoi(Value);
   else if (!strcasecmp(Name, "DiSEqC"))              DiSEqC             = atoi(Value);
+  else if (!strcasecmp(Name, "UseGotox"))            UseGotox           = atoi(Value);
+  else if (!strcasecmp(Name, "GotoxSpeed"))          GotoxSpeed         = atoi(Value);
+  else if (!strcasecmp(Name, "GotoxRepeat"))         GotoxRepeat        = atoi(Value);
+  else if (!strcasecmp(Name, "GotoxSN"))             GotoxSN            = atoi(Value);
+  else if (!strcasecmp(Name, "GotoxLat"))            GotoxLat           = atoi(Value);
+  else if (!strcasecmp(Name, "GotoxEW"))             GotoxEW            = atoi(Value);
+  else if (!strcasecmp(Name, "GotoxLong"))           GotoxLong          = atoi(Value);
+  else if (!strcasecmp(Name, "GotoxMaxSwing"))       GotoxMaxSwing      = atoi(Value);
+  else if (!strcasecmp(Name, "GotoxPrevSource"))     GotoxPrevSource    = 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);
@@ -680,6 +695,15 @@  bool cSetup::Save(void)
   Store("LnbFrequLo",         LnbFrequLo);
   Store("LnbFrequHi",         LnbFrequHi);
   Store("DiSEqC",             DiSEqC);
+  Store("UseGotox",           UseGotox);
+  Store("GotoxSpeed",         GotoxSpeed);
+  Store("GotoxRepeat",        GotoxRepeat);
+  Store("GotoxSN",            GotoxSN);
+  Store("GotoxLat",           GotoxLat);
+  Store("GotoxEW",            GotoxEW);
+  Store("GotoxLong",          GotoxLong);
+  Store("GotoxMaxSwing",	  GotoxMaxSwing);
+  Store("GotoxPrevSource",    GotoxPrevSource);
   Store("SetSystemTime",      SetSystemTime);
   Store("TimeSource",         cSource::ToString(TimeSource));
   Store("TimeTransponder",    TimeTransponder);
diff -rupN vdr-1.7.22//config.h vdr-1.7.22_gotoxx_patched//config.h
--- vdr-1.7.22//config.h	2011-12-03 15:19:52.000000000 +0100
+++ vdr-1.7.22_gotoxx_patched//config.h	2011-12-08 12:08:20.494922647 +0100
@@ -248,6 +248,15 @@  public:
   int LnbFrequLo;
   int LnbFrequHi;
   int DiSEqC;
+  int GotoxRepeat;
+  int GotoxSN;
+  int GotoxEW;
+  int GotoxSpeed;
+  int GotoxLat;
+  int GotoxLong;
+  int GotoxMaxSwing;
+  int UseGotox;
+  int GotoxPrevSource;
   int SetSystemTime;
   int TimeSource;
   int TimeTransponder;
diff -rupN vdr-1.7.22//diseqc.c vdr-1.7.22_gotoxx_patched//diseqc.c
--- vdr-1.7.22//diseqc.c	2011-09-17 16:13:31.000000000 +0200
+++ vdr-1.7.22_gotoxx_patched//diseqc.c	2011-12-08 12:08:20.495922647 +0100
@@ -250,6 +250,7 @@  cDiseqc::eDiseqcActions cDiseqc::Execute
           case 'V': return daVoltage18;
           case 'A': return daMiniA;
           case 'B': return daMiniB;
+	      case 'G': return daGotoX;          
           case 'W': *CurrentAction = Wait(*CurrentAction); break;
           case 'S': *CurrentAction = GetScrBank(*CurrentAction); break;
           case '[': *CurrentAction = GetCodes(*CurrentAction, Codes, MaxCodes);
@@ -267,6 +268,22 @@  cDiseqc::eDiseqcActions cDiseqc::Execute
   return daNone;
 }
 
+cDiseqc::eDiseqcActions cDiseqc::TestForGotoXX(const char **CurrentAction) const
+{
+  if (!*CurrentAction)
+     *CurrentAction = commands;
+  while (*CurrentAction && **CurrentAction) {
+        switch (*(*CurrentAction)++) {
+          case ' ': break;
+	      case 'G': return daGotoX;    
+          default: return daNone;
+          }
+        }
+  return daNone;
+}
+
+
+
 // --- cDiseqcs --------------------------------------------------------------
 
 cDiseqcs Diseqcs;
diff -rupN vdr-1.7.22//diseqc.h vdr-1.7.22_gotoxx_patched//diseqc.h
--- vdr-1.7.22//diseqc.h	2011-09-17 15:15:17.000000000 +0200
+++ vdr-1.7.22_gotoxx_patched//diseqc.h	2011-12-08 12:08:20.495922647 +0100
@@ -50,6 +50,7 @@  public:
     daVoltage18,
     daMiniA,
     daMiniB,
+    daGotoX,    
     daScr,
     daCodes,
     };
@@ -88,6 +89,7 @@  public:
       ///< be a pointer returned from a previous call to cDiseqcs::Get().
       ///< Frequency must be the frequency the tuner will be tuned to, and will be
       ///< set to the proper SCR frequency upon return (if SCR is used).
+  eDiseqcActions TestForGotoXX(const char **CurrentAction) const;      
   int Devices(void) const { return devices; }
   int Source(void) const { return source; }
   int Slof(void) const { return slof; }
diff -rupN vdr-1.7.22//dvbdevice.c vdr-1.7.22_gotoxx_patched//dvbdevice.c
--- vdr-1.7.22//dvbdevice.c	2011-12-03 16:24:27.000000000 +0100
+++ vdr-1.7.22_gotoxx_patched//dvbdevice.c	2011-12-08 12:10:21.273920511 +0100
@@ -15,9 +15,11 @@ 
 #include <linux/dvb/frontend.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
+#include <math.h>
 #include "channels.h"
 #include "diseqc.h"
 #include "dvbci.h"
+#include "skins.h"
 #include "menuitems.h"
 #include "sourceparams.h"
 
@@ -271,6 +273,7 @@  private:
   fe_delivery_system frontendType;
   cChannel channel;
   const cDiseqc *lastDiseqc;
+  int lastSource;
   const cScr *scr;
   eTunerStatus tunerStatus;
   cMutex mutex;
@@ -283,6 +286,7 @@  private:
   void ClearEventQueue(void) const;
   bool GetFrontendStatus(fe_status_t &Status) const;
   void ExecuteDiseqc(const cDiseqc *Diseqc, unsigned int *Frequency);
+  bool TestDiseqcForGotoXX(const cDiseqc *Diseqc);
   void ResetToneAndVoltage(void);
   bool SetFrontend(void);
   virtual void Action(void);
@@ -316,6 +320,7 @@  cDvbTuner::cDvbTuner(const cDvbDevice *D
   lockTimeout = 0;
   lastTimeoutReport = 0;
   lastDiseqc = NULL;
+  lastSource = 0;
   scr = NULL;
   tunerStatus = tsIdle;
   bondedTuner = NULL;
@@ -455,6 +460,97 @@  bool cDvbTuner::IsTunedTo(const cChannel
   return strcmp(channel.Parameters(), Channel->Parameters()) == 0;
 }
 
+void HandleGotox(int fd_frontend, int new_source)
+{
+  
+  int gotoXTable[10] = { 0x00, 0x02, 0x03, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0D, 0x0E };
+  int satlong;
+  int satprev;
+  float waitseconds = 0;
+  float waitmaximum = 60;
+
+  if (Setup.UseGotox == 0)
+    return;
+
+  // Check if zapped into new source position?
+  if (new_source != Setup.GotoxPrevSource) {
+    if ((new_source & 0xFF000000) != 0x53000000) 
+      return; // Fail, not S type source
+    satlong = (new_source & 0x00000FFFF);
+    satprev = (Setup.GotoxPrevSource & 0x0000FFFF);
+    if (new_source & 0x00008000) {
+      satlong ^= 0x0000FFFF; satlong++; satlong *= (-1);
+    }
+    if (Setup.GotoxPrevSource & 0x00008000) {
+      satprev ^= 0x0000FFFF; satprev++; satprev *= (-1);
+    }
+    if (Setup.GotoxSpeed > 0) {
+      waitseconds = fabs(satlong-satprev)/(float)(Setup.GotoxSpeed);
+      waitmaximum = 10*(float)(Setup.GotoxMaxSwing)/(float)(Setup.GotoxSpeed);
+      if (waitseconds < 0.0) waitseconds = 0.0; // Should not happen but ...
+      if (waitseconds > waitmaximum) waitseconds = 2 + waitmaximum; // Limit wait time to +2s over dish max. move
+    }
+    int Long=Setup.GotoxEW ? -Setup.GotoxLong : Setup.GotoxLong;
+    int Lat=Setup.GotoxSN ? -Setup.GotoxLat : Setup.GotoxLat;
+    double azimuth=M_PI+atan(tan((satlong-Long)*M_PI/1800)/sin(Lat*M_PI/1800));
+    double x=acos(cos((satlong-Long)*M_PI/1800)*cos(Lat*M_PI/1800));
+    double elevation=atan((cos(x)-0.1513)/sin(x));
+    double SatHourangle=180+atan((-cos(elevation)*sin(azimuth))/(sin(elevation)*cos(Lat*M_PI/1800)
+			   -cos(elevation)*sin(Lat*M_PI/1800)*cos(azimuth)))*180/M_PI;
+    int tmp=(int)(fabs(180-SatHourangle)*10);
+    tmp=(tmp/10)*0x10 + gotoXTable[ tmp % 10 ];
+    int p2=(tmp%0x0100);
+    int p1=(tmp/0x0100);
+    if (SatHourangle < 180)
+      p1 |= 0xe0;
+    else
+      p1 |= 0xd0;
+
+    dsyslog("DiSEqC GotoX %d (%d) -> %d (%d), wait time %4.1fs",
+	    satprev, Setup.GotoxPrevSource, satlong, new_source, waitseconds);
+
+#if 1
+    // Set high LNB voltage and tone off, then wait > 15ms
+    CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_18));
+    CHECK(ioctl(fd_frontend, FE_SET_TONE, SEC_TONE_OFF));
+    cCondWait::SleepMs(20);
+    
+    // Send 1st GotoX command, then wait > 15ms
+    uchar gotox_bytes[5] = { 0xe0, 0x31, 0x6e, p1, p2};
+    struct dvb_diseqc_master_cmd gotox_cmd;
+    memcpy(gotox_cmd.msg, gotox_bytes, 5);
+    gotox_cmd.msg_len = 5;
+    CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &gotox_cmd));
+    cCondWait::SleepMs(20);
+
+    // Send repeated GotoX command, then wait > 15ms
+    if (Setup.GotoxRepeat) {
+      gotox_bytes[0] = 0xe1;
+      memcpy(gotox_cmd.msg, gotox_bytes, 5);
+      CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &gotox_cmd));
+      cCondWait::SleepMs(20);
+    }
+    
+ 	 {
+ 	   char mess_move[60];
+        snprintf(mess_move,sizeof(mess_move),"Moving dish to %d.%d%s - %ds", \
+       	(new_source & 0x0800)?-satlong/10:satlong/10, \
+       	(new_source & 0x0800)?-satlong%10:satlong%10, \
+       	(new_source & 0x0800)?"E":"W", (int)waitseconds);
+      // Wait for dish movement and display message approx. for that time
+      Skins.QueueMessage(mtWarning, mess_move, int(1 + waitseconds - Setup.ChannelInfoTime), 0);
+      while (waitseconds > 0.0) {
+        cCondWait::SleepMs(100); // 100ms
+        waitseconds = waitseconds - 100e-3;
+      }
+    }
+#endif
+
+    Setup.GotoxPrevSource = new_source;
+    dsyslog("DiSEqC GotoX done.");
+  }
+}
+
 void cDvbTuner::SetChannel(const cChannel *Channel)
 {
   if (Channel) {
@@ -648,6 +744,7 @@  void cDvbTuner::ExecuteDiseqc(const cDis
         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::daGotoX:     HandleGotox(fd_frontend, channel.Source()); break;
         case cDiseqc::daCodes:     CHECK(ioctl(fd_frontend, FE_DISEQC_SEND_MASTER_CMD, &cmd)); break;
         default: esyslog("ERROR: unknown diseqc command %d", da);
         }
@@ -656,6 +753,21 @@  void cDvbTuner::ExecuteDiseqc(const cDis
      ResetToneAndVoltage(); // makes sure we don't block the bus!
 }
 
+bool cDvbTuner::TestDiseqcForGotoXX(const cDiseqc *Diseqc)
+{
+  struct dvb_diseqc_master_cmd cmd;
+  const char *CurrentAction = NULL;
+  for (;;) {
+    cmd.msg_len = sizeof(cmd.msg);
+    cDiseqc::eDiseqcActions da = Diseqc->TestForGotoXX(&CurrentAction);
+    if (da == cDiseqc::daNone)
+      break;
+    if (da == cDiseqc::daGotoX)
+      return true;
+  }
+  return false;
+}
+
 void cDvbTuner::ResetToneAndVoltage(void)
 {
   CHECK(ioctl(fd_frontend, FE_SET_VOLTAGE, SEC_VOLTAGE_13));
@@ -691,7 +803,8 @@  bool cDvbTuner::SetFrontend(void)
      if (Setup.DiSEqC) {
         if (const cDiseqc *diseqc = Diseqcs.Get(device->CardIndex() + 1, channel.Source(), frequency, dtp.Polarization(), &scr)) {
            frequency -= diseqc->Lof();
-           if (diseqc != lastDiseqc || diseqc->IsScr()) {
+           if (diseqc != lastDiseqc || diseqc->IsScr() || \
+                 ((TestDiseqcForGotoXX(diseqc) && (channel.Source() != lastSource )))) {
               if (GetBondedMaster() == this) {
                  ExecuteDiseqc(diseqc, &frequency);
                  if (frequency == 0)
@@ -700,6 +813,7 @@  bool cDvbTuner::SetFrontend(void)
               else
                  ResetToneAndVoltage();
               lastDiseqc = diseqc;
+              lastSource = channel.Source();
               }
            }
         else {
@@ -708,6 +822,10 @@  bool cDvbTuner::SetFrontend(void)
            }
         }
      else {
+         // Send GotoX DiSEqC command if activated in vdr setup. Then wait with high LNB voltage
+         // estimated time for dish movement
+        HandleGotox(fd_frontend, channel.Source()); 
+     
         int tone = SEC_TONE_OFF;
         if (frequency < (unsigned int)Setup.LnbSLOF) {
            frequency -= Setup.LnbFrequLo;
@@ -824,6 +942,7 @@  void cDvbTuner::Action(void)
                if (Timer.TimedOut()) {
                   tunerStatus = tsSet;
                   lastDiseqc = NULL;
+                  lastSource = 0;
                   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);
@@ -837,6 +956,7 @@  void cDvbTuner::Action(void)
                if (Status & FE_REINIT) {
                   tunerStatus = tsSet;
                   lastDiseqc = NULL;
+                  lastSource = 0;
                   isyslog("frontend %d/%d was reinitialized", adapter, frontend);
                   lastTimeoutReport = 0;
                   continue;
diff -rupN vdr-1.7.22//menu.c vdr-1.7.22_gotoxx_patched//menu.c
--- vdr-1.7.22//menu.c	2011-12-04 15:52:38.000000000 +0100
+++ vdr-1.7.22_gotoxx_patched//menu.c	2011-12-08 12:08:20.499922647 +0100
@@ -2925,6 +2925,15 @@  void cMenuSetupLNB::Setup(void)
          }
      }
 
+  Add(new cMenuEditBoolItem(tr("Setup.LNB$Use GotoX dish positioning"), &data.UseGotox));
+  if (data.UseGotox) {
+     Add(new cMenuEditBoolItem(tr("Setup.LNB$Repeat GotoX commands"), &data.GotoxRepeat));
+     Add(new cMenuEditIntpItem(tr("Setup.LNB$Latitude"), &data.GotoxLat,0,900,&data.GotoxSN,tr("North"),tr("South")));
+     Add(new cMenuEditIntpItem(tr("Setup.LNB$Longitude"), &data.GotoxLong,0,1800,&data.GotoxEW,tr("West"),tr("East")));
+     Add(new cMenuEditIntItem(tr("Setup.LNB$GotoxMaxSwing"), &data.GotoxMaxSwing,0,180));
+     Add(new cMenuEditIntdItem(tr("Setup.LNB$Rotor speed (deg/s)"), &data.GotoxSpeed, 1, 100));
+  }
+
   SetCurrent(Get(current));
   Display();
 }
@@ -2932,6 +2941,7 @@  void cMenuSetupLNB::Setup(void)
 eOSState cMenuSetupLNB::ProcessKey(eKeys Key)
 {
   int oldDiSEqC = data.DiSEqC;
+  int oldUseGotox = data.UseGotox;
   bool DeviceBondingsChanged = false;
   if (Key == kOk) {
      cString NewDeviceBondings = satCableNumbers.ToString();
@@ -2944,6 +2954,10 @@  eOSState cMenuSetupLNB::ProcessKey(eKeys
      Setup();
   else if (DeviceBondingsChanged)
      cDvbDevice::BondDevices(data.DeviceBondings);
+
+  if (Key != kNone && data.UseGotox != oldUseGotox)
+     Setup();
+      
   return state;
 }
 
diff -rupN vdr-1.7.22//menuitems.c vdr-1.7.22_gotoxx_patched//menuitems.c
--- vdr-1.7.22//menuitems.c	2011-08-12 15:19:40.000000000 +0200
+++ vdr-1.7.22_gotoxx_patched//menuitems.c	2011-12-08 12:08:20.501922647 +0100
@@ -1132,3 +1132,122 @@  void cMenuSetupPage::SetupStore(const ch
   if (plugin)
      plugin->SetupStore(Name, Value);
 }
+
+// cMenuEditIntpItem & cMenuEditIntdItem for GotoX function
+
+void cMenuEditIntpItem::Set(void)
+{
+  char buf[16];
+  snprintf(buf, sizeof(buf), "%d.%d %s", *value/10, *value % 10, *value2 ? trueString : falseString);
+  SetValue(buf);
+}
+
+void cMenuEditIntdItem::Set(void)
+{
+  char buf[16];
+  snprintf(buf, sizeof(buf), "%d.%d", *value/10, *value % 10);
+  SetValue(buf);
+}
+
+
+cMenuEditIntpItem::cMenuEditIntpItem(const char *Name, int *Value, int Min, int Max,int *Value2, const char *FalseString,const char *TrueString):cMenuEditIntItem(Name, Value, Min, Max)
+{
+  value = Value;
+  value2= Value2;
+  trueString = TrueString;
+  falseString = FalseString;
+  min = Min;
+  max = Max;
+  Set();
+}
+
+cMenuEditIntdItem::cMenuEditIntdItem(const char *Name, int *Value, int Min, int Max):cMenuEditIntItem(Name, Value, Min, Max)
+{
+  value = Value;
+  min = Min;
+  max = Max;
+  Set();
+}
+
+eOSState cMenuEditIntpItem::ProcessKey(eKeys Key)
+{
+  eOSState state = cMenuEditItem::ProcessKey(Key);
+  if (state == osUnknown)
+  {
+    int newValue = *value;
+    int newValue2= *value2;
+    Key = NORMALKEY(Key);
+    switch (Key) {
+      case kNone  : break;
+      case k0...k9:
+                    if (fresh)
+                    {
+                      *value = 0;
+                      fresh = false;
+                    }
+                    newValue = *value * 10 + (Key - k0);
+                    break;
+      case kLeft  :
+                    newValue2 = 0;
+                    fresh = true;
+                    break;
+      case kRight :
+                    newValue2 = 1;
+                    fresh = true;
+                    break;
+      default     :
+                    if (*value < min) { *value = min; Set(); }
+                    if (*value > max) { *value = max; Set(); }
+                    return state;
+                 }
+    if ((!fresh || min <= newValue) && newValue <= max)
+    {
+      *value = newValue;
+      *value2 = newValue2;
+      Set();
+    }
+ state = osContinue;
+  }
+  return state;
+}
+
+eOSState cMenuEditIntdItem::ProcessKey(eKeys Key)
+{
+  eOSState state = cMenuEditItem::ProcessKey(Key);
+  if (state == osUnknown)
+  {
+    int newValue = *value;
+    Key = NORMALKEY(Key);
+    switch (Key) {
+      case kNone  : break;
+      case k0...k9:
+                    if (fresh)
+                    {
+                      *value = 0;
+                      fresh = false;
+                    }
+                    newValue = *value * 10 + (Key - k0);
+                    break;
+      case kLeft  :
+	            newValue = *value - 1;
+                    fresh = true;
+                    break;
+      case kRight :
+                    newValue = *value + 1;
+                    fresh = true;
+                    break;
+      default     :
+                    if (*value < min) { *value = min; Set(); }
+                    if (*value > max) { *value = max; Set(); }
+                    return state;
+                 }
+    if ((!fresh || min <= newValue) && newValue <= max)
+    {
+      *value = newValue;
+      Set();
+    }
+    state = osContinue;
+  }
+  return state;
+}
+
diff -rupN vdr-1.7.22//menuitems.h vdr-1.7.22_gotoxx_patched//menuitems.h
--- vdr-1.7.22//menuitems.h	2011-06-13 15:46:03.000000000 +0200
+++ vdr-1.7.22_gotoxx_patched//menuitems.h	2011-12-08 12:08:20.502922647 +0100
@@ -204,4 +204,22 @@  public:
   void SetPlugin(cPlugin *Plugin);
   };
 
+class cMenuEditIntpItem : public cMenuEditIntItem {
+protected:
+  virtual void Set(void);
+  const char *falseString, *trueString;
+  int *value2;
+public:
+  cMenuEditIntpItem(const char *Name, int *Value, int Min = 0, int Max = INT_MAX, int *Value2=0, const char *FalseString = "", const char *TrueSting = NULL);
+  virtual eOSState ProcessKey(eKeys Key);
+};
+
+class cMenuEditIntdItem : public cMenuEditIntItem {
+protected:
+  virtual void Set(void);
+public:
+  cMenuEditIntdItem(const char *Name, int *Value, int Min = 0, int Max = INT_MAX);
+  virtual eOSState ProcessKey(eKeys Key);
+};
+
 #endif //__MENUITEMS_H