@em-foundation/emscope 25.4.1 → 25.5.3

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,10 @@
1
+ ## VERSION-25.5.0
2
+
3
+ * support for Otii-3 analyzer and battery emulator
4
+ * new `emscope grab -O, --otii3` option
5
+ * new `emscope grab -B, --battery-profile <index>` option
6
+ * new `emscope --soc <percent>` option
7
+
1
8
  ## VERSION-25-4.1
2
9
 
3
10
  * more robust port enumeration fix for PPK
package/README.md CHANGED
@@ -1,15 +1,4 @@
1
1
  <p align="center">
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>
2
+ <img src="https://raw.githubusercontent.com/em-foundation/emscope/main/docs/images/splash.png"
3
+ alt="EM•Scope" width="840">
4
+ </p>
package/dist/emscope CHANGED
@@ -32,6 +32,7 @@ var require_constants = __commonJS({
32
32
  "use strict";
33
33
  var WIN_SLASH = "\\\\/";
34
34
  var WIN_NO_SLASH = `[^${WIN_SLASH}]`;
35
+ var DEFAULT_MAX_EXTGLOB_RECURSION = 0;
35
36
  var DOT_LITERAL = "\\.";
36
37
  var PLUS_LITERAL = "\\+";
37
38
  var QMARK_LITERAL = "\\?";
@@ -82,6 +83,7 @@ var require_constants = __commonJS({
82
83
  SEP: "\\"
83
84
  };
84
85
  var POSIX_REGEX_SOURCE = {
86
+ __proto__: null,
85
87
  alnum: "a-zA-Z0-9",
86
88
  alpha: "a-zA-Z",
87
89
  ascii: "\\x00-\\x7F",
@@ -98,6 +100,7 @@ var require_constants = __commonJS({
98
100
  xdigit: "A-Fa-f0-9"
99
101
  };
100
102
  module2.exports = {
103
+ DEFAULT_MAX_EXTGLOB_RECURSION,
101
104
  MAX_LENGTH: 1024 * 64,
102
105
  POSIX_REGEX_SOURCE,
103
106
  // regular expressions
@@ -648,6 +651,213 @@ var require_parse = __commonJS({
648
651
  var syntaxError = (type2, char) => {
649
652
  return `Missing ${type2}: "${char}" - use "\\\\${char}" to match literal characters`;
650
653
  };
654
+ var splitTopLevel = (input) => {
655
+ const parts = [];
656
+ let bracket = 0;
657
+ let paren = 0;
658
+ let quote = 0;
659
+ let value = "";
660
+ let escaped = false;
661
+ for (const ch of input) {
662
+ if (escaped === true) {
663
+ value += ch;
664
+ escaped = false;
665
+ continue;
666
+ }
667
+ if (ch === "\\") {
668
+ value += ch;
669
+ escaped = true;
670
+ continue;
671
+ }
672
+ if (ch === '"') {
673
+ quote = quote === 1 ? 0 : 1;
674
+ value += ch;
675
+ continue;
676
+ }
677
+ if (quote === 0) {
678
+ if (ch === "[") {
679
+ bracket++;
680
+ } else if (ch === "]" && bracket > 0) {
681
+ bracket--;
682
+ } else if (bracket === 0) {
683
+ if (ch === "(") {
684
+ paren++;
685
+ } else if (ch === ")" && paren > 0) {
686
+ paren--;
687
+ } else if (ch === "|" && paren === 0) {
688
+ parts.push(value);
689
+ value = "";
690
+ continue;
691
+ }
692
+ }
693
+ }
694
+ value += ch;
695
+ }
696
+ parts.push(value);
697
+ return parts;
698
+ };
699
+ var isPlainBranch = (branch) => {
700
+ let escaped = false;
701
+ for (const ch of branch) {
702
+ if (escaped === true) {
703
+ escaped = false;
704
+ continue;
705
+ }
706
+ if (ch === "\\") {
707
+ escaped = true;
708
+ continue;
709
+ }
710
+ if (/[?*+@!()[\]{}]/.test(ch)) {
711
+ return false;
712
+ }
713
+ }
714
+ return true;
715
+ };
716
+ var normalizeSimpleBranch = (branch) => {
717
+ let value = branch.trim();
718
+ let changed = true;
719
+ while (changed === true) {
720
+ changed = false;
721
+ if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
722
+ value = value.slice(2, -1);
723
+ changed = true;
724
+ }
725
+ }
726
+ if (!isPlainBranch(value)) {
727
+ return;
728
+ }
729
+ return value.replace(/\\(.)/g, "$1");
730
+ };
731
+ var hasRepeatedCharPrefixOverlap = (branches) => {
732
+ const values = branches.map(normalizeSimpleBranch).filter(Boolean);
733
+ for (let i = 0; i < values.length; i++) {
734
+ for (let j = i + 1; j < values.length; j++) {
735
+ const a = values[i];
736
+ const b = values[j];
737
+ const char = a[0];
738
+ if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) {
739
+ continue;
740
+ }
741
+ if (a === b || a.startsWith(b) || b.startsWith(a)) {
742
+ return true;
743
+ }
744
+ }
745
+ }
746
+ return false;
747
+ };
748
+ var parseRepeatedExtglob = (pattern, requireEnd = true) => {
749
+ if (pattern[0] !== "+" && pattern[0] !== "*" || pattern[1] !== "(") {
750
+ return;
751
+ }
752
+ let bracket = 0;
753
+ let paren = 0;
754
+ let quote = 0;
755
+ let escaped = false;
756
+ for (let i = 1; i < pattern.length; i++) {
757
+ const ch = pattern[i];
758
+ if (escaped === true) {
759
+ escaped = false;
760
+ continue;
761
+ }
762
+ if (ch === "\\") {
763
+ escaped = true;
764
+ continue;
765
+ }
766
+ if (ch === '"') {
767
+ quote = quote === 1 ? 0 : 1;
768
+ continue;
769
+ }
770
+ if (quote === 1) {
771
+ continue;
772
+ }
773
+ if (ch === "[") {
774
+ bracket++;
775
+ continue;
776
+ }
777
+ if (ch === "]" && bracket > 0) {
778
+ bracket--;
779
+ continue;
780
+ }
781
+ if (bracket > 0) {
782
+ continue;
783
+ }
784
+ if (ch === "(") {
785
+ paren++;
786
+ continue;
787
+ }
788
+ if (ch === ")") {
789
+ paren--;
790
+ if (paren === 0) {
791
+ if (requireEnd === true && i !== pattern.length - 1) {
792
+ return;
793
+ }
794
+ return {
795
+ type: pattern[0],
796
+ body: pattern.slice(2, i),
797
+ end: i
798
+ };
799
+ }
800
+ }
801
+ }
802
+ };
803
+ var getStarExtglobSequenceOutput = (pattern) => {
804
+ let index = 0;
805
+ const chars = [];
806
+ while (index < pattern.length) {
807
+ const match = parseRepeatedExtglob(pattern.slice(index), false);
808
+ if (!match || match.type !== "*") {
809
+ return;
810
+ }
811
+ const branches = splitTopLevel(match.body).map((branch2) => branch2.trim());
812
+ if (branches.length !== 1) {
813
+ return;
814
+ }
815
+ const branch = normalizeSimpleBranch(branches[0]);
816
+ if (!branch || branch.length !== 1) {
817
+ return;
818
+ }
819
+ chars.push(branch);
820
+ index += match.end + 1;
821
+ }
822
+ if (chars.length < 1) {
823
+ return;
824
+ }
825
+ const source = chars.length === 1 ? utils.escapeRegex(chars[0]) : `[${chars.map((ch) => utils.escapeRegex(ch)).join("")}]`;
826
+ return `${source}*`;
827
+ };
828
+ var repeatedExtglobRecursion = (pattern) => {
829
+ let depth = 0;
830
+ let value = pattern.trim();
831
+ let match = parseRepeatedExtglob(value);
832
+ while (match) {
833
+ depth++;
834
+ value = match.body.trim();
835
+ match = parseRepeatedExtglob(value);
836
+ }
837
+ return depth;
838
+ };
839
+ var analyzeRepeatedExtglob = (body, options) => {
840
+ if (options.maxExtglobRecursion === false) {
841
+ return { risky: false };
842
+ }
843
+ const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
844
+ const branches = splitTopLevel(body).map((branch) => branch.trim());
845
+ if (branches.length > 1) {
846
+ if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) {
847
+ return { risky: true };
848
+ }
849
+ }
850
+ for (const branch of branches) {
851
+ const safeOutput = getStarExtglobSequenceOutput(branch);
852
+ if (safeOutput) {
853
+ return { risky: true, safeOutput };
854
+ }
855
+ if (repeatedExtglobRecursion(branch) > max) {
856
+ return { risky: true };
857
+ }
858
+ }
859
+ return { risky: false };
860
+ };
651
861
  var parse = (input, options) => {
652
862
  if (typeof input !== "string") {
653
863
  throw new TypeError("Expected a string");
@@ -778,6 +988,8 @@ var require_parse = __commonJS({
778
988
  token.prev = prev;
779
989
  token.parens = state.parens;
780
990
  token.output = state.output;
991
+ token.startIndex = state.index;
992
+ token.tokensIndex = tokens.length;
781
993
  const output = (opts.capture ? "(" : "") + token.open;
782
994
  increment("parens");
783
995
  push({ type: type2, value: value2, output: state.output ? "" : ONE_CHAR });
@@ -785,6 +997,26 @@ var require_parse = __commonJS({
785
997
  extglobs.push(token);
786
998
  };
787
999
  const extglobClose = (token) => {
1000
+ const literal = input.slice(token.startIndex, state.index + 1);
1001
+ const body = input.slice(token.startIndex + 2, state.index);
1002
+ const analysis = analyzeRepeatedExtglob(body, opts);
1003
+ if ((token.type === "plus" || token.type === "star") && analysis.risky) {
1004
+ const safeOutput = analysis.safeOutput ? (token.output ? "" : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) : void 0;
1005
+ const open = tokens[token.tokensIndex];
1006
+ open.type = "text";
1007
+ open.value = literal;
1008
+ open.output = safeOutput || utils.escapeRegex(literal);
1009
+ for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
1010
+ tokens[i].value = "";
1011
+ tokens[i].output = "";
1012
+ delete tokens[i].suffix;
1013
+ }
1014
+ state.output = token.output + open.output;
1015
+ state.backtrack = true;
1016
+ push({ type: "paren", extglob: true, value, output: "" });
1017
+ decrement("parens");
1018
+ return;
1019
+ }
788
1020
  let output = token.close + (opts.capture ? ")" : "");
789
1021
  let rest;
790
1022
  if (token.type === "negate") {
@@ -10550,6 +10782,18 @@ function charFromCodepoint(c) {
10550
10782
  (c - 65536 & 1023) + 56320
10551
10783
  );
10552
10784
  }
10785
+ function setProperty(object, key, value) {
10786
+ if (key === "__proto__") {
10787
+ Object.defineProperty(object, key, {
10788
+ configurable: true,
10789
+ enumerable: true,
10790
+ writable: true,
10791
+ value
10792
+ });
10793
+ } else {
10794
+ object[key] = value;
10795
+ }
10796
+ }
10553
10797
  var simpleEscapeCheck = new Array(256);
10554
10798
  var simpleEscapeMap = new Array(256);
10555
10799
  for (i = 0; i < 256; i++) {
@@ -10669,7 +10913,7 @@ function mergeMappings(state, destination, source, overridableKeys) {
10669
10913
  for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
10670
10914
  key = sourceKeys[index];
10671
10915
  if (!_hasOwnProperty$1.call(destination, key)) {
10672
- destination[key] = source[key];
10916
+ setProperty(destination, key, source[key]);
10673
10917
  overridableKeys[key] = true;
10674
10918
  }
10675
10919
  }
@@ -10709,16 +10953,7 @@ function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valu
10709
10953
  state.position = startPos || state.position;
10710
10954
  throwError(state, "duplicated mapping key");
10711
10955
  }
10712
- if (keyNode === "__proto__") {
10713
- Object.defineProperty(_result, keyNode, {
10714
- configurable: true,
10715
- enumerable: true,
10716
- writable: true,
10717
- value: valueNode
10718
- });
10719
- } else {
10720
- _result[keyNode] = valueNode;
10721
- }
10956
+ setProperty(_result, keyNode, valueNode);
10722
10957
  delete overridableKeys[keyNode];
10723
10958
  }
10724
10959
  return _result;
@@ -12306,7 +12541,6 @@ var jsYaml = {
12306
12541
  safeLoadAll,
12307
12542
  safeDump
12308
12543
  };
12309
- var js_yaml_default = jsYaml;
12310
12544
 
12311
12545
  // src/Core.ts
12312
12546
  var TAB = " ";
@@ -12315,6 +12549,7 @@ var Capture = class _Capture {
12315
12549
  static #CFILE = "capture.yaml";
12316
12550
  static #SAMPLING_RATE = /* @__PURE__ */ new Map([
12317
12551
  ["JS220", 1e6],
12552
+ ["Otii3", 5e4],
12318
12553
  ["PPK2", 1e5]
12319
12554
  ]);
12320
12555
  static #LOAD_KEYS = [
@@ -12342,7 +12577,7 @@ var Capture = class _Capture {
12342
12577
  cap._sampling_rate = _Capture.#SAMPLING_RATE.get(device) ?? 0;
12343
12578
  cap._sample_count = duration * cap.sampling_rate;
12344
12579
  cap._current_ds = new SampleSet(cap.sample_count);
12345
- cap._voltage_ds = new SampleSet(device == "JS220" ? cap.sample_count : 0);
12580
+ cap._voltage_ds = new SampleSet(device == "PPK2" ? 0 : cap.sample_count);
12346
12581
  const wd = cap.#workdir;
12347
12582
  if (import_fs.default.existsSync(wd)) {
12348
12583
  import_fs.default.rmSync(wd, { recursive: true });
@@ -12355,7 +12590,7 @@ var Capture = class _Capture {
12355
12590
  cap._rootdir = rootdir;
12356
12591
  fail(`captured data not found locally, try running 'emscope pack -u'`, !import_fs.default.existsSync(_Capture.workdir(rootdir)));
12357
12592
  const ytxt = import_fs.default.readFileSync(cap.#cpath, "utf-8");
12358
- const yobj = js_yaml_default.load(ytxt);
12593
+ const yobj = jsYaml.load(ytxt);
12359
12594
  for (const k of _Capture.#LOAD_KEYS) {
12360
12595
  cap[`_${k}`] = yobj.capture[k];
12361
12596
  }
@@ -12363,6 +12598,7 @@ var Capture = class _Capture {
12363
12598
  cap.current_ds.load(cap.#workdir, "current");
12364
12599
  switch (cap.device) {
12365
12600
  case "JS220":
12601
+ case "Otii3":
12366
12602
  cap._voltage_ds = new SampleSet(cap.sample_count);
12367
12603
  cap.voltage_ds.load(cap.#workdir, "voltage");
12368
12604
  break;
@@ -12371,7 +12607,7 @@ var Capture = class _Capture {
12371
12607
  break;
12372
12608
  }
12373
12609
  if (import_fs.default.existsSync(cap.#apath)) {
12374
- cap._aobj = js_yaml_default.load(import_fs.default.readFileSync(cap.#apath, "utf-8"));
12610
+ cap._aobj = jsYaml.load(import_fs.default.readFileSync(cap.#apath, "utf-8"));
12375
12611
  }
12376
12612
  return cap;
12377
12613
  }
@@ -12431,7 +12667,7 @@ var Capture = class _Capture {
12431
12667
  }
12432
12668
  bind(aobj) {
12433
12669
  this._aobj = aobj;
12434
- const ytxt = js_yaml_default.dump(aobj, { indent: 4, flowLevel: 4 });
12670
+ const ytxt = jsYaml.dump(aobj, { indent: 4, flowLevel: 4 });
12435
12671
  import_fs.default.writeFileSync(this.#apath, ytxt);
12436
12672
  infoMsg(`wrote '${_Capture.#AFILE}'`);
12437
12673
  }
@@ -12452,7 +12688,7 @@ var Capture = class _Capture {
12452
12688
  this.voltage_ds.save(this.#workdir, "voltage");
12453
12689
  const cobj = Object.fromEntries(_Capture.#SAVE_KEYS.map((k) => [k, this[k]]));
12454
12690
  const yobj = { capture: cobj };
12455
- const ytxt = js_yaml_default.dump(yobj, { indent: 4, flowLevel: 4 });
12691
+ const ytxt = jsYaml.dump(yobj, { indent: 4, flowLevel: 4 });
12456
12692
  import_fs.default.writeFileSync(this.#cpath, ytxt);
12457
12693
  infoMsg(`wrote '${_Capture.#CFILE}'`);
12458
12694
  }
@@ -12620,6 +12856,21 @@ function fail(msg, cond = true) {
12620
12856
  process.exit(1);
12621
12857
  }
12622
12858
  }
12859
+ function findConfig() {
12860
+ let dir = process.cwd();
12861
+ let fpath = "";
12862
+ while (true) {
12863
+ const full = import_path.default.join(dir, "emscope-local.json");
12864
+ if (import_fs.default.existsSync(full)) {
12865
+ fpath = full;
12866
+ break;
12867
+ }
12868
+ const parent = import_path.default.dirname(dir);
12869
+ if (parent === dir) break;
12870
+ dir = parent;
12871
+ }
12872
+ return fpath === "" ? void 0 : JSON.parse(import_fs.default.readFileSync(fpath, "utf-8"));
12873
+ }
12623
12874
  function infoMsg(msg) {
12624
12875
  console.log(`${TAB}${msg}`);
12625
12876
  }
@@ -12689,6 +12940,7 @@ ${dpath}:`);
12689
12940
  }
12690
12941
 
12691
12942
  // src/Detecter.ts
12943
+ var NO_CURRENT = 0;
12692
12944
  function exec(opts) {
12693
12945
  const cap = Capture.load(opts.capture);
12694
12946
  let params = {};
@@ -12787,6 +13039,7 @@ function measureSleep(osig) {
12787
13039
  const wsig = win.toSignal();
12788
13040
  const cur = wsig.avg();
12789
13041
  const m = win.toMarker();
13042
+ if (cur < NO_CURRENT) continue;
12790
13043
  if (cur < min_cur) {
12791
13044
  min_cur = cur;
12792
13045
  std = wsig.std();
@@ -13100,6 +13353,379 @@ async function execCapture(opts) {
13100
13353
  });
13101
13354
  }
13102
13355
 
13356
+ // src/Driver_Otti3.ts
13357
+ var import_net = __toESM(require("net"));
13358
+ async function execCapture2(opts) {
13359
+ const jobj = findConfig();
13360
+ fail("can't find 'emscope-local.json'", jobj === void 0);
13361
+ const config = jobj.otii3;
13362
+ const cap = Capture.create(opts.capture, opts.duration, "Otii3");
13363
+ const progress = new Progress("capturing: ");
13364
+ const otii = new OtiiSession("127.0.0.1", 1905);
13365
+ await otii.connect();
13366
+ if (!await otii.isLoggedIn()) {
13367
+ await otii.login(config.username, config.password);
13368
+ }
13369
+ for (const license of await otii.getLicenses()) {
13370
+ await otii.reserveLicense(license.id);
13371
+ }
13372
+ const dev = await otii.requireDevice();
13373
+ const projectId = await otii.getOrCreateProject();
13374
+ await otii.addToProject(dev.device_id);
13375
+ await otii.setMainVoltage(dev.device_id, opts.voltage);
13376
+ await otii.setMaxCurrent(dev.device_id, 0.5);
13377
+ await otii.enableChannel(dev.device_id, "mc", true);
13378
+ await otii.enableChannel(dev.device_id, "mv", true);
13379
+ const bp = opts.batteryProfile;
13380
+ const soc = opts.soc ?? 100;
13381
+ fail("missing battery profile index", bp !== void 0 && typeof bp != "number");
13382
+ if (bp) {
13383
+ await batteryConfig(otii, bp, soc, dev.device_id, config);
13384
+ } else {
13385
+ await otii.setSupplyPowerBox(dev.device_id);
13386
+ }
13387
+ await otii.setMain(dev.device_id, true);
13388
+ await progress.spin(2500);
13389
+ await record(otii, cap, progress, projectId, dev.device_id);
13390
+ await otii.setSupplyPowerBox(dev.device_id);
13391
+ await otii.close();
13392
+ return cap;
13393
+ }
13394
+ async function batteryConfig(otii, bidx, soc, deviceId, config) {
13395
+ const bname = config.batteries[bidx];
13396
+ fail("invalid battery index", typeof bname != "string");
13397
+ const profiles = await otii.getBatteryProfiles();
13398
+ let bprof = profiles.find((p) => p.name === bname);
13399
+ fail("no corresponding profile found", bprof === void 0);
13400
+ await otii.setSupplyBatteryEmulator(deviceId, {
13401
+ batteryProfileId: bprof.battery_profile_id,
13402
+ soc
13403
+ });
13404
+ }
13405
+ async function record(otii, cap, progress, projectId, deviceId) {
13406
+ const t0 = Date.now();
13407
+ const t1 = t0 + cap.duration * 1e3;
13408
+ await otii.startRecording(projectId);
13409
+ try {
13410
+ while (true) {
13411
+ const now = Date.now();
13412
+ if (now >= t1) {
13413
+ break;
13414
+ }
13415
+ progress.update(`${((now - t0) / 1e3).toFixed(3)} s`);
13416
+ await new Promise((resolve) => setTimeout(resolve, 100));
13417
+ }
13418
+ } finally {
13419
+ await otii.setMain(deviceId, false);
13420
+ await otii.stopRecording(projectId);
13421
+ }
13422
+ const recordingId = await otii.getLastRecording(projectId);
13423
+ const current = await otii.getAllChannelData(recordingId, deviceId, "mc");
13424
+ for (const sample of current.values) {
13425
+ cap.current_ds.add(sample);
13426
+ }
13427
+ const voltage = await otii.getAllChannelData(recordingId, deviceId, "mv");
13428
+ for (const sample of voltage.values) {
13429
+ cap.voltage_ds.add(sample);
13430
+ }
13431
+ await otii.deleteRecording(recordingId);
13432
+ progress.clear();
13433
+ }
13434
+ var OtiiSession = class {
13435
+ constructor(host = "127.0.0.1", port = 1905, debug = false) {
13436
+ this.host = host;
13437
+ this.port = port;
13438
+ this.debug = debug;
13439
+ this.socket = new import_net.default.Socket();
13440
+ this.rx = "";
13441
+ this.nextId = 1;
13442
+ this.ready = false;
13443
+ this.pending = /* @__PURE__ */ new Map();
13444
+ this.socket.on("data", (chunk) => this.onData(chunk));
13445
+ this.socket.on("error", (err) => this.onError(err));
13446
+ this.socket.on("close", () => this.onClose());
13447
+ }
13448
+ async connect(timeoutMs = 5e3) {
13449
+ if (this.ready) {
13450
+ return;
13451
+ }
13452
+ await new Promise((resolve, reject) => {
13453
+ const timer = setTimeout(() => {
13454
+ cleanup();
13455
+ reject(new Error("timeout waiting for connect banner"));
13456
+ }, timeoutMs);
13457
+ const onError = (err) => {
13458
+ cleanup();
13459
+ reject(err);
13460
+ };
13461
+ const cleanup = () => {
13462
+ clearTimeout(timer);
13463
+ this.socket.off("error", onError);
13464
+ this._onBanner = void 0;
13465
+ };
13466
+ this.socket.on("error", onError);
13467
+ this._onBanner = () => {
13468
+ cleanup();
13469
+ resolve();
13470
+ };
13471
+ this.socket.connect(this.port, this.host, () => {
13472
+ this.log(`socket connected to ${this.host}:${this.port}`);
13473
+ });
13474
+ });
13475
+ }
13476
+ async close() {
13477
+ if (this.socket.destroyed) {
13478
+ return;
13479
+ }
13480
+ await new Promise((resolve) => {
13481
+ this.socket.once("close", () => resolve());
13482
+ this.socket.end();
13483
+ });
13484
+ }
13485
+ async getDevices(timeoutSec = 5) {
13486
+ const data = await this.cmd("otii_get_devices", { timeout: timeoutSec }, (timeoutSec + 3) * 1e3);
13487
+ return data?.devices ?? [];
13488
+ }
13489
+ async requireDevice(deviceId) {
13490
+ const devices = await this.getDevices();
13491
+ if (deviceId) {
13492
+ const dev = devices.find((d) => d.device_id === deviceId);
13493
+ if (!dev) {
13494
+ throw new Error(`device not found: ${deviceId}`);
13495
+ }
13496
+ return dev;
13497
+ }
13498
+ if (devices.length === 0) {
13499
+ throw new Error("no devices found");
13500
+ }
13501
+ return devices[0];
13502
+ }
13503
+ async getOrCreateProject() {
13504
+ const active = await this.cmd("otii_get_active_project");
13505
+ const projectId = active?.project_id;
13506
+ if (projectId != null && projectId !== -1) {
13507
+ return projectId;
13508
+ }
13509
+ const created = await this.cmd("otii_create_project");
13510
+ if (created?.project_id == null) {
13511
+ throw new Error("failed to create project");
13512
+ }
13513
+ return created.project_id;
13514
+ }
13515
+ async addToProject(deviceId) {
13516
+ await this.cmd("arc_add_to_project", { device_id: deviceId }, 1e4);
13517
+ }
13518
+ async setMainVoltage(deviceId, value) {
13519
+ await this.cmd("arc_set_main_voltage", { device_id: deviceId, value });
13520
+ }
13521
+ async setMaxCurrent(deviceId, value) {
13522
+ await this.cmd("arc_set_max_current", { device_id: deviceId, value });
13523
+ }
13524
+ async enableChannel(deviceId, channel, enable) {
13525
+ await this.cmd("arc_enable_channel", { device_id: deviceId, channel, enable });
13526
+ }
13527
+ async setMain(deviceId, value) {
13528
+ await this.cmd("arc_set_main", { device_id: deviceId, enable: value });
13529
+ }
13530
+ async startRecording(projectId) {
13531
+ await this.cmd("project_start_recording", { project_id: projectId }, 1e4);
13532
+ }
13533
+ async stopRecording(projectId) {
13534
+ await this.cmd("project_stop_recording", { project_id: projectId }, 1e4);
13535
+ }
13536
+ async getLastRecording(projectId) {
13537
+ const data = await this.cmd("project_get_last_recording", { project_id: projectId }, 1e4);
13538
+ const recordingId = data?.recording_id;
13539
+ if (recordingId == null || recordingId === -1) {
13540
+ throw new Error("no recording found");
13541
+ }
13542
+ return recordingId;
13543
+ }
13544
+ async deleteRecording(recordingId) {
13545
+ await this.cmd("recording_delete", {
13546
+ recording_id: recordingId
13547
+ });
13548
+ }
13549
+ async getChannelDataCount(recordingId, deviceId, channel) {
13550
+ const data = await this.cmd("recording_get_channel_data_count", {
13551
+ recording_id: recordingId,
13552
+ device_id: deviceId,
13553
+ channel
13554
+ }, 15e3);
13555
+ return data.count;
13556
+ }
13557
+ async getChannelData(recordingId, deviceId, channel, index, count) {
13558
+ const data = await this.cmd("recording_get_channel_data", {
13559
+ recording_id: recordingId,
13560
+ device_id: deviceId,
13561
+ channel,
13562
+ index,
13563
+ count
13564
+ }, 3e4);
13565
+ return {
13566
+ interval: data.interval,
13567
+ values: data.values ?? []
13568
+ };
13569
+ }
13570
+ async getAllChannelData(recordingId, deviceId, channel) {
13571
+ const total = await this.getChannelDataCount(recordingId, deviceId, channel);
13572
+ let index = 0;
13573
+ let remaining = total;
13574
+ const chunkSize = 1e5;
13575
+ let interval = 0;
13576
+ const values = [];
13577
+ while (remaining > 0) {
13578
+ const count = Math.min(remaining, chunkSize);
13579
+ const data = await this.cmd("recording_get_channel_data", {
13580
+ recording_id: recordingId,
13581
+ device_id: deviceId,
13582
+ channel,
13583
+ index,
13584
+ count
13585
+ }, 3e4);
13586
+ if (index === 0) {
13587
+ interval = data.interval;
13588
+ }
13589
+ values.push(...data.values ?? []);
13590
+ index += count;
13591
+ remaining -= count;
13592
+ }
13593
+ return { interval, values };
13594
+ }
13595
+ async getBatteryProfiles() {
13596
+ const data = await this.cmd("otii_get_battery_profiles");
13597
+ return data?.battery_profiles ?? [];
13598
+ }
13599
+ async setSupplyBatteryEmulator(deviceId, opts) {
13600
+ await this.cmd("arc_set_supply_battery_emulator", {
13601
+ device_id: deviceId,
13602
+ battery_profile_id: opts.batteryProfileId,
13603
+ ...opts.series !== void 0 ? { series: opts.series } : {},
13604
+ ...opts.parallel !== void 0 ? { parallel: opts.parallel } : {},
13605
+ ...opts.soc !== void 0 ? { soc: opts.soc } : {},
13606
+ ...opts.usedCapacity !== void 0 ? { used_capacity: opts.usedCapacity } : {},
13607
+ ...opts.socTracking !== void 0 ? { soc_tracking: opts.socTracking } : {}
13608
+ }, 1e4);
13609
+ }
13610
+ async setSupplyPowerBox(deviceId) {
13611
+ await this.cmd("arc_set_supply_power_box", {
13612
+ device_id: deviceId
13613
+ }, 1e4);
13614
+ }
13615
+ async isLoggedIn() {
13616
+ const data = await this.cmd("otii_is_logged_in");
13617
+ return data.logged_in;
13618
+ }
13619
+ async login(username, password) {
13620
+ await this.cmd("otii_login", { username, password }, 1e4);
13621
+ }
13622
+ async getLicenses() {
13623
+ const data = await this.cmd("otii_get_licenses");
13624
+ return data?.licenses ?? [];
13625
+ }
13626
+ async hasLicense(licenseType) {
13627
+ const data = await this.cmd("otii_has_license", { license_type: licenseType });
13628
+ return data.has_license;
13629
+ }
13630
+ async reserveLicense(licenseId) {
13631
+ await this.cmd("otii_reserve_license", { license_id: licenseId }, 1e4);
13632
+ }
13633
+ async returnLicense(licenseId) {
13634
+ await this.cmd("otii_return_license", { license_id: licenseId }, 1e4);
13635
+ }
13636
+ async logout() {
13637
+ await this.cmd("otii_logout", void 0, 1e4);
13638
+ }
13639
+ async cmd(cmd, data, timeoutMs = 3e3) {
13640
+ const rsp = await this.request(cmd, data, timeoutMs);
13641
+ return rsp.data;
13642
+ }
13643
+ async request(cmd, data, timeoutMs = 3e3) {
13644
+ if (!this.ready) {
13645
+ throw new Error("not connected");
13646
+ }
13647
+ const transId = String(this.nextId++);
13648
+ const req = {
13649
+ type: "request",
13650
+ cmd,
13651
+ trans_id: transId
13652
+ };
13653
+ if (data !== void 0) {
13654
+ req.data = data;
13655
+ }
13656
+ return await new Promise((resolve, reject) => {
13657
+ const timer = setTimeout(() => {
13658
+ this.pending.delete(transId);
13659
+ reject(new Error(`timeout waiting for ${cmd}`));
13660
+ }, timeoutMs);
13661
+ this.pending.set(transId, { resolve, reject, timer });
13662
+ this.logObj("send", req);
13663
+ this.socket.write(JSON.stringify(req) + "\r\n");
13664
+ });
13665
+ }
13666
+ onData(chunk) {
13667
+ this.rx += chunk.toString("utf8");
13668
+ for (; ; ) {
13669
+ const idx = this.rx.indexOf("\r\n");
13670
+ if (idx < 0) {
13671
+ break;
13672
+ }
13673
+ const line = this.rx.slice(0, idx).trim();
13674
+ this.rx = this.rx.slice(idx + 2);
13675
+ if (!line) {
13676
+ continue;
13677
+ }
13678
+ const msg = JSON.parse(line);
13679
+ this.logObj("recv", msg);
13680
+ if (msg.type === "information" && (msg.info === "connected" || msg.message === "connected")) {
13681
+ this.ready = true;
13682
+ const onBanner = this._onBanner;
13683
+ onBanner?.();
13684
+ continue;
13685
+ }
13686
+ if (!msg.trans_id) {
13687
+ continue;
13688
+ }
13689
+ const p = this.pending.get(msg.trans_id);
13690
+ if (!p) {
13691
+ continue;
13692
+ }
13693
+ clearTimeout(p.timer);
13694
+ this.pending.delete(msg.trans_id);
13695
+ if (msg.type === "error") {
13696
+ p.reject(new Error(msg.errorcode ?? `server error for ${msg.cmd}`));
13697
+ } else {
13698
+ p.resolve(msg);
13699
+ }
13700
+ }
13701
+ }
13702
+ onError(err) {
13703
+ for (const [, p] of this.pending) {
13704
+ clearTimeout(p.timer);
13705
+ p.reject(err);
13706
+ }
13707
+ this.pending.clear();
13708
+ }
13709
+ onClose() {
13710
+ const err = new Error("socket closed");
13711
+ for (const [, p] of this.pending) {
13712
+ clearTimeout(p.timer);
13713
+ p.reject(err);
13714
+ }
13715
+ this.pending.clear();
13716
+ }
13717
+ log(msg) {
13718
+ if (this.debug) {
13719
+ console.log(msg);
13720
+ }
13721
+ }
13722
+ logObj(prefix, obj) {
13723
+ if (this.debug) {
13724
+ console.log(`${prefix}:`, obj);
13725
+ }
13726
+ }
13727
+ };
13728
+
13103
13729
  // src/Driver_PPK2.ts
13104
13730
  var import_serialport = __toESM(require_dist13());
13105
13731
  var mode_cmd_map = /* @__PURE__ */ new Map([
@@ -13117,7 +13743,7 @@ var MEAS_LOGIC = generateMask(8, 24);
13117
13743
  var MAX_PAYLOAD_COUNTER = 63;
13118
13744
  var DATALOSS_THRESHOLD = 500;
13119
13745
  var getMaskedValue = (value, { mask, pos }) => (value & mask) >> pos;
13120
- async function execCapture2(opts) {
13746
+ async function execCapture3(opts) {
13121
13747
  const path_list = await findDevices();
13122
13748
  const progress = new Progress("capturing: ");
13123
13749
  const port = new import_serialport.SerialPort({ path: path_list[0], baudRate: 9600, autoOpen: false });
@@ -13417,6 +14043,8 @@ async function exec3(opts) {
13417
14043
  }
13418
14044
  c = await execCapture(opts);
13419
14045
  } else if (opts.ppk2) {
14046
+ c = await execCapture3(opts);
14047
+ } else if (opts.otii3) {
13420
14048
  c = await execCapture2(opts);
13421
14049
  } else {
13422
14050
  fail("you must specify an analyzer device");
@@ -13612,7 +14240,7 @@ function printEventInfo(cap, markers) {
13612
14240
  avg += egy * scale;
13613
14241
  const dur = (cap.current_sig.offToSecs(m.width) * 1e3).toFixed(2).padStart(5, " ");
13614
14242
  const off_s = cap.current_sig.offToSecs(m.offset).toFixed(2).padStart(5, " ");
13615
- infoMsg(`${lab} :: time = ${off_s} s, energy = ${uJoules(egy)}, duration = ${dur} s`);
14243
+ infoMsg(`${lab} :: time = ${off_s} s, energy = ${uJoules(egy)}, duration = ${dur} ms`);
13616
14244
  lab = String.fromCharCode(lab.charCodeAt(0) + 1);
13617
14245
  }
13618
14246
  infoMsg("----");
@@ -13711,7 +14339,7 @@ var {
13711
14339
  var CAP = ["-c --capture <directory path>", "working capture directory", "."];
13712
14340
  var VERS = version();
13713
14341
  var CMD = new Command("emscope").option("-C, --capture-glob [name pattern]", `apply this command to each matching child capture directory (default "**")`).version(VERS);
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()));
14342
+ 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("-O --otii3", "use a Qoitech Otii3 device").option("-P --ppk2", "use a Nordic PPK2 device").addOption(new Option("-A --ampere-mode", "enable PPK ampere mode").conflicts(["sourceMode", "otii3", "js220"])).addOption(new Option("-S --source-mode", "enable PPK source mode").default(true).conflicts(["ampereMode", "otii3", "js220"])).addOption(new Option("-B, --battery-profile <index>", "use Otii battery profile (implies -O)").argParser(parseFloat).implies({ otii3: true }).conflicts(["js220", "ppk2"])).addOption(new Option("-p, --ppk2-supply <voltage>", "supply JS220 voltage from a PPK").argParser(parseFloat).conflicts(["otii3", "ppk2"])).addOption(new Option("-v, --voltage [value]", "source voltage").argParser(parseFloat).default(3.3).conflicts("js220")).addOption(new Option("--soc <percent>", "battery state of charge (implies -O)").argParser(parseFloat).implies({ otii3: true })).action((opts, cmd) => execCmd(exec3, opts, cmd.parent.opts()));
13715
14343
  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()));
13716
14344
  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()));
13717
14345
  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()));
@@ -13724,5 +14352,5 @@ try {
13724
14352
  /*! Bundled license information:
13725
14353
 
13726
14354
  js-yaml/dist/js-yaml.mjs:
13727
- (*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT *)
14355
+ (*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT *)
13728
14356
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@em-foundation/emscope",
3
- "version": "25.4.1",
3
+ "version": "25.5.3",
4
4
  "description": "benchmark energy efficiency in resource-constrained embedded systems",
5
5
  "bin": {
6
6
  "emscope": "dist/emscope"