@em-foundation/emscope 25.3.0 → 25.4.1

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,3 +1,12 @@
1
+ ## VERSION-25-4.1
2
+
3
+ * more robust port enumeration fix for PPK
4
+
5
+ ## VERSION-25-4.0
6
+
7
+ * added `--json` option
8
+ * port enumeration fix for PPK
9
+
1
10
  ## VERSION-25-3.0
2
11
 
3
12
  * aligned with new `BlueJoule/capture` directory structure
package/README.md CHANGED
@@ -1,4 +1,15 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/em-foundation/emscope/main/docs/images/splash.png"
3
- alt="EM•Scope" width="840">
4
- </p>
2
+ <img src="docs/images/logo.png" alt="EM•Scope Logo" width="400">
3
+ </p>
4
+ <br>
5
+ <p align="center">
6
+ <img src="docs/images/tagline.png" alt="EM•Scope TagLine" width="750">
7
+ </p>
8
+
9
+ -----
10
+
11
+ The **EM&bull;Scope** tool streamlines the capture, analysis, display, and delivery of real-time power-consumption measurements &ndash; used to characterize the overall energy efficiency of resource-constrained embedded systems.&thinsp; To encourage benchmarks for different HW/SW configurations performing comparable tasks, **EM&bull;Scope** introduces a novel metric for quantifying energy efficiency &ndash; the **EM&bull;erald**.
12
+
13
+ <p align="right">
14
+ <a href="https://github.com/em-foundation/emscope/blob/docs-stable/docs/ReadMore.md">read more<b>&nbsp;&#x27a6;</b></a>
15
+ </p>
package/dist/emscope CHANGED
@@ -13158,17 +13158,57 @@ async function powerOn(voltage) {
13158
13158
  progress.done();
13159
13159
  }
13160
13160
  async function findDevices() {
13161
- let res = new Array();
13161
+ let candidates = new Array();
13162
13162
  for (const port of await import_serialport.SerialPort.list()) {
13163
13163
  const vid = port.vendorId;
13164
13164
  const pid = port.productId;
13165
13165
  if (vid == "1915" && pid.toLowerCase() == "c00a") {
13166
- res.push(port.path);
13166
+ candidates.push(port.path);
13167
+ }
13168
+ }
13169
+ fail("no PPK2 analyzer found", candidates.length == 0);
13170
+ if (candidates.length == 1) return candidates;
13171
+ const res = new Array();
13172
+ for (const path of candidates) {
13173
+ if (await probeDataPort(path)) {
13174
+ res.push(path);
13167
13175
  }
13168
13176
  }
13169
- fail("no PPK2 analyzer found", res.length == 0);
13177
+ fail("no PPK2 data port found (tried probing all candidates)", res.length == 0);
13170
13178
  return res;
13171
13179
  }
