blob: 1f6485e1ea2cd5836042e6798634b707ee26d81f [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
12
13MAX_I2C_CLOCK_HZ = 100000
14
15class ServodError(Exception):
16 """Exception class for servod."""
17
18class Servod(object):
19 """Main class for Servo debug/controller Daemon."""
Todd Broch06338c52011-08-15 15:42:34 -070020 def __init__(self, config, vendor, product, serialname=None):
Todd Broche505b8d2011-03-21 18:19:54 -070021 """Servod constructor.
22
23 Args:
24 config: instance of SystemConfig containing all controls for
25 particular Servod invocation
26 vendor: usb vendor id of FTDI device
27 product: usb product id of FTDI device
Todd Brochad034442011-05-25 15:05:29 -070028 serialname: string of device serialname/number as defined in FTDI eeprom.
Todd Broche505b8d2011-03-21 18:19:54 -070029 """
30 self._logger = logging.getLogger("Servod")
31 self._logger.debug("")
32 self._vendor = vendor
33 self._product = product
Todd Brochad034442011-05-25 15:05:29 -070034 self._serialname = serialname
Todd Broche505b8d2011-03-21 18:19:54 -070035 self._syscfg = config
36 # list of objects (Fi2c, Fgpio) to physical interfaces (gpio, i2c) that ftdi
37 # interfaces are mapped to
38 self._interface_list = []
39 # Dict of Dict to map control name, function name to to tuple (params, drv)
40 # Ex) _drv_dict[name]['get'] = (params, drv)
41 self._drv_dict = {}
42
43 # TODO(tbroch) make this configuraeable. Presently we hardwire these
44 # interfaces with 1,3,4 as Fgpio objects and 2 Fi2c objects. Future
45 # revisions will provide alternate and perhaps mechanism to re-allocate the
46 # interface altogether. Note, interface i is (i - 1) in list
47 self._interface_list.append(self._init_gpio(1))
48 self._interface_list.append(self._init_i2c(2))
49 self._interface_list.append(self._init_gpio(3))
50 self._interface_list.append(self._init_gpio(4))
51
52 def _init_gpio(self, interface):
53 """Initialize gpio driver interface and open for use.
54
55 Args:
56 interface: interface number of FTDI device to use.
57
58 Returns:
59 Instance object of interface.
60 """
Todd Brochad034442011-05-25 15:05:29 -070061 fobj = ftdigpio.Fgpio(self._vendor, self._product, interface,
62 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070063 fobj.open()
64 return fobj
65
66 def _init_i2c(self, interface):
67 """Initialize i2c 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 = ftdii2c.Fi2c(self._vendor, self._product, interface,
76 self._serialname)
Todd Broche505b8d2011-03-21 18:19:54 -070077 fobj.open()
78 # Set the frequency of operation of the i2c bus.
79 # TODO(tbroch) make configureable
80 fobj.setclock(MAX_I2C_CLOCK_HZ)
81 return fobj
82
83 def _get_param_drv(self, control_name, is_get=True):
84 """Get access to driver for a given control.
85
86 Note, some controls have different parameter dictionaries for 'getting' the
87 control's value versus 'setting' it. Boolean is_get distinguishes which is
88 being requested.
89
90 Args:
91 control_name: string name of control
92 is_get: boolean to determine
93
94 Returns:
95 tuple (param, drv) where:
96 param: param dictionary for control
97 drv: instance object of driver for particular control
98
99 Raises:
100 ServodError: Error occurred while examining params dict
101 """
102 self._logger.debug("")
103 # if already setup just return tuple from driver dict
104 if control_name in self._drv_dict:
105 if is_get and ('get' in self._drv_dict[control_name]):
106 return self._drv_dict[control_name]['get']
107 if not is_get and ('set' in self._drv_dict[control_name]):
108 return self._drv_dict[control_name]['set']
109
110 params = self._syscfg.lookup_control_params(control_name, is_get)
111 if 'drv' not in params:
112 self._logger.error("Unable to determine driver for %s" % control_name)
113 raise ServodError("'drv' key not found in params dict")
114 if 'interface' not in params:
115 self._logger.error("Unable to determine interface for %s" %
116 control_name)
117
118 raise ServodError("'interface' key not found in params dict")
119 index = int(params['interface']) - 1
120 interface = self._interface_list[index]
121 servo_pkg = imp.load_module('servo', *imp.find_module('servo'))
122 drv_pkg = imp.load_module('drv',
123 *imp.find_module('drv', servo_pkg.__path__))
124 drv_name = params['drv']
125 drv_module = getattr(drv_pkg, drv_name)
126 drv_class = getattr(drv_module, drv_name)
127 drv = drv_class(interface, params)
128 if control_name not in self._drv_dict:
129 self._drv_dict[control_name] = {}
130 if is_get:
131 self._drv_dict[control_name]['get'] = (params, drv)
132 else:
133 self._drv_dict[control_name]['set'] = (params, drv)
134 return (params, drv)
135
136 def doc_all(self):
137 """Return all documenation for controls.
138
139 Returns:
140 string of <doc> text in config file (xml) and the params dictionary for
141 all controls.
142
143 For example:
144 warm_reset :: Reset the device warmly
145 ------------------------> {'interface': '1', 'map': 'onoff_i', ... }
146 """
147 return self._syscfg.display_config()
148
149 def doc(self, name):
150 """Retreive doc string in system config file for given control name.
151
152 Args:
153 name: name string of control to get doc string
154
155 Returns:
156 doc string of name
157
158 Raises:
159 NameError: if fails to locate control
160 """
161 self._logger.debug("name(%s)" % (name))
162 if self._syscfg.is_control(name):
163 return self._syscfg.get_control_docstring(name)
164 else:
165 raise NameError("No control %s" %name)
166
167 def get(self, name):
168 """Get control value.
169
170 Args:
171 name: name string of control
172
173 Returns:
174 Response from calling drv get method. Value is reformatted based on
175 control's dictionary parameters
176
177 Raises:
178 HwDriverError: Error occurred while using drv
179 """
180 self._logger.debug("name(%s)" % (name))
181 (param, drv) = self._get_param_drv(name)
182 try:
183 val = drv.get()
184 rd_val = self._syscfg.reformat_val(param, val)
185 self._logger.info("%s = %s" % (name, rd_val))
186 return rd_val
Todd Brochfbc499d2011-06-16 16:09:58 -0700187 except AttributeError, error:
188 self._logger.error("Getting %s: %s" % (name, error))
189 raise
Todd Broche505b8d2011-03-21 18:19:54 -0700190 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 """