mirror of
https://github.com/We-Dont-Byte/Mind_Reader.git
synced 2024-11-15 03:35:59 +00:00
Hub communication (#12)
* Add persistent accessibility pane This will facilitate more extensive usage of the menu than the context menu. * Fixed missing files * Add missing semicolon * Implement HubController * Parse messages on arrival Rather than queuing inbound messages, the HubController now saves pending promises/rejects for each pending request. Each inbound packet is checked at the time of arrival, and if the ID matches a pending response, the corresponding promise is called. This fixes a problem where the longer the time between reads, the more garbage responses queue up that are guaranteed to get thrown away the next time the next response was gathered. * Add clarification comment to send * Add logger, output * Use stat+stream instead of reading entire file on upload * Split MindReader view into accessability and hub sub-views * Add missing comma from conflict resolution * Fix issues, split commands into sub-lists * Add rebuild instructions * More accurate * Add tools for native modules instructions to README.md * Move commands to correct spot * Remove automatic connection I did not heed the warning where 'only the path is guaranteed' when listing open serial ports and made the assumption that the manufacturer would be known (hint: it wasn't). * Use device specific language for output title
This commit is contained in:
parent
913be402b1
commit
b3984daad5
72
README.md
72
README.md
@ -1,6 +1,6 @@
|
|||||||
<!-- header with logo -->
|
<!-- header with logo -->
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="Mind Reader Logo" src="./media/logo.png"></img>
|
<img alt="Mind Reader Logo" src="media/logo.png"></img>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1>Mind_Reader</h1>
|
<h1>Mind_Reader</h1>
|
||||||
@ -27,21 +27,77 @@ Python programming with LEGO Mindstorms. Our goal is to:
|
|||||||
- [JAWS](https://www.freedomscientific.com/products/software/jaws/)
|
- [JAWS](https://www.freedomscientific.com/products/software/jaws/)
|
||||||
- [Apple VoiceOver](https://support.apple.com/guide/voiceover-guide/welcome/web/)
|
- [Apple VoiceOver](https://support.apple.com/guide/voiceover-guide/welcome/web/)
|
||||||
|
|
||||||
<!-- TODO: still need this -->
|
|
||||||
- Play audio alerts for syntax and runtime errors.
|
|
||||||
|
|
||||||
- Present a summary of the scope for an individual line of code.
|
- Present a summary of the scope for an individual line of code.
|
||||||
|
|
||||||
- Save and load programs directly onto the LEGO Hub from within Visual Studio Code
|
- Save and load programs directly onto the LEGO Hub from within Visual Studio Code
|
||||||
|
|
||||||
# For Developers
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
<!-- TODO: version information -->
|
|
||||||
<!-- TODO: how to support native-usb functionality? -->
|
|
||||||
- [Git](https://git-scm.com/)
|
- [Git](https://git-scm.com/)
|
||||||
- [Node.js](https://nodejs.org/en/)
|
- [Node.js](https://nodejs.org/en/)
|
||||||
|
|
||||||
|
**NOTE**: While installing Node.js, there will be a section titled "Tools for Native Modules". Make sure that
|
||||||
|
'Automatically install the necessary tools' is checked:
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="50%" height="50%" alt="tools for native modules page with tool installation checked" src="media/nodejs_setup.png"></img>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
If the compiled serial port version is incompatible, you may see no options presented in the Mind_Reader actions panel:
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="50%" height="50%" alt="mind reader actions panel with no items:" src="media/missing_actions.png"></img>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
In this case, you will also need to rebuild the serial port component with `electron-rebuild`. This is a one-time setup
|
||||||
|
for each version of Visual Studio Code. You may need to repeat this process if you update your version of Visual Studio
|
||||||
|
Code.
|
||||||
|
|
||||||
|
## Installing `electron-rebuild`
|
||||||
|
**Use Git Bash on Windows, and the terminal on MacOS/Linux. These steps will refer to this as 'the terminal'**
|
||||||
|
|
||||||
|
### 1 Install the `electron-rebuild` tool
|
||||||
|
In the terminal install electron rebuild with `npm` that is included with [Node.js](https://nodejs.org/en/):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ npm install -g electron-rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2 Finding your electron version
|
||||||
|
On MacOS, go to Code > About Visual Studio Code.
|
||||||
|
|
||||||
|
On Windows and Linux, go to Help > About.
|
||||||
|
|
||||||
|
The electron version should be listed, e.g.: `Electron: 13.5.2`
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="35%" height="35%" alt="vscode information" src="media/vscode_info.png"></img>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### 3 Finding the Mind_Reader extension directory
|
||||||
|
On MacOS and Linux this is `~/.vscode/extensions`.
|
||||||
|
|
||||||
|
On Windows this is `C:\<YOUR USER>\.vscode\extensions\`. However, in Git Bash, it will appear like on MacOS and Linux
|
||||||
|
e.g.: `~/.vscode/extensions`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Find the Mind_Reader extension folder, this should look like `xxx.mind-reader-x.x.x`.
|
||||||
|
|
||||||
|
Navigate to the found folder in the terminal.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ cd ~/.vscode/extensions/<mind_reader_folder>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4 Running `electron-rebuild`
|
||||||
|
|
||||||
|
Then, run `electron-rebuild` with `ELECTRON_VERSION` replaced with the electron version found in step 2:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ electron-rebuild --version=ELECTRON_VERSION
|
||||||
|
```
|
||||||
|
|
||||||
|
# For Developers
|
||||||
## Development Quick Start
|
## Development Quick Start
|
||||||
Use the following to set up the extension for development.
|
Use the following to set up the extension for development.
|
||||||
|
|
||||||
|
BIN
media/missing_actions.png
Normal file
BIN
media/missing_actions.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
BIN
media/nodejs_setup.png
Normal file
BIN
media/nodejs_setup.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
media/vscode_info.png
Normal file
BIN
media/vscode_info.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
1214
package-lock.json
generated
1214
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
99
package.json
99
package.json
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mind-reader",
|
"name": "mind-reader",
|
||||||
"displayName": "Mind_Reader",
|
"displayName": "Mind_Reader",
|
||||||
|
"repository": "https://github.com/SingleSemesterSnobs/Mind_Reader",
|
||||||
"description": "",
|
"description": "",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -63,7 +64,6 @@
|
|||||||
"command": "mind-reader.runLineContext",
|
"command": "mind-reader.runLineContext",
|
||||||
"title": "Run Line Context"
|
"title": "Run Line Context"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.runCursorContext",
|
"command": "mind-reader.runCursorContext",
|
||||||
"title": "Run Cursor Context"
|
"title": "Run Cursor Context"
|
||||||
@ -71,8 +71,31 @@
|
|||||||
{
|
{
|
||||||
"command": "mind-reader.getIndent",
|
"command": "mind-reader.getIndent",
|
||||||
"title": "Get Line Indentation"
|
"title": "Get Line Indentation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mind-reader.connectHub",
|
||||||
|
"title": "Connect LEGO Hub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mind-reader.disconnectHub",
|
||||||
|
"title": "Disconnect LEGO Hub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mind-reader.uploadCurrentFile",
|
||||||
|
"title": "Upload current file to LEGO Hub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mind-reader.runProgram",
|
||||||
|
"title": "Run a program from the LEGO Hub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mind-reader.stopExecution",
|
||||||
|
"title": "Stop running program on the LEGO Hub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "mind-reader.deleteProgram",
|
||||||
|
"title": "Delete a program from the LEGO Hub"
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
"keybindings": [
|
"keybindings": [
|
||||||
{
|
{
|
||||||
@ -80,103 +103,86 @@
|
|||||||
"key": "numpad_subtract",
|
"key": "numpad_subtract",
|
||||||
"mac": "d"
|
"mac": "d"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.increaseFontScale",
|
"command": "mind-reader.increaseFontScale",
|
||||||
"key": "numpad_add",
|
"key": "numpad_add",
|
||||||
"mac": "[NumpadAdd]"
|
"mac": "[NumpadAdd]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.increaseEditorScale",
|
"command": "mind-reader.increaseEditorScale",
|
||||||
"key": "shift+numpad_add",
|
"key": "shift+numpad_add",
|
||||||
"mac": "Shift+[NumpadAdd]"
|
"mac": "Shift+[NumpadAdd]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.decreaseEditorScale",
|
"command": "mind-reader.decreaseEditorScale",
|
||||||
"key": "shift+numpad_subtract",
|
"key": "shift+numpad_subtract",
|
||||||
"mac": "Shift+[NumpadSubtract]"
|
"mac": "Shift+[NumpadSubtract]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.resetEditorScale",
|
"command": "mind-reader.resetEditorScale",
|
||||||
"key": "shift+enter",
|
"key": "shift+enter",
|
||||||
"mac": "Shift+[Enter]"
|
"mac": "Shift+[Enter]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.showAllSymbols",
|
"command": "mind-reader.showAllSymbols",
|
||||||
"key": "Ctrl+T",
|
"key": "Ctrl+T",
|
||||||
"mac": "Cmd+[KeyT]"
|
"mac": "Cmd+[KeyT]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.gotoLine",
|
"command": "mind-reader.gotoLine",
|
||||||
"key": "CTRL+G",
|
"key": "CTRL+G",
|
||||||
"mac": "Cmd+[KeyG]"
|
"mac": "Cmd+[KeyG]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.quickOpen",
|
"command": "mind-reader.quickOpen",
|
||||||
"key": "CTRL+P",
|
"key": "CTRL+P",
|
||||||
"mac": "Cmd+[KeyP]"
|
"mac": "Cmd+[KeyP]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.gotoSymbol",
|
"command": "mind-reader.gotoSymbol",
|
||||||
"key": "Ctrl+Shift+O",
|
"key": "Ctrl+Shift+O",
|
||||||
"mac": "Cmd+Shift+[KeyO]"
|
"mac": "Cmd+Shift+[KeyO]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.showProblems",
|
"command": "mind-reader.showProblems",
|
||||||
"key": "Ctrl+Shift+M",
|
"key": "Ctrl+Shift+M",
|
||||||
"mac": "Cmd+Shift+[KeyM]"
|
"mac": "Cmd+Shift+[KeyM]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.nextInFiles",
|
"command": "mind-reader.nextInFiles",
|
||||||
"key": "F8",
|
"key": "F8",
|
||||||
"mac": "[F8]"
|
"mac": "[F8]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.prevInFiles",
|
"command": "mind-reader.prevInFiles",
|
||||||
"key": "Shift+F8",
|
"key": "Shift+F8",
|
||||||
"mac": "Shift+[F8]"
|
"mac": "Shift+[F8]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.quickOpenPreviousRecentlyUsedEditorInGroup",
|
"command": "mind-reader.quickOpenPreviousRecentlyUsedEditorInGroup",
|
||||||
"key": "Ctrl+Tab",
|
"key": "Ctrl+Tab",
|
||||||
"mac": "Cmd+[Tab]"
|
"mac": "Cmd+[Tab]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.navigateBack",
|
"command": "mind-reader.navigateBack",
|
||||||
"key": "Ctrl+Alt+-",
|
"key": "Ctrl+Alt+-",
|
||||||
"mac": "Cmd+Alt+[Minus]"
|
"mac": "Cmd+Alt+[Minus]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.getuickInputBack",
|
"command": "mind-reader.getQuickInputBack",
|
||||||
"key": "Ctrl+Alt+-",
|
"key": "Ctrl+Alt+-",
|
||||||
"mac": "Cmd+Alt+[Minus]"
|
"mac": "Cmd+Alt+[Minus]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.navigateForward",
|
"command": "mind-reader.navigateForward",
|
||||||
"key": "Ctrl+Shift+-",
|
"key": "Ctrl+Shift+-",
|
||||||
"mac": "Cmd+Shift+[Minus]"
|
"mac": "Cmd+Shift+[Minus]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.selectTheme",
|
"command": "mind-reader.selectTheme",
|
||||||
"key": "Ctrl+Shift+1",
|
"key": "Ctrl+Shift+1",
|
||||||
"mac": "Cmd+Shift+[Digit1]"
|
"mac": "Cmd+Shift+[Digit1]"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"command": "mind-reader.getIndent",
|
"command": "mind-reader.getIndent",
|
||||||
"key": "Shift+Tab",
|
"key": "Shift+Tab",
|
||||||
@ -251,7 +257,6 @@
|
|||||||
"group": "mind-reader",
|
"group": "mind-reader",
|
||||||
"when": "activeEditor"
|
"when": "activeEditor"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"submenus": [
|
"submenus": [
|
||||||
@ -263,20 +268,20 @@
|
|||||||
"configuration": {
|
"configuration": {
|
||||||
"title": "Mind_Reader",
|
"title": "Mind_Reader",
|
||||||
"properties": {
|
"properties": {
|
||||||
"mindreader.productType": {
|
"mindReader.productType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Specifies the LEGO® product.",
|
"description": "Specifies the LEGO® product.",
|
||||||
"default": "MINDSTORMS® EV3",
|
"default": "MINDSTORMS EV3",
|
||||||
"enum": [
|
"enum": [
|
||||||
"MINDSTORMS® EV3",
|
"MINDSTORMS EV3",
|
||||||
"SPIKE™ Prime"
|
"SPIKE Prime"
|
||||||
],
|
],
|
||||||
"enumDescriptions": [
|
"enumDescriptions": [
|
||||||
"LEGO® MINDSTORMS® EV3 (31313)",
|
"LEGO® MINDSTORMS® EV3 (31313)",
|
||||||
"LEGO® Education SPIKE™ Prime Set (45678)"
|
"LEGO® Education SPIKE™ Prime Set (45678)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mindreader.reader.screenReader": {
|
"mindReader.reader.screenReader": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Specifies which screen reader to optimize for.",
|
"description": "Specifies which screen reader to optimize for.",
|
||||||
"default": "NVDA",
|
"default": "NVDA",
|
||||||
@ -291,37 +296,43 @@
|
|||||||
"Apple VoiceOver (macOS)"
|
"Apple VoiceOver (macOS)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mindreader.reader.contextWindow": {
|
"mindReader.reader.contextWindow": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "The number of words around the cursor to use when reading the cursor context",
|
"description": "The number of words around the cursor to use when reading the cursor context",
|
||||||
"default": 1
|
"default": 1
|
||||||
},
|
},
|
||||||
"mindreader.connection.connectAutomatically": {
|
"mindReader.connection.portPath": {
|
||||||
"type": "boolean",
|
|
||||||
"description": "Specifies whether to try to automatically detect and communicate with a connected Hub.",
|
|
||||||
"default": "true"
|
|
||||||
},
|
|
||||||
"mindreader.connection.portPath": {
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"markdownDescription": "Specifies the serial port path to use if `#mindreader.connectAutomatically#` is not set."
|
"markdownDescription": "Specifies the serial port path to use if `#mindReader.connectAutomatically#` is not set."
|
||||||
|
},
|
||||||
|
"mindReader.connection.clearOutputOnRun": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether to clear the output each time the program is run",
|
||||||
|
"default": "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"views": {
|
"views": {
|
||||||
"accessActions": [
|
"MindReader": [
|
||||||
{
|
{
|
||||||
"id": "accessActions",
|
"id": "accessActions",
|
||||||
"name": "Access Actions",
|
"name": "Access Actions",
|
||||||
"icon": "media/dep.svg",
|
"icon": "media/dep.svg",
|
||||||
"contextualTitle": "Accessibility Menu Actions"
|
"contextualTitle": "Accessibility Menu Actions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hubActions",
|
||||||
|
"name": "Hub Actions",
|
||||||
|
"icon": "media/dep.svg",
|
||||||
|
"contextualTitle": "Hub Connection Actions"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"viewsContainers": {
|
"viewsContainers": {
|
||||||
"activitybar": [
|
"activitybar": [
|
||||||
{
|
{
|
||||||
"id": "accessActions",
|
"id": "MindReader",
|
||||||
"title": "Access Actions",
|
"title": "MindReader Actions",
|
||||||
"icon": "media/dep.svg"
|
"icon": "media/dep.svg"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -336,17 +347,21 @@
|
|||||||
"test": "node ./out/test/runTest.js"
|
"test": "node ./out/test/runTest.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/vscode": "^1.60.0",
|
|
||||||
"@types/glob": "^7.1.3",
|
"@types/glob": "^7.1.3",
|
||||||
"@types/mocha": "^8.2.2",
|
"@types/mocha": "^8.2.2",
|
||||||
"@types/node": "14.x",
|
"@types/node": "16.x",
|
||||||
"eslint": "^7.27.0",
|
"@types/serialport": "^8.0.2",
|
||||||
|
"@types/vscode": "^1.60.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||||
"@typescript-eslint/parser": "^4.26.0",
|
"@typescript-eslint/parser": "^4.26.0",
|
||||||
|
"@vscode/test-electron": "^1.6.2",
|
||||||
|
"eslint": "^7.27.0",
|
||||||
"glob": "^7.1.7",
|
"glob": "^7.1.7",
|
||||||
"mocha": "^8.4.0",
|
"mocha": "^8.4.0",
|
||||||
"typescript": "^4.3.2",
|
"typescript": "^4.3.2",
|
||||||
"vscode-test": "^1.5.2",
|
"vscode-test": "^1.5.2"
|
||||||
"@vscode/test-electron": "^1.6.2"
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"serialport": "^9.2.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
43
src/commandNodeProvider.ts
Normal file
43
src/commandNodeProvider.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
import { CommandEntry } from './commands';
|
||||||
|
|
||||||
|
export class CommandItem extends vscode.TreeItem {
|
||||||
|
constructor(
|
||||||
|
public readonly label: string,
|
||||||
|
public readonly command: vscode.Command
|
||||||
|
) {
|
||||||
|
super(label, vscode.TreeItemCollapsibleState.None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class CommandNodeProvider implements vscode.TreeDataProvider<CommandItem> {
|
||||||
|
private items: CommandItem[] = [];
|
||||||
|
|
||||||
|
public constructor(commands: CommandEntry[]) {
|
||||||
|
// build and cache command items
|
||||||
|
for (const c of commands) {
|
||||||
|
let humanReadable = c.name.replace(/^mind-reader\./, ''); // strip extensions name
|
||||||
|
// Convert camelCaseText to Title Case Text
|
||||||
|
humanReadable = humanReadable.replace(/([A-Z])/g, ' $1');
|
||||||
|
humanReadable = humanReadable.charAt(0).toUpperCase() + humanReadable.slice(1);
|
||||||
|
|
||||||
|
this.items.push(new CommandItem(
|
||||||
|
humanReadable,
|
||||||
|
{
|
||||||
|
title: humanReadable,
|
||||||
|
command: c.name,
|
||||||
|
tooltip: humanReadable
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeItem(item: CommandItem): vscode.TreeItem {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(): Promise<CommandItem[]> {
|
||||||
|
return Promise.resolve(this.items);
|
||||||
|
}
|
||||||
|
}
|
206
src/commands.ts
206
src/commands.ts
@ -1,19 +1,22 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as pl from './pylex';
|
import * as pl from './pylex';
|
||||||
|
import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
import HubManager from './hubManager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Object} Command // Command to register with the VS Code Extension API
|
* @type {Object} Command // Command to register with the VS Code Extension API
|
||||||
* @prop {string} command // Name of the command; e.g., 'mind-reader.selectTheme'
|
* @prop {string} command // Name of the command; e.g., 'mind-reader.selectTheme'
|
||||||
* @prop {callback} callback // Callback to register when `command` is invoked
|
* @prop {callback} callback // Callback to register when `command` is invoked
|
||||||
*/
|
*/
|
||||||
type Command = {
|
export type CommandEntry = {
|
||||||
name: string,
|
name: string,
|
||||||
callback: () => void
|
callback: () => void
|
||||||
};
|
};
|
||||||
|
|
||||||
// The list of commands to register in the extension
|
// Accessibility Commands
|
||||||
const commands: Command[] = [
|
export const accessCommands: CommandEntry[] = [
|
||||||
{
|
{
|
||||||
name: 'mind-reader.selectTheme',
|
name: 'mind-reader.selectTheme',
|
||||||
|
|
||||||
@ -51,6 +54,23 @@ const commands: Command[] = [
|
|||||||
callback: resetEditorScale,
|
callback: resetEditorScale,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'mind-reader.getIndent',
|
||||||
|
callback: getIndent,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'mind-reader.runLineContext',
|
||||||
|
callback: runLineContext,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'mind-reader.runCursorContext',
|
||||||
|
callback: runCursorContext
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const navCommands: CommandEntry[] = [
|
||||||
{
|
{
|
||||||
name: 'mind-reader.openWebview',
|
name: 'mind-reader.openWebview',
|
||||||
callback: openWebview,
|
callback: openWebview,
|
||||||
@ -117,7 +137,7 @@ const commands: Command[] = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'mind-reader.getuickInputBack',
|
name: 'mind-reader.getQuickInputBack',
|
||||||
callback: () => vscode.commands.executeCommand('workbench.action.quickInputBack'),
|
callback: () => vscode.commands.executeCommand('workbench.action.quickInputBack'),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -125,18 +145,38 @@ const commands: Command[] = [
|
|||||||
name: 'mind-reader.navigateForward',
|
name: 'mind-reader.navigateForward',
|
||||||
callback: () => vscode.commands.executeCommand('workbench.action.navigateForward'),
|
callback: () => vscode.commands.executeCommand('workbench.action.navigateForward'),
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const hubCommands: CommandEntry[] = [
|
||||||
{
|
{
|
||||||
name: 'mind-reader.runLineContext',
|
name: 'mind-reader.connectHub',
|
||||||
callback: runLineContext,
|
callback: connectHub
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'mind-reader.runCursorContext',
|
name: 'mind-reader.disconnectHub',
|
||||||
callback: runCursorContext
|
callback: disconnectHub
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'mind-reader.getIndent',
|
name: 'mind-reader.uploadCurrentFile',
|
||||||
callback: getIndent
|
callback: uploadCurrentFile
|
||||||
}
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'mind-reader.runProgram',
|
||||||
|
callback: runProgram
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'mind-reader.stopExecution',
|
||||||
|
callback: stopExecution
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'mind-reader.deleteProgram',
|
||||||
|
callback: deleteProgram
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// COMMAND CALLBACK IMPLEMENTATIONS
|
// COMMAND CALLBACK IMPLEMENTATIONS
|
||||||
@ -278,17 +318,17 @@ function createContextString(context: pl.LexNode[], line: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// find up to `n` words around the cursor, where `n` is
|
// find up to `n` words around the cursor, where `n` is
|
||||||
// the value of `#mindreader.reader.contextWindow`
|
// the value of `#mindReader.reader.contextWindow`
|
||||||
function runCursorContext(): void {
|
function runCursorContext(): void {
|
||||||
let editor = vscode.window.activeTextEditor;
|
let editor = vscode.window.activeTextEditor;
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
vscode.window.showErrorMessage("RunCursorContext: No Active Editor");
|
vscode.window.showErrorMessage('RunCursorContext: No Active Editor');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cursorPos: vscode.Position = editor.selection.active;
|
const cursorPos: vscode.Position = editor.selection.active;
|
||||||
const text: string = editor.document.lineAt(cursorPos).text;
|
const text: string = editor.document.lineAt(cursorPos).text;
|
||||||
const windowSize: number = vscode.workspace.getConfiguration('mindreader').get('reader.contextWindow')!;
|
const windowSize: number = vscode.workspace.getConfiguration('mindReader').get('reader.contextWindow')!;
|
||||||
|
|
||||||
let trimmedText = text.trimStart(); // trim leading whitespace
|
let trimmedText = text.trimStart(); // trim leading whitespace
|
||||||
let leadingWS = text.length - trimmedText.length; // # of characters of leading whitespace
|
let leadingWS = text.length - trimmedText.length; // # of characters of leading whitespace
|
||||||
@ -343,4 +383,140 @@ function runCursorContext(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default commands;
|
// Current connected hub
|
||||||
|
let hub: HubManager | null = null;
|
||||||
|
|
||||||
|
// TODO: port option
|
||||||
|
async function connectHub(): Promise<void> {
|
||||||
|
if (hub) {
|
||||||
|
vscode.window.showWarningMessage('LEGO Hub is already connected, reconnecting...');
|
||||||
|
disconnectHub();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ports = await HubManager.queryPorts();
|
||||||
|
|
||||||
|
if (ports.length === 0) {
|
||||||
|
vscode.window.showErrorMessage('No ports found. Is the LEGO Hub connected?');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let portPath: string | undefined = vscode.workspace.getConfiguration('mindReader.connection').get('portPath');
|
||||||
|
|
||||||
|
if (!portPath) {
|
||||||
|
let slots: vscode.QuickPickItem[] = [];
|
||||||
|
for (const port of ports) {
|
||||||
|
slots.push({ label: port.path });
|
||||||
|
}
|
||||||
|
|
||||||
|
let picked = await vscode.window.showQuickPick(slots);
|
||||||
|
|
||||||
|
if (!picked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
portPath = picked.label;
|
||||||
|
}
|
||||||
|
hub = await HubManager.create(portPath);
|
||||||
|
vscode.window.showInformationMessage('LEGO Hub connected');
|
||||||
|
} catch (err) {
|
||||||
|
vscode.window.showErrorMessage('Could not connect to LEGO Hub');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disconnectHub(): Promise<void> {
|
||||||
|
if (!hub || !hub.isOpen()) {
|
||||||
|
vscode.window.showErrorMessage('LEGO Hub is not connected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await hub.close();
|
||||||
|
hub = null;
|
||||||
|
vscode.window.showInformationMessage('LEGO Hub disconnected');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadCurrentFile(): Promise<void> {
|
||||||
|
if (!hub || !hub.isOpen()) {
|
||||||
|
vscode.window.showErrorMessage('LEGO Hub is not connected!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vscode.window.activeTextEditor) {
|
||||||
|
vscode.window.showErrorMessage('No active text editor');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentFilePath = vscode.window.activeTextEditor.document.fileName;
|
||||||
|
|
||||||
|
if (currentFilePath) {
|
||||||
|
// construct quickpick
|
||||||
|
const slots: vscode.QuickPickItem[] = [];
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
slots.push({ label: i.toString() });
|
||||||
|
}
|
||||||
|
const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false });
|
||||||
|
|
||||||
|
if (!slotID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: progress bar?
|
||||||
|
vscode.window.showInformationMessage('Uploading current file');
|
||||||
|
await hub.uploadFile(currentFilePath, parseInt(slotID.label), path.basename(currentFilePath));
|
||||||
|
vscode.window.showInformationMessage(path.basename(currentFilePath) + ' uploaded to slot ' + slotID.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: find empty slots
|
||||||
|
async function runProgram(): Promise<void> {
|
||||||
|
if (!hub || !hub.isOpen()) {
|
||||||
|
vscode.window.showErrorMessage('LEGO Hub is not connected!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slots: vscode.QuickPickItem[] = [];
|
||||||
|
// construct quickpick
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
slots.push({ label: i.toString() });
|
||||||
|
}
|
||||||
|
const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false });
|
||||||
|
|
||||||
|
if (!slotID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vscode.window.showInformationMessage('Running program ' + slotID.label);
|
||||||
|
await hub.programExecute(parseInt(slotID.label));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopExecution(): Promise<void> {
|
||||||
|
if (!hub || !hub.isOpen()) {
|
||||||
|
vscode.window.showErrorMessage('LEGO Hub is not connected!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await hub.programTerminate();
|
||||||
|
vscode.window.showInformationMessage('Execution stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: find slots from status
|
||||||
|
async function deleteProgram(): Promise<void> {
|
||||||
|
if (!hub || !hub.isOpen()) {
|
||||||
|
vscode.window.showErrorMessage('LEGO Hub is not connected!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slots: vscode.QuickPickItem[] = [];
|
||||||
|
// construct quickpick
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
slots.push({ label: i.toString() });
|
||||||
|
}
|
||||||
|
const slotID = await vscode.window.showQuickPick(slots, { canPickMany: false });
|
||||||
|
|
||||||
|
if (!slotID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await hub.deleteProgram(parseInt(slotID.label));
|
||||||
|
vscode.window.showInformationMessage('Deleted program ' + slotID.label);
|
||||||
|
}
|
||||||
|
@ -1,20 +1,27 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
import * as pl from './pylex';
|
import * as pl from './pylex';
|
||||||
import commands from './commands';
|
|
||||||
|
|
||||||
import AccessNodeProvider from './accessNodeProvider';
|
import { accessCommands, hubCommands, navCommands } from './commands';
|
||||||
|
|
||||||
|
import CommandNodeProvider from './commandNodeProvider';
|
||||||
|
import Logger from './log';
|
||||||
|
|
||||||
|
// Output Logger
|
||||||
|
const product: string = vscode.workspace.getConfiguration('mindReader').get('productType')!;
|
||||||
|
const outputChannel = vscode.window.createOutputChannel(product + " Output");
|
||||||
|
export const logger = new Logger(outputChannel);
|
||||||
|
|
||||||
let parser: pl.Parser = new pl.Parser();
|
let parser: pl.Parser = new pl.Parser();
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
console.log('Congratulations, your extension "mind-reader" is now active!');
|
|
||||||
vscode.window.showInformationMessage('Mind_Reader is loaded!');
|
vscode.window.showInformationMessage('Mind_Reader is loaded!');
|
||||||
|
|
||||||
parser.parse('Beep Boop');
|
parser.parse('Beep Boop');
|
||||||
|
|
||||||
|
let allCommands = accessCommands.concat(hubCommands).concat(navCommands);
|
||||||
|
|
||||||
// Register Commands
|
// Register Commands
|
||||||
commands.forEach(command => {
|
allCommands.forEach(command => {
|
||||||
let disposable = vscode.commands.registerCommand(
|
let disposable = vscode.commands.registerCommand(
|
||||||
command.name,
|
command.name,
|
||||||
command.callback
|
command.callback
|
||||||
@ -22,9 +29,11 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
context.subscriptions.push(disposable);
|
context.subscriptions.push(disposable);
|
||||||
});
|
});
|
||||||
|
|
||||||
let provider = new AccessNodeProvider();
|
let accessProvider = new CommandNodeProvider(accessCommands);
|
||||||
vscode.window.registerTreeDataProvider('accessActions', provider);
|
vscode.window.registerTreeDataProvider('accessActions', accessProvider);
|
||||||
|
|
||||||
|
let hubProvider = new CommandNodeProvider(hubCommands);
|
||||||
|
vscode.window.registerTreeDataProvider('hubActions', hubProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate() {}
|
export function deactivate() {}
|
||||||
|
391
src/hubManager.ts
Normal file
391
src/hubManager.ts
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
import * as SerialPort from 'serialport';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
import { logger } from './extension';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type RPCRequest an RPC request message
|
||||||
|
*
|
||||||
|
* @prop {string} `'m'` method to invoke.
|
||||||
|
* @prop {Object?} `'p'` optional parameters for method.
|
||||||
|
* @prop {string|null?} `'i'` optional request ID.
|
||||||
|
*/
|
||||||
|
type RPCRequest = {
|
||||||
|
'm': string;
|
||||||
|
'p'?: Object;
|
||||||
|
'i'?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type RPCResponse an RPC response message
|
||||||
|
*
|
||||||
|
* @prop {any?} `'r'` RPC response body
|
||||||
|
* @prop {Object?} `'e'` RPC error body
|
||||||
|
* @prop {string|null} `'i'` required response ID.
|
||||||
|
*/
|
||||||
|
type RPCResponse = {
|
||||||
|
'r'?: any;
|
||||||
|
'e'?: Object;
|
||||||
|
'i': string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages sending and receiving of JSON Remote Procedure Call (JSON-RPC) protocol messages
|
||||||
|
* to the Hub.
|
||||||
|
*/
|
||||||
|
export default class HubManager {
|
||||||
|
private port: SerialPort;
|
||||||
|
private receiveBuffer: string = ''; // buffer for in-flight messages
|
||||||
|
private pendingRequests = new Map<string, [(result: any) => void, (error: string) => void]>(); // lists of requests that are still pending
|
||||||
|
|
||||||
|
// ======================== INSTANCE METHODS ========================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor, use static `create` + `init`
|
||||||
|
*/
|
||||||
|
private constructor(public portPath: string) { }
|
||||||
|
|
||||||
|
public isOpen(): boolean {
|
||||||
|
return this.port.isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a received data chunk from the serial port
|
||||||
|
*
|
||||||
|
* @param `data` Data received from serial port
|
||||||
|
*/
|
||||||
|
private async receiveData(data: string) {
|
||||||
|
// add data to buffer
|
||||||
|
this.receiveBuffer += data;
|
||||||
|
|
||||||
|
// get full lines in buffer
|
||||||
|
|
||||||
|
let msgs = this.receiveBuffer.split(/\r/); // split by newline
|
||||||
|
this.receiveBuffer = msgs.pop()!; // store unhandled data
|
||||||
|
|
||||||
|
msgs = msgs.filter(x => !x.startsWith('{"m":0,"p":')); // drop sensor broadcast response spam
|
||||||
|
|
||||||
|
for (const msg of msgs) {
|
||||||
|
// check if this msg is a response to a pending request
|
||||||
|
try {
|
||||||
|
let json: { [key: string]: any };
|
||||||
|
|
||||||
|
json = JSON.parse(msg);
|
||||||
|
|
||||||
|
let id = json['i'];
|
||||||
|
if (id && this.pendingRequests.has(id)) {
|
||||||
|
// a request is waiting on this response
|
||||||
|
let [resolve, reject] = this.pendingRequests.get(id) ?? [];
|
||||||
|
|
||||||
|
if (json['e'] && reject) {
|
||||||
|
// error
|
||||||
|
reject(Buffer.from(json['e'], 'base64').toString());
|
||||||
|
} else if (resolve) {
|
||||||
|
resolve(json['r']);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pendingRequests.delete(id);
|
||||||
|
} else if (json['m']) {
|
||||||
|
// Print errors
|
||||||
|
const params = json['p'];
|
||||||
|
switch (json['m']) {
|
||||||
|
case 'user_program_error':
|
||||||
|
logger.error(Buffer.from(params[3], 'base64').toString());
|
||||||
|
logger.error(Buffer.from(params[4], 'base64').toString());
|
||||||
|
break;
|
||||||
|
case 'runtime_error':
|
||||||
|
logger.error(Buffer.from(params[3], 'base64').toString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Could not parse JSON:', msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a created HubManager with the current option settings
|
||||||
|
*/
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.port = new SerialPort(
|
||||||
|
this.portPath,
|
||||||
|
{
|
||||||
|
autoOpen: true,
|
||||||
|
baudRate: 112500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
// error during port opening
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.port.setEncoding('utf-8');
|
||||||
|
|
||||||
|
// push lines received to data queue
|
||||||
|
|
||||||
|
let mgr = this;
|
||||||
|
this.port.on('data', data => {
|
||||||
|
mgr.receiveData(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
this.port.close(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an RPC message. The corresponding Promise resolve
|
||||||
|
* is saved into the `pendingRequests` map. When an inbound
|
||||||
|
* message is found that matches an ID in `pendingRequests`,
|
||||||
|
* the corresponding resolve is called. So, even though
|
||||||
|
* the `resolve` call does not appear explicitly here, it *does*
|
||||||
|
* get resolved at the appropriate time.
|
||||||
|
*
|
||||||
|
* @param `proc` Procedure to execute
|
||||||
|
* @param `params` Optional parameters for the procedure
|
||||||
|
* @param `id` The ID to use for the RPC message. Use null to indicate no ID/notification message.
|
||||||
|
* If neither a string or `null` is passed, an ID is automatically generated.
|
||||||
|
*/
|
||||||
|
public async send(request: RPCRequest): Promise<RPCResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (request['i'] === undefined) {
|
||||||
|
// generate an ID
|
||||||
|
request['i'] = 'mind-reader-' + HubManager.randomID();
|
||||||
|
}
|
||||||
|
|
||||||
|
// write JSON to port
|
||||||
|
|
||||||
|
this.pendingRequests.set(request['i'], [resolve, reject]);
|
||||||
|
this.port.write(JSON.stringify(request));
|
||||||
|
this.port.write('\r');
|
||||||
|
this.port.drain();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async uploadFile(file: string, slotid: number, name?: string, autoStart: boolean = false) {
|
||||||
|
const fileStats = fs.statSync(file);
|
||||||
|
name = name || file;
|
||||||
|
|
||||||
|
const ack: {[key: string]: any} = await this.startWriteProgram(
|
||||||
|
name,
|
||||||
|
fileStats.size,
|
||||||
|
slotid,
|
||||||
|
fileStats.birthtime.getTime(),
|
||||||
|
fileStats.mtime.getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const blockSize = ack.blocksize;
|
||||||
|
const transferid = ack.transferid;
|
||||||
|
|
||||||
|
let dataStream = fs.createReadStream(file, { highWaterMark: blockSize });
|
||||||
|
for await (const data of dataStream) {
|
||||||
|
await this.writePackage(data, transferid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoStart) {
|
||||||
|
return Promise.resolve(await this.programExecute(slotid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= Hub Methods =========================
|
||||||
|
//
|
||||||
|
// These methods each handle a single RPC method's communication.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a program that is saved on the hub
|
||||||
|
*
|
||||||
|
* @param `slotid` Slot ID of the program to run
|
||||||
|
*/
|
||||||
|
public async programExecute(slotid: number): Promise<RPCResponse> {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
let response = await this.send({
|
||||||
|
'm': 'program_execute',
|
||||||
|
'p': {
|
||||||
|
'slotid': slotid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminate a currently running program
|
||||||
|
*/
|
||||||
|
public async programTerminate(): Promise<RPCResponse> {
|
||||||
|
return Promise.resolve(
|
||||||
|
await this.send({ 'm': 'program_terminate' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the storage status of the hub
|
||||||
|
*
|
||||||
|
* TODO: fill with actual example
|
||||||
|
* slot
|
||||||
|
* decoded name
|
||||||
|
* size
|
||||||
|
* last modified
|
||||||
|
* project_id
|
||||||
|
* type
|
||||||
|
*/
|
||||||
|
public async getStorageStatus(): Promise<RPCResponse> {
|
||||||
|
return Promise.resolve(
|
||||||
|
await this.send({ 'm': 'get_storage_status' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the Hub that a program write is about to occur.
|
||||||
|
*
|
||||||
|
* @param `name` Name of the program.
|
||||||
|
* @param `name` Size of the program TODO: in bytes?
|
||||||
|
* @param `slotID` Slot ID to write the program to
|
||||||
|
* @param `created` Creation timestamp
|
||||||
|
* @param `modified` Modified timestamp
|
||||||
|
*/
|
||||||
|
public async startWriteProgram(
|
||||||
|
name: string,
|
||||||
|
size: number,
|
||||||
|
slotID: number,
|
||||||
|
created: number,
|
||||||
|
modified: number
|
||||||
|
): Promise<RPCResponse> {
|
||||||
|
// file meta data
|
||||||
|
let stat = {
|
||||||
|
'created': created,
|
||||||
|
'modified': modified,
|
||||||
|
'name': name,
|
||||||
|
'type': 'python', // always python
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'project_id': HubManager.randomID(16),
|
||||||
|
};
|
||||||
|
|
||||||
|
return Promise.resolve(
|
||||||
|
await this.send({
|
||||||
|
'm': 'start_write_program',
|
||||||
|
'p': {
|
||||||
|
'slotid': slotID,
|
||||||
|
'size': size,
|
||||||
|
'meta': stat
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a package chunk of a file
|
||||||
|
*
|
||||||
|
* @param `data` Chunk of data to write
|
||||||
|
* @param `transferID` The transfer id for the current transfer
|
||||||
|
*/
|
||||||
|
public async writePackage(data: string, transferID: string): Promise<RPCResponse> {
|
||||||
|
return Promise.resolve(
|
||||||
|
await this.send({
|
||||||
|
'm': 'write_package',
|
||||||
|
'p': {
|
||||||
|
'data': Buffer.from(data, 'utf-8').toString('base64'),
|
||||||
|
'transferid': transferID
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a program from slot `fromSlot` to slot `toSlot`.
|
||||||
|
* TODO: verify
|
||||||
|
* If the destination already has a program stored in it, the two programs
|
||||||
|
* are swapped
|
||||||
|
*
|
||||||
|
* @param `fromSlot` slot to move from
|
||||||
|
* @param `toSlot` slot to move to
|
||||||
|
*/
|
||||||
|
public async moveProgram(oldSlotID: number, newSlotID: number) {
|
||||||
|
return Promise.resolve(
|
||||||
|
await this.send({
|
||||||
|
'm': 'move_project',
|
||||||
|
'p': {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'old_slotid': oldSlotID,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'new_slotid': newSlotID
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a program from the slot on the Hub
|
||||||
|
*
|
||||||
|
* @param `slotID` the slot to delete
|
||||||
|
*/
|
||||||
|
public async deleteProgram(slotID: number) {
|
||||||
|
return Promise.resolve(
|
||||||
|
await this.send({
|
||||||
|
'm': 'remove_project',
|
||||||
|
'p': {
|
||||||
|
'slotid': slotID
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get firmware info for the connected device
|
||||||
|
*/
|
||||||
|
public async getHubInfo() {
|
||||||
|
return Promise.resolve(
|
||||||
|
await this.send({
|
||||||
|
'm': 'get_hub_info'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================== INSTANCE METHODS ========================
|
||||||
|
|
||||||
|
public static async create(portPath: string): Promise<HubManager> {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
try {
|
||||||
|
let mgr = new HubManager(portPath);
|
||||||
|
await mgr.init();
|
||||||
|
|
||||||
|
return resolve(mgr);
|
||||||
|
} catch (err) {
|
||||||
|
|
||||||
|
// could not connect to port
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of serial devices
|
||||||
|
*/
|
||||||
|
public static async queryPorts() {
|
||||||
|
// get all ports
|
||||||
|
let ports = await SerialPort.list();
|
||||||
|
|
||||||
|
// filter by manufacturer
|
||||||
|
return Promise.resolve(ports);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random identifier consisting of lowercase
|
||||||
|
* letters, numbers, and underscores
|
||||||
|
*
|
||||||
|
* @param `len` The length of the string to generate
|
||||||
|
*/
|
||||||
|
static randomID(len: number = 4): string {
|
||||||
|
let result = '';
|
||||||
|
let characters = 'abcdefghijklmnopqrstuvwxyz0123456789_';
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
result += characters[Math.floor(Math.random() * characters.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
23
src/log.ts
Normal file
23
src/log.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export default class Logger {
|
||||||
|
constructor(
|
||||||
|
public readonly outputChannel: vscode.OutputChannel
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public log(text: string): void {
|
||||||
|
this.outputChannel.appendLine(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(text: string): void {
|
||||||
|
this.outputChannel.appendLine('[INFO]\r' + text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warn(text: string): void {
|
||||||
|
this.outputChannel.appendLine('[WARNING]\r' + text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(text: string): void {
|
||||||
|
this.outputChannel.appendLine('[ERROR]\r' + text);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user