PositionerSetup: add missing orb_pos in createConfig removed in d73c5a51d0602a0d3a4ad...
[openblackhole/openblackhole-enigma2.git] / lib / python / Plugins / SystemPlugins / PositionerSetup / plugin.py
1 from enigma import eTimer, eDVBSatelliteEquipmentControl, eDVBResourceManager, \
2         eDVBDiseqcCommand, eDVBFrontendParametersSatellite, eDVBFrontendParameters,\
3         iDVBFrontend
4
5 from Screens.Screen import Screen
6 from Screens.MessageBox import MessageBox
7 from Screens.ChoiceBox import ChoiceBox
8 from Plugins.Plugin import PluginDescriptor
9 from Screens.Satconfig import NimSetup
10 from Screens.InfoBar import InfoBar
11 from Components.Label import Label
12 from Components.Button import Button
13 from Components.ConfigList import ConfigList
14 from Components.ConfigList import ConfigListScreen
15 from Components.TunerInfo import TunerInfo
16 from Components.ActionMap import NumberActionMap, ActionMap
17 from Components.NimManager import nimmanager
18 from Components.MenuList import MenuList
19 from Components.ScrollLabel import ScrollLabel
20 from Components.config import config, ConfigSatlist, ConfigNothing, ConfigSelection, \
21          ConfigSubsection, ConfigInteger, ConfigFloat, KEY_LEFT, KEY_RIGHT, KEY_0, getConfigListEntry
22 from Components.TuneTest import Tuner
23 from Components.Pixmap import Pixmap
24 from Tools.Transponder import ConvertToHumanReadable
25
26 from time import sleep
27 from operator import mul as mul
28 from random import SystemRandom as SystemRandom
29 from threading import Thread as Thread
30 from threading import Event as Event
31
32 import log
33 import rotor_calc
34
35 class PositionerSetup(Screen):
36
37         @staticmethod
38         def satposition2metric(position):
39                 if position > 1800:
40                         position = 3600 - position
41                         orientation = "west"
42                 else:
43                         orientation = "east"
44                 return (position, orientation)
45
46         @staticmethod
47         def orbital2metric(position, orientation):
48                 if orientation == "west":
49                         position = 360 - position
50                 if orientation == "south":
51                         position = - position
52                 return position
53
54         @staticmethod
55         def longitude2orbital(position):
56                 if position >= 180:
57                         return 360 - position, "west"
58                 else:
59                         return position, "east"
60
61         @staticmethod
62         def latitude2orbital(position):
63                 if position >= 0:
64                         return position, "north"
65                 else:
66                         return -position, "south"
67
68         FIRST_UPDATE_INTERVAL = 500                             # milliseconds
69         UPDATE_INTERVAL = 50                                    # milliseconds
70         STATUS_MSG_TIMEOUT = 2                                  # seconds
71         LOG_SIZE = 16 * 1024                                    # log buffer size
72
73         def __init__(self, session, feid):
74                 self.session = session
75                 Screen.__init__(self, session)
76                 self.setTitle(_("Positioner setup"))
77                 self.feid = feid
78                 self.oldref = self.session.nav.getCurrentlyPlayingServiceOrGroup()
79                 self.oldref_stop = False
80                 self.rotor_diseqc = True
81                 self.frontend = None
82                 self.rotor_pos = config.usage.showdish.value and config.misc.lastrotorposition.value != 9999
83                 self.orb_pos = 0
84                 getCurrentTuner = None
85                 getCurrentSat = None
86                 self.availablesats = []
87                 log.open(self.LOG_SIZE)
88                 if config.Nims[self.feid].configMode.value == 'advanced':
89                         self.advanced = True
90                         self.advancedconfig = config.Nims[self.feid].advanced
91                         self.advancedsats = self.advancedconfig.sat
92                         self.availablesats = map(lambda x: x[0], nimmanager.getRotorSatListForNim(self.feid))
93                 else:
94                         self.advanced = False
95                         self.availablesats = map(lambda x: x[0], nimmanager.getRotorSatListForNim(self.feid))
96
97                 cur = { }
98                 if not self.openFrontend():
99                         service = self.session.nav.getCurrentService()
100                         feInfo = service and service.frontendInfo()
101                         if feInfo:
102                                 cur_info = feInfo.getTransponderData(True)
103                                 frontendData = feInfo.getAll(True)
104                                 getCurrentTuner = frontendData and frontendData.get("tuner_number", None)
105                                 getCurrentSat = cur_info.get('orbital_position', None)
106                         del feInfo
107                         del service
108                         if self.oldref and getCurrentTuner is not None:
109                                 if getCurrentTuner < 4 and self.feid == getCurrentTuner:
110                                         self.oldref_stop = True
111                                 if self.oldref_stop:
112                                         self.session.nav.stopService() # try to disable foreground service
113                                         if getCurrentSat is not None and getCurrentSat in self.availablesats:
114                                                 cur = cur_info
115                                         else:
116                                                 self.rotor_diseqc = False
117                         getCurrentTuner = None
118                         getCurrentSat = None
119                         if not self.openFrontend():
120                                 if hasattr(session, 'pipshown') and session.pipshown: # try to disable pip
121                                         service = self.session.pip.pipservice
122                                         feInfo = service and service.frontendInfo()
123                                         if feInfo:
124                                                 cur_pip_info = feInfo.getTransponderData(True)
125                                                 frontendData = feInfo.getAll(True)
126                                                 getCurrentTuner = frontendData and frontendData.get("tuner_number", None)
127                                                 getCurrentSat = cur_pip_info.get('orbital_position', None)
128                                                 if getCurrentTuner is not None and getCurrentTuner < 4 and self.feid == getCurrentTuner:
129                                                         if getCurrentSat is not None and getCurrentSat in self.availablesats:
130                                                                 cur = cur_pip_info
131                                                         else:
132                                                                 self.rotor_diseqc = False
133                                         del feInfo
134                                         del service
135                                         InfoBar.instance and hasattr(InfoBar.instance, "showPiP") and InfoBar.instance.showPiP()
136                                         if hasattr(session, 'pip'):  # try to disable pip again
137                                                 del session.pip
138                                                 session.pipshown = False
139                                         if not self.openFrontend():
140                                                 self.frontend = None # in normal case this should not happen
141                                                 if hasattr(self, 'raw_channel'):
142                                                         del self.raw_channel
143                         if self.frontend is None:
144                                 self.messageTimer = eTimer()
145                                 self.messageTimer.callback.append(self.showMessageBox)
146                                 self.messageTimer.start(2000, True)
147                 self.frontendStatus = { }
148                 self.diseqc = Diseqc(self.frontend)
149                 # True means we dont like that the normal sec stuff sends commands to the rotor!
150                 self.tuner = Tuner(self.frontend, ignore_rotor = True)
151
152                 tp = ( cur.get("frequency", 0) / 1000,
153                         cur.get("symbol_rate", 0) / 1000,
154                         cur.get("polarization", eDVBFrontendParametersSatellite.Polarisation_Horizontal),
155                         cur.get("fec_inner", eDVBFrontendParametersSatellite.FEC_Auto),
156                         cur.get("inversion", eDVBFrontendParametersSatellite.Inversion_Unknown),
157                         cur.get("orbital_position", 0),
158                         cur.get("system", eDVBFrontendParametersSatellite.System_DVB_S),
159                         cur.get("modulation", eDVBFrontendParametersSatellite.Modulation_QPSK),
160                         cur.get("rolloff", eDVBFrontendParametersSatellite.RollOff_alpha_0_35),
161                         cur.get("pilot", eDVBFrontendParametersSatellite.Pilot_Unknown))
162
163                 self.tuner.tune(tp)
164                 self.isMoving = False
165                 self.stopOnLock = False
166
167                 self.red = Button("")
168                 self["key_red"] = self.red
169                 self.green = Button("")
170                 self["key_green"] = self.green
171                 self.yellow = Button("")
172                 self["key_yellow"] = self.yellow
173                 self.blue = Button("")
174                 self["key_blue"] = self.blue
175
176                 self.list = []
177                 self["list"] = ConfigList(self.list)
178
179                 self["snr_db"] = TunerInfo(TunerInfo.SNR_DB, statusDict = self.frontendStatus)
180                 self["snr_bar"] = TunerInfo(TunerInfo.SNR_BAR, statusDict = self.frontendStatus)
181                 self["snr_percentage"] = TunerInfo(TunerInfo.SNR_PERCENTAGE, statusDict = self.frontendStatus)
182                 self["agc_percentage"] = TunerInfo(TunerInfo.AGC_PERCENTAGE, statusDict = self.frontendStatus)
183                 self["agc_bar"] = TunerInfo(TunerInfo.AGC_BAR, statusDict = self.frontendStatus)
184                 self["ber_value"] = TunerInfo(TunerInfo.BER_VALUE, statusDict = self.frontendStatus)
185                 self["ber_bar"] = TunerInfo(TunerInfo.BER_BAR, statusDict = self.frontendStatus)
186                 self["lock_state"] = TunerInfo(TunerInfo.LOCK_STATE, statusDict = self.frontendStatus)
187
188                 self["rotorstatus"] = Label("")
189                 self["frequency_value"] = Label("")
190                 self["symbolrate_value"] = Label("")
191                 self["fec_value"] = Label("")
192                 self["status_bar"] = Label("")
193                 self["SNR"] = Label(_("SNR:"))
194                 self["BER"] = Label(_("BER:"))
195                 self["AGC"] = Label(_("AGC:"))
196                 self["Frequency"] = Label(_("Frequency:"))
197                 self["Symbolrate"] = Label (_("Symbol rate:"))
198                 self["FEC"] = Label (_("FEC:"))
199                 self["Lock"] = Label(_("Lock:"))
200                 self["lock_off"] = Pixmap()
201                 self["lock_on"] = Pixmap()
202                 self["lock_on"].hide()
203                 if self.rotor_pos:
204                         if hasattr(eDVBSatelliteEquipmentControl.getInstance(), "getTargetOrbitalPosition"):
205                                 current_pos = eDVBSatelliteEquipmentControl.getInstance().getTargetOrbitalPosition()
206                                 if current_pos != 0 and current_pos != config.misc.lastrotorposition.value:
207                                         config.misc.lastrotorposition.value = current_pos
208                                         config.misc.lastrotorposition.save()
209                         text = _("Current rotor position: ") + self.OrbToStr(config.misc.lastrotorposition.value)
210                         self["rotorstatus"].setText(text)
211                 self.statusMsgTimeoutTicks = 0
212                 self.statusMsgBlinking = False
213                 self.statusMsgBlinkCount = 0
214                 self.statusMsgBlinkRate = 500 / self.UPDATE_INTERVAL    # milliseconds
215                 self.tuningChangedTo(tp)
216
217                 self["actions"] = NumberActionMap(["DirectionActions", "OkCancelActions", "ColorActions", "TimerEditActions", "InputActions", "InfobarMenuActions"],
218                 {
219                         "ok": self.keyOK,
220                         "cancel": self.keyCancel,
221                         "up": self.keyUp,
222                         "down": self.keyDown,
223                         "left": self.keyLeft,
224                         "right": self.keyRight,
225                         "red": self.redKey,
226                         "green": self.greenKey,
227                         "yellow": self.yellowKey,
228                         "blue": self.blueKey,
229                         "log": self.showLog,
230                         "mainMenu": self.furtherOptions,
231                         "1": self.keyNumberGlobal,
232                         "2": self.keyNumberGlobal,
233                         "3": self.keyNumberGlobal,
234                         "4": self.keyNumberGlobal,
235                         "5": self.keyNumberGlobal,
236                         "6": self.keyNumberGlobal,
237                         "7": self.keyNumberGlobal,
238                         "8": self.keyNumberGlobal,
239                         "9": self.keyNumberGlobal,
240                         "0": self.keyNumberGlobal
241                 }, -1)
242
243                 self.updateColors("tune")
244                 self.statusTimer = eTimer()
245                 self.rotorStatusTimer = eTimer() 
246                 self.statusTimer.callback.append(self.updateStatus)
247                 self.rotorStatusTimer.callback.append(self.startStatusTimer)
248                 self.collectingStatistics = False
249                 self.statusTimer.start(self.FIRST_UPDATE_INTERVAL, True)
250                 self.dataAvailable = Event()
251                 self.onClose.append(self.__onClose)
252                 self.createConfig()
253                 self.createSetup()
254
255         def __onClose(self):
256                 self.statusTimer.stop()
257                 log.close();
258                 self.session.nav.playService(self.oldref)
259
260         def OrbToStr(self, orbpos):
261                 if orbpos > 1800:
262                         orbpos = 3600 - orbpos
263                         return "%d.%d\xc2\xb0 W" % (orbpos/10, orbpos%10)
264                 return "%d.%d\xc2\xb0 E" % (orbpos/10, orbpos%10)
265
266         def setDishOrbosValue(self):
267                 if self.getRotorMovingState():
268                         if self.orb_pos != 0 and self.orb_pos != config.misc.lastrotorposition.value:
269                                 config.misc.lastrotorposition.value = self.orb_pos
270                                 config.misc.lastrotorposition.save()
271                         text = _("Moving to position") + " " + self.OrbToStr(self.orb_pos)
272                         self.startStatusTimer()
273                 else:
274                         text = _("Current rotor position: ") + self.OrbToStr(config.misc.lastrotorposition.value)
275                 self["rotorstatus"].setText(text)
276
277         def startStatusTimer(self):
278                 self.rotorStatusTimer.start(1000, True)
279
280         def getRotorMovingState(self):
281                 return eDVBSatelliteEquipmentControl.getInstance().isRotorMoving()
282
283         def showMessageBox(self):
284                 text = _("Sorry, this tuner is in use.")
285                 if self.session.nav.getRecordings():
286                         text += "\n"
287                         text += _("Maybe the reason that recording is currently running. Please stop the recording before trying to configure the positioner.")
288                 self.session.open(MessageBox, text, MessageBox.TYPE_ERROR)
289
290         def restartPrevService(self, yesno):
291                 if yesno:
292                         if self.frontend:
293                                 self.frontend = None
294                                 if hasattr(self, 'raw_channel'):
295                                         del self.raw_channel
296                 else:
297                         self.oldref = None
298                 self.close(None)
299
300         def keyCancel(self):
301                 if self.oldref is not None:
302                         if self.oldref_stop:
303                                 self.session.openWithCallback(self.restartPrevService, MessageBox, _("Zap back to service before positioner setup?"), MessageBox.TYPE_YESNO)
304                         else:
305                                 self.restartPrevService(True)
306                 else:
307                         self.restartPrevService(False)
308
309         def openFrontend(self):
310                 self.frontend = None
311                 if hasattr(self, 'raw_channel'):
312                         del self.raw_channel
313                 res_mgr = eDVBResourceManager.getInstance()
314                 if res_mgr:
315                         self.raw_channel = res_mgr.allocateRawChannel(self.feid)
316                         if self.raw_channel:
317                                 self.frontend = self.raw_channel.getFrontend()
318                                 if self.frontend:
319                                         return True
320                                 else:
321                                         print "getFrontend failed"
322                         else:
323                                 print "getRawChannel failed"
324                 else:
325                         print "getResourceManager instance failed"
326                 return False
327
328         def setLNB(self, lnb):
329                 try:
330                         self.sitelon = lnb.longitude.float
331                         self.longitudeOrientation = lnb.longitudeOrientation.value
332                         self.sitelat = lnb.latitude.float
333                         self.latitudeOrientation = lnb.latitudeOrientation.value
334                         self.tuningstepsize = lnb.tuningstepsize.float
335                         self.rotorPositions = lnb.rotorPositions.value
336                         self.turningspeedH = lnb.turningspeedH.float
337                         self.turningspeedV = lnb.turningspeedV.float
338                 except: # some reasonable defaults from NimManager
339                         self.sitelon = 5.1
340                         self.longitudeOrientation = 'east'
341                         self.sitelat = 50.767
342                         self.latitudeOrientation = 'north'
343                         self.tuningstepsize = 0.36
344                         self.rotorPositions = 99
345                         self.turningspeedH = 2.3
346                         self.turningspeedV = 1.7
347                 self.sitelat = PositionerSetup.orbital2metric(self.sitelat, self.latitudeOrientation)
348                 self.sitelon = PositionerSetup.orbital2metric(self.sitelon, self.longitudeOrientation)
349
350         def createConfig(self):
351                 rotorposition = 1
352                 orb_pos = 0
353                 self.printMsg(_("Using tuner %s") % chr(0x41 + self.feid))
354                 if not self.advanced:
355                         self.printMsg(_("Configuration mode: %s") % _("simple"))
356                         nim = config.Nims[self.feid]
357                         self.sitelon = nim.longitude.float
358                         self.longitudeOrientation = nim.longitudeOrientation.value
359                         self.sitelat = nim.latitude.float
360                         self.latitudeOrientation = nim.latitudeOrientation.value
361                         self.sitelat = PositionerSetup.orbital2metric(self.sitelat, self.latitudeOrientation)
362                         self.sitelon = PositionerSetup.orbital2metric(self.sitelon, self.longitudeOrientation)
363                         self.tuningstepsize = nim.tuningstepsize.float
364                         self.rotorPositions = nim.rotorPositions.value
365                         self.turningspeedH = nim.turningspeedH.float
366                         self.turningspeedV = nim.turningspeedV.float
367                 else:   # it is advanced
368                         lnb = None
369                         self.printMsg(_("Configuration mode: %s") % _("advanced"))
370                         fe_data = { }
371                         if self.frontend:
372                                 self.frontend.getFrontendData(fe_data)
373                                 self.frontend.getTransponderData(fe_data, True)
374                                 orb_pos = fe_data.get("orbital_position", None)
375                                 if orb_pos is not None and orb_pos in self.availablesats:
376                                         rotorposition = int(self.advancedsats[orb_pos].rotorposition.value)
377                                 lnb = self.getLNBfromConfig(orb_pos)
378                         self.setLNB(lnb)
379                 self.positioner_tune = ConfigNothing()
380                 self.positioner_move = ConfigNothing()
381                 self.positioner_finemove = ConfigNothing()
382                 self.positioner_limits = ConfigNothing()
383                 self.positioner_storage = ConfigInteger(default = rotorposition, limits = (1, self.rotorPositions))
384                 self.allocatedIndices = []
385                 m = PositionerSetup.satposition2metric(orb_pos)
386                 self.orbitalposition = ConfigFloat(default = [int(m[0] / 10), m[0] % 10], limits = [(0,180),(0,9)])
387                 self.orientation = ConfigSelection([("east", _("East")), ("west", _("West"))], default = m[1])
388                 for x in (self.positioner_tune, self.positioner_storage, self.orbitalposition):
389                         x.addNotifier(self.retune, initial_call = False)
390
391         def retune(self, configElement):
392                 self.createSetup()
393
394         def getUsals(self):
395                 usals = None
396                 if self.frontend is not None:
397                         if self.advanced:
398                                 fe_data = { }
399                                 self.frontend.getFrontendData(fe_data)
400                                 self.frontend.getTransponderData(fe_data, True)
401                                 orb_pos = fe_data.get("orbital_position", -9999)
402                                 try:
403                                         pos = str(PositionerSetup.orbital2metric(self.orbitalposition.float, self.orientation.value))
404                                         orb_val = int(pos.replace('.', ''))
405                                 except:
406                                         orb_val = -9999
407                                 sat = -9999
408                                 if orb_val == orb_pos:
409                                         sat = orb_pos
410                                 elif orb_val != -9999 and orb_val in self.availablesats:
411                                         sat = orb_val
412                                 if sat != -9999 and sat in self.availablesats:
413                                         usals = self.advancedsats[sat].usals.value
414                                         self.rotor_diseqc = True
415                                 return usals
416                         else:
417                                 self.rotor_diseqc = True
418                                 return True
419                 return usals
420
421         def getLNBfromConfig(self, orb_pos):
422                 if orb_pos is None or orb_pos == 0:
423                         return None
424                 lnb = None
425                 if orb_pos in self.availablesats:
426                         lnbnum = int(self.advancedsats[orb_pos].lnb.value)
427                         if not lnbnum:
428                                 for allsats in range(3601, 3607):
429                                         lnbnum = int(self.advancedsats[allsats].lnb.value)
430                                         if lnbnum:
431                                                 break
432                         if lnbnum:
433                                 self.printMsg(_("Using LNB %d") % lnbnum)
434                                 lnb = self.advancedconfig.lnb[lnbnum]
435                 if not lnb:
436                         self.logMsg(_("Warning: no LNB; using factory defaults."), timeout = 8)
437                 return lnb
438
439         def createSetup(self):
440                 self.list = []
441                 self.list.append((_("Tune and focus"), self.positioner_tune, "tune"))
442                 self.list.append((_("Movement"), self.positioner_move, "move"))
443                 self.list.append((_("Fine movement"), self.positioner_finemove, "finemove"))
444                 self.list.append((_("Set limits"), self.positioner_limits, "limits"))
445                 self.list.append((_("Memory index") + (self.getUsals() and " (USALS)" or ""), self.positioner_storage, "storage"))
446                 self.list.append((_("Goto"), self.orbitalposition, "goto"))
447                 self.list.append((" ", self.orientation, "goto"))
448                 self["list"].l.setList(self.list)
449
450         def keyOK(self):
451                 pass
452
453         def getCurrentConfigPath(self):
454                 return self["list"].getCurrent()[2]
455
456         def keyUp(self):
457                 if not self.isMoving:
458                         self["list"].instance.moveSelection(self["list"].instance.moveUp)
459                         self.updateColors(self.getCurrentConfigPath())
460
461         def keyDown(self):
462                 if not self.isMoving:
463                         self["list"].instance.moveSelection(self["list"].instance.moveDown)
464                         self.updateColors(self.getCurrentConfigPath())
465
466         def keyNumberGlobal(self, number):
467                 if self.frontend is None:
468                         return
469                 self["list"].handleKey(KEY_0 + number)
470
471         def keyLeft(self):
472                 if self.frontend is None:
473                         return
474                 self["list"].handleKey(KEY_LEFT)
475
476         def keyRight(self):
477                 if self.frontend is None:
478                         return
479                 self["list"].handleKey(KEY_RIGHT)
480
481         def updateColors(self, entry):
482                 if self.frontend is None:
483                         return
484                 if entry == "tune":
485                         self.red.setText(_("Tune"))
486                         self.green.setText(_("Auto focus"))
487                         self.yellow.setText(_("Calibrate"))
488                         self.blue.setText(_("Calculate"))
489                 elif entry == "move":
490                         if self.isMoving:
491                                 self.red.setText(_("Stop"))
492                                 self.green.setText(_("Stop"))
493                                 self.yellow.setText(_("Stop"))
494                                 self.blue.setText(_("Stop"))
495                         else:
496                                 self.red.setText(_("Move west"))
497                                 self.green.setText(_("Search west"))
498                                 self.yellow.setText(_("Search east"))
499                                 self.blue.setText(_("Move east"))
500                 elif entry == "finemove":
501                         self.red.setText("")
502                         self.green.setText(_("Step west"))
503                         self.yellow.setText(_("Step east"))
504                         self.blue.setText("")
505                 elif entry == "limits":
506                         self.red.setText(_("Limits off"))
507                         self.green.setText(_("Limit west"))
508                         self.yellow.setText(_("Limit east"))
509                         self.blue.setText(_("Limits on"))
510                 elif entry == "storage":
511                         self.red.setText("")
512                         if self.getUsals() is False:
513                                 self.green.setText(_("Store position"))
514                                 self.yellow.setText(_("Goto position"))
515                         else:
516                                 self.green.setText("")
517                                 self.yellow.setText("")
518                         if self.advanced and self.getUsals() is False:
519                                 self.blue.setText(_("Allocate"))
520                         else:
521                                 self.blue.setText("")
522                 elif entry == "goto":
523                         self.red.setText("")
524                         self.green.setText(_("Goto 0"))
525                         self.yellow.setText(_("Goto X"))
526                         self.blue.setText("")
527                 else:
528                         self.red.setText("")
529                         self.green.setText("")
530                         self.yellow.setText("")
531                         self.blue.setText("")
532
533         def printMsg(self, msg):
534                 print msg
535                 print>>log, msg
536
537         def stopMoving(self):
538                 self.printMsg(_("Stop"))
539                 self.diseqccommand("stop")
540                 self.isMoving = False
541                 self.stopOnLock = False
542                 self.statusMsg(_("Stopped"), timeout = self.STATUS_MSG_TIMEOUT)
543
544         def redKey(self):
545                 if self.frontend is None:
546                         return
547                 entry = self.getCurrentConfigPath()
548                 if entry == "move":
549                         if self.isMoving:
550                                 self.stopMoving()
551                         else:
552                                 self.printMsg(_("Move west"))
553                                 self.diseqccommand("moveWest", 0)
554                                 self.isMoving = True
555                                 self.statusMsg(_("Moving west ..."), blinking = True)
556                         self.updateColors("move")
557                 elif entry == "limits":
558                         self.printMsg(_("Limits off"))
559                         self.diseqccommand("limitOff")
560                         self.statusMsg(_("Limits cancelled"), timeout = self.STATUS_MSG_TIMEOUT)
561                 elif entry == "tune":
562                         fe_data = { }
563                         self.frontend.getFrontendData(fe_data)
564                         self.frontend.getTransponderData(fe_data, True)
565                         feparm = self.tuner.lastparm.getDVBS()
566                         fe_data["orbital_position"] = feparm.orbital_position
567                         self.statusTimer.stop()
568                         self.session.openWithCallback(self.tune, TunerScreen, self.feid, fe_data)
569
570         def greenKey(self):
571                 if self.frontend is None:
572                         return
573                 entry = self.getCurrentConfigPath()
574                 if entry == "tune":
575                         # Auto focus
576                         self.printMsg(_("Auto focus"))
577                         print>>log, (_("Site latitude") + "      : %5.1f %s") % PositionerSetup.latitude2orbital(self.sitelat)
578                         print>>log, (_("Site longitude") + "     : %5.1f %s") % PositionerSetup.longitude2orbital(self.sitelon)
579                         Thread(target = self.autofocus).start()
580                 elif entry == "move":
581                         if self.isMoving:
582                                 self.stopMoving()
583                         else:
584                                 self.printMsg(_("Search west"))
585                                 self.isMoving = True
586                                 self.stopOnLock = True
587                                 self.diseqccommand("moveWest", 0)
588                                 self.statusMsg(_("Searching west ..."), blinking = True)
589                         self.updateColors("move")
590                 elif entry == "finemove":
591                         self.printMsg(_("Step west"))
592                         self.diseqccommand("moveWest", 0xFF) # one step
593                         self.statusMsg(_("Stepped west"), timeout = self.STATUS_MSG_TIMEOUT)
594                 elif entry == "storage":
595                         if self.getUsals() is False:
596                                 menu = [(_("Yes"), "yes"), (_("No"), "no")]
597                                 available_orbos = False
598                                 orbos = None
599                                 if self.advanced:
600                                         try:
601                                                 orb_pos = str(PositionerSetup.orbital2metric(self.orbitalposition.float, self.orientation.value))
602                                                 orbos = int(orb_pos.replace('.', ''))
603                                         except:
604                                                 pass
605                                         if orbos is not None and orbos in self.availablesats:
606                                                 available_orbos = True
607                                                 menu.append((_("Yes (save index in setup tuner)"), "save"))
608                                 index = int(self.positioner_storage.value)
609                                 text =  _("Really store at index %2d for current position?") % index
610                                 def saveAction(choice):
611                                         if choice:
612                                                 if choice[1] in ("yes", "save"):
613                                                         self.printMsg(_("Store at index"))
614                                                         self.diseqccommand("store", index)
615                                                         self.statusMsg((_("Position stored at index") + " %2d") % index, timeout = self.STATUS_MSG_TIMEOUT)
616                                                         if choice[1] == "save" and available_orbos:
617                                                                 self.advancedsats[orbos].rotorposition.value = index
618                                                                 self.advancedsats[orbos].rotorposition.save()
619                                 self.session.openWithCallback(saveAction, ChoiceBox, title=text, list=menu)
620                 elif entry == "limits":
621                         self.printMsg(_("Limit west"))
622                         self.diseqccommand("limitWest")
623                         self.statusMsg(_("West limit set"), timeout = self.STATUS_MSG_TIMEOUT)
624                 elif entry == "goto":
625                         self.printMsg(_("Goto 0"))
626                         self.diseqccommand("moveTo", 0)
627                         self.statusMsg(_("Moved to position 0"), timeout = self.STATUS_MSG_TIMEOUT)
628
629         def yellowKey(self):
630                 if self.frontend is None:
631                         return
632                 entry = self.getCurrentConfigPath()
633                 if entry == "move":
634                         if self.isMoving:
635                                 self.stopMoving()
636                         else:
637                                 self.printMsg(_("Move east"))
638                                 self.isMoving = True
639                                 self.stopOnLock = True
640                                 self.diseqccommand("moveEast", 0)
641                                 self.statusMsg(_("Searching east ..."), blinking = True)
642                         self.updateColors("move")
643                 elif entry == "finemove":
644                         self.printMsg(_("Step east"))
645                         self.diseqccommand("moveEast", 0xFF) # one step
646                         self.statusMsg(_("Stepped east"), timeout = self.STATUS_MSG_TIMEOUT)
647                 elif entry == "storage":
648                         if self.getUsals() is False:
649                                 self.printMsg(_("Goto index position"))
650                                 index = int(self.positioner_storage.value)
651                                 self.diseqccommand("moveTo", index)
652                                 self.statusMsg((_("Moved to position at index") + " %2d") % index, timeout = self.STATUS_MSG_TIMEOUT)
653                 elif entry == "limits":
654                         self.printMsg(_("Limit east"))
655                         self.diseqccommand("limitEast")
656                         self.statusMsg(_("East limit set"), timeout = self.STATUS_MSG_TIMEOUT)
657                 elif entry == "goto":
658                         self.printMsg(_("Move to position X"))
659                         satlon = self.orbitalposition.float
660                         position = ("%5.1f %s") % (satlon, self.orientation.value)
661                         print>>log, (_("Satellite longitude:") + " %s") % position
662                         satlon = PositionerSetup.orbital2metric(satlon, self.orientation.value)
663                         self.statusMsg((_("Moving to position") + " %s") % position, timeout = self.STATUS_MSG_TIMEOUT)
664                         self.gotoX(satlon)
665                 elif entry == "tune":
666                         # Start USALS calibration
667                         self.printMsg(_("USALS calibration"))
668                         print>>log, (_("Site latitude") + "      : %5.1f %s") % PositionerSetup.latitude2orbital(self.sitelat)
669                         print>>log, (_("Site longitude") + "     : %5.1f %s") % PositionerSetup.longitude2orbital(self.sitelon)
670                         Thread(target = self.gotoXcalibration).start()
671
672         def blueKey(self):
673                 if self.frontend is None:
674                         return
675                 entry = self.getCurrentConfigPath()
676                 if entry == "move":
677                         if self.isMoving:
678                                 self.stopMoving()
679                         else:
680                                 self.printMsg(_("Move east"))
681                                 self.diseqccommand("moveEast", 0)
682                                 self.isMoving = True
683                                 self.statusMsg(_("Moving east ..."), blinking = True)
684                         self.updateColors("move")
685                 elif entry == "limits":
686                         self.printMsg(_("Limits on"))
687                         self.diseqccommand("limitOn")
688                         self.statusMsg(_("Limits enabled"), timeout = self.STATUS_MSG_TIMEOUT)
689                 elif entry == "tune":
690                         # Start (re-)calculate
691                         self.session.openWithCallback(self.recalcConfirmed, MessageBox, _("This will (re-)calculate all positions of your rotor and may remove previously memorised positions and fine-tuning!\nAre you sure?"), MessageBox.TYPE_YESNO, default = False, timeout = 10)
692                 elif entry == "storage":
693                         if self.advanced and self.getUsals() is False:
694                                 self.printMsg(_("Allocate unused memory index"))
695                                 while(True):
696                                         if not len(self.allocatedIndices):
697                                                 for sat in self.availablesats:
698                                                         usals = self.advancedsats[sat].usals.value
699                                                         if not usals:
700                                                                 self.allocatedIndices.append(int(self.advancedsats[sat].rotorposition.value))
701                                                 if len(self.allocatedIndices) == self.rotorPositions:
702                                                         self.statusMsg(_("No free index available"), timeout = self.STATUS_MSG_TIMEOUT)
703                                                         break
704                                         index = 1
705                                         if len(self.allocatedIndices):
706                                                 for i in sorted(self.allocatedIndices):
707                                                         if i != index:
708                                                                 break
709                                                         index += 1
710                                         if index <= self.rotorPositions:
711                                                 self.positioner_storage.value = index
712                                                 self["list"].invalidateCurrent()
713                                                 self.allocatedIndices.append(index)
714                                                 self.statusMsg((_("Index allocated:") + " %2d") % index, timeout = self.STATUS_MSG_TIMEOUT)
715                                                 break
716                                         else:
717                                                 self.allocatedIndices = []
718
719         def recalcConfirmed(self, yesno):
720                 if yesno:
721                         self.printMsg(_("Calculate all positions"))
722                         print>>log, (_("Site latitude") + "      : %5.1f %s") % PositionerSetup.latitude2orbital(self.sitelat)
723                         print>>log, (_("Site longitude") + "     : %5.1f %s") % PositionerSetup.longitude2orbital(self.sitelon)
724                         lon = self.sitelon
725                         if lon >= 180:
726                                 lon -= 360
727                         if lon < -30:   # americas, make unsigned binary west positive polarity
728                                 lon = -lon
729                         lon = int(round(lon)) & 0xFF
730                         lat = int(round(self.sitelat)) & 0xFF
731                         index = int(self.positioner_storage.value) & 0xFF
732                         self.diseqccommand("calc", (((index << 8) | lon) << 8) | lat)
733                         self.statusMsg(_("Calculation complete"), timeout = self.STATUS_MSG_TIMEOUT)
734
735         def showLog(self):
736                 self.session.open(PositionerSetupLog)
737
738         def diseqccommand(self, cmd, param = 0):
739                 print>>log, "Diseqc(%s, %X)" % (cmd, param)
740                 self["rotorstatus"].setText("")
741                 self.diseqc.command(cmd, param)
742                 self.tuner.retune()
743
744         def tune(self, transponder):
745                 # re-start the update timer
746                 self.statusTimer.start(self.UPDATE_INTERVAL, True)
747                 self.createSetup()
748                 if transponder is not None:
749                         self.tuner.tune(transponder)
750                         self.tuningChangedTo(transponder)
751                 feparm = self.tuner.lastparm.getDVBS()
752                 orb_pos = feparm.orbital_position
753                 m = PositionerSetup.satposition2metric(orb_pos)
754                 self.orbitalposition.value = [int(m[0] / 10), m[0] % 10]
755                 self.orientation.value = m[1]
756                 if self.advanced:
757                         if orb_pos in self.availablesats:
758                                 rotorposition = int(self.advancedsats[orb_pos].rotorposition.value)
759                                 self.positioner_storage.value = rotorposition
760                                 self.allocatedIndices = []
761                         self.setLNB(self.getLNBfromConfig(orb_pos))
762
763         def furtherOptions(self):
764                 menu = []
765                 text = _("Select action")
766                 description = _("Open setup tuner ") + "%s" % chr(0x41 + self.feid)
767                 menu.append((description, self.openTunerSetup))
768                 def openAction(choice):
769                         if choice:
770                                 choice[1]()
771                 self.session.openWithCallback(openAction, ChoiceBox, title=text, list=menu)
772
773         def openTunerSetup(self):
774                 self.session.openWithCallback(self.closeTunerSetup, NimSetup, self.feid)
775
776         def closeTunerSetup(self):
777                 self.restartPrevService(True)
778
779         def isLocked(self):
780                 return self.frontendStatus.get("tuner_locked", 0) == 1
781
782         def statusMsg(self, msg, blinking = False, timeout = 0):                        # timeout in seconds
783                 self.statusMsgBlinking = blinking
784                 if not blinking:
785                         self["status_bar"].visible = True
786                 self["status_bar"].setText(msg)
787                 self.statusMsgTimeoutTicks = (timeout * 1000 + self.UPDATE_INTERVAL / 2) / self.UPDATE_INTERVAL
788
789         def updateStatus(self):
790                 self.statusTimer.start(self.UPDATE_INTERVAL, True)
791                 if self.frontend:
792                         self.frontend.getFrontendStatus(self.frontendStatus)
793                 if self.rotor_diseqc:
794                         self["snr_db"].update()
795                         self["snr_percentage"].update()
796                         self["ber_value"].update()
797                         self["snr_bar"].update()
798                         self["agc_percentage"].update()
799                         self["agc_bar"].update()
800                         self["ber_bar"].update()
801                         self["lock_state"].update()
802                         if self["lock_state"].getValue(TunerInfo.LOCK):
803                                 self["lock_on"].show()
804                         else:
805                                 self["lock_on"].hide()
806                 if self.statusMsgBlinking:
807                         self.statusMsgBlinkCount += 1
808                         if self.statusMsgBlinkCount == self.statusMsgBlinkRate:
809                                 self.statusMsgBlinkCount = 0
810                                 self["status_bar"].visible = not self["status_bar"].visible
811                 if self.statusMsgTimeoutTicks > 0:
812                         self.statusMsgTimeoutTicks -= 1
813                         if self.statusMsgTimeoutTicks == 0:
814                                 self["status_bar"].setText("")
815                                 self.statusMsgBlinking = False
816                                 self["status_bar"].visible = True
817                 if self.isLocked() and self.isMoving and self.stopOnLock:
818                         self.stopMoving()
819                         self.updateColors(self.getCurrentConfigPath())
820                 if self.collectingStatistics:
821                         self.low_rate_adapter_count += 1
822                         if self.low_rate_adapter_count == self.MAX_LOW_RATE_ADAPTER_COUNT:
823                                 self.low_rate_adapter_count = 0
824                                 self.snr_percentage += self["snr_percentage"].getValue(TunerInfo.SNR)
825                                 self.lock_count += self["lock_state"].getValue(TunerInfo.LOCK)
826                                 self.stat_count += 1
827                                 if self.stat_count == self.max_count:
828                                         self.collectingStatistics = False
829                                         count = float(self.stat_count)
830                                         self.lock_count /= count
831                                         self.snr_percentage *= 100.0 / 0x10000 / count
832                                         self.dataAvailable.set()
833
834         def tuningChangedTo(self, tp):
835
836                 def setLowRateAdapterCount(symbolrate):
837                         # change the measurement time and update interval in case of low symbol rate,
838                         # since more time is needed for the front end in that case.
839                         # It is an heuristic determination without any pretence. For symbol rates
840                         # of 5000 the interval is multiplied by 3 until 15000 which is seen
841                         # as a high symbol rate. Linear interpolation elsewhere.
842                         return max(int(round((3 - 1) * (symbolrate - 15000) / (5000 - 15000) + 1)), 1)
843  
844                 self.symbolrate = tp[1]
845                 self.polarisation = tp[2]
846                 self.MAX_LOW_RATE_ADAPTER_COUNT = setLowRateAdapterCount(self.symbolrate)
847                 if len(self.tuner.getTransponderData()):
848                         transponderdata = ConvertToHumanReadable(self.tuner.getTransponderData(), "DVB-S")
849                 else:
850                         transponderdata = { }
851                 polarization_text = ""
852                 polarization = transponderdata.get("polarization")
853                 if polarization:
854                         polarization_text = str(polarization)
855                         if polarization_text == _("Horizontal"):
856                                 polarization_text = " H"
857                         elif polarization_text == _("Vertical"):
858                                 polarization_text = " V"
859                         elif polarization_text == _("Circular right"):
860                                 polarization_text = " R"
861                         elif polarization_text == _("Circular left"):
862                                 polarization_text = " L"
863                 frequency_text = ""
864                 frequency = transponderdata.get("frequency")
865                 if frequency:
866                         frequency_text = str(frequency / 1000) + polarization_text
867                 self["frequency_value"].setText(frequency_text)
868                 symbolrate_text = ""
869                 symbolrate = transponderdata.get("symbol_rate")
870                 if symbolrate:
871                         symbolrate_text = str(symbolrate / 1000)
872                 self["symbolrate_value"].setText(symbolrate_text)
873                 fec_text = ""
874                 fec_inner = transponderdata.get("fec_inner")
875                 if fec_inner:
876                         if frequency and symbolrate:
877                                 fec_text = str(fec_inner)
878                 self["fec_value"].setText(fec_text)
879
880
881         @staticmethod
882         def rotorCmd2Step(rotorCmd, stepsize):
883                 return round(float(rotorCmd & 0xFFF) / 0x10 / stepsize) * (1 - ((rotorCmd & 0x1000) >> 11))
884
885         @staticmethod
886         def gotoXcalc(satlon, sitelat, sitelon):
887                 def azimuth2Rotorcode(angle):
888                         gotoXtable = (0x00, 0x02, 0x03, 0x05, 0x06, 0x08, 0x0A, 0x0B, 0x0D, 0x0E)
889                         a = int(round(abs(angle) * 10.0))
890                         return ((a / 10) << 4) + gotoXtable[a % 10]
891
892                 satHourAngle = rotor_calc.calcSatHourangle(satlon, sitelat, sitelon)
893                 if sitelat >= 0: # Northern Hemisphere
894                         rotorCmd = azimuth2Rotorcode(180 - satHourAngle)
895                         if satHourAngle <= 180: # the east
896                                 rotorCmd |= 0xE000
897                         else:                                   # west
898                                 rotorCmd |= 0xD000
899                 else: # Southern Hemisphere
900                         if satHourAngle <= 180: # the east
901                                 rotorCmd = azimuth2Rotorcode(satHourAngle) | 0xD000
902                         else: # west
903                                 rotorCmd = azimuth2Rotorcode(360 - satHourAngle) | 0xE000
904                 return rotorCmd
905
906         def gotoX(self, satlon):
907                 rotorCmd = PositionerSetup.gotoXcalc(satlon, self.sitelat, self.sitelon)
908                 self.diseqccommand("gotoX", rotorCmd)
909                 x = PositionerSetup.rotorCmd2Step(rotorCmd, self.tuningstepsize)
910                 print>>log, (_("Rotor step position:") + " %4d") % x
911                 return x
912
913         def getTurningspeed(self):
914                 if self.polarisation == eDVBFrontendParametersSatellite.Polarisation_Horizontal:
915                         turningspeed = self.turningspeedH
916                 else:
917                         turningspeed = self.turningspeedV
918                 return max(turningspeed, 0.1)
919
920         TURNING_START_STOP_DELAY = 1.600        # seconds
921         MAX_SEARCH_ANGLE = 12.0                         # degrees
922         MAX_FOCUS_ANGLE = 6.0                           # degrees
923         LOCK_LIMIT = 0.1                                        # ratio
924         MEASURING_TIME = 2.500                          # seconds
925
926         def measure(self, time = MEASURING_TIME):       # time in seconds
927                 self.snr_percentage = 0.0
928                 self.lock_count = 0.0
929                 self.stat_count = 0
930                 self.low_rate_adapter_count = 0
931                 self.max_count = max(int((time * 1000 + self.UPDATE_INTERVAL / 2)/ self.UPDATE_INTERVAL), 1)
932                 self.collectingStatistics = True
933                 self.dataAvailable.clear()
934                 self.dataAvailable.wait()
935
936         def logMsg(self, msg, timeout = 0):
937                 self.statusMsg(msg, timeout = timeout)
938                 self.printMsg(msg)
939
940         def sync(self):
941                 self.lock_count = 0.0
942                 n = 0
943                 while self.lock_count < (1 - self.LOCK_LIMIT) and n < 5:
944                         self.measure(time = 0.500)
945                         n += 1
946                 if self.lock_count < (1 - self.LOCK_LIMIT):
947                         return False
948                 return True
949
950         randomGenerator = None
951         def randomBool(self):
952                 if self.randomGenerator is None:
953                         self.randomGenerator = SystemRandom()
954                 return self.randomGenerator.random() >= 0.5
955
956         def gotoXcalibration(self):
957
958                 def move(x):
959                         z = self.gotoX(x + satlon)
960                         time = int(abs(x - prev_pos) / turningspeed + 2 * self.TURNING_START_STOP_DELAY)
961                         sleep(time * self.MAX_LOW_RATE_ADAPTER_COUNT)
962                         return z
963
964                 def reportlevels(pos, level, lock):
965                         print>>log, (_("Signal quality") + " %5.1f" + chr(176) + "   : %6.2f") % (pos, level)
966                         print>>log, (_("Lock ratio") + "     %5.1f" + chr(176) + "   : %6.2f") % (pos, lock)
967
968                 def optimise(readings):
969                         xi = readings.keys()
970                         yi = map(lambda (x, y) : x, readings.values())
971                         x0 = sum(map(mul, xi, yi)) / sum(yi)
972                         xm = xi[yi.index(max(yi))]
973                         return (x0, xm)
974
975                 def toGeopos(x):
976                         if x < 0:
977                                 return _("W")
978                         else:
979                                 return _("E")
980
981                 def toGeoposEx(x):
982                         if x < 0:
983                                 return _("west")
984                         else:
985                                 return _("east")
986
987                 self.logMsg(_("GotoX calibration"))
988                 satlon = self.orbitalposition.float
989                 print>>log, (_("Satellite longitude:") + " %5.1f" + chr(176) + " %s") % (satlon, self.orientation.value)
990                 satlon = PositionerSetup.orbital2metric(satlon, self.orientation.value)
991                 prev_pos = 0.0                                          # previous relative position w.r.t. satlon
992                 turningspeed = self.getTurningspeed()
993
994                 x = 0.0                                                         # relative position w.r.t. satlon
995                 dir = 1
996                 if self.randomBool():
997                         dir = -dir
998                 while abs(x) < self.MAX_SEARCH_ANGLE:
999                         if self.sync():
1000                                 break
1001                         x += (1.0 * dir)                                                # one degree east/west
1002                         self.statusMsg((_("Searching") + " " + toGeoposEx(dir) + " %2d" + chr(176)) % abs(x), blinking = True)
1003                         move(x)
1004                         prev_pos = x
1005                 else:
1006                         x = 0.0
1007                         dir = -dir
1008                         while abs(x) < self.MAX_SEARCH_ANGLE:
1009                                 x += (1.0 * dir)                                        # one degree east/west
1010                                 self.statusMsg((_("Searching") + " " + toGeoposEx(dir) + " %2d" + chr(176)) % abs(x), blinking = True)
1011                                 move(x)
1012                                 prev_pos = x
1013                                 if self.sync():
1014                                         break
1015                         else:
1016                                 msg = _("Cannot find any signal ..., aborting !")
1017                                 self.printMsg(msg)
1018                                 self.statusMsg("")
1019                                 self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, timeout = 5)
1020                                 return
1021                 x = round(x / self.tuningstepsize) * self.tuningstepsize
1022                 move(x)
1023                 prev_pos = x
1024                 measurements = {}
1025                 self.measure()
1026                 print>>log, (_("Initial signal quality") + " %5.1f" + chr(176) + ": %6.2f") % (x, self.snr_percentage)
1027                 print>>log, (_("Initial lock ratio") + "     %5.1f" + chr(176) + ": %6.2f") % (x, self.lock_count)
1028                 measurements[x] = (self.snr_percentage, self.lock_count)
1029
1030                 start_pos = x
1031                 x = 0.0
1032                 dir = 1
1033                 if self.randomBool():
1034                         dir = -dir
1035                 while x < self.MAX_FOCUS_ANGLE:
1036                         x += self.tuningstepsize * dir                                  # one step east/west
1037                         self.statusMsg((_("Moving") + " " + toGeoposEx(dir) + " %5.1f" + chr(176)) % abs(x + start_pos), blinking = True)
1038                         move(x + start_pos)
1039                         prev_pos = x + start_pos
1040                         self.measure()
1041                         measurements[x + start_pos] = (self.snr_percentage, self.lock_count)
1042                         reportlevels(x + start_pos, self.snr_percentage, self.lock_count)
1043                         if self.lock_count < self.LOCK_LIMIT:
1044                                 break
1045                 else:
1046                         msg = _("Cannot determine") + " " + toGeoposEx(dir) + " " + _("limit ..., aborting !")
1047                         self.printMsg(msg)
1048                         self.statusMsg("")
1049                         self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, timeout = 5)
1050                         return
1051                 x = 0.0
1052                 dir = -dir
1053                 self.statusMsg((_("Moving") + " " + toGeoposEx(dir) + " %5.1f" + chr(176)) % abs(start_pos), blinking = True)
1054                 move(start_pos)
1055                 prev_pos = start_pos
1056                 if not self.sync():
1057                         msg = _("Sync failure moving back to origin !")
1058                         self.printMsg(msg)
1059                         self.statusMsg("")
1060                         self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, timeout = 5)
1061                         return
1062                 while abs(x) < self.MAX_FOCUS_ANGLE:
1063                         x += self.tuningstepsize * dir                                  # one step west/east
1064                         self.statusMsg((_("Moving") + " " + toGeoposEx(dir) + " %5.1f" + chr(176)) % abs(x + start_pos), blinking = True)
1065                         move(x + start_pos)
1066                         prev_pos = x + start_pos
1067                         self.measure()
1068                         measurements[x + start_pos] = (self.snr_percentage, self.lock_count)
1069                         reportlevels(x + start_pos, self.snr_percentage, self.lock_count)
1070                         if self.lock_count < self.LOCK_LIMIT:
1071                                 break
1072                 else:
1073                         msg = _("Cannot determine") + " " + toGeoposEx(dir) + " " + _("limit ..., aborting !")
1074                         self.printMsg(msg)
1075                         self.statusMsg("")
1076                         self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, timeout = 5)
1077                         return
1078                 (x0, xm) = optimise(measurements)
1079                 x = move(x0)
1080                 if satlon > 180:
1081                         satlon -= 360
1082                 x0 += satlon
1083                 xm += satlon
1084                 print>>log, (_("Weighted position") + "     : %5.1f" + chr(176) + " %s") % (abs(x0), toGeopos(x0))
1085                 print>>log, (_("Strongest position") + "    : %5.1f" + chr(176) + " %s") % (abs(xm), toGeopos(xm))
1086                 self.logMsg((_("Final position at") + " %5.1f" + chr(176) + " %s / %d; " + _("offset is") + " %4.1f" + chr(176)) % (abs(x0), toGeopos(x0), x, x0 - satlon), timeout = 10)
1087
1088         def autofocus(self):
1089
1090                 def move(x):
1091                         if x > 0:
1092                                 self.diseqccommand("moveEast", (-x) & 0xFF)
1093                         elif x < 0:
1094                                 self.diseqccommand("moveWest", x & 0xFF)
1095                         if x != 0:
1096                                 time = int(abs(x) * self.tuningstepsize / turningspeed + 2 * self.TURNING_START_STOP_DELAY)
1097                                 sleep(time * self.MAX_LOW_RATE_ADAPTER_COUNT)
1098
1099                 def reportlevels(pos, level, lock):
1100                         print>>log, (_("Signal quality") + " [%2d]   : %6.2f") % (pos, level)
1101                         print>>log, (_("Lock ratio") + " [%2d]       : %6.2f") % (pos, lock)
1102
1103                 def optimise(readings):
1104                         xi = readings.keys()
1105                         yi = map(lambda (x, y) : x, readings.values())
1106                         x0 = int(round(sum(map(mul, xi, yi)) / sum(yi)))
1107                         xm = xi[yi.index(max(yi))]
1108                         return (x0, xm)
1109
1110                 def toGeoposEx(x):
1111                         if x < 0:
1112                                 return _("west")
1113                         else:
1114                                 return _("east")
1115
1116                 self.logMsg(_("Auto focus commencing ..."))
1117                 turningspeed = self.getTurningspeed()
1118                 measurements = {}
1119                 maxsteps = max(min(round(self.MAX_FOCUS_ANGLE / self.tuningstepsize), 0x1F), 3)
1120                 self.measure()
1121                 print>>log, (_("Initial signal quality:") + " %6.2f") % self.snr_percentage
1122                 print>>log, (_("Initial lock ratio") + "    : %6.2f") % self.lock_count
1123                 if self.lock_count < 1 - self.LOCK_LIMIT:
1124                         msg = _("There is no signal to lock on !")
1125                         self.printMsg(msg)
1126                         self.statusMsg("")
1127                         self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, timeout = 5)
1128                         return
1129                 print>>log, _("Signal OK, proceeding")
1130                 x = 0
1131                 dir = 1
1132                 if self.randomBool():
1133                         dir = -dir
1134                 measurements[x] = (self.snr_percentage, self.lock_count)
1135                 nsteps = 0
1136                 while nsteps < maxsteps:
1137                         x += dir
1138                         self.statusMsg((_("Moving") + " " + toGeoposEx(dir) + " %2d") % abs(x), blinking = True)
1139                         move(dir)               # one step
1140                         self.measure()
1141                         measurements[x] = (self.snr_percentage, self.lock_count)
1142                         reportlevels(x, self.snr_percentage, self.lock_count)
1143                         if self.lock_count < self.LOCK_LIMIT:
1144                                 break
1145                         nsteps += 1
1146                 else:
1147                         msg = _("Cannot determine") + " " + toGeoposEx(dir) + " " + _("limit ..., aborting !")
1148                         self.printMsg(msg)
1149                         self.statusMsg("")
1150                         self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, timeout = 5)
1151                         return
1152                 dir = -dir
1153                 self.statusMsg(_("Moving") + " " + toGeoposEx(dir) + "  0", blinking = True)
1154                 move(-x)
1155                 if not self.sync():
1156                         msg = _("Sync failure moving back to origin !")
1157                         self.printMsg(msg)
1158                         self.statusMsg("")
1159                         self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, timeout = 5)
1160                         return
1161                 x = 0
1162                 nsteps = 0
1163                 while nsteps < maxsteps:
1164                         x += dir
1165                         self.statusMsg((_("Moving") + " " + toGeoposEx(dir) + " %2d") % abs(x), blinking = True)
1166                         move(dir)               # one step
1167                         self.measure()
1168                         measurements[x] = (self.snr_percentage, self.lock_count)
1169                         reportlevels(x, self.snr_percentage, self.lock_count)
1170                         if self.lock_count < self.LOCK_LIMIT:
1171                                 break
1172                         nsteps += 1
1173                 else:
1174                         msg = _("Cannot determine") + " " + toGeoposEx(dir) + " " + _("limit ..., aborting !")
1175                         self.printMsg(msg)
1176                         self.statusMsg("")
1177                         self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, timeout = 5)
1178                         return
1179                 (x0, xm) = optimise(measurements)
1180                 print>>log, (_("Weighted position") + "     : %2d") % x0
1181                 print>>log, (_("Strongest position") + "    : %2d") % xm
1182                 self.logMsg((_("Final position at index") + " %2d (%5.1f" + chr(176) + ")") % (x0, x0 * self.tuningstepsize), timeout = 6)
1183                 move(x0 - x)
1184
1185 class Diseqc:
1186         def __init__(self, frontend):
1187                 self.frontend = frontend
1188
1189         def command(self, what, param = 0):
1190                 if self.frontend:
1191                         cmd = eDVBDiseqcCommand()
1192                         if what == "moveWest":
1193                                 string = 'E03169' + ("%02X" % param)
1194                         elif what == "moveEast":
1195                                 string = 'E03168' + ("%02X" % param)
1196                         elif what == "moveTo":
1197                                 string = 'E0316B' + ("%02X" % param)
1198                         elif what == "store":
1199                                 string = 'E0316A' + ("%02X" % param)
1200                         elif what == "gotoX":
1201                                 string = 'E0316E' + ("%04X" % param)
1202                         elif what == "calc":
1203                                 string = 'E0316F' + ("%06X" % param)
1204                         elif what == "limitOn":
1205                                 string = 'E0316A00'
1206                         elif what == "limitOff":
1207                                 string = 'E03163'
1208                         elif what == "limitEast":
1209                                 string = 'E03166'
1210                         elif what == "limitWest":
1211                                 string = 'E03167'
1212                         else:
1213                                 string = 'E03160' #positioner stop
1214
1215                         print "diseqc command:",
1216                         print string
1217                         cmd.setCommandString(string)
1218                         self.frontend.setTone(iDVBFrontend.toneOff)
1219                         sleep(0.015) # wait 15msec after disable tone
1220                         self.frontend.sendDiseqc(cmd)
1221                         if string == 'E03160': #positioner stop
1222                                 sleep(0.050)
1223                                 self.frontend.sendDiseqc(cmd) # send 2nd time
1224
1225 class PositionerSetupLog(Screen):
1226         skin = """
1227                 <screen position="center,center" size="560,400" title="Positioner setup log" >
1228                         <ePixmap name="red"    position="0,0"   zPosition="2" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
1229                         <ePixmap name="green"  position="230,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
1230                         <ePixmap name="blue"   position="420,0" zPosition="2" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
1231
1232                         <widget name="key_red" position="0,0" size="140,40" valign="center" halign="center" zPosition="4"  foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
1233                         <widget name="key_green" position="230,0" size="140,40" halign="center" valign="center"  zPosition="4"  foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
1234                         <widget name="key_blue" position="420,0" size="140,40" valign="center" halign="center" zPosition="4"  foregroundColor="white" font="Regular;20" transparent="1" shadowColor="background" shadowOffset="-2,-2" />
1235
1236                         <ePixmap alphatest="on" pixmap="skin_default/icons/clock.png" position="480,383" size="14,14" zPosition="3"/>
1237                         <widget font="Regular;18" halign="left" position="505,380" render="Label" size="55,20" source="global.CurrentTime" transparent="1" valign="center" zPosition="3">
1238                                 <convert type="ClockToText">Default</convert>
1239                         </widget>
1240                         <widget name="list" font="Regular;16" position="10,40" size="540,340" />
1241                 </screen>"""
1242
1243         def __init__(self, session):
1244                 self.session = session
1245                 Screen.__init__(self, session)
1246                 self.setTitle(_("Positioner setup log"))
1247                 self["key_red"] = Button(_("Exit/OK"))
1248                 self["key_green"] = Button(_("Save"))
1249                 self["key_blue"] = Button(_("Clear"))
1250                 self["list"] = ScrollLabel(log.getvalue())
1251                 self["actions"] = ActionMap(["DirectionActions", "OkCancelActions", "ColorActions"],
1252                 {
1253                         "red": self.cancel,
1254                         "green": self.save,
1255                         "save": self.save,
1256                         "blue": self.clear,
1257                         "cancel": self.cancel,
1258                         "ok": self.cancel,
1259                         "left": self["list"].pageUp,
1260                         "right": self["list"].pageDown,
1261                         "up": self["list"].pageUp,
1262                         "down": self["list"].pageDown,
1263                         "pageUp": self["list"].pageUp,
1264                         "pageDown": self["list"].pageDown
1265                 }, -2)
1266
1267         def save(self):
1268                 try:
1269                         f = open('/tmp/positionersetup.log', 'w')
1270                         f.write(log.getvalue())
1271                         f.close()
1272                         self.session.open(MessageBox, _("Write to /tmp/positionersetup.log"), MessageBox.TYPE_INFO, timeout = 5)
1273                 except Exception, e:
1274                         self["list"].setText(_("Failed to write /tmp/positionersetup.log: ") + str(e))
1275                 self.close(True)
1276
1277         def cancel(self):
1278                 self.close(False)
1279
1280         def clear(self):
1281                 log.logfile.reset()
1282                 log.logfile.truncate()
1283                 self.close(False)
1284
1285 class TunerScreen(ConfigListScreen, Screen):
1286         skin = """
1287                 <screen position="center,center" size="520,400" title="Tune">
1288                         <widget name="config" position="20,10" size="460,350" scrollbarMode="showOnDemand" />
1289                         <widget name="introduction" position="60,360" size="450,23" halign="left" font="Regular;20" />
1290                 </screen>"""
1291
1292         def __init__(self, session, feid, fe_data):
1293                 self.feid = feid
1294                 self.fe_data = fe_data
1295                 Screen.__init__(self, session)
1296                 self.setTitle(_("Tune"))
1297                 ConfigListScreen.__init__(self, None)
1298                 self.createConfig(fe_data)
1299                 self.initialSetup()
1300                 self.createSetup()
1301                 self.tuning.sat.addNotifier(self.tuningSatChanged)
1302                 self.tuning.type.addNotifier(self.tuningTypeChanged)
1303                 self.scan_sat.system.addNotifier(self.systemChanged)
1304
1305                 self["actions"] = NumberActionMap(["SetupActions"],
1306                 {
1307                         "ok": self.keyGo,
1308                         "cancel": self.keyCancel,
1309                 }, -2)
1310                 self["introduction"] = Label(_("Press OK, save and exit..."))
1311
1312         def createConfig(self, frontendData):
1313                 satlist = nimmanager.getRotorSatListForNim(self.feid)
1314                 orb_pos = self.fe_data.get("orbital_position", None)
1315                 self.tuning = ConfigSubsection()
1316                 self.tuning.type = ConfigSelection(
1317                                 default = "manual_transponder",
1318                                 choices = { "manual_transponder" : _("Manual transponder"),
1319                                                         "predefined_transponder" : _("Predefined transponder") } )
1320                 self.tuning.sat = ConfigSatlist(list = satlist)
1321                 if orb_pos is not None:
1322                         orb_pos_str = str(orb_pos)
1323                         for sat in satlist:
1324                                 if sat[0] == orb_pos and self.tuning.sat.value != orb_pos_str:
1325                                         self.tuning.sat.value = orb_pos_str
1326                 self.updateTransponders()
1327
1328                 defaultSat = {
1329                         "orbpos": 192,
1330                         "system": eDVBFrontendParametersSatellite.System_DVB_S,
1331                         "frequency": 11836,
1332                         "inversion": eDVBFrontendParametersSatellite.Inversion_Unknown,
1333                         "symbolrate": 27500,
1334                         "polarization": eDVBFrontendParametersSatellite.Polarisation_Horizontal,
1335                         "fec": eDVBFrontendParametersSatellite.FEC_Auto,
1336                         "fec_s2": eDVBFrontendParametersSatellite.FEC_9_10,
1337                         "modulation": eDVBFrontendParametersSatellite.Modulation_QPSK }
1338                 if frontendData is not None:
1339                         ttype = frontendData.get("tuner_type", "UNKNOWN")
1340                         defaultSat["system"] = frontendData.get("system", eDVBFrontendParametersSatellite.System_DVB_S)
1341                         defaultSat["frequency"] = frontendData.get("frequency", 0) / 1000
1342                         defaultSat["inversion"] = frontendData.get("inversion", eDVBFrontendParametersSatellite.Inversion_Unknown)
1343                         defaultSat["symbolrate"] = frontendData.get("symbol_rate", 0) / 1000
1344                         defaultSat["polarization"] = frontendData.get("polarization", eDVBFrontendParametersSatellite.Polarisation_Horizontal)
1345                         if defaultSat["system"] == eDVBFrontendParametersSatellite.System_DVB_S2:
1346                                 defaultSat["fec_s2"] = frontendData.get("fec_inner", eDVBFrontendParametersSatellite.FEC_Auto)
1347                                 defaultSat["rolloff"] = frontendData.get("rolloff", eDVBFrontendParametersSatellite.RollOff_alpha_0_35)
1348                                 defaultSat["pilot"] = frontendData.get("pilot", eDVBFrontendParametersSatellite.Pilot_Unknown)
1349                         else:
1350                                 defaultSat["fec"] = frontendData.get("fec_inner", eDVBFrontendParametersSatellite.FEC_Auto)
1351                         defaultSat["modulation"] = frontendData.get("modulation", eDVBFrontendParametersSatellite.Modulation_QPSK)
1352                         defaultSat["orbpos"] = frontendData.get("orbital_position", 0)
1353
1354                 self.scan_sat = ConfigSubsection()
1355                 self.scan_sat.system = ConfigSelection(default = defaultSat["system"], choices = [
1356                         (eDVBFrontendParametersSatellite.System_DVB_S, _("DVB-S")),
1357                         (eDVBFrontendParametersSatellite.System_DVB_S2, _("DVB-S2"))])
1358                 self.scan_sat.frequency = ConfigInteger(default = defaultSat["frequency"], limits = (1, 99999))
1359                 self.scan_sat.inversion = ConfigSelection(default = defaultSat["inversion"], choices = [
1360                         (eDVBFrontendParametersSatellite.Inversion_Off, _("Off")),
1361                         (eDVBFrontendParametersSatellite.Inversion_On, _("On")),
1362                         (eDVBFrontendParametersSatellite.Inversion_Unknown, _("Auto"))])
1363                 self.scan_sat.symbolrate = ConfigInteger(default = defaultSat["symbolrate"], limits = (1, 99999))
1364                 self.scan_sat.polarization = ConfigSelection(default = defaultSat["polarization"], choices = [
1365                         (eDVBFrontendParametersSatellite.Polarisation_Horizontal, _("horizontal")),
1366                         (eDVBFrontendParametersSatellite.Polarisation_Vertical, _("vertical")),
1367                         (eDVBFrontendParametersSatellite.Polarisation_CircularLeft, _("circular left")),
1368                         (eDVBFrontendParametersSatellite.Polarisation_CircularRight, _("circular right"))])
1369                 self.scan_sat.fec = ConfigSelection(default = defaultSat["fec"], choices = [
1370                         (eDVBFrontendParametersSatellite.FEC_Auto, _("Auto")),
1371                         (eDVBFrontendParametersSatellite.FEC_1_2, "1/2"),
1372                         (eDVBFrontendParametersSatellite.FEC_2_3, "2/3"),
1373                         (eDVBFrontendParametersSatellite.FEC_3_4, "3/4"),
1374                         (eDVBFrontendParametersSatellite.FEC_5_6, "5/6"),
1375                         (eDVBFrontendParametersSatellite.FEC_7_8, "7/8"),
1376                         (eDVBFrontendParametersSatellite.FEC_None, _("None"))])
1377                 self.scan_sat.fec_s2 = ConfigSelection(default = defaultSat["fec_s2"], choices = [
1378                         (eDVBFrontendParametersSatellite.FEC_1_2, "1/2"),
1379                         (eDVBFrontendParametersSatellite.FEC_2_3, "2/3"),
1380                         (eDVBFrontendParametersSatellite.FEC_3_4, "3/4"),
1381                         (eDVBFrontendParametersSatellite.FEC_3_5, "3/5"),
1382                         (eDVBFrontendParametersSatellite.FEC_4_5, "4/5"),
1383                         (eDVBFrontendParametersSatellite.FEC_5_6, "5/6"),
1384                         (eDVBFrontendParametersSatellite.FEC_7_8, "7/8"),
1385                         (eDVBFrontendParametersSatellite.FEC_8_9, "8/9"),
1386                         (eDVBFrontendParametersSatellite.FEC_9_10, "9/10")])
1387                 self.scan_sat.modulation = ConfigSelection(default = defaultSat["modulation"], choices = [
1388                         (eDVBFrontendParametersSatellite.Modulation_QPSK, "QPSK"),
1389                         (eDVBFrontendParametersSatellite.Modulation_8PSK, "8PSK")])
1390                 self.scan_sat.rolloff = ConfigSelection(default = defaultSat.get("rolloff", eDVBFrontendParametersSatellite.RollOff_alpha_0_35), choices = [
1391                         (eDVBFrontendParametersSatellite.RollOff_alpha_0_35, "0.35"),
1392                         (eDVBFrontendParametersSatellite.RollOff_alpha_0_25, "0.25"),
1393                         (eDVBFrontendParametersSatellite.RollOff_alpha_0_20, "0.20"),
1394                         (eDVBFrontendParametersSatellite.RollOff_auto, _("Auto"))])
1395                 self.scan_sat.pilot = ConfigSelection(default = defaultSat.get("pilot", eDVBFrontendParametersSatellite.Pilot_Unknown), choices = [
1396                         (eDVBFrontendParametersSatellite.Pilot_Off, _("Off")),
1397                         (eDVBFrontendParametersSatellite.Pilot_On, _("On")),
1398                         (eDVBFrontendParametersSatellite.Pilot_Unknown, _("Auto"))])
1399
1400         def initialSetup(self):
1401                 currtp = self.transponderToString([None, self.scan_sat.frequency.value, self.scan_sat.symbolrate.value, self.scan_sat.polarization.value])
1402                 if currtp in self.tuning.transponder.choices:
1403                         self.tuning.type.value = "predefined_transponder"
1404                 else:
1405                         self.tuning.type.value = "manual_transponder"
1406
1407         def createSetup(self):
1408                 self.list = []
1409                 self.list.append(getConfigListEntry(_('Tune'), self.tuning.type))
1410                 self.list.append(getConfigListEntry(_('Satellite'), self.tuning.sat))
1411                 nim = nimmanager.nim_slots[self.feid]
1412
1413                 if self.tuning.type.value == "manual_transponder":
1414                         if nim.isCompatible("DVB-S2"):
1415                                 self.list.append(getConfigListEntry(_('System'), self.scan_sat.system))
1416                         else:
1417                                 # downgrade to dvb-s, in case a -s2 config was active
1418                                 self.scan_sat.system.value = eDVBFrontendParametersSatellite.System_DVB_S
1419                         self.list.append(getConfigListEntry(_('Frequency'), self.scan_sat.frequency))
1420                         self.list.append(getConfigListEntry(_("Polarisation"), self.scan_sat.polarization))
1421                         self.list.append(getConfigListEntry(_('Symbol rate'), self.scan_sat.symbolrate))
1422                         if self.scan_sat.system.value == eDVBFrontendParametersSatellite.System_DVB_S:
1423                                 self.list.append(getConfigListEntry(_("FEC"), self.scan_sat.fec))
1424                                 self.list.append(getConfigListEntry(_('Inversion'), self.scan_sat.inversion))
1425                         elif self.scan_sat.system.value == eDVBFrontendParametersSatellite.System_DVB_S2:
1426                                 self.list.append(getConfigListEntry(_("FEC"), self.scan_sat.fec_s2))
1427                                 self.list.append(getConfigListEntry(_('Inversion'), self.scan_sat.inversion))
1428                                 self.modulationEntry = getConfigListEntry(_('Modulation'), self.scan_sat.modulation)
1429                                 self.list.append(self.modulationEntry)
1430                                 self.list.append(getConfigListEntry(_('Roll-off'), self.scan_sat.rolloff))
1431                                 self.list.append(getConfigListEntry(_('Pilot'), self.scan_sat.pilot))
1432                 else: # "predefined_transponder"
1433                         self.list.append(getConfigListEntry(_("Transponder"), self.tuning.transponder))
1434                         currtp = self.transponderToString([None, self.scan_sat.frequency.value, self.scan_sat.symbolrate.value, self.scan_sat.polarization.value])
1435                         self.tuning.transponder.setValue(currtp)
1436                 self["config"].list = self.list
1437                 self["config"].l.setList(self.list)
1438
1439         def tuningSatChanged(self, *parm):
1440                 self.updateTransponders()
1441                 self.createSetup()
1442
1443         def tuningTypeChanged(self, *parm):
1444                 self.createSetup()
1445
1446         def systemChanged(self, *parm):
1447                 self.createSetup()
1448
1449         def transponderToString(self, tr, scale = 1):
1450                 if tr[3] == 0:
1451                         pol = "H"
1452                 elif tr[3] == 1:
1453                         pol = "V"
1454                 elif tr[3] == 2:
1455                         pol = "CL"
1456                 elif tr[3] == 3:
1457                         pol = "CR"
1458                 else:
1459                         pol = "??"
1460                 return str(tr[1] / scale) + "," + pol + "," + str(tr[2] / scale)
1461
1462         def updateTransponders(self):
1463                 if len(self.tuning.sat.choices):
1464                         transponderlist = nimmanager.getTransponders(int(self.tuning.sat.value))
1465                         tps = []
1466                         for transponder in transponderlist:
1467                                 tps.append(self.transponderToString(transponder, scale = 1000))
1468                         self.tuning.transponder = ConfigSelection(choices = tps)
1469
1470         def keyLeft(self):
1471                 ConfigListScreen.keyLeft(self)
1472
1473         def keyRight(self):
1474                 ConfigListScreen.keyRight(self)
1475
1476         def keyGo(self):
1477                 returnvalue = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
1478                 satpos = int(self.tuning.sat.value)
1479                 if self.tuning.type.value == "manual_transponder":
1480                         if self.scan_sat.system.value == eDVBFrontendParametersSatellite.System_DVB_S2:
1481                                 fec = self.scan_sat.fec_s2.value
1482                         else:
1483                                 fec = self.scan_sat.fec.value
1484                         returnvalue = (
1485                                 self.scan_sat.frequency.value,
1486                                 self.scan_sat.symbolrate.value,
1487                                 self.scan_sat.polarization.value,
1488                                 fec,
1489                                 self.scan_sat.inversion.value,
1490                                 satpos,
1491                                 self.scan_sat.system.value,
1492                                 self.scan_sat.modulation.value,
1493                                 self.scan_sat.rolloff.value,
1494                                 self.scan_sat.pilot.value)
1495                 elif self.tuning.type.value == "predefined_transponder":
1496                         transponder = nimmanager.getTransponders(satpos)[self.tuning.transponder.index]
1497                         returnvalue = (transponder[1] / 1000, transponder[2] / 1000,
1498                                 transponder[3], transponder[4], 2, satpos, transponder[5], transponder[6], transponder[8], transponder[9])
1499                 self.close(returnvalue)
1500
1501         def keyCancel(self):
1502                 self.close(None)
1503
1504 class RotorNimSelection(Screen):
1505         skin = """
1506                 <screen position="center,center" size="400,130" title="Select slot">
1507                         <widget name="nimlist" position="20,10" size="360,100" />
1508                 </screen>"""
1509
1510         def __init__(self, session):
1511                 Screen.__init__(self, session)
1512                 self.setTitle(_("Select slot"))
1513                 nimlist = nimmanager.getNimListOfType("DVB-S")
1514                 nimMenuList = []
1515                 for x in nimlist:
1516                         if len(nimmanager.getRotorSatListForNim(x)) != 0:
1517                                 nimMenuList.append((nimmanager.nim_slots[x].friendly_full_description, x))
1518
1519                 self["nimlist"] = MenuList(nimMenuList)
1520
1521                 self["actions"] = ActionMap(["OkCancelActions"],
1522                 {
1523                         "ok": self.okbuttonClick ,
1524                         "cancel": self.close
1525                 }, -1)
1526
1527         def okbuttonClick(self):
1528                 selection = self["nimlist"].getCurrent()
1529                 self.session.open(PositionerSetup, selection[1])
1530
1531 def PositionerMain(session, **kwargs):
1532         nimList = nimmanager.getNimListOfType("DVB-S")
1533         if len(nimList) == 0:
1534                 session.open(MessageBox, _("No positioner capable frontend found."), MessageBox.TYPE_ERROR)
1535         else:
1536                 usableNims = []
1537                 for x in nimList:
1538                         configured_rotor_sats = nimmanager.getRotorSatListForNim(x)
1539                         if len(configured_rotor_sats) != 0:
1540                                 usableNims.append(x)
1541                 if len(usableNims) == 1:
1542                         session.open(PositionerSetup, usableNims[0])
1543                 elif len(usableNims) > 1:
1544                         session.open(RotorNimSelection)
1545                 else:
1546                         session.open(MessageBox, _("No tuner is configured for use with a diseqc positioner!"), MessageBox.TYPE_ERROR)
1547
1548 def PositionerSetupStart(menuid, **kwargs):
1549         if menuid == "scan":
1550                 return [(_("Positioner setup"), PositionerMain, "positioner_setup", None)]
1551         else:
1552                 return []
1553
1554 def Plugins(**kwargs):
1555         if (nimmanager.hasNimType("DVB-S")):
1556                 return PluginDescriptor(name=_("Positioner setup"), description = _("Setup your positioner"), where = PluginDescriptor.WHERE_MENU, needsRestart = False, fnc = PositionerSetupStart)
1557         else:
1558                 return []