13180
+ async function probeDataPort(path, timeoutMs = 500) {
13181
+ let port;
13182
+ try {
13183
+ port = new import_serialport.SerialPort({ path, baudRate: 9600, autoOpen: false });
13184
+ await new Promise((resolve, reject) => {
13185
+ port.open((err) => err ? reject(err) : resolve());
13186
+ });
13187
+ return await new Promise((resolve) => {
13188
+ let responded = false;
13189
+ const onData = () => {
13190
+ if (!responded) {
13191
+ responded = true;
13192
+ resolve(true);
13193
+ }
13194
+ };
13195
+ port.on("data", onData);
13196
+ port.write([25 /* GET_META_DATA */]);
13197
+ setTimeout(() => {
13198
+ if (!responded) {
13199
+ responded = true;
13200
+ resolve(false);
13201
+ }
13202
+ }, timeoutMs);
13203
+ });
13204
+ } catch {
13205
+ return false;
13206
+ } finally {
13207
+ if (port?.isOpen) {
13208
+ await new Promise((resolve) => port.close(() => resolve()));
13209
+ }
13210
+ }
13211
+ }
13172
13212
  function parseMods(mods) {
13173
13213
  mods = mods.replace("END", "").trim().toLowerCase().replace(/-nan/g, "null").replace(/\n/g, ',\n"').replace(/: /g, '": ');
13174
13214
  let res;
@@ -13509,8 +13549,9 @@ function exec4(opts) {
13509
13549
  const cap = Capture.load(opts.capture);
13510
13550
  fail(`no prior analysis: run 'emscope scan ...'`, cap.analysis === void 0);
13511
13551
  const aobj = cap.analysis;
13552
+ const json2 = !!opts.json;
13512
13553
  if (opts.eventInfo) {
13513
- printEventInfo(cap, aobj.events);
13554
+ json2 ? printEventInfoJson(cap, aobj.events) : printEventInfo(cap, aobj.events);
13514
13555
  return;
13515
13556
  }
13516
13557
  if (opts.jlsFile) {
@@ -13518,16 +13559,16 @@ function exec4(opts) {
13518
13559
  return;
13519
13560
  }
13520
13561
  if (opts.sleepInfo) {
13521
- printSleepInfo(cap, aobj.sleep);
13562
+ json2 ? printSleepInfoJson(cap, aobj.sleep) : printSleepInfo(cap, aobj.sleep);
13522
13563
  return;
13523
13564
  }
13524
13565
  if (opts.whatIf !== void 0) {
13525
13566
  const ev_rate = opts.whatIf === true ? 1 : opts.whatIf;
13526
- printResults(cap, aobj, ev_rate, opts.score);
13567
+ json2 ? printResultsJson(cap, aobj, ev_rate) : printResults(cap, aobj, ev_rate, opts.score);
13527
13568
  return;
13528
13569
  }
13529
13570
  if (opts.score) {
13530
- printResults(cap, aobj, 1, true);
13571
+ json2 ? printResultsJson(cap, aobj, 1) : printResults(cap, aobj, 1, true);
13531
13572
  return;
13532
13573
  }
13533
13574
  fail(`no options found: run 'emscope view -h'`);
@@ -13577,6 +13618,27 @@ function printEventInfo(cap, markers) {
13577
13618
  infoMsg("----");
13578
13619
  infoMsg(`average energy over ${markers.length} event(s): ${uJoules(avg)}`);
13579
13620
  }
13621
+ function printEventInfoJson(cap, markers) {
13622
+ const events = markers.map((m, i) => {
13623
+ const energy = cap.energyWithin(m);
13624
+ const duration = cap.current_sig.offToSecs(m.width);
13625
+ const time = cap.current_sig.offToSecs(m.offset);
13626
+ return {
13627
+ id: String.fromCharCode("A".charCodeAt(0) + i),
13628
+ time,
13629
+ energy,
13630
+ duration
13631
+ };
13632
+ });
13633
+ const totalEnergy = events.reduce((sum, e) => sum + e.energy, 0);
13634
+ const avgEnergy = events.length > 0 ? totalEnergy / events.length : 0;
13635
+ console.log(JSON.stringify({
13636
+ type: "event_info",
13637
+ eventCount: events.length,
13638
+ averageEnergy: avgEnergy,
13639
+ events
13640
+ }));
13641
+ }
13580
13642
  function printResults(cap, aobj, ev_rate, score_only) {
13581
13643
  const sleep_pwr = aobj.sleep.avg * cap.avg_voltage;
13582
13644
  score_only || infoMsg(`event period: ${secsToHms(ev_rate)}`);
@@ -13594,9 +13656,39 @@ function printResults(cap, aobj, ev_rate, score_only) {
13594
13656
  score_only || infoMsg("----");
13595
13657
  infoMsg(`${ems.toFixed(2)} EM\u2022eralds`);
13596
13658
  }
13659
+ function printResultsJson(cap, aobj, ev_rate) {
13660
+ const sleep_pwr = aobj.sleep.avg * cap.avg_voltage;
13661
+ const egy_1s = cap.energyWithin(aobj.span) / cap.current_sig.offToSecs(aobj.span.width);
13662
+ const egy_1e = egy_1s - sleep_pwr * 1;
13663
+ const egy_1c = sleep_pwr * ev_rate + egy_1e;
13664
+ const egy_1d = egy_1c * 86400 / ev_rate;
13665
+ const egy_1m = egy_1d * 30;
13666
+ const ems = 2400 / egy_1m;
13667
+ console.log(JSON.stringify({
13668
+ type: "score",
13669
+ emeralds: parseFloat(ems.toFixed(2)),
13670
+ cycleRate: ev_rate,
13671
+ sleepCurrent: aobj.sleep.avg,
13672
+ sleepPower: sleep_pwr,
13673
+ voltage: cap.avg_voltage,
13674
+ eventEnergy: egy_1e,
13675
+ energyPerCycle: egy_1c,
13676
+ energyPerDay: egy_1d,
13677
+ energyPerMonth: egy_1m
13678
+ }));
13679
+ }
13597
13680
  function printSleepInfo(cap, si) {
13598
13681
  infoMsg(`sleep current = ${uAmps(si.avg).trim()} @ ${cap.avg_voltage.toFixed(1)} V, standard deviation = ${uAmps(si.std).trim()}`);
13599
13682
  }
13683
+ function printSleepInfoJson(cap, si) {
13684
+ console.log(JSON.stringify({
13685
+ type: "sleep_info",
13686
+ sleepCurrent: si.avg,
13687
+ standardDeviation: si.std,
13688
+ voltage: cap.avg_voltage,
13689
+ sleepPower: si.avg * cap.avg_voltage
13690
+ }));
13691
+ }
13600
13692
 
13601
13693
  // node_modules/commander/esm.mjs
13602
13694
  var import_index = __toESM(require_commander(), 1);
@@ -13621,7 +13713,7 @@ var VERS = version();
13621
13713
  var CMD = new Command("emscope").option("-C, --capture-glob [name pattern]", `apply this command to each matching child capture directory (default "**")`).version(VERS);
13622
13714
  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()));
13623
13715
  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()));
13624
- 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()));
13716
+ 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").option("--json", "output results as JSON (machine-readable)").action((opts, cmd) => execCmd(exec4, opts, cmd.parent.opts()));
13625
13717
  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()));
13626
13718
  try {
13627
13719
  CMD.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@em-foundation/emscope",
3
- "version": "25.3.0",
3
+ "version": "25.4.1",
4
4
  "description": "benchmark energy efficiency in resource-constrained embedded systems",
5
5
  "bin": {
6
6
  "emscope": "dist/emscope"
@@ -29,6 +29,9 @@
29
29
  "author": "The EM Foundation",
30
30
  "license": "MIT",
31
31
  "preferGlobal": true,
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
32
35
  "bugs": {
33
36
  "url": "https://github.com/em-foundation/emscope/issues"
34
37
  },