blob: f98e2d2dfc39111c1ef78da2aa9267ec4e901dc4 [file] [log] [blame]
Todd Broche505b8d2011-03-21 18:19:54 -07001# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Servo Server."""
5import imp
6import logging
7import SimpleXMLRPCServer
8
9# TODO(tbroch) deprecate use of relative imports
Todd Broche505b8d2011-03-21 18:19:54 -070010import ftdigpio
11import ftdii2c
Todd Brochdbb09982011-10-02 07:14:26 -070012import ftdi_common
Todd Broch47c43f42011-05-26 15:11:31 -070013import ftdiuart
Todd Broche505b8d2011-03-21 18:19:54 -070014
15MAX_I2C_CLOCK_HZ = 100000
16
Todd Brochdbb09982011-10-02 07:14:26 -070017
Todd Broche505b8d2011-03-21 18:19:54 -070018class ServodError(Exception):
19 """Exception class for servod."""
20
21class Servod(object):
22 """Main class for Servo debug/controller Daemon."""
Todd Brochdbb09982011-10-02 07:14:26 -070023 def __init__(self, config, vendor, product, serialname=None, interfaces=None):
Todd Broche505b8d2011-03-21 18:19:54 -070024 """Servod constructor.
25
26 Args:
27 config: instance of SystemConfig containing all controls for
28 particular Servod invocation
29 vendor: usb vendor id of FTDI device
30 product: usb product id of FTDI device
Todd Brochad034442011-05-25 15:05:29 -070031 serialname: string of device serialname/number as defined in FTDI eeprom.
Todd Brochdbb09982011-10-02 07:14:26 -070032 interfaces: list of strings of interface types the server will instantiate
33
34 Raises:
35 ServodError: if unable to locate init method for particular interface
Todd Broche505b8d2011-03-21 18:19:54 -070036 """
37 self._logger = logging.getLogger("Servod")
38 self._logger.debug("")
39 self._vendor = vendor
40 self._product = product
Todd Brochad034442011-05-25 15:05:29 -070041 self._serialname = serialname
Todd Broche505b8d2011-03-21 18:19:54 -070042 self._syscfg = config
43 # list of objects (Fi2c, Fgpio) to physical interfaces (gpio, i2c) that ftdi
44 # interfaces are mapped to
45 self._interface_list = []
46 # Dict of Dict to map control name, function name to to tuple (params, drv)
47 # Ex) _drv_dict[name]['get'] = (params, drv)
48 self._drv_dict = {}
49
Todd Brochdbb09982011-10-02 07:14:26 -070050 # Note, interface i is (i - 1) in list
51 if not interfaces:
52 interfaces = ftdi_common.INTERFACE_DEFAULTS[vendor][product]
53
54 for i, name in enumerate(interfaces):
55 self._logger.info("Initializing FTDI interface %d to %s", i + 1, name)
56 try:
57 func = getattr(self, '_init_%s' % name)
58 except AttributeError:
59 raise ServodError("Unable to locate init for interface %s" % name)
Todd Broch888da782011-10-07 14:29:09 -070060 result = func(i + 1)
61 if isinstance(result, tuple):
62 self._interface_list.extend(result)
63 else:
64 self._interface_list.append(result)
Todd Broche505b8d2011-03-21 18:19:54 -070065
66 def _init_gpio(self, interface):
67 """Initialize gpio driver interface and open for use.
68
69 Args:
70 interface: interface number of FTDI device to use.
71
72 Returns:
73 Instance object of interface.
74 """
Todd Brochad034442011-05-25 15:05:29 -070075 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
76 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070077 fobj.open()
78 return fobj
79
80 def _init_i2c(self, interface):
81 """Initialize i2c interface and open for use.
82
83 Args:
84 interface: interface number of FTDI device to use
85
86 Returns:
87 Instance object of interface
88 """
Todd Brochad034442011-05-25 15:05:29 -070089 fobj = ftdii2c.Fi2c(self._vendor, self._product, interface,
90 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070091 fobj.open()
92 # Set the frequency of operation of the i2c bus.
93 # TODO(tbroch) make configureable
94 fobj.setclock(MAX_I2C_CLOCK_HZ)
95 return fobj
96
Todd Broch47c43f42011-05-26 15:11:31 -070097 def _init_uart(self, interface):
98 """Initialize uart inteface and open for use
99
100 Note, the uart runs in a separate thread (pthreads). Users wishing to
101 interact with it will query control for the pty's pathname and connect
102 with there favorite console program. For example:
103 cu -l /dev/pts/22
104
105 Args:
106 interface: interface number of FTDI device to use
107
108 Returns:
109 Instance object of interface
110 """
111 fobj = ftdiuart.Fuart(self._vendor, self._product, interface)
112 fobj.run()
113 self._logger.info("%s" % fobj.get_pty())
114 return fobj
115
Todd Broch888da782011-10-07 14:29:09 -0700116 def _init_gpiouart(self, interface):
117 """Initialize special gpio + uart interface and open for use
118
119 Note, the uart runs in a separate thread (pthreads). Users wishing to
120 interact with it will query control for the pty's pathname and connect
121 with there favorite console program. For example:
122 cu -l /dev/pts/22
123
124 Args:
125 interface: interface number of FTDI device to use
126
127 Returns:
128 Instance objects of interface
129 """
130 fgpio = self._init_gpio(interface)
131 fuart = ftdiuart.Fuart(self._vendor, self._product, interface, fgpio._fc)
132 fuart.run()
133 self._logger.info("uart pty: %s" % fuart.get_pty())
134 return fgpio, fuart
135
Todd Broche505b8d2011-03-21 18:19:54 -0700136 def _get_param_drv(self, control_name, is_get=True):
137 """Get access to driver for a given control.
138
139 Note, some controls have different parameter dictionaries for 'getting' the
140 control's value versus 'setting' it. Boolean is_get distinguishes which is
141 being requested.
142
143 Args:
144 control_name: string name of control
145 is_get: boolean to determine
146
147 Returns:
148 tuple (param, drv) where:
149 param: param dictionary for control
150 drv: instance object of driver for particular control
151
152 Raises:
153 ServodError: Error occurred while examining params dict
154 """
155 self._logger.debug("")
156 # if already setup just return tuple from driver dict
157 if control_name in self._drv_dict:
158 if is_get and ('get' in self._drv_dict[control_name]):
159 return self._drv_dict[control_name]['get']
160 if not is_get and ('set' in self._drv_dict[control_name]):
161 return self._drv_dict[control_name]['set']
162
163 params = self._syscfg.lookup_control_params(control_name, is_get)
164 if 'drv' not in params:
165 self._logger.error("Unable to determine driver for %s" % control_name)
166 raise ServodError("'drv' key not found in params dict")
167 if 'interface' not in params:
168 self._logger.error("Unable to determine interface for %s" %
169 control_name)
170
171 raise ServodError("'interface' key not found in params dict")
172 index = int(params['interface']) - 1
173 interface = self._interface_list[index]
174 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
175 drv_pkg = imp.load_module('drv',
176 *imp.find_module('drv', servo_pkg.__path__))
177 drv_name = params['drv']
178 drv_module = getattr(drv_pkg, drv_name)
179 drv_class = getattr(drv_module, drv_name)
180 drv = drv_class(interface, params)
181 if control_name not in self._drv_dict:
182 self._drv_dict[control_name] = {}
183 if is_get:
184 self._drv_dict[control_name]['get'] = (params, drv)
185 else:
186 self._drv_dict[control_name]['set'] = (params, drv)
187 return (params, drv)
188
189 def doc_all(self):
190 """Return all documenation for controls.
191
192 Returns:
193 string of <doc> text in config file (xml) and the params dictionary for
194 all controls.
195
196 For example:
197 warm_reset :: Reset the device warmly
198 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
199 """
200 return self._syscfg.display_config()
201
202 def doc(self, name):
203 """Retreive doc string in system config file for given control name.
204
205 Args:
206 name: name string of control to get doc string
207
208 Returns:
209 doc string of name
210
211 Raises:
212 NameError: if fails to locate control
213 """
214 self._logger.debug("name(%s)" % (name))
215 if self._syscfg.is_control(name):
216 return self._syscfg.get_control_docstring(name)
217 else:
218 raise NameError("No control %s" %name)
219
220 def get(self, name):
221 """Get control value.
222
223 Args:
224 name: name string of control
225
226 Returns:
227 Response from calling drv get method. Value is reformatted based on
228 control's dictionary parameters
229
230 Raises:
231 HwDriverError: Error occurred while using drv
232 """
233 self._logger.debug("name(%s)" % (name))
234 (param, drv) = self._get_param_drv(name)
235 try:
236 val = drv.get()
237 rd_val = self._syscfg.reformat_val(param, val)
238 self._logger.info("%s = %s" % (name, rd_val))
239 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700240 except AttributeError, error:
241 self._logger.error("Getting %s: %s" % (name, error))
242 raise
Todd Broche505b8d2011-03-21 18:19:54 -0700243 except drv.hw_driver.HwDriverError:
244 self._logger.error("Getting %s" % (name))
245 raise
246 def get_all(self, verbose):
247 """Get all controls values.
248
249 Args:
250 verbose: Boolean on whether to return doc info as well
251
252 Returns:
253 string creating from trying to get all values of all controls. In case of
254 error attempting access to control, response is 'ERR'.
255 """
256 rsp = ""
257 for name in self._syscfg.syscfg_dict['control']:
258 self._logger.debug("name = %s" %name)
259 try:
260 value = self.get(name)
261 except Exception:
262 value = "ERR"
263 pass
264 if verbose:
265 rsp += "GET %s = %s :: %s\n" % (name, value, self.doc(name))
266 else:
267 rsp += "%s:%s\n" % (name, value)
268 return rsp
269
270 def set(self, name, wr_val_str):
271 """Set control.
272
273 Args:
274 name: name string of control
275 wr_val_str: value string to write. Can be integer, float or a
276 alpha-numerical that is mapped to a integer or float.
277
278 Raises:
279 HwDriverError: Error occurred while using driver
280 """
281 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
282 (params, drv) = self._get_param_drv(name, False)
283 wr_val = self._syscfg.resolve_val(params, wr_val_str)
284 try:
285 drv.set(wr_val)
286 except drv.hw_driver.HwDriverError:
287 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
288 raise
289 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
290 # & client I still have to return something to appease the
291 # marshall/unmarshall
292 return True
293
294 def echo(self, echo):
295 """Dummy echo function for testing/examples.
296
297 Args:
298 echo: string to echo back to client
299 """
300 self._logger.debug("echo(%s)" % (echo))
301 return "ECH0ING: %s" % (echo)
302
Todd Brochdbb09982011-10-02 07:14:26 -0700303
Todd Broche505b8d2011-03-21 18:19:54 -0700304def test():
305 """Integration testing.
306
307 TODO(tbroch) Enhance integration test and add unittest (see mox)
308 """
309 logging.basicConfig(level=logging.DEBUG,
310 format="%(asctime)s - %(name)s - " +
311 "%(levelname)s - %(message)s")
312 # configure server & listen
313 servod_obj = Servod(1)
314 # 4 == number of interfaces on a FT4232H device
315 for i in xrange(4):
316 if i == 1:
317 # its an i2c interface ... see __init__ for details and TODO to make
318 # this configureable
319 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
320 else:
321 # its a gpio interface
322 servod_obj._interface_list[i].wr_rd(0)
323
324 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
325 allow_none=True)
326 server.register_introspection_functions()
327 server.register_multicall_functions()
328 server.register_instance(servod_obj)
329 logging.info("Listening on localhost port 9999")
330 server.serve_forever()
331
332if __name__ == "__main__":
333 test()
334
335 # simple client transaction would look like
336 """
337 remote_uri = 'http://localhost:9999'
338 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
339 send_str = "Hello_there"
340 print "Sent " + send_str + ", Recv " + client.echo(send_str)
341 """