├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .prettierrc.yaml ├── .pylintrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODING_STANDARDS.md ├── CONTRIBUTING.md ├── LICENSE ├── PRIVACY.md ├── README.md ├── SECURITY.md ├── ThirdPartyNotices.txt ├── assets ├── dark-theme │ ├── open-simulator.svg │ ├── run-on-simulator.svg │ └── save-to-board.svg ├── icon.png ├── light-theme │ ├── open-simulator.svg │ ├── run-on-simulator.svg │ └── save-to-board.svg └── readmeFiles │ ├── clue │ ├── check_preview_mode.gif │ ├── clue.png │ └── open_settings.PNG │ ├── cpx.jpg │ ├── cpx │ ├── cpx-deploy.png │ ├── cpx-img.png │ ├── cpx-new-file.gif │ └── cpx-run.gif │ ├── deploy.png │ ├── deployToBoard.png │ ├── findExamples.jpg │ ├── getting_started.png │ ├── microbit │ ├── microbit-deploy.png │ ├── microbit-new-file.gif │ ├── microbit-run.gif │ └── microbit.png │ ├── newFile.gif │ ├── new_file.gif │ ├── otherSensors.gif │ ├── run.gif │ └── slider_basedSensor.gif ├── docs ├── developers-setup.md ├── how-to-use.md ├── install.md ├── telemetry.md └── vsix-install-instructions.png ├── gulpfile.js ├── jest.config.js ├── locales └── en │ ├── out │ └── constants.i18n.json │ └── package.i18n.json ├── misc └── usbmapping.json ├── package-lock.json ├── package.json ├── package.nls.json ├── src ├── adafruit_circuitplayground │ ├── __init__.py │ ├── constants.py │ ├── express.py │ ├── locale │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ └── express.po │ │ └── express.pot │ ├── pixel.py │ └── test │ │ ├── __init__.py │ │ ├── sample.mp4 │ │ ├── sample.wav │ │ ├── test_express.py │ │ └── test_pixel.py ├── base_circuitpython │ ├── __init__.py │ ├── base_cp_constants.py │ ├── board.py │ ├── digitalio.py │ ├── displayio │ │ ├── __init__.py │ │ ├── bitmap.py │ │ ├── color_type.py │ │ ├── constants.py │ │ ├── group.py │ │ ├── palette.py │ │ ├── test │ │ │ ├── __init__.py │ │ │ ├── img │ │ │ │ └── group_test_result.bmp │ │ │ ├── test_bitmap.py │ │ │ ├── test_group.py │ │ │ ├── test_palette.py │ │ │ └── test_tile_grid.py │ │ └── tile_grid.py │ ├── fontio.py │ ├── fonts │ │ └── ter-u12n.bdf │ ├── img │ │ └── blinka.bmp │ ├── neopixel_write.py │ ├── pulseio.py │ ├── terminal_handler.py │ ├── terminalio.py │ └── test │ │ ├── __init__.py │ │ └── test_terminal_handler.py ├── check_if_venv.py ├── check_python_dependencies.py ├── clue │ ├── __init__.py │ ├── adafruit_clue.py │ ├── adafruit_display_text │ │ └── label.py │ ├── adafruit_slideshow.py │ └── test │ │ ├── __init__.py │ │ ├── img │ │ ├── test_clue_text_1.bmp │ │ ├── test_display_text_1.bmp │ │ ├── test_display_text_2.bmp │ │ ├── test_display_text_3.bmp │ │ ├── test_display_text_4.bmp │ │ ├── test_image_shapes_1.bmp │ │ ├── test_image_shapes_2.bmp │ │ ├── test_image_shapes_3.bmp │ │ ├── test_image_shapes_4.bmp │ │ ├── test_image_shapes_5.bmp │ │ ├── test_image_text_1.bmp │ │ ├── test_image_text_2.bmp │ │ ├── test_image_text_3.bmp │ │ └── test_image_text_4.bmp │ │ ├── slideshow_pics │ │ ├── pic_1.bmp │ │ ├── pic_2.bmp │ │ ├── pic_3.bmp │ │ ├── pic_4.bmp │ │ ├── pic_5.bmp │ │ ├── pic_6.bmp │ │ ├── pic_7.bmp │ │ └── pic_8.bmp │ │ ├── test_adafruit_clue.py │ │ ├── test_adafruit_display_shapes.py │ │ ├── test_adafruit_display_text.py │ │ ├── test_adafruit_slideshow.py │ │ └── test_helpers.py ├── common │ ├── __init__.py │ ├── constants.py │ ├── debugger_communication_client.py │ ├── telemetry.py │ ├── telemetry_events.py │ ├── test │ │ ├── __init__.py │ │ ├── test_debugger_communication_client.py │ │ └── test_utils.py │ └── utils.py ├── constants.ts ├── cpxWorkspace.ts ├── debug_user_code.py ├── debugger │ ├── debugAdapter.ts │ └── debugAdapterFactory.ts ├── debuggerCommunicationServer.ts ├── dev-requirements.txt ├── device.py ├── deviceContext.ts ├── extension.ts ├── extension_utils │ └── utils.ts ├── install_dependencies.py ├── latest_release_note.ts ├── micropython │ ├── __init__.py │ ├── audio.py │ ├── microbit │ │ ├── __init__.py │ │ ├── __model │ │ │ ├── accelerometer.py │ │ │ ├── button.py │ │ │ ├── compass.py │ │ │ ├── constants.py │ │ │ ├── display.py │ │ │ ├── i2c.py │ │ │ ├── image.py │ │ │ ├── microbit_model.py │ │ │ ├── producer_property.py │ │ │ └── spi.py │ │ └── test │ │ │ ├── __init__.py │ │ │ ├── test_accelerometer.py │ │ │ ├── test_button.py │ │ │ ├── test_display.py │ │ │ ├── test_image.py │ │ │ ├── test_init.py │ │ │ └── test_microbit_model.py │ ├── music.py │ ├── radio.py │ ├── speech.py │ └── utime.py ├── process_user_code.py ├── python_constants.py ├── requirements.txt ├── serialMonitor.ts ├── serialPortControl.ts ├── service │ ├── PopupService.ts │ ├── debuggerCommunicationService.ts │ ├── deviceSelectionService.ts │ ├── fileSelectionService.ts │ ├── messagingService.ts │ ├── setupService.ts │ ├── telemetryHandlerService.ts │ └── webviewService.ts ├── simulatorDebugConfigurationProvider.ts ├── telemetry │ ├── getPackageInfo.ts │ └── telemetryAI.ts ├── templates │ ├── clue_template.py │ ├── cpx_template.py │ └── microbit_template.py ├── test │ ├── runTest.ts │ └── suite │ │ └── index.ts ├── usbDetector.ts ├── view │ ├── App.css │ ├── App.spec.tsx │ ├── App.tsx │ ├── __snapshots__ │ │ └── App.spec.tsx.snap │ ├── components │ │ ├── Button.tsx │ │ ├── Dropdown.tsx │ │ ├── clue │ │ │ ├── Clue.spec.tsx │ │ │ ├── Clue.tsx │ │ │ ├── ClueImage.tsx │ │ │ ├── ClueSimulator.tsx │ │ │ ├── Clue_svg.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Clue.spec.tsx.snap │ │ ├── cpx │ │ │ ├── Accessibility_utils.ts │ │ │ ├── Cpx.spec.tsx │ │ │ ├── Cpx.tsx │ │ │ ├── CpxImage.tsx │ │ │ ├── CpxSimulator.tsx │ │ │ ├── Cpx_svg.tsx │ │ │ ├── Cpx_svg_style.tsx │ │ │ ├── Svg_utils.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Cpx.spec.tsx.snap │ │ ├── microbit │ │ │ ├── Microbit.spec.tsx │ │ │ ├── Microbit.tsx │ │ │ ├── MicrobitImage.tsx │ │ │ ├── MicrobitSimulator.tsx │ │ │ ├── Microbit_svg.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Microbit.spec.tsx.snap │ │ ├── simulator │ │ │ └── ActionBar.tsx │ │ └── toolbar │ │ │ ├── GenericSliderComponent.tsx │ │ │ ├── Gesture.tsx │ │ │ ├── InputSlider.tsx │ │ │ ├── SensorButton.tsx │ │ │ ├── SensorModalUtils.tsx │ │ │ ├── ToolBar.tsx │ │ │ ├── Toolbar.spec.tsx │ │ │ ├── __snapshots__ │ │ │ └── Toolbar.spec.tsx.snap │ │ │ ├── clue │ │ │ ├── ClueModalContent.tsx │ │ │ └── ClueSensorProperties.tsx │ │ │ ├── cpx │ │ │ ├── CpxModalContent.tsx │ │ │ └── CpxSensorProperties.tsx │ │ │ └── microbit │ │ │ ├── MicrobitModalContent.tsx │ │ │ └── MicrobitSensorProperties.tsx │ ├── constants.ts │ ├── container │ │ └── device │ │ │ ├── Device.spec.tsx │ │ │ ├── Device.tsx │ │ │ └── __snapshots__ │ │ │ └── Device.spec.tsx.snap │ ├── context.ts │ ├── index.css │ ├── index.tsx │ ├── pages │ │ ├── __snapshots__ │ │ │ └── gettingStarted.spec.tsx.snap │ │ ├── gettingStarted.css │ │ ├── gettingStarted.spec.tsx │ │ ├── gettingStarted.tsx │ │ └── gettingStartedPictures │ │ │ └── debugger │ │ │ ├── debugger_vars.png │ │ │ ├── debugging.gif │ │ │ ├── start_debugging.jpg │ │ │ └── toolbar.png │ ├── react-app-env.d.ts │ ├── styles │ │ ├── Button.css │ │ ├── Dropdown.css │ │ ├── InputSlider.css │ │ ├── LightSensorBar.css │ │ ├── MotionSensorBar.css │ │ ├── SensorButton.css │ │ ├── Simulator.css │ │ ├── SimulatorSvg.css │ │ ├── TemperatureSensorBar.css │ │ └── ToolBar.css │ ├── svgs │ │ ├── arrow_right_svg.tsx │ │ ├── close_svg.tsx │ │ ├── play_svg.tsx │ │ ├── refresh_svg.tsx │ │ ├── stop_svg.tsx │ │ ├── tag_input_svg.tsx │ │ ├── tag_output_svg.tsx │ │ └── toolbar_svg.tsx │ ├── translations │ │ └── en.json │ ├── tsconfig.json │ ├── utils │ │ └── MessageUtils.tsx │ └── viewUtils.tsx └── vscode_import.ts ├── tsconfig.json ├── tslint.json └── webpack.config.js /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # At least one of the code owners below will be required on each PR: 2 | 3 | * @markAtMicrosoft @smmatte @adclements @abmahdy @iennae @nasadigital @isadorasophia @sagarmanchanda 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description: 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | ## Type of change 6 | 7 | Please delete options that are not relevant. 8 | 9 | - [ ] Bug fix (non-breaking change which fixes an issue) 10 | - [ ] New feature (non-breaking change which adds functionality) 11 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - [ ] This change requires a documentation update 13 | 14 | # Limitations: 15 | 16 | Please describe limitations of this PR 17 | 18 | # Testing: 19 | 20 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 21 | 22 | - [ ] Test A 23 | - [ ] Test B 24 | 25 | # Checklist: 26 | 27 | - [ ] My code follows the style guidelines of this project 28 | - [ ] My code has been formatted with `npm run format` and passes the checks in `npm run check` 29 | - [ ] I have performed a self-review of my own code 30 | - [ ] I have commented my code, particularly in hard-to-understand areas 31 | - [ ] I have made corresponding changes to the documentation 32 | - [ ] My changes generate no new warnings 33 | - [ ] Any dependent changes have been merged and published in downstream modules 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, python dependencies, build the source code and run tests 2 | 3 | name: CI 4 | 5 | on: 6 | push: 7 | branches: [ dev, staging ] 8 | pull_request: 9 | branches: [ dev, staging ] 10 | 11 | jobs: 12 | build-and-test: 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [macos-latest, windows-latest, ubuntu-18.04] 20 | node-version: [10.x] 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Install Linux dependencies 25 | if: matrix.os == 'ubuntu-18.04' 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install g++-multilib 29 | sudo apt-get install -y build-essential 30 | sudo apt-get install libudev-dev 31 | - name: Use Node.js ${{ matrix.node-version }} and install npm dependencies 32 | uses: actions/setup-node@v2 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | - run: npm install 36 | - name: Setup Python environment 37 | uses: actions/setup-python@v2 38 | with: 39 | python-version: 3.x 40 | - run: | 41 | python -m pip install --upgrade pip 42 | pip install -r ./src/dev-requirements.txt 43 | - name: Use npm to compile, format-check and test 44 | uses: actions/setup-node@v1 45 | with: 46 | node-version: ${{ matrix.node-version }} 47 | - run: npm run compile 48 | - run: npm run check 49 | - name: Run tests 50 | uses: GabrielBB/xvfb-action@v1.0 51 | with: 52 | run: npm run test 53 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | trailingComma: "es5" 2 | tabWidth: 4 3 | semi: true 4 | endOfLine: auto 5 | printWidth: 80 -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.vscode-typescript-tslint-plugin" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 15 | "preLaunchTask": "npm: watch" 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 25 | ], 26 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"], 27 | "preLaunchTask": "npm: watch" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | } 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "type": "gulp", 21 | "task": "add-locales", 22 | "problemMatcher": [] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "embedded-python" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /CODING_STANDARDS.md: -------------------------------------------------------------------------------- 1 | # Coding Standards for this Project 2 | 3 | ## Coding guidelines for TypeScript 4 | 5 | - The following standards are inspired from [Coding guidelines for TypeScript](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines) (which you should follow when something is not specified in this document, although any pre-existing practices in a file being edited trump either style guide). 6 | 7 | ### Names 8 | 9 | - Use `PascalCase` for type names. 10 | - Use `I` as a prefix for interface names only when an interface is implemented by a class. 11 | - Use `PascalCase` for enum values. 12 | - Use `camelCase` for function names. 13 | - Use `camelCase` for property names and local variables. 14 | - Do not use `_` as a prefix for private properties (unless used as backing properties). 15 | - Use whole words in names when possible. 16 | 17 | ### Types 18 | 19 | - Do not export types/functions unless you need to share it across multiple components. 20 | - Do not introduce new types/values to the global namespace. 21 | - Shared types should be defined in `types.ts`. 22 | Within a file, type definitions should come first. 23 | 24 | ### null and undefined 25 | 26 | Use undefined. Do not use null. 27 | 28 | ### Comments 29 | 30 | - Comments must end with a period. 31 | - Use JSDoc style comments for functions, interfaces, enums, and classes. 32 | 33 | ### Strings 34 | 35 | Use single quotes for strings. 36 | 37 | ### Imports 38 | 39 | - Use ES6 module imports. 40 | - Do not use bare `import *`; all imports should either explicitly pull in an object or import an entire module, otherwise you're implicitly polluting the global namespace and making it difficult to figure out from code examination where a name originates from. 41 | 42 | ### Style 43 | 44 | - Use `prettier` to format `TypeScript` and `JavaScript` code. 45 | - Use arrow functions over anonymous function expressions. 46 | - Always surround loop and conditional bodies with curly braces. Statements on the same line are allowed to omit braces. 47 | - Open curly braces always go on the same line as whatever necessitates them. 48 | - Parenthesized constructs should have no surrounding whitespace. 49 | - A single space follows commas, colons, and semicolons in those constructs. For example: 50 | 51 | - `for (var i = 0, n = str.length; i < 10; i++) { }` 52 | - `if (x < 10) { }` 53 | - `function f(x: number, y: string): void { }` 54 | 55 | - `else` goes on the same line from the closing curly brace. 56 | - Use 4 spaces per indentation. 57 | - All files must end with an empty line. 58 | 59 | ## Coding Standards for Python 60 | 61 | Please follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html). 62 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 14 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Project Device Simulator Express 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy and Telemetry Notice 2 | 3 | ## Data Collection 4 | 5 | The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft's privacy statement. Our privacy statement is located at https://go.microsoft.com/fwlink/?LinkID=824704​​​​​​​. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. 6 | 7 | ## Disable Telemetry 8 | 9 | The Microsoft Device Simulator Express Extension for Visual Studio Code collects usage 10 | data and sends it to Microsoft to help improve our products and 11 | services. Read our 12 | [privacy statement](https://privacy.microsoft.com/privacystatement) to 13 | learn more. This extension respects the `telemetry.enableTelemetry` 14 | setting which you can learn more about at 15 | https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting. 16 | 17 | To disable telemetry, follow these steps: 18 | 1) Open **File** (Open **Code** on macOS) 19 | 2) Select **Preferences** 20 | 3) Select **Settings** 21 | 4) Search for `telemetry` 22 | 5) Uncheck the **Telemetry: Enable Telemetry** setting -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /assets/dark-theme/open-simulator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/dark-theme/run-on-simulator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/dark-theme/save-to-board.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/icon.png -------------------------------------------------------------------------------- /assets/light-theme/open-simulator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/light-theme/run-on-simulator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/light-theme/save-to-board.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/readmeFiles/clue/check_preview_mode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/clue/check_preview_mode.gif -------------------------------------------------------------------------------- /assets/readmeFiles/clue/clue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/clue/clue.png -------------------------------------------------------------------------------- /assets/readmeFiles/clue/open_settings.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/clue/open_settings.PNG -------------------------------------------------------------------------------- /assets/readmeFiles/cpx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/cpx.jpg -------------------------------------------------------------------------------- /assets/readmeFiles/cpx/cpx-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/cpx/cpx-deploy.png -------------------------------------------------------------------------------- /assets/readmeFiles/cpx/cpx-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/cpx/cpx-img.png -------------------------------------------------------------------------------- /assets/readmeFiles/cpx/cpx-new-file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/cpx/cpx-new-file.gif -------------------------------------------------------------------------------- /assets/readmeFiles/cpx/cpx-run.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/cpx/cpx-run.gif -------------------------------------------------------------------------------- /assets/readmeFiles/deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/deploy.png -------------------------------------------------------------------------------- /assets/readmeFiles/deployToBoard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/deployToBoard.png -------------------------------------------------------------------------------- /assets/readmeFiles/findExamples.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/findExamples.jpg -------------------------------------------------------------------------------- /assets/readmeFiles/getting_started.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/getting_started.png -------------------------------------------------------------------------------- /assets/readmeFiles/microbit/microbit-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/microbit/microbit-deploy.png -------------------------------------------------------------------------------- /assets/readmeFiles/microbit/microbit-new-file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/microbit/microbit-new-file.gif -------------------------------------------------------------------------------- /assets/readmeFiles/microbit/microbit-run.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/microbit/microbit-run.gif -------------------------------------------------------------------------------- /assets/readmeFiles/microbit/microbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/microbit/microbit.png -------------------------------------------------------------------------------- /assets/readmeFiles/newFile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/newFile.gif -------------------------------------------------------------------------------- /assets/readmeFiles/new_file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/new_file.gif -------------------------------------------------------------------------------- /assets/readmeFiles/otherSensors.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/otherSensors.gif -------------------------------------------------------------------------------- /assets/readmeFiles/run.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/run.gif -------------------------------------------------------------------------------- /assets/readmeFiles/slider_basedSensor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/assets/readmeFiles/slider_basedSensor.gif -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Instructions on How to Install and Run the Extension 2 | 3 | ## Steps to manually install the extension 4 | 5 | 1. Link to the latest releases : 6 | [Releases](https://github.com/microsoft/vscode-python-embedded/releases) 7 | 2. Click on the latest release 8 | 3. At the bottom of the page download the .vsix file 9 | 4. To install the .vsix file : 10 | - Go to the directory where the downloaded vsix file is and run in a command console: `code --install-extension ` 11 | - Or in VS Code, go to the extension tab (a), in menu (b) select 'Install from VSIX' (c) and search the file you downloaded 12 | ![VSIX Install Instructions](./vsix-install-instructions.png) 13 | 14 | ## Prerequisites 15 | 16 | _Note: You need to install all the dependencies in order to use the extension._ 17 | 18 | - [VS Code](https://code.visualstudio.com/Download) 19 | - [Node](https://nodejs.org/en/download/) 20 | - [Python 3.7.4](https://www.python.org/downloads/) 21 | - **Warning :** Make sure you've included `python` (or `python3.7`) and `pip` to your `PATH` in your **environment variables**. 22 | _(Note: the easiest way to do it might be when you install Python, you can select the "Add to PATH" option directly. Otherwise you can search how to insert it manually, but make sure that when you type `python` (or `python3.7`) in a terminal, the command is recognized.)_ 23 | - [Python VS Code extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) 24 | - **Note:** This extension is installed automatically from the marketplace when you install our extension 25 | 26 | * Python Modules 27 | - **Note:** On extension activation, you will be prompted with a popup asking if you want the modules to be automatically installed for you. The following python modules should be downloaded when you select "yes" on the prompt message. **If modules are not installed correctly, please use the "pip install" commands listed below.** 28 | - pywin32 **(on Windows only)**: `pip install pywin32` 29 | - On Windows, you need to use the above command in the console to manually install pywin32. 30 | - *playsound*: `pip install playsound` 31 | - *pytest*: `pip install pytest` 32 | - *python-socketio*: `pip install python-socketio` 33 | - *requests*: `pip install requests` 34 | - *applicationinsights*: `pip install applicationinsights` 35 | 36 | *italics*: used in simulation mode only 37 | ## How to use the Extension 38 | 39 | - [How to use the Extension](/docs/how-to-use.md) 40 | -------------------------------------------------------------------------------- /docs/telemetry.md: -------------------------------------------------------------------------------- 1 | # Device Simulator Express Telemetry 2 | 3 | The Device Simulator Express logs usage data and diagnostics telemetry through [Application Insights](https://azure.microsoft.com/en-us/services/monitor/). 4 | 5 | ## Telemetry Gathered 6 | 7 | This extension collects basic diagnostics telemetry and usage data: 8 | 9 | - **Diagnostics telemetry**: performance of extension commands and success / error rate 10 | - **Usage telemetry**: user usage of extension commands and API calls 11 | 12 | ## Usage Telemetry 13 | 14 | Through the Application Insights API, telemetry events are collected on The Device Simulator Express extension usage. The follow table describes the Telemetry events we collect: 15 | 16 | | **Property** | **Note** | 17 | | :--------------------: | --------------------------------------------------------------------------------------------- | 18 | | **Event Name** | Unique event name/descriptor for the event. For ex: Device Simulator Express/COMMAND_NEW_FILE | 19 | | **VS Code Session ID** | A unique identifier for the current session (changes each time the editor is started) | 20 | | **VS Code Machine ID** | A unique identifier for the computer | 21 | | **VS Code Version** | VS Code version being used by the user | 22 | | **Extension Version** | The Device Simulator Express extension version being used | 23 | | **OS** | User's operating system | 24 | | **Performance** | A number indicating how long the command or API call took to execute | 25 | | **Result** | If the event succeeded or not | 26 | -------------------------------------------------------------------------------- /docs/vsix-install-instructions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/docs/vsix-install-instructions.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // The root of your source code, typically /src 3 | // `` is a token Jest substitutes 4 | roots: ["/src"], 5 | 6 | // Jest transformations -- this adds support for TypeScript 7 | // using ts-jest 8 | transform: { 9 | '^.+\\.tsx?$': 'ts-jest', 10 | ".+\\.(css|styl|less|sass|scss)$": "jest-transform-css" 11 | }, 12 | 13 | // Runs special logic, such as cleaning up components 14 | // when using React Testing Library and adds special 15 | // extended assertions to Jest 16 | setupFilesAfterEnv: [ 17 | "@testing-library/jest-dom/extend-expect" 18 | ], 19 | 20 | // Test spec file resolution pattern 21 | // Matches parent folder `__tests__` and filename 22 | // should contain `test` or `spec`. 23 | testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", 24 | 25 | // Module file extensions for importing 26 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 27 | 28 | }; -------------------------------------------------------------------------------- /locales/en/out/constants.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "dialogResponses.agreeAndRun": "Agree and Run", 3 | "dialogResponses.acceptPrivacy": "Got it", 4 | "dialogResponses.cancel": "Cancel", 5 | "dialogResponses.select": "Select", 6 | "dialogResponses.dontShowAgain": "Don't Show Again", 7 | "dialogResponses.exampleCode": "Example Code on GitHub", 8 | "dialogResponses.help": "I need help", 9 | "dialogResponses.installPython": "Install from python.org", 10 | "dialogResponses.installNow": "Install Now", 11 | "dialogResponses.dontInstall": "Don't Install", 12 | "dialogResponses.tutorials": "Tutorials on Adafruit", 13 | "dialogResponses.readInstall": "Read installation docs", 14 | "error.debuggerServerInitFailed": "Warning : The Debugger Server cannot be opened. Please try to free the port {0} if it's already in use or select another one in your Settings 'Device Simulator Express: Debugger Server Port' and start another debug session.\n You can still debug your code but you won't be able to use the Simulator.", 15 | "error.debuggingSessionInProgress": "[ERROR] A debugging session is currently in progress, please stop it before running your code. \n", 16 | "error.noDevice": "No plugged in boards detected. Please double check if your board is connected and/or properly formatted", 17 | "error.noFileToRun": "\n[ERROR] We can't find the .py file to run. Open up a new .py file, or browse through some examples\n", 18 | "error.noFileToDeploy": "\n[ERROR] We can't find a Python file to deploy to your device.\n", 19 | "error.noFolderCreated": "In order to use the Serial Monitor, you need to open a folder and reload VS Code.", 20 | "error.noProgramFoundDebug": "Cannot find a program to debug.", 21 | "error.noPythonPath": "We found that you don't have Python 3 installed on your computer, please install the latest version, add it to your PATH and try again.", 22 | "error.stderr": "\n[ERROR] {0} \n", 23 | "error.unexpectedMessage": "Webview sent an unexpected message", 24 | "info.deployDevice": "\n[INFO] Deploying code to the device...\n", 25 | "info.deploySimulator": "\n[INFO] Deploying code to the simulator...\n", 26 | "info.deploySuccess": "\n[INFO] Code successfully deployed\n", 27 | "info.extensionActivated": "Congratulations, your extension Adafruit_Simulator is now active!", 28 | "info.firstTimeWebview": "To reopen the simulator select the command \"Open Simulator\" from command palette.", 29 | "info.installPythonDependencies": "Do you want us to try and install this extensions dependencies for you?", 30 | "error.invalidFileExtensionDebug": "The file you tried to run isn\\'t a Python file.", 31 | "info.newFile": "New to Python or the Circuit Playground Express? We are here to help!", 32 | "info.noDeviceChosenToDeployTo": "\n[INFO] No device was selected to deploy to.\n", 33 | "info.noDeviceChosenToSimulateTo": "\n[INFO] No device was selected to simulate.\n", 34 | "info.noDeviceChosenForNewFile": "\n[INFO] No device was selected to open a template file for.\n", 35 | "info.redirect": "You are being redirected.", 36 | "info.runningCode": "Running user code", 37 | "info.privacyStatement": "Privacy Statement", 38 | "info.successfulInstall": "Successfully installed Python dependencies.", 39 | "info.thirdPartyWebsite": "By clicking \"Agree and Proceed\" you will be redirected to adafruit.com, a third party website not managed by Microsoft. Please note that your activity on adafruit.com is subject to Adafruit's privacy policy", 40 | "info.welcomeOutputTab": "Welcome to the Device Simulator Express output tab!\n\n", 41 | "label.webviewPanel": "Device Simulator Express", 42 | "name": "Device Simulator Express", 43 | 44 | "warning.agreeAndRun": "By selecting ‘Agree and Run’, you understand the extension executes Python code on your local computer, which may be a potential security risk." 45 | } 46 | -------------------------------------------------------------------------------- /locales/en/package.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceSimulatorExpressExtension.commands.common.installDependencies": "Install Extension Dependencies", 3 | "deviceSimulatorExpressExtension.commands.common.label": "Device Simulator Express", 4 | "deviceSimulatorExpressExtension.commands.common.runSimulator": "Run Simulator", 5 | "deviceSimulatorExpressExtension.commands.common.gettingStarted": "Getting Started", 6 | "deviceSimulatorExpressExtension.commands.common.changeBaudRate": "Change Baud Rate", 7 | "deviceSimulatorExpressExtension.commands.common.closeSerialMonitor": "Close Serial Monitor", 8 | "deviceSimulatorExpressExtension.commands.common.openSerialMonitor": "Open Serial Monitor", 9 | "deviceSimulatorExpressExtension.commands.common.selectSerialPort": "Select Serial Port", 10 | "deviceSimulatorExpressExtension.commands.common.deployToDevice": "Deploy to Device", 11 | "deviceSimulatorExpressExtension.commands.common.openSimulator": "Open Simulator", 12 | "deviceSimulatorExpressExtension.commands.common.newFile": "New File", 13 | "deviceSimulatorExpressExtension.configuration.title": "Device Simulator Express configuration", 14 | "deviceSimulatorExpressExtension.configuration.properties.configEnvOnChange": "When you change the Python interpreter, the Device Simulator Express will automatically configure itself for the required dependencies.", 15 | "deviceSimulatorExpressExtension.configuration.properties.debuggerPort": "The port the Server will listen on for communication with the debugger.", 16 | "deviceSimulatorExpressExtension.configuration.properties.dependencyChecker": "Whether or not to ask if we can download dependencies. If unchecked, the extension will default to never download dependencies, except when automatically creating a virtual environment in the extension files." 17 | } -------------------------------------------------------------------------------- /misc/usbmapping.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "index_file": "https://adafruit.github.io/arduino-board-index/package_adafruit_index.json", 4 | "boards": [ 5 | { 6 | "vid": "239a", 7 | "pid": [ 8 | "8019" 9 | ], 10 | "name": "Adafruit Circuit Playground Express", 11 | "package": "adafruit", 12 | "architecture": "samd", 13 | "id": "cpx" 14 | } 15 | 16 | ] 17 | } 18 | ] -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceSimulatorExpressExtension.commands.common.installDependencies": "Install Extension Dependencies", 3 | "deviceSimulatorExpressExtension.commands.common.label": "Device Simulator Express", 4 | "deviceSimulatorExpressExtension.commands.common.runSimulator": "Run Simulator", 5 | "deviceSimulatorExpressExtension.commands.common.gettingStarted": "Getting Started", 6 | "deviceSimulatorExpressExtension.commands.common.changeBaudRate": "Change Baud Rate", 7 | "deviceSimulatorExpressExtension.commands.common.closeSerialMonitor": "Close Serial Monitor", 8 | "deviceSimulatorExpressExtension.commands.common.openSerialMonitor": "Open Serial Monitor", 9 | "deviceSimulatorExpressExtension.commands.common.selectSerialPort": "Select Serial Port", 10 | "deviceSimulatorExpressExtension.commands.common.deployToDevice": "Deploy to Device", 11 | "deviceSimulatorExpressExtension.commands.common.openSimulator": "Open Simulator", 12 | "deviceSimulatorExpressExtension.commands.common.newFile": "New File", 13 | "deviceSimulatorExpressExtension.configuration.title": "Device Simulator Express configuration", 14 | "deviceSimulatorExpressExtension.configuration.properties.configEnvOnChange": "When you change the Python interpreter, the Device Simulator Express will automatically configure itself for the required dependencies.", 15 | "deviceSimulatorExpressExtension.configuration.properties.debuggerPort": "The port the Server will listen on for communication with the debugger.", 16 | "deviceSimulatorExpressExtension.configuration.properties.dependencyChecker": "Whether or not to ask for dependency downloads. If unchecked, the extension will default to never download dependencies, except when automatically creating a virtual environment in the extension files." 17 | } -------------------------------------------------------------------------------- /src/adafruit_circuitplayground/__init__.py: -------------------------------------------------------------------------------- 1 | # added compatibility for new import structure in CircuitPython 2 | # https://github.com/adafruit/Adafruit_CircuitPython_CircuitPlayground/pull/79 3 | 4 | from .express import cpx as cp 5 | -------------------------------------------------------------------------------- /src/adafruit_circuitplayground/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | 4 | 5 | class EXPRESS_STATE: 6 | BUTTON_A = "button_a" 7 | BUTTON_B = "button_b" 8 | ACCELERATION = "acceleration" 9 | BRIGHTNESS = "brightness" 10 | PIXELS = "pixels" 11 | RED_LED = "red_led" 12 | SWITCH = "switch" 13 | TEMPERATURE = "temperature" 14 | LIGHT = "light" 15 | MOTION_X = "motion_x" 16 | MOTION_Y = "motion_y" 17 | MOTION_Z = "motion_z" 18 | TOUCH = "touch" 19 | SHAKE = "shake" 20 | DETECT_TAPS = "detect_taps" 21 | 22 | 23 | ASSIGN_PIXEL_TYPE_ERROR = ( 24 | "The pixel color value type should be tuple, list or hexadecimal." 25 | ) 26 | 27 | BRIGHTNESS_RANGE_ERROR = "The brightness value should be a number between 0 and 1." 28 | 29 | CPX = "CPX" 30 | 31 | INDEX_ERROR = ( 32 | "The index is not a valid number, you can access the Neopixels from 0 to 9." 33 | ) 34 | 35 | MAC_OS = "darwin" 36 | 37 | NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" 38 | 39 | NOT_SUITABLE_FILE_ERROR = ( 40 | "Your .wav file is not suitable for the Circuit Playground Express." 41 | ) 42 | 43 | PIXEL_RANGE_ERROR = ( 44 | "The pixel hexadicimal color value should be in range #000000 and #FFFFFF." 45 | ) 46 | 47 | VALID_PIXEL_ASSIGN_ERROR = "The pixel color value should be a tuple with three values between 0 and 255 or a hexadecimal color between 0x000000 and 0xFFFFFF." 48 | 49 | ERROR_SENDING_EVENT = "Error trying to send event to the process : " 50 | 51 | TIME_DELAY = 0.03 52 | 53 | 54 | EVENTS_BUTTON_PRESS = [ 55 | EXPRESS_STATE.BUTTON_A, 56 | EXPRESS_STATE.BUTTON_B, 57 | EXPRESS_STATE.SWITCH, 58 | ] 59 | EVENTS_SENSOR_CHANGED = [ 60 | EXPRESS_STATE.TEMPERATURE, 61 | EXPRESS_STATE.LIGHT, 62 | EXPRESS_STATE.MOTION_X, 63 | EXPRESS_STATE.MOTION_Y, 64 | EXPRESS_STATE.MOTION_Z, 65 | ] 66 | 67 | ALL_EXPECTED_INPUT_EVENTS = [ 68 | EXPRESS_STATE.BUTTON_A, 69 | EXPRESS_STATE.BUTTON_B, 70 | EXPRESS_STATE.SWITCH, 71 | EXPRESS_STATE.TEMPERATURE, 72 | EXPRESS_STATE.LIGHT, 73 | EXPRESS_STATE.SHAKE, 74 | EXPRESS_STATE.MOTION_X, 75 | EXPRESS_STATE.MOTION_Y, 76 | EXPRESS_STATE.MOTION_Z, 77 | EXPRESS_STATE.TOUCH, 78 | ] 79 | -------------------------------------------------------------------------------- /src/adafruit_circuitplayground/locale/en/LC_MESSAGES/express.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-08-21 13:35-0700\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: express.py:190 21 | msgid " is not a path to a .wav file." 22 | msgstr "" 23 | 24 | #: express.py:192 25 | msgid "Please use Python 3 or higher." 26 | msgstr "" 27 | -------------------------------------------------------------------------------- /src/adafruit_circuitplayground/locale/express.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2019-08-21 13:35-0700\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: express.py:190 21 | msgid " is not a path to a .wav file." 22 | msgstr "" 23 | 24 | #: express.py:192 25 | msgid "Please use Python 3 or higher." 26 | msgstr "" 27 | -------------------------------------------------------------------------------- /src/adafruit_circuitplayground/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/adafruit_circuitplayground/test/__init__.py -------------------------------------------------------------------------------- /src/adafruit_circuitplayground/test/sample.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/adafruit_circuitplayground/test/sample.mp4 -------------------------------------------------------------------------------- /src/adafruit_circuitplayground/test/sample.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/adafruit_circuitplayground/test/sample.wav -------------------------------------------------------------------------------- /src/base_circuitpython/__init__.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import os 3 | import sys 4 | 5 | abs_path = pathlib.Path(__file__).parent.absolute() 6 | clue_path = os.path.join(abs_path, "../clue") 7 | sys.path.insert(0, os.path.join(abs_path)) 8 | sys.path.insert(0, os.path.join(clue_path)) 9 | -------------------------------------------------------------------------------- /src/base_circuitpython/base_cp_constants.py: -------------------------------------------------------------------------------- 1 | class CLUE_STATE: 2 | BUTTON_A = "button_a" 3 | BUTTON_B = "button_b" 4 | PRESSED_BUTTONS = "pressed_buttons" 5 | SEA_LEVEL_PRESSURE = "sea_level_pressure" 6 | TEMPERATURE = "temperature" 7 | PROXIMITY = "proximity" 8 | GESTURE = "gesture" 9 | HUMIDITY = "humidity" 10 | PRESSURE = "pressure" 11 | PIXEL = "pixel" 12 | # Accelerometer 13 | MOTION_X = "motion_x" 14 | MOTION_Y = "motion_y" 15 | MOTION_Z = "motion_z" 16 | # Light/color sensor 17 | LIGHT_R = "light_r" 18 | LIGHT_G = "light_g" 19 | LIGHT_B = "light_b" 20 | LIGHT_C = "light_c" 21 | # Magnetometer 22 | MAGNET_X = "magnet_x" 23 | MAGNET_Y = "magnet_y" 24 | MAGNET_Z = "magnet_z" 25 | # Gyroscope 26 | GYRO_X = "gyro_x" 27 | GYRO_Y = "gyro_y" 28 | GYRO_Z = "gyro_z" 29 | # LEDs 30 | RED_LED = "red_led" 31 | WHITE_LEDS = "white_leds" 32 | 33 | 34 | CPX = "CPX" 35 | CLUE = "CLUE" 36 | PIXELS = "pixels" 37 | SHAKE = "shake" 38 | 39 | CLUE_PIN = "D18" 40 | 41 | CLUE = "CLUE" 42 | BASE_64 = "display_base64" 43 | IMG_DIR_NAME = "img" 44 | SCREEN_HEIGHT_WIDTH = 240 45 | 46 | EXPECTED_INPUT_BUTTONS = set([CLUE_STATE.BUTTON_A, CLUE_STATE.BUTTON_B]) 47 | 48 | ALL_EXPECTED_INPUT_EVENTS = set( 49 | [ 50 | CLUE_STATE.TEMPERATURE, 51 | CLUE_STATE.LIGHT_R, 52 | CLUE_STATE.LIGHT_G, 53 | CLUE_STATE.LIGHT_B, 54 | CLUE_STATE.LIGHT_C, 55 | CLUE_STATE.MOTION_X, 56 | CLUE_STATE.MOTION_Y, 57 | CLUE_STATE.MOTION_Z, 58 | CLUE_STATE.HUMIDITY, 59 | CLUE_STATE.PRESSURE, 60 | CLUE_STATE.PROXIMITY, 61 | CLUE_STATE.GESTURE, 62 | CLUE_STATE.GYRO_X, 63 | CLUE_STATE.GYRO_Y, 64 | CLUE_STATE.GYRO_Z, 65 | CLUE_STATE.MAGNET_X, 66 | CLUE_STATE.MAGNET_Y, 67 | CLUE_STATE.MAGNET_Z, 68 | ] 69 | ) 70 | 71 | BMP_IMG = "BMP" 72 | 73 | BMP_IMG_ENDING = ".bmp" 74 | 75 | NO_VALID_IMGS_ERR = "No valid images" 76 | 77 | BLINKA_BMP = "blinka.bmp" 78 | CLUE_TERMINAL_LINE_HEIGHT = 16 79 | CLUE_TERMINAL_LINE_NUM_MAX = 15 80 | CLUE_TERMINAL_X_OFFSET = 15 81 | CLUE_TERMINAL_Y_OFFSET = 5 82 | CLUE_TERMINAL_LINE_BREAK_AMT = 37 83 | -------------------------------------------------------------------------------- /src/base_circuitpython/board.py: -------------------------------------------------------------------------------- 1 | # dummy class for references to board display to work 2 | # https://learn.adafruit.com/arduino-to-circuitpython/the-board-module 3 | 4 | 5 | import terminal_handler 6 | 7 | 8 | class __Display: 9 | def __init__(self): 10 | self.active_group = None 11 | self.terminal = terminal_handler.Terminal() 12 | 13 | def show(self, group=None): 14 | if group != self.active_group: 15 | self.active_group = group 16 | 17 | if group == None: 18 | self.terminal._Terminal__draw() 19 | return 20 | 21 | # if the group has no attribute called 22 | # "draw", then it is liable for updating itself 23 | # when it calls show 24 | if hasattr(group, "_Group__draw"): 25 | group._Group__draw() 26 | 27 | 28 | DISPLAY = __Display() 29 | 30 | # default pin for neopixel, 31 | # shows that this could 32 | # refer to the CPX 33 | # or CLUE neopixel pin 34 | NEOPIXEL = "D00" 35 | 36 | # ETC PINS WITHIN THE CLUE THAT MAY BE REFERENCED 37 | # found by runing print(dir(board)) on clue 38 | A0 = 0 39 | A1 = 0 40 | A2 = 0 41 | A3 = 0 42 | A4 = 0 43 | A5 = 0 44 | A6 = 0 45 | A7 = 0 46 | 47 | ACCELEROMETER_GYRO_INTERRUPT = 0 48 | 49 | BUTTON_A = 0 50 | BUTTON_B = 0 51 | 52 | D0 = 0 53 | D1 = 0 54 | D2 = 0 55 | D3 = 0 56 | D4 = 0 57 | D5 = 0 58 | D6 = 0 59 | D7 = 0 60 | D8 = 0 61 | D9 = 0 62 | D10 = 0 63 | D11 = 0 64 | D12 = 0 65 | D13 = 0 66 | D14 = 0 67 | D15 = 0 68 | D16 = 0 69 | D17 = 0 70 | D18 = 0 71 | D19 = 0 72 | D20 = 0 73 | 74 | I2C = 0 75 | 76 | L = 0 77 | 78 | MICROPHONE_CLOCK = 0 79 | MICROPHONE_DATA = 0 80 | 81 | MISO = 0 82 | MOSI = 0 83 | 84 | P0 = 0 85 | P1 = 0 86 | P2 = 0 87 | P3 = 0 88 | P4 = 0 89 | P5 = 0 90 | P6 = 0 91 | P7 = 0 92 | P8 = 0 93 | P9 = 0 94 | P10 = 0 95 | P11 = 0 96 | P12 = 0 97 | P13 = 0 98 | P14 = 0 99 | P15 = 0 100 | P16 = 0 101 | P17 = 0 102 | P18 = 0 103 | P19 = 0 104 | P20 = 0 105 | 106 | PROXIMITY_LIGHT_INTERRUPT = 0 107 | 108 | RX = 0 109 | SCK = 0 110 | SCL = 0 111 | SDA = 0 112 | 113 | SPEAKER = 0 114 | 115 | SPI = 0 116 | 117 | TFT_BACKLIGHT = 0 118 | TFT_CS = 0 119 | TFT_DC = 0 120 | TFT_MOSI = 0 121 | TFT_RESET = 0 122 | TFT_SCK = 0 123 | 124 | TX = 0 125 | 126 | UART = 0 127 | 128 | WHITE_LEDS = 0 129 | -------------------------------------------------------------------------------- /src/base_circuitpython/digitalio.py: -------------------------------------------------------------------------------- 1 | # dummy class for neopixel library to work 2 | 3 | # original implementation docs for digitalio: 4 | # https://circuitpython.readthedocs.io/en/5.0.x/shared-bindings/digitalio/__init__.html 5 | 6 | 7 | class DigitalInOut: 8 | def __init__(self, pin): 9 | self.pin = pin 10 | pass 11 | 12 | def deinit(self): 13 | pass 14 | 15 | 16 | class Direction: 17 | OUTPUT = 0 18 | -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/__init__.py: -------------------------------------------------------------------------------- 1 | # Displayio implementation loosely based on the 2 | # displayio package in Adafruit CircuitPython 3 | 4 | # https://circuitpython.readthedocs.io/en/5.0.x/shared-bindings/displayio/__init__.html 5 | 6 | from .bitmap import Bitmap 7 | from .group import Group 8 | from .palette import Palette 9 | 10 | # references to img and bmp_img are for testing purposes 11 | from .tile_grid import TileGrid 12 | -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/bitmap.py: -------------------------------------------------------------------------------- 1 | from . import constants as CONSTANTS 2 | 3 | # Bitmap implementation loosely based on the 4 | # displayio.Bitmap class in Adafruit CircuitPython 5 | 6 | # https://circuitpython.readthedocs.io/en/5.0.x/shared-bindings/displayio/Bitmap.html 7 | 8 | # The colour of a certain pixel is interpreted 9 | # within the TileGrid instance that the object 10 | # lives within. Each pixel is an integer value 11 | # that refers to the colours in the palette via index. 12 | 13 | 14 | class Bitmap: 15 | """ 16 | .. currentmodule:: displayio 17 | 18 | `Bitmap` -- Stores values in a 2D array 19 | ========================================================================== 20 | 21 | Stores values of a certain size in a 2D array 22 | 23 | .. class:: Bitmap(width, height, value_count) 24 | 25 | Create a Bitmap object with the given fixed size. Each pixel stores a value that is used to 26 | index into a corresponding palette. This enables differently colored sprites to share the 27 | underlying Bitmap. value_count is used to minimize the memory used to store the Bitmap. 28 | 29 | :param int width: The number of values wide 30 | :param int height: The number of values high 31 | :param int value_count: The number of possible pixel values. 32 | 33 | """ 34 | 35 | def __init__(self, width, height, value_count=255): 36 | self.__width = width 37 | self.__height = height 38 | self.values = bytearray(width * height) 39 | 40 | @property 41 | def width(self): 42 | """ 43 | .. attribute:: width 44 | 45 | Width of the bitmap. (read only) 46 | 47 | """ 48 | return self.__width 49 | 50 | @property 51 | def height(self): 52 | """ 53 | .. attribute:: height 54 | 55 | Height of the bitmap. (read only) 56 | 57 | """ 58 | return self.__height 59 | 60 | def __setitem__(self, index, value): 61 | """ 62 | .. method:: __setitem__(index, value) 63 | 64 | Sets the value at the given index. The index can either be an x,y tuple or an int equal 65 | to ``y * width + x``. 66 | 67 | This allows you to:: 68 | 69 | bitmap[0,1] = 3 70 | 71 | """ 72 | if isinstance(index, tuple): 73 | if index[0] >= self.width or index[1] >= self.height: 74 | raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) 75 | 76 | index = index[0] + index[1] * self.width 77 | 78 | try: 79 | self.values[index] = value 80 | except IndexError: 81 | raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) 82 | 83 | def __getitem__(self, index): 84 | """ 85 | .. method:: __getitem__(index) 86 | 87 | Returns the value at the given index. The index can either be an x,y tuple or an int equal 88 | to ``y * width + x``. 89 | 90 | This allows you to:: 91 | 92 | print(bitmap[0,1]) 93 | 94 | """ 95 | 96 | if isinstance(index, tuple): 97 | if index[0] >= self.width or index[1] >= self.height: 98 | raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) 99 | 100 | index = index[0] + index[1] * self.width 101 | 102 | try: 103 | return self.values[index] 104 | except IndexError: 105 | raise IndexError(CONSTANTS.PIXEL_OUT_OF_BOUNDS) 106 | -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/color_type.py: -------------------------------------------------------------------------------- 1 | # Datatype for the items within a palette 2 | class _ColorType: 3 | def __init__(self, rgb888): 4 | self.rgb888 = rgb888 5 | self.transparent = False 6 | 7 | def __eq__(self, other): 8 | return ( 9 | isinstance(other, _ColorType) 10 | and self.rgb888 == other.rgb888 11 | and self.transparent == other.transparent 12 | ) 13 | -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/constants.py: -------------------------------------------------------------------------------- 1 | PIXEL_OUT_OF_BOUNDS = "pixel coordinates out of bounds" 2 | PALETTE_OUT_OF_RANGE = "Palette index out of range" 3 | TILE_OUT_OF_BOUNDS = "Tile index out of bounds" 4 | INCORR_SUBCLASS = "Layer must be a Group or TileGrid subclass." 5 | LAYER_ALREADY_IN_GROUP = "Layer already in a group." 6 | GROUP_FULL = "Group Full" 7 | SCALE_TOO_SMALL = "scale must be >= 1" 8 | 9 | SCREEN_HEIGHT_WIDTH = 240 10 | 11 | CLUE = "CLUE" 12 | BASE_64 = "display_base64" 13 | -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/palette.py: -------------------------------------------------------------------------------- 1 | from .color_type import _ColorType 2 | from . import constants as CONSTANTS 3 | 4 | # Palette implementation loosely based on the 5 | # displayio.Palette class in Adafruit CircuitPython 6 | # (with only the functions needed for the CLUE) 7 | 8 | # https://circuitpython.readthedocs.io/en/5.0.x/shared-bindings/displayio/Palette.html 9 | 10 | 11 | class Palette: 12 | """ 13 | `Palette` -- Stores a mapping from bitmap pixel palette_indexes to display colors 14 | ========================================================================================= 15 | 16 | Map a pixel palette_index to a full color. Colors are transformed to the display's format internally to 17 | save memory. 18 | 19 | .. class:: Palette(color_count) 20 | 21 | Create a Palette object to store a set number of colors. 22 | 23 | :param int color_count: The number of colors in the Palette 24 | """ 25 | 26 | def __init__(self, color_count): 27 | self.__color_count = color_count 28 | self.__contents = [] 29 | 30 | # set all colours to black by default 31 | for i in range(self.__color_count): 32 | self.__contents.append(_ColorType((0, 0, 0))) 33 | 34 | def __setitem__(self, index, value): 35 | """ 36 | .. method:: __setitem__(index, value) 37 | 38 | Sets the pixel color at the given index. The index should be an integer in the range 0 to color_count-1. 39 | 40 | The value argument represents a color, and can be from 0x000000 to 0xFFFFFF (to represent an RGB value). 41 | Value can be an int, bytes (3 bytes (RGB) or 4 bytes (RGB + pad byte)), bytearray, 42 | or a tuple or list of 3 integers. 43 | 44 | This allows you to:: 45 | 46 | palette[0] = 0xFFFFFF # set using an integer 47 | palette[1] = b'\xff\xff\x00' # set using 3 bytes 48 | palette[2] = b'\xff\xff\x00\x00' # set using 4 bytes 49 | palette[3] = bytearray(b'\x00\x00\xFF') # set using a bytearay of 3 or 4 bytes 50 | palette[4] = (10, 20, 30) # set using a tuple of 3 integers 51 | 52 | """ 53 | if index >= self.__color_count: 54 | raise IndexError(CONSTANTS.PALETTE_OUT_OF_RANGE) 55 | 56 | self.__contents[index].rgb888 = value 57 | 58 | def __len__(self): 59 | """ 60 | .. method:: __len__() 61 | 62 | Returns the number of colors in a Palette 63 | 64 | """ 65 | return self.__color_count 66 | 67 | def make_transparent(self, index): 68 | """ 69 | .. method:: make_transparent(palette_index) 70 | """ 71 | self.__toggle_transparency(index, True) 72 | 73 | def make_opaque(self, index): 74 | """ 75 | .. method:: make_opaque(palette_index) 76 | """ 77 | self.__toggle_transparency(index, False) 78 | 79 | def __toggle_transparency(self, index, transparency): 80 | if self.__contents[index]: 81 | self.__contents[index].transparent = transparency 82 | -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/base_circuitpython/displayio/test/__init__.py -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/test/img/group_test_result.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/base_circuitpython/displayio/test/img/group_test_result.bmp -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/test/test_bitmap.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ..bitmap import Bitmap 3 | from .. import constants as CONSTANTS 4 | 5 | 6 | class TestBitmap(object): 7 | @pytest.mark.parametrize("x, y", [(1, 1), (2, 6), (0, 0)]) 8 | def test_create_bitmap(self, x, y): 9 | bitmap = Bitmap(x, y) 10 | 11 | @pytest.mark.parametrize("x, y, palette_num", [(1, 1, 4), (2, 3, 6), (4, 4, 9)]) 12 | def test_set_and_get_pixel(self, x, y, palette_num): 13 | bitmap = Bitmap(5, 5) 14 | bitmap[x, y] = palette_num 15 | assert palette_num == bitmap[x, y] 16 | 17 | @pytest.mark.parametrize("i, palette_num", [(1, 1), (24, 2)]) 18 | def test_set_and_get_pixel_singular_index(self, i, palette_num): 19 | bitmap = Bitmap(5, 5) 20 | bitmap[i] = palette_num 21 | assert palette_num == bitmap[i] 22 | 23 | @pytest.mark.parametrize( 24 | "x_size, y_size, x_coord, y_coord", 25 | [(1, 1, 0, 4), (1, 1, 4, 0), (200, 200, 300, 1), (200, 200, 1, 300)], 26 | ) 27 | def test_get_set_index_err(self, x_size, y_size, x_coord, y_coord): 28 | bitmap = Bitmap(x_size, y_size) 29 | 30 | with pytest.raises(IndexError, match=CONSTANTS.PIXEL_OUT_OF_BOUNDS): 31 | bitmap[x_coord, y_coord] = 0 32 | 33 | with pytest.raises(IndexError, match=CONSTANTS.PIXEL_OUT_OF_BOUNDS): 34 | val = bitmap[x_coord, y_coord] 35 | 36 | @pytest.mark.parametrize("x_size, y_size, i", [(1, 1, 3), (200, 200, 40000)]) 37 | def test_get_set_index_err_singular_index(self, x_size, y_size, i): 38 | bitmap = Bitmap(x_size, y_size) 39 | 40 | with pytest.raises(IndexError, match=CONSTANTS.PIXEL_OUT_OF_BOUNDS): 41 | bitmap[i] = 0 42 | 43 | with pytest.raises(IndexError, match=CONSTANTS.PIXEL_OUT_OF_BOUNDS): 44 | val = bitmap[i] 45 | -------------------------------------------------------------------------------- /src/base_circuitpython/displayio/test/test_palette.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ..palette import Palette 3 | from .. import constants as CONSTANTS 4 | from ..color_type import _ColorType 5 | 6 | 7 | class TestPalette(object): 8 | @pytest.mark.parametrize( 9 | "color_count, palette_num, val", 10 | [(11, 5, (255, 255, 255)), (1, 0, (255, 0, 255)), (4, 3, (0, 0, 0))], 11 | ) 12 | def test_get_and_set_palette(self, color_count, palette_num, val): 13 | palette = Palette(color_count) 14 | palette[palette_num] = val 15 | assert palette._Palette__contents[palette_num] == _ColorType(val) 16 | 17 | @pytest.mark.parametrize("palette_size, palette_index", [(3, 7), (0, 0), (3, 3)]) 18 | def test_get_and_set_palette_err(self, palette_size, palette_index): 19 | palette = Palette(palette_size) 20 | with pytest.raises(IndexError, match=CONSTANTS.PALETTE_OUT_OF_RANGE): 21 | palette[palette_index] = 0 22 | 23 | def test_set_transparency(self): 24 | palette = Palette(5) 25 | assert palette._Palette__contents[2].transparent == False 26 | 27 | palette.make_transparent(2) 28 | assert palette._Palette__contents[2].transparent == True 29 | 30 | palette.make_opaque(2) 31 | assert palette._Palette__contents[2].transparent == False 32 | -------------------------------------------------------------------------------- /src/base_circuitpython/fontio.py: -------------------------------------------------------------------------------- 1 | # dummy library for adafruit_bitmap_font to work 2 | 3 | # original implementation docs for fontio: 4 | # https://circuitpython.readthedocs.io/en/5.0.x/shared-bindings/fontio/__init__.html 5 | 6 | # file taken from adafruit_bitmap_font's examples: 7 | # https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font/blob/master/test/fontio.py 8 | import collections 9 | import displayio 10 | 11 | Glyph = collections.namedtuple( 12 | "Glyph", 13 | ["bitmap", "tile_index", "width", "height", "dx", "dy", "shift_x", "shift_y"], 14 | ) 15 | -------------------------------------------------------------------------------- /src/base_circuitpython/img/blinka.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/base_circuitpython/img/blinka.bmp -------------------------------------------------------------------------------- /src/base_circuitpython/neopixel_write.py: -------------------------------------------------------------------------------- 1 | # overriden neopixel_write library to write to frontend 2 | 3 | # original implementation docs for neopixel_write: 4 | # https://circuitpython.readthedocs.io/en/5.0.x/shared-bindings/neopixel_write/__init__.html 5 | 6 | 7 | import pathlib 8 | import sys 9 | import os 10 | 11 | import common 12 | from adafruit_circuitplayground import cp 13 | import base_cp_constants as CONSTANTS 14 | 15 | 16 | def neopixel_write(gpio, buf): 17 | """Write buf out on the given DigitalInOut.""" 18 | 19 | if len(tuple(buf)) > 0: 20 | # if we are explicitly given 21 | # the clue pin, that means that 22 | # the clue is definitely the active device 23 | # because the constructor for the 24 | # clue is what calls neopixel 25 | # with the clue pin argument 26 | if gpio.pin != CONSTANTS.CLUE_PIN: 27 | send_cpx(buf) 28 | send_clue(buf) 29 | 30 | 31 | def send_clue(buf): 32 | sendable_json = {CONSTANTS.PIXELS: tuple(buf)} 33 | if common.utils.debug_mode: 34 | common.debugger_communication_client.debug_send_to_simulator( 35 | sendable_json, CONSTANTS.CLUE 36 | ) 37 | else: 38 | common.utils.send_to_simulator(sendable_json, CONSTANTS.CLUE) 39 | 40 | 41 | def send_cpx(buf): 42 | buf_list = list(buf) 43 | ret_list = [] 44 | temp_list = [] 45 | for idx, elem in enumerate(buf_list): 46 | if idx % 3 == 0 and idx != 0: 47 | ret_list.append(tuple(temp_list)) 48 | temp_list = [] 49 | temp_list.append(elem) 50 | 51 | if len(temp_list) == 3: 52 | ret_list.append(tuple(temp_list)) 53 | 54 | max_index = min(len(ret_list), 10) 55 | cp.pixels[0:max_index] = ret_list[0:max_index] 56 | -------------------------------------------------------------------------------- /src/base_circuitpython/pulseio.py: -------------------------------------------------------------------------------- 1 | from common import utils 2 | 3 | 4 | class PulseIn: 5 | def __init__(self, pin, maxlen=2, *, idle_state=False): 6 | utils.print_for_unimplemented_functions(PulseIn.__init__.__qualname__) 7 | 8 | 9 | class PulseOut: 10 | def __init__(self, carrier): 11 | utils.print_for_unimplemented_functions(PulseOut.__init__.__qualname__) 12 | 13 | 14 | class PWMOut: 15 | def __init__(self, pin, *, duty_cycle=0, frequency=500, variable_frequency=False): 16 | utils.print_for_unimplemented_functions(PWMOut.__init__.__qualname__) 17 | -------------------------------------------------------------------------------- /src/base_circuitpython/terminalio.py: -------------------------------------------------------------------------------- 1 | # overriden terminalio library, which uses 2 | # adafruit_bitmap_font to load the default font 3 | 4 | # original implementation docs for terminalio: 5 | # https://circuitpython.readthedocs.io/en/5.0.x/shared-bindings/terminalio/__init__.html 6 | 7 | from adafruit_bitmap_font import bitmap_font # pylint: disable=wrong-import-position 8 | 9 | import os 10 | import pathlib 11 | 12 | abs_path = pathlib.Path(__file__).parent.absolute() 13 | 14 | # load default font 15 | FONT = bitmap_font.load_font(os.path.join(abs_path, "fonts", "ter-u12n.bdf")) 16 | -------------------------------------------------------------------------------- /src/base_circuitpython/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/base_circuitpython/test/__init__.py -------------------------------------------------------------------------------- /src/base_circuitpython/test/test_terminal_handler.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import base_circuitpython.terminal_handler 4 | from common import utils 5 | from unittest import mock 6 | 7 | 8 | class TestTerminal(object): 9 | def setup_method(self): 10 | 11 | utils.send_to_simulator = mock.Mock() 12 | 13 | @pytest.mark.parametrize( 14 | "str_vals", 15 | [ 16 | (["potato", "Lorem ipsum"]), 17 | ([""]), 18 | ([".......", "123456", "", "test"]), 19 | (["123456789 123456789 123456789 1234567"]), 20 | ], 21 | ) 22 | def test_single_line(self, str_vals): 23 | self.terminal = base_circuitpython.terminal_handler.Terminal() 24 | for s in str_vals: 25 | self.terminal.add_str_to_terminal(s) 26 | 27 | result = list(self.terminal._Terminal__output_values) 28 | result.reverse() 29 | 30 | # output should just be the reversed version since all lines 31 | # don't have newline or exceed 37 characters 32 | assert str_vals == result 33 | 34 | @pytest.mark.parametrize( 35 | "str_vals, expected", 36 | [ 37 | ( 38 | ["\nCode done running. Waiting for reload."], 39 | [".", "Code done running. Waiting for reload", ""], 40 | ), 41 | ( 42 | ["TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTEST"], 43 | ["ESTTESTTEST", "TESTTESTTESTTESTTESTTESTTESTTESTTESTT"], 44 | ), 45 | ( 46 | ["\nCode done running. Waiting for reload.", "........."], 47 | [".........", ".", "Code done running. Waiting for reload", ""], 48 | ), 49 | ( 50 | ["TEST TEST TEST TEST TEST ", "..."], 51 | ["...", " ", "TEST TEST TEST TEST TEST "], 52 | ), 53 | ], 54 | ) 55 | def test_multiline(self, str_vals, expected): 56 | self.terminal = base_circuitpython.terminal_handler.Terminal() 57 | for s in str_vals: 58 | self.terminal.add_str_to_terminal(s) 59 | 60 | result = list(self.terminal._Terminal__output_values) 61 | assert result == expected 62 | -------------------------------------------------------------------------------- /src/check_if_venv.py: -------------------------------------------------------------------------------- 1 | # from https://stackoverflow.com/questions/1871549/determine-if-python-is-running-inside-virtualenv 2 | import sys 3 | 4 | isVenv = hasattr(sys, "real_prefix") or ( 5 | hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix 6 | ) 7 | 8 | # prints result for frontend to read 9 | # 1 -> is a venv 10 | # 0 -> is NOT a venv 11 | print(int(isVenv)) 12 | -------------------------------------------------------------------------------- /src/check_python_dependencies.py: -------------------------------------------------------------------------------- 1 | # from https://stackoverflow.com/questions/16294819/check-if-my-python-has-all-required-packages 2 | import sys 3 | import pkg_resources 4 | import python_constants as CONSTANTS 5 | 6 | 7 | def check_for_dependencies(): 8 | with open(f"{sys.path[0]}/requirements.txt") as f: 9 | dependencies = [x.strip() for x in f.readlines()] 10 | 11 | cleaned_dependencies = [] 12 | 13 | # getting names of packages from tar.gz files 14 | 15 | # FOR PRE-DOWNLOADED TAR.GZ FILES, ENSURE THAT 16 | # THERE ARE NO DASHES AFTER THE ONE THAT IS 17 | # AT THE END OF THE PACKAGE NAME. 18 | # So, it would be: 19 | # {package_name}-{trailing_verison_info}.tar.gz 20 | for dep in dependencies: 21 | if len(dep) > 7 and dep.strip()[-7:] == ".tar.gz": 22 | last_dash = dep.rfind("-") 23 | dep = dep[:last_dash] 24 | 25 | cleaned_dependencies.append(dep) 26 | 27 | # here, if a dependency is not met, a DistributionNotFound or VersionConflict 28 | # exception is caught and replaced with a new exception with a clearer description. 29 | try: 30 | pkg_resources.require(cleaned_dependencies) 31 | except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict) as e: 32 | raise Exception(CONSTANTS.DEPEND_ERR) 33 | 34 | 35 | if __name__ == "__main__": 36 | check_for_dependencies() 37 | -------------------------------------------------------------------------------- /src/clue/__init__.py: -------------------------------------------------------------------------------- 1 | import base_circuitpython 2 | -------------------------------------------------------------------------------- /src/clue/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/__init__.py -------------------------------------------------------------------------------- /src/clue/test/img/test_clue_text_1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_clue_text_1.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_display_text_1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_display_text_1.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_display_text_2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_display_text_2.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_display_text_3.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_display_text_3.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_display_text_4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_display_text_4.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_shapes_1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_shapes_1.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_shapes_2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_shapes_2.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_shapes_3.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_shapes_3.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_shapes_4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_shapes_4.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_shapes_5.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_shapes_5.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_text_1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_text_1.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_text_2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_text_2.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_text_3.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_text_3.bmp -------------------------------------------------------------------------------- /src/clue/test/img/test_image_text_4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/img/test_image_text_4.bmp -------------------------------------------------------------------------------- /src/clue/test/slideshow_pics/pic_1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/slideshow_pics/pic_1.bmp -------------------------------------------------------------------------------- /src/clue/test/slideshow_pics/pic_2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/slideshow_pics/pic_2.bmp -------------------------------------------------------------------------------- /src/clue/test/slideshow_pics/pic_3.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/slideshow_pics/pic_3.bmp -------------------------------------------------------------------------------- /src/clue/test/slideshow_pics/pic_4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/slideshow_pics/pic_4.bmp -------------------------------------------------------------------------------- /src/clue/test/slideshow_pics/pic_5.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/slideshow_pics/pic_5.bmp -------------------------------------------------------------------------------- /src/clue/test/slideshow_pics/pic_6.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/slideshow_pics/pic_6.bmp -------------------------------------------------------------------------------- /src/clue/test/slideshow_pics/pic_7.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/slideshow_pics/pic_7.bmp -------------------------------------------------------------------------------- /src/clue/test/slideshow_pics/pic_8.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/clue/test/slideshow_pics/pic_8.bmp -------------------------------------------------------------------------------- /src/clue/test/test_adafruit_display_shapes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pathlib 3 | import os 4 | 5 | import displayio 6 | 7 | from PIL import Image 8 | from unittest import mock 9 | 10 | from common import utils 11 | 12 | 13 | from adafruit_display_shapes.rect import Rect 14 | from adafruit_display_shapes.circle import Circle 15 | from adafruit_display_shapes.roundrect import RoundRect 16 | 17 | from .test_helpers import helper 18 | from base_circuitpython import base_cp_constants as CONSTANTS 19 | import board 20 | 21 | 22 | class TestAdafruitDisplayShapes(object): 23 | def setup_method(self): 24 | self.abs_path = pathlib.Path(__file__).parent.absolute() 25 | 26 | utils.send_to_simulator = mock.Mock() 27 | self.main_img = Image.new( 28 | "RGBA", 29 | (CONSTANTS.SCREEN_HEIGHT_WIDTH, CONSTANTS.SCREEN_HEIGHT_WIDTH), 30 | (0, 0, 0, 0), 31 | ) 32 | 33 | def test_shapes(self): 34 | 35 | expected_images = [] 36 | for i in range(5): 37 | img = Image.open( 38 | os.path.join( 39 | self.abs_path, 40 | CONSTANTS.IMG_DIR_NAME, 41 | f"test_image_shapes_{i+1}.bmp", 42 | ) 43 | ) 44 | 45 | img.putalpha(255) 46 | expected_images.append(img.load()) 47 | 48 | # TAKEN FROM ADAFRUIT'S DISPLAY SHAPES LIBRARY 49 | # https://github.com/ladyada/Adafruit_CircuitPython_Display_Shapes/blob/master/examples/display_shapes_simpletest.py 50 | splash = displayio.Group(max_size=10) 51 | splash._Group__show = self.__send_helper 52 | board.DISPLAY.show(splash) 53 | color_bitmap = displayio.Bitmap(320, 240, 1) 54 | color_palette = displayio.Palette(1) 55 | color_palette[0] = 0xFFFFFF 56 | bg_sprite = displayio.TileGrid( 57 | color_bitmap, x=0, y=0, pixel_shader=color_palette 58 | ) 59 | splash.append(bg_sprite) 60 | helper._Helper__test_image_equality(self.main_img.load(), expected_images[0]) 61 | 62 | rect = Rect(80, 20, 41, 41, fill=0x00FF00) 63 | splash.append(rect) 64 | helper._Helper__test_image_equality(self.main_img.load(), expected_images[1]) 65 | circle = Circle(100, 100, 20, fill=0x00FF00, outline=0xFF00FF) 66 | splash.append(circle) 67 | 68 | helper._Helper__test_image_equality(self.main_img.load(), expected_images[2]) 69 | 70 | rect2 = Rect(50, 100, 61, 81, outline=0x0, stroke=3) 71 | splash.append(rect2) 72 | 73 | helper._Helper__test_image_equality(self.main_img.load(), expected_images[3]) 74 | 75 | roundrect = RoundRect(10, 10, 61, 81, 10, fill=0x0, outline=0xFF00FF, stroke=6) 76 | splash.append(roundrect) 77 | 78 | helper._Helper__test_image_equality(self.main_img.load(), expected_images[4]) 79 | 80 | def __send_helper(self, image): 81 | self.main_img = image 82 | -------------------------------------------------------------------------------- /src/clue/test/test_adafruit_display_text.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import os 4 | import sys 5 | import pathlib 6 | from PIL import Image 7 | from unittest import mock 8 | 9 | from common import utils 10 | 11 | import displayio 12 | import terminalio 13 | 14 | from adafruit_display_text import label 15 | 16 | from .test_helpers import helper 17 | from base_circuitpython import base_cp_constants as CONSTANTS 18 | 19 | # to keep track of test # to find right expected bmp 20 | test_count = 0 21 | 22 | 23 | class TestAdafruitDisplayText(object): 24 | def setup_method(self): 25 | self.abs_path = pathlib.Path(__file__).parent.absolute() 26 | # Create a new black (default) image 27 | utils.send_to_simulator = mock.Mock() 28 | 29 | @pytest.mark.parametrize( 30 | "text, x,y, scale, color", 31 | [ 32 | ("Hello World", 1, 10, 4, (0, 22, 103)), 33 | ("WWWWwwwmMMmmm", 30, 6, 1, (190, 173, 222)), 34 | ("wOooo00ooo", 104, 49, 9, 0xEFEFEF), 35 | ("!!!\n yay!", 100, 100, 5, (200, 200, 255)), 36 | ], 37 | ) 38 | def test_display_text(self, text, x, y, scale, color): 39 | global test_count 40 | 41 | expected_image = Image.open( 42 | os.path.join( 43 | self.abs_path, 44 | CONSTANTS.IMG_DIR_NAME, 45 | f"test_display_text_{test_count+1}.bmp", 46 | ) 47 | ) 48 | expected_image.convert("RGBA") 49 | expected_image.putalpha(255) 50 | loaded_img = expected_image.load() 51 | 52 | text_area = label.Label( 53 | terminalio.FONT, 54 | text=text, 55 | scale=scale, 56 | color=color, 57 | check_active_group_ref=False, 58 | ) 59 | text_area.x = x 60 | text_area.y = y 61 | 62 | main_img = text_area._Group__draw() 63 | 64 | helper._Helper__test_image_equality(main_img.load(), loaded_img) 65 | test_count += 1 66 | -------------------------------------------------------------------------------- /src/clue/test/test_adafruit_slideshow.py: -------------------------------------------------------------------------------- 1 | from ..adafruit_slideshow import SlideShow, PlayBackDirection, PlayBackOrder 2 | import board 3 | import pathlib 4 | import os 5 | 6 | from PIL import Image 7 | from .test_helpers import helper 8 | from base_circuitpython import base_cp_constants as CONSTANTS 9 | 10 | from unittest import mock 11 | 12 | from common import utils 13 | 14 | 15 | class TestAdafruitSlideShow(object): 16 | def setup_method(self): 17 | self.abs_path = pathlib.Path(__file__).parent.absolute() 18 | 19 | # Create a new black (default) image 20 | self.main_img = Image.new( 21 | "RGBA", 22 | (CONSTANTS.SCREEN_HEIGHT_WIDTH, CONSTANTS.SCREEN_HEIGHT_WIDTH), 23 | (0, 0, 0, 0), 24 | ) 25 | 26 | utils.send_to_simulator = mock.Mock() 27 | 28 | def test_slideshow(self): 29 | 30 | pic_dir = os.path.join(self.abs_path, "slideshow_pics") 31 | slideshow_images = [] 32 | for i in range(8): 33 | img = Image.open(os.path.join(pic_dir, f"pic_{i+1}.bmp")) 34 | img = img.convert("RGBA") 35 | img.putalpha(255) 36 | 37 | img = img.crop( 38 | (0, 0, CONSTANTS.SCREEN_HEIGHT_WIDTH, CONSTANTS.SCREEN_HEIGHT_WIDTH) 39 | ) 40 | 41 | if img.size[0] < 240 or img.size[1] < 240: 42 | black_overlay = Image.new( 43 | "RGBA", 44 | CONSTANTS.SCREEN_HEIGHT_WIDTH, 45 | CONSTANTS.SCREEN_HEIGHT_WIDTH, 46 | ) 47 | black_overlay.paste(img) 48 | img = black_overlay 49 | 50 | slideshow_images.append(img) 51 | 52 | # Create the slideshow object that plays through once alphabetically. 53 | slideshow = SlideShow( 54 | board.DISPLAY, 55 | dwell=3, 56 | folder=pic_dir, 57 | loop=True, 58 | fade_effect=True, 59 | auto_advance=True, 60 | order=PlayBackOrder.ALPHABETICAL, 61 | direction=PlayBackDirection.FORWARD, 62 | ) 63 | 64 | slideshow._SlideShow__send = self.__send_helper 65 | 66 | # first image's appear time is unstable,since it fades/scrolls in 67 | # can only predict following ones... 68 | 69 | for i in range(1, 8): 70 | slideshow.advance() 71 | helper._Helper__test_image_equality( 72 | self.main_img.load(), slideshow_images[i].load() 73 | ) 74 | 75 | # Create the slideshow object that plays through once backwards. 76 | slideshow2 = SlideShow( 77 | board.DISPLAY, 78 | dwell=3, 79 | folder=pic_dir, 80 | loop=True, 81 | fade_effect=False, 82 | auto_advance=True, 83 | order=PlayBackOrder.ALPHABETICAL, 84 | direction=PlayBackDirection.BACKWARD, 85 | ) 86 | 87 | slideshow2._SlideShow__send = self.__send_helper 88 | 89 | helper._Helper__test_image_equality( 90 | self.main_img.load(), slideshow_images[7].load() 91 | ) 92 | 93 | for i in range(6, -1, -1): 94 | slideshow2.advance() 95 | helper._Helper__test_image_equality( 96 | self.main_img.load(), slideshow_images[i].load() 97 | ) 98 | 99 | def __send_helper(self, image): 100 | self.main_img = image 101 | -------------------------------------------------------------------------------- /src/clue/test/test_helpers.py: -------------------------------------------------------------------------------- 1 | from base_circuitpython import base_cp_constants as CONSTANTS 2 | 3 | 4 | class Helper: 5 | def __test_image_equality(self, image_1, image_2): 6 | for i in range(CONSTANTS.SCREEN_HEIGHT_WIDTH): 7 | for j in range(CONSTANTS.SCREEN_HEIGHT_WIDTH): 8 | pixel_1 = image_1[j, i] 9 | pixel_2 = image_2[j, i] 10 | 11 | if not isinstance(pixel_1, tuple): 12 | pixel_1 = self.hex2rgba(pixel_1) 13 | 14 | if not isinstance(pixel_2, tuple): 15 | pixel_2 = self.hex2rgba(pixel_2) 16 | assert pixel_1[0:3] == pixel_2[0:3] 17 | 18 | def hex2rgba(self, curr_colour): 19 | 20 | ret_list = [] 21 | 22 | for i in range(3, -1, -1): 23 | val = (curr_colour >> (2 ** (i + 1))) & 255 24 | if val == 0: 25 | ret_list.append(0) 26 | else: 27 | ret_list.append(val) 28 | 29 | return tuple(ret_list) 30 | 31 | 32 | helper = Helper() 33 | -------------------------------------------------------------------------------- /src/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/common/__init__.py -------------------------------------------------------------------------------- /src/common/constants.py: -------------------------------------------------------------------------------- 1 | MAC_OS = "darwin" 2 | 3 | ERROR_SENDING_EVENT = "Error trying to send event to the process : " 4 | 5 | ACTIVE_DEVICE_FIELD = "active_device" 6 | STATE_FIELD = "state" 7 | 8 | CONNECTION_ATTEMPTS = 10 9 | TIME_DELAY = 0.03 10 | DEFAULT_PORT = "5577" 11 | 12 | MICROPYTHON_LIBRARY_NAME = "micropython" 13 | -------------------------------------------------------------------------------- /src/common/debugger_communication_client.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | 4 | import sys 5 | import json 6 | 7 | # WARNING: importing socketio will sometimes cause errors in normal execution 8 | # try to import common instead of common.debugger_communication_cleint 9 | import socketio 10 | 11 | import copy 12 | import pathlib 13 | 14 | from . import constants as CONSTANTS 15 | from . import utils 16 | import threading 17 | import os 18 | import python_constants as TOPLEVEL_CONSTANTS 19 | 20 | from adafruit_circuitplayground.express import cpx 21 | from adafruit_circuitplayground.constants import CPX 22 | 23 | # add ref for micropython and clue 24 | abs_path_to_parent_dir = os.path.dirname( 25 | os.path.join(pathlib.Path(__file__).parent, "..", "..") 26 | ) 27 | sys.path.insert( 28 | 0, os.path.join(abs_path_to_parent_dir, TOPLEVEL_CONSTANTS.MICROPYTHON_LIBRARY_NAME) 29 | ) 30 | 31 | sys.path.insert(0, os.path.join(abs_path_to_parent_dir, TOPLEVEL_CONSTANTS.CLUE_DIR)) 32 | 33 | from microbit.__model.microbit_model import __mb as mb 34 | from microbit.__model.constants import MICROBIT 35 | 36 | from base_circuitpython.base_cp_constants import CLUE 37 | from adafruit_clue import clue 38 | 39 | device_dict = {CPX: cpx, MICROBIT: mb, CLUE: clue} 40 | processing_state_event = threading.Event() 41 | previous_state = {} 42 | 43 | # similar to utils.send_to_simulator, but for debugging 44 | # (needs handle to device-specific debugger) 45 | def debug_send_to_simulator(state, active_device): 46 | global previous_state 47 | if state != previous_state: 48 | previous_state = copy.deepcopy(state) 49 | 50 | updated_state = utils.update_state_with_device_name(state, active_device) 51 | message = utils.create_message(updated_state) 52 | 53 | update_state(json.dumps(message)) 54 | 55 | 56 | # Create Socket Client 57 | sio = socketio.Client(reconnection_attempts=CONSTANTS.CONNECTION_ATTEMPTS) 58 | 59 | # TODO: Get port from process_user_code.py via childprocess communication 60 | 61 | 62 | # Initialize connection 63 | def init_connection(port=CONSTANTS.DEFAULT_PORT): 64 | sio.connect("http://localhost:{}".format(port)) 65 | 66 | 67 | # Transfer the user's inputs to the API 68 | def __update_api_state(data): 69 | try: 70 | event_state = json.loads(data) 71 | active_device_string = event_state.get(CONSTANTS.ACTIVE_DEVICE_FIELD) 72 | 73 | if active_device_string is not None: 74 | active_device = device_dict.get(active_device_string) 75 | if active_device is not None: 76 | active_device.update_state(event_state.get(CONSTANTS.STATE_FIELD)) 77 | 78 | except Exception as e: 79 | print(CONSTANTS.ERROR_SENDING_EVENT, e, file=sys.stderr, flush=True) 80 | 81 | 82 | # Method : Update State 83 | def update_state(state): 84 | processing_state_event.clear() 85 | sio.emit("updateState", state) 86 | processing_state_event.wait() 87 | 88 | 89 | # Event : Button pressed (A, B, A+B, Switch) 90 | # or Sensor changed (Temperature, light, Motion) 91 | @sio.on("input_changed") 92 | def input_changed(data): 93 | sio.emit("receivedState", data) 94 | __update_api_state(data) 95 | 96 | 97 | @sio.on("received_state") 98 | def received_state(data): 99 | processing_state_event.set() 100 | 101 | 102 | @sio.on("process_disconnect") 103 | def process_disconnect(data): 104 | sio.disconnect() 105 | -------------------------------------------------------------------------------- /src/common/telemetry.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from applicationinsights import TelemetryClient 4 | from common import constants as CONSTANTS 5 | from .telemetry_events import TelemetryEvent 6 | 7 | 8 | class Telemetry: 9 | def __init__(self): 10 | # State of the telemetry 11 | self.__enable_telemetry = True 12 | self.telemetry_client = TelemetryClient("__AIKEY__") 13 | self.telemetry_state = dict.fromkeys( 14 | [name for name, _ in TelemetryEvent.__members__.items()], False 15 | ) 16 | self.extension_name = "Device Simulator Express" 17 | 18 | def send_telemetry(self, event_name: TelemetryEvent): 19 | if ( 20 | self.__enable_telemetry 21 | and self.telemetry_available() 22 | and not self.telemetry_state[event_name.name] 23 | and not sys.platform.startswith(CONSTANTS.MAC_OS) 24 | ): 25 | self.telemetry_client.track_event( 26 | f"{self.extension_name}/{event_name.value}" 27 | ) 28 | self.telemetry_client.flush() 29 | self.telemetry_state[event_name.name] = True 30 | 31 | def telemetry_available(self): 32 | return self.telemetry_client.context.instrumentation_key == "__AIKEY__" 33 | 34 | 35 | telemetry_py = Telemetry() 36 | -------------------------------------------------------------------------------- /src/common/telemetry_events.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class TelemetryEvent(enum.Enum): 5 | CPX_API_ACCELERATION = "CPX.API.ACCELERATION" 6 | CPX_API_BUTTON_A = "CPX.API.BUTTON.A" 7 | CPX_API_BUTTON_B = "CPX.API.BUTTON.B" 8 | CPX_API_SWITCH = "CPX.API.SWITCH" 9 | CPX_API_TEMPERATURE = "CPX.API.TEMPERATURE" 10 | CPX_API_BRIGHTNESS = "CPX.API.BRIGHTNESS" 11 | CPX_API_LIGHT = "CPX.API.LIGHT" 12 | CPX_API_TOUCH = "CPX.API.TOUCH" 13 | CPX_API_SHAKE = "CPX.API.SHAKE" 14 | CPX_API_TAPPED = "CPX.API.TAPPED" 15 | CPX_API_PLAY_FILE = "CPX.API.PLAY.FILE" 16 | CPX_API_PLAY_TONE = "CPX.API.PLAY.TONE" 17 | CPX_API_START_TONE = "CPX.API.START.TONE" 18 | CPX_API_STOP_TONE = "CPX.API.STOP.TONE" 19 | CPX_API_DETECT_TAPS = "CPX.API.DETECT.TAPS" 20 | CPX_API_ADJUST_THRESHOLD = "CPX.API.ADJUST.THRESHOLD" 21 | CPX_API_RED_LED = "CPX.API.RED.LED" 22 | CPX_API_PIXELS = "CPX.API.PIXELS" 23 | MICROBIT_API_TEMPERATURE = "MICROBIT.API.TEMPERATURE" 24 | MICROBIT_API_ACCELEROMETER = "MICROBIT.API.ACCELEROMETER" 25 | MICROBIT_API_GESTURE = "MICROBIT.API.GESTURE" 26 | MICROBIT_API_DISPLAY_SCROLL = "MICROBIT.API.DISPLAY.SCROLL" 27 | MICROBIT_API_DISPLAY_SHOW = "MICROBIT.API.DISPLAY.SHOW" 28 | MICROBIT_API_DISPLAY_OTHER = "MICROBIT.API.DISPLAY_OTHER" 29 | MICROBIT_API_LIGHT_LEVEL = "MICROBIT.API.LIGHT.LEVEL" 30 | MICROBIT_API_IMAGE_CREATION = "MICROBIT.API.IMAGE.CREATION" 31 | MICROBIT_API_IMAGE_OTHER = "MICROBIT.API.IMAGE.OTHER" 32 | MICROBIT_API_IMAGE_STATIC = "MICROBIT.API.IMAGE.STATIC" 33 | MICROBIT_API_BUTTON = "MICROBIT.API.BUTTON" 34 | MICROBIT_API_COMPASS = "MICROBIT.API.COMPASS" 35 | MICROBIT_API_I2C = "MICROBIT.API.I2C" 36 | MICROBIT_API_SPI = "MICROBIT.API.SPI" 37 | MICROBIT_API_AUDIO = "MICROBIT.API.AUDIO" 38 | MICROBIT_API_MUSIC = "MICROBIT.API.MUSIC" 39 | MICROBIT_API_NEOPIXEL = "MICROBIT.API.NEOPIXEL" 40 | MICROBIT_API_RADIO = "MICROBIT.API.RADIO" 41 | MICROBIT_API_SPEECH = "MICROBIT.API.SPEECH" 42 | MICROBIT_API_UTIME = "MICROBIT.API.UTIME" 43 | CLUE_API_ACCELERATION = "CLUE.API.ACCELERATION" 44 | CLUE_API_BUTTON_A = "CLUE.API.BUTTON.A" 45 | CLUE_API_BUTTON_B = "CLUE.API.BUTTON.B" 46 | CLUE_API_WERE_PRESSED = "CLUE.API.WERE.PRESSED" 47 | CLUE_API_SHAKE = "CLUE.API.SHAKE" 48 | CLUE_API_COLOR = "CLUE.API.COLOR" 49 | CLUE_API_TEMPERATURE = "CLUE.API.TEMPERATURE" 50 | CLUE_API_MAGNETIC = "CLUE.API.MAGNETIC" 51 | CLUE_API_PROXIMITY = "CLUE.API.PROXIMITY" 52 | CLUE_API_GYRO = "CLUE.API.GYRO" 53 | CLUE_API_GESTURE = "CLUE.API.GESTURE" 54 | CLUE_API_HUMIDITY = "CLUE.API.HUMIDITY" 55 | CLUE_API_PRESSURE = "CLUE.API.PRESSURE" 56 | CLUE_API_ALTITUDE = "CLUE.API.ALTITUDE" 57 | CLUE_API_SEA_LEVEL_PRESSURE = "CLUE.API.SEA.LEVEL.PRESSURE" 58 | CLUE_API_PIXEL = "CLUE.API.PIXEL" 59 | CLUE_API_TOUCH = "CLUE.API.TOUCH" 60 | CLUE_API_WHITE_LEDS = "CLUE.API.WHITE.LEDS" 61 | CLUE_API_RED_LED = "CLUE.API.RED.LED" 62 | CLUE_API_SOUND = "CLUE.API.SOUND" 63 | CLUE_API_TEXT_DISPLAY = "CLUE.API.TEXT.DISPLAY" 64 | CLUE_API_SLIDESHOW = "CLUE.API.SLIDESHOW" 65 | CLUE_API_TILE_GRID = "CLUE.API.TILE.GRID" 66 | -------------------------------------------------------------------------------- /src/common/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/common/test/__init__.py -------------------------------------------------------------------------------- /src/common/test/test_utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from unittest import mock 4 | 5 | from common import constants as CONSTANTS 6 | from common import utils 7 | 8 | 9 | class TestUtils(object): 10 | def test_remove_leading_slashes(self): 11 | original = "///a//b/" 12 | expected = "a//b/" 13 | assert expected == utils.remove_leading_slashes(original) 14 | 15 | def test_escape_notOSX(self): 16 | _utils_sys = utils.sys 17 | if sys.platform.startswith(CONSTANTS.MAC_OS): 18 | utils.sys = mock.MagicMock() 19 | utils.sys.configure_mock(platform="win32") 20 | original = "a b" 21 | assert original == utils.escape_if_OSX(original) 22 | utils.sys = _utils_sys 23 | 24 | def test_escape_isOSX(self): 25 | _utils_sys = utils.sys 26 | if not sys.platform.startswith(CONSTANTS.MAC_OS): 27 | utils.sys = mock.MagicMock() 28 | utils.sys.configure_mock(platform="darwin") 29 | original = "a b" 30 | expected = "a%20b" 31 | assert expected == utils.escape_if_OSX(original) 32 | utils.sys = _utils_sys 33 | -------------------------------------------------------------------------------- /src/common/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | 4 | from . import constants as CONSTANTS 5 | import json 6 | import copy 7 | import time 8 | import sys 9 | 10 | previous_state = {} 11 | 12 | abs_path_to_user_file = "" 13 | debug_mode = False 14 | 15 | 16 | def update_state_with_device_name(state, device_name): 17 | updated_state = dict(state) 18 | 19 | state_ext = { 20 | "device_name": device_name, 21 | } 22 | updated_state.update(state_ext) 23 | 24 | return updated_state 25 | 26 | 27 | def create_message(msg, send_type="state"): 28 | if isinstance(msg, dict): 29 | msg = json.dumps(msg) 30 | 31 | message = {"type": send_type, "data": msg} 32 | return message 33 | 34 | 35 | def send_to_simulator(state, device_name): 36 | global previous_state 37 | 38 | updated_state = update_state_with_device_name(state, device_name) 39 | message = create_message(updated_state) 40 | 41 | if updated_state != previous_state: 42 | previous_state = copy.deepcopy(updated_state) 43 | print(json.dumps(message) + "\0", end="", file=sys.__stdout__, flush=True) 44 | time.sleep(CONSTANTS.TIME_DELAY) 45 | 46 | 47 | def send_print_to_simulator(raw_msg): 48 | data_str = str(raw_msg) 49 | message = create_message(data_str, "print") 50 | print(json.dumps(message) + "\0", file=sys.__stdout__, flush=True) 51 | time.sleep(CONSTANTS.TIME_DELAY) 52 | 53 | 54 | def remove_leading_slashes(string): 55 | string = string.lstrip("\\/") 56 | return string 57 | 58 | 59 | def escape_if_OSX(file_name): 60 | if sys.platform == CONSTANTS.MAC_OS: 61 | file_name = file_name.replace(" ", "%20") 62 | return file_name 63 | 64 | 65 | def print_for_unimplemented_functions(function_name): 66 | msg = f"'{function_name}' is not implemented in the simulator but it will work on the actual device!\n" 67 | send_print_to_simulator(msg) 68 | -------------------------------------------------------------------------------- /src/cpxWorkspace.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as vscode from "vscode"; 4 | 5 | export class CPXWorkspace { 6 | static get rootPath(): string | undefined { 7 | const workspaceFolders = vscode.workspace.workspaceFolders; 8 | if (!workspaceFolders || workspaceFolders.length === 0) { 9 | return undefined; 10 | } 11 | 12 | for (const workspaceFolder of workspaceFolders) { 13 | const workspaceFolderPath = workspaceFolder.uri.fsPath; 14 | const cpxConfigPath = path.join( 15 | workspaceFolderPath, 16 | ".vscode", 17 | "cpx.json" 18 | ); 19 | if (fs.existsSync(cpxConfigPath)) { 20 | return workspaceFolderPath; 21 | } 22 | } 23 | 24 | return workspaceFolders[0].uri.fsPath; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/debug_user_code.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | 4 | import os 5 | import sys 6 | import traceback 7 | from pathlib import Path 8 | import python_constants as CONSTANTS 9 | import check_python_dependencies 10 | from common import utils 11 | 12 | # will propagate errors if dependencies aren't sufficient 13 | check_python_dependencies.check_for_dependencies() 14 | 15 | abs_path_to_parent_dir = os.path.dirname(os.path.abspath(__file__)) 16 | 17 | # Insert absolute path to Adafruit library for CPX into sys.path 18 | abs_path_to_adafruit_lib = os.path.join( 19 | abs_path_to_parent_dir, CONSTANTS.ADAFRUIT_LIBRARY_NAME 20 | ) 21 | sys.path.insert(0, abs_path_to_adafruit_lib) 22 | 23 | # Insert absolute path to Micropython libraries for micro:bit into sys.path 24 | abs_path_to_micropython_lib = os.path.join( 25 | abs_path_to_parent_dir, CONSTANTS.MICROPYTHON_LIBRARY_NAME 26 | ) 27 | sys.path.insert(0, abs_path_to_micropython_lib) 28 | 29 | # Insert absolute path to library for CLUE into sys.path 30 | sys.path.insert(0, os.path.join(abs_path_to_parent_dir, CONSTANTS.CLUE)) 31 | 32 | # Insert absolute path to Circuitpython libraries for CLUE into sys.path 33 | sys.path.insert(0, os.path.join(abs_path_to_parent_dir, CONSTANTS.CIRCUITPYTHON)) 34 | 35 | # This import must happen after the sys.path is modified 36 | from common import debugger_communication_client 37 | 38 | # get board so we can get terminal handle 39 | import board 40 | 41 | # get handle to terminal for clue 42 | curr_terminal = board.DISPLAY.terminal 43 | 44 | ## Execute User Code ## 45 | 46 | # Get user's code path 47 | abs_path_to_code_file = "" 48 | if len(sys.argv) > 1 and sys.argv[1]: 49 | abs_path_to_code_file = sys.argv[1] 50 | else: 51 | raise FileNotFoundError(CONSTANTS.ERROR_NO_FILE) 52 | 53 | # Get Debugger Server Port 54 | server_port = CONSTANTS.DEFAULT_PORT 55 | if len(sys.argv) > 2: 56 | server_port = sys.argv[2] 57 | 58 | # Init Communication 59 | debugger_communication_client.init_connection(server_port) 60 | 61 | # Init API variables 62 | utils.abs_path_to_user_file = abs_path_to_code_file 63 | utils.debug_mode = True 64 | 65 | # overriding print function so that it shows on clue terminal 66 | def print_decorator(func): 67 | global curr_terminal 68 | 69 | def wrapped_func(*args, **kwargs): 70 | curr_terminal.add_str_to_terminal("".join(str(e) for e in args)) 71 | return func(*args, **kwargs) 72 | 73 | return wrapped_func 74 | 75 | 76 | print = print_decorator(print) 77 | 78 | # Execute the user's code file 79 | with open(abs_path_to_code_file, encoding="utf8") as user_code_file: 80 | curr_terminal.add_str_to_terminal(CONSTANTS.CODE_START_MSG_CLUE) 81 | user_code = user_code_file.read() 82 | try: 83 | codeObj = compile(user_code, abs_path_to_code_file, CONSTANTS.EXEC_COMMAND) 84 | exec(codeObj, {"print": print}) 85 | sys.stdout.flush() 86 | except Exception as e: 87 | exc_type, exc_value, exc_traceback = sys.exc_info() 88 | errorMessage = CONSTANTS.ERROR_TRACEBACK 89 | stackTrace = traceback.format_exception(exc_type, exc_value, exc_traceback) 90 | 91 | for frameIndex in range(2, len(stackTrace) - 1): 92 | errorMessage += "\t" + str(stackTrace[frameIndex]) 93 | print(e, errorMessage, file=sys.stderr, flush=True) 94 | curr_terminal.add_str_to_terminal(CONSTANTS.CODE_FINISHED_MSG_CLUE) 95 | board.DISPLAY.show(None) 96 | -------------------------------------------------------------------------------- /src/debugger/debugAdapter.ts: -------------------------------------------------------------------------------- 1 | import { DebugAdapterTracker, DebugConsole, DebugSession } from "vscode"; 2 | import { DebuggerCommunicationService } from "../service/debuggerCommunicationService"; 3 | import { MessagingService } from "../service/messagingService"; 4 | import { DEBUG_COMMANDS } from "../view/constants"; 5 | 6 | export class DebugAdapter implements DebugAdapterTracker { 7 | private readonly console: DebugConsole | undefined; 8 | private readonly messagingService: MessagingService; 9 | private debugCommunicationService: DebuggerCommunicationService; 10 | constructor( 11 | debugSession: DebugSession, 12 | messagingService: MessagingService, 13 | debugCommunicationService: DebuggerCommunicationService 14 | ) { 15 | this.console = debugSession.configuration.console; 16 | this.messagingService = messagingService; 17 | this.debugCommunicationService = debugCommunicationService; 18 | } 19 | onWillStartSession() { 20 | // To Implement 21 | } 22 | onWillReceiveMessage(message: any): void { 23 | if (message.command) { 24 | // Only send pertinent debug messages 25 | switch (message.command) { 26 | case DEBUG_COMMANDS.CONTINUE: 27 | this.messagingService.sendStartMessage(); 28 | break; 29 | case DEBUG_COMMANDS.STACK_TRACE: 30 | this.messagingService.sendPauseMessage(); 31 | break; 32 | case DEBUG_COMMANDS.DISCONNECT: 33 | // Triggered on stop event for debugger 34 | if (!message.arguments.restart) { 35 | this.debugCommunicationService.handleStopEvent(); 36 | } 37 | break; 38 | } 39 | } 40 | } 41 | // A debugger error should unlock the webview 42 | onError() { 43 | this.messagingService.sendStartMessage(); 44 | } 45 | // Device is always running when exiting debugging mode 46 | onExit() { 47 | this.messagingService.sendStartMessage(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/debugger/debugAdapterFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DebugAdapterTracker, 3 | DebugAdapterTrackerFactory, 4 | DebugSession, 5 | ProviderResult, 6 | } from "vscode"; 7 | import { DebuggerCommunicationService } from "../service/debuggerCommunicationService"; 8 | import { MessagingService } from "../service/messagingService"; 9 | import { DebugAdapter } from "./debugAdapter"; 10 | 11 | export class DebugAdapterFactory implements DebugAdapterTrackerFactory { 12 | private debugSession: DebugSession; 13 | private messagingService: MessagingService; 14 | private debugCommunicationService: DebuggerCommunicationService; 15 | constructor( 16 | debugSession: DebugSession, 17 | messagingService: MessagingService, 18 | debugCommunicationService: DebuggerCommunicationService 19 | ) { 20 | this.debugSession = debugSession; 21 | this.messagingService = messagingService; 22 | this.debugCommunicationService = debugCommunicationService; 23 | } 24 | public createDebugAdapterTracker( 25 | session: DebugSession 26 | ): ProviderResult { 27 | return new DebugAdapter( 28 | session, 29 | this.messagingService, 30 | this.debugCommunicationService 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/dev-requirements.txt: -------------------------------------------------------------------------------- 1 | -r ./requirements.txt 2 | black==19.10b0 3 | pytest==5.0.1 -------------------------------------------------------------------------------- /src/install_dependencies.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | import pathlib 4 | import os 5 | 6 | os.chdir(str(pathlib.Path(__file__).parent.parent.absolute())) 7 | subprocess.check_call( 8 | [sys.executable, "-m", "pip", "install", "-r", "./out/requirements.txt"] 9 | ) 10 | -------------------------------------------------------------------------------- /src/micropython/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/micropython/__init__.py -------------------------------------------------------------------------------- /src/micropython/audio.py: -------------------------------------------------------------------------------- 1 | from common import utils 2 | from common.telemetry import telemetry_py 3 | from common.telemetry_events import TelemetryEvent 4 | 5 | # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/audio.html. 6 | 7 | 8 | def play(source, wait=True, pin="pin0", return_pin=None): 9 | """ 10 | This function is not implemented in the simulator. 11 | 12 | Play the source to completion. 13 | 14 | ``source`` is an iterable, each element of which must be an ``AudioFrame``. 15 | 16 | If ``wait`` is ``True``, this function will block until the source is exhausted. 17 | 18 | ``pin`` specifies which pin the speaker is connected to. 19 | 20 | ``return_pin`` specifies a differential pin to connect to the speaker 21 | instead of ground. 22 | """ 23 | utils.print_for_unimplemented_functions(play.__name__) 24 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_AUDIO) 25 | 26 | 27 | class AudioFrame: 28 | """ 29 | This class is not implemented in the simulator. 30 | 31 | An ``AudioFrame`` object is a list of 32 samples each of which is a signed byte 32 | (whole number between -128 and 127). 33 | 34 | It takes just over 4 ms to play a single frame. 35 | """ 36 | 37 | def __init__(self): 38 | utils.print_for_unimplemented_functions(AudioFrame.__init__.__qualname__) 39 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_AUDIO) 40 | -------------------------------------------------------------------------------- /src/micropython/microbit/__init__.py: -------------------------------------------------------------------------------- 1 | from .__model.image import Image 2 | from .__model.microbit_model import __mb 3 | from common.telemetry import telemetry_py 4 | from common.telemetry_events import TelemetryEvent 5 | 6 | accelerometer = __mb.accelerometer 7 | button_a = __mb.button_a 8 | button_b = __mb.button_b 9 | compass = __mb.compass 10 | display = __mb.display 11 | i2c = __mb.i2c 12 | spi = __mb.spi 13 | 14 | 15 | def panic(n): 16 | """ 17 | Enter a panic mode. Requires restart. Pass in an arbitrary integer <= 255 to indicate a status 18 | """ 19 | __mb.panic(n) 20 | 21 | 22 | def reset(): 23 | """ 24 | Restart the board. 25 | """ 26 | __mb.reset() 27 | 28 | 29 | def sleep(n): 30 | """ 31 | Wait for ``n`` milliseconds. One second is 1000 milliseconds, so:: 32 | microbit.sleep(1000) 33 | will pause the execution for one second. ``n`` can be an integer or 34 | a floating point number. 35 | """ 36 | __mb.sleep(n) 37 | 38 | 39 | def running_time(): 40 | """ 41 | Return the number of milliseconds since the board was switched on or 42 | restarted. 43 | """ 44 | return __mb.running_time() 45 | 46 | 47 | def temperature(): 48 | """ 49 | Return the temperature of the micro:bit in degrees Celcius. 50 | """ 51 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_TEMPERATURE) 52 | return __mb.temperature() 53 | -------------------------------------------------------------------------------- /src/micropython/microbit/__model/button.py: -------------------------------------------------------------------------------- 1 | from common.telemetry import telemetry_py 2 | from common.telemetry_events import TelemetryEvent 3 | 4 | 5 | class Button: 6 | # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/button.html. 7 | def __init__(self): 8 | self.__pressed = False 9 | self.__presses = 0 10 | self.__prev_pressed = False 11 | 12 | def is_pressed(self): 13 | """ 14 | Returns ``True`` if the specified button ``button`` is currently being 15 | held down, and ``False`` otherwise. 16 | """ 17 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_BUTTON) 18 | return self.__pressed 19 | 20 | def was_pressed(self): 21 | """ 22 | Returns ``True`` or ``False`` to indicate if the button was pressed 23 | (went from up to down) since the device started or the last time this 24 | method was called. Calling this method will clear the press state so 25 | that the button must be pressed again before this method will return 26 | ``True`` again. 27 | """ 28 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_BUTTON) 29 | res = self.__prev_pressed 30 | self.__prev_pressed = False 31 | return res 32 | 33 | def get_presses(self): 34 | """ 35 | Returns the running total of button presses, and resets this total 36 | to zero before returning. 37 | """ 38 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_BUTTON) 39 | res = self.__presses 40 | self.__presses = 0 41 | return res 42 | 43 | def __press_down(self): 44 | self.__pressed = True 45 | self.__prev_pressed = True 46 | self.__presses += 1 47 | 48 | def __release(self): 49 | self.__pressed = False 50 | 51 | def __update(self, is_button_pressed): 52 | if is_button_pressed is not None: 53 | was_button_pressed = self.is_pressed() 54 | 55 | if is_button_pressed != was_button_pressed: 56 | if is_button_pressed: 57 | self.__press_down() 58 | else: 59 | self.__release() 60 | -------------------------------------------------------------------------------- /src/micropython/microbit/__model/i2c.py: -------------------------------------------------------------------------------- 1 | from common import utils 2 | from common.telemetry import telemetry_py 3 | from common.telemetry_events import TelemetryEvent 4 | 5 | 6 | class I2c: 7 | # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/i2c.html. 8 | def init(self, freq=100000, sda="pin20", scl="pin19"): 9 | """ 10 | This function is not implemented in the simulator. 11 | 12 | Re-initialize peripheral with the specified clock frequency ``freq`` on the 13 | specified ``sda`` and ``scl`` pins. 14 | 15 | Warning: 16 | 17 | Changing the I²C pins from defaults will make the accelerometer and 18 | compass stop working, as they are connected internally to those pins. 19 | """ 20 | utils.print_for_unimplemented_functions(I2c.init.__qualname__) 21 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) 22 | 23 | def scan(self): 24 | """ 25 | This function is not implemented in the simulator. 26 | 27 | Scan the bus for devices. Returns a list of 7-bit addresses corresponding 28 | to those devices that responded to the scan. 29 | """ 30 | utils.print_for_unimplemented_functions(I2c.scan.__name__) 31 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) 32 | 33 | def read(self, addr, n, repeat=False): 34 | """ 35 | This function is not implemented in the simulator. 36 | 37 | Read ``n`` bytes from the device with 7-bit address ``addr``. If ``repeat`` 38 | is ``True``, no stop bit will be sent. 39 | """ 40 | utils.print_for_unimplemented_functions(I2c.read.__name__) 41 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) 42 | 43 | def write(self, addr, buf, repeat=False): 44 | """ 45 | This function is not implemented in the simulator. 46 | 47 | Write bytes from ``buf`` to the device with 7-bit address ``addr``. If 48 | ``repeat`` is ``True``, no stop bit will be sent. 49 | """ 50 | utils.print_for_unimplemented_functions(I2c.write.__name__) 51 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_I2C) 52 | -------------------------------------------------------------------------------- /src/micropython/microbit/__model/microbit_model.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from common import utils 4 | from .accelerometer import Accelerometer 5 | from .button import Button 6 | from .compass import Compass 7 | from .display import Display 8 | from .i2c import I2c 9 | from .spi import SPI 10 | from . import constants as CONSTANTS 11 | 12 | 13 | class MicrobitModel: 14 | def __init__(self): 15 | # State in the Python process 16 | self.accelerometer = Accelerometer() 17 | self.button_a = Button() 18 | self.button_b = Button() 19 | self.compass = Compass() 20 | self.display = Display() 21 | self.i2c = I2c() 22 | self.spi = SPI() 23 | 24 | self.__start_time = time.time() 25 | self.__temperature = 0 26 | self.__microbit_button_dict = { 27 | "button_a": self.button_a, 28 | "button_b": self.button_b, 29 | } 30 | 31 | def panic(self, n): 32 | # Due to the shim, there is another call frame. 33 | utils.print_for_unimplemented_functions(MicrobitModel.panic.__name__) 34 | 35 | def reset(self): 36 | # Due to the shim, there is another call frame. 37 | utils.print_for_unimplemented_functions(MicrobitModel.reset.__name__) 38 | 39 | def sleep(self, n): 40 | time.sleep(n / 1000) 41 | 42 | def running_time(self): 43 | print(f"time. time: {time.time()}") 44 | return time.time() - self.__start_time 45 | 46 | def temperature(self): 47 | return self.__temperature 48 | 49 | def __set_temperature(self, temperature): 50 | if ( 51 | temperature < CONSTANTS.MIN_TEMPERATURE 52 | or temperature > CONSTANTS.MAX_TEMPERATURE 53 | ): 54 | raise ValueError(CONSTANTS.INVALID_TEMPERATURE_ERR) 55 | else: 56 | self.__temperature = temperature 57 | 58 | def update_state(self, new_state): 59 | self.__update_buttons(new_state) 60 | self.__update_motion(new_state) 61 | self.__update_light(new_state) 62 | self.__update_temp(new_state) 63 | self.__update_gesture(new_state) 64 | 65 | # helpers 66 | def __update_buttons(self, new_state): 67 | # get button pushes 68 | for button_name in CONSTANTS.EXPECTED_INPUT_BUTTONS: 69 | button = self.__microbit_button_dict[button_name] 70 | button._Button__update(new_state.get(button_name)) 71 | 72 | def __update_motion(self, new_state): 73 | # set motion_x, motion_y, motion_z 74 | for name, direction in CONSTANTS.EXPECTED_INPUT_ACCEL.items(): 75 | self.accelerometer._Accelerometer__update_motion( 76 | direction, new_state.get(name) 77 | ) 78 | 79 | def __update_light(self, new_state): 80 | # set light level 81 | new_light_level = new_state.get(CONSTANTS.EXPECTED_INPUT_LIGHT) 82 | self.display._Display__update_light_level(new_light_level) 83 | 84 | def __update_temp(self, new_state): 85 | # set temperature 86 | new_temp = new_state.get(CONSTANTS.EXPECTED_INPUT_TEMP) 87 | if new_temp is not None: 88 | previous_temp = self.temperature() 89 | if new_temp != previous_temp: 90 | self._MicrobitModel__set_temperature(new_temp) 91 | 92 | def __update_gesture(self, new_state): 93 | # set gesture 94 | new_gesture = new_state.get(CONSTANTS.EXPECTED_INPUT_GESTURE) 95 | self.accelerometer._Accelerometer__update_gesture(new_gesture) 96 | 97 | 98 | __mb = MicrobitModel() 99 | -------------------------------------------------------------------------------- /src/micropython/microbit/__model/producer_property.py: -------------------------------------------------------------------------------- 1 | class ProducerProperty(property): 2 | def __get__(self, cls, owner): 3 | return classmethod(self.fget).__get__(cls, owner)() 4 | -------------------------------------------------------------------------------- /src/micropython/microbit/__model/spi.py: -------------------------------------------------------------------------------- 1 | from common import utils 2 | from common.telemetry import telemetry_py 3 | from common.telemetry_events import TelemetryEvent 4 | 5 | 6 | class SPI: 7 | # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/spi.html. 8 | def init( 9 | baudrate=1000000, bits=8, mode=0, sclk="pin13", mosi="pin15", miso="pin14" 10 | ): 11 | """ 12 | This function is not implemented in the simulator. 13 | 14 | Initialize SPI communication with the specified parameters on the 15 | specified ``pins``. Note that for correct communication, the parameters 16 | have to be the same on both communicating devices. 17 | 18 | The ``baudrate`` defines the speed of communication. 19 | 20 | The ``bits`` defines the size of bytes being transmitted. Currently only 21 | ``bits=8`` is supported. However, this may change in the future. 22 | 23 | The ``mode`` determines the combination of clock polarity and phase 24 | according to the following convention, with polarity as the high order bit 25 | and phase as the low order bit: 26 | 27 | Polarity (aka CPOL) 0 means that the clock is at logic value 0 when idle 28 | and goes high (logic value 1) when active; polarity 1 means the clock is 29 | at logic value 1 when idle and goes low (logic value 0) when active. Phase 30 | (aka CPHA) 0 means that data is sampled on the leading edge of the clock, 31 | and 1 means on the trailing edge. 32 | 33 | The ``sclk``, ``mosi`` and ``miso`` arguments specify the pins to use for 34 | each type of signal. 35 | """ 36 | utils.print_for_unimplemented_functions(SPI.init.__qualname__) 37 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) 38 | 39 | def read(self, nbytes): 40 | """ 41 | This function is not implemented in the simulator. 42 | 43 | Read at most ``nbytes``. Returns what was read. 44 | """ 45 | utils.print_for_unimplemented_functions(SPI.read.__name__) 46 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) 47 | 48 | def write(self, buffer): 49 | """ 50 | This function is not implemented in the simulator. 51 | 52 | Write the ``buffer`` of bytes to the bus. 53 | """ 54 | utils.print_for_unimplemented_functions(SPI.write.__name__) 55 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) 56 | 57 | def write_readinto(self, out, in_): 58 | """ 59 | This function is not implemented in the simulator. 60 | 61 | Write the ``out`` buffer to the bus and read any response into the ``in_`` 62 | buffer. The length of the buffers should be the same. The buffers can be 63 | the same object. 64 | """ 65 | utils.print_for_unimplemented_functions(SPI.write_readinto.__name__) 66 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPI) 67 | -------------------------------------------------------------------------------- /src/micropython/microbit/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/micropython/microbit/test/__init__.py -------------------------------------------------------------------------------- /src/micropython/microbit/test/test_button.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ..__model.button import Button 3 | 4 | 5 | class TestButton(object): 6 | def setup_method(self): 7 | self.button = Button() 8 | 9 | def test_press_down(self): 10 | self.button._Button__press_down() 11 | assert self.button._Button__presses == 1 12 | assert self.button._Button__pressed 13 | assert self.button._Button__prev_pressed 14 | self.button._Button__press_down() 15 | assert self.button._Button__presses == 2 16 | assert self.button._Button__pressed 17 | assert self.button._Button__prev_pressed 18 | 19 | def test_release(self): 20 | self.button._Button__pressed = True 21 | self.button._Button__prev_pressed = False 22 | self.button._Button__release() 23 | assert not self.button._Button__pressed 24 | 25 | def test_is_pressed(self): 26 | assert not self.button.is_pressed() 27 | self.button._Button__press_down() 28 | assert self.button.is_pressed() 29 | 30 | def test_was_pressed(self): 31 | assert not self.button.was_pressed() 32 | self.button._Button__press_down() 33 | self.button._Button__release() 34 | assert self.button.was_pressed() 35 | # Button resets __prev_pressed after was_pressed() is called. 36 | assert not self.button.was_pressed() 37 | 38 | @pytest.mark.parametrize("presses", [1, 2, 4]) 39 | def test_get_presses(self, presses): 40 | assert 0 == self.button.get_presses() 41 | for i in range(presses): 42 | self.button._Button__press_down() 43 | self.button._Button__release() 44 | assert presses == self.button.get_presses() 45 | # Presses is reset to 0 after get_presses() is called. 46 | assert 0 == self.button.get_presses() 47 | -------------------------------------------------------------------------------- /src/micropython/microbit/test/test_init.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pytest 4 | from unittest import mock 5 | 6 | from .. import * 7 | from ..__model.microbit_model import MicrobitModel 8 | 9 | # tests methods in __init__.py 10 | 11 | 12 | class TestShim(object): 13 | def test_sleep(self): 14 | # Save pointer to function about to be mocked 15 | real_function = MicrobitModel.sleep 16 | 17 | milliseconds = 100 18 | MicrobitModel.sleep = mock.Mock() 19 | sleep(milliseconds) 20 | MicrobitModel.sleep.assert_called_with(milliseconds) 21 | 22 | # Restore original function 23 | MicrobitModel.sleep = real_function 24 | 25 | def test_running_time(self): 26 | # Save pointer to function about to be mocked 27 | real_function = MicrobitModel.running_time 28 | 29 | MicrobitModel.running_time = mock.Mock() 30 | running_time() 31 | MicrobitModel.running_time.assert_called_once() 32 | 33 | # Restore original function 34 | MicrobitModel.running_time = real_function 35 | 36 | def test_temperature(self): 37 | # Save pointer to function about to be mocked 38 | real_function = MicrobitModel.temperature 39 | 40 | MicrobitModel.temperature = mock.Mock() 41 | temperature() 42 | MicrobitModel.temperature.asser_called_once() 43 | 44 | # Restore original function 45 | MicrobitModel.temperature = real_function 46 | -------------------------------------------------------------------------------- /src/micropython/microbit/test/test_microbit_model.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pytest 4 | from unittest import mock 5 | from ..__model import constants as CONSTANTS 6 | from ..__model.microbit_model import MicrobitModel 7 | 8 | 9 | class TestMicrobitModel(object): 10 | def setup_method(self): 11 | self.__mb = MicrobitModel() 12 | 13 | @pytest.mark.parametrize("value", [9, 30, 1999]) 14 | def test_sleep(self, value): 15 | time.sleep = mock.Mock() 16 | self.__mb.sleep(value) 17 | time.sleep.assert_called_with(value / 1000) 18 | 19 | def test_running_time(self): 20 | mock_start_time = 10 21 | mock_end_time = 300 22 | self.__mb._MicrobitModel__start_time = mock_start_time 23 | time.time = mock.MagicMock(return_value=mock_end_time) 24 | assert mock_end_time - mock_start_time == pytest.approx( 25 | self.__mb.running_time() 26 | ) 27 | 28 | @pytest.mark.parametrize( 29 | "temperature", 30 | [ 31 | CONSTANTS.MIN_TEMPERATURE, 32 | CONSTANTS.MIN_TEMPERATURE + 1, 33 | 0, 34 | CONSTANTS.MAX_TEMPERATURE - 1, 35 | CONSTANTS.MAX_TEMPERATURE, 36 | ], 37 | ) 38 | def test_temperature(self, temperature): 39 | self.__mb._MicrobitModel__set_temperature(temperature) 40 | assert temperature == self.__mb.temperature() 41 | 42 | @pytest.mark.parametrize( 43 | "invalid_temperature", 44 | [CONSTANTS.MIN_TEMPERATURE - 1, CONSTANTS.MAX_TEMPERATURE + 1], 45 | ) 46 | def test_invalid_temperature(self, invalid_temperature): 47 | with pytest.raises(ValueError): 48 | self.__mb._MicrobitModel__set_temperature(invalid_temperature) 49 | -------------------------------------------------------------------------------- /src/micropython/speech.py: -------------------------------------------------------------------------------- 1 | from common import utils 2 | from common.telemetry import telemetry_py 3 | from common.telemetry_events import TelemetryEvent 4 | 5 | # The implementation is based off of https://microbit-micropython.readthedocs.io/en/v1.0.1/speech.html. 6 | 7 | 8 | def translate(words): 9 | """ 10 | This function is not implemented in the simulator. 11 | 12 | Given English words in the string ``words``, return a string containing 13 | a best guess at the appropriate phonemes to pronounce. The output is 14 | generated from this 15 | `text to phoneme translation table `_. 16 | 17 | This function should be used to generate a first approximation of phonemes 18 | that can be further hand-edited to improve accuracy, inflection and 19 | emphasis. 20 | """ 21 | utils.print_for_unimplemented_functions(translate.__name__) 22 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) 23 | 24 | 25 | def pronounce(phonemes, pitch=64, speed=72, mouth=128, throat=128): 26 | """ 27 | This function is not implemented in the simulator. 28 | 29 | Pronounce the phonemes in the string ``phonemes``. See below for details of 30 | how to use phonemes to finely control the output of the speech synthesiser. 31 | Override the optional pitch, speed, mouth and throat settings to change the 32 | timbre (quality) of the voice. 33 | """ 34 | utils.print_for_unimplemented_functions(pronounce.__name__) 35 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) 36 | 37 | 38 | def say(words, pitch=64, speed=72, mouth=128, throat=128): 39 | """ 40 | This function is not implemented in the simulator. 41 | 42 | Say the English words in the string ``words``. The result is semi-accurate 43 | for English. Override the optional pitch, speed, mouth and throat 44 | settings to change the timbre (quality) of the voice. This is a short-hand 45 | equivalent of: ``speech.pronounce(speech.translate(words))`` 46 | """ 47 | utils.print_for_unimplemented_functions(say.__name__) 48 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) 49 | 50 | 51 | def sing(phonemes, pitch=64, speed=72, mouth=128, throat=128): 52 | """ 53 | This function is not implemented in the simulator. 54 | 55 | Sing the phonemes contained in the string ``phonemes``. Changing the pitch 56 | and duration of the note is described below. Override the optional pitch, 57 | speed, mouth and throat settings to change the timbre (quality) of the 58 | voice. 59 | """ 60 | utils.print_for_unimplemented_functions(sing.__name__) 61 | telemetry_py.send_telemetry(TelemetryEvent.MICROBIT_API_SPEECH) 62 | -------------------------------------------------------------------------------- /src/python_constants.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT license. 3 | 4 | ACTIVE_DEVICE_FIELD = "active_device" 5 | 6 | ADAFRUIT_DRIVE_NAME = "CIRCUITPY" 7 | 8 | DEPEND_ERR = 'The required dependencies aren\'t downloaded. Please use CTRL+SHIFT+P to open the command palette and select "Device Simulator Express: Install Extension Dependencies".' 9 | 10 | DEVICE_NOT_IMPLEMENTED_ERROR = "Device not implemented." 11 | 12 | ENABLE_TELEMETRY = "enable_telemetry" 13 | 14 | EXEC_COMMAND = "exec" 15 | ERROR_SENDING_EVENT = "Error trying to send event to the process : " 16 | ERROR_TRACEBACK = "\n\tTraceback of code execution : \n" 17 | ERROR_NO_FILE = "Error : No file was passed to the process to execute.\n" 18 | 19 | ADAFRUIT_LIBRARY_NAME = "adafruit_circuitplayground" 20 | MICROPYTHON_LIBRARY_NAME = "micropython" 21 | 22 | LINUX_OS = "linux" 23 | 24 | MAC_OS = "darwin" 25 | MOUNT_COMMAND = "mount" 26 | 27 | NO_ADAFRUIT_DEVICE_DETECTED_ERROR_TITLE = ( 28 | "No Adafruit device (Circuit Playground Express or Clue) detected" 29 | ) 30 | NO_ADAFRUIT_DEVICE_DETECTED_ERROR_DETAIL = ( 31 | "Could not find drive with name 'CIRCUITPYTHON'. Detected OS: {}" 32 | ) 33 | NO_MICROBIT_DETECTED_ERROR_TITLE = "No micro:bit detected" 34 | NOT_SUPPORTED_OS = 'The OS "{}" not supported.' 35 | NOT_IMPLEMENTED_ERROR = "This method is not implemented by the simulator" 36 | 37 | STATE_FIELD = "state" 38 | 39 | UTF_FORMAT = "utf-8" 40 | 41 | WINDOWS_OS = "win32" 42 | 43 | DEFAULT_PORT = "5577" 44 | 45 | CPX = "CPX" 46 | 47 | MICROBIT = "micro:bit" 48 | 49 | CLUE = "CLUE" 50 | CLUE_DIR = "clue" 51 | 52 | CIRCUITPYTHON = "base_circuitpython" 53 | 54 | CODE_START_MSG_CLUE = "soft reboot\ncode.py output:" 55 | CODE_FINISHED_MSG_CLUE = "\nCode done running. Waiting for reload." 56 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | playsound==1.2.2 2 | applicationinsights==0.11.9 3 | python-socketio==4.3.1 4 | requests==2.22.0 5 | pywin32==227; platform_system == "Windows" 6 | PyObjC; platform_system == "darwin" 7 | uflash==1.3.0 8 | adafruit-circuitpython-fancyled==1.3.3 9 | Pillow==8.1.1 10 | adafruit-circuitpython-bitmap_font==1.1.0 11 | adafruit-circuitpython-display-shapes==1.2.0 12 | adafruit-circuitpython-neopixel==5.0.0 -------------------------------------------------------------------------------- /src/service/PopupService.ts: -------------------------------------------------------------------------------- 1 | // import { Webview } from "vscode"; 2 | import * as vscode from "vscode"; 3 | import { LATEST_RELEASE_NOTE } from "../latest_release_note"; 4 | 5 | export class PopupService { 6 | public static openReleaseNote() { 7 | const panel = vscode.window.createWebviewPanel( 8 | "releaseNote", 9 | "Release Note", 10 | vscode.ViewColumn.One, 11 | {} 12 | ); 13 | 14 | panel.webview.html = LATEST_RELEASE_NOTE; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/service/debuggerCommunicationService.ts: -------------------------------------------------------------------------------- 1 | import { DebuggerCommunicationServer } from "../debuggerCommunicationServer"; 2 | 3 | export class DebuggerCommunicationService { 4 | private currentDebuggerServer?: DebuggerCommunicationServer; 5 | private previousDebuggerServerToDisconnect?: DebuggerCommunicationServer; 6 | 7 | public setCurrentDebuggerServer(debugServer: DebuggerCommunicationServer) { 8 | this.currentDebuggerServer = debugServer; 9 | } 10 | // Used for restart and stop event 11 | public resetCurrentDebuggerServer() { 12 | if (this.currentDebuggerServer) { 13 | this.currentDebuggerServer.closeConnection(); 14 | } 15 | this.previousDebuggerServerToDisconnect = this.currentDebuggerServer; 16 | this.currentDebuggerServer = undefined; 17 | } 18 | public getCurrentDebuggerServer() { 19 | return this.currentDebuggerServer; 20 | } 21 | // Only used for stop event 22 | public handleStopEvent() { 23 | if (this.previousDebuggerServerToDisconnect) { 24 | this.previousDebuggerServerToDisconnect.disconnectFromPort(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/service/deviceSelectionService.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_DEVICE } from "../constants"; 2 | 3 | export class DeviceSelectionService { 4 | private currentActiveDevice: string = DEFAULT_DEVICE; 5 | 6 | public getCurrentActiveDevice(): string { 7 | return this.currentActiveDevice; 8 | } 9 | public setCurrentActiveDevice(newActiveDevice: string) { 10 | this.currentActiveDevice = newActiveDevice; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/service/messagingService.ts: -------------------------------------------------------------------------------- 1 | import { Webview } from "vscode"; 2 | 3 | import * as fs from "fs"; 4 | import { VSCODE_MESSAGES_TO_WEBVIEW } from "../view/constants"; 5 | import { DeviceSelectionService } from "./deviceSelectionService"; 6 | export class MessagingService { 7 | private currentWebviewTarget: Webview | undefined; 8 | private deviceSelectionService: DeviceSelectionService; 9 | 10 | constructor(deviceSelectionService: DeviceSelectionService) { 11 | this.deviceSelectionService = deviceSelectionService; 12 | } 13 | public setWebview(webview: Webview) { 14 | this.currentWebviewTarget = webview; 15 | } 16 | 17 | // Send a message to webview if it exists 18 | public sendMessageToWebview(command: string, stateToSend: Object) { 19 | if (this.currentWebviewTarget) { 20 | this.currentWebviewTarget.postMessage({ 21 | command, 22 | active_device: this.deviceSelectionService.getCurrentActiveDevice(), 23 | state: { ...stateToSend }, 24 | }); 25 | } 26 | } 27 | public sendStartMessage() { 28 | this.currentWebviewTarget.postMessage({ 29 | command: VSCODE_MESSAGES_TO_WEBVIEW.RUN_DEVICE, 30 | }); 31 | } 32 | public sendPauseMessage() { 33 | this.currentWebviewTarget.postMessage({ 34 | command: VSCODE_MESSAGES_TO_WEBVIEW.PAUSE_DEVICE, 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/simulatorDebugConfigurationProvider.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import * as vscode from "vscode"; 5 | import { CONSTANTS, DialogResponses } from "./constants"; 6 | import { getServerPortConfig } from "./extension_utils/utils"; 7 | 8 | export class SimulatorDebugConfigurationProvider 9 | implements vscode.DebugConfigurationProvider { 10 | public deviceSimulatorExpressDebug: boolean; 11 | 12 | constructor(private pathToScript: string) { 13 | this.deviceSimulatorExpressDebug = false; 14 | } 15 | 16 | /** 17 | * Modify the debug configuration just before a debug session is being launched. 18 | */ 19 | public resolveDebugConfiguration( 20 | folder: vscode.WorkspaceFolder | undefined, 21 | config: vscode.DebugConfiguration, 22 | token?: vscode.CancellationToken 23 | ): vscode.ProviderResult { 24 | const activeTextEditor = vscode.window.activeTextEditor; 25 | 26 | // Create a configuration if no launch.json exists or if it's empty 27 | if (!config.type && !config.request && !config.name) { 28 | if ( 29 | activeTextEditor && 30 | activeTextEditor.document.languageId === "python" 31 | ) { 32 | config.type = "deviceSimulatorExpress"; 33 | config.request = "launch"; 34 | config.name = "Device Simulator Express Debugger"; 35 | config.console = "integratedTerminal"; 36 | } 37 | } 38 | // Check config type 39 | if (config.type === CONSTANTS.DEBUG_CONFIGURATION_TYPE) { 40 | this.deviceSimulatorExpressDebug = true; 41 | if (activeTextEditor) { 42 | const currentFilePath = activeTextEditor.document.fileName; 43 | 44 | // Check file type and name 45 | if (!(activeTextEditor.document.languageId === "python")) { 46 | return vscode.window 47 | .showErrorMessage( 48 | CONSTANTS.ERROR.INVALID_FILE_EXTENSION_DEBUG 49 | ) 50 | .then(() => { 51 | return undefined; // Abort launch 52 | }); 53 | } 54 | // Set the new configuration type so the python debugger can take over 55 | config.type = "python"; 56 | // Set process_user_code path as program 57 | config.program = this.pathToScript; 58 | // Set user's code path and server's port as args 59 | config.args = [ 60 | currentFilePath, 61 | getServerPortConfig().toString(), 62 | ]; 63 | // Set rules 64 | config.rules = [ 65 | { path: this.pathToScript, include: false }, 66 | { 67 | module: "adafruit_circuitplayground", 68 | include: false, 69 | }, 70 | { module: "playsound", include: false }, 71 | ]; 72 | } 73 | } 74 | // Abort / show error message if can't find process_user_code.py 75 | if (!config.program) { 76 | return vscode.window 77 | .showErrorMessage(CONSTANTS.ERROR.NO_PROGRAM_FOUND_DEBUG) 78 | .then(() => { 79 | return undefined; // Abort launch 80 | }); 81 | } 82 | return config; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/telemetry/getPackageInfo.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as vscode from "vscode"; 4 | 5 | export interface IPackageJson { 6 | name?: string; 7 | version?: string; 8 | instrumentationKey: string; 9 | } 10 | 11 | const getPackagePath = (context: vscode.ExtensionContext) => { 12 | const onDiskPath = vscode.Uri.file( 13 | path.join(context.extensionPath, "package.json") 14 | ); 15 | const packagePath = onDiskPath.with({ scheme: "vscode-resource" }); 16 | 17 | return packagePath; 18 | }; 19 | 20 | export default function getPackageInfo( 21 | context: vscode.ExtensionContext 22 | ): { 23 | extensionName: string; 24 | extensionVersion: string; 25 | instrumentationKey: string; 26 | } { 27 | let packageJson: IPackageJson; 28 | const packagePath = getPackagePath(context); 29 | 30 | try { 31 | packageJson = JSON.parse(fs.readFileSync(packagePath.fsPath, "utf8")); 32 | } catch (error) { 33 | console.error(`Failed to read from package.json: ${error}`); 34 | throw new Error(`Failed to read from package.json: ${error}`); 35 | } 36 | 37 | const extensionName: string | undefined = packageJson.name; 38 | const extensionVersion: string | undefined = packageJson.version; 39 | const instrumentationKey: string | undefined = 40 | packageJson.instrumentationKey; 41 | 42 | if (!extensionName) { 43 | throw new Error( 44 | "Extension's package.json is missing instrumentation key." 45 | ); 46 | } 47 | 48 | if (!extensionVersion) { 49 | throw new Error("Extension's package.json is missing version."); 50 | } 51 | 52 | if (!extensionVersion) { 53 | throw new Error("Extension's package.json is missing version."); 54 | } 55 | 56 | return { extensionName, extensionVersion, instrumentationKey }; 57 | } 58 | -------------------------------------------------------------------------------- /src/telemetry/telemetryAI.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import TelemetryReporter from "vscode-extension-telemetry"; 3 | import getPackageInfo from "./getPackageInfo"; 4 | 5 | // tslint:disable-next-line:export-name 6 | export default class TelemetryAI { 7 | private static telemetryReporter: TelemetryReporter; 8 | private static enableTelemetry: boolean | undefined; 9 | 10 | constructor(vscodeContext: vscode.ExtensionContext) { 11 | TelemetryAI.telemetryReporter = this.createTelemetryReporter( 12 | vscodeContext 13 | ); 14 | TelemetryAI.enableTelemetry = vscode.workspace 15 | .getConfiguration() 16 | .get("telemetry.enableTelemetry"); 17 | if (TelemetryAI.enableTelemetry === undefined) { 18 | TelemetryAI.enableTelemetry = true; 19 | } 20 | } 21 | 22 | public getExtensionName(context: vscode.ExtensionContext): string { 23 | const { extensionName } = getPackageInfo(context); 24 | return extensionName; 25 | } 26 | 27 | public getExtensionVersionNumber(context: vscode.ExtensionContext): string { 28 | const { extensionVersion } = getPackageInfo(context); 29 | return extensionVersion; 30 | } 31 | 32 | public sendTelemetryIfEnabled( 33 | eventName: string, 34 | properties?: { [key: string]: string }, 35 | measurements?: { [key: string]: number } 36 | ) { 37 | if (TelemetryAI.enableTelemetry) { 38 | TelemetryAI.telemetryReporter.sendTelemetryEvent( 39 | eventName, 40 | properties, 41 | measurements 42 | ); 43 | } 44 | } 45 | 46 | public trackFeatureUsage( 47 | eventName: string, 48 | eventProperties?: { [key: string]: string } 49 | ) { 50 | this.sendTelemetryIfEnabled(eventName, eventProperties); 51 | } 52 | 53 | public runWithLatencyMeasure( 54 | functionToRun: () => void, 55 | eventName: string 56 | ): void { 57 | const numberOfNanosecondsInSecond: number = 1000000000; 58 | const startTime: number = Number(process.hrtime.bigint()); 59 | functionToRun(); 60 | const latency: number = Number(process.hrtime.bigint()) - startTime; 61 | const measurement = { 62 | duration: latency / numberOfNanosecondsInSecond, 63 | }; 64 | this.sendTelemetryIfEnabled(eventName, {}, measurement); 65 | } 66 | 67 | private createTelemetryReporter( 68 | context: vscode.ExtensionContext 69 | ): TelemetryReporter { 70 | const { 71 | extensionName, 72 | extensionVersion, 73 | instrumentationKey, 74 | } = getPackageInfo(context); 75 | const reporter: TelemetryReporter = new TelemetryReporter( 76 | extensionName, 77 | extensionVersion, 78 | instrumentationKey 79 | ); 80 | context.subscriptions.push(reporter); 81 | return reporter; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/templates/clue_template.py: -------------------------------------------------------------------------------- 1 | """ 2 | To get started, check out the "Device Simulator Express: Getting Started" command in the command pallete, which you can access with `CMD + SHIFT + P` For Mac and `CTRL + SHIFT + P` for Windows and Linux. 3 | 4 | To learn more about the CLUE and CircuitPython, check this link out: 5 | https://learn.adafruit.com/adafruit-clue/circuitpython 6 | 7 | Find example code for CPX on: 8 | https://blog.adafruit.com/2020/02/12/three-fun-sensor-packed-projects-to-try-on-your-clue-adafruitlearningsystem-adafruit-circuitpython-adafruit/ 9 | """ 10 | 11 | from adafruit_clue import clue 12 | 13 | clue_data = clue.simple_text_display(title="Hello World", title_scale=3) 14 | 15 | while True: 16 | clue_data.show() 17 | -------------------------------------------------------------------------------- /src/templates/cpx_template.py: -------------------------------------------------------------------------------- 1 | """ 2 | To get started, check out the "Device Simulator Express: Getting Started" command in the command pallete, which you can access with `CMD + SHIFT + P` For Mac and `CTRL + SHIFT + P` for Windows and Linux. 3 | 4 | Getting started with CPX and CircuitPython intro on: 5 | https://learn.adafruit.com/circuitpython-made-easy-on-circuit-playground-express/circuit-playground-express-library 6 | 7 | Find example code for CPX on: 8 | https://github.com/adafruit/Adafruit_CircuitPython_CircuitPlayground/tree/master/examples 9 | """ 10 | 11 | # import CPX library 12 | from adafruit_circuitplayground import cp 13 | 14 | while True: 15 | # start your code here 16 | pass 17 | -------------------------------------------------------------------------------- /src/templates/microbit_template.py: -------------------------------------------------------------------------------- 1 | """ 2 | To get started, check out the "Device Simulator Express: Getting Started" command in the command pallete, which you can access with `CMD + SHIFT + P` For Mac and `CTRL + SHIFT + P` for Windows and Linux. 3 | 4 | Get started with micro:bit and MicroPython on: 5 | https://microbit-micropython.readthedocs.io/en/latest/. 6 | """ 7 | 8 | from microbit import * 9 | 10 | while True: 11 | display.scroll("Hello World!") 12 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import { runTests } from "vscode-test"; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, "../../"); 10 | 11 | // The path to the extension test script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, "./suite/index"); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error("Failed to run tests"); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testsRoot: string, clb: (error: Error, failures?: number) => void): void 9 | // that the extension host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | import * as glob from "glob"; 14 | import * as Mocha from "mocha"; 15 | import * as path from "path"; 16 | 17 | // tslint:disable-next-line: export-name 18 | export function run(): Promise { 19 | // Create the mocha test 20 | const mocha = new Mocha({ 21 | ui: "tdd", 22 | }); 23 | mocha.useColors(true); 24 | 25 | const testsRoot = path.resolve(__dirname, ".."); 26 | 27 | return new Promise((c, e) => { 28 | glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { 29 | if (err) { 30 | return e(err); 31 | } 32 | 33 | // Add files to the test suite 34 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 35 | 36 | try { 37 | // Run the mocha test 38 | mocha.run(failures => { 39 | if (failures > 0) { 40 | e(new Error(`${failures} tests failed.`)); 41 | } else { 42 | c(); 43 | } 44 | }); 45 | } catch (err) { 46 | e(err); 47 | } 48 | }); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/view/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | display: flex; 4 | justify-content: space-between; 5 | } 6 | 7 | .App-main { 8 | background-color: var(--vscode-editor-background); 9 | padding: 0px 0.75em 0px 0.75em; 10 | min-height: 100vh; 11 | width: 100%; 12 | margin-top: 24px; 13 | margin-bottom: 53px; 14 | max-height: 400px; 15 | overflow: scroll; 16 | } 17 | -------------------------------------------------------------------------------- /src/view/App.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { IntlProvider } from "react-intl"; 4 | import * as testRenderer from "react-test-renderer"; 5 | import App from "./App"; 6 | 7 | describe("App component ", () => { 8 | it("should render correctly", () => { 9 | const component = testRenderer 10 | .create( 11 | 12 | 13 | 14 | ) 15 | .toJSON(); 16 | expect(component).toMatchSnapshot(); 17 | }); 18 | it("should render without crashing", () => { 19 | const div = document.createElement("div"); 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | div 25 | ); 26 | ReactDOM.unmountComponentAtNode(div); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/view/App.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import * as React from "react"; 5 | import "./App.css"; 6 | import { 7 | DEVICE_LIST_KEY, 8 | VIEW_STATE, 9 | VSCODE_MESSAGES_TO_WEBVIEW, 10 | WEBVIEW_ATTRIBUTES_KEY, 11 | WEBVIEW_TYPES, 12 | } from "./constants"; 13 | import { Device } from "./container/device/Device"; 14 | import { ViewStateContext } from "./context"; 15 | import { GettingStartedPage } from "./pages/gettingStarted"; 16 | 17 | interface IState { 18 | currentDevice: string; 19 | viewState: VIEW_STATE; 20 | type?: WEBVIEW_TYPES; 21 | } 22 | 23 | const defaultState = { 24 | currentDevice: DEVICE_LIST_KEY.CPX, 25 | viewState: VIEW_STATE.RUNNING, 26 | type: undefined, 27 | }; 28 | 29 | class App extends React.Component<{}, IState> { 30 | constructor() { 31 | super({}); 32 | this.state = defaultState; 33 | } 34 | componentDidMount() { 35 | if (document.currentScript) { 36 | const webviewTypeAttribute = document.currentScript.getAttribute( 37 | WEBVIEW_ATTRIBUTES_KEY.TYPE 38 | ) as WEBVIEW_TYPES; 39 | if (webviewTypeAttribute) { 40 | this.setState({ type: webviewTypeAttribute }); 41 | } 42 | if (webviewTypeAttribute === WEBVIEW_TYPES.SIMULATOR) { 43 | const initialDevice = document.currentScript.getAttribute( 44 | WEBVIEW_ATTRIBUTES_KEY.INITIAL_DEVICE 45 | ); 46 | 47 | if (initialDevice) { 48 | this.setState({ currentDevice: initialDevice }); 49 | window.addEventListener("message", this.handleMessage); 50 | } 51 | } 52 | } 53 | } 54 | componentWillUnmount() { 55 | window.removeEventListener("message", this.handleMessage); 56 | } 57 | 58 | render() { 59 | return ( 60 |
61 |
62 | 63 | {this.loadContent()} 64 | 65 |
66 |
67 | ); 68 | } 69 | loadContent = () => { 70 | console.log(this.state.type); 71 | switch (this.state.type) { 72 | case WEBVIEW_TYPES.GETTING_STARTED: 73 | return ; 74 | case WEBVIEW_TYPES.SIMULATOR: 75 | return ( 76 | 77 | ); 78 | } 79 | return; 80 | }; 81 | 82 | handleMessage = (event: any): void => { 83 | const message = event.data; 84 | 85 | switch (message.command) { 86 | case VSCODE_MESSAGES_TO_WEBVIEW.SET_DEVICE: 87 | if (message.active_device !== this.state.currentDevice) { 88 | this.setState({ currentDevice: message.active_device }); 89 | } 90 | break; 91 | case VSCODE_MESSAGES_TO_WEBVIEW.RUN_DEVICE: 92 | this.setState({ viewState: VIEW_STATE.RUNNING }); 93 | break; 94 | case VSCODE_MESSAGES_TO_WEBVIEW.PAUSE_DEVICE: 95 | this.setState({ viewState: VIEW_STATE.PAUSE }); 96 | break; 97 | } 98 | }; 99 | } 100 | 101 | export default App; 102 | -------------------------------------------------------------------------------- /src/view/__snapshots__/App.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`App component should render correctly 1`] = ` 4 |
7 |
10 |
11 | `; 12 | -------------------------------------------------------------------------------- /src/view/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "../styles/Button.css"; 3 | 4 | export interface IButtonProps { 5 | label: string; 6 | image: any; 7 | focusable: boolean; 8 | styleLabel: string; 9 | width: number; 10 | onClick: (event: React.MouseEvent) => void; 11 | } 12 | 13 | // Functional Component render 14 | const Button: React.FC = props => { 15 | const iconSvg: SVGElement = props.image as SVGElement; 16 | const buttonStyle = { width: props.width }; 17 | const tabIndex = props.focusable ? 0 : -1; 18 | 19 | return ( 20 | 31 | ); 32 | }; 33 | 34 | export default Button; 35 | -------------------------------------------------------------------------------- /src/view/components/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import * as React from "react"; 5 | import "../styles/Dropdown.css"; 6 | 7 | export interface IDropdownProps { 8 | options: string[]; 9 | // styleLabel: string; 10 | name: string; 11 | onSelect?: (event: React.ChangeEvent) => void; 12 | } 13 | 14 | export const Dropdown: React.FC = props => { 15 | return ( 16 | 24 | ); 25 | }; 26 | 27 | const renderOptions = (options: string[]) => { 28 | return options.map((name, index) => { 29 | return ( 30 | 33 | ); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /src/view/components/clue/Clue.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { IntlProvider } from "react-intl"; 4 | import * as testRenderer from "react-test-renderer"; 5 | import { Clue } from "./Clue"; 6 | 7 | describe("Clue component", () => { 8 | it("should render correctly", () => { 9 | const component = testRenderer 10 | .create( 11 | 12 | 13 | 14 | ) 15 | .toJSON(); 16 | expect(component).toMatchSnapshot(); 17 | }); 18 | 19 | it("should render without crashing", () => { 20 | const div = document.createElement("div"); 21 | ReactDOM.render( 22 | 23 | 24 | , 25 | div 26 | ); 27 | ReactDOM.unmountComponentAtNode(div); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/view/components/cpx/Accessibility_utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | // Helpers designed to help to make a simulator accessible. 5 | namespace accessibility { 6 | export function makeFocusable(elem: SVGElement): void { 7 | elem.setAttribute("focusable", "true"); 8 | elem.setAttribute("tabindex", "0"); 9 | } 10 | 11 | export function setAria( 12 | elem: Element, 13 | role?: string, 14 | label?: string 15 | ): void { 16 | if (role && !elem.hasAttribute("role")) { 17 | elem.setAttribute("role", role); 18 | } 19 | 20 | if (label && !elem.hasAttribute("aria-label")) { 21 | elem.setAttribute("aria-label", label); 22 | } 23 | } 24 | } 25 | 26 | export default accessibility; 27 | -------------------------------------------------------------------------------- /src/view/components/cpx/Cpx.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { IntlProvider } from "react-intl"; 4 | import * as testRenderer from "react-test-renderer"; 5 | import { Cpx } from "./Cpx"; 6 | 7 | describe("CPX component", () => { 8 | it("should render correctly", () => { 9 | const component = testRenderer 10 | .create( 11 | 12 | 13 | 14 | ) 15 | .toJSON(); 16 | expect(component).toMatchSnapshot(); 17 | }); 18 | 19 | it("should render without crashing", () => { 20 | const div = document.createElement("div"); 21 | ReactDOM.render( 22 | 23 | 24 | , 25 | div 26 | ); 27 | ReactDOM.unmountComponentAtNode(div); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/view/components/cpx/Cpx.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import * as React from "react"; 5 | import { CPX_TOOLBAR_ICON_ID } from "../../components/toolbar/SensorModalUtils"; 6 | import ToolBar from "../../components/toolbar/ToolBar"; 7 | import { SENSOR_LIST, VSCODE_MESSAGES_TO_WEBVIEW } from "../../constants"; 8 | import * as TOOLBAR_SVG from "../../svgs/toolbar_svg"; 9 | import Simulator from "./CpxSimulator"; 10 | 11 | // Component grouping the functionality for circuit playground express 12 | const DEFAULT_STATE = { 13 | sensors: { 14 | [SENSOR_LIST.TEMPERATURE]: 0, 15 | [SENSOR_LIST.LIGHT]: 0, 16 | [SENSOR_LIST.MOTION_X]: 0, 17 | [SENSOR_LIST.MOTION_Y]: 0, 18 | [SENSOR_LIST.MOTION_Z]: 0, 19 | }, 20 | }; 21 | 22 | export class Cpx extends React.Component { 23 | state = DEFAULT_STATE; 24 | componentDidMount() { 25 | window.addEventListener("message", this.handleMessage); 26 | } 27 | 28 | componentWillUnmount() { 29 | // Make sure to remove the DOM listener when the component is unmounted. 30 | window.removeEventListener("message", this.handleMessage); 31 | } 32 | handleMessage = (event: any): void => { 33 | const message = event.data; 34 | 35 | switch (message.command) { 36 | case VSCODE_MESSAGES_TO_WEBVIEW.RESET: 37 | this.setState({ ...DEFAULT_STATE }); 38 | break; 39 | } 40 | }; 41 | 42 | render() { 43 | return ( 44 | 45 | 46 | 51 | 52 | ); 53 | } 54 | updateSensor = (sensor: SENSOR_LIST, value: number) => { 55 | this.setState({ sensors: { ...this.state.sensors, [sensor]: value } }); 56 | }; 57 | } 58 | 59 | const CPX_TOOLBAR_BUTTONS: Array<{ label: any; image: any }> = [ 60 | { 61 | image: TOOLBAR_SVG.SLIDER_SWITCH_SVG, 62 | label: CPX_TOOLBAR_ICON_ID.SWITCH, 63 | }, 64 | { 65 | image: TOOLBAR_SVG.PUSH_BUTTON_SVG, 66 | label: CPX_TOOLBAR_ICON_ID.PUSH_BUTTON, 67 | }, 68 | { 69 | image: TOOLBAR_SVG.RED_LED_SVG, 70 | label: CPX_TOOLBAR_ICON_ID.RED_LED, 71 | }, 72 | { 73 | image: TOOLBAR_SVG.SOUND_SVG, 74 | label: CPX_TOOLBAR_ICON_ID.SOUND, 75 | }, 76 | { 77 | image: TOOLBAR_SVG.TEMPERATURE_SVG, 78 | label: CPX_TOOLBAR_ICON_ID.TEMPERATURE, 79 | }, 80 | { 81 | image: TOOLBAR_SVG.LIGHT_SVG, 82 | label: CPX_TOOLBAR_ICON_ID.LIGHT, 83 | }, 84 | { 85 | image: TOOLBAR_SVG.NEO_PIXEL_SVG, 86 | label: CPX_TOOLBAR_ICON_ID.NEO_PIXEL, 87 | }, 88 | { 89 | image: TOOLBAR_SVG.SPEAKER_SVG, 90 | label: CPX_TOOLBAR_ICON_ID.SPEAKER, 91 | }, 92 | { 93 | image: TOOLBAR_SVG.MOTION_SVG, 94 | label: CPX_TOOLBAR_ICON_ID.MOTION, 95 | }, 96 | { 97 | image: TOOLBAR_SVG.IR_SVG, 98 | label: CPX_TOOLBAR_ICON_ID.IR, 99 | }, 100 | { 101 | image: TOOLBAR_SVG.GPIO_SVG, 102 | label: CPX_TOOLBAR_ICON_ID.GPIO, 103 | }, 104 | ]; 105 | -------------------------------------------------------------------------------- /src/view/components/cpx/Svg_utils.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | // Adapted from : https://github.com/microsoft/pxt/blob/master/pxtsim/svg.ts 5 | 6 | // tslint:disable-next-line: no-namespace 7 | namespace svg { 8 | export function addClass(el: SVGElement, cls: string) { 9 | if (el.classList) { 10 | el.classList.add(cls); 11 | } else if (el.className.baseVal.indexOf(cls) < 0) { 12 | el.className.baseVal += " " + cls; 13 | } 14 | } 15 | 16 | export function removeClass(el: SVGElement, cls: string) { 17 | if (el.classList) { 18 | el.classList.remove(cls); 19 | } else { 20 | el.className.baseVal = el.className.baseVal 21 | .replace(cls, "") 22 | .replace(/\s{2,}/, " "); 23 | } 24 | } 25 | 26 | export function hydrate(el: SVGElement, props: any) { 27 | for (const k in props) { 28 | if (k == "title") { 29 | svg.title(el, props[k]); 30 | } else { 31 | el.setAttributeNS(null, k, props[k]); 32 | } 33 | } 34 | } 35 | 36 | export function createElement(name: string, props?: any): SVGElement { 37 | const newElement = document.createElementNS( 38 | "http://www.w3.org/2000/svg", 39 | name 40 | ); 41 | if (props) { 42 | svg.hydrate(newElement, props); 43 | } 44 | return newElement; 45 | } 46 | 47 | export function child( 48 | parent: Element, 49 | name: string, 50 | props?: any 51 | ): SVGElement { 52 | const childElement = svg.createElement(name, props); 53 | parent.appendChild(childElement); 54 | return childElement; 55 | } 56 | 57 | export function fill(el: SVGElement, c: string) { 58 | el.style.fill = c; 59 | } 60 | 61 | export function filter(el: SVGElement, c: string) { 62 | el.style.filter = c; 63 | } 64 | 65 | export function fills(els: SVGElement[], c: string) { 66 | els.forEach(el => (el.style.fill = c)); 67 | } 68 | 69 | export function mkTitle(txt: string): SVGTitleElement { 70 | const t = svg.createElement("title") as SVGTitleElement; 71 | t.textContent = txt; 72 | return t; 73 | } 74 | 75 | export function title(el: SVGElement, txt: string): SVGTitleElement { 76 | const t = mkTitle(txt); 77 | el.appendChild(t); 78 | return t; 79 | } 80 | 81 | export function setLed( 82 | ledStatus: boolean, 83 | offColor: string, 84 | onColor: string, 85 | ledElement: SVGElement | null, 86 | gradientStopElement: SVGStopElement | null 87 | ) { 88 | if (ledStatus) { 89 | ledElement?.setAttribute("fill", onColor); 90 | gradientStopElement?.setAttribute("stop-opacity", "1"); 91 | } else { 92 | ledElement?.setAttribute("fill", offColor); 93 | gradientStopElement?.setAttribute("stop-opacity", "0"); 94 | } 95 | } 96 | } 97 | 98 | export default svg; 99 | -------------------------------------------------------------------------------- /src/view/components/microbit/Microbit.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { IntlProvider } from "react-intl"; 4 | import * as testRenderer from "react-test-renderer"; 5 | import { Microbit } from "./Microbit"; 6 | 7 | describe("Microbit component ", () => { 8 | it("should render correctly", () => { 9 | const component = testRenderer 10 | .create( 11 | 12 | 13 | 14 | ) 15 | .toJSON(); 16 | expect(component).toMatchSnapshot(); 17 | }); 18 | 19 | it("should render without crashing", () => { 20 | const div = document.createElement("div"); 21 | ReactDOM.render( 22 | 23 | 24 | , 25 | div 26 | ); 27 | ReactDOM.unmountComponentAtNode(div); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/view/components/simulator/ActionBar.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import * as React from "react"; 5 | import { CONSTANTS } from "../../constants"; 6 | import RefreshLogo from "../../svgs/refresh_svg"; 7 | import Button from "../Button"; 8 | 9 | interface IProps { 10 | onTogglePlay: (event: React.MouseEvent) => void; 11 | onToggleRefresh: (event: React.MouseEvent) => void; 12 | playStopImage: JSX.Element; 13 | playStopLabel: string; 14 | } 15 | 16 | // Component including the actions done on the Simulator (play/stop, refresh) 17 | 18 | class ActionBar extends React.Component { 19 | public render() { 20 | const { 21 | onTogglePlay, 22 | onToggleRefresh, 23 | playStopImage, 24 | playStopLabel, 25 | } = this.props; 26 | return ( 27 |
28 |
45 | ); 46 | } 47 | } 48 | export default ActionBar; 49 | -------------------------------------------------------------------------------- /src/view/components/toolbar/GenericSliderComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SENSOR_LIST } from "../../constants"; 3 | import { ISensorProps, ISliderProps } from "../../viewUtils"; 4 | import InputSlider from "./InputSlider"; 5 | 6 | interface IProps { 7 | axisProperties: ISensorProps; 8 | axisValues: { 9 | // key is accessed with axisLabel 10 | [key: string]: number; 11 | }; 12 | onUpdateValue: (sensor: SENSOR_LIST, value: number) => void; 13 | } 14 | export const GenericSliderComponent: React.FC = props => { 15 | return ( 16 |
17 | {props.axisProperties.sliderProps.map( 18 | (sliderProperties: ISliderProps, index: number) => { 19 | return ( 20 | 21 | 34 |
35 |
36 | ); 37 | } 38 | )} 39 |
40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/view/components/toolbar/Gesture.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { CONSTANTS } from "../../constants"; 3 | import { Dropdown } from "../Dropdown"; 4 | import SensorButton from "./SensorButton"; 5 | 6 | const GESTURE_BUTTON_MESSAGE = "Send Gesture"; 7 | interface IProps { 8 | gestures: string[]; 9 | onSelectGestures?: (event: React.ChangeEvent) => void; 10 | onSendGesture?: (isActive: boolean) => void; 11 | } 12 | export class Gesture extends React.Component { 13 | private sensorButtonRef: React.RefObject = React.createRef(); 14 | render() { 15 | return ( 16 |
25 | 30 | { 34 | if (this.props.onSendGesture) { 35 | this.props.onSendGesture(true); 36 | } 37 | }} 38 | onMouseUp={() => { 39 | if (this.props.onSendGesture) { 40 | this.props.onSendGesture(false); 41 | } 42 | }} 43 | onKeyDown={this.handleOnKeyDown} 44 | onKeyUp={this.handleOnKeyUp} 45 | type="gesture" 46 | /> 47 |
48 | ); 49 | } 50 | private handleOnKeyDown = (e: React.KeyboardEvent) => { 51 | if (e.key === CONSTANTS.KEYBOARD_KEYS.ENTER) { 52 | this.sensorButtonRef!.current!.setButtonClass(true); 53 | if (this.props.onSendGesture) { 54 | this.props.onSendGesture(true); 55 | } 56 | } 57 | }; 58 | 59 | private handleOnKeyUp = ( 60 | e: React.KeyboardEvent, 61 | onSendGesture?: (isActive: boolean) => void 62 | ) => { 63 | if (e.key === CONSTANTS.KEYBOARD_KEYS.ENTER) { 64 | this.sensorButtonRef!.current!.setButtonClass(false); 65 | 66 | if (this.props.onSendGesture) { 67 | this.props.onSendGesture(false); 68 | } 69 | } 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /src/view/components/toolbar/SensorButton.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import * as React from "react"; 5 | import { VIEW_STATE } from "../../constants"; 6 | import { ViewStateContext } from "../../context"; 7 | import "../../styles/SensorButton.css"; 8 | import { ISensorButtonProps } from "../../viewUtils"; 9 | 10 | class SensorButton extends React.Component { 11 | private buttonRef: React.RefObject = React.createRef(); 12 | 13 | public setButtonClass = (isActive: boolean) => { 14 | const isInputDisabled = this.context === VIEW_STATE.PAUSE; 15 | 16 | if (isActive && !isInputDisabled) { 17 | this.buttonRef.current?.setAttribute( 18 | "class", 19 | "sensor-button active-button" 20 | ); 21 | } else if (this.buttonRef.current) { 22 | this.buttonRef!.current!.setAttribute("class", "sensor-button"); 23 | } 24 | }; 25 | render() { 26 | const isInputDisabled = this.context === VIEW_STATE.PAUSE; 27 | 28 | return ( 29 | 42 | ); 43 | } 44 | } 45 | SensorButton.contextType = ViewStateContext; 46 | export default SensorButton; 47 | -------------------------------------------------------------------------------- /src/view/components/toolbar/Toolbar.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { IntlProvider } from "react-intl"; 4 | import * as testRenderer from "react-test-renderer"; 5 | import { SENSOR_LIST } from "../../constants"; 6 | import * as TOOLBAR_SVG from "../../svgs/toolbar_svg"; 7 | import { MICROBIT_TOOLBAR_ICON_ID } from "./SensorModalUtils"; 8 | import Toolbar from "./ToolBar"; 9 | 10 | const MOCK_TOOLBAR_BUTTONS: Array<{ label: string; image: JSX.Element }> = [ 11 | { 12 | image: TOOLBAR_SVG.LIGHT_SVG, 13 | label: MICROBIT_TOOLBAR_ICON_ID.LIGHT, 14 | }, 15 | { 16 | image: TOOLBAR_SVG.MOTION_SVG, 17 | label: MICROBIT_TOOLBAR_ICON_ID.ACCELEROMETER, 18 | }, 19 | ]; 20 | const mockUpdateSensors = () => { 21 | return; 22 | }; 23 | const mockInitialValues = { 24 | [SENSOR_LIST.TEMPERATURE]: 0, 25 | [SENSOR_LIST.LIGHT]: 0, 26 | }; 27 | describe("Toolbar component ", () => { 28 | it("should render correctly", () => { 29 | const component = testRenderer 30 | .create( 31 | 32 | 37 | 38 | ) 39 | .toJSON(); 40 | expect(component).toMatchSnapshot(); 41 | }); 42 | 43 | it("should render without crashing", () => { 44 | const div = document.createElement("div"); 45 | ReactDOM.render( 46 | 47 | 52 | , 53 | div 54 | ); 55 | ReactDOM.unmountComponentAtNode(div); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/view/components/toolbar/cpx/CpxSensorProperties.tsx: -------------------------------------------------------------------------------- 1 | import { SENSOR_LIST } from "../../../constants"; 2 | import { ISensorProps, ISliderProps } from "../../../viewUtils"; 3 | 4 | const LIGHT_SLIDER_PROPS: ISliderProps = { 5 | maxValue: 320, 6 | minValue: 0, 7 | minLabel: "Dark", 8 | maxLabel: "Bright", 9 | type: "light", 10 | axisLabel: "L", 11 | step: 1, 12 | }; 13 | 14 | export const LIGHT_SENSOR_PROPERTIES: ISensorProps = { 15 | LABEL: "Light sensor", 16 | sliderProps: [LIGHT_SLIDER_PROPS], 17 | unitLabel: "Lux", 18 | }; 19 | 20 | const TEMPERATURE_SLIDER_PROPS: ISliderProps = { 21 | axisLabel: "T", 22 | maxLabel: "Hot", 23 | maxValue: 125, 24 | minLabel: "Cold", 25 | minValue: -55, 26 | type: SENSOR_LIST.TEMPERATURE, 27 | step: 0.1, 28 | }; 29 | export const TEMPERATURE_SENSOR_PROPERTIES: ISensorProps = { 30 | LABEL: "Temperature sensor", 31 | sliderProps: [TEMPERATURE_SLIDER_PROPS], 32 | unitLabel: "°C", 33 | }; 34 | 35 | const MOTION_SLIDER_PROPS_X: ISliderProps = { 36 | axisLabel: "X", 37 | maxLabel: "Right", 38 | maxValue: 78, 39 | minLabel: "Left", 40 | minValue: -78, 41 | type: SENSOR_LIST.MOTION_X, 42 | step: 1, 43 | }; 44 | const MOTION_SLIDER_PROPS_Y: ISliderProps = { 45 | axisLabel: "Y", 46 | maxLabel: "Front", 47 | maxValue: 78, 48 | minLabel: "Back", 49 | minValue: -78, 50 | type: SENSOR_LIST.MOTION_Y, 51 | step: 1, 52 | }; 53 | const MOTION_SLIDER_PROPS_Z: ISliderProps = { 54 | axisLabel: "Z", 55 | maxLabel: "Down", 56 | maxValue: 78, 57 | minLabel: "Up", 58 | minValue: -78, 59 | type: SENSOR_LIST.MOTION_Z, 60 | step: 1, 61 | }; 62 | 63 | export const MOTION_SENSOR_PROPERTIES: ISensorProps = { 64 | LABEL: "Motion sensor", 65 | sliderProps: [ 66 | MOTION_SLIDER_PROPS_X, 67 | MOTION_SLIDER_PROPS_Y, 68 | MOTION_SLIDER_PROPS_Z, 69 | ], 70 | unitLabel: "Lux", 71 | }; 72 | -------------------------------------------------------------------------------- /src/view/components/toolbar/microbit/MicrobitSensorProperties.tsx: -------------------------------------------------------------------------------- 1 | import { SENSOR_LIST } from "../../../constants"; 2 | import { ISensorProps, ISliderProps } from "../../../viewUtils"; 3 | 4 | const LIGHT_SLIDER_PROPS: ISliderProps = { 5 | maxValue: 255, 6 | minValue: 0, 7 | minLabel: "Dark", 8 | maxLabel: "Bright", 9 | type: "light", 10 | axisLabel: "L", 11 | step: 1, 12 | }; 13 | 14 | export const LIGHT_SENSOR_PROPERTIES: ISensorProps = { 15 | LABEL: "Light sensor", 16 | sliderProps: [LIGHT_SLIDER_PROPS], 17 | unitLabel: "Lux", 18 | }; 19 | 20 | const MOTION_SLIDER_PROPS_X: ISliderProps = { 21 | axisLabel: "X", 22 | maxLabel: "Right", 23 | maxValue: 1023, 24 | minLabel: "Left", 25 | minValue: -1023, 26 | type: SENSOR_LIST.MOTION_X, 27 | step: 1, 28 | }; 29 | 30 | const MOTION_SLIDER_PROPS_Y: ISliderProps = { 31 | axisLabel: "Y", 32 | maxLabel: "Front", 33 | maxValue: 1023, 34 | minLabel: "Back", 35 | minValue: -1023, 36 | type: SENSOR_LIST.MOTION_Y, 37 | step: 1, 38 | }; 39 | 40 | const MOTION_SLIDER_PROPS_Z: ISliderProps = { 41 | axisLabel: "Z", 42 | maxLabel: "Down", 43 | maxValue: 1023, 44 | minLabel: "Up", 45 | minValue: -1023, 46 | type: SENSOR_LIST.MOTION_Z, 47 | step: 1, 48 | }; 49 | 50 | export const MOTION_SENSOR_PROPERTIES: ISensorProps = { 51 | LABEL: "Motion sensor", 52 | sliderProps: [ 53 | MOTION_SLIDER_PROPS_X, 54 | MOTION_SLIDER_PROPS_Y, 55 | MOTION_SLIDER_PROPS_Z, 56 | ], 57 | unitLabel: "m/s2", 58 | }; 59 | 60 | const TEMPERATURE_SLIDER_PROPS: ISliderProps = { 61 | axisLabel: "T", 62 | maxLabel: "Hot", 63 | maxValue: 125, 64 | minLabel: "Cold", 65 | minValue: -55, 66 | type: SENSOR_LIST.TEMPERATURE, 67 | step: 1, 68 | }; 69 | 70 | export const TEMPERATURE_SENSOR_PROPERTIES: ISensorProps = { 71 | LABEL: "Temperature sensor", 72 | sliderProps: [TEMPERATURE_SLIDER_PROPS], 73 | unitLabel: "°C", 74 | }; 75 | -------------------------------------------------------------------------------- /src/view/container/device/Device.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { IntlProvider } from "react-intl"; 4 | import * as testRenderer from "react-test-renderer"; 5 | import { DEVICE_LIST_KEY } from "../../constants"; 6 | import { Device } from "./Device"; 7 | 8 | describe("Device component ", () => { 9 | it("should render correctly", () => { 10 | const component = testRenderer 11 | .create( 12 | 13 | 14 | 15 | ) 16 | .toJSON(); 17 | expect(component).toMatchSnapshot(); 18 | }); 19 | 20 | it("should render without crashing", () => { 21 | const div = document.createElement("div"); 22 | ReactDOM.render( 23 | 24 | 25 | , 26 | div 27 | ); 28 | ReactDOM.unmountComponentAtNode(div); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/view/container/device/Device.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import * as React from "react"; 5 | import { Clue } from "../../components/clue/Clue"; 6 | import { Cpx } from "../../components/cpx/Cpx"; 7 | import { Microbit } from "../../components/microbit/Microbit"; 8 | import { DEVICE_LIST_KEY } from "../../constants"; 9 | 10 | interface IProps { 11 | currentSelectedDevice: string; 12 | } 13 | // Container to switch between multiple devices 14 | 15 | export class Device extends React.Component { 16 | constructor(props: IProps) { 17 | super(props); 18 | } 19 | render() { 20 | const { currentSelectedDevice } = this.props; 21 | 22 | return ( 23 |
24 | 25 | {loadSelectedDevice(currentSelectedDevice)} 26 | 27 |
28 | ); 29 | } 30 | } 31 | const loadSelectedDevice = (currentSelectedDevice: string) => { 32 | switch (currentSelectedDevice) { 33 | case DEVICE_LIST_KEY.CPX: 34 | return ; 35 | case DEVICE_LIST_KEY.MICROBIT: 36 | return ; 37 | case DEVICE_LIST_KEY.CLUE: 38 | return ; 39 | default: 40 | return null; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/view/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { VIEW_STATE } from "./constants"; 3 | 4 | // View is running by default 5 | 6 | export const ViewStateContext = React.createContext(VIEW_STATE.RUNNING); 7 | -------------------------------------------------------------------------------- /src/view/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Joti+One"); 2 | 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | font-family: sans-serif; 7 | } 8 | 9 | html, 10 | body { 11 | overflow: hidden; 12 | height: 100%; 13 | } 14 | -------------------------------------------------------------------------------- /src/view/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import * as React from "react"; 5 | import * as ReactDOM from "react-dom"; 6 | import { IntlProvider } from "react-intl"; 7 | import App from "./App"; 8 | 9 | import "./index.css"; 10 | 11 | const messageEn = require("./translations/en.json"); 12 | const locale = "en"; 13 | 14 | const message = { 15 | en: messageEn, 16 | }; 17 | ReactDOM.render( 18 | 19 | 20 | , 21 | document.getElementById("root") 22 | ); 23 | -------------------------------------------------------------------------------- /src/view/pages/gettingStarted.css: -------------------------------------------------------------------------------- 1 | .inv { 2 | display: none; 3 | } 4 | 5 | .codeText { 6 | overflow-x: auto; 7 | white-space: pre-wrap; 8 | word-wrap: break-word; 9 | } 10 | 11 | li:not(:last-child) { 12 | margin-bottom: 6px; 13 | } 14 | 15 | .normalFontWeight { 16 | font-weight: normal; 17 | } 18 | 19 | .deviceSelector { 20 | width: 250px; 21 | border: 1px solid var(--vscode-dropdown-border); 22 | background-color: var(--vscode-dropdown-background); 23 | color: var(--vscode-dropdown-foreground); 24 | margin: 0 0 5px; 25 | padding: 8px; 26 | border-radius: 5px; 27 | font-size: 12px; 28 | padding-right: 30px; 29 | } 30 | 31 | .deviceSelector optgroup { 32 | background-color: var(--vscode-dropdown-listBackground); 33 | color: var(--vscode-dropdown-foreground); 34 | } 35 | 36 | .deviceSelector option { 37 | background-color: var(--vscode-dropdown-listBackground); 38 | color: var(--vscode-dropdown-foreground); 39 | } 40 | 41 | .codeBox { 42 | display: block; 43 | width: 90%; 44 | margin: 10px; 45 | padding: 15px; 46 | text-align: left; 47 | background: none; 48 | border: 1px solid grey; 49 | border-radius: 4px; 50 | background-color: var(--vscode-textCodeBlock-background); 51 | } 52 | 53 | .container { 54 | text-align: left; 55 | padding: 0 10px 20px 10px; 56 | } 57 | -------------------------------------------------------------------------------- /src/view/pages/gettingStarted.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { IntlProvider } from "react-intl"; 4 | import * as testRenderer from "react-test-renderer"; 5 | import { GettingStartedPage } from "./gettingStarted"; 6 | 7 | describe("GettingStartedPage component ", () => { 8 | it("should render correctly", () => { 9 | const component = testRenderer 10 | .create( 11 | 12 | 13 | 14 | ) 15 | .toJSON(); 16 | expect(component).toMatchSnapshot(); 17 | }); 18 | 19 | it("should render without crashing", () => { 20 | const div = document.createElement("div"); 21 | ReactDOM.render( 22 | 23 | 24 | , 25 | div 26 | ); 27 | ReactDOM.unmountComponentAtNode(div); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/view/pages/gettingStartedPictures/debugger/debugger_vars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/view/pages/gettingStartedPictures/debugger/debugger_vars.png -------------------------------------------------------------------------------- /src/view/pages/gettingStartedPictures/debugger/debugging.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/view/pages/gettingStartedPictures/debugger/debugging.gif -------------------------------------------------------------------------------- /src/view/pages/gettingStartedPictures/debugger/start_debugging.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/view/pages/gettingStartedPictures/debugger/start_debugging.jpg -------------------------------------------------------------------------------- /src/view/pages/gettingStartedPictures/debugger/toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/vscode-python-devicesimulator/b27b917fc1ac17b9957345ae4a762e3ae05956a6/src/view/pages/gettingStartedPictures/debugger/toolbar.png -------------------------------------------------------------------------------- /src/view/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/view/styles/Button.css: -------------------------------------------------------------------------------- 1 | .button { 2 | height: 32px; 3 | background: var(--vscode-debugToolBar-background); 4 | border-color: var(--vscode-debugToolBar-background); 5 | } 6 | 7 | .button-icon { 8 | fill: var(--vscode-badgeForegroundOverride); 9 | } 10 | 11 | .button-rectangle { 12 | stroke: var(--vscode-badgeForegroundOverride); 13 | } 14 | 15 | .play-button { 16 | border-radius: 8px 0px 0px 8px; 17 | border-color: var(--vscode-highContrastButtonBorderOverride-color); 18 | } 19 | 20 | .refresh-button { 21 | border-radius: 0px 8px 8px 0px; 22 | border-color: var(--vscode-highContrastButtonBorderOverride-color); 23 | } 24 | 25 | .button:focus, 26 | .button:hover { 27 | background-color: var(--vscode-terminal-selectionBackground); 28 | } 29 | 30 | .button:active { 31 | background-color: var(--vscode-editor-selectionHighlightBackground); 32 | } 33 | 34 | .toolbar-button { 35 | border: none; 36 | } 37 | 38 | .toolbar-button:hover { 39 | outline: none; 40 | } 41 | 42 | .edge-button { 43 | pointer-events: none; 44 | border: none; 45 | } 46 | 47 | .button-pressed { 48 | background-color: var(--vscode-button-background); 49 | outline: none; 50 | } 51 | -------------------------------------------------------------------------------- /src/view/styles/Dropdown.css: -------------------------------------------------------------------------------- 1 | .dropdown { 2 | background: var(--vscode-debugToolBar-background); 3 | border-color: var(--vscode-foreground); 4 | border-radius: 2px; 5 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.22); 6 | color: var(--vscode-foreground); 7 | height: 32px; 8 | width: 100%; 9 | } 10 | 11 | select.dropdown:hover, 12 | select.dropdown:focus, 13 | select.dropdown:active { 14 | outline: 1px solid var(--vscode-button-background); 15 | } 16 | 17 | option { 18 | height: 32px; 19 | background: var(--vscode-debugToolBar-background); 20 | outline: 0; 21 | align-items: center; 22 | font-size: 14px; 23 | color: var(--vscode-foreground); 24 | } 25 | -------------------------------------------------------------------------------- /src/view/styles/InputSlider.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --slider-gray-color: #cccccc; 3 | --slider-width: 240px; 4 | } 5 | .inputSlider { 6 | height: 48px; 7 | margin-bottom: 60px; 8 | display: table-cell; 9 | } 10 | .sliderValue { 11 | -webkit-appearance: none; 12 | text-align: center; 13 | width: 48px; 14 | height: 32px; 15 | background-color: var(--vscode-editor-background); 16 | margin-right: 15px; 17 | margin-top: auto; 18 | margin-bottom: auto; 19 | margin-left: 5px; 20 | color: var(--badgeForegroundOverride); 21 | border-radius: 2px; 22 | font-size: 16px; 23 | font-weight: bold; 24 | border-width: 1px; 25 | border-radius: 2px; 26 | border-color: var(--vscode-highContrastButtonBorderOverride-color); 27 | } 28 | 29 | .slider { 30 | -webkit-appearance: none; 31 | background-color: var(--slider-gray-color); 32 | height: 1px; 33 | width: var(--slider-width); 34 | vertical-align: middle; 35 | } 36 | .slider::-webkit-slider-thumb { 37 | -webkit-appearance: none; 38 | appearance: none; 39 | width: 16px; 40 | height: 16px; 41 | border-radius: 50%; 42 | background: var(--vscode-textLink-activeForeground); 43 | cursor: pointer; 44 | } 45 | 46 | .slider::-webkit-slider-runnable-track:focus, 47 | .inputSlider:focus, 48 | .slider:focus { 49 | outline: none; 50 | } 51 | 52 | .sliderValue:focus, 53 | .sliderValue:hover { 54 | -webkit-appearance: none; 55 | color: var(--vscode-textLink-activeForeground); 56 | outline: 1px solid var(--vscode-textLink-activeForeground); 57 | } 58 | .maxLabel, 59 | .minLabel { 60 | display: inline-block; 61 | position: absolute; 62 | vertical-align: top; 63 | } 64 | 65 | .maxLabel { 66 | right: 0; 67 | } 68 | 69 | .minLabel { 70 | left: 0; 71 | } 72 | 73 | .sliderArea, 74 | .sliderValue { 75 | display: inline-block; 76 | vertical-align: middle; 77 | } 78 | 79 | .sliderArea { 80 | width: var(--slider-width); 81 | height: 49px; 82 | } 83 | .downLabelArea { 84 | width: var(--slider-width); 85 | height: 15px; 86 | margin-top: 10px; 87 | position: relative; 88 | font-size: 14px; 89 | } 90 | .upLabelArea { 91 | width: var(--slider-width); 92 | height: 15px; 93 | margin-bottom: 10px; 94 | position: relative; 95 | font-weight: bolder; 96 | font-size: 16px; 97 | } 98 | 99 | .slider, 100 | .upLabelArea, 101 | .downLabelArea { 102 | display: block; 103 | } 104 | -------------------------------------------------------------------------------- /src/view/styles/LightSensorBar.css: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 14px; 3 | text-align: left; 4 | font-weight: bold; 5 | } 6 | 7 | .header { 8 | -webkit-appearance: none; 9 | height: 30px; 10 | margin-bottom: 2px; 11 | } 12 | 13 | .lightSensorBar { 14 | -webkit-appearance: none; 15 | margin-top: 10px; 16 | width: 400px; 17 | margin-left: auto; 18 | margin-right: auto; 19 | padding-bottom: 16px; 20 | } 21 | -------------------------------------------------------------------------------- /src/view/styles/MotionSensorBar.css: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 14px; 3 | text-align: left; 4 | } 5 | 6 | .header { 7 | -webkit-appearance: none; 8 | height: 30px; 9 | margin-bottom: 2px; 10 | } 11 | 12 | .MotionSensorBar { 13 | width: 100%; 14 | margin-left: auto; 15 | margin-right: auto; 16 | } 17 | 18 | .sensor-button-container { 19 | padding: 10px 0; 20 | } 21 | -------------------------------------------------------------------------------- /src/view/styles/SensorButton.css: -------------------------------------------------------------------------------- 1 | .sensor-button { 2 | color: var(--vscode-badgeForegroundOverride); 3 | text-align: center; 4 | background-color: var(--vscode-button-background); 5 | width: 100%; 6 | height: 32px; 7 | font-weight: bolder; 8 | border-color: var(--vscode-highContrastButtonBorderOverride-color); 9 | border-width: 1px; 10 | border-style: solid; 11 | padding: 4px; 12 | } 13 | 14 | .sensor-button:focus, 15 | .sensor-button:active { 16 | outline-width: thick; 17 | outline-offset: 4px; 18 | outline: 2px solid var(--vscode-focusBorder); 19 | background-color: var(--vscode-button-hoverBackground); 20 | } 21 | 22 | .sensor-button:active, 23 | .active-button { 24 | opacity: 0.5; 25 | } 26 | 27 | .sensor-button:hover { 28 | background-color: var(--vscode-button-hoverBackground); 29 | } 30 | -------------------------------------------------------------------------------- /src/view/styles/Simulator.css: -------------------------------------------------------------------------------- 1 | .simulator { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | max-width: 700px; 7 | max-height: 700px; 8 | margin-left: auto; 9 | margin-right: auto; 10 | } 11 | 12 | .buttons { 13 | display: flex; 14 | flex-direction: row; 15 | padding-top: 20px; 16 | justify-content: center; 17 | } 18 | 19 | .file-selector { 20 | padding: 20px; 21 | width: 80%; 22 | height: 30px; 23 | border: 1px solid var(--vscode-debugToolBar-background); 24 | border-radius: 3px; 25 | } 26 | 27 | .shake-pressed { 28 | /* Start the shake animation and make the animation last for 0.5 seconds */ 29 | animation: shake 0.5s; 30 | 31 | /* When the animation is finished, start again */ 32 | animation-iteration-count: infinite; 33 | } 34 | 35 | @keyframes shake { 36 | 0% { 37 | transform: translate(1px, 1px) rotate(0deg); 38 | } 39 | 10% { 40 | transform: translate(-1px, -2px) rotate(-1deg); 41 | } 42 | 20% { 43 | transform: translate(-3px, 0px) rotate(1deg); 44 | } 45 | 30% { 46 | transform: translate(3px, 2px) rotate(0deg); 47 | } 48 | 40% { 49 | transform: translate(1px, -1px) rotate(1deg); 50 | } 51 | 50% { 52 | transform: translate(-1px, 2px) rotate(-1deg); 53 | } 54 | 60% { 55 | transform: translate(-3px, 1px) rotate(0deg); 56 | } 57 | 70% { 58 | transform: translate(3px, 1px) rotate(-1deg); 59 | } 60 | 80% { 61 | transform: translate(-1px, -1px) rotate(1deg); 62 | } 63 | 90% { 64 | transform: translate(1px, 2px) rotate(0deg); 65 | } 66 | 100% { 67 | transform: translate(1px, -2px) rotate(-1deg); 68 | } 69 | } 70 | 71 | .microbit-container { 72 | max-width: 350px; 73 | padding: 20px; 74 | } 75 | .clue-container { 76 | width: 100%; 77 | max-width: 500px; 78 | padding: 20px; 79 | } 80 | .cpx-container { 81 | width: 100%; 82 | padding-top: 10px; 83 | } 84 | -------------------------------------------------------------------------------- /src/view/styles/TemperatureSensorBar.css: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 14px; 3 | text-align: left; 4 | font-weight: bold; 5 | } 6 | 7 | .header { 8 | -webkit-appearance: none; 9 | height: 30px; 10 | margin-bottom: 2px; 11 | } 12 | .temperatureSensorBar { 13 | margin-top: 10px; 14 | width: 440px; 15 | margin-left: auto; 16 | margin-right: auto; 17 | padding-bottom: 16px; 18 | } 19 | -------------------------------------------------------------------------------- /src/view/svgs/arrow_right_svg.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const ARROW_RIGHT_SVG = ( 4 | 12 | 18 | 19 | ); 20 | 21 | export default ARROW_RIGHT_SVG; 22 | -------------------------------------------------------------------------------- /src/view/svgs/close_svg.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const CLOSE_SVG = ( 4 | 11 | 17 | 23 | 24 | ); 25 | 26 | export default CLOSE_SVG; 27 | -------------------------------------------------------------------------------- /src/view/svgs/play_svg.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const PLAY_SVG = ( 4 | 11 | 16 | 17 | ); 18 | 19 | export default PLAY_SVG; 20 | -------------------------------------------------------------------------------- /src/view/svgs/refresh_svg.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "../styles/Button.css"; 3 | 4 | export const REFRESH_SVG = ( 5 | 12 | 18 | 19 | ); 20 | 21 | export default REFRESH_SVG; 22 | -------------------------------------------------------------------------------- /src/view/svgs/stop_svg.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const STOP_SVG = ( 4 | 11 | 18 | 19 | ); 20 | 21 | export default STOP_SVG; 22 | -------------------------------------------------------------------------------- /src/view/svgs/tag_input_svg.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const TAG_INPUT_SVG = ( 4 | 11 | 15 | 24 | 25 | ); 26 | 27 | export default TAG_INPUT_SVG; 28 | -------------------------------------------------------------------------------- /src/view/svgs/tag_output_svg.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const TAG_OUTPUT_SVG = ( 4 | 11 | 15 | 23 | 24 | ); 25 | 26 | export default TAG_OUTPUT_SVG; 27 | -------------------------------------------------------------------------------- /src/view/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "moduleResolution": "node", 5 | "target": "es6", 6 | "outDir": "out", 7 | "lib": ["es6", "dom"], 8 | "jsx": "react", 9 | "sourceMap": true, 10 | "rootDir": "..", 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "experimentalDecorators": true 16 | }, 17 | "exclude": ["node_modules"], 18 | "include": ["./index.tsx"] 19 | } 20 | -------------------------------------------------------------------------------- /src/view/utils/MessageUtils.tsx: -------------------------------------------------------------------------------- 1 | interface vscode { 2 | postMessage(message: any): void; 3 | } 4 | 5 | declare const vscode: vscode; 6 | 7 | export const sendMessage = ( 8 | type: string, 9 | state: TState 10 | ) => { 11 | vscode.postMessage({ command: type, text: state }); 12 | }; 13 | -------------------------------------------------------------------------------- /src/view/viewUtils.tsx: -------------------------------------------------------------------------------- 1 | import { SENSOR_LIST } from "./constants"; 2 | 3 | // Copyright (c) Microsoft Corporation. 4 | // Licensed under the MIT license. 5 | export interface ISliderProps { 6 | minValue: number; 7 | maxValue: number; 8 | maxLabel: string; 9 | minLabel: string; 10 | type: string | SENSOR_LIST; 11 | axisLabel: string; 12 | value?: number; 13 | onUpdateValue?: (sensor: SENSOR_LIST, value: number) => void; 14 | step: number; 15 | } 16 | 17 | export interface ISensorButtonProps { 18 | label: string; 19 | type: string; 20 | onMouseUp?: (event: React.PointerEvent) => void; 21 | onMouseDown?: (event: React.PointerEvent) => void; 22 | onKeyUp?: (event: React.KeyboardEvent) => void; 23 | onKeyDown?: (event: React.KeyboardEvent) => void; 24 | } 25 | export interface ISensorProps { 26 | LABEL: string; 27 | sliderProps: ISliderProps[]; 28 | unitLabel: string; 29 | } 30 | 31 | export const X_SLIDER_INDEX = 0; 32 | export const Y_SLIDER_INDEX = 1; 33 | export const Z_SLIDER_INDEX = 2; 34 | -------------------------------------------------------------------------------- /src/vscode_import.ts: -------------------------------------------------------------------------------- 1 | declare const acquireVsCodeApi; 2 | 3 | const vscode = acquireVsCodeApi(); 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": ["es6", "dom"], 7 | "sourceMap": true, 8 | "rootDir": "src", 9 | "jsx": "react", 10 | "alwaysStrict": true 11 | }, 12 | "exclude": ["node_modules", ".vscode-test"] 13 | } 14 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/tslint-microsoft-contrib"], 3 | "extends": [ 4 | "tslint:latest", 5 | "tslint-react", 6 | "tslint-config-prettier", 7 | "tslint-react-hooks", 8 | "tslint-microsoft-contrib/latest" 9 | ], 10 | "rules": { 11 | "no-implicit-dependencies": [true, "dev"], 12 | "no-string-throw": true, 13 | "no-unused-expression": true, 14 | "no-duplicate-variable": true, 15 | "no-empty": false, 16 | "no-relative-imports": false, 17 | "max-func-body-length": false, 18 | "curly": true, 19 | "class-name": true, 20 | "triple-equals": true, 21 | "object-literal-sort-keys": true, 22 | "react-hooks-nesting": "error", 23 | "ordered-imports": true, 24 | "import-name": false, 25 | "member-access": false, 26 | "no-console": false, 27 | "jsx-boolean-value": false, 28 | "no-unnecessary-semicolons": false, 29 | "no-http-string": false, 30 | "export-name": false, 31 | "interface-name": false 32 | }, 33 | "defaultSeverity": "warning" 34 | } 35 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const tsImportPlugin = require("ts-import-plugin"); 3 | 4 | module.exports = { 5 | entry: { 6 | simulator: "./src/view/index.tsx" 7 | }, 8 | output: { 9 | path: path.resolve(__dirname, "out"), 10 | filename: "[name].js" 11 | }, 12 | devServer: { 13 | historyApiFallback: true 14 | }, 15 | devtool: "source-map", 16 | resolve: { 17 | extensions: [".js", ".ts", ".tsx", ".json"] 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(ts|tsx)$/, 23 | loader: "ts-loader", 24 | options: { 25 | getCustomTransformers: () => ({ 26 | before: [ 27 | tsImportPlugin({ 28 | libraryName: "antd", 29 | libraryDirectory: "es", 30 | style: true 31 | }) 32 | ] 33 | }) 34 | } 35 | }, 36 | { 37 | test: /\.less$/, 38 | use: [ 39 | { 40 | loader: "style-loader" 41 | }, 42 | { 43 | loader: "css-loader", 44 | options: { 45 | importLoaders: 1, 46 | sourceMap: true 47 | } 48 | }, 49 | { 50 | loader: "less-loader", 51 | options: { 52 | javascriptEnabled: true, 53 | sourceMap: true, 54 | modifyVars: { 55 | "@body-background": "var(--background-color)" 56 | } 57 | } 58 | } 59 | ] 60 | }, 61 | { 62 | test: /\.css$/, 63 | use: [ 64 | { 65 | loader: "style-loader" 66 | }, 67 | { 68 | loader: "css-loader" 69 | } 70 | ] 71 | }, 72 | { 73 | test: /\.svg$/, 74 | loader: "svg-inline" 75 | } 76 | ] 77 | } 78 | // When importing a module whose path matches one of the following, just 79 | // assume a corresponding global variable exists and use that instead. 80 | // This is important because it allows us to avoid bundling all of our 81 | // dependencies, which allows browsers to cache those libraries between builds. 82 | // externals: { 83 | // react: "React", 84 | // "react-dom": "ReactDOM" 85 | // } 86 | }; 87 | --------------------------------------------------------------------------------