blob: 1c493aaf1905ecd2a499c4692deb8b8c94b86ced [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)
60 self._interface_list.append(func(i + 1))
Todd Broche505b8d2011-03-21 18:19:54 -070061
62 def _init_gpio(self, interface):
63 """Initialize gpio driver interface and open for use.
64
65 Args:
66 interface: interface number of FTDI device to use.
67
68 Returns:
69 Instance object of interface.
70 """
Todd Brochad034442011-05-25 15:05:29 -070071 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
72 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070073 fobj.open()
74 return fobj
75
76 def _init_i2c(self, interface):
77 """Initialize i2c interface and open for use.
78
79 Args:
80 interface: interface number of FTDI device to use
81
82 Returns:
83 Instance object of interface
84 """
Todd Brochad034442011-05-25 15:05:29 -070085 fobj = ftdii2c.Fi2c(self._vendor, self._product, interface,
86 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070087 fobj.open()
88 # Set the frequency of operation of the i2c bus.
89 # TODO(tbroch) make configureable
90 fobj.setclock(MAX_I2C_CLOCK_HZ)
91 return fobj
92
Todd Broch47c43f42011-05-26 15:11:31 -070093 def _init_uart(self, interface):
94 """Initialize uart inteface and open for use
95
96 Note, the uart runs in a separate thread (pthreads). Users wishing to
97 interact with it will query control for the pty's pathname and connect
98 with there favorite console program. For example:
99 cu -l /dev/pts/22
100
101 Args:
102 interface: interface number of FTDI device to use
103
104 Returns:
105 Instance object of interface
106 """
107 fobj = ftdiuart.Fuart(self._vendor, self._product, interface)
108 fobj.run()
109 self._logger.info("%s" % fobj.get_pty())
110 return fobj
111
Todd Broche505b8d2011-03-21 18:19:54 -0700112 def _get_param_drv(self, control_name, is_get=True):
113 """Get access to driver for a given control.
114
115 Note, some controls have different parameter dictionaries for 'getting' the
116 control's value versus 'setting' it. Boolean is_get distinguishes which is
117 being requested.
118
119 Args:
120 control_name: string name of control
121 is_get: boolean to determine
122
123 Returns:
124 tuple (param, drv) where:
125 param: param dictionary for control
126 drv: instance object of driver for particular control
127
128 Raises:
129 ServodError: Error occurred while examining params dict
130 """
131 self._logger.debug("")
132 # if already setup just return tuple from driver dict
133 if control_name in self._drv_dict:
134 if is_get and ('get' in self._drv_dict[control_name]):
135 return self._drv_dict[control_name]['get']
136 if not is_get and ('set' in self._drv_dict[control_name]):
137 return self._drv_dict[control_name]['set']
138
139 params = self._syscfg.lookup_control_params(control_name, is_get)
140 if 'drv' not in params:
141 self._logger.error("Unable to determine driver for %s" % control_name)
142 raise ServodError("'drv' key not found in params dict")
143 if 'interface' not in params:
144 self._logger.error("Unable to determine interface for %s" %
145 control_name)
146
147 raise ServodError("'interface' key not found in params dict")
148 index = int(params['interface']) - 1
149 interface = self._interface_list[index]
150 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
151 drv_pkg = imp.load_module('drv',
152 *imp.find_module('drv', servo_pkg.__path__))
153 drv_name = params['drv']
154 drv_module = getattr(drv_pkg, drv_name)
155 drv_class = getattr(drv_module, drv_name)
156 drv = drv_class(interface, params)
157 if control_name not in self._drv_dict:
158 self._drv_dict[control_name] = {}
159 if is_get:
160 self._drv_dict[control_name]['get'] = (params, drv)
161 else:
162 self._drv_dict[control_name]['set'] = (params, drv)
163 return (params, drv)
164
165 def doc_all(self):
166 """Return all documenation for controls.
167
168 Returns:
169 string of <doc> text in config file (xml) and the params dictionary for
170 all controls.
171
172 For example:
173 warm_reset :: Reset the device warmly
174 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
175 """
176 return self._syscfg.display_config()
177
178 def doc(self, name):
179 """Retreive doc string in system config file for given control name.
180
181 Args:
182 name: name string of control to get doc string
183
184 Returns:
185 doc string of name
186
187 Raises:
188 NameError: if fails to locate control
189 """
190 self._logger.debug("name(%s)" % (name))
191 if self._syscfg.is_control(name):
192 return self._syscfg.get_control_docstring(name)
193 else:
194 raise NameError("No control %s" %name)
195
196 def get(self, name):
197 """Get control value.
198
199 Args:
200 name: name string of control
201
202 Returns:
203 Response from calling drv get method. Value is reformatted based on
204 control's dictionary parameters
205
206 Raises:
207 HwDriverError: Error occurred while using drv
208 """
209 self._logger.debug("name(%s)" % (name))
210 (param, drv) = self._get_param_drv(name)
211 try:
212 val = drv.get()
213 rd_val = self._syscfg.reformat_val(param, val)
214 self._logger.info("%s = %s" % (name, rd_val))
215 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700216 except AttributeError, error:
217 self._logger.error("Getting %s: %s" % (name, error))
218 raise
Todd Broche505b8d2011-03-21 18:19:54 -0700219 except drv.hw_driver.HwDriverError:
220 self._logger.error("Getting %s" % (name))
221 raise
222 def get_all(self, verbose):
223 """Get all controls values.
224
225 Args:
226 verbose: Boolean on whether to return doc info as well
227
228 Returns:
229 string creating from trying to get all values of all controls. In case of
230 error attempting access to control, response is 'ERR'.
231 """
232 rsp = ""
233 for name in self._syscfg.syscfg_dict['control']:
234 self._logger.debug("name = %s" %name)
235 try:
236 value = self.get(name)
237 except Exception:
238 value = "ERR"
239 pass
240 if verbose:
241 rsp += "GET %s = %s :: %s\n" % (name, value, self.doc(name))
242 else:
243 rsp += "%s:%s\n" % (name, value)
244 return rsp
245
246 def set(self, name, wr_val_str):
247 """Set control.
248
249 Args:
250 name: name string of control
251 wr_val_str: value string to write. Can be integer, float or a
252 alpha-numerical that is mapped to a integer or float.
253
254 Raises:
255 HwDriverError: Error occurred while using driver
256 """
257 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
258 (params, drv) = self._get_param_drv(name, False)
259 wr_val = self._syscfg.resolve_val(params, wr_val_str)
260 try:
261 drv.set(wr_val)
262 except drv.hw_driver.HwDriverError:
263 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
264 raise
265 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
266 # & client I still have to return something to appease the
267 # marshall/unmarshall
268 return True
269
270 def echo(self, echo):
271 """Dummy echo function for testing/examples.
272
273 Args:
274 echo: string to echo back to client
275 """
276 self._logger.debug("echo(%s)" % (echo))
277 return "ECH0ING: %s" % (echo)
278
Todd Brochdbb09982011-10-02 07:14:26 -0700279
Todd Broche505b8d2011-03-21 18:19:54 -0700280def test():
281 """Integration testing.
282
283 TODO(tbroch) Enhance integration test and add unittest (see mox)
284 """
285 logging.basicConfig(level=logging.DEBUG,
286 format="%(asctime)s - %(name)s - " +
287 "%(levelname)s - %(message)s")
288 # configure server & listen
289 servod_obj = Servod(1)
290 # 4 == number of interfaces on a FT4232H device
291 for i in xrange(4):
292 if i == 1:
293 # its an i2c interface ... see __init__ for details and TODO to make
294 # this configureable
295 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
296 else:
297 # its a gpio interface
298 servod_obj._interface_list[i].wr_rd(0)
299
300 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
301 allow_none=True)
302 server.register_introspection_functions()
303 server.register_multicall_functions()
304 server.register_instance(servod_obj)
305 logging.info("Listening on localhost port 9999")
306 server.serve_forever()
307
308if __name__ == "__main__":
309 test()
310
311 # simple client transaction would look like
312 """
313 remote_uri = 'http://localhost:9999'
314 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
315 send_str = "Hello_there"
316 print "Sent " + send_str + ", Recv " + client.echo(send_str)
317 """