Skip to content

Commit e8bf643

Browse files
feat: option to simulate machine.[deep]sleep in devmode (wip / experimental)
1 parent 903b556 commit e8bf643

File tree

4 files changed

+99
-26
lines changed

4 files changed

+99
-26
lines changed

src/Watcher/DeviceManager.js

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
const { removeOverlappingInstructions } = require("./utils");
1+
const { scripts } = require("./scripts");
2+
const { removeOverlappingInstructions, fakeDeepSleep } = require("./utils");
23

34
/**
45
* @typedef {'change'|'create'|'delete'} FileAction
@@ -19,6 +20,7 @@ class DeviceManager {
1920
this.project = this.watcher.project;
2021
this.pymakr = this.project.pymakr;
2122
this.log = watcher.log.createChild(device.name);
23+
this.bootPyIsOutdated = true;
2224

2325
/** @type {FileInstruction[]} */
2426
this.fileInstructions = [];
@@ -35,6 +37,31 @@ class DeviceManager {
3537
return uploadWhen === "always" || (uploadWhen === "outOfSync" && this.outOfSync);
3638
}
3739

40+
async ensureBootPy() {
41+
const prependStr = [
42+
"# EDIT BY PYMAKR DEV",
43+
"# The below script is used by Pymakr in dev mode",
44+
"# To remove it, disable dev mode and reupload the project.",
45+
"",
46+
scripts.importFakeMachine(),
47+
"",
48+
"# END OF EDIT BY PYMAKR DEV",
49+
"",
50+
].join("\n");
51+
52+
// todo pseudo code
53+
const files = await this.device.adapter.listFiles();
54+
if (!files.map((file) => file.filename).includes("boot.py"))
55+
await this.device.adapter.putFile("boot.py", Buffer.from(prependStr));
56+
else {
57+
const content = (await this.device.adapter.getFile("boot.py")).toString();
58+
59+
if (!content.match(prependStr)) {
60+
this.device.adapter.putFile("boot.py", Buffer.from(prependStr + content));
61+
}
62+
}
63+
}
64+
3865
async uploadProjectIfNeeded() {
3966
if (!this.device.adapter.__proxyMeta.target.isConnected()) return;
4067

@@ -44,6 +71,11 @@ class DeviceManager {
4471
await this.device.pymakr.commands.uploadProject({ device: this.device, project: this.project });
4572
}
4673

74+
async uploadPymakrDev() {
75+
console.log("uploading pymakr dev");
76+
return this.pymakr.commands.upload({ fsPath: __dirname + "/_pymakr_dev" }, this.device, "_pymakr_dev");
77+
}
78+
4779
/**
4880
* Send a change/create/delete file instruction to the device
4981
* @param {FileInstruction} fileInstruction
@@ -67,14 +99,26 @@ class DeviceManager {
6799
if (this.fileInstructions.length) await this.handleNewInstructions();
68100
}
69101

102+
async installDevTools() {
103+
await this.device.runScript('print("[dev] uploading Pymakr devtools")');
104+
await this.uploadPymakrDev();
105+
await this.device.runScript('print("[dev] patching boot.dev")');
106+
await this.ensureBootPy();
107+
this.bootPyIsOutdated = false;
108+
}
109+
110+
shouldInstallDevTools() {
111+
return this.project.config.dev?.simulateDeepSleep && this.bootPyIsOutdated;
112+
}
113+
70114
/**
71115
* Stops the current running script, performs file changes and restarts the device or main script
72116
*/
73117
async updateAndRestart() {
74118
const modulesToDelete = ["main.py"];
75119

76120
this.log.debug("stop script");
77-
await this.device.pymakr.commands.stopScript({ device: this.device });
121+
await this.device.pymakr.commands.stopScript({ device: this.device }, 0);
78122

79123
this.log.debug("run instructions");
80124
// Loop to make sure we get all instructions before we reset.
@@ -84,12 +128,14 @@ class DeviceManager {
84128
this.fileInstructions.length = 0;
85129
for (const instruction of instructions) modulesToDelete.push(await this.runInstruction(instruction));
86130
}
87-
console.log("copying", __dirname + "/fakemachine.py");
88-
await this.pymakr.commands.upload({ fsPath: __dirname + "/fakemachine.py" }, this.device, "fakemachine.py");
89131

90-
/** @type {'restartScript'|'softRestartDevice'|'hardRestartDevice'} */
91-
const onUpdate = this.project.config?.dev?.onUpdate || "restartScript";
92-
await this[onUpdate](modulesToDelete);
132+
const installDevTools = this.shouldInstallDevTools();
133+
134+
/** @type {'restartScript'|'softRestartDevice'|'hardRestartDevice'|'installDevToolsAndRestart'} */
135+
const restartScript = installDevTools ? "hardRestartDevice" : this.project.config.dev?.onUpdate || "restartScript";
136+
137+
if (installDevTools) await this.installDevTools();
138+
await this[restartScript](modulesToDelete);
93139

94140
await new Promise((resolve) => setTimeout(resolve, 500));
95141
}
@@ -116,15 +162,14 @@ class DeviceManager {
116162
*/
117163
async runInstruction({ file, action }) {
118164
this.log.debug("run instruction", { file, action });
119-
const target = require("path").relative(this.watcher.project.folder, file).replace(/\\/g, "/");
120-
121-
// // todo remove promise
122-
// await new Promise((resolve) => setTimeout(resolve, 100));
123-
if (action === "delete") {
124-
await this.device.remove(target);
125-
} else {
126-
await this.pymakr.commands.upload({ fsPath: file }, this.device, target);
165+
const target = require("path").relative(this.project.folder, file).replace(/\\/g, "/");
166+
if (target === "boot.py") {
167+
this.bootPyIsOutdated = true;
127168
}
169+
170+
if (action === "delete") await this.device.remove(target);
171+
else await this.pymakr.commands.upload({ fsPath: file }, this.device, target, fakeDeepSleep);
172+
128173
return target;
129174
}
130175
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import time
2+
import machine
3+
4+
5+
def sleep(msTime):
6+
print('fake_machine.sleep start')
7+
time.sleep(msTime/1000)
8+
print('fake_machine.sleep end')
9+
# send an exit rawRepl command
10+
print('\x04\x04>')
11+
time.sleep(0.1)
12+
machine.reset()
13+
14+
15+
def deepSleep(msTime):
16+
print('fake_machine.deepSleep start')
17+
time.sleep(msTime/1000)
18+
print('fake_machine.deepSleep end')
19+
# send an exit rawRepl command
20+
print('\x04\x04>')
21+
time.sleep(0.1)
22+
machine.reset()

src/Watcher/scripts.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
const rmDoubleSlash = (str) => str.replace(/\/+/, "/");
22

33
const scripts = {
4-
addPymakrToPath: (rootPath) =>
4+
importFakeMachine: () =>
55
[
6+
'print("[dev] Importing fake_machine")',
67
"import sys",
7-
"try:",
8-
` sys.path.index('${rmDoubleSlash(rootPath + "/_pymakr_dev")}')`,
9-
"except Exception as e:",
10-
" if(e.args[0] == 'object not in sequence'):",
11-
` sys.path.append('${rmDoubleSlash(rootPath + "/_pymakr_dev")}')`,
12-
" else:",
13-
" raise",
8+
'sys.path.append("/flash/_pymakr_dev")',
9+
"import fake_machine",
1410
].join("\r\n"),
11+
checkForSysPath: (rootPath) =>
12+
["import sys", ` sys.path.index('${rmDoubleSlash(rootPath + "/_pymakr_dev")}')`].join("\r\n"),
1513
restart: (modulesToDelete) =>
1614
[
1715
"print('')",

src/Watcher/utils.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
const removeOverlappingInstructions = (fileInstructions) => {
66
let pool = [...fileInstructions];
7-
const newInstructions = []
7+
const newInstructions = [];
88

99
while (pool.length) {
1010
const lastInstruction = pool.pop();
@@ -13,7 +13,15 @@ const removeOverlappingInstructions = (fileInstructions) => {
1313
pool = pool.filter(({ file }) => !file.startsWith(lastInstruction.file));
1414
if (!shouldRemoveSelf) newInstructions.unshift(lastInstruction);
1515
}
16-
return newInstructions
16+
return newInstructions;
1717
};
1818

19-
module.exports = { removeOverlappingInstructions };
19+
const fakeDeepSleep = (id, str) =>
20+
str
21+
.replace(/machine\.sleep/gm, "fake_machine.sleep")
22+
.replace(/machine\.deepSleep/gm, "fake_machine.sleep")
23+
.replace(/import machine/gm, "import fake_machine")
24+
.replace(/from machine import sleep/gm, "from fake_machine import sleep")
25+
.replace(/from machine import deepSleep/gm, "from fake_machine import deepSleep");
26+
27+
module.exports = { removeOverlappingInstructions, fakeDeepSleep };

0 commit comments

Comments
 (0)