From 9aa405a16d7aeeb3b9ecbd0ea568c3f05379e7e3 Mon Sep 17 00:00:00 2001 From: Jason Tranchida Date: Mon, 26 Oct 2020 19:17:50 -0700 Subject: [PATCH 1/9] Improved handling of programmer selection - Selected programmer is now saved to and loaded from the arduino.json file - Arduino.json is monitored for changes, and changing file will update selected programmer & ui - Programmer selection UI now shows both the friendly name of the programmer, as well as the arduino name - Minor fix to deviceContexts to fire change events after all states are modified - Layed groundwork to support querying list of programmers for the current board from arduino toolchain --- src/arduino/programmerManager.ts | 122 ++++++++++++------------------- src/deviceContext.ts | 6 +- 2 files changed, 49 insertions(+), 79 deletions(-) diff --git a/src/arduino/programmerManager.ts b/src/arduino/programmerManager.ts index a060f211..d5dbdf98 100644 --- a/src/arduino/programmerManager.ts +++ b/src/arduino/programmerManager.ts @@ -4,39 +4,40 @@ import { DeviceContext } from "../deviceContext"; import { ArduinoApp } from "./arduino"; import { IArduinoSettings } from "./arduinoSettings"; -export enum ProgrammerList { - "AVR ISP", - "AVRISP mkII", - "USBtinyISP", - "ArduinoISP", - "ArduinoISP.org", - "USBasp", - "Parallel Programmer", - "Arduino as ISP", - "Arduino Gemma", - "BusPirate as ISP", - "Atmel STK500 development board", - "Atmel JTAGICE3 (ISP mode)", - "Atmel JTAGICE3 (JTAG mode)", - "Atmel-ICE (AVR)", -} - export class ProgrammerManager { - - private static _programmerManager: ProgrammerManager = null; - - private _currentprogrammer: ProgrammerList; - private _programmervalue: string; private _programmerStatusBar: vscode.StatusBarItem; + // Static list of 'available' programmers. This should be repopulated by the currently selected board type. + private _availableProgrammers = { + "arduino:avrisp": "AVR ISP", + "arduino:avrispmkii": "AVRISP mkII", + "arduino:usbtinyisp": "USBtinyISP", + "arduino:arduinoisp": "ArduinoISP", + "arduino:usbasp": "USBasp", + "arduino:parallel": "Parallel Programmer", + "arduino:arduinoasisp": "Arduino as ISP", + "arduino:usbGemma": "Arduino Gemma", + "arduino:buspirate": "BusPirate as ISP", + "arduino:stk500": "Atmel STK500 development board", + "arduino:jtag3isp": "Atmel JTAGICE3 (ISP mode)", + "arduino:jtag3": "Atmel JTAGICE3 (JTAG mode)", + "arduino:atmel_ice": "Atmel-ICE (AVR)", + }; + constructor(private _settings: IArduinoSettings, private _arduinoApp: ArduinoApp) { - this._programmerStatusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, constants.statusBarPriority.PROGRAMMER); + this._programmerStatusBar = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, + constants.statusBarPriority.PROGRAMMER, + ); this._programmerStatusBar.command = "arduino.selectProgrammer"; this._programmerStatusBar.tooltip = "Select Programmer"; - this._programmerStatusBar.text = ""; + } + + private getFriendlyName(programmer: string): string { + const friendlyName = this._availableProgrammers[programmer]; + return friendlyName ? friendlyName : programmer; } } diff --git a/src/deviceContext.ts b/src/deviceContext.ts index 2e7f18e3..5e24aea1 100644 --- a/src/deviceContext.ts +++ b/src/deviceContext.ts @@ -151,9 +151,9 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable { this._configuration = deviceConfigJson.configuration; this._output = deviceConfigJson.output; this._debugger = deviceConfigJson["debugger"]; - this._onDidChange.fire(); this._prebuild = deviceConfigJson.prebuild; this._programmer = deviceConfigJson.programmer; + this._onDidChange.fire(); } else { Logger.notifyUserError("arduinoFileError", new Error(constants.messages.ARDUINO_FILE_ERROR)); } @@ -164,9 +164,9 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable { this._configuration = null; this._output = null; this._debugger = null; - this._onDidChange.fire(); this._prebuild = null; this._programmer = null; + this._onDidChange.fire(); } return this; }, (reason) => { @@ -182,9 +182,9 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable { this._configuration = null; this._output = null; this._debugger = null; - this._onDidChange.fire(); this._prebuild = null; this._programmer = null; + this._onDidChange.fire(); return this; }); From 5a1ce01dd13f7108b6d613092b96b3f26a3b0dba Mon Sep 17 00:00:00 2001 From: Jason Tranchida Date: Thu, 29 Oct 2020 18:29:21 -0700 Subject: [PATCH 2/9] Parse the list of programmers from packages --- src/arduino/boardManager.ts | 28 +++++++++++++++- src/arduino/package.ts | 32 ++++++++++++++++++ src/arduino/programmer.ts | 67 +++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/arduino/programmer.ts diff --git a/src/arduino/boardManager.ts b/src/arduino/boardManager.ts index d8267afa..eb28ae65 100644 --- a/src/arduino/boardManager.ts +++ b/src/arduino/boardManager.ts @@ -13,7 +13,8 @@ import { DeviceContext } from "../deviceContext"; import { ArduinoApp } from "./arduino"; import { IArduinoSettings } from "./arduinoSettings"; import { parseBoardDescriptor } from "./board"; -import { IBoard, IPackage, IPlatform } from "./package"; +import { IBoard, IPackage, IPlatform, IProgrammer } from "./package"; +import { parseProgrammerDescriptor } from "./programmer"; import { VscodeSettings } from "./vscodeSettings"; export class BoardManager { @@ -22,6 +23,8 @@ export class BoardManager { private _platforms: IPlatform[]; + private _programmers: Map; + private _installedPlatforms: IPlatform[]; private _boards: Map; @@ -69,6 +72,7 @@ export class BoardManager { // Load all supported boards type. this.loadInstalledBoards(); + this.loadInstalledProgrammers(); this.updateStatusBar(); this._boardConfigStatusBar.show(); @@ -140,6 +144,10 @@ export class BoardManager { return this._boards; } + public get installedProgrammers(): Map { + return this._programmers; + } + public get currentBoard(): IBoard { return this._currentBoard; } @@ -243,6 +251,7 @@ export class BoardManager { this._installedPlatforms.push(existingPlatform); } this.loadInstalledBoardsFromPlatform(existingPlatform); + this.loadInstalledProgrammersFromPlatform(existingPlatform); } } } @@ -396,6 +405,23 @@ export class BoardManager { } } + private loadInstalledProgrammers(): void { + this._programmers = new Map(); + this._installedPlatforms.forEach((plat) => { + this.loadInstalledProgrammersFromPlatform(plat); + }); + } + + private loadInstalledProgrammersFromPlatform(plat: IPlatform) { + if (util.fileExistsSync(path.join(plat.rootBoardPath, "programmers.txt"))) { + const programmersContent = fs.readFileSync(path.join(plat.rootBoardPath, "programmers.txt"), "utf8"); + const res = parseProgrammerDescriptor(programmersContent, plat); + res.forEach((prog) => { + this._programmers.set(prog.key, prog); + }); + } + } + private listBoards(): IBoard[] { const result = []; this._boards.forEach((b) => { diff --git a/src/arduino/package.ts b/src/arduino/package.ts index 09e1144c..0d74882a 100644 --- a/src/arduino/package.ts +++ b/src/arduino/package.ts @@ -220,3 +220,35 @@ export interface IBoard { */ getPackageName(); } + +/** + * Interface for classes that represent an Arduino supported programmer. + * + * @interface + */ +export interface IProgrammer { + + /** + * Unique key that represent the programmer in the package:name. + * @property {string} + */ + key: string; + + /** + * Programmer name for Arduino compilation such as `avrisp`, `atmel_ice` + * @property {string} + */ + name: string; + + /** + * The human readable name displayed in the Arduino programmer selection menu + * @property {string} + */ + displayName: string; + + /** + * Reference to the platform that contains this board. + * @prop {IPlatform} + */ + platform: IPlatform; +} diff --git a/src/arduino/programmer.ts b/src/arduino/programmer.ts new file mode 100644 index 00000000..d2fdceb0 --- /dev/null +++ b/src/arduino/programmer.ts @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { IPlatform, IProgrammer } from "./package"; + +export function parseProgrammerDescriptor(programmerDescriptor: string, plat: IPlatform): Map { + const progrmmerLineRegex = /([^\.]+)\.(\S+)=(.+)/; + + const result = new Map(); + const lines = programmerDescriptor.split(/[\r|\r\n|\n]/); + const menuMap = new Map(); + + lines.forEach((line) => { + // Ignore comments. + if (line.startsWith("#")) { + return; + } + + const match = progrmmerLineRegex.exec(line); + if (match && match.length > 3) { + let programmer = result.get(match[1]); + if (!programmer) { + programmer = new Programmer(match[1], plat); + result.set(programmer.name + , programmer); + } + if (match[2] === "name") { + programmer.displayName = match[3].trim(); + } + } + }); + return result; +} + +export class Programmer implements IProgrammer { + constructor(private _name: string, + private _platform: IPlatform, + private _displayName: string = _name) { + } + + public get name(): string { + return this._name; + } + + public get platform(): IPlatform { + return this._platform; + } + + public get displayName(): string { + return this._displayName; + } + + public set displayName(value: string) { + this._displayName = value; + } + + /** + * @returns {string} Return programmer key in format packageName:name + */ + public get key() { + return `${this.getPackageName}:${this.name}`; + } + + private get getPackageName(): string { + return this.platform.packageName ? this.platform.packageName : this.platform.package.name; + } +} From 539d97a2a267e2a5526b4aa4edeea2c7be2d7b50 Mon Sep 17 00:00:00 2001 From: Jason Tranchida Date: Thu, 29 Oct 2020 18:30:43 -0700 Subject: [PATCH 3/9] Tests for parsing programmers --- test/boardmanager.test.ts | 31 +++++ .../hardware/arduino/avr/programmers.txt | 115 ++++++++++++++++++ .../hardware/esp8266/esp8266/programmers.txt | 14 +++ 3 files changed, 160 insertions(+) create mode 100644 test/resources/ArduinoIDE/hardware/arduino/avr/programmers.txt create mode 100644 test/resources/Documents/Arduino/hardware/esp8266/esp8266/programmers.txt diff --git a/test/boardmanager.test.ts b/test/boardmanager.test.ts index b02526df..efb85551 100644 --- a/test/boardmanager.test.ts +++ b/test/boardmanager.test.ts @@ -12,6 +12,7 @@ import { ArduinoSettings } from "../src/arduino/arduinoSettings"; import { parseBoardDescriptor } from "../src/arduino/board"; import { BoardManager } from "../src/arduino/boardManager"; import { IPlatform } from "../src/arduino/package"; +import { parseProgrammerDescriptor } from "../src/arduino/programmer"; import * as util from "../src/common/util"; suite("Arduino: Board Manager.", () => { @@ -68,6 +69,14 @@ suite("Arduino: Board Manager.", () => { "should parse installed boards from custom packages ($sketchbook/hardware directory)"); }); + test("should be able to load installed programmers", () => { + assert.equal(boardManager.installedProgrammers.size, 17, `Expected to find programmers for dummy & AVR boards`); + assert.ok(boardManager.installedProgrammers.get("arduino:avrispmkii"), + "should parse installed programmers from Arduino IDE built-in packages"); + assert.ok(boardManager.installedProgrammers.get("esp8266:esp8266_dummy"), + "should parse installed programmers from custom packages ($sketchbook/hardware directory)"); + }); + test("should parse boards.txt correctly", () => { const arduinoAvrBoard = fs.readFileSync(Path.join(Resources.mockedIDEPackagePath, "arduino/avr/boards.txt"), "utf8"); const platform = { @@ -93,6 +102,28 @@ suite("Arduino: Board Manager.", () => { assert.equal(diecimilaBoard.customConfig, "cpu=atmega328"); }); + test("should parse programmers.txt correctly", () => { + const arduinoAvrBoard = fs.readFileSync(Path.join(Resources.mockedIDEPackagePath, "arduino/avr/programmers.txt"), "utf8"); + const platform = { + name: "Arduino AVR Boards", + architecture: "avr", + package: { + name: "arduino", + }, + }; + const programmerDescriptors = parseProgrammerDescriptor(arduinoAvrBoard, platform); + + const avrispmkii = programmerDescriptors.get("avrispmkii"); + assert.equal(avrispmkii.name, "avrispmkii"); + assert.equal(avrispmkii.displayName, "AVRISP mkII"); + assert.equal(avrispmkii.key, "arduino:avrispmkii"); + + const usbGemma = programmerDescriptors.get("usbGemma"); + assert.equal(usbGemma.name, "usbGemma"); + assert.equal(usbGemma.displayName, "Arduino Gemma"); + assert.equal(usbGemma.key, "arduino:usbGemma"); + }); + test("should parse platform.txt correctly", () => { const platformConfig = util.parseConfigFile(Path.join(Resources.mockedSketchbookPath, "hardware/esp8266/esp8266/platform.txt")); assert.equal(platformConfig.get("name"), "ESP8266 Modules"); diff --git a/test/resources/ArduinoIDE/hardware/arduino/avr/programmers.txt b/test/resources/ArduinoIDE/hardware/arduino/avr/programmers.txt new file mode 100644 index 00000000..69ddf692 --- /dev/null +++ b/test/resources/ArduinoIDE/hardware/arduino/avr/programmers.txt @@ -0,0 +1,115 @@ +avrisp.name=AVR ISP +avrisp.communication=serial +avrisp.protocol=stk500v1 +avrisp.program.protocol=stk500v1 +avrisp.program.tool=avrdude +avrisp.program.extra_params=-P{serial.port} + +avrispmkii.name=AVRISP mkII +avrispmkii.communication=usb +avrispmkii.protocol=stk500v2 +avrispmkii.program.protocol=stk500v2 +avrispmkii.program.tool=avrdude +avrispmkii.program.extra_params=-Pusb + +usbtinyisp.name=USBtinyISP +usbtinyisp.protocol=usbtiny +usbtinyisp.program.tool=avrdude +usbtinyisp.program.extra_params= + +arduinoisp.name=ArduinoISP +arduinoisp.protocol=arduinoisp +arduinoisp.program.tool=avrdude +arduinoisp.program.extra_params= + +arduinoisporg.name=ArduinoISP.org +arduinoisporg.protocol=arduinoisporg +arduinoisporg.program.tool=avrdude +arduinoisporg.program.extra_params= + +usbasp.name=USBasp +usbasp.communication=usb +usbasp.protocol=usbasp +usbasp.program.protocol=usbasp +usbasp.program.tool=avrdude +usbasp.program.extra_params=-Pusb + +parallel.name=Parallel Programmer +parallel.protocol=dapa +parallel.force=true +# parallel.delay=200 +parallel.program.tool=avrdude +parallel.program.extra_params=-F + +arduinoasisp.name=Arduino as ISP +arduinoasisp.communication=serial +arduinoasisp.protocol=stk500v1 +arduinoasisp.speed=19200 +arduinoasisp.program.protocol=stk500v1 +arduinoasisp.program.speed=19200 +arduinoasisp.program.tool=avrdude +arduinoasisp.program.extra_params=-P{serial.port} -b{program.speed} + +arduinoasispatmega32u4.name=Arduino as ISP (ATmega32U4) +arduinoasispatmega32u4.communication=serial +arduinoasispatmega32u4.protocol=arduino +arduinoasispatmega32u4.speed=19200 +arduinoasispatmega32u4.program.protocol=arduino +arduinoasispatmega32u4.program.speed=19200 +arduinoasispatmega32u4.program.tool=avrdude +arduinoasispatmega32u4.program.extra_params=-P{serial.port} -b{program.speed} + +usbGemma.name=Arduino Gemma +usbGemma.protocol=arduinogemma +usbGemma.program.tool=avrdude +usbGemma.program.extra_params= +usbGemma.config.path={runtime.platform.path}/bootloaders/gemma/avrdude.conf + +## Notes about Dangerous Prototypes Bus Pirate as ISP +## Bus Pirate V3 need Firmware v5.10 or later +## Bus Pirate V4 need Firmware v6.3-r2151 or later +## Could happen that BP does not have enough current to power an Arduino board +## through the ICSP connector. In this case disconnect the +Vcc from ICSP connector +## and power Arduino board in the normal way. +buspirate.name=BusPirate as ISP +buspirate.communication=serial +buspirate.protocol=buspirate +buspirate.program.protocol=buspirate +buspirate.program.tool=avrdude +buspirate.program.extra_params=-P{serial.port} + +# STK500 firmware version v1 and v2 use different serial protocols. +# Using the 'stk500' protocol tells avrdude to try and autodetect the +# firmware version. If this leads to problems, we might need to add +# stk500v1 and stk500v2 entries to allow explicitely selecting the +# firmware version. +stk500.name=Atmel STK500 development board +stk500.communication=serial +stk500.protocol=stk500 +stk500.program.protocol=stk500 +stk500.program.tool=avrdude +stk500.program.extra_params=-P{serial.port} + +jtag3isp.name=Atmel JTAGICE3 (ISP mode) +jtag3isp.communication=usb +jtag3isp.protocol=jtag3isp +jtag3isp.program.protocol=jtag3isp +jtag3isp.program.tool=avrdude +jtag3isp.program.extra_params= + +jtag3.name=Atmel JTAGICE3 (JTAG mode) +jtag3.communication=usb +jtag3.protocol=jtag3 +jtag3.program.protocol=jtag3 +jtag3.program.tool=avrdude +# Set a bitclock of 0.1us (the fastest supported value). This should +# work regardless of the crystal used, since JTAG doesn't use the MCU +# clock but dictates its own clock. +jtag3.program.extra_params=-B0.1 + +atmel_ice.name=Atmel-ICE (AVR) +atmel_ice.communication=usb +atmel_ice.protocol=atmelice_isp +atmel_ice.program.protocol=atmelice_isp +atmel_ice.program.tool=avrdude +atmel_ice.program.extra_params=-Pusb diff --git a/test/resources/Documents/Arduino/hardware/esp8266/esp8266/programmers.txt b/test/resources/Documents/Arduino/hardware/esp8266/esp8266/programmers.txt new file mode 100644 index 00000000..8fe950d4 --- /dev/null +++ b/test/resources/Documents/Arduino/hardware/esp8266/esp8266/programmers.txt @@ -0,0 +1,14 @@ +#********************************************** +# Dummy esp8266 programmer +#********************************************** +esp8266_dummy.name=Dummy esp8266 programmer +esp8266_dummy.program.tool=bootburn +esp8266_dummy.program.cmd=esp8266prog +esp8266_dummy.program.cmd.windows=esp8266prog.exe + +#********************************************** +# Dummy esp8266 bootloader programmer +#********************************************** +esp8266_boot_dummy.name=Dummy esp8266 bootloader programmer +esp8266_boot_dummy.program.tool=bootburn +esp8266_boot_dummy.program.cmd=esp8266bootprog \ No newline at end of file From 5f7044bcc4ab5b75562a595670df65c76f8f4322 Mon Sep 17 00:00:00 2001 From: Jason Tranchida Date: Thu, 29 Oct 2020 18:31:51 -0700 Subject: [PATCH 4/9] Show board specific list of programmers when selecting Populate the selected programmer and it's display name using list of programmers provided by BoardManager. When selecting programmer, only present the user a list of programmers relevant to the current board. --- src/arduino/programmerManager.ts | 73 +++++++++++++++++--------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/src/arduino/programmerManager.ts b/src/arduino/programmerManager.ts index d5dbdf98..9e344618 100644 --- a/src/arduino/programmerManager.ts +++ b/src/arduino/programmerManager.ts @@ -3,28 +3,15 @@ import * as constants from "../common/constants"; import { DeviceContext } from "../deviceContext"; import { ArduinoApp } from "./arduino"; import { IArduinoSettings } from "./arduinoSettings"; +import { IBoard, IProgrammer } from "./package"; export class ProgrammerManager { - private _programmervalue: string; + public static notFoundDisplayValue: string = ""; + private getDisplayName(programmerKey: string): string { + const programmer = this._arduinoApp.boardManager.installedProgrammers.get(programmerKey); + return programmer ? programmer.displayName : programmerKey; } - private getFriendlyName(programmer: string): string { - const friendlyName = this._availableProgrammers[programmer]; - return friendlyName ? friendlyName : programmer; + private getAvailableProgrammers(currentBoard: IBoard): IProgrammer[] { + if (!currentBoard || !currentBoard.platform) { + return []; + } + + // Filter the list of all programmers to those that share the same platform as the board + const availableProgrammers: IProgrammer[] = []; + for (const programmer of this._arduinoApp.boardManager.installedProgrammers.values()) { + if (programmer.platform === currentBoard.platform) { + availableProgrammers.push(programmer); + } + } + + return availableProgrammers; } } From f1c43ff6d32dbf39a966a4b1476bba10619d9386 Mon Sep 17 00:00:00 2001 From: Jason Tranchida Date: Thu, 29 Oct 2020 18:32:17 -0700 Subject: [PATCH 5/9] Initial set of tests for ProgrammerManager --- src/deviceContext.ts | 17 ++++ test/devicecontext.test.ts | 1 + test/programmermanager.test.ts | 100 ++++++++++++++++++++++ test/resources/blink/.vscode/arduino.json | 3 +- 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 test/programmermanager.test.ts diff --git a/src/deviceContext.ts b/src/deviceContext.ts index 5e24aea1..23cf52c6 100644 --- a/src/deviceContext.ts +++ b/src/deviceContext.ts @@ -96,6 +96,8 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable { private _programmer: string; + private _suppressSaveContext: boolean = false; + /** * @constructor */ @@ -203,6 +205,13 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable { if (!ArduinoWorkspace.rootPath) { return; } + + if (this._suppressSaveContext) { + // fine the change event, but do not actually save the file + this._onDidChange.fire(); + return; + } + const deviceConfigFile = path.join(ArduinoWorkspace.rootPath, ARDUINO_CONFIG_FILE); let deviceConfigJson: any = {}; if (util.fileExistsSync(deviceConfigFile)) { @@ -300,6 +309,14 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable { this.saveContext(); } + public get suppressSaveContext() { + return this._suppressSaveContext; + } + + public set suppressSaveContext(value: boolean) { + this._suppressSaveContext = value; + } + public async initialize() { if (ArduinoWorkspace.rootPath && util.fileExistsSync(path.join(ArduinoWorkspace.rootPath, ARDUINO_CONFIG_FILE))) { vscode.window.showInformationMessage("Arduino.json is already generated."); diff --git a/test/devicecontext.test.ts b/test/devicecontext.test.ts index fac14055..4579b5c6 100644 --- a/test/devicecontext.test.ts +++ b/test/devicecontext.test.ts @@ -17,6 +17,7 @@ suite("Arduino: Device Context config", () => { assert.equal(deviceContext.configuration, "cpu=atmega328"); assert.equal(deviceContext.output, null); assert.equal(deviceContext.debugger_, null); + assert.equal(deviceContext.programmer, "arduino:jtag3isp"); done(); }); } catch (error) { diff --git a/test/programmermanager.test.ts b/test/programmermanager.test.ts new file mode 100644 index 00000000..b0e9711b --- /dev/null +++ b/test/programmermanager.test.ts @@ -0,0 +1,100 @@ +import * as assert from "assert"; +import * as TypeMoq from "typemoq"; +import * as vscode from "vscode"; + +import { ArduinoApp } from "../src/arduino/arduino"; +import { ArduinoSettings } from "../src/arduino/arduinoSettings"; +import { Board } from "../src/arduino/board"; +import { BoardManager } from "../src/arduino/boardManager"; +import { IPlatform, IProgrammer } from "../src/arduino/package"; +import { Programmer } from "../src/arduino/programmer"; +import { ProgrammerManager } from "../src/arduino/programmerManager"; +import { DeviceContext } from "../src/deviceContext"; +import * as Resources from "./resources"; + +suite("Arduino: Programmer Manager.", () => { + let programmerManager: ProgrammerManager; + let restoreSuppress: boolean; + let programmers: IProgrammer[]; + + setup((done) => { + // Suppress saving the device context, as not to polute the test arduino.json file + restoreSuppress = DeviceContext.getInstance().suppressSaveContext; + DeviceContext.getInstance().suppressSaveContext = true; + + // Mock two different platforms + const platformMock1 = TypeMoq.Mock.ofType(); + platformMock1.setup((x) => x.packageName).returns(() => "mockplatform1"); + const platformMock2 = TypeMoq.Mock.ofType(); + platformMock2.setup((x) => x.packageName).returns(() => "mockplatform2"); + + // Mock a single selected board, using the the first mocked platform + const mockedBoard = TypeMoq.Mock.ofType(Board); + mockedBoard.setup((x) => x.platform).returns(() => platformMock1.object); + + // Setup a list of installed programmers + programmers = [ + new Programmer("test1", platformMock1.object, "test1_display"), + new Programmer("test2", platformMock1.object), + new Programmer("test3", platformMock2.object)]; + + const installedProgrammers = new Map(); + programmers.forEach((v) => installedProgrammers.set(v.key, v)); + + // Mock the BoardManager with minimal set of required functionality + const mockBoardManager = TypeMoq.Mock.ofType(BoardManager); + mockBoardManager.setup((x) => x.currentBoard).returns(() => mockedBoard.object); + mockBoardManager.setup((x) => x.installedProgrammers).returns(() => installedProgrammers); + mockBoardManager.setup((x) => x.onBoardTypeChanged).returns(() => new vscode.EventEmitter().event); + + // Mock minimal ArduinoApp + const arduinoApp = TypeMoq.Mock.ofType(ArduinoApp); + arduinoApp.setup((x) => x.boardManager).returns(() => mockBoardManager.object); + + try { + programmerManager = new ProgrammerManager(TypeMoq.Mock.ofType(ArduinoSettings).object, arduinoApp.object); + done(); + } catch (error) { + done(`Failed to initialize ProgrammerManager: ${error}`); + } + }); + + teardown(() => { + // Restpre the supression state for the DeviceContext + DeviceContext.getInstance().suppressSaveContext = restoreSuppress; + }); + + test("value stored in arduino.ino should load by default", () => { + assert.equal(programmerManager.currentProgrammer, "arduino:jtag3isp"); + assert.equal(programmerManager.currentDisplayName, "arduino:jtag3isp"); + }); + + test("changing arduino.ino value should change programmer", (done) => { + DeviceContext.getInstance().programmer = programmers[0].key; + setTimeout(() => { + assert.equal(programmerManager.currentProgrammer, programmers[0].key); + assert.equal(programmerManager.currentDisplayName, programmers[0].displayName); + done(); + }, 200); + }); + + test("changing arduino.ino value to null should clear displayname to not found value", (done) => { + DeviceContext.getInstance().programmer = null; + setTimeout(() => { + assert.equal(programmerManager.currentProgrammer, null); + assert.equal(programmerManager.currentDisplayName, ProgrammerManager.notFoundDisplayValue); + done(); + }, 200); + }); + + test("changing arduino.ino value to an unknown value will be accepted, and will replicate value as displayname", (done) => { + const unknownProgrammerValue = "unknown:programmer"; + + DeviceContext.getInstance().programmer = unknownProgrammerValue; + setTimeout(() => { + assert.equal(programmerManager.currentProgrammer, unknownProgrammerValue); + assert.equal(programmerManager.currentDisplayName, unknownProgrammerValue); + done(); + }, 200); + }); +}); diff --git a/test/resources/blink/.vscode/arduino.json b/test/resources/blink/.vscode/arduino.json index 5990ff4b..32bd2323 100644 --- a/test/resources/blink/.vscode/arduino.json +++ b/test/resources/blink/.vscode/arduino.json @@ -4,5 +4,6 @@ "board": "arduino:avr:diecimila", "output": null, "_debugger": null, - "configuration": "cpu=atmega328" + "configuration": "cpu=atmega328", + "programmer": "arduino:jtag3isp" } \ No newline at end of file From 34b0f370e8b1f5629c09668055291017157de472 Mon Sep 17 00:00:00 2001 From: Adi Azulay Date: Tue, 8 Dec 2020 18:00:41 -0500 Subject: [PATCH 6/9] add support for cli --- src/arduino/boardManager.ts | 2 +- src/arduino/package.ts | 7 +------ src/arduino/programmer.ts | 7 ------- src/arduino/programmerManager.ts | 6 +++--- test/boardmanager.test.ts | 6 ++---- test/devicecontext.test.ts | 2 +- test/programmermanager.test.ts | 10 +++++----- test/resources/Arduino15/preferences.txt | 2 +- test/resources/blink/.vscode/arduino.json | 2 +- 9 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/arduino/boardManager.ts b/src/arduino/boardManager.ts index eb28ae65..eb1c2cd1 100644 --- a/src/arduino/boardManager.ts +++ b/src/arduino/boardManager.ts @@ -417,7 +417,7 @@ export class BoardManager { const programmersContent = fs.readFileSync(path.join(plat.rootBoardPath, "programmers.txt"), "utf8"); const res = parseProgrammerDescriptor(programmersContent, plat); res.forEach((prog) => { - this._programmers.set(prog.key, prog); + this._programmers.set(prog.name, prog); }); } } diff --git a/src/arduino/package.ts b/src/arduino/package.ts index 0d74882a..f31a8403 100644 --- a/src/arduino/package.ts +++ b/src/arduino/package.ts @@ -228,12 +228,6 @@ export interface IBoard { */ export interface IProgrammer { - /** - * Unique key that represent the programmer in the package:name. - * @property {string} - */ - key: string; - /** * Programmer name for Arduino compilation such as `avrisp`, `atmel_ice` * @property {string} @@ -246,6 +240,7 @@ export interface IProgrammer { */ displayName: string; + // TODO: verify if this is still need or not, might be needed for the IDE /** * Reference to the platform that contains this board. * @prop {IPlatform} diff --git a/src/arduino/programmer.ts b/src/arduino/programmer.ts index d2fdceb0..312b0b3d 100644 --- a/src/arduino/programmer.ts +++ b/src/arduino/programmer.ts @@ -54,13 +54,6 @@ export class Programmer implements IProgrammer { this._displayName = value; } - /** - * @returns {string} Return programmer key in format packageName:name - */ - public get key() { - return `${this.getPackageName}:${this.name}`; - } - private get getPackageName(): string { return this.platform.packageName ? this.platform.packageName : this.platform.package.name; } diff --git a/src/arduino/programmerManager.ts b/src/arduino/programmerManager.ts index f99ce4eb..667b63df 100644 --- a/src/arduino/programmerManager.ts +++ b/src/arduino/programmerManager.ts @@ -61,7 +61,7 @@ export class ProgrammerManager { const selectionItems = this.getAvailableProgrammers(this._arduinoApp.boardManager.currentBoard).map( (programmer) => ({ label: programmer.displayName, - description: programmer.key, + description: programmer.name, programmer })); const chosen = await vscode.window.showQuickPick(selectionItems, { placeHolder: "Select programmer", @@ -70,8 +70,8 @@ export class ProgrammerManager { return; } - this.setProgrammerValue(chosen.programmer.key); - DeviceContext.getInstance().programmer = chosen.programmer.key; + this.setProgrammerValue(chosen.programmer.name); + DeviceContext.getInstance().programmer = chosen.programmer.name; } private setProgrammerValue(programmerKey: string | null) { diff --git a/test/boardmanager.test.ts b/test/boardmanager.test.ts index efb85551..900858ab 100644 --- a/test/boardmanager.test.ts +++ b/test/boardmanager.test.ts @@ -71,9 +71,9 @@ suite("Arduino: Board Manager.", () => { test("should be able to load installed programmers", () => { assert.equal(boardManager.installedProgrammers.size, 17, `Expected to find programmers for dummy & AVR boards`); - assert.ok(boardManager.installedProgrammers.get("arduino:avrispmkii"), + assert.ok(boardManager.installedProgrammers.get("avrispmkii"), "should parse installed programmers from Arduino IDE built-in packages"); - assert.ok(boardManager.installedProgrammers.get("esp8266:esp8266_dummy"), + assert.ok(boardManager.installedProgrammers.get("esp8266_dummy"), "should parse installed programmers from custom packages ($sketchbook/hardware directory)"); }); @@ -116,12 +116,10 @@ suite("Arduino: Board Manager.", () => { const avrispmkii = programmerDescriptors.get("avrispmkii"); assert.equal(avrispmkii.name, "avrispmkii"); assert.equal(avrispmkii.displayName, "AVRISP mkII"); - assert.equal(avrispmkii.key, "arduino:avrispmkii"); const usbGemma = programmerDescriptors.get("usbGemma"); assert.equal(usbGemma.name, "usbGemma"); assert.equal(usbGemma.displayName, "Arduino Gemma"); - assert.equal(usbGemma.key, "arduino:usbGemma"); }); test("should parse platform.txt correctly", () => { diff --git a/test/devicecontext.test.ts b/test/devicecontext.test.ts index 4579b5c6..659de503 100644 --- a/test/devicecontext.test.ts +++ b/test/devicecontext.test.ts @@ -17,7 +17,7 @@ suite("Arduino: Device Context config", () => { assert.equal(deviceContext.configuration, "cpu=atmega328"); assert.equal(deviceContext.output, null); assert.equal(deviceContext.debugger_, null); - assert.equal(deviceContext.programmer, "arduino:jtag3isp"); + assert.equal(deviceContext.programmer, "jtag3isp"); done(); }); } catch (error) { diff --git a/test/programmermanager.test.ts b/test/programmermanager.test.ts index b0e9711b..58d61535 100644 --- a/test/programmermanager.test.ts +++ b/test/programmermanager.test.ts @@ -39,7 +39,7 @@ suite("Arduino: Programmer Manager.", () => { new Programmer("test3", platformMock2.object)]; const installedProgrammers = new Map(); - programmers.forEach((v) => installedProgrammers.set(v.key, v)); + programmers.forEach((v) => installedProgrammers.set(v.name, v)); // Mock the BoardManager with minimal set of required functionality const mockBoardManager = TypeMoq.Mock.ofType(BoardManager); @@ -65,14 +65,14 @@ suite("Arduino: Programmer Manager.", () => { }); test("value stored in arduino.ino should load by default", () => { - assert.equal(programmerManager.currentProgrammer, "arduino:jtag3isp"); - assert.equal(programmerManager.currentDisplayName, "arduino:jtag3isp"); + assert.equal(programmerManager.currentProgrammer, "jtag3isp"); + assert.equal(programmerManager.currentDisplayName, "jtag3isp"); }); test("changing arduino.ino value should change programmer", (done) => { - DeviceContext.getInstance().programmer = programmers[0].key; + DeviceContext.getInstance().programmer = programmers[0].name; setTimeout(() => { - assert.equal(programmerManager.currentProgrammer, programmers[0].key); + assert.equal(programmerManager.currentProgrammer, programmers[0].name); assert.equal(programmerManager.currentDisplayName, programmers[0].displayName); done(); }, 200); diff --git a/test/resources/Arduino15/preferences.txt b/test/resources/Arduino15/preferences.txt index 6a875364..2378e9c3 100644 --- a/test/resources/Arduino15/preferences.txt +++ b/test/resources/Arduino15/preferences.txt @@ -45,7 +45,7 @@ preproc.save_build_files=false preproc.substitute_floats=true preproc.substitute_unicode=true preproc.web_colors=true -programmer=arduino:avrispmkii +programmer=avrispmkii run.display=1 run.options= run.options.memory=false diff --git a/test/resources/blink/.vscode/arduino.json b/test/resources/blink/.vscode/arduino.json index 32bd2323..1aac96aa 100644 --- a/test/resources/blink/.vscode/arduino.json +++ b/test/resources/blink/.vscode/arduino.json @@ -5,5 +5,5 @@ "output": null, "_debugger": null, "configuration": "cpu=atmega328", - "programmer": "arduino:jtag3isp" + "programmer": "jtag3isp" } \ No newline at end of file From 7f017433d9c27e5f9eaf57afd7522c66f8a630dd Mon Sep 17 00:00:00 2001 From: Adi Azulay Date: Tue, 8 Dec 2020 20:33:05 -0500 Subject: [PATCH 7/9] fix hardcoded package name for programmers --- src/arduino/arduino.ts | 2 +- src/arduino/programmerManager.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index a4b9b1aa..5a59ca19 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -165,7 +165,7 @@ export class ArduinoApp { if (this.useArduinoCli()) { args.push("--programmer", selectProgrammer) } else { - args.push("--useprogrammer", "--pref", "programmer=arduino:" + selectProgrammer) + args.push("--useprogrammer", "--pref", "programmer=" + selectProgrammer) } } diff --git a/src/arduino/programmerManager.ts b/src/arduino/programmerManager.ts index 667b63df..8207cfc5 100644 --- a/src/arduino/programmerManager.ts +++ b/src/arduino/programmerManager.ts @@ -3,7 +3,7 @@ import * as constants from "../common/constants"; import { DeviceContext } from "../deviceContext"; import { ArduinoApp } from "./arduino"; import { IArduinoSettings } from "./arduinoSettings"; -import { IBoard, IProgrammer } from "./package"; +import { IBoard, IPlatform, IProgrammer } from "./package"; export class ProgrammerManager { public static notFoundDisplayValue: string = ""; @@ -53,21 +53,22 @@ export class ProgrammerManager { return; } - this.setProgrammerValue(this._settings.useArduinoCli ? chosen.programmer.name : chosen.programmer.key); - DeviceContext.getInstance().programmer = chosen.programmer.name; + this.setProgrammerValue(chosen.programmer.name); + DeviceContext.getInstance().programmer = this._programmerValue; } - private setProgrammerValue(programmerKey: string | null) { - this._programmerValue = programmerKey; + private setProgrammerValue(programmerName: string | null) { + const programmer = this._arduinoApp.boardManager.installedProgrammers.get(programmerName); + this._programmerValue = this._settings.useArduinoCli ? programmerName : programmer ? programmer.key : programmerName; this._programmerDisplayName = this._programmerValue - ? this.getDisplayName(this._programmerValue) + ? this.getDisplayName(programmerName) : ProgrammerManager.notFoundDisplayValue; this._programmerStatusBar.text = this._programmerDisplayName; } - private getDisplayName(programmerKey: string): string { - const programmer = this._arduinoApp.boardManager.installedProgrammers.get(programmerKey); - return programmer ? programmer.displayName : programmerKey; + private getDisplayName(programmerName: string): string { + const programmer = this._arduinoApp.boardManager.installedProgrammers.get(programmerName); + return programmer ? programmer.displayName : programmerName; } private getAvailableProgrammers(currentBoard: IBoard): IProgrammer[] { diff --git a/test/programmermanager.test.ts b/test/programmermanager.test.ts index 13278d81..a73baed0 100644 --- a/test/programmermanager.test.ts +++ b/test/programmermanager.test.ts @@ -72,7 +72,7 @@ suite("Arduino: Programmer Manager.", () => { test("changing arduino.ino value should change programmer", (done) => { DeviceContext.getInstance().programmer = programmers[0].name; setTimeout(() => { - assert.equal(programmerManager.currentProgrammer, programmers[0].name); + assert.equal(programmerManager.currentProgrammer, programmers[0].key); assert.equal(programmerManager.currentDisplayName, programmers[0].displayName); done(); }, 200);