Mbed Host Tests
conn_proxy.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 """
3 mbed SDK
4 Copyright (c) 2011-2016 ARM Limited
5 
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9 
10  http://www.apache.org/licenses/LICENSE-2.0
11 
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 """
18 
19 from past.builtins import basestring
20 
21 import re
22 import sys
23 import uuid
24 from time import time
25 from mbed_host_tests.host_tests_logger import HtrunLogger
26 from .conn_primitive_serial import SerialConnectorPrimitive
27 from .conn_primitive_remote import RemoteConnectorPrimitive
28 from .conn_primitive_fastmodel import FastmodelConnectorPrimitive
29 
30 if (sys.version_info > (3, 0)):
31  from queue import Empty as QueueEmpty # Queue here refers to the module, not a class
32 else:
33  from Queue import Empty as QueueEmpty
34 
36  """! Simple auxiliary class used to walk through a buffer and search for KV tokens """
37  def __init__(self):
38  self.KIVI_REGEXKIVI_REGEX = r"\{\{([\w\d_-]+);([^\}]+)\}\}"
39  self.buffbuff = str()
40  self.kvlkvl = []
41  self.re_kvre_kv = re.compile(self.KIVI_REGEXKIVI_REGEX)
42 
43  def append(self, payload):
44  """! Append stream buffer with payload and process. Returns non-KV strings"""
45  logger = HtrunLogger('CONN')
46  try:
47  self.buffbuff += payload.decode('utf-8')
48  except UnicodeDecodeError:
49  logger.prn_wrn("UnicodeDecodeError encountered!")
50  self.buffbuff += payload.decode('utf-8','ignore')
51  lines = self.buffbuff.split('\n')
52  self.buffbuff = lines[-1] # remaining
53  lines.pop(-1)
54  # List of line or strings that did not match K,V pair.
55  discarded = []
56 
57  for line in lines:
58  m = self.re_kvre_kv.search(line)
59  if m:
60  (key, value) = m.groups()
61  self.kvlkvl.append((key, value, time()))
62  line = line.strip()
63  match = m.group(0)
64  pos = line.find(match)
65  before = line[:pos]
66  after = line[pos + len(match):]
67  if len(before) > 0:
68  discarded.append(before)
69  if len(after) > 0:
70  # not a K,V pair part
71  discarded.append(after)
72  else:
73  # not a K,V pair
74  discarded.append(line)
75  return discarded
76 
77  def search(self):
78  """! Check if there is a KV value in buffer """
79  return len(self.kvlkvl) > 0
80 
81  def pop_kv(self):
82  if len(self.kvlkvl):
83  return self.kvlkvl.pop(0)
84  return None, None, time()
85 
86 
87 def conn_primitive_factory(conn_resource, config, event_queue, logger):
88  """! Factory producing connectors based on type and config
89  @param conn_resource Name of connection primitive (e.g. 'serial' for
90  local serial port connection or 'grm' for global resource manager)
91  @param event_queue Even queue of Key-Value protocol
92  @param config Global configuration for connection process
93  @param logger Host Test logger instance
94  @return Object of type <ConnectorPrimitive> or None if type of connection primitive unknown (conn_resource)
95  """
96  polling_timeout = int(config.get('polling_timeout', 60))
97  logger.prn_inf("notify event queue about extra %d sec timeout for serial port pooling"%polling_timeout)
98  event_queue.put(('__timeout', polling_timeout, time()))
99 
100  if conn_resource == 'serial':
101  # Standard serial port connection
102  # Notify event queue we will wait additional time for serial port to be ready
103 
104  # Get extra configuration related to serial port
105  port = config.get('port')
106  baudrate = config.get('baudrate')
107 
108  logger.prn_inf("initializing serial port listener... ")
109  connector = SerialConnectorPrimitive(
110  'SERI',
111  port,
112  baudrate,
113  config=config)
114  elif conn_resource == 'grm':
115  # Start GRM (Gloabal Resource Mgr) collection
116  logger.prn_inf("initializing global resource mgr listener... ")
117  connector = RemoteConnectorPrimitive('GLRM', config=config)
118  elif conn_resource == 'fmc':
119  # Start Fast Model Connection collection
120  logger.prn_inf("initializing fast model connection")
121  connector = FastmodelConnectorPrimitive('FSMD', config=config)
122  else:
123  logger.pn_err("unknown connection resource!")
124  raise NotImplementedError("ConnectorPrimitive factory: unknown connection resource '%s'!"% conn_resource)
125 
126  return connector
127 
128 
129 def conn_process(event_queue, dut_event_queue, config):
130 
131  def __notify_conn_lost():
132  error_msg = connector.error()
133  connector.finish()
134  event_queue.put(('__notify_conn_lost', error_msg, time()))
135 
136  def __notify_sync_failed():
137  error_msg = connector.error()
138  connector.finish()
139  event_queue.put(('__notify_sync_failed', error_msg, time()))
140 
141  logger = HtrunLogger('CONN')
142  logger.prn_inf("starting connection process...")
143 
144  # Send connection process start event to host process
145  # NOTE: Do not send any other Key-Value pairs before this!
146  event_queue.put(('__conn_process_start', 1, time()))
147 
148  # Configuration of conn_opriocess behaviour
149  sync_behavior = int(config.get('sync_behavior', 1))
150  sync_timeout = config.get('sync_timeout', 1.0)
151  conn_resource = config.get('conn_resource', 'serial')
152  last_sync = False
153 
154  # Create connector instance with proper configuration
155  connector = conn_primitive_factory(conn_resource, config, event_queue, logger)
156 
157  # If the connector failed, stop the process now
158  if not connector.connected():
159  logger.prn_err("Failed to connect to resource")
160  __notify_conn_lost()
161  return 0
162 
163  # Create simple buffer we will use for Key-Value protocol data
164  kv_buffer = KiViBufferWalker()
165 
166  # List of all sent to target UUIDs (if multiple found)
167  sync_uuid_list = []
168 
169  # We will ignore all kv pairs before we get sync back
170  sync_uuid_discovered = False
171 
172  def __send_sync(timeout=None):
173  sync_uuid = str(uuid.uuid4())
174  # Handshake, we will send {{sync;UUID}} preamble and wait for mirrored reply
175  if timeout:
176  logger.prn_inf("Reset the part and send in new preamble...")
177  connector.reset()
178  logger.prn_inf("resending new preamble '%s' after %0.2f sec"% (sync_uuid, timeout))
179  else:
180  logger.prn_inf("sending preamble '%s'"% sync_uuid)
181 
182  if connector.write_kv('__sync', sync_uuid):
183  return sync_uuid
184  else:
185  return None
186 
187  # Send simple string to device to 'wake up' greentea-client k-v parser
188  if not connector.write("mbed" * 10, log=True):
189  # Failed to write 'wake up' string, exit conn_process
190  __notify_conn_lost()
191  return 0
192 
193 
194  # Sync packet management allows us to manipulate the way htrun sends __sync packet(s)
195  # With current settings we can force on htrun to send __sync packets in this manner:
196  #
197  # * --sync=0 - No sync packets will be sent to target platform
198  # * --sync=-10 - __sync packets will be sent unless we will reach
199  # timeout or proper response is sent from target platform
200  # * --sync=N - Send up to N __sync packets to target platform. Response
201  # is sent unless we get response from target platform or
202  # timeout occur
203 
204  if sync_behavior > 0:
205  # Sending up to 'n' __sync packets
206  logger.prn_inf("sending up to %s __sync packets (specified with --sync=%s)"% (sync_behavior, sync_behavior))
207  sync_uuid = __send_sync()
208 
209  if sync_uuid:
210  sync_uuid_list.append(sync_uuid)
211  sync_behavior -= 1
212  else:
213  __notify_conn_lost()
214  return 0
215  elif sync_behavior == 0:
216  # No __sync packets
217  logger.prn_wrn("skipping __sync packet (specified with --sync=%s)"% sync_behavior)
218  else:
219  # Send __sync until we go reply
220  logger.prn_inf("sending multiple __sync packets (specified with --sync=%s)"% sync_behavior)
221 
222  sync_uuid = __send_sync()
223  if sync_uuid:
224  sync_uuid_list.append(sync_uuid)
225  sync_behavior -= 1
226  else:
227  __notify_conn_lost()
228  return 0
229 
230  loop_timer = time()
231  while True:
232 
233  # Check if connection is lost to serial
234  if not connector.connected():
235  __notify_conn_lost()
236  break
237 
238  # Send data to DUT
239  try:
240  (key, value, _) = dut_event_queue.get(block=False)
241  except QueueEmpty:
242  pass # Check if target sent something
243  else:
244  # Return if state machine in host_test_default has finished to end process
245  if key == '__host_test_finished' and value == True:
246  logger.prn_inf("received special event '%s' value='%s', finishing"% (key, value))
247  connector.finish()
248  return 0
249  elif key == '__reset':
250  logger.prn_inf("received special event '%s', resetting dut" % (key))
251  connector.reset()
252  event_queue.put(("reset_complete", 0, time()))
253  elif not connector.write_kv(key, value):
254  connector.write_kv(key, value)
255  __notify_conn_lost()
256  break
257 
258  # Since read is done every 0.2 sec, with maximum baud rate we can receive 2304 bytes in one read in worst case.
259  data = connector.read(2304)
260  if data:
261  # Stream data stream KV parsing
262  print_lines = kv_buffer.append(data)
263  for line in print_lines:
264  logger.prn_rxd(line)
265  event_queue.put(('__rxd_line', line, time()))
266  while kv_buffer.search():
267  key, value, timestamp = kv_buffer.pop_kv()
268 
269  if sync_uuid_discovered:
270  event_queue.put((key, value, timestamp))
271  logger.prn_inf("found KV pair in stream: {{%s;%s}}, queued..."% (key, value))
272  else:
273  if key == '__sync':
274  if value in sync_uuid_list:
275  sync_uuid_discovered = True
276  event_queue.put((key, value, time()))
277  idx = sync_uuid_list.index(value)
278  logger.prn_inf("found SYNC in stream: {{%s;%s}} it is #%d sent, queued..."% (key, value, idx))
279  else:
280  logger.prn_err("found faulty SYNC in stream: {{%s;%s}}, ignored..."% (key, value))
281  logger.prn_inf("Resetting the part and sync timeout to clear out the buffer...")
282  connector.reset()
283  loop_timer = time()
284  else:
285  logger.prn_wrn("found KV pair in stream: {{%s;%s}}, ignoring..."% (key, value))
286 
287  if not sync_uuid_discovered:
288  # Resending __sync after 'sync_timeout' secs (default 1 sec)
289  # to target platform. If 'sync_behavior' counter is != 0 we
290  # will continue to send __sync packets to target platform.
291  # If we specify 'sync_behavior' < 0 we will send 'forever'
292  # (or until we get reply)
293 
294  if sync_behavior != 0:
295  time_to_sync_again = time() - loop_timer
296  if time_to_sync_again > sync_timeout:
297  sync_uuid = __send_sync(timeout=time_to_sync_again)
298 
299  if sync_uuid:
300  sync_uuid_list.append(sync_uuid)
301  sync_behavior -= 1
302  loop_timer = time()
303  #Sync behavior will be zero and if last sync fails we should report connection
304  #lost
305  if sync_behavior == 0:
306  last_sync = True
307  else:
308  __notify_conn_lost()
309  break
310  elif last_sync == True:
311  #SYNC lost connection event : Device not responding, send sync failed
312  __notify_sync_failed()
313  break
314 
315  return 0
Simple auxiliary class used to walk through a buffer and search for KV tokens.
Definition: conn_proxy.py:35
def search(self)
Check if there is a KV value in buffer.
Definition: conn_proxy.py:77
def append(self, payload)
Append stream buffer with payload and process.
Definition: conn_proxy.py:43
def conn_primitive_factory(conn_resource, config, event_queue, logger)
Factory producing connectors based on type and config.
Definition: conn_proxy.py:87
def conn_process(event_queue, dut_event_queue, config)
Definition: conn_proxy.py:129