blob: 7e1778138608466d298456a951cb2d71cfb8ecdd [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
190 except drv.hw_driver.HwDriverError:
191 self._logger.error("Getting %s" % (name))
192 raise
193 def get_all(self, verbose):
194 """Get all controls values.
195
196 Args:
197 verbose: Boolean on whether to return doc info as well
198
199 Returns:
200 string creating from trying to get all values of all controls. In case of
201 error attempting access to control, response is 'ERR'.
202 """
203 rsp = ""
204 for name in self._syscfg.syscfg_dict['control']:
205 self._logger.debug("name = %s" %name)
206 try:
207 value = self.get(name)
208 except Exception:
209 value = "ERR"
210 pass
211 if verbose:
212 rsp += "GET %s = %s :: %s\n" % (name, value, self.doc(name))
213 else:
214 rsp += "%s:%s\n" % (name, value)
215 return rsp
216
217 def set(self, name, wr_val_str):
218 """Set control.
219
220 Args:
221 name: name string of control
222 wr_val_str: value string to write. Can be integer, float or a
223 alpha-numerical that is mapped to a integer or float.
224
225 Raises:
226 HwDriverError: Error occurred while using driver
227 """
228 self._logger.debug("name(%s) wr_val(%s)" % (name, wr_val_str))
229 (params, drv) = self._get_param_drv(name, False)
230 wr_val = self._syscfg.resolve_val(params, wr_val_str)
231 try:
232 drv.set(wr_val)
233 except drv.hw_driver.HwDriverError:
234 self._logger.error("Setting %s -> %s" % (name, wr_val_str))
235 raise
236 # TODO(tbroch) Figure out why despite allow_none=True for both xmlrpc server
237 # & client I still have to return something to appease the
238 # marshall/unmarshall
239 return True
240
241 def echo(self, echo):
242 """Dummy echo function for testing/examples.
243
244 Args:
245 echo: string to echo back to client
246 """
247 self._logger.debug("echo(%s)" % (echo))
248 return "ECH0ING: %s" % (echo)
249
250def test():
251 """Integration testing.
252
253 TODO(tbroch) Enhance integration test and add unittest (see mox)
254 """
255 logging.basicConfig(level=logging.DEBUG,
256 format="%(asctime)s - %(name)s - " +
257 "%(levelname)s - %(message)s")
258 # configure server & listen
259 servod_obj = Servod(1)
260 # 4 == number of interfaces on a FT4232H device
261 for i in xrange(4):
262 if i == 1:
263 # its an i2c interface ... see __init__ for details and TODO to make
264 # this configureable
265 servod_obj._interface_list[i].wr_rd(0x21, [0], 1)
266 else:
267 # its a gpio interface
268 servod_obj._interface_list[i].wr_rd(0)
269
270 server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 9999),
271 allow_none=True)
272 server.register_introspection_functions()
273 server.register_multicall_functions()
274 server.register_instance(servod_obj)
275 logging.info("Listening on localhost port 9999")
276 server.serve_forever()
277
278if __name__ == "__main__":
279 test()
280
281 # simple client transaction would look like
282 """
283 remote_uri = 'http://localhost:9999'
284 client = xmlrpclib.ServerProxy(remote_uri, verbose=False)
285 send_str = "Hello_there"
286 print "Sent " + send_str + ", Recv " + client.echo(send_str)
287 """