@em-foundation/emscope 25.1.0-rc.0 → 25.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
- ## VERSION-25-0.2
1
+ ## VERSION-25-2.0
2
+
3
+ * new `emscope grab -p, --ppk-supply` option
4
+ * new `emscope scan --refresh` option
5
+ * adds capture_time to generated .jls file
6
+ * minor bug fixes
7
+ * works with Joulescope UI 1.3.9
8
+
9
+ ## VERSION-25-1.0
2
10
 
3
11
  * uses `BlueJoule` repo as exemplar
4
12
  * updated `README` documentation
package/dist/emscope CHANGED
@@ -12323,6 +12323,7 @@ var Capture = class _Capture {
12323
12323
  "duration",
12324
12324
  "sampling_rate",
12325
12325
  "sample_count",
12326
+ "version",
12326
12327
  "voltage"
12327
12328
  ];
12328
12329
  static #SAVE_KEYS = [
@@ -12335,6 +12336,7 @@ var Capture = class _Capture {
12335
12336
  cap._rootdir = rootdir;
12336
12337
  cap._duration = duration;
12337
12338
  cap._device = device;
12339
+ cap._version = version();
12338
12340
  cap._voltage = voltage;
12339
12341
  cap._creation_date = /* @__PURE__ */ new Date();
12340
12342
  cap._sampling_rate = _Capture.#SAMPLING_RATE.get(device) ?? 0;
@@ -12686,10 +12688,28 @@ ${dpath}:`);
12686
12688
  // src/Detecter.ts
12687
12689
  function exec(opts) {
12688
12690
  const cap = Capture.load(opts.capture);
12689
- const aobj = analyze(cap, opts.trim, opts.gap, opts.minDuration, opts.minEnergy);
12691
+ let params = {};
12692
+ if (opts.refresh) {
12693
+ if (cap.analysis) {
12694
+ for (const opt of cap.analysis?.options) {
12695
+ const a = opt.split(" ");
12696
+ const [pname, pval] = [a[0].slice(2), Number(a[1])];
12697
+ params[pname] = Number(pval);
12698
+ }
12699
+ if (Number.isNaN(params.trim)) {
12700
+ params.trim = cap.analysis.events.length;
12701
+ }
12702
+ }
12703
+ } else {
12704
+ params.gap = opts.gap;
12705
+ params.min_dur = opts.minDuration;
12706
+ params.min_egy = opts.minEnergy;
12707
+ params.trim = opts.trim;
12708
+ }
12709
+ const aobj = analyze(cap, params);
12690
12710
  cap.bind(aobj);
12691
12711
  }
12692
- function analyze(cap, trim, gap, min_dur, min_egy) {
12712
+ function analyze(cap, params = {}) {
12693
12713
  infoMsg("analyzing captured data...");
12694
12714
  const rsig = cap.current_sig;
12695
12715
  const width = rsig.secsToOff(25e-5);
@@ -12717,26 +12737,26 @@ function analyze(cap, trim, gap, min_dur, min_egy) {
12717
12737
  }
12718
12738
  }
12719
12739
  let options = new Array();
12720
- if (gap !== void 0) {
12721
- markers = combineMarkers(rsig, markers, rsig.secsToOff(gap / 1e3));
12722
- options.push(`--gap ${gap}`);
12740
+ if (params.gap !== void 0) {
12741
+ markers = combineMarkers(rsig, markers, rsig.secsToOff(params.gap / 1e3));
12742
+ options.push(`--gap ${params.gap}`);
12723
12743
  }
12724
- if (min_dur != void 0) {
12725
- const min_wid = rsig.secsToOff(min_dur / 1e3);
12744
+ if (params.min_dur != void 0) {
12745
+ const min_wid = rsig.secsToOff(params.min_dur / 1e3);
12726
12746
  markers = markers.filter((m) => m.width >= min_wid);
12727
- options.push(`--min-duration ${min_dur}`);
12747
+ options.push(`--min-duration ${params.min_dur}`);
12728
12748
  }
12729
- if (min_egy != void 0) {
12730
- markers = markers.filter((m) => cap.energyWithin(m) >= min_egy / 1e6);
12731
- options.push(`--min-energy ${min_egy}`);
12749
+ if (params.min_egy != void 0) {
12750
+ markers = markers.filter((m) => cap.energyWithin(m) >= params.min_egy / 1e6);
12751
+ options.push(`--min-energy ${params.min_egy}`);
12732
12752
  }
12733
12753
  let span = rsig.window(rsig.data.length).toMarker();
12734
- if (trim) {
12735
- [span, markers] = trimEvents(cap, markers, trim);
12736
- options.push(`--trim ${trim}`);
12754
+ if (params.trim) {
12755
+ [span, markers] = trimEvents(cap, markers, params.trim);
12756
+ options.push(`--trim ${params.trim}`);
12737
12757
  }
12738
12758
  infoMsg(`found ${markers.length} event(s)`);
12739
- return { span, events: markers, sleep: si, options };
12759
+ return { span, events: markers, sleep: si, options, version: version() };
12740
12760
  }
12741
12761
  function combineMarkers(sig, markers, gap) {
12742
12762
  let res = new Array();
@@ -12843,7 +12863,8 @@ ${block}
12843
12863
  Fs3.writeFileSync(file, out);
12844
12864
  }
12845
12865
  function mkGen(cap) {
12846
- const aobj = cap.analysis ?? analyze(cap);
12866
+ fail(`no prior analysis: run 'emscope scan ...'`, cap.analysis === void 0);
12867
+ const aobj = cap.analysis;
12847
12868
  const si = aobj.sleep;
12848
12869
  const sl_v = cap.avg_voltage;
12849
12870
  const sl_avg = aobj.sleep.avg;
@@ -12911,6 +12932,16 @@ function deflateLfs(repo, gpath) {
12911
12932
  import_child_process.default.execFileSync("git", ["lfs", "pull", "--include", gpath], { cwd: repo, stdio: "inherit" });
12912
12933
  import_child_process.default.execFileSync("git", ["lfs", "checkout", gpath], { cwd: repo, stdio: "inherit" });
12913
12934
  }
12935
+ function fetchOid(repo, gpath) {
12936
+ let res = "";
12937
+ try {
12938
+ const out = import_child_process.default.execFileSync("git", ["show", gpath], { cwd: repo, stdio: ["ignore", "pipe", "ignore"] }).toString();
12939
+ const m = out.match(/^\s*oid sha256:([0-9a-f]{64})/m);
12940
+ if (m) res = m[1];
12941
+ } catch {
12942
+ }
12943
+ return res;
12944
+ }
12914
12945
  function findRepoDir(capdir) {
12915
12946
  let repo = "";
12916
12947
  let dir = capdir;
@@ -12951,6 +12982,12 @@ function toggleLfs(capdir, opts) {
12951
12982
  import_fs2.default.rmSync(Capture.workdir(capdir), { recursive: true });
12952
12983
  return;
12953
12984
  }
12985
+ const oid = fetchOid(repo, `:${gpath}`);
12986
+ fail(`'emscope-capture.zip' not yet committed`, !oid);
12987
+ const oid_head = fetchOid(repo, `HEAD:${gpath}`);
12988
+ if (oid_head && oid_head != oid) {
12989
+ restoreLfs(repo, gpath);
12990
+ }
12954
12991
  if (desc_flag) {
12955
12992
  deflateLfs(repo, gpath);
12956
12993
  }
@@ -13027,10 +13064,6 @@ var DATALOSS_THRESHOLD = 500;
13027
13064
  var getMaskedValue = (value, { mask, pos }) => (value & mask) >> pos;
13028
13065
  async function execCapture2(opts) {
13029
13066
  const path_list = await findDevices();
13030
- if (path_list.length == 0) {
13031
- console.error("*** no PPK2 analyzer");
13032
- process.exit(1);
13033
- }
13034
13067
  const progress = new Progress("capturing: ");
13035
13068
  const port = new import_serialport.SerialPort({ path: path_list[0], baudRate: 9600, autoOpen: false });
13036
13069
  await new Promise((resolve, reject) => {
@@ -13040,7 +13073,7 @@ async function execCapture2(opts) {
13040
13073
  port.on("data", () => {
13041
13074
  });
13042
13075
  const cap = Capture.create(opts.capture, opts.duration, "PPK2", opts.voltage);
13043
- const ppk = new PPK2(port, cap);
13076
+ const ppk = new PPK2(port);
13044
13077
  await ppk.getModifiers();
13045
13078
  if (opts.ampereMode) {
13046
13079
  ppk.setMode("ampere");
@@ -13049,11 +13082,26 @@ async function execCapture2(opts) {
13049
13082
  ppk.setSourceVoltage(cap.voltage * 1e3);
13050
13083
  }
13051
13084
  ppk.togglePower("on");
13052
- await ppk.capture(progress);
13085
+ await ppk.record(cap, progress);
13053
13086
  ppk.togglePower("off");
13054
13087
  ppk.close();
13055
13088
  return cap;
13056
13089
  }
13090
+ async function powerOn(voltage) {
13091
+ const path_list = await findDevices();
13092
+ const progress = new Progress("powering: ");
13093
+ const port = new import_serialport.SerialPort({ path: path_list[0], baudRate: 9600, autoOpen: false });
13094
+ await new Promise((resolve, reject) => {
13095
+ port.open((err) => err ? reject(err) : resolve());
13096
+ });
13097
+ await progress.spin(2500);
13098
+ const ppk = new PPK2(port);
13099
+ await ppk.getModifiers();
13100
+ ppk.setMode("source");
13101
+ ppk.setSourceVoltage(voltage * 1e3);
13102
+ ppk.togglePower("on");
13103
+ progress.done();
13104
+ }
13057
13105
  async function findDevices() {
13058
13106
  let res = new Array();
13059
13107
  for (const port of await import_serialport.SerialPort.list()) {
@@ -13063,6 +13111,7 @@ async function findDevices() {
13063
13111
  res.push(port.path);
13064
13112
  }
13065
13113
  }
13114
+ fail("no PPK2 analyzer found", res.length == 0);
13066
13115
  return res;
13067
13116
  }
13068
13117
  function parseMods(mods) {
@@ -13077,7 +13126,6 @@ function parseMods(mods) {
13077
13126
  return res;
13078
13127
  }
13079
13128
  var PPK2 = class {
13080
- #cap;
13081
13129
  #port;
13082
13130
  #modifiers = {
13083
13131
  r: [1031.64, 101.65, 10.15, 0.94, 0.043],
@@ -13103,15 +13151,14 @@ var PPK2 = class {
13103
13151
  #afterSpike;
13104
13152
  #consecutiveRangeSample;
13105
13153
  #currentVdd = 0;
13106
- constructor(port, cap) {
13107
- this.#cap = cap;
13154
+ constructor(port) {
13108
13155
  this.#port = port;
13109
13156
  }
13110
- async capture(progress) {
13157
+ async record(cap, progress) {
13111
13158
  await progress.spin(2e3);
13112
13159
  this.startMeasurement();
13113
13160
  return await new Promise((resolve) => {
13114
- const raw = Buffer.allocUnsafe(this.#cap.duration * 1e5 * 4);
13161
+ const raw = Buffer.allocUnsafe(cap.duration * 1e5 * 4);
13115
13162
  let offset = 0;
13116
13163
  this.#port.on("data", (buf) => {
13117
13164
  progress.update(`${(offset / 1e5 / 4).toFixed(3)} s`);
@@ -13124,7 +13171,7 @@ var PPK2 = class {
13124
13171
  });
13125
13172
  this.stopMeasurement();
13126
13173
  for (const adcVal of new Uint32Array(raw.buffer, raw.byteOffset, raw.length / 4)) {
13127
- this.#cap.current_ds.add(this.#handleRawDataSet(adcVal) / 1e6);
13174
+ cap.current_ds.add(this.#handleRawDataSet(adcVal) / 1e6);
13128
13175
  }
13129
13176
  resolve();
13130
13177
  }
@@ -13269,6 +13316,10 @@ var PPK2 = class {
13269
13316
  async function exec3(opts) {
13270
13317
  let c;
13271
13318
  if (opts.js220) {
13319
+ if (opts.ppk2Supply) {
13320
+ const voltage = opts.ppk2Supply === true ? 3.3 : Number(opts.ppk2Supply);
13321
+ await powerOn(voltage);
13322
+ }
13272
13323
  c = await execCapture(opts);
13273
13324
  } else if (opts.ppk2) {
13274
13325
  c = await execCapture2(opts);
@@ -13290,7 +13341,7 @@ function saveSignal(cap, cname, span, markers = []) {
13290
13341
  const msig_I = cap.current_sig.window(span.width, span.offset).toSignal();
13291
13342
  const msig_V = cap.voltage_sig.window(span.width, span.offset).toSignal();
13292
13343
  const data_V = msig_V.data.length > 0 ? msig_V.data : new Float32Array(msig_I.data.length).fill(cap.avg_voltage);
13293
- writer.store(msig_I.data, data_V, msig_I.sample_rate);
13344
+ writer.store(msig_I.data, data_V, msig_I.sample_rate, Math.trunc(cap.creation_date.getTime() / 1e3));
13294
13345
  let cobj = {
13295
13346
  id: "joulescope.ui.waveform_widget",
13296
13347
  version: "1.0",
@@ -13346,7 +13397,8 @@ var WriterAux = class _WriterAux {
13346
13397
  this.#jfile.close();
13347
13398
  infoMsg(`wrote '${this.cname}.jls'`);
13348
13399
  }
13349
- store(f32_I, f32_V, sample_rate) {
13400
+ store(f32_I, f32_V, sample_rate, utc) {
13401
+ utc = Date.now() / 1e3;
13350
13402
  const sdef = {
13351
13403
  source_id: 1,
13352
13404
  name: "mysource",
@@ -13374,11 +13426,13 @@ var WriterAux = class _WriterAux {
13374
13426
  };
13375
13427
  this.#jfile.signalDef(sigdef);
13376
13428
  this.#jfile.writeF32(1, f32_I);
13429
+ this.#jfile.setTime(1, utc);
13377
13430
  sigdef.signal_id = 2;
13378
13431
  sigdef.name = "voltage";
13379
13432
  sigdef.units = "V";
13380
13433
  this.#jfile.signalDef(sigdef);
13381
13434
  this.#jfile.writeF32(2, f32_V);
13435
+ this.#jfile.setTime(2, utc);
13382
13436
  const f32_W = new Float32Array(f32_I.length);
13383
13437
  for (let i = 0; i < f32_W.length; i++) {
13384
13438
  f32_W[i] = f32_I[i] * f32_V[i];
@@ -13388,6 +13442,7 @@ var WriterAux = class _WriterAux {
13388
13442
  sigdef.units = "W";
13389
13443
  this.#jfile.signalDef(sigdef);
13390
13444
  this.#jfile.writeF32(3, f32_W);
13445
+ this.#jfile.setTime(3, utc);
13391
13446
  }
13392
13447
  };
13393
13448
 
@@ -13397,7 +13452,8 @@ var import_os = __toESM(require("os"));
13397
13452
  var import_path3 = __toESM(require("path"));
13398
13453
  function exec4(opts) {
13399
13454
  const cap = Capture.load(opts.capture);
13400
- const aobj = cap.analysis ?? analyze(cap);
13455
+ fail(`no prior analysis: run 'emscope scan ...'`, cap.analysis === void 0);
13456
+ const aobj = cap.analysis;
13401
13457
  if (opts.eventInfo) {
13402
13458
  printEventInfo(cap, aobj.events);
13403
13459
  return;
@@ -13509,8 +13565,8 @@ var {
13509
13565
  var CAP = ["-c --capture <directory path>", "working capture directory", "."];
13510
13566
  var VERS = version();
13511
13567
  var CMD = new Command("emscope").option("-C, --capture-glob [name pattern]", `apply this command to each matching child capture directory (default "**")`).version(VERS);
13512
- CMD.command("grab").description("record power signals with an attached capture device").option(CAP[0], CAP[1], CAP[2]).option("-d --duration <value>", "capture duration in seconds", parseFloat, 3).option("-J --js220", "use a Joulescope JS220 device").option("-P --ppk2", "use a Nordic PPK2 device").addOption(new Option("-A --ampere-mode", "enable PPK ampere mode").conflicts(["sourceMode", "js220"])).addOption(new Option("-S --source-mode", "enable PPK source mode").default(true).conflicts(["ampereMode", "js220"])).addOption(new Option("-v, --voltage [value]", "source voltage").argParser(parseFloat).default(3.3).conflicts("js220")).action((opts, cmd) => execCmd(exec3, opts, cmd.parent.opts()));
13513
- CMD.command("scan").description("analyze captured data and locate active events").option(CAP[0], CAP[1], CAP[2]).option("-d, --min-duration <milliseconds>", "remove events whose duration is under a threshold", parseFloat).option("-e, --min-energy <microJoules>", "remove events whose energy is under a threshold", parseFloat).option("-g, --gap <milliseconds>", "combine adjacent events whose gap is under a threshold", parseFloat).option("-t --trim <event count>", "remove extra events", parseFloat).action((opts, cmd) => execCmd(exec, opts, cmd.parent.opts()));
13568
+ CMD.command("grab").description("record power signals with an attached capture device").option(CAP[0], CAP[1], CAP[2]).option("-d --duration <value>", "capture duration in seconds", parseFloat, 3).option("-J --js220", "use a Joulescope JS220 device").option("-P --ppk2", "use a Nordic PPK2 device").addOption(new Option("-A --ampere-mode", "enable PPK ampere mode").conflicts(["sourceMode", "js220"])).addOption(new Option("-S --source-mode", "enable PPK source mode").default(true).conflicts(["ampereMode", "js220"])).addOption(new Option("-p, --ppk2-supply [voltage]", "supply JS220 voltage from a PPK").argParser(parseFloat).conflicts("ppk2")).addOption(new Option("-v, --voltage [value]", "source voltage").argParser(parseFloat).default(3.3).conflicts("js220")).action((opts, cmd) => execCmd(exec3, opts, cmd.parent.opts()));
13569
+ CMD.command("scan").description("analyze captured data and locate active events").option(CAP[0], CAP[1], CAP[2]).option("-d, --min-duration <milliseconds>", "remove events whose duration is under a threshold", parseFloat).option("-e, --min-energy <microJoules>", "remove events whose energy is under a threshold", parseFloat).option("-g, --gap <milliseconds>", "combine adjacent events whose gap is under a threshold", parseFloat).option("-t --trim <event count>", "remove extra events", parseFloat).option("--refresh", "(re-)scan using the last set of options").action((opts, cmd) => execCmd(exec, opts, cmd.parent.opts()));
13514
13570
  CMD.command("view").description("present captured data in different formats").option(CAP[0], CAP[1], CAP[2]).option("-e --event-info", "characterize power consumption when active").option("-j --jls-file [event ID]", "generate a Joulescope .jls file containing events").option("-s --sleep-info", "characterize power consumption when inactive").option("-w --what-if [event period]", `extrapolate results for a given event period (default: '00:00:01')`, parseHms).option("--score", "only print the EM\u2022eralds benchmark score").action((opts, cmd) => execCmd(exec4, opts, cmd.parent.opts()));
13515
13571
  CMD.command("pack").description(`bundle captured data into an 'emscope.zip' file`).option(CAP[0], CAP[1], CAP[2]).option("-a --about-file", `update the 'ABOUT.md' file only`).option("-s,--status", `status of the 'emscope.zip' file`).option("-u --unpack", `deflate the 'emscope.zip' file for local use`).option("-z --zip-file", `generate the 'emscope.zip' file`).option("--restore", `restores the 'emscope.zip' LFS descriptor (debug only)`).action((opts, cmd) => execCmd(exec2, opts, cmd.parent.opts()));
13516
13572
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@em-foundation/emscope",
3
- "version": "25.1.0-rc.0",
3
+ "version": "25.2.0",
4
4
  "description": "benchmark energy efficiency in resource-constrained embedded systems",
5
5
  "bin": {
6
6
  "emscope": "dist/emscope"
@@ -18,21 +18,28 @@
18
18
  "type": "git",
19
19
  "url": "git+https://github.com/em-foundation/emscope.git"
20
20
  },
21
- "keywords": [],
22
- "author": "",
21
+ "keywords": [
22
+ "openem",
23
+ "em-foundation",
24
+ "emscope",
25
+ "em",
26
+ "emfoundation",
27
+ "embedded"
28
+ ],
29
+ "author": "The EM Foundation",
23
30
  "license": "MIT",
24
31
  "preferGlobal": true,
25
32
  "bugs": {
26
33
  "url": "https://github.com/em-foundation/emscope/issues"
27
34
  },
28
- "homepage": "https://github.com/em-foundation/emscope/blob/main/docs/ReadMore.md",
35
+ "homepage": "https://github.com/em-foundation/emscope/blob/docs-stable/docs/ReadMore.md",
29
36
  "dependencies": {
30
37
  "@serialport/bindings-cpp": "^13.0.1",
31
38
  "@types/adm-zip": "^0.5.7",
32
39
  "@types/js-yaml": "^4.0.9",
33
40
  "adm-zip": "^0.5.16",
34
41
  "commander": "^14.0.0",
35
- "jls-writer": "https://github.com/em-foundation/npm-packages/releases/download/resources/jls-writer-0.0.2.tgz",
42
+ "jls-writer": "https://github.com/em-foundation/npm-packages/releases/download/resources/jls-writer-0.0.3.tgz",
36
43
  "joulescope_driver": "^1.10.0",
37
44
  "js-yaml": "^4.1.0",
38
45
  "picomatch": "^4.0.3",