Merge debugger features into master branch (#306)

* Add Arduino debugger start session. (#285)

* Add Arduino debugger start session.

* Fix based on code review.

* Support debugger for az3166 (#302)

* fix  some debug issues:
1. findFile resolve multiple openocd.exe
2. openocd has two possible script folders : ../scripts and ../share/openocd/scripts
3. if output is undefined, the elf path is not right.

* revert unexpected change

* fix a issue when source file is change but elf is not compiled again.

* fix errors on mac

* fix tslint error

* Debug feature bug fix. (#303)

* 1. make the load for serialport native lazy
2. stop the debugger when early check fails
3. resolve the openocd/gdb from other package if current package doesn't contain them

* refine the output folder name.

* 1. use static getter method to implement lazy load

* fix tslint error

*  refact debug code (#305)

* 1. make the load for serialport native lazy
2. stop the debugger when early check fails
3. resolve the openocd/gdb from other package if current package doesn't contain them

* refine the output folder name.

* 1. use static getter method to implement lazy load

* fix tslint error

* 1. refact debug code for separate interface and target, interfaces are listed due to usb pid/vid, target is linked to target board, swd is the only transport currently supported.

* fix tslint error.

* refine the current board code from this._boardManager in debugmanager.

* Update usbmapping.json

typo

* fix the issue of package name and multiple pid match

* 1.add arduino zero board
2. change debug manager to configurator

* 1. for boards which support debug, check whether the expected debugger is connected.

* change tab size for package.json
feature/builder
Yaohai Zheng 7 years ago committed by GitHub
parent e3b9c5e45a
commit 6d7ed7d75a
  1. 6
      .editorconfig
  2. 66
      misc/debuggerUsbMapping.json
  3. 18
      misc/openOCDMapping.json
  4. 115
      misc/usbmapping.json
  5. 305
      package.json
  6. 18
      src/arduino/arduino.ts
  7. 2
      src/arduino/board.ts
  8. 5
      src/arduino/package.ts
  9. 7
      src/common/platform.ts
  10. 13
      src/common/sys/darwin.ts
  11. 9
      src/common/sys/linux.ts
  12. 8
      src/common/sys/win32.ts
  13. 8
      src/common/util.ts
  14. 174
      src/debug/configurator.ts
  15. 154
      src/debug/debuggerManager.ts
  16. 39
      src/deviceContext.ts
  17. 25
      src/extension.ts
  18. 279
      src/serialmonitor/serialportctrl.ts
  19. 10
      src/serialmonitor/usbDetector.ts

@ -6,9 +6,13 @@ root = true
# Tab indentation
[*]
indent_style = space
indent_size = 2
indent_size = 4
# The indent size used in the `package.json` file cannot be changed
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
[{.travis.yml,npm-shrinkwrap.json,package.json}]
indent_size = 2
[vendor/**.js]
indent_style = space
indent_size = 2

@ -0,0 +1,66 @@
[
{
"vid": "0483",
"pid": "3744",
"name": "ST Link V1.0",
"short_name": "stlink-1.0",
"config_file": "stlink-v1.cfg"
},
{
"vid": "0483",
"pid": "3748",
"name": "ST Link V2.0",
"short_name": "stlink-2.0",
"config_file": "stlink-v2.cfg"
},
{
"vid": "0483",
"pid": "374b",
"name": "ST Link V2.1",
"short_name": "stlink-2.1",
"config_file": "stlink-v2-1.cfg"
},
{
"pid": [
"0101",
"0102",
"0103",
"0104",
"0105",
"0106",
"0107",
"0108",
"0109",
"0110",
"0111",
"0112",
"0113",
"0114",
"0115",
"0116",
"0117",
"0118"
],
"vid": "1366",
"name": "JLink",
"short_name": "jlink",
"config_file": "jlink.cfg"
},
{
"vid": "03eb",
"pid": "2111",
"name": "CMSIS-DAP",
"short_name": "cmsis-dap",
"config_file": "cmsis-dap.cfg"
},
{
"vid": "03eb",
"pid": [
"2111",
"2157"
],
"name": "CMSIS-DAP",
"short_name": "cmsis-dap",
"config_file": "cmsis-dap.cfg"
}
]

@ -0,0 +1,18 @@
[
{
"board": "arduino:samd:mzero_pro_bl_dbg",
"interface": "interface/cmsis-dap.cfg",
"target": "target/at91samdXX.cfg"
},
{
"board": "arduino:samd:mzero_bl",
"interface": "interface/cmsis-dap.cfg",
"target": "target/at91samdXX.cfg"
},
{
"board": "AZ3166:stm32f4:MXCHIP_AZ3166",
"interface": "interface/stlink-v2-1.cfg",
"target": "target/stm32f4x.cfg"
}
]

@ -1,76 +1,147 @@
[{
[
{
"index_file": "package_index.json",
"boards": [{
"boards": [
{
"vid": "2341",
"pid": "804e",
"name": "Arduino/Genuino MKR1000",
"package": "arduino",
"architecture": "samd",
"id": "mkr1000"
}, {
"id": "mkr1000",
"target": "at91samdXX.cfg"
},
{
"vid": "8087",
"pid": "0a9e",
"name": "Intel Edison",
"package": "arduino",
"architecture": "samd",
"id": "edison"
}, {
},
{
"vid": "03eb",
"pid": "2111",
"name": "Arduino M0 Pro (Programming USB Port)",
"package": "arduino",
"architecture": "samd",
"id": "mzero_pro_bl_dbg",
"target": "at91samdXX.cfg",
"interface": "cmsis-dap.cfg"
},
{
"vid": "03eb",
"pid": "2157",
"name": "Arduino/Genuino Zero (Programming Port)",
"package": "arduino",
"architecture": "samd",
"id": "arduino_zero_edbg",
"target": "at91samdXX.cfg",
"interface": "cmsis-dap.cfg"
},
{
"vid": "2341",
"pid": ["804d", "004d", "824d"],
"name": "Arduino/Genuino Zero (Native USB Port)",
"package": "arduino",
"architecture": "samd",
"id": "arduino_zero_edbg",
"target": "at91samdXX.cfg",
"interface": "cmsis-dap.cfg"
},
{
"vid": "2a03",
"pid": "804f",
"name": "Arduino M0 Pro (Native USB Port)",
"package": "arduino",
"architecture": "samd",
"id": "mzero_pro_bl"
}, {
"id": "mzero_pro_bl",
"target": "at91samdXX.cfg"
},
{
"vid": "2341",
"pid": "8041",
"name": "Arduino Yún",
"package": "arduino",
"architecture": "avr",
"id": "yun"
}, {
},
{
"vid": "2341",
"pid": "003e",
"name": "Arduino Due",
"package": "arduino",
"architecture": "sam",
"id": "arduino_due_x"
}]
}, {
}
]
},
{
"index_file": "https://www.adafruit.com/package_adafruit_index.json",
"boards": [
{
"vid": "239a",
"pid": [
"0010",
"8010",
"0008"
],
"name": "Adafruit WICED Feather",
"package": "adafruit",
"architecture": "wiced",
"id": "feather",
"target": "stm32f2x.cfg"
}
]
},
{
"index_file": "http://arduino.esp8266.com/stable/package_esp8266com_index.json",
"boards": [{
"boards": [
{
"vid": "10c4",
"pid": "ea60",
"name": "Adafruit HUZZAH ESP8266",
"package": "esp8266",
"architecture": "esp8266",
"id": "huzzah"
}, {
},
{
"vid": "0403",
"pid": "6015",
"name": "SparkFun ESP8266 Thing Dev",
"package": "esp8266",
"architecture": "esp8266",
"id": "thingdev"
}]
}, {
}
]
},
{
"index_file": "https://adafruit.github.io/arduino-board-index/package_adafruit_index.json",
"boards": [{
"boards": [
{
"vid": "239a",
"pid": "800b",
"name": "Adafruit Feather M0",
"package": "adafruit",
"architecture": "samd",
"id": "adafruit_feather_m0"
}]
}, {
"id": "adafruit_feather_m0",
"target": "at91samdXX.cfg"
}
]
},
{
"index_file": "https://raw.githubusercontent.com/VSChina/azureiotdevkit_tools/master/package_azureboard_index.json",
"boards": [{
"boards": [
{
"vid": "0483",
"pid": "374b",
"name": "MXCHIP AZ3166",
"package": "AZ3166",
"architecture": "stm32f4",
"id": "MXCHIP_AZ3166"
}]
}]
"id": "MXCHIP_AZ3166",
"interface": "stlink-v2-1.cfg",
"target": "stm32f4x.cfg"
}
]
}
]

@ -12,6 +12,7 @@
"icon": "images/arduino.svg",
"categories": [
"Languages",
"Debuggers",
"Snippets",
"Formatters"
],
@ -87,6 +88,310 @@
"title": "Arduino: Examples"
}
],
"debuggers": [
{
"type": "arduino",
"label": "Arduino",
"startSessionCommand": "arduino.debug.startSession",
"enableBreakpointsFor": {
"languageIds": [
"c",
"cpp"
]
},
"configurationSnippets": [
{
"label": "Arduino: Launch Debugger",
"description": "Debug Arduino sketch",
"body": {
"name": "Arduino",
"type": "arduino",
"request": "launch",
"program": "$${{file}}",
"cwd": "$${{workspaceRoot}}",
"MIMode": "gdb",
"targetArchitecture": "arm",
"miDebuggerPath": "",
"debugServerPath": "",
"debugServerArgs": "",
"customLaunchSetupCommands": [
{
"text": "target remote localhost:3333"
},
{
"text": "file $${{file}}"
},
{
"text": "load"
},
{
"text": "monitor reset halt"
},
{
"text": "monitor reset init"
}
],
"stopAtEntry": true,
"serverStarted": "Info\\\\ :\\\\ [\\\\w\\\\d\\\\.]*:\\\\ hardware",
"launchCompleteCommand": "exec-continue",
"filterStderr": true,
"args": []
}
}
],
"initialConfigurations": [
{
"name": "Arduino",
"type": "arduino",
"request": "launch",
"program": "${file}",
"cwd": "${workspaceRoot}",
"MIMode": "gdb",
"targetArchitecture": "arm",
"miDebuggerPath": "",
"debugServerPath": "",
"debugServerArgs": "",
"customLaunchSetupCommands": [
{
"text": "target remote localhost:3333"
},
{
"text": "file ${file}"
},
{
"text": "load"
},
{
"text": "monitor reset halt"
},
{
"text": "monitor reset init"
}
],
"stopAtEntry": true,
"serverStarted": "Info\\ :\\ [\\w\\d\\.]*:\\ hardware",
"launchCompleteCommand": "exec-continue",
"filterStderr": true,
"args": []
}
],
"configurationAttributes": {
"launch": {
"required": [
"program"
],
"properties": {
"program": {
"type": "string",
"description": "Full path to program executable.",
"default": "${workspaceRoot}/arduino.elf"
},
"args": {
"type": "array",
"description": "Command line arguments passed to the program.",
"items": {
"type": "string"
},
"default": []
},
"type": {
"type": "string",
"description": "The type of the engine.",
"default": "arduino"
},
"targetArchitecture": {
"type": "string",
"description": "The architecture of the debuggee.",
"default": "arm"
},
"cwd": {
"type": "string",
"description": "The working directory of the target",
"default": "."
},
"setupCommands": {
"type": "array",
"description": "One or more GDB commands to execute in order to setup the underlying debugger. Example: \"setupCommands\": [ { \"text\": \"-enable-pretty-printing\", \"description\": \"Enable GDB pretty printing\", \"ignoreFailures\": true }].",
"items": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The debugger command to execute.",
"default": ""
},
"description": {
"type": "string",
"description": "Optional description for the command.",
"default": ""
},
"ignoreFailures": {
"type": "boolean",
"description": "If true, failures from the command should be ignored. Default value is false.",
"default": "false"
}
}
},
"default": []
},
"customLaunchSetupCommands": {
"type": "array",
"description": "If provided, this replaces the default commands used to launch a target with some other commands. For example, this can be \"-target-attach\" in order to attach to a target process. An empty command list replaces the launch commands with nothing, which can be useful if the debugger is being provided launch options as command line options. Example: \"customLaunchSetupCommands\": [ { \"text\": \"target-run\", \"description\": \"run target\", \"ignoreFailures\": false }].",
"items": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The debugger command to execute.",
"default": ""
},
"description": {
"type": "string",
"description": "Optional description for the command.",
"default": ""
},
"ignoreFailures": {
"type": "boolean",
"description": "If true, failures from the command should be ignored. Default value is false.",
"default": ""
}
}
},
"default": []
},
"launchCompleteCommand": {
"enum": [
"exec-run",
"exec-continue",
"None"
],
"description": "The command to execute after the debugger is fully setup in order to cause the target process to run. Allowed values are \"exec-run\", \"exec-continue\", \"None\". The default value is \"exec-run\".",
"default": "exec-run"
},
"visualizerFile": {
"type": "string",
"description": ".natvis file to be used when debugging this process. This option is not compatible with GDB pretty printing. Please also see \"showDisplayString\" if using this setting.",
"default": ""
},
"showDisplayString": {
"type": "boolean",
"description": "When a visualizerFile is specified, showDisplayString will enable the display string. Turning this option on can cause slower performance during debugging.",
"default": "true"
},
"additionalSOLibSearchPath": {
"type": "string",
"description": "Semicolon separated list of directories to use to search for .so files. Example: \"c:\\dir1;c:\\dir2\".",
"default": ""
},
"MIMode": {
"type": "string",
"description": "Indicates the console debugger that the MIDebugEngine will connect to. Allowed values are \"gdb\" \"lldb\".",
"default": "gdb"
},
"miDebuggerPath": {
"type": "string",
"description": "The path to the mi debugger (such as gdb). When unspecified, it will search path first for the debugger.",
"default": "/usr/bin/gdb"
},
"miDebuggerServerAddress": {
"type": "string",
"description": "Network address of the MI Debugger Server to connect to (example: localhost:1234).",
"default": "serveraddress:port"
},
"stopAtEntry": {
"type": "boolean",
"description": "Optional parameter. If true, the debugger should stop at the entrypoint of the target. If processId is passed, has no effect.",
"default": false
},
"debugServerPath": {
"type": "string",
"description": "Optional full path to debug server to launch. Defaults to null.",
"default": ""
},
"debugServerArgs": {
"type": "string",
"description": "Optional debug server args. Defaults to null.",
"default": ""
},
"serverStarted": {
"type": "string",
"description": "Optional server-started pattern to look for in the debug server output. Defaults to null.",
"default": ""
},
"filterStdout": {
"type": "boolean",
"description": "Search stdout stream for server-started pattern and log stdout to debug output. Defaults to true.",
"default": "true"
},
"filterStderr": {
"type": "boolean",
"description": "Search stderr stream for server-started pattern and log stderr to debug output. Defaults to false.",
"default": "false"
},
"serverLaunchTimeout": {
"type": "integer",
"description": "Optional time, in milliseconds, for the debugger to wait for the debugServer to start up. Default is 10000.",
"default": "10000"
},
"coreDumpPath": {
"type": "string",
"description": "Optional full path to a core dump file for the specified program. Defaults to null.",
"default": ""
},
"externalConsole": {
"type": "boolean",
"description": "If true, a console is launched for the debuggee. If false, no console is launched. Note this option is ignored in some cases for technical reasons.",
"default": "false"
},
"sourceFileMap": {
"type": "object",
"description": "Optional source file mappings passed to the debug engine. Example: '{ \"/original/source/path\":\"/current/source/path\" }'",
"default": {
"<source-path>": "<target-path>"
}
},
"logging": {
"type": "object",
"description": "Optional flags to determine what types of messages should be logged to the Debug Console.",
"default": {},
"properties": {
"exceptions": {
"type": "boolean",
"description": "Optional flag to determine whether exception messages should be logged to the Debug Console. Defaults to true.",
"default": true
},
"moduleLoad": {
"type": "boolean",
"description": "Optional flag to determine whether module load events should be logged to the Debug Console. Defaults to true.",
"default": true
},
"programOutput": {
"type": "boolean",
"description": "Optional flag to determine whether program output should be logged to the Debug Console. Defaults to true.",
"default": true
},
"engineLogging": {
"type": "boolean",
"description": "Optional flag to determine whether diagnostic engine logs should be logged to the Debug Console. Defaults to false.",
"default": false
},
"trace": {
"type": "boolean",
"description": "Optional flag to determine whether diagnostic adapter command tracing should be logged to the Debug Console. Defaults to false.",
"default": false
},
"traceResponse": {
"type": "boolean",
"description": "Optional flag to determine whether diagnostic adapter command and response tracing should be logged to the Debug Console. Defaults to false.",
"default": false
}
}
}
}
}
}
}
],
"keybindings": [
{
"command": "arduino.verify",

@ -133,7 +133,7 @@ export class ArduinoApp {
});
}
public async verify() {
public async verify(output: string = "") {
const dc = DeviceContext.getIntance();
const boardDescriptor = this.getBoardBuildString(dc);
if (!boardDescriptor) {
@ -157,12 +157,22 @@ export class ArduinoApp {
if (VscodeSettings.getIntance().logLevel === "verbose") {
args.push("--verbose");
}
if (output) {
const outputPath = path.join(vscode.workspace.rootPath, output);
args.push("--pref", `build.path=${outputPath}`);
}
arduinoChannel.show();
await util.spawn(this._settings.commandPath, arduinoChannel.channel, args).then((result) => {
// we need to return the result of verify
try {
const result = await util.spawn(this._settings.commandPath, arduinoChannel.channel, args);
arduinoChannel.end(`Finished verify sketch - ${dc.sketch}${os.EOL}`);
}, (reason) => {
return true;
} catch (reason) {
arduinoChannel.error(`Exit with code=${reason.code}${os.EOL}`);
});
return false;
}
}
// Add selected library path to the intellisense search path.

@ -132,7 +132,7 @@ export class Board implements IBoard {
return false;
}
private getPackageName() {
public getPackageName() {
return this.platform.packageName ? this.platform.packageName : this.platform.package.name;
}
}

@ -216,4 +216,9 @@ export interface IBoard {
* Upldate the configuration
*/
updateConfig(configId: string, optionId: string): boolean;
/**
* Get the board package name
*/
getPackageName();
}

@ -23,3 +23,10 @@ export function validateArduinoPath(arduinoPath: string): boolean {
export function findFile(fileName: string, cwd: string): string {
return internalSysLib.findFile(fileName, cwd);
}
export function getExecutableFileName(fileName: string): string {
if (isWindows) {
return `${fileName}.exe`;
}
return fileName;
}

@ -26,16 +26,17 @@ export function validateArduinoPath(arduinoPath: string): boolean {
}
export function findFile(fileName: string, cwd: string): string {
let result;
let pathString;
try {
result = childProcess.execSync("find ${cwd} -name ${fileName} -type f", { encoding: "utf8" });
result = path.resolve(result).trim();
pathString = childProcess.execSync(`find ${cwd} -name ${fileName} -type f`, { encoding: "utf8" }).split("\n");
if (fileExistsSync(result)) {
result = path.normalize(result);
if (pathString && pathString[0] && fileExistsSync(pathString[0].trim())) {
pathString = path.normalize(pathString[0].trim());
} else {
pathString = null;
}
} catch (ex) {
// Ignore the errors.
}
return result;
return pathString;
}

@ -29,11 +29,12 @@ export function validateArduinoPath(arduinoPath: string): boolean {
export function findFile(fileName: string, cwd: string): string {
let pathString;
try {
pathString = childProcess.execSync("find ${cwd} -name ${fileName} -type f", { encoding: "utf8" });
pathString = path.resolve(pathString).trim();
pathString = childProcess.execSync(`find ${cwd} -name ${fileName} -type f`, { encoding: "utf8" }).split("\n");
if (fileExistsSync(pathString)) {
pathString = path.normalize(pathString);
if (pathString && pathString[0] && fileExistsSync(pathString[0].trim())) {
pathString = path.normalize(pathString[0].trim());
} else {
pathString = null;
}
} catch (ex) {
// Ignore the errors.

@ -32,11 +32,9 @@ export function findFile(fileName: string, cwd: string): string {
let result;
try {
let pathString;
pathString = childProcess.execSync(`dir ${fileName} /S /B`, {encoding: "utf8", cwd});
pathString = path.resolve(pathString).trim();
if (fileExistsSync(pathString)) {
result = path.normalize(pathString);
pathString = childProcess.execSync(`dir ${fileName} /S /B`, {encoding: "utf8", cwd}).split("\n");
if (pathString && pathString[0] && fileExistsSync(pathString[0].trim())) {
result = path.normalize(pathString[0].trim());
}
} catch (ex) {
// Ignore the errors.

@ -295,7 +295,7 @@ export function padStart(sourceString: string, targetLength: number, padString?:
return sourceString;
}
if (!String.prototype.padStart) {
if (!(String.prototype as any).padStart) {
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
padString = String(padString || " ");
if (sourceString.length > targetLength) {
@ -308,7 +308,7 @@ export function padStart(sourceString: string, targetLength: number, padString?:
return padString.slice(0, targetLength) + sourceString;
}
} else {
return sourceString.padStart(targetLength, padString);
return (sourceString as any).padStart(targetLength, padString);
}
}
@ -365,3 +365,7 @@ export function getRegistryValues(hive: string, key: string, name: string): Prom
}
});
}
export function convertToHex(number, width = 0) {
return padStart(number.toString(16), width, "0");
}

@ -0,0 +1,174 @@
/*--------------------------------------------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*-------------------------------------------------------------------------------------------*/
import * as childProcess from "child_process";
import * as fs from "fs";
import * as path from "path";
import * as vscode from "vscode";
import { ArduinoApp } from "../arduino/arduino";
import { ArduinoSettings } from "../arduino/arduinoSettings";
import { BoardManager } from "../arduino/boardManager";
import * as platform from "../common/platform";
import * as util from "../common/util";
import { DeviceContext } from "../deviceContext";
import { DebuggerManager } from "./debuggerManager";
/**
* Automatically generate the Arduino board's debug settings.
*/
export class DebugConfigurator {
private _debuggerManager: DebuggerManager;
constructor(
private _extensionRoot: string,
private _arduinoApp: ArduinoApp,
private _arduinoSettings: ArduinoSettings,
private _boardManager: BoardManager,
) {
this._debuggerManager = new DebuggerManager(_extensionRoot, _arduinoSettings, _boardManager);
}
public async run(config) {
if (!this._debuggerManager.initialized) {
this._debuggerManager.initialize();
}
// Default settings:
if (!config.request) {
config = {
name: "Arduino",
type: "arduino",
request: "launch",
program: "${file}",
cwd: "${workspaceRoot}",
MIMode: "gdb",
logging: {
engineLogging: true,
},
targetArchitecture: "arm",
customLaunchSetupCommands: [
{
text: "target remote localhost:3333",
},
{
text: "file ${file}",
},
{
text: "load",
},
{
text: "monitor reset halt",
},
{
text: "monitor reset init",
},
],
stopAtEntry: true,
serverStarted: "Info\\ :\\ [\\w\\d\\.]*:\\ hardware",
launchCompleteCommand: "exec-continue",
filterStderr: true,
args: [],
};
}
if (!this.resolveOpenOcd(config)) {
return;
}
if (!await this.resolveOpenOcdOptions(config)) {
return;
}
if (!this.resolveDebuggerPath(config)) {
return;
}
if (!await this.resolveProgramPath(config)) {
return;
}
// Use the C++ debugger MIEngine as the real internal debugger
config.type = "cppdbg";
vscode.commands.executeCommand("vscode.startDebug", config);
}
private async resolveProgramPath(config) {
const dc = DeviceContext.getIntance();
if (!config.program || config.program === "${file}") {
// make a unique temp folder because keeping same temp folder will corrupt the build when board is changed
const outputFolder = path.join(dc.output || `.build`, this._boardManager.currentBoard.board);
util.mkdirRecursivelySync(path.join(vscode.workspace.rootPath, outputFolder));
config.program = path.join(vscode.workspace.rootPath, outputFolder, `${path.basename(dc.sketch)}.elf`);
// always compile elf to make sure debug the right elf
if (!await this._arduinoApp.verify(outputFolder)) {
vscode.window.showErrorMessage("Failure to verify the program, please check output for details.");
return false;
}
config.program = config.program.replace(/\\/g, "/");
config.customLaunchSetupCommands.forEach((obj) => {
if (obj.text && obj.text.indexOf("${file}") > 0) {
obj.text = obj.text.replace(/\$\{file\}/, config.program);
}
});
}
if (!util.fileExistsSync(config.program)) {
vscode.window.showErrorMessage("Cannot find the elf file.");
return false;
}
return true;
}
private resolveDebuggerPath(config) {
if (!config.miDebuggerPath) {
config.miDebuggerPath = platform.findFile(platform.getExecutableFileName("arm-none-eabi-gdb"),
path.join(this._arduinoSettings.packagePath, "packages", this._boardManager.currentBoard.getPackageName()));
}
if (!util.fileExistsSync(config.miDebuggerPath)) {
config.miDebuggerPath = this._debuggerManager.miDebuggerPath;
}
if (!util.fileExistsSync(config.miDebuggerPath)) {
vscode.window.showErrorMessage("Cannot find the debugger path.");
return false;
}
return true;
}
private resolveOpenOcd(config) {
const dc = DeviceContext.getIntance();
if (!config.debugServerPath) {
config.debugServerPath = platform.findFile(platform.getExecutableFileName("openocd"),
path.join(this._arduinoSettings.packagePath, "packages",
this._boardManager.currentBoard.getPackageName()));
}
if (!util.fileExistsSync(config.debugServerPath)) {
config.debugServerPath = this._debuggerManager.debugServerPath;
}
if (!util.fileExistsSync(config.debugServerPath)) {
vscode.window.showErrorMessage("Cannot find the OpenOCD from the launch.json debugServerPath property." +
"Please input the right path of OpenOCD");
return false;
}
return true;
}
private async resolveOpenOcdOptions(config) {
if (config.debugServerPath && !config.debugServerArgs) {
try {
config.debugServerArgs = await this._debuggerManager.resolveOpenOcdOptions(config);
if (!config.debugServerArgs) {
return false;
}
} catch (error) {
vscode.window.showErrorMessage(error.message);
return false;
}
}
return true;
}
}

@ -0,0 +1,154 @@
import * as fs from "fs";
import * as path from "path";
import * as vscode from "vscode";
import { ArduinoSettings } from "../arduino/arduinoSettings";
import { BoardManager } from "../arduino/boardManager";
import * as platform from "../common/platform";
import * as util from "../common/util";
import { DeviceContext } from "../deviceContext";
export class DebuggerManager {
private _initialized: boolean = false;
private _usbDector;
private _debugServerPath: string;
private _miDebuggerPath: string;
private _debuggerMappings: any = {};
private _debuggerBoardMappings: any = {};
constructor(
private _extensionRoot: string,
private _arduinoSettings: ArduinoSettings,
private _boardManager: BoardManager) {
}
public initialize() {
const debugFileContent = fs.readFileSync(path.join(this._extensionRoot, "misc", "debuggerUsbMapping.json"), "utf8");
const usbFileContent = fs.readFileSync(path.join(this._extensionRoot, "misc", "usbmapping.json"), "utf8");
for (const _debugger of JSON.parse(debugFileContent)) {
if (Array.isArray(_debugger.pid)) {
for (const pid of _debugger.pid) {
this._debuggerMappings[`${pid}%${_debugger.vid}`] = { ..._debugger, pid, vid: _debugger.vid };
}
} else {
this._debuggerMappings[`${_debugger.pid}%${_debugger.vid}`] = { ..._debugger, pid: _debugger.pid, vid: _debugger.vid };
}
}
for (const config of JSON.parse(usbFileContent)) {
for (const board of config.boards) {
if (board.interface || board.target) {
this._debuggerBoardMappings[[board.package, board.architecture, board.id].join(":")] = board;
}
}
}
this._usbDector = require("../../../vendor/node-usb-native").detector;
this._debugServerPath = platform.findFile(platform.getExecutableFileName("openocd"),
path.join(this._arduinoSettings.packagePath, "packages"));
if (!util.fileExistsSync(this._debugServerPath)) {
this._debugServerPath = "";
}
this._miDebuggerPath = platform.findFile(platform.getExecutableFileName("arm-none-eabi-gdb"),
path.join(this._arduinoSettings.packagePath, "packages"));
if (!util.fileExistsSync(this._miDebuggerPath)) {
this._miDebuggerPath = "";
}
this._initialized = true;
}
public get initialized(): boolean {
return this._initialized;
}
public get miDebuggerPath(): string {
return this._miDebuggerPath;
}
public get debugServerPath(): string {
return this._debugServerPath;
}
public async listDebuggers(): Promise<any[]> {
const usbDeviceList = await this._usbDector.find();
const keys = [];
const results = [];
usbDeviceList.forEach((device) => {
if (device.vendorId && device.productId) {
/* tslint:disable:max-line-length*/
const key = util.convertToHex(device.productId, 4) + "%" + util.convertToHex(device.vendorId, 4);
const relatedDebugger = this._debuggerMappings[key];
if (relatedDebugger && keys.indexOf(key) < 0) {
keys.push(key);
results.push(relatedDebugger);
}
}
});
return results;
}
public async resolveOpenOcdOptions(config): Promise<string> {
const board = this._boardManager.currentBoard.key;
const debugConfig = this._debuggerBoardMappings[board];
const dc = DeviceContext.getIntance();
const debuggerConfiged: string = dc.debugger_;
if (!debugConfig) {
throw new Error(`Debug for board ${this._boardManager.currentBoard.name} is not supported by now.`);
}
let resolvedDebugger;
const debuggers = await this.listDebuggers();
if (!debuggers.length) {
throw new Error(`No supported debuggers are connected.`);
}
// rule 1: if this board has debuggers, use its own debugger
if (debugConfig.interface) {
resolvedDebugger = debuggers.find((_debugger) => {
return _debugger.short_name === debugConfig.interface || _debugger.config_file === debugConfig.interface;
});
if (!resolvedDebugger) {
throw new Error(`Debug port for board ${this._boardManager.currentBoard.name} is not connected.`);
}
}
// rule 2: if there is only one debugger, use the only debugger
if (!resolvedDebugger && !debuggerConfiged && debuggers.length === 1) {
resolvedDebugger = debuggers[0];
}
// rule 3: if there is any configuration about debugger, use this configuration
if (!resolvedDebugger && debuggerConfiged) {
resolvedDebugger = debuggers.find((_debugger) => {
return _debugger.short_name === debuggerConfiged || _debugger.config_file === debuggerConfiged;
});
}
if (!resolvedDebugger) {
const chosen = await vscode.window.showQuickPick(<vscode.QuickPickItem[]>debuggers.map((l): vscode.QuickPickItem => {
return {
description: `(0x${l.vid}:0x${l.pid})`,
label: l.name,
};
}).sort((a, b): number => {
return a.label === b.label ? 0 : (a.label > b.label ? 1 : -1);
}), { placeHolder: "Select a debugger" });
if (chosen && chosen.label) {
resolvedDebugger = debuggers.find((_debugger) => _debugger.name === chosen.label);
if (resolvedDebugger) {
dc.debugger_ = resolvedDebugger.config_file;
}
}
if (!resolvedDebugger) {
return "";
}
}
const debugServerPath = config.debugServerPath;
let scriptsFolder = path.join(path.dirname(debugServerPath), "../scripts/");
if (!util.directoryExistsSync(scriptsFolder)) {
scriptsFolder = path.join(path.dirname(debugServerPath), "../share/openocd/scripts/");
}
if (!util.directoryExistsSync(scriptsFolder)) {
throw new Error("Cannot find scripts folder from openocd.");
}
if (resolvedDebugger.config_file.includes("jlink")) {
// only swd is supported now
return `-s ${scriptsFolder} -f interface/${resolvedDebugger.config_file} -c "transport select swd" -f target/${debugConfig.target}`;
}
return `-s ${scriptsFolder} -f interface/${resolvedDebugger.config_file} -f target/${debugConfig.target}`;
}
}

@ -38,6 +38,17 @@ export interface IDeviceContext {
*/
sketch: string;
/**
* Arduino build output path
*/
output: string;
/**
* Arduino debugger
*/
debugger_: string;
/**
* Arduino custom board configuration
* @property {string}
@ -65,6 +76,10 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable {
private _sketch: string;
private _output: string;
private _debugger: string;
private _configuration: string;
private _arduinoApp: ArduinoApp;
@ -133,6 +148,8 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable {
this._board = deviceConfigJson.board;
this._sketch = deviceConfigJson.sketch;
this._configuration = deviceConfigJson.configuration;
this._output = deviceConfigJson.output;
this._debugger = deviceConfigJson._debugger;
this._onDidChange.fire();
} else {
Logger.notifyUserError("arduinoFileError", new Error(constants.messages.ARDUINO_FILE_ERROR));
@ -142,6 +159,8 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable {
this._board = null;
this._sketch = null;
this._configuration = null;
this._output = null;
this._debugger = null;
this._onDidChange.fire();
}
return this;
@ -164,6 +183,8 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable {
deviceConfigJson.sketch = this.sketch;
deviceConfigJson.port = this.port;
deviceConfigJson.board = this.board;
deviceConfigJson.output = this.output;
deviceConfigJson._debugger = this.debugger_;
deviceConfigJson.configuration = this.configuration;
util.mkdirRecursivelySync(path.dirname(deviceConfigFile));
@ -201,6 +222,24 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable {
this.saveContext();
}
public get output() {
return this._output;
}
public set output(value: string) {
this._output = value;
this.saveContext();
}
public get debugger_() {
return this._debugger;
}
public set debugger_(value: string) {
this._debugger = value;
this.saveContext();
}
public get configuration() {
return this._configuration;
}

@ -13,6 +13,7 @@ import { BoardManager } from "./arduino/boardManager";
import { ExampleManager } from "./arduino/exampleManager";
import { LibraryManager } from "./arduino/libraryManager";
import { ARDUINO_MANAGER_PROTOCOL, ARDUINO_MODE, BOARD_CONFIG_URI, BOARD_MANAGER_URI, EXAMPLES_URI, LIBRARY_MANAGER_URI } from "./common/constants";
import { DebugConfigurator } from "./debug/configurator";
import { DeviceContext } from "./deviceContext";
import { CompletionProvider } from "./langService/completionProvider";
import * as Logger from "./logger/logger";
@ -128,7 +129,10 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(registerCommand("arduino.verify", async () => {
if (!status.compile) {
status.compile = "verify";
await arduinoApp.verify();
try {
await arduinoApp.verify();
} catch (ex) {
}
delete status.compile;
}
}, () => {
@ -138,7 +142,10 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(registerCommand("arduino.upload", async () => {
if (!status.compile) {
status.compile = "upload";
await arduinoApp.upload();
try {
await arduinoApp.upload();
} catch (ex) {
}
delete status.compile;
}
},
@ -148,6 +155,20 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(registerCommand("arduino.addLibPath", (path) => arduinoApp.addLibPath(path)));
const arduinoConfigurator = new DebugConfigurator(context.extensionPath, arduinoApp, arduinoSettings, boardManager);
// Arduino debugger
context.subscriptions.push(registerCommand("arduino.debug.startSession", async (config) => {
if (!status.debug) {
status.debug = "debug";
try {
await arduinoConfigurator.run(config);
} catch (ex) {
}
delete status.debug;
}
}));
// serial monitor commands
const serialMonitor = SerialMonitor.getIntance();
context.subscriptions.push(serialMonitor);

@ -7,158 +7,165 @@ import * as os from "os";
import { OutputChannel, QuickPickItem, StatusBarAlignment, StatusBarItem, window } from "vscode";
interface ISerialPortDetail {
comName: string;
manufacturer: string;
vendorId: string;
productId: string;
comName: string;
manufacturer: string;
vendorId: string;
productId: string;
}
export class SerialPortCtrl {
public static list(): Promise<ISerialPortDetail[]> {
return new Promise((resolve, reject) => {
SerialPortCtrl.serialport.list((e: any, ports: ISerialPortDetail[]) => {
if (e) {
reject(e);
} else {
resolve(ports);
}
});
});
public static get serialport(): any {
if (!SerialPortCtrl._serialport) {
SerialPortCtrl._serialport = require("../../../vendor/node-usb-native").SerialPort;
}
return SerialPortCtrl._serialport;
}
private static serialport = require("../../../vendor/node-usb-native").SerialPort;
public static list(): Promise<ISerialPortDetail[]> {
return new Promise((resolve, reject) => {
SerialPortCtrl.serialport.list((e: any, ports: ISerialPortDetail[]) => {
if (e) {
reject(e);
} else {
resolve(ports);
}
});
});
}
private _currentPort: string;
private _currentBaudRate: number;
private _currentSerialPort = null;
private static _serialport: any;
public constructor(port: string, baudRate: number, private _outputChannel: OutputChannel) {
this._currentBaudRate = baudRate;
this._currentPort = port;
}
private _currentPort: string;
private _currentBaudRate: number;
private _currentSerialPort = null;
public get isActive(): boolean {
return this._currentSerialPort && this._currentSerialPort.isOpen();
}
public constructor(port: string, baudRate: number, private _outputChannel: OutputChannel) {
this._currentBaudRate = baudRate;
this._currentPort = port;
}
public get currentPort(): string {
return this._currentPort;
}
public get isActive(): boolean {
return this._currentSerialPort && this._currentSerialPort.isOpen();
}
public get currentPort(): string {
return this._currentPort;
}
public open(): Promise<any> {
this._outputChannel.appendLine(`[Starting] Opening the serial port - ${this._currentPort}`);
return new Promise((resolve, reject) => {
if (this._currentSerialPort && this._currentSerialPort.isOpen()) {
this._currentSerialPort.close((err) => {
if (err) {
return reject(err);
}
this._currentSerialPort = null;
return this.open().then(() => {
resolve();
}, (error) => {
reject(error);
});
});
public open(): Promise<any> {
this._outputChannel.appendLine(`[Starting] Opening the serial port - ${this._currentPort}`);
return new Promise((resolve, reject) => {
if (this._currentSerialPort && this._currentSerialPort.isOpen()) {
this._currentSerialPort.close((err) => {
if (err) {
return reject(err);
}
this._currentSerialPort = null;
return this.open().then(() => {
resolve();
}, (error) => {
reject(error);
});
});
} else {
this._currentSerialPort = new SerialPortCtrl.serialport(this._currentPort, { baudRate: this._currentBaudRate });
this._outputChannel.show();
this._currentSerialPort.on("open", () => {
this._currentSerialPort.write("TestingOpen", (err) => {
// TODO: Fix this on the serial port lib: https://github.com/EmergingTechnologyAdvisors/node-serialport/issues/795
if (err && !(err.message.indexOf("Writing to COM port (GetOverlappedResult): Unknown error code 121") >= 0)) {
this._outputChannel.appendLine(`[Error] Failed to open the serial port - ${this._currentPort}`);
reject(err);
} else {
this._currentSerialPort = new SerialPortCtrl.serialport(this._currentPort, { baudRate: this._currentBaudRate });
this._outputChannel.show();
this._currentSerialPort.on("open", () => {
this._currentSerialPort.write("TestingOpen", (err) => {
// TODO: Fix this on the serial port lib: https://github.com/EmergingTechnologyAdvisors/node-serialport/issues/795
if (err && !(err.message.indexOf("Writing to COM port (GetOverlappedResult): Unknown error code 121") >= 0)) {
this._outputChannel.appendLine(`[Error] Failed to open the serial port - ${this._currentPort}`);
reject(err);
} else {
this._outputChannel.appendLine(`[Info] Opened the serial port - ${this._currentPort}`);
resolve();
}
});
});
this._currentSerialPort.on("data", (_event) => {
this._outputChannel.append(_event.toString());
});
this._currentSerialPort.on("error", (_error) => {
this._outputChannel.appendLine("[Error]" + _error.toString());
});
this._outputChannel.appendLine(`[Info] Opened the serial port - ${this._currentPort}`);
resolve();
}
});
});
}
public sendMessage(text: string): Promise<any> {
return new Promise((resolve, reject) => {
if (!text || !this._currentSerialPort || !this.isActive) {
resolve();
return;
}
this._currentSerialPort.write(text, (error) => {
if (!error) {
resolve();
} else {
return reject(error);
}
});
this._currentSerialPort.on("data", (_event) => {
this._outputChannel.append(_event.toString());
});
}
public changePort(newPort: string): Promise<any> {
return new Promise((resolve, reject) => {
if (newPort === this._currentPort) {
resolve();
return;
}
this._currentPort = newPort;
if (!this._currentSerialPort || !this.isActive) {
resolve();
return;
}
this._currentSerialPort.close((err) => {
if (err) {
reject(err);
} else {
this._currentSerialPort = null;
resolve();
}
});
this._currentSerialPort.on("error", (_error) => {
this._outputChannel.appendLine("[Error]" + _error.toString());
});
}
}
});
}
public stop(): Promise<any> {
return new Promise((resolve, reject) => {
if (!this._currentSerialPort || !this.isActive) {
resolve(false);
return;
}
this._currentSerialPort.close((err) => {
if (this._outputChannel) {
this._outputChannel.appendLine(`[Done] Closed the serial port ${os.EOL}`);
}
this._currentSerialPort = null;
if (err) {
reject(err);
} else {
resolve(true);
}
});
});
}
public changeBaudRate(newRate: number): Promise<any> {
return new Promise((resolve, reject) => {
this._currentBaudRate = newRate;
if (!this._currentSerialPort || !this.isActive) {
resolve();
return;
}
this._currentSerialPort.update({ baudRate: this._currentBaudRate }, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
public sendMessage(text: string): Promise<any> {
return new Promise((resolve, reject) => {
if (!text || !this._currentSerialPort || !this.isActive) {
resolve();
return;
}
this._currentSerialPort.write(text, (error) => {
if (!error) {
resolve();
} else {
return reject(error);
}
});
});
}
public changePort(newPort: string): Promise<any> {
return new Promise((resolve, reject) => {
if (newPort === this._currentPort) {
resolve();
return;
}
this._currentPort = newPort;
if (!this._currentSerialPort || !this.isActive) {
resolve();
return;
}
this._currentSerialPort.close((err) => {
if (err) {
reject(err);
} else {
this._currentSerialPort = null;
resolve();
}
});
});
}
public stop(): Promise<any> {
return new Promise((resolve, reject) => {
if (!this._currentSerialPort || !this.isActive) {
resolve(false);
return;
}
this._currentSerialPort.close((err) => {
if (this._outputChannel) {
this._outputChannel.appendLine(`[Done] Closed the serial port ${os.EOL}`);
}
this._currentSerialPort = null;
if (err) {
reject(err);
} else {
resolve(true);
}
});
});
}
public changeBaudRate(newRate: number): Promise<any> {
return new Promise((resolve, reject) => {
this._currentBaudRate = newRate;
if (!this._currentSerialPort || !this.isActive) {
resolve();
return;
}
this._currentSerialPort.update({ baudRate: this._currentBaudRate }, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
}

@ -16,7 +16,7 @@ import { SerialMonitor } from "./serialMonitor";
export class UsbDetector {
private _usbDector: null;
private _usbDector;
private _boardDescriptors = null;
@ -39,8 +39,8 @@ export class UsbDetector {
this._usbDector.on("add", (device) => {
if (device.vendorId && device.productId) {
const deviceDescriptor = this.getUsbDeviceDescriptor(
util.padStart(device.vendorId.toString(16), 4, "0"), // vid and pid both are 2 bytes long.
util.padStart(device.productId.toString(16), 4, "0"),
util.convertToHex(device.vendorId, 4), // vid and pid both are 2 bytes long.
util.convertToHex(device.productId, 4),
this._extensionRoot);
// Not supported device for discovery.
@ -79,7 +79,7 @@ export class UsbDetector {
const currBoard = this._boardManager.currentBoard;
if (currBoard.board !== deviceDescriptor.id
|| currBoard.platform.architecture !== deviceDescriptor.architecture
|| currBoard.platform.package.name !== deviceDescriptor.package) {
|| currBoard.getPackageName() !== deviceDescriptor.package) {
vscode.window.showInformationMessage(`Detected board ${deviceDescriptor.name}. Would you like to switch to this board type?`,
"Yes", "No")
.then((ans) => {
@ -134,7 +134,7 @@ export class UsbDetector {
});
}
return this._boardDescriptors.find((obj) => {
return obj.vid === vendorId && obj.pid === productId;
return obj.vid === vendorId && (obj.pid === productId || (obj.pid.indexOf && obj.pid.indexOf(productId) >= 0));
});
}
}

Loading…
Cancel
Save