@@ -70,37 +70,34 @@ def _set_debugging(self, level):
7070
7171
7272class PebbleCommand (BaseCommand ):
73- valid_connections = {'phone' , 'qemu' , 'cloudpebble' , 'emulator' , 'serial' }
73+ connection_handlers = set ()
74+
75+ @classmethod
76+ def register_connection_handler (cls , impl ):
77+ cls .connection_handlers .add (impl )
7478
7579 @classmethod
7680 def _shared_parser (cls ):
7781 parser = argparse .ArgumentParser (add_help = False )
78- if len (cls .valid_connections ) < 2 :
79- group = parser
80- else :
82+ handlers = cls .valid_connection_handlers ()
83+ # Having a group inside a mutually exclusive group breaks --help unless there are is
84+ # something for it to be mutually exlusive with.
85+ if len (handlers ) > 1 :
8186 group = parser .add_mutually_exclusive_group ()
82- if 'phone' in cls .valid_connections :
83- group .add_argument ('--phone' , metavar = 'phone_ip' ,
84- help = "When using the developer connection, your phone's IP or hostname. "
85- "Equivalent to PEBBLE_PHONE." )
86- if 'qemu' in cls .valid_connections :
87- group .add_argument ('--qemu' , nargs = '?' , const = 'localhost:12344' , metavar = 'host' ,
88- help = "Use this option to connect directly to a QEMU instance. "
89- "Equivalent to PEBBLE_QEMU." )
90- if 'cloudpebble' in cls .valid_connections :
91- group .add_argument ('--cloudpebble' , action = 'store_true' , help = "Use this option to connect to your phone via"
92- " the CloudPebble connection. Equivalent to "
93- "PEBBLE_CLOUDPEBBLE." )
94- if 'emulator' in cls .valid_connections :
95- emu_group = group .add_argument_group ()
96- emu_group .add_argument ('--emulator' , type = str , help = "Launch an emulator. Equivalent to PEBBLE_EMULATOR." ,
97- choices = pebble_platforms )
98- emu_group .add_argument ('--sdk' , type = str , help = "SDK version to launch. Defaults to the active SDK"
99- " (currently {})" .format (sdk_version ()))
100- if 'serial' in cls .valid_connections :
101- group .add_argument ('--serial' , type = str , help = "Connected directly, given a path to a serial device." )
87+ else :
88+ group = parser
89+ for handler_impl in handlers :
90+ handler_impl .add_argument_handler (group )
10291 return super (PebbleCommand , cls )._shared_parser () + [parser ]
10392
93+ @classmethod
94+ def valid_connection_handlers (cls ):
95+ valid_connections = getattr (cls , 'valid_connections' , None )
96+ if not valid_connections :
97+ return cls .connection_handlers
98+
99+ return set ([handler for handler in cls .connection_handlers if handler .name in valid_connections ])
100+
104101 def __call__ (self , args ):
105102 super (PebbleCommand , self ).__call__ (args )
106103 try :
@@ -110,107 +107,199 @@ def __call__(self, args):
110107
111108 def _connect (self , args ):
112109 self ._set_debugging (args .v )
113- if getattr (args , 'phone' , None ):
114- return self ._connect_phone (args .phone )
115- elif getattr (args , 'qemu' , None ):
116- return self ._connect_qemu (args .qemu )
117- elif getattr (args , 'emulator' , None ):
118- return self ._connect_emulator (args .emulator , args .sdk )
119- elif getattr (args , 'cloudpebble' , None ):
120- return self ._connect_cloudpebble ()
121- elif getattr (args , 'serial' , None ):
122- return self ._connect_serial (args .serial )
110+ for handler_impl in self .valid_connection_handlers ():
111+ if handler_impl .is_selected (args ):
112+ break
123113 else :
124- if 'phone' in self .valid_connections and 'PEBBLE_PHONE' in os .environ :
125- return self ._connect_phone (os .environ ['PEBBLE_PHONE' ])
126- elif 'qemu' in self .valid_connections and 'PEBBLE_QEMU' in os .environ :
127- return self ._connect_qemu (os .environ ['PEBBLE_QEMU' ])
128- elif 'cloudpebble' in self .valid_connections and os .environ .get ('PEBBLE_CLOUDPEBBLE' , False ):
129- return self ._connect_cloudpebble ()
130- elif 'serial' in self .valid_connections and 'PEBBLE_BT_SERIAL' in os .environ :
131- return self ._connect_serial (os .environ ['PEBBLE_BT_SERIAL' ])
132- elif 'emulator' in self .valid_connections :
133- running = []
134- emulator_platform = None
135- emulator_sdk = None
136- if 'PEBBLE_EMULATOR' in os .environ :
137- emulator_platform = os .environ ['PEBBLE_EMULATOR' ]
138- if emulator_platform not in pebble_platforms :
139- raise ToolError ("PEBBLE_EMULATOR is set to '{}', which is not a valid platform "
140- "(pick from {})" .format (emulator_platform , ', ' .join (pebble_platforms )))
141- emulator_sdk = os .environ .get ('PEBBLE_EMULATOR_VERSION' , sdk_version ())
142- else :
143- for platform , sdks in get_all_emulator_info ().items ():
144- for sdk in sdks :
145- if ManagedEmulatorTransport .is_emulator_alive (platform , sdk ):
146- running .append ((platform , sdk ))
147- if len (running ) == 1 :
148- emulator_platform , emulator_sdk = running [0 ]
149- elif len (running ) > 1 :
150- raise ToolError ("Multiple emulators are running; you must specify which to use." )
151- if emulator_platform is not None :
152- return self ._connect_emulator (emulator_platform , emulator_sdk )
153- raise ToolError ("No pebble connection specified." )
154-
155- def _connect_phone (self , phone ):
114+ # No selected transport, fallback to a running emulator if available
115+ if PebbleTransportEmulator .get_running_emulators ():
116+ handler_impl = PebbleTransportEmulator
117+ else :
118+ raise ToolError ("No pebble connection specified." )
119+
120+ transport = handler_impl .get_transport (args )
121+ connection = PebbleConnection (transport , ** self ._get_debug_args ())
122+ connection .connect ()
123+ connection .run_async ()
124+ handler_impl .post_connect (connection )
125+ return connection
126+
127+ def _get_debug_args (self ):
128+ args = {}
129+ if self ._verbosity >= 3 :
130+ args ['log_packet_level' ] = logging .DEBUG
131+ if self ._verbosity >= 4 :
132+ args ['log_protocol_level' ] = logging .DEBUG
133+ return args
134+
135+
136+ class SelfRegisteringTransportConfiguration (type ):
137+ def __init__ (cls , name , bases , dct ):
138+ if hasattr (cls , 'name' ) and cls .name is not None :
139+ PebbleCommand .register_connection_handler (cls )
140+ super (SelfRegisteringTransportConfiguration , cls ).__init__ (name , bases , dct )
141+
142+
143+ class PebbleTransportConfiguration (with_metaclass (SelfRegisteringTransportConfiguration )):
144+ transport_class = None
145+ env_var = None
146+ name = None
147+
148+ @classmethod
149+ def _config_env_var (cls ):
150+ env_var_name = cls .env_var if cls .env_var else 'PEBBLE_%s' % cls .name .upper ()
151+ return os .environ .get (env_var_name )
152+
153+ @classmethod
154+ def is_selected (cls , args ):
155+ return getattr (args , cls .name , None ) or cls ._config_env_var ()
156+
157+ @classmethod
158+ def _connect_args (cls , args ):
159+ arg_val = getattr (args , cls .name , None )
160+ if arg_val :
161+ return (arg_val ,)
162+
163+ env_val = cls ._config_env_var ()
164+ if env_val :
165+ return (env_val ,)
166+
167+ @classmethod
168+ def get_transport (cls , args ):
169+ return cls .transport_class (* cls ._connect_args (args ))
170+
171+ @classmethod
172+ def add_argument_handler (cls ):
173+ raise NotImplementedError
174+
175+ @classmethod
176+ def post_connect (cls , connection ):
177+ pass
178+
179+
180+ class PebbleTransportSerial (PebbleTransportConfiguration ):
181+ transport_class = SerialTransport
182+ env_var = 'PEBBLE_BT_SERIAL'
183+ name = 'serial'
184+
185+ @classmethod
186+ def add_argument_handler (cls , parser ):
187+ parser .add_argument ('--serial' , type = str , help = "Connected directly, given a path to a serial device." )
188+
189+
190+ class PebbleTransportPhone (PebbleTransportConfiguration ):
191+ transport_class = WebsocketTransport
192+ name = 'phone'
193+
194+ @classmethod
195+ def _connect_args (cls , args ):
196+ phone , = super (PebbleTransportPhone , cls )._connect_args (args )
156197 parts = phone .split (':' )
157198 ip = parts [0 ]
158199 if len (parts ) == 2 :
159200 port = int (parts [1 ])
160201 else :
161202 port = 9000
162- connection = PebbleConnection (WebsocketTransport ("ws://{}:{}/" .format (ip , port )), ** self ._get_debug_args ())
163- connection .connect ()
164- connection .run_async ()
165- return connection
166203
167- def _connect_qemu (self , qemu ):
168- parts = qemu .split (':' )
204+ return ("ws://{}:{}/" .format (ip , port ),)
205+
206+ @classmethod
207+ def add_argument_handler (cls , parser ):
208+ parser .add_argument ('--phone' , metavar = 'phone_ip' ,
209+ help = "When using the developer connection, your phone's IP or hostname. "
210+ "Equivalent to PEBBLE_PHONE." )
211+
212+
213+ class PebbleTransportQemu (PebbleTransportConfiguration ):
214+ transport_class = QemuTransport
215+ name = 'qemu'
216+
217+ @classmethod
218+ def _connect_args (cls , args ):
219+ phone , = super (PebbleTransportQemu , cls )._connect_args (args )
220+ parts = phone .split (':' )
169221 ip = parts [0 ]
170- if not ip :
171- ip = '127.0.0.1'
172222 if len (parts ) == 2 :
173223 port = int (parts [1 ])
174224 else :
175225 port = 12344
176- connection = PebbleConnection (QemuTransport (ip , port ), ** self ._get_debug_args ())
177- connection .connect ()
178- connection .run_async ()
179- return connection
180226
181- def _connect_emulator (self , platform , sdk ):
182- connection = PebbleConnection (ManagedEmulatorTransport (platform , sdk ), ** self ._get_debug_args ())
183- connection .connect ()
184- connection .run_async ()
227+ return (ip , port ,)
228+
229+ @classmethod
230+ def add_argument_handler (cls , parser ):
231+ parser .add_argument ('--qemu' , nargs = '?' , const = 'localhost:12344' , metavar = 'host' ,
232+ help = "Use this option to connect directly to a QEMU instance. "
233+ "Equivalent to PEBBLE_QEMU." )
234+
235+
236+ class PebbleTransportCloudPebble (PebbleTransportConfiguration ):
237+ transport_class = CloudPebbleTransport
238+ name = 'cloudpebble'
239+
240+ @classmethod
241+ def _connect_args (cls , args ):
242+ return ()
243+
244+ @classmethod
245+ def add_argument_handler (cls , parser ):
246+ parser .add_argument ('--cloudpebble' , action = 'store_true' ,
247+ help = "Use this option to connect to your phone via"
248+ " the CloudPebble connection. Equivalent to "
249+ "PEBBLE_CLOUDPEBBLE." )
250+
251+
252+ class PebbleTransportEmulator (PebbleTransportConfiguration ):
253+ transport_class = ManagedEmulatorTransport
254+ name = 'emulator'
255+
256+ @classmethod
257+ def get_running_emulators (cls ):
258+ running = []
259+ for platform , sdks in get_all_emulator_info ().items ():
260+ for sdk in sdks :
261+ if ManagedEmulatorTransport .is_emulator_alive (platform , sdk ):
262+ running .append ((platform , sdk ))
263+ return running
264+
265+ @classmethod
266+ def _connect_args (cls , args ):
267+ emulator_platform = getattr (args , 'emulator' , None )
268+ emulator_sdk = getattr (args , 'sdk' , None )
269+ if emulator_platform :
270+ return emulator_platform , emulator_sdk
271+ elif 'PEBBLE_EMULATOR' in os .environ :
272+ emulator_platform = os .environ ['PEBBLE_EMULATOR' ]
273+ if emulator_platform not in pebble_platforms :
274+ raise ToolError ("PEBBLE_EMULATOR is set to '{}', which is not a valid platform "
275+ "(pick from {})" .format (emulator_platform , ', ' .join (pebble_platforms )))
276+ emulator_sdk = os .environ .get ('PEBBLE_EMULATOR_VERSION' , sdk_version ())
277+ else :
278+ running = cls .get_running_emulators ()
279+ if len (running ) == 1 :
280+ emulator_platform , emulator_sdk = running [0 ]
281+ elif len (running ) > 1 :
282+ raise ToolError ("Multiple emulators are running; you must specify which to use." )
283+
284+ return (emulator_platform , emulator_sdk )
285+
286+ @classmethod
287+ def post_connect (cls , connection ):
185288 # Make sure the timezone is set usefully.
186289 if connection .firmware_version .major >= 3 :
187290 ts = time .time ()
188291 tz_offset = - time .altzone if time .localtime (ts ).tm_isdst and time .daylight else - time .timezone
189292 tz_offset_minutes = tz_offset // 60
190293 tz_name = "UTC%+d" % (tz_offset_minutes / 60 )
191294 connection .send_packet (TimeMessage (message = SetUTC (unix_time = ts , utc_offset = tz_offset_minutes , tz_name = tz_name )))
192- return connection
193-
194- def _connect_cloudpebble (self ):
195- connection = PebbleConnection (CloudPebbleTransport (), ** self ._get_debug_args ())
196- connection .connect ()
197- connection .run_async ()
198- return connection
199-
200- def _connect_serial (self , device ):
201- connection = PebbleConnection (SerialTransport (device ), ** self ._get_debug_args ())
202- connection .connect ()
203- connection .run_async ()
204- return connection
205-
206- def _get_debug_args (self ):
207- args = {}
208- if self ._verbosity >= 3 :
209- args ['log_packet_level' ] = logging .DEBUG
210- if self ._verbosity >= 4 :
211- args ['log_protocol_level' ] = logging .DEBUG
212- return args
213295
296+ @classmethod
297+ def add_argument_handler (cls , parser ):
298+ emu_group = parser .add_argument_group ()
299+ emu_group .add_argument ('--emulator' , type = str , help = "Launch an emulator. Equivalent to PEBBLE_EMULATOR." ,
300+ choices = pebble_platforms )
301+ emu_group .add_argument ('--sdk' , type = str , help = "SDK version to launch. Defaults to the active SDK"
302+ " (currently {})" .format (sdk_version ()))
214303
215304def register_children (parser ):
216305 subparsers = parser .add_subparsers (title = "command" )
0 commit comments