Skip to content

Commit ef79d51

Browse files
author
Jakob Rosenberg
authored
Merge pull request #268 from pycom/next-staging
Next staging
2 parents 54ad7d8 + 2a1fd70 commit ef79d51

25 files changed

+602
-138
lines changed

GET_STARTED.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
1. First [download and install Visual Studio Code](https://code.visualstudio.com/).
44
2. Install the [Pymakr VSCode Extension](https://marketplace.visualstudio.com/items?itemName=pycom.Pymakr)
5-
6-
_(We're installing the preview, but once the project reaches "stable" we'll, be using the regular extension.)_
75

8-
<img src="./media/readme/install-pymakr.gif" />
6+
_(We're installing the preview, but once the project reaches "stable" we'll, be using the regular extension.)_
7+
8+
<img src="./media/readme/install-pymakr.gif" />
99

1010
3. That's it! You've installed the Pymakr Extension for VSCode
1111

@@ -35,27 +35,30 @@ Once the project is ready to run, it needs to be uploaded to a device.
3535

3636
To speed up development, you can put a project in `development mode`.
3737

38-
In development mode, Pymakr automatically propagates file changes in and restarts the main script.
38+
In development mode, Pymakr automatically propagates local file changes to connected devices and then restarts the main script.
3939

4040
Dev mode can be configured in `pymakr.json`
41+
4142
```json
4243
{
43-
"onUpdate": "restartScript" | "softRestartDevice" | "hardRestartDevice"
44+
"onUpdate": "restartScript" | "softRestartDevice" | "hardRestartDevice"
4445
}
4546
```
4647

4748
**onUpdate:** Action to be called once file changes have been propagated.
49+
4850
- **restartScript** Clears the `main.py` module as well as any changed modules. Then imports `boot.py` and `main.py`.
4951
- **softRestartDevice** Performs <kbd>ctrl + d</kbd>
5052
- **hardRestartDevice** Performs `machine.reset()`
5153

5254
#### NOTE
53-
*`machine.sleep` and `machine.deepsleep` do not work in development since they stop the USB connection.*
55+
56+
_`machine.sleep` and `machine.deepsleep` do not work in development since they stop the USB connection._
5457

5558
---
5659

5760
## Hint: Organizing your setup
5861

5962
Having to switch between different tabs can be cumbersome. To solve this, you can drag your devices and projects to the file explorer view.
6063

61-
![](./media/readme/move-view.gif)
64+
![](./media/readme/move-view.gif)

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,16 @@
358358
}
359359
],
360360
"commands": [
361+
{
362+
"command": "pymakr.showDebugMenu",
363+
"title": "Shows debugging menu",
364+
"category": "PyMakr"
365+
},
366+
{
367+
"command": "pymakr.openOnDevice",
368+
"title": "Open file on device",
369+
"category": "PyMakr"
370+
},
361371
{
362372
"command": "pymakr.logState",
363373
"title": "[DEBUG] Logs current state to the terminal",
@@ -776,6 +786,11 @@
776786
{
777787
"command": "pymakr.uploadPrompt",
778788
"group": "pymakr"
789+
},
790+
{
791+
"command": "pymakr.openOnDevice",
792+
"group": "pymakr",
793+
"when": "resourceScheme == file"
779794
}
780795
],
781796
"pymakr.explorerContextMenu": [
@@ -792,6 +807,11 @@
792807
{
793808
"command": "pymakr.uploadPrompt",
794809
"group": "pymakr"
810+
},
811+
{
812+
"command": "pymakr.openOnDevice",
813+
"group": "pymakr",
814+
"when": "resourceScheme == file"
795815
}
796816
],
797817
"view/item/context": [
@@ -928,7 +948,7 @@
928948
"cheap-watch": "^1.0.4",
929949
"consolite": "^0.3.8",
930950
"hookar": "^0.0.7-0",
931-
"micropython-ctl-cont": "^1.14.3",
951+
"micropython-ctl-cont": "^1.15.3",
932952
"picomatch": "^2.3.1",
933953
"prompts": "^2.4.2",
934954
"serialport": "^10.4.0"

pymakr.schema.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,25 @@
7171
"type": "boolean",
7272
"default": false
7373
},
74+
"uploadOnDevStart": {
75+
"description": "Uploads project to device when dev mode is started.",
76+
"type": "string",
77+
"default": "outOfSync",
78+
"anyOf": [
79+
{
80+
"const": "always",
81+
"description": "Will upload the project to all connected devices."
82+
},
83+
{
84+
"const": "never",
85+
"description": "Will not upload project to any devices."
86+
},
87+
{
88+
"const": "outOfSync",
89+
"description": "Will upload the project to any connected device that is detected to be out of sync."
90+
}
91+
]
92+
},
7493
"onUpdate": {
7594
"description": "Action to run after file changes have been propagates",
7695
"type": "string",

src/Device.js

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ class Device {
118118
subscribe(() => this.onChanged());
119119

120120
this.busy.subscribe((val) => this.log.debugShort(val ? "busy..." : "idle."));
121+
this.busy.subscribe(
122+
(isBusy) =>
123+
!isBusy &&
124+
this.adapter.__proxyMeta.isBusy &&
125+
this.log.error(
126+
"Device is not busy, but the adapter queue is active. Current task:",
127+
[...this.adapter.__proxyMeta.history].pop()
128+
)
129+
);
121130

122131
this.readUntil = createReadUntil();
123132
this.readUntil(/\n>>> [^\n]*$/, (matches) => this.busy.set(!matches), { callOnFalse: true });
@@ -187,7 +196,7 @@ class Device {
187196
}
188197
});
189198
// resetting the device should also reset the waiting calls
190-
this.adapter.__proxyMeta.reset();
199+
[...this.adapter.__proxyMeta.history].pop().promise.finally(() => this.adapter.__proxyMeta.reset());
191200
this.log.info("send \\x06 (safeboot)");
192201
this.adapter.sendData("\x06"); // safeboot
193202
});
@@ -231,7 +240,7 @@ class Device {
231240
*/
232241
async runScript(script, options) {
233242
options = Object.assign({}, runScriptDefaults, options);
234-
this.log.debug(`runScript:\n\n${script}\n\n`);
243+
this.log.debug(`queued runScript:\n\n${script}\n\n`);
235244
this.busy.set(true);
236245
const start = Date.now();
237246
const result = await this.adapter.runScript(script + "\n\r\n\r\n", options);
@@ -244,6 +253,17 @@ class Device {
244253
return result;
245254
}
246255

256+
/**
257+
* Run a Python user script on this device
258+
* Running user scripts sets device.action to null. This ensures that they're shown correctly in the GUI.
259+
* @param {string} script
260+
* @param {import("micropython-ctl-cont").RunScriptOptions=} options
261+
*/
262+
async runUserScript(script, options) {
263+
script = "# user script\n" + script;
264+
this.runScript(script, options);
265+
}
266+
247267
/**
248268
* Creates a MicroPythonDevice
249269
*/
@@ -252,10 +272,11 @@ class Device {
252272

253273
// We need to wrap the rawAdapter in a blocking proxy to make sure commands
254274
// run in sequence rather in in parallel. See JSDoc comment for more info.
255-
const adapter = createBlockingProxy(rawAdapter, { exceptions: ["sendData", "connectSerial"] });
275+
const adapter = createBlockingProxy(rawAdapter, { exceptions: ["sendData", "connectSerial", "getState"] });
256276
adapter.__proxyMeta.beforeEachCall(({ item }) => {
257277
this.action.set(item.field.toString());
258278
this.busy.set(true);
279+
if (item.field.toString() === "runScript" && item.args[0].startsWith("# user script\n")) this.action.set(null);
259280
});
260281

261282
// emit line break to trigger a `>>>`. This triggers the `busyStatusUpdater`
@@ -382,8 +403,8 @@ class Device {
382403
if (!this.config.rootPath) this.config = { ...this.config, rootPath: await this.getRootPath() };
383404
await this.updatePymakrConf();
384405
this.state.stale = false;
385-
this.busy.set(false);
386-
resolve();
406+
// busy could be truish as vscode could be trying to access a file
407+
this.busy.when(false).then(resolve);
387408
}
388409
});
389410

@@ -423,20 +444,26 @@ class Device {
423444
}
424445

425446
/**
426-
* @param {number} retries
447+
*
448+
* @param {number} safeBootAfterNumRetries attempt to safe boot on each retry after n failed ctrl + c attempts
449+
* @param {number} retries how many times to attempt to send ctrl + c and ctrl + f
450+
* @param {number} retryInterval how long to wait between each retry
451+
* @returns
427452
*/
428-
stopScript(retries = 10, retryInterval = 500) {
429-
this.log.debug("stop script");
453+
stopScript(safeBootAfterNumRetries = 2, retries = 10, retryInterval = 500) {
454+
this.log.debug("stop script", { safeBootAfterNumRetries, retries, retryInterval });
430455
return new Promise((resolve, reject) => {
431456
if (this.busy.get()) {
432457
let counter = 0;
433458
const intervalHandle = setInterval(() => {
434459
if (counter >= retries) {
435460
clearInterval(intervalHandle);
461+
this.log.debug("ctrl + c failed. Attempting soft reboot (ctrl + f)");
436462
reject(`timed out after ${retries} retries in ${(retries * retryInterval) / 1000}s`);
437463
} else {
438464
counter++;
439-
this.adapter.sendData("\x03");
465+
const cmd = counter > safeBootAfterNumRetries ? "\x06\x03" : "\x03";
466+
this.adapter.sendData(cmd);
440467
this.log.log(`retry stop script (${counter})`);
441468
}
442469
}, retryInterval);
@@ -447,9 +474,9 @@ class Device {
447474
clearInterval(intervalHandle);
448475
}
449476
});
450-
this.adapter.sendData("\x03");
477+
const cmd = safeBootAfterNumRetries == 0 ? "\x06\x03" : "\x03";
478+
this.adapter.sendData(cmd);
451479
} else {
452-
this.adapter.sendData("\x03");
453480
resolve();
454481
}
455482
});
@@ -475,10 +502,14 @@ class Device {
475502
* @param {{
476503
* onScanComplete: (files: string[]) => void,
477504
* onUpload: (file: string) => void,
505+
* transform: (id: string, body: string) => string
478506
* }} options
479507
*/
480508
async upload(source, destination, options) {
481-
destination = posix.join(this.config.rootPath, `/${destination}`.replace(/\/+/g, "/"));
509+
this.log.debug("upload", source, "to", destination);
510+
destination = posix.join(this.config.rootPath, destination.replace(/[\/\\]+/g, "/"));
511+
await this.adapter.mkdirs(dirname(destination));
512+
482513
const root = source;
483514
const ignores = [...this.pymakr.config.get().get("ignore")];
484515
const pymakrConfig = getNearestPymakrConfig(source);
@@ -491,7 +522,11 @@ class Device {
491522

492523
const _uploadFile = async (file, destination) => {
493524
this.log.traceShort("uploadFile", file, "to", destination);
494-
const data = Buffer.from(readFileSync(file));
525+
526+
const data = options.transform
527+
? Buffer.from(options.transform(file, readFileSync(file, "utf-8")))
528+
: Buffer.from(readFileSync(file));
529+
495530
return this.adapter.putFile(destination, data, { checkIfSimilarBeforeUpload: true });
496531
};
497532

src/Project.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Project {
3737
this.err = `Could not parse config: ${this.configFile.fsPath}`;
3838
this.pymakr.log.error("could not parse config:", this.configFile.fsPath);
3939
this.pymakr.notifier.notifications.couldNotParsePymakrConfig(this);
40+
return {};
4041
}
4142
}
4243

0 commit comments

Comments
 (0)