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