diff --git a/gulpfile.js b/gulpfile.js index ce55740d6..7bf6b6239 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -38,6 +38,7 @@ const pythonToMove = [ "./src/microbit/*.*", "./src/microbit/!(test)/**/*", "./src/*.py", + "./src/common/*.py", "./src/requirements.txt", ]; diff --git a/package.json b/package.json index 3eb061020..6ec86ed53 100644 --- a/package.json +++ b/package.json @@ -1,365 +1,366 @@ { - "name": "__EXTENSIONNAME__", - "displayName": "__DISPLAYNAME__", - "description": "__DESCRIPTION__", - "version": "0.0.0-UNTRACKEDVERSION", - "publisher": "__PUBLISHER__", - "instrumentationKey": "__AIKEY__", - "icon": "assets/icon.png", - "engines": { - "vscode": "^1.34.0" - }, - "categories": [ - "Other" - ], - "preview": true, - "license": "MIT", - "homepage": "https://github.com/microsoft/vscode-python-devicesimulator", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode-python-devicesimulator" - }, - "bugs": { - "url": "https://github.com/microsoft/vscode-python-devicesimulator/issues" - }, - "keywords": [ - "python", - "CircuitPython", - "Adafruit" - ], - "activationEvents": [ - "onCommand:deviceSimulatorExpress.openSerialMonitor", - "onCommand:deviceSimulatorExpress.openSimulator", - "onCommand:deviceSimulatorExpress.runSimulator", - "onCommand:deviceSimulatorExpress.newFile", - "onCommand:deviceSimulatorExpress.runDevice", - "onCommand:deviceSimulatorExpress.runSimulatorEditorButton", - "onCommand:deviceSimulatorExpress.selectSerialPort", - "onDebug" - ], - "main": "./out/extension.js", - "contributes": { - "commands": [ - { - "command": "deviceSimulatorExpress.changeBaudRate", - "title": "%deviceSimulatorExpressExtension.commands.changeBaudRate%", - "category": "%deviceSimulatorExpressExtension.commands.label%" - }, - { - "command": "deviceSimulatorExpress.closeSerialMonitor", - "title": "%deviceSimulatorExpressExtension.commands.closeSerialMonitor%", - "category": "%deviceSimulatorExpressExtension.commands.label%" - }, - { - "command": "deviceSimulatorExpress.openSerialMonitor", - "title": "%deviceSimulatorExpressExtension.commands.openSerialMonitor%", - "category": "%deviceSimulatorExpressExtension.commands.label%" - }, - { - "command": "deviceSimulatorExpress.openSimulator", - "title": "%deviceSimulatorExpressExtension.commands.openSimulator%", - "category": "%deviceSimulatorExpressExtension.commands.label%", - "icon": { - "light": "./assets/light-theme/open-simulator.svg", - "dark": "./assets/dark-theme/open-simulator.svg" - } - }, - { - "command": "deviceSimulatorExpress.runSimulator", - "title": "%deviceSimulatorExpressExtension.commands.runSimulator%", - "category": "%deviceSimulatorExpressExtension.commands.label%" - }, - { - "command": "deviceSimulatorExpress.runSimulatorEditorButton", - "title": "%deviceSimulatorExpressExtension.commands.runSimulator%", - "category": "%deviceSimulatorExpressExtension.commands.label%", - "icon": { - "light": "./assets/light-theme/run-on-simulator.svg", - "dark": "./assets/dark-theme/run-on-simulator.svg" - } - }, - { - "command": "deviceSimulatorExpress.newFile", - "title": "%deviceSimulatorExpressExtension.commands.newFile%", - "category": "%deviceSimulatorExpressExtension.commands.label%" - }, - { - "command": "deviceSimulatorExpress.runDevice", - "title": "%deviceSimulatorExpressExtension.commands.runDevice%", - "category": "%deviceSimulatorExpressExtension.commands.label%", - "icon": { - "light": "./assets/light-theme/save-to-board.svg", - "dark": "./assets/dark-theme/save-to-board.svg" - } - }, - { - "command": "deviceSimulatorExpress.selectSerialPort", - "title": "%deviceSimulatorExpressExtension.commands.selectSerialPort%", - "category": "%deviceSimulatorExpressExtension.commands.label%" - } - ], - "colors": [ - { - "id": "highContrastButtonBorderOverride.color", - "description": "Color for the high contrast border updated", - "defaults": { - "dark": "debugToolBar.background", - "light": "debugToolBar.background", - "highContrast": "#6FC3DF" - } - }, - { - "id": "badgeForegroundOverride", - "description": "Color that fixes the issue with midnight blue ", - "defaults": { - "dark": "#FFFFFF", - "light": "badge.foreground", - "highContrast": "#FFFFFF" - } - } + "name": "__EXTENSIONNAME__", + "displayName": "__DISPLAYNAME__", + "description": "__DESCRIPTION__", + "version": "0.0.0-UNTRACKEDVERSION", + "publisher": "__PUBLISHER__", + "instrumentationKey": "__AIKEY__", + "icon": "assets/icon.png", + "engines": { + "vscode": "^1.34.0" + }, + "categories": [ + "Other" ], - "menus": { - "commandPalette": [ - { - "command": "deviceSimulatorExpress.runSimulatorEditorButton", - "when": "false" - } - ], - "editor/title": [ - { - "when": "editorLangId==python && config.deviceSimulatorExpress.showOpenIconInEditorTitleMenu", - "command": "deviceSimulatorExpress.openSimulator", - "group": "navigation@1" - }, - { - "when": "editorLangId==python && config.deviceSimulatorExpress.showSimulatorIconInEditorTitleMenu", - "command": "deviceSimulatorExpress.runSimulatorEditorButton", - "group": "navigation@2" - }, - { - "when": "editorLangId==python && config.deviceSimulatorExpress.showDeviceIconInEditorTitleMenu", - "command": "deviceSimulatorExpress.runDevice", - "group": "navigation@3" - } - ] + "preview": true, + "license": "MIT", + "homepage": "https://github.com/microsoft/vscode-python-devicesimulator", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode-python-devicesimulator" }, - "configuration": { - "type": "object", - "title": "%deviceSimulatorExpressExtension.configuration.title%", - "properties": { - "deviceSimulatorExpress.enableUSBDetection": { - "type": "boolean", - "default": true - }, - "deviceSimulatorExpress.showOpenIconInEditorTitleMenu": { - "type": "boolean", - "default": true, - "description": "%deviceSimulatorExpressExtension.configuration.properties.open%", - "scope": "resource" - }, - "deviceSimulatorExpress.showSimulatorIconInEditorTitleMenu": { - "type": "boolean", - "default": true, - "description": "%deviceSimulatorExpressExtension.configuration.properties.simulator%", - "scope": "resource" - }, - "deviceSimulatorExpress.showDeviceIconInEditorTitleMenu": { - "type": "boolean", - "default": true, - "description": "%deviceSimulatorExpressExtension.configuration.properties.device%", - "scope": "resource" - }, - "deviceSimulatorExpress.showDependencyInstall": { - "type": "boolean", - "default": true, - "scope": "resource" - }, - "deviceSimulatorExpress.showNewFilePopup": { - "type": "boolean", - "default": true, - "scope": "resource" - }, - "deviceSimulatorExpress.debuggerServerPort": { - "type": "number", - "default": 5577, - "description": "%deviceSimulatorExpressExtension.configuration.properties.debuggerPort%", - "scope": "resource" - } - } + "bugs": { + "url": "https://github.com/microsoft/vscode-python-devicesimulator/issues" }, - "breakpoints": [ - { - "language": "python" - } + "keywords": [ + "python", + "CircuitPython", + "Adafruit" ], - "debuggers": [ - { - "type": "deviceSimulatorExpress", - "label": "Device Simulator Express Debugger", - "languages": [ - "python" + "activationEvents": [ + "onCommand:deviceSimulatorExpress.openSerialMonitor", + "onCommand:deviceSimulatorExpress.openSimulator", + "onCommand:deviceSimulatorExpress.runSimulator", + "onCommand:deviceSimulatorExpress.newFile", + "onCommand:deviceSimulatorExpress.runDevice", + "onCommand:deviceSimulatorExpress.runSimulatorEditorButton", + "onCommand:deviceSimulatorExpress.selectSerialPort", + "onDebug" + ], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "deviceSimulatorExpress.changeBaudRate", + "title": "%deviceSimulatorExpressExtension.commands.changeBaudRate%", + "category": "%deviceSimulatorExpressExtension.commands.label%" + }, + { + "command": "deviceSimulatorExpress.closeSerialMonitor", + "title": "%deviceSimulatorExpressExtension.commands.closeSerialMonitor%", + "category": "%deviceSimulatorExpressExtension.commands.label%" + }, + { + "command": "deviceSimulatorExpress.openSerialMonitor", + "title": "%deviceSimulatorExpressExtension.commands.openSerialMonitor%", + "category": "%deviceSimulatorExpressExtension.commands.label%" + }, + { + "command": "deviceSimulatorExpress.openSimulator", + "title": "%deviceSimulatorExpressExtension.commands.openSimulator%", + "category": "%deviceSimulatorExpressExtension.commands.label%", + "icon": { + "light": "./assets/light-theme/open-simulator.svg", + "dark": "./assets/dark-theme/open-simulator.svg" + } + }, + { + "command": "deviceSimulatorExpress.runSimulator", + "title": "%deviceSimulatorExpressExtension.commands.runSimulator%", + "category": "%deviceSimulatorExpressExtension.commands.label%" + }, + { + "command": "deviceSimulatorExpress.runSimulatorEditorButton", + "title": "%deviceSimulatorExpressExtension.commands.runSimulator%", + "category": "%deviceSimulatorExpressExtension.commands.label%", + "icon": { + "light": "./assets/light-theme/run-on-simulator.svg", + "dark": "./assets/dark-theme/run-on-simulator.svg" + } + }, + { + "command": "deviceSimulatorExpress.newFile", + "title": "%deviceSimulatorExpressExtension.commands.newFile%", + "category": "%deviceSimulatorExpressExtension.commands.label%" + }, + { + "command": "deviceSimulatorExpress.runDevice", + "title": "%deviceSimulatorExpressExtension.commands.runDevice%", + "category": "%deviceSimulatorExpressExtension.commands.label%", + "icon": { + "light": "./assets/light-theme/save-to-board.svg", + "dark": "./assets/dark-theme/save-to-board.svg" + } + }, + { + "command": "deviceSimulatorExpress.selectSerialPort", + "title": "%deviceSimulatorExpressExtension.commands.selectSerialPort%", + "category": "%deviceSimulatorExpressExtension.commands.label%" + } ], - "configurationAttributes": { - "launch": { - "properties": { - "program": { - "type": "string", - "description": "Absolute path to the code file.", - "default": "${file}" - }, - "stopOnEntry": { - "type": "boolean", - "description": "Automatically stop after launch.", - "default": true - }, - "console": { - "enum": [ - "internalConsole", - "integratedTerminal", - "externalTerminal" - ], - "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", - "default": "integratedTerminal" - }, - "args": { - "type": "array", - "description": "Command line arguments passed to the program.", - "default": [], - "items": { - "filePath": "string", - "serverPort": "string" + "colors": [ + { + "id": "highContrastButtonBorderOverride.color", + "description": "Color for the high contrast border updated", + "defaults": { + "dark": "debugToolBar.background", + "light": "debugToolBar.background", + "highContrast": "#6FC3DF" + } + }, + { + "id": "badgeForegroundOverride", + "description": "Color that fixes the issue with midnight blue ", + "defaults": { + "dark": "#FFFFFF", + "light": "badge.foreground", + "highContrast": "#FFFFFF" + } + } + ], + "menus": { + "commandPalette": [ + { + "command": "deviceSimulatorExpress.runSimulatorEditorButton", + "when": "false" + } + ], + "editor/title": [ + { + "when": "editorLangId==python && config.deviceSimulatorExpress.showOpenIconInEditorTitleMenu", + "command": "deviceSimulatorExpress.openSimulator", + "group": "navigation@1" + }, + { + "when": "editorLangId==python && config.deviceSimulatorExpress.showSimulatorIconInEditorTitleMenu", + "command": "deviceSimulatorExpress.runSimulatorEditorButton", + "group": "navigation@2" + }, + { + "when": "editorLangId==python && config.deviceSimulatorExpress.showDeviceIconInEditorTitleMenu", + "command": "deviceSimulatorExpress.runDevice", + "group": "navigation@3" } - }, - "rules": { - "type": "array", - "description": "Debugger rules.", - "default": [], - "items": { - "path": "string", - "include": "boolean" + ] + }, + "configuration": { + "type": "object", + "title": "%deviceSimulatorExpressExtension.configuration.title%", + "properties": { + "deviceSimulatorExpress.enableUSBDetection": { + "type": "boolean", + "default": true + }, + "deviceSimulatorExpress.showOpenIconInEditorTitleMenu": { + "type": "boolean", + "default": true, + "description": "%deviceSimulatorExpressExtension.configuration.properties.open%", + "scope": "resource" + }, + "deviceSimulatorExpress.showSimulatorIconInEditorTitleMenu": { + "type": "boolean", + "default": true, + "description": "%deviceSimulatorExpressExtension.configuration.properties.simulator%", + "scope": "resource" + }, + "deviceSimulatorExpress.showDeviceIconInEditorTitleMenu": { + "type": "boolean", + "default": true, + "description": "%deviceSimulatorExpressExtension.configuration.properties.device%", + "scope": "resource" + }, + "deviceSimulatorExpress.showDependencyInstall": { + "type": "boolean", + "default": true, + "scope": "resource" + }, + "deviceSimulatorExpress.showNewFilePopup": { + "type": "boolean", + "default": true, + "scope": "resource" + }, + "deviceSimulatorExpress.debuggerServerPort": { + "type": "number", + "default": 5577, + "description": "%deviceSimulatorExpressExtension.configuration.properties.debuggerPort%", + "scope": "resource" } - } } - } }, - "initialConfigurations": [ - { - "type": "deviceSimulatorExpress", - "request": "launch", - "name": "Device Simulator Express Debugger", - "console": "integratedTerminal" - } + "breakpoints": [ + { + "language": "python" + } ], - "configurationSnippets": [ - { - "label": "Device Simulator Express Debugger : Launch", - "description": "Device Simulator Express Debugger - A configuration for debugging a python code file for the Device Simulator Express simulator.", - "body": { - "type": "deviceSimulatorExpress", - "request": "launch", - "name": "Device Simulator Express Debugger", - "console": "integratedTerminal" + "debuggers": [ + { + "type": "deviceSimulatorExpress", + "label": "Device Simulator Express Debugger", + "languages": [ + "python" + ], + "configurationAttributes": { + "launch": { + "properties": { + "program": { + "type": "string", + "description": "Absolute path to the code file.", + "default": "${file}" + }, + "stopOnEntry": { + "type": "boolean", + "description": "Automatically stop after launch.", + "default": true + }, + "console": { + "enum": [ + "internalConsole", + "integratedTerminal", + "externalTerminal" + ], + "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", + "default": "integratedTerminal" + }, + "args": { + "type": "array", + "description": "Command line arguments passed to the program.", + "default": [], + "items": { + "filePath": "string", + "serverPort": "string" + } + }, + "rules": { + "type": "array", + "description": "Debugger rules.", + "default": [], + "items": { + "path": "string", + "include": "boolean" + } + } + } + } + }, + "initialConfigurations": [ + { + "type": "deviceSimulatorExpress", + "request": "launch", + "name": "Device Simulator Express Debugger", + "console": "integratedTerminal" + } + ], + "configurationSnippets": [ + { + "label": "Device Simulator Express Debugger : Launch", + "description": "Device Simulator Express Debugger - A configuration for debugging a python code file for the Device Simulator Express simulator.", + "body": { + "type": "deviceSimulatorExpress", + "request": "launch", + "name": "Device Simulator Express Debugger", + "console": "integratedTerminal" + } + } + ] } - } ] - } + }, + "scripts": { + "start": "webpack-dev-server", + "vscode:prepublish": "npm run compile", + "build": "gulp build", + "clean": "gulp clean", + "compile": "npm-run-all compile:*", + "compile:extension": "gulp compile", + "compile:views": "webpack --mode development", + "watch": "npm-run-all -p watch:*", + "watch:extension": "tsc --watch", + "watch:views": "webpack --watch --mode development", + "pretest": "npm run compile", + "test": "npm-run-all test:*", + "test:extension-tests": "node ./out/test/runTest.js", + "test:ts": "jest", + "test:api-tests": "pytest src", + "lint": "npm-run-all lint:*", + "lint:ts": "tslint -c tslint.json src/**/*.{ts,tsx}", + "lint:python": "pylint src", + "format": "npm-run-all format:*", + "format:ts": "prettier --config .prettierrc.yaml --write src/**/*.{css,ts,tsx}", + "format:python": "black src", + "check": "npm-run-all check:*", + "check:ts": "prettier --config .prettierrc.yaml --check src/**/*.{css,ts,tsx}", + "check:python": "black src --check", + "package": "vsce package" + }, + "devDependencies": { + "@types/glob": "^7.1.1", + "@types/node": "^10.12.21", + "@types/react": "16.8.6", + "@types/react-dom": "16.8.4", + "@types/vscode": "^1.34.0", + "css-loader": "^1.0.0", + "del": "^4.0.0", + "event-stream": "^4.0.1", + "gulp": "^4.0.2", + "gulp-cli": "^2.1.0", + "gulp-filter": "^5.1.0", + "gulp-sourcemaps": "^2.6.5", + "gulp-typescript": "^5.0.1", + "less": "^3.7.0", + "less-loader": "^4.1.0", + "mocha": "^6.1.4", + "npm-run-all": "^4.1.3", + "prettier": "^1.19.1", + "react-scripts": "3.0.1", + "style-loader": "^0.21.0", + "ts-import-plugin": "^1.5.4", + "ts-loader": "^4.4.2", + "tslint": "^5.12.1", + "tslint-config-prettier": "^1.18.0", + "tslint-microsoft-contrib": "^6.1.0", + "tslint-react": "^3.6.0", + "tslint-react-hooks": "^2.0.0", + "typescript": "^3.3.1", + "typescript-react-intl": "^0.4.0", + "version-from-git": "^1.1.1", + "vsce": "^1.47.0", + "vscode-nls-dev": "^3.2.6", + "vscode-test": "^1.0.0", + "webpack": "^4.15.1", + "webpack-cli": "^3.0.8" + }, + "dependencies": { + "@babel/preset-typescript": "^7.8.3", + "@testing-library/jest-dom": "^5.0.2", + "@testing-library/react": "^9.4.0", + "@types/jest": "^24.9.0", + "@types/open": "^6.1.0", + "@types/react-test-renderer": "^16.9.0", + "@types/socket.io": "^2.1.2", + "babel-jest": "^25.1.0", + "compare-versions": "^3.5.1", + "eventemitter2": "^5.0.1", + "glob": "^7.1.4", + "jest": "^25.1.0", + "jest-transform-css": "^2.0.0", + "office-ui-fabric-react": "^7.85.0", + "open": "^6.4.0", + "os": "^0.1.1", + "react": "^16.9.0", + "react-dom": "^16.9.0", + "react-intl": "^3.1.9", + "react-test-renderer": "^16.9.0", + "socket.io": "^2.2.0", + "svg-inline-react": "^3.1.0", + "ts-jest": "^25.0.0", + "util": "^0.12.1", + "vscode-extension-telemetry": "^0.1.1", + "vscode-nls": "^4.1.0" + }, + "eslintConfig": { + "extends": "react-app" + }, + "extensionDependencies": [ + "ms-python.python" ] - }, - "scripts": { - "vscode:prepublish": "npm run compile", - "build": "gulp build", - "clean": "gulp clean", - "compile": "npm-run-all compile:*", - "compile:extension": "gulp compile", - "compile:views": "webpack --mode development", - "watch": "npm-run-all -p watch:*", - "watch:extension": "tsc --watch", - "watch:views": "webpack --watch --mode development", - "pretest": "npm run compile", - "test": "npm-run-all test:*", - "test:extension-tests": "node ./out/test/runTest.js", - "test:ts": "jest", - "test:api-tests": "pytest src", - "lint": "npm-run-all lint:*", - "lint:ts": "tslint -c tslint.json src/**/*.{ts,tsx}", - "lint:python": "pylint src", - "format": "npm-run-all format:*", - "format:ts": "prettier --config .prettierrc.yaml --write src/**/*.{css,ts,tsx}", - "format:python": "black src", - "check": "npm-run-all check:*", - "check:ts": "prettier --config .prettierrc.yaml --check src/**/*.{css,ts,tsx}", - "check:python": "black src --check", - "package": "vsce package" - }, - "devDependencies": { - "@types/glob": "^7.1.1", - "@types/node": "^10.12.21", - "@types/react": "16.8.6", - "@types/react-dom": "16.8.4", - "@types/vscode": "^1.34.0", - "css-loader": "^1.0.0", - "del": "^4.0.0", - "event-stream": "^4.0.1", - "gulp": "^4.0.2", - "gulp-cli": "^2.1.0", - "gulp-filter": "^5.1.0", - "gulp-sourcemaps": "^2.6.5", - "gulp-typescript": "^5.0.1", - "less": "^3.7.0", - "less-loader": "^4.1.0", - "mocha": "^6.1.4", - "npm-run-all": "^4.1.3", - "prettier": "^1.19.1", - "react-scripts": "^3.3.1", - "style-loader": "^0.21.0", - "ts-import-plugin": "^1.5.4", - "ts-loader": "^4.4.2", - "tslint": "^5.12.1", - "tslint-config-prettier": "^1.18.0", - "tslint-microsoft-contrib": "^6.1.0", - "tslint-react": "^3.6.0", - "tslint-react-hooks": "^2.0.0", - "typescript": "^3.3.1", - "typescript-react-intl": "^0.4.0", - "version-from-git": "^1.1.1", - "vsce": "^1.47.0", - "vscode-nls-dev": "^3.3.1", - "vscode-test": "^1.0.0", - "webpack": "^4.15.1", - "webpack-cli": "^3.0.8" - }, - "dependencies": { - "@babel/preset-typescript": "^7.8.3", - "@testing-library/jest-dom": "^5.0.2", - "@testing-library/react": "^9.4.0", - "@types/jest": "^24.9.0", - "@types/open": "^6.1.0", - "@types/react-test-renderer": "^16.9.0", - "@types/socket.io": "^2.1.2", - "babel-jest": "^25.1.0", - "compare-versions": "^3.5.1", - "eventemitter2": "^5.0.1", - "glob": "^7.1.4", - "jest": "^25.1.0", - "jest-transform-css": "^2.0.0", - "office-ui-fabric-react": "^7.85.0", - "open": "^6.4.0", - "os": "^0.1.1", - "react": "^16.9.0", - "react-dom": "^16.9.0", - "react-intl": "^3.1.9", - "react-test-renderer": "^16.9.0", - "socket.io": "^2.2.0", - "svg-inline-react": "^3.1.0", - "ts-jest": "^25.0.0", - "util": "^0.12.1", - "vscode-extension-telemetry": "^0.1.1", - "vscode-nls": "^4.1.0" - }, - "eslintConfig": { - "extends": "react-app" - }, - "extensionDependencies": [ - "ms-python.python" - ] } diff --git a/src/adafruit_circuitplayground/constants.py b/src/adafruit_circuitplayground/constants.py index 26642a710..5cb698560 100644 --- a/src/adafruit_circuitplayground/constants.py +++ b/src/adafruit_circuitplayground/constants.py @@ -7,6 +7,8 @@ BRIGHTNESS_RANGE_ERROR = "The brightness value should be a number between 0 and 1." +CPX = "CPX" + INDEX_ERROR = ( "The index is not a valid number, you can access the Neopixels from 0 to 9." ) diff --git a/src/adafruit_circuitplayground/debugger_communication_client.py b/src/adafruit_circuitplayground/debugger_communication_client.py index 0536550ea..d48fe748a 100644 --- a/src/adafruit_circuitplayground/debugger_communication_client.py +++ b/src/adafruit_circuitplayground/debugger_communication_client.py @@ -4,8 +4,26 @@ import sys import json import socketio +import copy from . import express from . import constants as CONSTANTS +from common import utils + + +previous_state = {} + +# similar to utils.send_to_simulator, but for debugging +# (needs handle to device-specific debugger) +def debug_show(state): + global previous_state + + if state != previous_state: + previous_state = copy.deepcopy(state) + + updated_state = utils.update_state_with_device_name(state, CONSTANTS.CPX) + message = utils.create_message(updated_state) + + update_state(json.dumps(message)) # Create Socket Client diff --git a/src/adafruit_circuitplayground/express.py b/src/adafruit_circuitplayground/express.py index f08e1b65c..16b6807a6 100644 --- a/src/adafruit_circuitplayground/express.py +++ b/src/adafruit_circuitplayground/express.py @@ -5,12 +5,14 @@ import sys import os import playsound +from common import utils from .pixel import Pixel -from . import utils + from . import constants as CONSTANTS from collections import namedtuple from applicationinsights import TelemetryClient from .telemetry import telemetry_py +from . import debugger_communication_client Acceleration = namedtuple("acceleration", ["x", "y", "z"]) @@ -105,7 +107,10 @@ def light(self): return self.__state["light"] def __show(self): - utils.show(self.__state, self.__debug_mode) + if self.__debug_mode: + debugger_communication_client.debug_send_to_simulator(self.__state) + else: + utils.send_to_simulator(self.__state, CONSTANTS.CPX) def __touch(self, i): return self.__state["touch"][i - 1] diff --git a/src/adafruit_circuitplayground/pixel.py b/src/adafruit_circuitplayground/pixel.py index 3833de2f7..631311ce8 100644 --- a/src/adafruit_circuitplayground/pixel.py +++ b/src/adafruit_circuitplayground/pixel.py @@ -3,11 +3,13 @@ import json import sys +from common import utils from . import constants as CONSTANTS -from . import utils + from applicationinsights import TelemetryClient from . import constants as CONSTANTS from .telemetry import telemetry_py +from . import debugger_communication_client class Pixel: @@ -19,7 +21,11 @@ def __init__(self, state, debug_mode=False): def show(self): # Send the state to the extension so that React re-renders the Webview - utils.show(self.__state, self.__debug_mode) + # or send the state to the debugger (within this library) + if self.__debug_mode: + debugger_communication_client.debug_send_to_simulator(self.__state) + else: + utils.send_to_simulator(self.__state, CONSTANTS.CPX) def __show_if_auto_write(self): if self.auto_write: diff --git a/src/adafruit_circuitplayground/test/test_utils.py b/src/adafruit_circuitplayground/test/test_utils.py index 3c25128e6..a3c38e1b1 100644 --- a/src/adafruit_circuitplayground/test/test_utils.py +++ b/src/adafruit_circuitplayground/test/test_utils.py @@ -3,7 +3,7 @@ from unittest import mock from .. import constants as CONSTANTS -from .. import utils +from common import utils class TestUtils(object): diff --git a/src/adafruit_circuitplayground/utils.py b/src/adafruit_circuitplayground/utils.py deleted file mode 100644 index 14fddf227..000000000 --- a/src/adafruit_circuitplayground/utils.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -import sys -import json -import copy -import time -from . import constants as CONSTANTS -from . import debugger_communication_client -from applicationinsights import TelemetryClient - -previous_state = {} - - -def show(state, debug_mode=False): - global previous_state - if state != previous_state: - previous_state = copy.deepcopy(state) - message = {"type": "state", "data": json.dumps(state)} - if debug_mode: - debugger_communication_client.update_state(json.dumps(message)) - else: - print(json.dumps(message) + "\0", end="", file=sys.__stdout__, flush=True) - time.sleep(CONSTANTS.TIME_DELAY) - - -def remove_leading_slashes(string): - string = string.lstrip("\\/") - return string - - -def escape_if_OSX(file_name): - if sys.platform.startswith(CONSTANTS.MAC_OS): - file_name = file_name.replace(" ", "%20") - return file_name diff --git a/src/common/__init__.py b/src/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/common/constants.py b/src/common/constants.py new file mode 100644 index 000000000..97ea32386 --- /dev/null +++ b/src/common/constants.py @@ -0,0 +1,3 @@ +MAC_OS = "darwin" + +TIME_DELAY = 0.03 diff --git a/src/common/utils.py b/src/common/utils.py new file mode 100644 index 000000000..a468044f7 --- /dev/null +++ b/src/common/utils.py @@ -0,0 +1,49 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +from . import constants as CONSTANTS +import json +import copy +import time +import sys + +previous_state = {} + + +def update_state_with_device_name(state, device_name): + updated_state = dict(state) + + state_ext = { + "device_name": device_name, + } + updated_state.update(state_ext) + + return updated_state + + +def create_message(state): + message = {"type": "state", "data": json.dumps(state)} + return message + + +def send_to_simulator(state, device_name): + global previous_state + + updated_state = update_state_with_device_name(state, device_name) + message = create_message(updated_state) + + if updated_state != previous_state: + previous_state = copy.deepcopy(updated_state) + print(json.dumps(message) + "\0", end="", file=sys.__stdout__, flush=True) + time.sleep(CONSTANTS.TIME_DELAY) + + +def remove_leading_slashes(string): + string = string.lstrip("\\/") + return string + + +def escape_if_OSX(file_name): + if sys.platform.startswith(CONSTANTS.MAC_OS): + file_name = file_name.replace(" ", "%20") + return file_name diff --git a/src/constants.ts b/src/constants.ts index 0a2db62dc..495fe0c04 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -22,6 +22,10 @@ export const CONFIG = { }; export const CONSTANTS = { + DEVICE_NAME: { + CPX: "CPX", + MICROBIT: "micro:bit", + }, DEBUG_CONFIGURATION_TYPE: "deviceSimulatorExpress", DEPENDENCY_CHECKER: { PIP3: "pip3", @@ -317,14 +321,7 @@ export enum TelemetryEventName { PERFORMANCE_NEW_FILE = "PERFORMANCE.NEW.FILE", PERFORMANCE_OPEN_SIMULATOR = "PERFORMANCE.OPEN.SIMULATOR", } - -export enum WebviewMessages { - BUTTON_PRESS = "button-press", - PLAY_SIMULATOR = "play-simulator", - SENSOR_CHANGED = "sensor-changed", - REFRESH_SIMULATOR = "refresh-simulator", - SLIDER_TELEMETRY = "slider-telemetry", -} +export const DEFAULT_DEVICE = CONSTANTS.DEVICE_NAME.CPX; // tslint:disable-next-line: no-namespace export namespace DialogResponses { diff --git a/src/extension.ts b/src/extension.ts index 888015803..60144e10a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -10,10 +10,10 @@ import { CONFIG, CONSTANTS, CPX_CONFIG_FILE, + DEFAULT_DEVICE, DialogResponses, SERVER_INFO, TelemetryEventName, - WebviewMessages, } from "./constants"; import { CPXWorkspace } from "./cpxWorkspace"; import { DebuggerCommunicationServer } from "./debuggerCommunicationServer"; @@ -22,6 +22,7 @@ import { SerialMonitor } from "./serialMonitor"; import { SimulatorDebugConfigurationProvider } from "./simulatorDebugConfigurationProvider"; import TelemetryAI from "./telemetry/telemetryAI"; import { UsbDetector } from "./usbDetector"; +import { WEBVIEW_MESSAGES } from "./view/constants"; let currentFileAbsPath: string = ""; let currentTextDocument: vscode.TextDocument; @@ -34,6 +35,9 @@ let debuggerCommunicationHandler: DebuggerCommunicationServer; let firstTimeClosed: boolean = true; let shouldShowInvalidFileNamePopup: boolean = true; let shouldShowRunCodePopup: boolean = true; + +let currentActiveDevice: string = DEFAULT_DEVICE; + export let outChannel: vscode.OutputChannel | undefined; function loadScript(context: vscode.ExtensionContext, scriptPath: string) { @@ -50,7 +54,11 @@ const setPathAndSendMessage = ( if (currentPanel) { currentPanel.webview.postMessage({ command: "current-file", - state: { running_file: newFilePath }, + active_device: currentActiveDevice, + + state: { + running_file: newFilePath, + }, }); } }; @@ -148,7 +156,7 @@ export async function activate(context: vscode.ExtensionContext) { message => { const messageJson = JSON.stringify(message.text); switch (message.command) { - case WebviewMessages.BUTTON_PRESS: + case WEBVIEW_MESSAGES.BUTTON_PRESS: // Send input to the Python process handleButtonPressTelemetry(message.text); console.log(`About to write ${messageJson} \n`); @@ -165,7 +173,7 @@ export async function activate(context: vscode.ExtensionContext) { ); } break; - case WebviewMessages.PLAY_SIMULATOR: + case WEBVIEW_MESSAGES.TOGGLE_PLAY_STOP: console.log(`Play button ${messageJson} \n`); if (message.text.state as boolean) { setPathAndSendMessage( @@ -187,8 +195,16 @@ export async function activate(context: vscode.ExtensionContext) { } else { killProcessIfRunning(); } + + if (childProcess) { + childProcess.stdin.write( + messageJson + "\n" + ); + } + break; - case WebviewMessages.SENSOR_CHANGED: + + case WEBVIEW_MESSAGES.SENSOR_CHANGED: checkForTelemetry(message.text); console.log(`Sensor changed ${messageJson} \n`); if ( @@ -204,13 +220,17 @@ export async function activate(context: vscode.ExtensionContext) { ); } break; - case WebviewMessages.REFRESH_SIMULATOR: + case WEBVIEW_MESSAGES.REFRESH_SIMULATOR: console.log("Refresh button"); runSimulatorCommand(); break; - case WebviewMessages.SLIDER_TELEMETRY: + case WEBVIEW_MESSAGES.SLIDER_TELEMETRY: handleSensorTelemetry(message.text); break; + case WEBVIEW_MESSAGES.SWITCH_DEVICE: + switchDevice(message.text.active_device); + killProcessIfRunning(); + break; default: vscode.window.showInformationMessage( CONSTANTS.ERROR.UNEXPECTED_MESSAGE @@ -336,7 +356,10 @@ export async function activate(context: vscode.ExtensionContext) { if (childProcess !== undefined) { if (currentPanel) { console.info("Sending clearing state command"); - currentPanel.webview.postMessage({ command: "reset-state" }); + currentPanel.webview.postMessage({ + command: "reset-state", + active_device: currentActiveDevice, + }); } // TODO: We need to check the process was correctly killed childProcess.kill(); @@ -443,7 +466,10 @@ export async function activate(context: vscode.ExtensionContext) { } // Activate the run webview button - currentPanel.webview.postMessage({ command: "activate-play" }); + currentPanel.webview.postMessage({ + command: "activate-play", + active_device: currentActiveDevice, + }); childProcess = cp.spawn(pythonExecutableName, [ utils.getPathToScript( @@ -480,12 +506,19 @@ export async function activate(context: vscode.ExtensionContext) { console.log( `Process state output = ${messageToWebview.data}` ); - currentPanel.webview.postMessage({ - command: "set-state", - state: JSON.parse( - messageToWebview.data - ), - }); + const messageData = JSON.parse( + messageToWebview.data + ); + if ( + messageData.device_name === + currentActiveDevice + ) { + currentPanel.webview.postMessage({ + active_device: currentActiveDevice, + command: "set-state", + state: messageData, + }); + } break; case "print": @@ -530,6 +563,7 @@ export async function activate(context: vscode.ExtensionContext) { if (currentPanel) { console.log("Sending clearing state command"); currentPanel.webview.postMessage({ + active_device: currentActiveDevice, command: "reset-state", }); } @@ -832,6 +866,7 @@ export async function activate(context: vscode.ExtensionContext) { if (currentPanel) { debuggerCommunicationHandler.setWebview(currentPanel); currentPanel.webview.postMessage({ + currentActiveDevice, command: "activate-play", }); } @@ -861,7 +896,10 @@ export async function activate(context: vscode.ExtensionContext) { debuggerCommunicationHandler = undefined; } if (currentPanel) { - currentPanel.webview.postMessage({ command: "reset-state" }); + currentPanel.webview.postMessage({ + command: "reset-state", + active_device: currentActiveDevice, + }); } } }); @@ -1050,6 +1088,9 @@ function getWebviewContent(context: vscode.ExtensionContext) { `; } +function switchDevice(deviceName: string) { + currentActiveDevice = deviceName; +} // this method is called when your extension is deactivated export async function deactivate() { diff --git a/src/microbit/model/constants.py b/src/microbit/model/constants.py index ebe061a18..7f566ea90 100644 --- a/src/microbit/model/constants.py +++ b/src/microbit/model/constants.py @@ -1,3 +1,5 @@ +MICROBIT = "micro:bit" + # string arguments for constructor BLANK_5X5 = "00000:00000:00000:00000:00000:" @@ -118,3 +120,5 @@ NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" UNSUPPORTED_ADD_TYPE = "unsupported types for __add__:" SAME_SIZE_ERR = "images must be the same size" + +TIME_DELAY = 0.03 diff --git a/src/microbit/model/display.py b/src/microbit/model/display.py index 82784ba91..d0531c131 100644 --- a/src/microbit/model/display.py +++ b/src/microbit/model/display.py @@ -1,6 +1,7 @@ import copy import time import threading +from common import utils from . import constants as CONSTANTS from .image import Image @@ -73,6 +74,7 @@ def scroll(self, value, delay=150, wait=True, loop=False, monospace=False): appended_image, x, 0, CONSTANTS.LED_WIDTH, CONSTANTS.LED_HEIGHT ) self.__lock.release() + self.__update_client() Display.sleep_ms(delay) @@ -132,6 +134,7 @@ def show(self, value, delay=400, wait=True, loop=False, clear=False): self.__image = image self.__lock.release() + self.__update_client() if use_delay: Display.sleep_ms(delay) @@ -151,11 +154,13 @@ def set_pixel(self, x, y, value): self.__lock.acquire() self.__image.set_pixel(x, y, value) self.__lock.release() + self.__update_client() def clear(self): self.__lock.acquire() self.__image = Image() self.__lock.release() + self.__update_client() def on(self): self.__on = True @@ -172,13 +177,13 @@ def read_light_level(self): # Helpers def __get_array(self): + self.__lock.acquire() if self.is_on(): - self.__lock.acquire() leds = copy.deepcopy(self.__image._Image__LED) - self.__lock.release() - return leds else: - return self.__blank_image._Image__LED + leds = self.__blank_image._Image__LED + self.__lock.release() + return leds @staticmethod def __get_image_from_char(c): @@ -264,6 +269,10 @@ def __create_scroll_image(images): return scroll_image + def __update_client(self): + sendable_json = {"leds": self.__get_array()} + utils.send_to_simulator(sendable_json, CONSTANTS.MICROBIT) + @staticmethod def sleep_ms(ms): time.sleep(ms / 1000) diff --git a/src/microbit/shim.py b/src/microbit/shim.py index a47d50bd0..86302057a 100644 --- a/src/microbit/shim.py +++ b/src/microbit/shim.py @@ -3,6 +3,7 @@ microbit = microbit_model.mb Image = image.Image +display = microbit.display def sleep(n): diff --git a/src/microbit/test/test_display.py b/src/microbit/test/test_display.py index 23f3abdda..47559af88 100644 --- a/src/microbit/test/test_display.py +++ b/src/microbit/test/test_display.py @@ -2,6 +2,7 @@ import threading from unittest import mock +from common import utils from ..model import constants as CONSTANTS from ..model.display import Display from ..model.image import Image @@ -17,6 +18,7 @@ class TestDisplay(object): def setup_method(self): self.display = Display() + utils.send_to_simulator = mock.Mock() @pytest.mark.parametrize("x, y, brightness", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) def test_set_and_get_pixel(self, x, y, brightness): diff --git a/src/process_user_code.py b/src/process_user_code.py index 1991f9816..8bc283c7e 100644 --- a/src/process_user_code.py +++ b/src/process_user_code.py @@ -30,6 +30,7 @@ # This import must happen after the sys.path is modified from adafruit_circuitplayground.express import cpx from adafruit_circuitplayground.telemetry import telemetry_py +from microbit.model.microbit_model import mb # Handle User Inputs Thread @@ -43,7 +44,7 @@ def run(self): sys.stdin.flush() try: new_state = json.loads(read_val) - for event in CONSTANTS.EXPECTED_INPUT_EVENTS: + for event in CONSTANTS.EXPECTED_INPUT_EVENTS_CPX: cpx._Express__state[event] = new_state.get( event, cpx._Express__state[event] ) @@ -95,6 +96,7 @@ def execute_user_code(abs_path_to_code_file): user_code = threading.Thread(args=(sys.argv[1],), target=execute_user_code) telemetry_state = json.loads(sys.argv[2]) + telemetry_py._Telemetry__enable_telemetry = telemetry_state.get( CONSTANTS.ENABLE_TELEMETRY, True ) diff --git a/src/python_constants.py b/src/python_constants.py index a700a3143..4b9d0338f 100644 --- a/src/python_constants.py +++ b/src/python_constants.py @@ -4,7 +4,7 @@ CPX_DRIVE_NAME = "CIRCUITPY" ENABLE_TELEMETRY = "enable_telemetry" -EXPECTED_INPUT_EVENTS = [ +EXPECTED_INPUT_EVENTS_CPX = [ "button_a", "button_b", "switch", @@ -42,3 +42,7 @@ WINDOWS_OS = "win32" DEFAULT_PORT = "5577" + +CPX = "CPX" + +MICROBIT = "micro:bit" diff --git a/src/view/App.tsx b/src/view/App.tsx index 1c9cbcb98..c22402e9b 100644 --- a/src/view/App.tsx +++ b/src/view/App.tsx @@ -6,8 +6,9 @@ import { PivotItem } from "office-ui-fabric-react"; import * as React from "react"; import "./App.css"; import { Tab } from "./components/tab/Tab"; -import { DEVICE_LIST_KEY } from "./constants"; +import { DEVICE_LIST_KEY, WEBVIEW_MESSAGES } from "./constants"; import { Device } from "./container/device/Device"; +import { sendMessage } from "./utils/MessageUtils"; interface IState { currentDevice: string; @@ -36,6 +37,9 @@ class App extends React.Component<{}, IState> { handleDeviceChange = (item?: PivotItem) => { if (item && item.props && item.props.itemKey) { + sendMessage(WEBVIEW_MESSAGES.SWITCH_DEVICE, { + active_device: item.props.itemKey, + }); this.setState({ currentDevice: item.props.itemKey }); } }; diff --git a/src/view/components/cpx/Cpx.tsx b/src/view/components/cpx/Cpx.tsx index a762740af..57afaadd0 100644 --- a/src/view/components/cpx/Cpx.tsx +++ b/src/view/components/cpx/Cpx.tsx @@ -2,10 +2,10 @@ // Licensed under the MIT license. import * as React from "react"; -import Simulator from "../../components/Simulator"; import { TOOLBAR_ICON_ID } from "../../components/toolbar/SensorModalUtils"; import ToolBar from "../../components/toolbar/ToolBar"; import * as TOOLBAR_SVG from "../../svgs/toolbar_svg"; +import Simulator from "./CpxSimulator"; // Component grouping the functionality for circuit playground express diff --git a/src/view/components/cpx/CpxImage.tsx b/src/view/components/cpx/CpxImage.tsx index 16c0f9808..fd3688b12 100644 --- a/src/view/components/cpx/CpxImage.tsx +++ b/src/view/components/cpx/CpxImage.tsx @@ -20,7 +20,6 @@ interface IProps { onMouseLeave: (button: HTMLElement, event: Event) => void; } -// export class CpxImage extends React.Component { componentDidMount() { const svgElement = window.document.getElementById("cpx_svg"); diff --git a/src/view/components/Simulator.tsx b/src/view/components/cpx/CpxSimulator.tsx similarity index 92% rename from src/view/components/Simulator.tsx rename to src/view/components/cpx/CpxSimulator.tsx index 0d6826492..fb3b23e09 100644 --- a/src/view/components/Simulator.tsx +++ b/src/view/components/cpx/CpxSimulator.tsx @@ -2,14 +2,16 @@ // Licensed under the MIT license. import * as React from "react"; -import { CONSTANTS } from "../constants"; -import "../styles/Simulator.css"; -import PlayLogo from "../svgs/play_svg"; -import StopLogo from "../svgs/stop_svg"; -import { BUTTON_NEUTRAL, BUTTON_PRESSED } from "./cpx/Cpx_svg_style"; -import { CpxImage, updatePinTouch, updateSwitch } from "./cpx/CpxImage"; -import Dropdown from "./Dropdown"; -import ActionBar from "./simulator/ActionBar"; +import { CONSTANTS, WEBVIEW_MESSAGES, DEVICE_LIST_KEY } from "../../constants"; +import { sendMessage } from "../../utils/MessageUtils"; + +import "../../styles/Simulator.css"; +import PlayLogo from "../../svgs/play_svg"; +import StopLogo from "../../svgs/stop_svg"; +import Dropdown from "../Dropdown"; +import ActionBar from "../simulator/ActionBar"; +import { BUTTON_NEUTRAL, BUTTON_PRESSED } from "./Cpx_svg_style"; +import { CpxImage, updatePinTouch, updateSwitch } from "./CpxImage"; interface ICpxState { pixels: number[][]; @@ -29,9 +31,6 @@ interface IState { cpx: ICpxState; play_button: boolean; } -interface IMyProps { - children?: any; -} const DEFAULT_CPX_STATE: ICpxState = { brightness: 1.0, @@ -55,18 +54,8 @@ const DEFAULT_CPX_STATE: ICpxState = { shake: false, }; -interface vscode { - postMessage(message: any): void; -} - -declare const vscode: vscode; - -const sendMessage = (type: string, state: any) => { - vscode.postMessage({ command: type, text: state }); -}; - -class Simulator extends React.Component { - constructor(props: IMyProps) { +class Simulator extends React.Component<{}, IState> { + constructor(props: Readonly<{}>) { super(props); this.state = { active_editors: [], @@ -88,11 +77,13 @@ class Simulator extends React.Component { handleMessage = (event: any): void => { const message = event.data; // The JSON data our extension sent + if (message.active_device !== DEVICE_LIST_KEY.CPX) { + return; + } switch (message.command) { case "reset-state": console.log("Clearing the state"); this.setState({ - ...this.state, cpx: DEFAULT_CPX_STATE, play_button: false, }); @@ -102,14 +93,12 @@ class Simulator extends React.Component { "Setting the state: " + JSON.stringify(message.state) ); this.setState({ - ...this.state, cpx: message.state, play_button: true, }); break; case "activate-play": this.setState({ - ...this.state, play_button: !this.state.play_button, }); break; @@ -119,14 +108,12 @@ class Simulator extends React.Component { message.state.activePythonEditors ); this.setState({ - ...this.state, active_editors: message.state.activePythonEditors, }); break; case "current-file": console.log("Setting current file", message.state.running_file); this.setState({ - ...this.state, running_file: message.state.running_file, }); break; @@ -184,7 +171,8 @@ class Simulator extends React.Component { } protected togglePlayClick() { - sendMessage("play-simulator", { + sendMessage(WEBVIEW_MESSAGES.TOGGLE_PLAY_STOP, { + active_device: CONSTANTS.DEVICE_NAME.CPX, selected_file: this.state.selected_file, state: !this.state.play_button, }); @@ -197,7 +185,7 @@ class Simulator extends React.Component { } protected refreshSimulatorClick() { - sendMessage("refresh-simulator", true); + sendMessage(WEBVIEW_MESSAGES.REFRESH_SIMULATOR, true); const button = window.document.getElementById( CONSTANTS.ID_NAME.REFRESH_BUTTON ); @@ -208,7 +196,6 @@ class Simulator extends React.Component { protected onSelectBlur(event: React.FocusEvent) { this.setState({ - ...this.state, selected_file: event.currentTarget.value, }); } diff --git a/src/view/components/microbit/Microbit.tsx b/src/view/components/microbit/Microbit.tsx index 2fd6d3ae9..d7e906429 100644 --- a/src/view/components/microbit/Microbit.tsx +++ b/src/view/components/microbit/Microbit.tsx @@ -3,19 +3,17 @@ import * as React from "react"; import "../../styles/Simulator.css"; -import { MicrobitImage } from "./MicrobitImage"; +import { MicrobitSimulator } from "./MicrobitSimulator"; // Component grouping the functionality for micro:bit functionalities export class Microbit extends React.Component { render() { return ( -
-
- -
- {/* Implement actionbar here */} -
+ + + {/* Implement toolbar here */} + ); } } diff --git a/src/view/components/microbit/MicrobitImage.tsx b/src/view/components/microbit/MicrobitImage.tsx index 383d5d637..98c554607 100644 --- a/src/view/components/microbit/MicrobitImage.tsx +++ b/src/view/components/microbit/MicrobitImage.tsx @@ -3,8 +3,79 @@ import * as React from "react"; import "../../styles/Microbit.css"; -import { MICROBIT_SVG } from "./Microbit_svg"; +import { MicrobitSvg } from "./Microbit_svg"; -export const MicrobitImage: React.FC = () => { - return MICROBIT_SVG; +interface EventTriggers { + onMouseUp: (button: HTMLElement, event: Event, buttonKey: string) => void; + onMouseDown: (button: HTMLElement, event: Event, buttonKey: string) => void; + onMouseLeave: ( + button: HTMLElement, + event: Event, + buttonKey: string + ) => void; +} +interface IProps { + eventTriggers: EventTriggers; + leds: number[][]; +} + +// Displays the SVG and call necessary svg modification. +export class MicrobitImage extends React.Component { + private svgRef: React.RefObject = React.createRef(); + constructor(props: IProps) { + super(props); + } + componentDidMount() { + const svgElement = this.svgRef.current; + if (svgElement) { + updateAllLeds(this.props.leds, svgElement.getLeds()); + setupAllButtons(this.props.eventTriggers, svgElement.getButtons()); + } + } + componentDidUpdate() { + if (this.svgRef.current) { + updateAllLeds(this.props.leds, this.svgRef.current.getLeds()); + } + } + render() { + return ; + } +} +const setupButton = ( + buttonElement: HTMLElement, + eventTriggers: EventTriggers, + key: string +) => { + buttonElement.onmousedown = e => { + eventTriggers.onMouseDown(buttonElement, e, key); + }; + buttonElement.onmouseup = e => { + eventTriggers.onMouseUp(buttonElement, e, key); + }; + buttonElement.onmouseleave = e => { + eventTriggers.onMouseLeave(buttonElement, e, key); + }; +}; +const setupAllButtons = (eventTriggers: EventTriggers, buttonRefs: Object) => { + for (const [key, ref] of Object.entries(buttonRefs)) { + if (ref.current) { + setupButton(ref.current, eventTriggers, key); + } + } +}; +const updateAllLeds = ( + leds: number[][], + ledRefs: Array>[] +) => { + for (let j = 0; j < leds.length; j++) { + for (let i = 0; i < leds[0].length; i++) { + const ledElement = ledRefs[j][i].current; + if (ledElement) { + setupLed(ledElement, leds[i][j]); + } + } + } +}; +const setupLed = (ledElement: SVGRectElement, brightness: number) => { + ledElement.style.opacity = (brightness / 10).toString(); }; diff --git a/src/view/components/microbit/MicrobitSimulator.tsx b/src/view/components/microbit/MicrobitSimulator.tsx new file mode 100644 index 000000000..5899334d7 --- /dev/null +++ b/src/view/components/microbit/MicrobitSimulator.tsx @@ -0,0 +1,142 @@ +import * as React from "react"; +import CONSTANTS, { WEBVIEW_MESSAGES, DEVICE_LIST_KEY } from "../../constants"; +import PlayLogo from "../../svgs/play_svg"; +import StopLogo from "../../svgs/stop_svg"; +import { sendMessage } from "../../utils/MessageUtils"; +import Dropdown from "../Dropdown"; +import ActionBar from "../simulator/ActionBar"; +import { MicrobitImage } from "./MicrobitImage"; + +const initialLedState = [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], +]; + +interface IState { + active_editors: string[]; + running_file: string; + leds: number[][]; + play_button: boolean; + selected_file: string; +} +export class MicrobitSimulator extends React.Component { + constructor() { + super({}); + this.state = { + leds: initialLedState, + play_button: false, + selected_file: "", + active_editors: [], + running_file: "", + }; + } + handleMessage = (event: any): void => { + const message = event.data; + + if (message.active_device !== DEVICE_LIST_KEY.MICROBIT) { + return; + } + + switch (message.command) { + case "reset-state": + this.setState({ + leds: initialLedState, + play_button: false, + }); + break; + case "set-state": + this.setState({ + leds: message.state.leds, + }); + break; + case "activate-play": + this.setState({ + play_button: !this.state.play_button, + }); + break; + case "visible-editors": + this.setState({ + active_editors: message.state.activePythonEditors, + }); + break; + case "current-file": + this.setState({ + running_file: message.state.running_file, + }); + break; + default: + console.log("Invalid message received from the extension."); + break; + } + }; + componentDidMount() { + window.addEventListener("message", this.handleMessage); + } + componentWillUnmount() { + window.removeEventListener("message", this.handleMessage); + } + + render() { + const playStopImage = this.state.play_button ? StopLogo : PlayLogo; + + return ( +
+
+ +
+
+ +
+ +
+ ); + } + protected togglePlayClick = () => { + sendMessage(WEBVIEW_MESSAGES.TOGGLE_PLAY_STOP, { + active_device: CONSTANTS.DEVICE_NAME.MICROBIT, + selected_file: this.state.selected_file, + state: !this.state.play_button, + }); + }; + protected onSelectFile(event: React.FocusEvent) { + this.setState({ + selected_file: event.currentTarget.value, + }); + } + protected refreshSimulatorClick = () => { + sendMessage(WEBVIEW_MESSAGES.REFRESH_SIMULATOR, true); + }; + protected onMouseUp(button: HTMLElement, event: Event, key: string) { + event.preventDefault(); + console.log(`To implement onMouseUp on ${key}`); + } + protected onMouseDown(button: HTMLElement, event: Event, key: string) { + event.preventDefault(); + console.log(`To implement onMouseDown ${key}`); + } + protected onMouseLeave(button: HTMLElement, event: Event, key: string) { + event.preventDefault(); + console.log(`To implement onMouseLeave ${key}`); + } +} diff --git a/src/view/components/microbit/Microbit_svg.tsx b/src/view/components/microbit/Microbit_svg.tsx index 0ff29a282..83dabaebd 100644 --- a/src/view/components/microbit/Microbit_svg.tsx +++ b/src/view/components/microbit/Microbit_svg.tsx @@ -4,1530 +4,1866 @@ // Adapted from : https://makecode.microbit.org/#editor import * as React from "react"; - +interface IRefObject { + [key: string]: React.RefObject; +} /* tslint:disable */ -export const MICROBIT_SVG = ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (0,0) - - - - (1,0) - - - - (2,0) - - - - (3,0) - - - - (4,0) - - - - (0,1) - - - - (1,1) - - - - (2,1) - - - - (3,1) - - - - (4,1) - - - - (0,2) - - - - (1,2) - - - - (2,2) - - - - (3,2) - - - - (4,2) - - - - (0,3) - - - - (1,3) - - - - (2,3) - - - - (3,3) - - - - (4,3) - - - - (0,4) - - - - (1,4) - - - - (2,4) - - - - (3,4) - - - - (4,4) - - - - - - - - - - P0, ANALOG IN - - - P1, ANALOG IN - - - P2, ANALOG IN - - - P3, ANALOG IN, LED Col 1 - - - P4, ANALOG IN, LED Col 2 - - - P5, BUTTON A - - - P6, LED Col 9 - - - P7, LED Col 8 - - - P8 - - - P9, LED Col 7 - - - P10, ANALOG IN, LED Col 3 - - - P11, BUTTON B - - - P12, RESERVED ACCESSIBILITY - - - P13, SPI - SCK - - - P14, SPI - MISO - - - P15, SPI - MOSI - - - P16, SPI - Chip Select - - - P17, +3v3 - - - P18, +3v3 - - - P19, I2C - SCL - - - P20, I2C - SDA - - - GND - - - GND - - - +3v3 - - - GND - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); +const N_LED_COLUMN = 5; +const N_LED_ROW = 5; +export class MicrobitSvg extends React.Component { + constructor(props: Readonly<{}>) { + super(props); + for (let j = 0; j < N_LED_ROW; j++) { + const led_row: React.RefObject[] = []; + for (let i = 0; i < N_LED_COLUMN; i++) { + led_row.push(React.createRef()); + } + this.ledRefs.push(led_row); + } + } + private svgRef: React.RefObject = React.createRef(); + + private buttonRefs: IRefObject = { + BTN_A: React.createRef(), + BTN_B: React.createRef(), + BTN_AB: React.createRef(), + }; + + private ledRefs: React.RefObject[][] = []; + public getSvgRef(): React.RefObject { + return this.svgRef; + } + public getButtons(): IRefObject { + return this.buttonRefs; + } + public getLeds(): React.RefObject[][] { + return this.ledRefs; + } + render() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (0,0) + + + + (1,0) + + + + (2,0) + + + + (3,0) + + + + (4,0) + + + + (0,1) + + + + (1,1) + + + + (2,1) + + + + (3,1) + + + + (4,1) + + + + (0,2) + + + + (1,2) + + + + (2,2) + + + + (3,2) + + + + (4,2) + + + + (0,3) + + + + (1,3) + + + + (2,3) + + + + (3,3) + + + + (4,3) + + + + (0,4) + + + + (1,4) + + + + (2,4) + + + + (3,4) + + + + (4,4) + + + + + + + + + + P0, ANALOG IN + + + P1, ANALOG IN + + + P2, ANALOG IN + + + P3, ANALOG IN, LED Col 1 + + + P4, ANALOG IN, LED Col 2 + + + P5, BUTTON A + + + P6, LED Col 9 + + + P7, LED Col 8 + + + P8 + + + P9, LED Col 7 + + + P10, ANALOG IN, LED Col 3 + + + P11, BUTTON B + + + P12, RESERVED ACCESSIBILITY + + + P13, SPI - SCK + + + P14, SPI - MISO + + + P15, SPI - MOSI + + + P16, SPI - Chip Select + + + P17, +3v3 + + + P18, +3v3 + + + P19, I2C - SCL + + + P20, I2C - SDA + + + GND + + + GND + + + +3v3 + + + GND + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/src/view/constants.ts b/src/view/constants.ts index 651a10c59..fc1f53631 100644 --- a/src/view/constants.ts +++ b/src/view/constants.ts @@ -46,8 +46,18 @@ export const CONSTANTS = { TOOLBAR_INFO: `Explore what's on the board:`, }; export enum DEVICE_LIST_KEY { - CPX = "cpx", - MICROBIT = "microbit", + CPX = "CPX", + MICROBIT = "micro:bit", +} + +// +export enum WEBVIEW_MESSAGES { + SWITCH_DEVICE = "switch-device", + REFRESH_SIMULATOR = "refresh-simulator", + TOGGLE_PLAY_STOP = "toggle-play-stop", + BUTTON_PRESS = "button-press", + SENSOR_CHANGED = "sensor-changed", + SLIDER_TELEMETRY = "slider-telemetry", } export default CONSTANTS; diff --git a/src/view/container/device/__snapshots__/Device.spec.tsx.snap b/src/view/container/device/__snapshots__/Device.spec.tsx.snap index bec919b26..dc4d9fbd9 100644 --- a/src/view/container/device/__snapshots__/Device.spec.tsx.snap +++ b/src/view/container/device/__snapshots__/Device.spec.tsx.snap @@ -7,6 +7,25 @@ exports[`Device component should render correctly 1`] = `
+
+
+ +
+
@@ -2449,7 +2468,7 @@ exports[`Device component should render correctly 1`] = ` style={ Object { "fill": "rgb(51, 51, 51)", - "visibility": "hidden", + "visibility": "visible", } } tabIndex={0} @@ -2497,7 +2516,7 @@ exports[`Device component should render correctly 1`] = ` style={ Object { "fill": "rgb(17, 17, 17)", - "visibility": "hidden", + "visibility": "visible", } } /> @@ -2546,6 +2565,65 @@ exports[`Device component should render correctly 1`] = `
+
+ + +
`; diff --git a/src/view/styles/Simulator.css b/src/view/styles/Simulator.css index 000983a53..99f14ee48 100644 --- a/src/view/styles/Simulator.css +++ b/src/view/styles/Simulator.css @@ -66,6 +66,7 @@ .microbit-container { max-width: 350px; + padding: 20px; } .cpx-container { width: 100%; diff --git a/src/view/utils/MessageUtils.tsx b/src/view/utils/MessageUtils.tsx new file mode 100644 index 000000000..41e093455 --- /dev/null +++ b/src/view/utils/MessageUtils.tsx @@ -0,0 +1,12 @@ +interface vscode { + postMessage(message: any): void; +} + +declare const vscode: vscode; + +export const sendMessage = ( + type: string, + state: TState +) => { + vscode.postMessage({ command: type, text: state }); +}; diff --git a/tslint.json b/tslint.json index 4b199b36e..534279116 100644 --- a/tslint.json +++ b/tslint.json @@ -1,32 +1,34 @@ { - "rulesDirectory": ["node_modules/tslint-microsoft-contrib"], - "extends": [ - "tslint:latest", - "tslint-react", - "tslint-config-prettier", - "tslint-react-hooks", - "tslint-microsoft-contrib/latest" - ], - "rules": { - "no-implicit-dependencies": [true, "dev"], - "no-string-throw": true, - "no-unused-expression": true, - "no-duplicate-variable": true, - "no-empty": false, - "no-relative-imports": false, - "max-func-body-length": false, - "curly": true, - "class-name": true, - "triple-equals": true, - "object-literal-sort-keys": true, - "react-hooks-nesting": "error", - "ordered-imports": true, - "import-name": false, - "member-access": false, - "no-console": false, - "jsx-boolean-value": false, - "no-unnecessary-semicolons": false, - "no-http-string": false - }, - "defaultSeverity": "warning" + "rulesDirectory": ["node_modules/tslint-microsoft-contrib"], + "extends": [ + "tslint:latest", + "tslint-react", + "tslint-config-prettier", + "tslint-react-hooks", + "tslint-microsoft-contrib/latest" + ], + "rules": { + "no-implicit-dependencies": [true, "dev"], + "no-string-throw": true, + "no-unused-expression": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-relative-imports": false, + "max-func-body-length": false, + "curly": true, + "class-name": true, + "triple-equals": true, + "object-literal-sort-keys": true, + "react-hooks-nesting": "error", + "ordered-imports": true, + "import-name": false, + "member-access": false, + "no-console": false, + "jsx-boolean-value": false, + "no-unnecessary-semicolons": false, + "no-http-string": false, + "export-name": false, + "interface-name": false + }, + "defaultSeverity": "warning" }