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_NAMEMOCK_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'. "
"Please use list_mbeds with 'unique_names=True' and "
"'read_details_txt=True'")
109 """! Function adds extra information for each mbed device
110 @return Returns list of mbed devices plus extended data like 'platform_name_unique'
111 @details Get information about mbeds with extended parameters/info included
114 return self.
list_mbedslist_mbeds(unique_names=
True, read_details_txt=
True)
117 self, fs_interaction=FSInteraction.BeforeFilter,
118 filter_function=None, unique_names=False,
119 read_details_txt=False):
120 """ List details of connected devices
121 @return Returns list of structures with detailed info about each mbed
122 @param fs_interaction A member of the FSInteraction class that picks the
123 trade of between quality of service and speed
124 @param filter_function Function that is passed each mbed candidate,
125 should return True if it should be included in the result
126 Ex. mbeds = list_mbeds(filter_function=lambda m: m['platform_name'] == 'K64F')
127 @param unique_names A boolean controlling the presence of the
128 'platform_unique_name' member of the output dict
129 @param read_details_txt A boolean controlling the presense of the
130 output dict attributes read from other files present on the 'mount_point'
131 @details Function returns list of dictionaries with mbed attributes 'mount_point', TargetID name etc.
132 Function returns mbed list with platform names if possible
136 logger.debug(
"Candidates for display %r", candidates)
138 for device
in candidates:
140 if ((
not device[
'mount_point']
or
143 if (device[
'target_id_usb_id']
and device[
'serial_port']):
145 "MBED with target id '%s' is connected, but not mounted. "
146 "Use the '-u' flag to include it in the list.",
147 device[
'target_id_usb_id'])
149 platform_data = self.
plat_dbplat_db.get(device[
'target_id_usb_id'][0:4],
150 device_type=device[
'device_type']
or 'daplink', verbose_data=
True)
151 device.update(platform_data
or {
"platform_name":
None})
155 FSInteraction.Never: self.
_fs_never_fs_never
156 }[fs_interaction](device, filter_function, read_details_txt)
157 if maybe_device
and (maybe_device[
'mount_point']
or self.
list_unmountedlist_unmounted):
159 name = device[
'platform_name']
160 platform_count.setdefault(name, -1)
161 platform_count[name] += 1
162 device[
'platform_name_unique'] = (
163 "%s[%d]" % (name, platform_count[name]))
165 device.update(self.
retarget_dataretarget_data[device[
'target_id']])
166 logger.debug(
"retargeting %s with %r",
173 device[
'device_type'] = device[
'device_type']
if device[
'device_type']
else 'unknown'
174 result.append(maybe_device)
178 def _fs_never(self, device, filter_function, read_details_txt):
179 """Filter device without touching the file system of the device"""
180 device[
'target_id'] = device[
'target_id_usb_id']
181 device[
'target_id_mbed_htm'] =
None
182 if not filter_function
or filter_function(device):
187 def _fs_before_id_check(self, device, filter_function, read_details_txt):
188 """Filter device after touching the file system of the device.
189 Said another way: Touch the file system before filtering
192 device[
'target_id'] = device[
'target_id_usb_id']
194 if not filter_function
or filter_function(device):
199 def _fs_after_id_check(self, device, filter_function, read_details_txt):
200 """Filter device before touching the file system of the device.
201 Said another way: Touch the file system after filtering
203 device[
'target_id'] = device[
'target_id_usb_id']
204 device[
'target_id_mbed_htm'] =
None
205 if not filter_function
or filter_function(device):
211 def _update_device_from_fs(self, device, read_details_txt):
212 """ Updates the device information based on files from its 'mount_point'
213 @param device Dictionary containing device information
214 @param read_details_txt A boolean controlling the presense of the
215 output dict attributes read from other files present on the 'mount_point'
217 if not device.get(
'mount_point',
None):
221 directory_entries = os.listdir(device[
'mount_point'])
222 device[
'directory_entries'] = directory_entries
223 device[
'target_id'] = device[
'target_id_usb_id']
229 if device.get(
'device_type') ==
'jlink':
232 if device.get(
'device_type') ==
'atmel':
235 except (OSError, IOError)
as e:
237 'Marking device with mount point "%s" as unmounted due to the '
238 'following error: %s', device[
'mount_point'], e)
239 device[
'mount_point'] =
None
242 def _detect_device_type(self, device):
243 """ Returns a string of the device type
244 @param device Dictionary containing device information
245 @return Device type located in VENDOR_ID_DEVICE_TYPE_MAP or None if unknown
251 def _update_device_details_daplink_compatible(self, device, read_details_txt):
252 """ Updates the daplink-specific device information based on files from its 'mount_point'
253 @param device Dictionary containing device information
254 @param read_details_txt A boolean controlling the presense of the
255 output dict attributes read from other files present on the 'mount_point'
257 lowercase_directory_entries = [e.lower()
for e
in device[
'directory_entries']]
258 if self.
MBED_HTM_NAMEMBED_HTM_NAME.lower()
in lowercase_directory_entries:
260 elif not read_details_txt:
261 logger.debug(
'Since mbed.htm is not present, attempting to use '
262 'details.txt for the target id')
263 read_details_txt =
True
265 if read_details_txt
and self.
DETAILS_TXT_NAMEDETAILS_TXT_NAME.lower()
in lowercase_directory_entries:
266 details_txt = self.
_details_txt_details_txt(device[
'mount_point'])
or {}
267 device.update({
"daplink_%s" % f.lower().replace(
' ',
'_'): v
268 for f, v
in details_txt.items()})
271 if device.get(
'daplink_unique_id',
None):
272 device[
'target_id'] = device[
'daplink_unique_id']
274 if device[
'target_id']:
275 identifier = device[
'target_id'][0:4]
276 platform_data = self.
plat_dbplat_db.get(identifier,
277 device_type=
'daplink',
279 if not platform_data:
280 logger.warning(
'daplink entry: "%s" not found in platform database', identifier)
282 device.update(platform_data)
284 device[
'platform_name'] =
None
286 def _update_device_details_jlink(self, device, _):
287 """ Updates the jlink-specific device information based on files from its 'mount_point'
288 @param device Dictionary containing device information
290 lower_case_map = {e.lower(): e
for e
in device[
'directory_entries']}
292 if 'board.html' in lower_case_map:
293 board_file_key =
'board.html'
294 elif 'user guide.html' in lower_case_map:
295 board_file_key =
'user guide.html'
297 logger.warning(
'No valid file found to update JLink device details')
300 board_file_path = os.path.join(device[
'mount_point'], lower_case_map[board_file_key])
301 with open(board_file_path,
'r')
as board_file:
302 board_file_lines = board_file.readlines()
304 for line
in board_file_lines:
305 m = re.search(
r'url=([\w\d\:\-/\\\?\.=-_]+)', line)
307 device[
'url'] = m.group(1).strip()
308 identifier = device[
'url'].split(
'/')[-1]
309 platform_data = self.
plat_dbplat_db.get(identifier,
312 if not platform_data:
313 logger.warning(
'jlink entry: "%s", not found in platform database', identifier)
315 device.update(platform_data)
318 def _update_device_from_htm(self, device):
319 """Set the 'target_id', 'target_id_mbed_htm', 'platform_name' and
320 'daplink_*' attributes by reading from mbed.htm on the device
322 htm_target_id, daplink_info = self.
_read_htm_ids_read_htm_ids(device[
'mount_point'])
324 device.update({
"daplink_%s" % f.lower().replace(
' ',
'_'): v
325 for f, v
in daplink_info.items()})
327 logger.debug(
"Found htm target id, %s, for usb target id %s",
328 htm_target_id, device[
'target_id_usb_id'])
329 device[
'target_id'] = htm_target_id
331 logger.debug(
"Could not read htm on from usb id %s. "
332 "Falling back to usb id",
333 device[
'target_id_usb_id'])
334 device[
'target_id'] = device[
'target_id_usb_id']
335 device[
'target_id_mbed_htm'] = htm_target_id
337 def _update_device_details_atmel(self, device, _):
338 """ Updates the Atmel device information based on files from its 'mount_point'
339 @param device Dictionary containing device information
340 @param read_details_txt A boolean controlling the presense of the
341 output dict attributes read from other files present on the 'mount_point'
349 device[
'target_id'] = device[
'target_id_usb_id'][4:8]
350 platform_data = self.
plat_dbplat_db.get(device[
'target_id'],
354 device.update(platform_data
or {
"platform_name":
None})
357 """! Replace (or add if manufacture id doesn't exist) entry in self.manufacture_ids
358 @param oper '+' add new mock / override existing entry
359 '-' remove mid from mocking entry
360 @return Mocked structure (json format)
363 self.
plat_dbplat_db.add(mid, platform_name, permanent=
True)
365 self.
plat_dbplat_db.remove(mid, permanent=
True)
367 raise ValueError(
"oper can only be [+-]")
369 @deprecated("List formatting methods are deprecated for a simpler API. "
"Please use 'list_mbeds' instead.")
371 """! Creates list of all available mappings for target_id -> Platform
372 @return String with table formatted output
374 from prettytable
import PrettyTable, HEADER
376 columns = [
'target_id_prefix',
'platform_name']
377 pt = PrettyTable(columns, junction_char=
"|", hrules=HEADER)
381 for target_id_prefix, platform_name
in sorted(self.
plat_dbplat_db.items()):
382 pt.add_row([target_id_prefix, platform_name])
384 return pt.get_string()
387 """! Load retarget data from local file
388 @return Curent retarget configuration (dictionary)
397 except ValueError
as e:
402 """! Enable retargeting
403 @details Read data from local retarget configuration file
404 @return Retarget data structure read from configuration file
410 """! Returns simple dummy platform """
411 if not hasattr(self,
"dummy_counter"):
418 "platform_name": platform_name,
419 "platform_name_unique":
"%s[%d]"% (platform_name, self.
dummy_counterdummy_counter[platform_name]),
420 "mount_point":
"DUMMY",
421 "serial_port":
"DUMMY",
422 "target_id":
"DUMMY",
423 "target_id_mbed_htm":
"DUMMY",
424 "target_id_usb_id":
"DUMMY",
425 "daplink_version":
"DUMMY"
431 """! Return a dictionary of supported target ids and the corresponding platform name
432 @param device_type Filter which device entries are returned from the platform database
433 @return Dictionary of { 'target_id': 'platform_name', ... }
436 if device_type
is not None:
437 kwargs[
'device_type'] = device_type
439 items = self.
plat_dbplat_db.items(**kwargs)
440 return {i[0]: i[1]
for i
in items}
442 @deprecated("List formatting methods are deprecated to simplify the API. "
"Please use 'list_mbeds' instead.")
444 """! Useful if you just want to know which platforms are currently available on the system
445 @return List of (unique values) available platforms
449 for i, val
in enumerate(mbeds):
450 platform_name = str(val[
'platform_name'])
451 if platform_name
not in result:
452 result.append(platform_name)
455 @deprecated("List formatting methods are deprecated to simplify the API. "
"Please use 'list_mbeds' instead.")
457 """! Useful if you just want to know how many platforms of each type are currently available on the system
458 @return Dict of platform: platform_count
462 for i, val
in enumerate(mbeds):
463 platform_name = str(val[
'platform_name'])
464 if platform_name
not in result:
465 result[platform_name] = 1
467 result[platform_name] += 1
470 @deprecated("List formatting methods are deprecated to simplify the API. "
"Please use 'list_mbeds' instead.")
472 """! Get information about mbeds with extended parameters/info included
473 @return Returns dictionary where keys are TargetIDs and values are mbed structures
474 @details Ordered by target id (key: target_id).
478 for mbed
in mbed_list:
479 target_id = mbed[
'target_id']
480 result[target_id] = mbed
484 """! Object to string casting
486 @return Stringified class object should be prettytable formated string
490 @deprecated("List formatting methods are deprecated to simplify the API. "
"Please use 'list_mbeds' instead.")
491 def get_string(self, border=False, header=True, padding_width=1, sortby='platform_name'):
492 """! Printing with some sql table like decorators
493 @param border Table border visibility
494 @param header Table header visibility
495 @param padding_width Table padding
496 @param sortby Column used to sort results
497 @return Returns string which can be printed on console
499 from prettytable
import PrettyTable, HEADER
501 mbeds = self.
list_mbedslist_mbeds(unique_names=
True, read_details_txt=
True)
503 """ ['platform_name', 'mount_point', 'serial_port', 'target_id'] - columns generated from USB auto-detection
504 ['platform_name_unique', ...] - columns generated outside detection subsystem (OS dependent detection)
506 columns = [
'platform_name',
'platform_name_unique',
'mount_point',
'serial_port',
'target_id',
'daplink_version']
507 pt = PrettyTable(columns, junction_char=
"|", hrules=HEADER)
514 row.append(mbed[col]
if col
in mbed
and mbed[col]
else 'unknown')
516 result = pt.get_string(border=border, header=header, padding_width=padding_width, sortby=sortby)
521 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
523 """! Loads from file JSON formatted string to data structure
524 @return None if JSON can be loaded
527 with open(json_spec_filename)
as data_file:
529 return json.load(data_file)
530 except ValueError
as json_error_msg:
531 logger.error(
"Parsing file(%s): %s", json_spec_filename, json_error_msg)
533 except IOError
as fileopen_error_msg:
534 logger.warning(fileopen_error_msg)
537 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
542 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
547 def _read_htm_ids(self, mount_point):
548 """! Function scans mbed.htm to get information about TargetID.
549 @param mount_point mbed mount point (disk / drive letter)
550 @return Function returns targetID, in case of failure returns None.
551 @details Note: This function should be improved to scan variety of boards' mbed.htm files
555 for line
in self.
_htm_lines_htm_lines(mount_point):
559 result[
'version'], result[
'build'] = ver_bld
561 m = re.search(
r'url=([\w\d\:/\\\?\.=-_]+)', line)
563 result[
'url'] = m.group(1).strip()
564 return target_id, result
566 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
570 def _mbed_htm_comment_section_ver_build(self, line):
571 """! Check for Version and Build date of interface chip firmware im mbed.htm file
572 @return (version, build) tuple if successful, None if no info found
575 m = re.search(
r'^<!-- Version: (\d+) Build: ([\d\w: ]+) -->', line)
577 version_str, build_str = m.groups()
578 return (version_str.strip(), build_str.strip())
581 m = re.search(
r'^<!-- Version: (\d+) Build: ([\d\w: ]+) Git Commit SHA', line)
583 version_str, build_str = m.groups()
584 return (version_str.strip(), build_str.strip())
587 m = re.search(
r'^<!-- Version: ([\d+\.]+)\. build (\d+) -->', line)
589 version_str, build_str = m.groups()
590 return (version_str.strip(), build_str.strip())
593 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
597 def _htm_lines(self, mount_point):
599 mbed_htm_path = join(mount_point, self.
MBED_HTM_NAMEMBED_HTM_NAME)
600 with open(mbed_htm_path,
'r')
as f:
603 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
607 def _details_txt(self, mount_point):
608 """! Load DETAILS.TXT to dictionary:
611 Build: Aug 24 2015 17:06:30
612 Git Commit SHA: 27a236b9fe39c674a703c5c89655fbd26b8e27e1
617 # DAPLink Firmware - see https://mbed.com/daplink
618 Unique ID: 0240000029164e45002f0012706e0006f301000097969900
621 Automation allowed: 0
622 Daplink Mode: Interface
623 Interface Version: 0240
624 Git SHA: c765cbb590f57598756683254ca38b211693ae5e
626 USB Interfaces: MSD, CDC, HID
627 Interface CRC: 0x26764ebf
631 path_to_details_txt = os.path.join(mount_point, self.
DETAILS_TXT_NAMEDETAILS_TXT_NAME)
632 with open(path_to_details_txt,
'r')
as f:
636 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
640 def _parse_details(self, lines):
643 if not line.startswith(
'#'):
644 key, _, value = line.partition(
':')
646 result[key] = value.strip()
647 if 'Interface Version' in result:
648 result[
'Version'] = result[
'Interface Version']
651 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
655 def _target_id_from_htm(self, line):
656 """! Extract Target id from htm line.
657 @return Target id or None
660 m = re.search(
'\?code=([a-fA-F0-9]+)', line)
662 result = m.groups()[0]
663 logger.debug(
"Found target id %s in htm line %s", result, line)
666 m = re.search(
'\?auth=([a-fA-F0-9]+)', line)
668 result = m.groups()[0]
669 logger.debug(
"Found target id %s in htm line %s", result, line)
675 """! Check if a mount point is ready for file operations
677 return exists(path)
and isdir(path)
680 @deprecated("This method will be removed from the public API. "
"Please use 'list_mbeds' instead")
682 return MbedLsToolsBase._run_cli_process(cmd, shell)
685 def _run_cli_process(cmd, shell=True):
686 """! Runs command as a process and return stdout, stderr and ret code
687 @param cmd Command to execute
688 @return Tuple of (stdout, stderr, returncode)
690 from subprocess
import Popen, PIPE
692 p = Popen(cmd, shell=shell, stdout=PIPE, stderr=PIPE)
693 _stdout, _stderr = p.communicate()
694 return _stdout, _stderr, p.returncode