blob: b00ff60b8f607234fc666a2d5ee76105982c2a40 [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
10import ftdi_common
11import ftdigpio
12import ftdii2c
13
14MAX_I2C_CLOCK_HZ = 100000
15
16class ServodError(Exception):
17 """Exception class for servod."""
18
19class Servod(object):
20 """Main class for Servo debug/controller Daemon."""
21 def __init__(self, config, vendor=ftdi_common.DEFAULT_VID,
Todd Brochad034442011-05-25 15:05:29 -070022 product=ftdi_common.DEFAULT_PID,
23 serialname=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 Broche505b8d2011-03-21 18:19:54 -070032 """
33 self._logger = logging.getLogger("Servod")
34 self._logger.debug("")
35 self._vendor = vendor
36 self._product = product
Todd Brochad034442011-05-25 15:05:29 -070037 self._serialname = serialname
Todd Broche505b8d2011-03-21 18:19:54 -070038 self._syscfg = config
39 # list of objects (Fi2c, Fgpio) to physical interfaces (gpio, i2c) that ftdi
40 # interfaces are mapped to
41 self._interface_list = []
42 # Dict of Dict to map control name, function name to to tuple (params, drv)
43 # Ex) _drv_dict[name]['get'] = (params, drv)
44 self._drv_dict = {}
45
46 # TODO(tbroch) make this configuraeable. Presently we hardwire these
47 # interfaces with 1,3,4 as Fgpio objects and 2 Fi2c objects. Future
48 # revisions will provide alternate and perhaps mechanism to re-allocate the
49 # interface altogether. Note, interface i is (i - 1) in list
50 self._interface_list.append(self._init_gpio(1))
51 self._interface_list.append(self._init_i2c(2))
52 self._interface_list.append(self._init_gpio(3))
53 self._interface_list.append(self._init_gpio(4))
54
55 def _init_gpio(self, interface):
56 """Initialize gpio driver interface and open for use.
57
58 Args:
59 interface: interface number of FTDI device to use.
60
61 Returns:
62 Instance object of interface.
63 """
Todd Brochad034442011-05-25 15:05:29 -070064 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
65 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070066 fobj.open()
67 return fobj
68
69 def _init_i2c(self, interface):
70 """Initialize i2c interface and open for use.
71
72 Args:
73 interface: interface number of FTDI device to use
74
75 Returns:
76 Instance object of interface
77 """
Todd Brochad034442011-05-25 15:05:29 -070078 fobj = ftdii2c.Fi2c(self._vendor, self._product, interface,
79 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070080 fobj.open()
81 # Set the frequency of operation of the i2c bus.
82 # TODO(tbroch) make configureable
83 fobj.setclock(MAX_I2C_CLOCK_HZ)
84 return fobj
85
86 def _get_param_drv(self, control_name, is_get=True):
87 """Get access to driver for a given control.
88
89 Note, some controls have different parameter dictionaries for 'getting' the
90 control's value versus 'setting' it. Boolean is_get distinguishes which is
91 being requested.
92
93 Args:
94 control_name: string name of control
95 is_get: boolean to determine
96
97 Returns:
98 tuple (param, drv) where:
99 param: param dictionary for control
100 drv: instance object of driver for particular control
101
102 Raises:
103 ServodError: Error occurred while examining params dict
104 """
105 self._logger.debug("")
106 # if already setup just return tuple from driver dict
107 if control_name in self._drv_dict:
108 if is_get and ('get' in self._drv_dict[control_name]):
109 return self._drv_dict[control_name]['get']
110 if not is_get and ('set' in self._drv_dict[control_name]):
111 return self._drv_dict[control_name]['set']
112
113 params = self._syscfg.lookup_control_params(control_name, is_get)
114 if 'drv' not in params:
115 self._logger.error("Unable to determine driver for %s" % control_name)
116 raise ServodError("'drv' key not found in params dict")
117 if 'interface' not in params:
118 self._logger.error("Unable to determine interface for %s" %
119 control_name)
120
121 raise ServodError("'interface' key not found in params dict")
122 index = int(params['interface']) - 1
123 interface = self._interface_list[index]
124 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
125 drv_pkg = imp.load_module('drv',
126 *imp.find_module('drv', servo_pkg.__path__))
127 drv_name = params['drv']
128 drv_module = getattr(drv_pkg, drv_name)
129 drv_class = getattr(drv_module, drv_name)
130 drv = drv_class(interface, params)
131 if control_name not in self._drv_dict:
132 self._drv_dict[control_name] = {}
133 if is_get:
134 self._drv_dict[control_name]['get'] = (params, drv)
135 else:
136 self._drv_dict[control_name]['set'] = (params, drv)
137 return (params, drv)
138
139 def doc_all(self):
140 """Return all documenation for controls.
141
142 Returns:
143 string of <doc> text in config file (xml) and the params dictionary for
144 all controls.
145
146 For example:
147 warm_reset :: Reset the device warmly
148 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
149 """
150 return self._syscfg.display_config()
151
152 def doc(self, name):
153 """Retreive doc string in system config file for given control name.
154
155 Args:
156 name: name string of control to get doc string
157
158 Returns:
159 doc string of name
160
161 Raises:
162 NameError: if fails to locate control
163 """
164 self._logger.debug("name(%s)" % (name))
165 if self._syscfg.is_control(name):
166 return self._syscfg.get_control_docstring(name)
167 else:
168 raise NameError("No control %s" %name)
169
170 def get(self, name):
171 """Get control value.
172
173 Args:
174 name: name string of control
175
176 Returns:
177 Response from calling drv get method. Value is reformatted based on
178 control's dictionary parameters
179
180 Raises:
181 HwDriverError: Error occurred while using drv
182 """
183 self._logger.debug("name(%s)" % (name))
184 (param, drv) = self._get_param_drv(name)
185 try:
186 val = drv.get()
187 rd_val = self._syscfg.reformat_val(param, val)
188 self._logger.info("%s = %s" % (name, rd_val))
189 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700190 except AttributeError, error:
191 self._logger.error("Getting %s: %s" % (name, error))
192 raise
Todd Broche505b8d2011-03-21 18:19:54 -0700193 except drv.hw_driver.HwDriverError:
194 self._logger.error("Getting %s" % (name))
195 raise
196 def get_all(self, verbose):
197 """Get all controls values.
198
199 Args:
200 verbose: Boolean on whether to return doc info as well
201
202 Returns:
203 string creating from trying to get all values of all controls. In case of
204 error attempting access to control, response is 'ERR'.
205 """
206 rsp = ""
207 for name in self._syscfg.syscfg_dict['control']:
208 self._logger.debug("name = %s" %name)
209 try:
210 value = self.get(name)
211 except Exception:
212 value = "ERR"
213 pass
214 if verbose:
215 rsp += "GET %s = %s :: %s\n" % (name, value, self.doc(name))
216 else:
217 rsp += "%s:%s\n" % (name, value)
218 return rsp
219
220 def set(self, name, wr_val_str):
221 """Set control.
222
223 Args:
224 name: name string of control
225 wr_val_str: value string to write. Can be integer, float or a
226 alpha-numerical that is mapped to a integer or float.
227
228 Raises:
229 HwDriverError: Error occurred while using driver
230 """
231 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
232 (params, drv) = self._get_param_drv(name, False)
233 wr_val = self._syscfg.resolve_val(params, wr_val_str)
234 try:
235 drv.set(wr_val)
236 except drv.hw_driver.HwDriverError:
237 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
238 raise
239 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
240 # & client I still have to return something to appease the
241 # marshall/unmarshall
242 return True
243
244 def echo(self, echo):
245 """Dummy echo function for testing/examples.
246
247 Args:
248 echo: string to echo back to client
249 """
250 self._logger.debug("echo(%s)" % (echo))
251 return "ECH0ING: %s" % (echo)
252
253def test():
254 """Integration testing.
255
256 TODO(tbroch) Enhance integration test and add unittest (see mox)
257 """
258 logging.basicConfig(level=logging.DEBUG,
259 format="%(asctime)s - %(name)s - " +
260 "%(levelname)s - %(message)s")
261 # configure server & listen
262 servod_obj = Servod(1)
263 # 4 == number of interfaces on a FT4232H device
264 for i in xrange(4):
265 if i == 1:
266 # its an i2c interface ... see __init__ for details and TODO to make
267 # this configureable
268 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
269 else:
270 # its a gpio interface
271 servod_obj._interface_list[i].wr_rd(0)
272
273 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
274 allow_none=True)
275 server.register_introspection_functions()
276 server.register_multicall_functions()
277 server.register_instance(servod_obj)
278 logging.info("Listening on localhost port 9999")
279 server.serve_forever()
280
281if __name__ == "__main__":
282 test()
283
284 # simple client transaction would look like
285 """
286 remote_uri = 'http://localhost:9999'
287 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
288 send_str = "Hello_there"
289 print "Sent " + send_str + ", Recv " + client.echo(send_str)
290 """