| 1 |
# -*- coding: utf-8 -*- |
| 2 |
# See the file "LICENSE" for the full license governing this code. |
| 3 |
import logging |
| 4 |
logger = logging.getLogger(__name__) |
| 5 |
from random import choice |
| 6 |
from string import letters |
| 7 |
|
| 8 |
from PySide import QtCore |
| 9 |
|
| 10 |
from mxlib import MxRemote, MxClient |
| 11 |
from mx.mixer.models import MxMarionet |
| 12 |
|
| 13 |
|
| 14 |
class PVRState(QtCore.QState): |
| 15 |
"""Main state of the state machine. |
| 16 |
All the other states are children to this. |
| 17 |
""" |
| 18 |
|
| 19 |
state = None |
| 20 |
remote = None |
| 21 |
marionet = None |
| 22 |
|
| 23 |
# signal to change the screen url |
| 24 |
ready = QtCore.Signal('QString') |
| 25 |
# signals to update remote control UI |
| 26 |
entered = QtCore.Signal('QString') |
| 27 |
exited = QtCore.Signal('QString') |
| 28 |
|
| 29 |
def __init__(self, *args): |
| 30 |
QtCore.QState.__init__(self, *args) |
| 31 |
self.setObjectName('main') |
| 32 |
self.marionet = MxMarionet.getInstance() |
| 33 |
|
| 34 |
# slot to receive PVR IP address |
| 35 |
@QtCore.Slot('QString') |
| 36 |
def initRemote(self,addr): |
| 37 |
"""Executes a function and refreshes the view.""" |
| 38 |
logger.debug("init remote @ %s" % addr) |
| 39 |
self.remote = MxRemote(addr) |
| 40 |
logger.debug(self.remote) |
| 41 |
|
| 42 |
# slot for puppeteer to execute function |
| 43 |
@QtCore.Slot('QString') |
| 44 |
def execFunction(self,f): |
| 45 |
"""Executes the specified function on the remote device.""" |
| 46 |
logger.debug("button pressed for function %s" % f) |
| 47 |
self.marionet.runscript(getattr(self.remote,f)) |
| 48 |
self._refresh() |
| 49 |
|
| 50 |
def _refresh(self): |
| 51 |
"""Emits two signals: entered() and ready(). |
| 52 |
|
| 53 |
The first one is for the remote control UI and the second is for the screen UI, |
| 54 |
giving a parameter with a new random url pointing to imagedata. |
| 55 |
""" |
| 56 |
# url requires random string for webView to update |
| 57 |
randstr = ''.join(choice(letters) for i in xrange(8)) |
| 58 |
url = "http://localhost:8010/imagedata?"+randstr |
| 59 |
self.entered.emit(self.state) |
| 60 |
logger.debug("screen is ready to refresh") |
| 61 |
self.ready.emit(url) |
| 62 |
|
| 63 |
|
| 64 |
class MxOffState(QtCore.QState): |
| 65 |
|
| 66 |
# on entry, this signal is sent to wait until the PVR is powered |
| 67 |
pvrResponse = QtCore.Signal('QString') |
| 68 |
|
| 69 |
def __init__(self, *args): |
| 70 |
QtCore.QState.__init__(self, *args) |
| 71 |
self.setObjectName('off') |
| 72 |
|
| 73 |
def onEntry(self, *args, **kwargs): |
| 74 |
"""Waits for UDP broadcast from PVR.""" |
| 75 |
self.parent().state = self.objectName() |
| 76 |
logger.debug("Waiting for PVR...") |
| 77 |
print "Please wait, detecting PVR device Maximum 8000 in the network . . ." |
| 78 |
ip = MxClient.get_ip() |
| 79 |
if ip is not None: |
| 80 |
logger.debug("Maximum IP found: "+ip) |
| 81 |
else: |
| 82 |
logger.debug("Maximum IP was no detected * * * going to debug state with builtin mock PVR server") |
| 83 |
ip = 'localhost:8010/pvrmock' |
| 84 |
print "Not found. Using mockup PVR for demoing." |
| 85 |
self.pvrResponse.emit(ip) |
| 86 |
|
| 87 |
|
| 88 |
class MxUiState(QtCore.QState): |
| 89 |
"""Meta state for various UI states.""" |
| 90 |
name = None |
| 91 |
|
| 92 |
class Meta: |
| 93 |
abstract = True |
| 94 |
|
| 95 |
def __init__(self, parent=None): |
| 96 |
QtCore.QState.__init__(self, parent) |
| 97 |
self.setObjectName(self.name) |
| 98 |
|
| 99 |
def onEntry(self, *args, **kwargs): |
| 100 |
state = self.objectName() |
| 101 |
logger.debug("entered state: %s" % state) |
| 102 |
self.parent().state = state |
| 103 |
script = getattr(self.parent().remote,state) |
| 104 |
self.parent().marionet.runscript(script) |
| 105 |
self.parent()._refresh() |
| 106 |
|
| 107 |
def onExit(self,*args): |
| 108 |
logger.debug("exiting: %s" % self.objectName()) |
| 109 |
self.parent().exited.emit(self.objectName()) |
| 110 |
|
| 111 |
|
| 112 |
class MxOnState(MxUiState): |
| 113 |
name = "on" |
| 114 |
|
| 115 |
class MxEPGState(MxUiState): |
| 116 |
name = "epg" |
| 117 |
|
| 118 |
class MxPlayListState(MxUiState): |
| 119 |
name = "playlist" |
| 120 |
|
| 121 |
class MxPlayState(MxUiState): |
| 122 |
name = "play" |
| 123 |
|
| 124 |
class MxPauseState(MxUiState): |
| 125 |
name = "pause" |
| 126 |
|
| 127 |
class MxInfoState(MxUiState): |
| 128 |
name = "info" |
| 129 |
|
| 130 |
|
| 131 |
class MxPuppeteer(QtCore.QThread): |
| 132 |
"""Thread for the state machine. |
| 133 |
|
| 134 |
The state machine controls the resource that the screen will display. |
| 135 |
""" |
| 136 |
|
| 137 |
pvr = None |
| 138 |
state_machine = None |
| 139 |
|
| 140 |
# State machine catches these signals to enter specific states. |
| 141 |
# Some signals do not change the state, so they are passed onto self.pvr. |
| 142 |
# Finite amount of state<-signal->state transitions are defined at __init__. |
| 143 |
# |
| 144 |
# -> epg |
| 145 |
sig_epg = QtCore.Signal() |
| 146 |
# -> playlist |
| 147 |
sig_playlist = QtCore.Signal() |
| 148 |
# -> play |
| 149 |
sig_ok = QtCore.Signal() # from playlist |
| 150 |
sig_play = QtCore.Signal() # from paused state |
| 151 |
# -> pause |
| 152 |
sig_pause = QtCore.Signal() |
| 153 |
# -> info |
| 154 |
sig_info = QtCore.Signal() |
| 155 |
# -> playlist |
| 156 |
sig_stop = QtCore.Signal() |
| 157 |
# -> on |
| 158 |
sig_exit = QtCore.Signal() |
| 159 |
|
| 160 |
# slot for button press signal from UI |
| 161 |
@QtCore.Slot('QString') |
| 162 |
def buttonFunction(self,f): |
| 163 |
"""Executed at button press. |
| 164 |
|
| 165 |
Controls the flow of logic. |
| 166 |
""" |
| 167 |
logger.debug("button pressed for function %s in state %s" % (f,self.pvr.state)) |
| 168 |
|
| 169 |
# functions that do not change the state |
| 170 |
if f in ["ff","rew","next","prev","up","down","left","right","chup","chdn"]: |
| 171 |
self.pvr.execFunction(f) |
| 172 |
|
| 173 |
# unselect state |
| 174 |
elif f == self.pvr.state: |
| 175 |
self.sig_exit.emit() # exits the current state |
| 176 |
|
| 177 |
# select state |
| 178 |
else: |
| 179 |
getattr(self, 'sig_'+f).emit() |
| 180 |
|
| 181 |
|
| 182 |
def __init__(self,parent=None): |
| 183 |
"""Thread controlling QStateMachine.""" |
| 184 |
QtCore.QThread.__init__(self, parent) |
| 185 |
self.state_machine = QtCore.QStateMachine() |
| 186 |
|
| 187 |
# this state machine controls the PVR state |
| 188 |
pvr = PVRState(self.state_machine) |
| 189 |
self.pvr = pvr |
| 190 |
self.state_machine.setInitialState(pvr) |
| 191 |
|
| 192 |
off_state = MxOffState(pvr) |
| 193 |
pvr.setInitialState(off_state) |
| 194 |
|
| 195 |
# on_state when the device is powered but doing nothing |
| 196 |
on_state = MxOnState(pvr) |
| 197 |
# epg_state and list_state are main states |
| 198 |
epg_state = MxEPGState(pvr) |
| 199 |
list_state = MxPlayListState(pvr) |
| 200 |
# play_state |
| 201 |
play_state = MxPlayState(pvr) |
| 202 |
# pause state |
| 203 |
pause_state = MxPauseState(pvr) |
| 204 |
info_state = MxInfoState(pvr) |
| 205 |
|
| 206 |
### state transitions ####### ####### ####### ####### ####### |
| 207 |
# |
| 208 |
# off -> on |
| 209 |
off_state.addTransition( |
| 210 |
off_state, QtCore.SIGNAL('pvrResponse(QString)'), on_state) |
| 211 |
# on -> info |
| 212 |
on_state.addTransition( |
| 213 |
self, QtCore.SIGNAL('sig_info()'), info_state) |
| 214 |
# on -> epg |
| 215 |
on_state.addTransition( |
| 216 |
self, QtCore.SIGNAL('sig_epg()'), epg_state) |
| 217 |
# on -> playlist |
| 218 |
on_state.addTransition( |
| 219 |
self, QtCore.SIGNAL('sig_playlist()'), list_state) |
| 220 |
# epg -> playlist |
| 221 |
epg_state.addTransition( |
| 222 |
self, QtCore.SIGNAL('sig_playlist()'), list_state) |
| 223 |
# playlist -> epg |
| 224 |
list_state.addTransition( |
| 225 |
self, QtCore.SIGNAL('sig_epg()'), epg_state) |
| 226 |
# epg -> on |
| 227 |
epg_state.addTransition( |
| 228 |
self, QtCore.SIGNAL('sig_exit()'), on_state) |
| 229 |
# playlist -> on |
| 230 |
list_state.addTransition( |
| 231 |
self, QtCore.SIGNAL('sig_exit()'), on_state) |
| 232 |
# info -> on |
| 233 |
info_state.addTransition( |
| 234 |
self, QtCore.SIGNAL('sig_exit()'), on_state) |
| 235 |
# info -> epg |
| 236 |
info_state.addTransition( |
| 237 |
self, QtCore.SIGNAL('sig_epg()'), epg_state) |
| 238 |
# info -> playlist |
| 239 |
info_state.addTransition( |
| 240 |
self, QtCore.SIGNAL('sig_playlist()'), list_state) |
| 241 |
# playlist -> play |
| 242 |
list_state.addTransition( |
| 243 |
self, QtCore.SIGNAL('sig_ok()'), play_state) |
| 244 |
# play -> playlist |
| 245 |
play_state.addTransition( |
| 246 |
self, QtCore.SIGNAL('sig_stop()'), list_state) |
| 247 |
# play -> pause |
| 248 |
play_state.addTransition( |
| 249 |
self, QtCore.SIGNAL('sig_pause()'), pause_state) |
| 250 |
# pause -> play |
| 251 |
pause_state.addTransition( |
| 252 |
self, QtCore.SIGNAL('sig_play()'), play_state) |
| 253 |
# pause -> stop |
| 254 |
pause_state.addTransition( |
| 255 |
self, QtCore.SIGNAL('sig_stop()'), list_state) |
| 256 |
|
| 257 |
# connect signal to wait for the device being powered |
| 258 |
QtCore.QObject.connect( |
| 259 |
off_state, QtCore.SIGNAL('pvrResponse(QString)'), |
| 260 |
pvr, QtCore.SLOT('initRemote(QString)')) |
| 261 |
|
| 262 |
def run(self): |
| 263 |
logger.debug("Running MxPuppeteer thread") |
| 264 |
self.state_machine.start() |