3 Copyright (c) 2011-2015 ARM Limited
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
9 http://www.apache.org/licenses/LICENSE-2.0
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
22 from os.path
import expanduser
25 from os
import listdir
26 from os.path
import isfile, join, exists, isdir
28 from abc
import ABCMeta, abstractmethod
30 from .platform_database
import PlatformDatabase, LOCAL_PLATFORM_DATABASE, \
32 mbedls_root_logger = logging.getLogger(
"mbedls")
33 mbedls_root_logger.setLevel(logging.WARNING)
35 logger = logging.getLogger(
"mbedls.lstools_base")
36 logger.addHandler(logging.NullHandler())
39 """Deprecate a function/method with a decorator"""
40 def actual_decorator(func):
41 @functools.wraps(func)
42 def new_func(*args, **kwargs):
43 logger.warning(
"Call to deprecated function %s. %s",
44 func.__name__, reason)
45 return func(*args, **kwargs)
47 return actual_decorator
55 """ Base class for mbed-lstools, defines mbed-ls tools interface for
56 mbed-enabled devices detection for various hosts
59 __metaclass__ = ABCMeta
66 HOME_DIR = expanduser(
"~")
67 MOCK_FILE_NAME =
'.mbedls-mock'
68 RETARGET_FILE_NAME =
'mbedls.json'
69 DETAILS_TXT_NAME =
'DETAILS.TXT'
70 MBED_HTM_NAME =
'mbed.htm'
72 VENDOR_ID_DEVICE_TYPE_MAP = {
79 def __init__(self, list_unmounted=False, **kwargs):
85 if isfile(self.
MOCK_FILE_NAME)
or (
"force_mock" in kwargs
and kwargs[
'force_mock']):
87 elif isfile(LOCAL_MOCKS_DATABASE):
88 platform_dbs.append(LOCAL_MOCKS_DATABASE)
89 platform_dbs.append(LOCAL_PLATFORM_DATABASE)
91 primary_database=platform_dbs[0])
94 if 'skip_retarget' not in kwargs
or not kwargs[
'skip_retarget']:
99 """Find all candidate devices connected to this computer
101 Note: Should not open any files
103 @return A dict with the keys 'mount_point', 'serial_port' and 'target_id_usb_id'
107 @
deprecated(
"Functionality has been moved into 'list_mbeds'. "
108 "Please use list_mbeds with 'unique_names=True' and "
109 "'read_details_txt=True'")
111 """! Function adds extra information for each mbed device
112 @return Returns list of mbed devices plus extended data like 'platform_name_unique'
113 @details Get information about mbeds with extended parameters/info included
116 return self.
list_mbeds(unique_names=
True, read_details_txt=
True)
119 self, fs_interaction=FSInteraction.BeforeFilter,
120 filter_function=None, unique_names=False,
121 read_details_txt=False):
122 """ List details of connected devices
123 @return Returns list of structures with detailed info about each mbed
124 @param fs_interaction A member of the FSInteraction class that picks the
125 trade of between quality of service and speed
126 @param filter_function Function that is passed each mbed candidate,
127 should return True if it should be included in the result
128 Ex. mbeds = list_mbeds(filter_function=lambda m: m['platform_name'] == 'K64F')
129 @param unique_names A boolean controlling the presence of the
130 'platform_unique_name' member of the output dict
131 @param read_details_txt A boolean controlling the presense of the
132 output dict attributes read from other files present on the 'mount_point'
133 @details Function returns list of dictionaries with mbed attributes 'mount_point', TargetID name etc.
134 Function returns mbed list with platform names if possible
138 logger.debug(
"Candidates for display %r", candidates)
140 for device
in candidates:
142 if ((
not device[
'mount_point']
or
145 if (device[
'target_id_usb_id']
and device[
'serial_port']):
147 "MBED with target id '%s' is connected, but not mounted. "
148 "Use the '-u' flag to include it in the list.",
149 device[
'target_id_usb_id'])
151 platform_data = self.
plat_db.get(device[
'target_id_usb_id'][0:4],
152 device_type=device[
'device_type']
or 'daplink', verbose_data=
True)
153 device.update(platform_data
or {
"platform_name":
None})
158 }[fs_interaction](device, filter_function, read_details_txt)
159 if maybe_device
and (maybe_device[
'mount_point']
or self.
list_unmounted):
161 name = device[
'platform_name']
162 platform_count.setdefault(name, -1)
163 platform_count[name] += 1
164 device[
'platform_name_unique'] = (
165 "%s[%d]" % (name, platform_count[name]))
168 logger.debug(
"retargeting %s with %r",
175 device[
'device_type'] = device[
'device_type']
if device[
'device_type']
else 'unknown'
176 result.append(maybe_device)
180 def _fs_never(self, device, filter_function, read_details_txt):
181 """Filter device without touching the file system of the device"""
182 device[
'target_id'] = device[
'target_id_usb_id']
183 device[
'target_id_mbed_htm'] =
None
184 if not filter_function
or filter_function(device):
189 def _fs_before_id_check(self, device, filter_function, read_details_txt):
190 """Filter device after touching the file system of the device.
191 Said another way: Touch the file system before filtering
194 device[
'target_id'] = device[
'target_id_usb_id']
196 if not filter_function
or filter_function(device):
201 def _fs_after_id_check(self, device, filter_function, read_details_txt):
202 """Filter device before touching the file system of the device.
203 Said another way: Touch the file system after filtering
205 device[
'target_id'] = device[
'target_id_usb_id']
206 device[
'target_id_mbed_htm'] =
None
207 if not filter_function
or filter_function(device):
213 def _update_device_from_fs(self, device, read_details_txt):
214 """ Updates the device information based on files from its 'mount_point'
215 @param device Dictionary containing device information
216 @param read_details_txt A boolean controlling the presense of the
217 output dict attributes read from other files present on the 'mount_point'
219 if not device.get(
'mount_point',
None):
223 directory_entries = os.listdir(device[
'mount_point'])
224 device[
'directory_entries'] = directory_entries
225 device[
'target_id'] = device[
'target_id_usb_id']
231 if device.get(
'device_type') ==
'jlink':
234 if device.get(
'device_type') ==
'atmel':
237 except (OSError, IOError)
as e:
239 'Marking device with mount point "%s" as unmounted due to the '
240 'following error: %s', device[
'mount_point'], e)
241 device[
'mount_point'] =
None
244 def _detect_device_type(self, device):
245 """ Returns a string of the device type
246 @param device Dictionary containing device information
247 @return Device type located in VENDOR_ID_DEVICE_TYPE_MAP or None if unknown
253 def _update_device_details_daplink_compatible(self, device, read_details_txt):
254 """ Updates the daplink-specific device information based on files from its 'mount_point'
255 @param device Dictionary containing device information
256 @param read_details_txt A boolean controlling the presense of the
257 output dict attributes read from other files present on the 'mount_point'
259 lowercase_directory_entries = [e.lower()
for e
in device[
'directory_entries']]
260 if self.
MBED_HTM_NAME.lower()
in lowercase_directory_entries:
262 elif not read_details_txt:
263 logger.debug(
'Since mbed.htm is not present, attempting to use '
264 'details.txt for the target id')
265 read_details_txt =
True
267 if read_details_txt
and self.
DETAILS_TXT_NAME.lower()
in lowercase_directory_entries:
268 details_txt = self.
_details_txt(device[
'mount_point'])
or {}
269 device.update({
"daplink_%s" % f.lower().replace(
' ',
'_'): v
270 for f, v
in details_txt.items()})
273 if device.get(
'daplink_unique_id',
None):
274 device[
'target_id'] = device[
'daplink_unique_id']
276 if device[
'target_id']:
277 identifier = device[
'target_id'][0:4]
278 platform_data = self.
plat_db.get(identifier,
279 device_type=
'daplink',
281 if not platform_data:
282 logger.warning(
'daplink entry: "%s" not found in platform database', identifier)
284 device.update(platform_data)
286 device[
'platform_name'] =
None
288 def _update_device_details_jlink(self, device, _):
289 """ Updates the jlink-specific device information based on files from its 'mount_point'
290 @param device Dictionary containing device information
292 lower_case_map = {e.lower(): e
for e
in device[
'directory_entries']}
294 if 'board.html' in lower_case_map:
295 board_file_key =
'board.html'
296 elif 'user guide.html' in lower_case_map:
297 board_file_key =
'user guide.html'
299 logger.warning(
'No valid file found to update JLink device details')
302 board_file_path = os.path.join(device[
'mount_point'], lower_case_map[board_file_key])
303 with open(board_file_path,
'r')
as board_file:
304 board_file_lines = board_file.readlines()
306 for line
in board_file_lines:
307 m = re.search(
r'url=([\w\d\:\-/\\\?\.=-_]+)', line)
309 device[
'url'] = m.group(1).strip()
310 identifier = device[
'url'].split(
'/')[-1]
311 platform_data = self.
plat_db.get(identifier,
314 if not platform_data:
315 logger.warning(
'jlink entry: "%s", not found in platform database', identifier)
317 device.update(platform_data)
320 def _update_device_from_htm(self, device):
321 """Set the 'target_id', 'target_id_mbed_htm', 'platform_name' and
322 'daplink_*' attributes by reading from mbed.htm on the device
324 htm_target_id, daplink_info = self.
_read_htm_ids(device[
'mount_point'])
326 device.update({
"daplink_%s" % f.lower().replace(
' ',
'_'): v
327 for f, v
in daplink_info.items()})
329 logger.debug(
"Found htm target id, %s, for usb target id %s",
330 htm_target_id, device[
'target_id_usb_id'])
331 device[
'target_id'] = htm_target_id
333 logger.debug(
"Could not read htm on from usb id %s. "
334 "Falling back to usb id",
335 device[
'target_id_usb_id'])
336 device[
'target_id'] = device[
'target_id_usb_id']
337 device[
'target_id_mbed_htm'] = htm_target_id
339 def _update_device_details_atmel(self, device, _):
340 """ Updates the Atmel device information based on files from its 'mount_point'
341 @param device Dictionary containing device information
342 @param read_details_txt A boolean controlling the presense of the
343 output dict attributes read from other files present on the 'mount_point'
351 device[
'target_id'] = device[
'target_id_usb_id'][4:8]
352 platform_data = self.
plat_db.get(device[
'target_id'],
356 device.update(platform_data
or {
"platform_name":
None})
359 """! Replace (or add if manufacture id doesn't exist) entry in self.manufacture_ids
360 @param oper '+' add new mock / override existing entry
361 '-' remove mid from mocking entry
362 @return Mocked structure (json format)
365 self.
plat_db.add(mid, platform_name, permanent=
True)
367 self.
plat_db.remove(mid, permanent=
True)
369 raise ValueError(
"oper can only be [+-]")
371 @
deprecated(
"List formatting methods are deprecated for a simpler API. "
372 "Please use 'list_mbeds' instead.")
374 """! Creates list of all available mappings for target_id -> Platform
375 @return String with table formatted output
377 from prettytable
import PrettyTable, HEADER
379 columns = [
'target_id_prefix',
'platform_name']
380 pt = PrettyTable(columns, junction_char=
"|", hrules=HEADER)
384 for target_id_prefix, platform_name
in sorted(self.
plat_db.items()):
385 pt.add_row([target_id_prefix, platform_name])
387 return pt.get_string()
390 """! Load retarget data from local file
391 @return Curent retarget configuration (dictionary)
400 except ValueError
as e:
405 """! Enable retargeting
406 @details Read data from local retarget configuration file
407 @return Retarget data structure read from configuration file
413 """! Returns simple dummy platform """
414 if not hasattr(self,
"dummy_counter"):
421 "platform_name": platform_name,
422 "platform_name_unique":
"%s[%d]"% (platform_name, self.
dummy_counter[platform_name]),
423 "mount_point":
"DUMMY",
424 "serial_port":
"DUMMY",
425 "target_id":
"DUMMY",
426 "target_id_mbed_htm":
"DUMMY",
427 "target_id_usb_id":
"DUMMY",
428 "daplink_version":
"DUMMY"
434 """! Return a dictionary of supported target ids and the corresponding platform name
435 @param device_type Filter which device entries are returned from the platform database
436 @return Dictionary of { 'target_id': 'platform_name', ... }
439 if device_type
is not None:
440 kwargs[
'device_type'] = device_type
442 items = self.
plat_db.items(**kwargs)
443 return {i[0]: i[1]
for i
in items}
445 @
deprecated(
"List formatting methods are deprecated to simplify the API. "
446 "Please use 'list_mbeds' instead.")
448 """! Useful if you just want to know which platforms are currently available on the system
449 @return List of (unique values) available platforms
453 for i, val
in enumerate(mbeds):
454 platform_name = str(val[
'platform_name'])
455 if platform_name
not in result:
456 result.append(platform_name)
459 @
deprecated(
"List formatting methods are deprecated to simplify the API. "
460 "Please use 'list_mbeds' instead.")
462 """! Useful if you just want to know how many platforms of each type are currently available on the system
463 @return Dict of platform: platform_count
467 for i, val
in enumerate(mbeds):
468 platform_name = str(val[
'platform_name'])
469 if platform_name
not in result:
470 result[platform_name] = 1
472 result[platform_name] += 1
475 @
deprecated(
"List formatting methods are deprecated to simplify the API. "
476 "Please use 'list_mbeds' instead.")
478 """! Get information about mbeds with extended parameters/info included
479 @return Returns dictionary where keys are TargetIDs and values are mbed structures
480 @details Ordered by target id (key: target_id).
484 for mbed
in mbed_list:
485 target_id = mbed[
'target_id']
486 result[target_id] = mbed
490 """! Object to string casting
492 @return Stringified class object should be prettytable formated string
496 @
deprecated(
"List formatting methods are deprecated to simplify the API. "
497 "Please use 'list_mbeds' instead.")
498 def get_string(self, border=False, header=True, padding_width=1, sortby='platform_name'):
499 """! Printing with some sql table like decorators
500 @param border Table border visibility
501 @param header Table header visibility
502 @param padding_width Table padding
503 @param sortby Column used to sort results
504 @return Returns string which can be printed on console
506 from prettytable
import PrettyTable, HEADER
508 mbeds = self.
list_mbeds(unique_names=
True, read_details_txt=
True)
510 """ ['platform_name', 'mount_point', 'serial_port', 'target_id'] - columns generated from USB auto-detection
511 ['platform_name_unique', ...] - columns generated outside detection subsystem (OS dependent detection)
513 columns = [
'platform_name',
'platform_name_unique',
'mount_point',
'serial_port',
'target_id',
'daplink_version']
514 pt = PrettyTable(columns, junction_char=
"|", hrules=HEADER)
521 row.append(mbed[col]
if col
in mbed
and mbed[col]
else 'unknown')
523 result = pt.get_string(border=border, header=header, padding_width=padding_width, sortby=sortby)
528 @
deprecated(
"This method will be removed from the public API. "
529 "Please use 'list_mbeds' instead")
531 """! Loads from file JSON formatted string to data structure
532 @return None if JSON can be loaded
535 with open(json_spec_filename)
as data_file:
537 return json.load(data_file)
538 except ValueError
as json_error_msg:
539 logger.error(
"Parsing file(%s): %s", json_spec_filename, json_error_msg)
541 except IOError
as fileopen_error_msg:
542 logger.warning(fileopen_error_msg)
545 @
deprecated(
"This method will be removed from the public API. "
546 "Please use 'list_mbeds' instead")
551 @
deprecated(
"This method will be removed from the public API. "
552 "Please use 'list_mbeds' instead")
557 def _read_htm_ids(self, mount_point):
558 """! Function scans mbed.htm to get information about TargetID.
559 @param mount_point mbed mount point (disk / drive letter)
560 @return Function returns targetID, in case of failure returns None.
561 @details Note: This function should be improved to scan variety of boards' mbed.htm files
569 result[
'version'], result[
'build'] = ver_bld
571 m = re.search(
r'url=([\w\d\:/\\\?\.=-_]+)', line)
573 result[
'url'] = m.group(1).strip()
574 return target_id, result
576 @
deprecated(
"This method will be removed from the public API. "
577 "Please use 'list_mbeds' instead")
581 def _mbed_htm_comment_section_ver_build(self, line):
582 """! Check for Version and Build date of interface chip firmware im mbed.htm file
583 @return (version, build) tuple if successful, None if no info found
586 m = re.search(
r'^<!-- Version: (\d+) Build: ([\d\w: ]+) -->', line)
588 version_str, build_str = m.groups()
589 return (version_str.strip(), build_str.strip())
592 m = re.search(
r'^<!-- Version: (\d+) Build: ([\d\w: ]+) Git Commit SHA', line)
594 version_str, build_str = m.groups()
595 return (version_str.strip(), build_str.strip())
598 m = re.search(
r'^<!-- Version: ([\d+\.]+)\. build (\d+) -->', line)
600 version_str, build_str = m.groups()
601 return (version_str.strip(), build_str.strip())
604 @
deprecated(
"This method will be removed from the public API. "
605 "Please use 'list_mbeds' instead")
609 def _htm_lines(self, mount_point):
612 with open(mbed_htm_path,
'r')
as f:
615 @
deprecated(
"This method will be removed from the public API. "
616 "Please use 'list_mbeds' instead")
620 def _details_txt(self, mount_point):
621 """! Load DETAILS.TXT to dictionary:
624 Build: Aug 24 2015 17:06:30
625 Git Commit SHA: 27a236b9fe39c674a703c5c89655fbd26b8e27e1
630 # DAPLink Firmware - see https://mbed.com/daplink
631 Unique ID: 0240000029164e45002f0012706e0006f301000097969900
634 Automation allowed: 0
635 Daplink Mode: Interface
636 Interface Version: 0240
637 Git SHA: c765cbb590f57598756683254ca38b211693ae5e
639 USB Interfaces: MSD, CDC, HID
640 Interface CRC: 0x26764ebf
645 with open(path_to_details_txt,
'r')
as f:
649 @
deprecated(
"This method will be removed from the public API. "
650 "Please use 'list_mbeds' instead")
654 def _parse_details(self, lines):
657 if not line.startswith(
'#'):
658 key, _, value = line.partition(
':')
660 result[key] = value.strip()
661 if 'Interface Version' in result:
662 result[
'Version'] = result[
'Interface Version']
665 @
deprecated(
"This method will be removed from the public API. "
666 "Please use 'list_mbeds' instead")
670 def _target_id_from_htm(self, line):
671 """! Extract Target id from htm line.
672 @return Target id or None
675 m = re.search(
'\?code=([a-fA-F0-9]+)', line)
677 result = m.groups()[0]
678 logger.debug(
"Found target id %s in htm line %s", result, line)
681 m = re.search(
'\?auth=([a-fA-F0-9]+)', line)
683 result = m.groups()[0]
684 logger.debug(
"Found target id %s in htm line %s", result, line)
690 """! Check if a mount point is ready for file operations
692 return exists(path)
and isdir(path)
695 @
deprecated(
"This method will be removed from the public API. "
696 "Please use 'list_mbeds' instead")
698 return MbedLsToolsBase._run_cli_process(cmd, shell)
701 def _run_cli_process(cmd, shell=True):
702 """! Runs command as a process and return stdout, stderr and ret code
703 @param cmd Command to execute
704 @return Tuple of (stdout, stderr, returncode)
706 from subprocess
import Popen, PIPE
708 p = Popen(cmd, shell=shell, stdout=PIPE, stderr=PIPE)
709 _stdout, _stderr = p.communicate()
710 return _stdout, _stderr, p.returncode