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()