summaryrefslogtreecommitdiff
path: root/docs/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'docs/src/components')
-rw-r--r--docs/src/components/custom-board-form.js100
-rw-r--r--docs/src/components/power-estimate.js266
2 files changed, 366 insertions, 0 deletions
diff --git a/docs/src/components/custom-board-form.js b/docs/src/components/custom-board-form.js
new file mode 100644
index 0000000..0279f6b
--- /dev/null
+++ b/docs/src/components/custom-board-form.js
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2021 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from "react";
+import PropTypes from "prop-types";
+
+function CustomBoardForm({
+ bindPsuType,
+ bindOutputV,
+ bindEfficiency,
+ bindQuiescentMicroA,
+ bindOtherQuiescentMicroA,
+}) {
+ return (
+ <div className="profilerSection">
+ <h3>Custom Board</h3>
+ <div className="row">
+ <div className="col col--4">
+ <div className="profilerInput">
+ <label>Power Supply Type</label>
+ <select {...bindPsuType}>
+ <option hidden value="">
+ Select a PSU type
+ </option>
+ <option value="LDO">LDO</option>
+ <option value="SWITCHING">Switching</option>
+ </select>
+ </div>
+ </div>
+ <div className="col col--4">
+ <div className="profilerInput">
+ <label>
+ Output Voltage{" "}
+ <span tooltip="Output Voltage of the PSU used by the system">
+ ⓘ
+ </span>
+ </label>
+ <input {...bindOutputV} type="range" min="1.8" step=".1" max="5" />
+ <span>{parseFloat(bindOutputV.value).toFixed(1)}V</span>
+ </div>
+ {bindPsuType.value === "SWITCHING" && (
+ <div className="profilerInput">
+ <label>
+ PSU Efficiency{" "}
+ <span tooltip="The estimated efficiency with a VIN of 3.8 and the output voltage entered above">
+ ⓘ
+ </span>
+ </label>
+ <input
+ {...bindEfficiency}
+ type="range"
+ min=".50"
+ step=".01"
+ max="1"
+ />
+ <span>{Math.round(bindEfficiency.value * 100)}%</span>
+ </div>
+ )}
+ </div>
+ <div className="col col--4">
+ <div className="profilerInput">
+ <label>
+ PSU Quiescent{" "}
+ <span tooltip="The standby usage of the PSU">ⓘ</span>
+ </label>
+ <div className="inputBox">
+ <input {...bindQuiescentMicroA} type="number" />
+ <span>µA</span>
+ </div>
+ </div>
+ <div className="profilerInput">
+ <label>
+ Other Quiescent{" "}
+ <span tooltip="Any other standby usage of the board (voltage dividers, extra ICs, etc)">
+ ⓘ
+ </span>
+ </label>
+ <div className="inputBox">
+ <input {...bindOtherQuiescentMicroA} type="number" />
+ <span>µA</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+CustomBoardForm.propTypes = {
+ bindPsuType: PropTypes.Object,
+ bindOutputV: PropTypes.Object,
+ bindEfficiency: PropTypes.Object,
+ bindQuiescentMicroA: PropTypes.Object,
+ bindOtherQuiescentMicroA: PropTypes.Object,
+};
+
+export default CustomBoardForm;
diff --git a/docs/src/components/power-estimate.js b/docs/src/components/power-estimate.js
new file mode 100644
index 0000000..2619862
--- /dev/null
+++ b/docs/src/components/power-estimate.js
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2021 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from "react";
+import PropTypes from "prop-types";
+import { displayPower, underglowPower, zmkBase } from "../data/power";
+import "../css/power-estimate.css";
+
+// Average monthly discharge percent
+const lithiumIonMonthlyDischargePercent = 5;
+// Average voltage of a lithium ion battery based of discharge graphs
+const lithiumIonAverageVoltage = 3.8;
+// Average discharge efficiency of li-ion https://en.wikipedia.org/wiki/Lithium-ion_battery
+const lithiumIonDischargeEfficiency = 0.85;
+// Range of the discharge efficiency
+const lithiumIonDischargeEfficiencyRange = 0.05;
+
+// Proportion of time spent typing (keys being pressed down and scanning). Estimated to 2%.
+const timeSpentTyping = 0.02;
+
+// Nordic power profiler kit accuracy
+const measurementAccuracy = 0.2;
+
+const batVolt = lithiumIonAverageVoltage;
+
+const palette = [
+ "#bbdefb",
+ "#90caf9",
+ "#64b5f6",
+ "#42a5f5",
+ "#2196f3",
+ "#1e88e5",
+ "#1976d2",
+];
+
+function formatUsage(microWatts) {
+ if (microWatts > 1000) {
+ return (microWatts / 1000).toFixed(1) + "mW";
+ }
+
+ return Math.round(microWatts) + "µW";
+}
+
+function voltageEquivalentCalc(powerSupply) {
+ if (powerSupply.type === "LDO") {
+ return batVolt;
+ } else if (powerSupply.type === "SWITCHING") {
+ return powerSupply.outputVoltage / powerSupply.efficiency;
+ }
+}
+
+function formatMinutes(minutes, precision, floor) {
+ let message = "";
+ let count = 0;
+
+ let units = ["year", "month", "week", "day", "hour", "minute"];
+ let multiples = [60 * 24 * 365, 60 * 24 * 30, 60 * 24 * 7, 60 * 24, 60, 1];
+
+ for (let i = 0; i < units.length; i++) {
+ if (minutes >= multiples[i]) {
+ const timeCount = floor
+ ? Math.floor(minutes / multiples[i])
+ : Math.ceil(minutes / multiples[i]);
+ minutes -= timeCount * multiples[i];
+ count++;
+ message +=
+ timeCount + (timeCount > 1 ? ` ${units[i]}s ` : ` ${units[i]} `);
+ }
+
+ if (count == precision) return message;
+ }
+
+ return message || "0 minutes";
+}
+
+function PowerEstimate({
+ board,
+ splitType,
+ batteryMilliAh,
+ usage,
+ underglow,
+ display,
+}) {
+ if (!board || !board.powerSupply.type || !batteryMilliAh) {
+ return (
+ <div className="powerEstimate">
+ <h3>
+ <span>{splitType !== "standalone" ? splitType + ": " : " "}...</span>
+ </h3>
+ <div className="powerEstimateBar">
+ <div
+ className="powerEstimateBarSection"
+ style={{
+ width: "100%",
+ background: "#e0e0e0",
+ mixBlendMode: "overlay",
+ }}
+ ></div>
+ </div>
+ </div>
+ );
+ }
+
+ const powerUsage = [];
+ let totalUsage = 0;
+
+ const voltageEquivalent = voltageEquivalentCalc(board.powerSupply);
+
+ // Lithium ion self discharge
+ const lithiumMonthlyDischargemAh =
+ parseInt(batteryMilliAh) * (lithiumIonMonthlyDischargePercent / 100);
+ const lithiumDischargeMicroA = (lithiumMonthlyDischargemAh * 1000) / 30 / 24;
+ const lithiumDischargeMicroW = lithiumDischargeMicroA * batVolt;
+
+ totalUsage += lithiumDischargeMicroW;
+ powerUsage.push({
+ title: "Battery Self Discharge",
+ usage: lithiumDischargeMicroW,
+ });
+
+ // Quiescent current
+ const quiescentMicroATotal =
+ parseInt(board.powerSupply.quiescentMicroA) +
+ parseInt(board.otherQuiescentMicroA);
+ const quiescentMicroW = quiescentMicroATotal * voltageEquivalent;
+
+ totalUsage += quiescentMicroW;
+ powerUsage.push({
+ title: "Board Quiescent Usage",
+ usage: quiescentMicroW,
+ });
+
+ // ZMK overall usage
+ const zmkMicroA =
+ zmkBase[splitType].idle +
+ (splitType !== "peripheral" ? zmkBase.hostConnection * usage.bondedQty : 0);
+
+ const zmkMicroW = zmkMicroA * voltageEquivalent;
+ const zmkUsage = zmkMicroW * (1 - usage.percentAsleep);
+
+ totalUsage += zmkUsage;
+ powerUsage.push({
+ title: "ZMK Base Usage",
+ usage: zmkUsage,
+ });
+
+ // ZMK typing usage
+ const zmkTypingMicroA = zmkBase[splitType].typing * timeSpentTyping;
+
+ const zmkTypingMicroW = zmkTypingMicroA * voltageEquivalent;
+ const zmkTypingUsage = zmkTypingMicroW * (1 - usage.percentAsleep);
+
+ totalUsage += zmkTypingUsage;
+ powerUsage.push({
+ title: "ZMK Typing Usage",
+ usage: zmkTypingUsage,
+ });
+
+ if (underglow.glowEnabled) {
+ const underglowAverageLedMicroA =
+ underglow.glowBrightness *
+ (underglowPower.ledOn - underglowPower.ledOff) +
+ underglowPower.ledOff;
+
+ const underglowMicroA =
+ underglowPower.firmware +
+ underglow.glowQuantity * underglowAverageLedMicroA;
+
+ const underglowMicroW = underglowMicroA * voltageEquivalent;
+
+ const underglowUsage = underglowMicroW * (1 - usage.percentAsleep);
+
+ totalUsage += underglowUsage;
+ powerUsage.push({
+ title: "RGB Underglow",
+ usage: underglowUsage,
+ });
+ }
+
+ if (display.displayEnabled && display.displayType) {
+ const { activePercent, active, sleep } = displayPower[display.displayType];
+
+ const displayMicroA = active * activePercent + sleep * (1 - activePercent);
+ const displayMicroW = displayMicroA * voltageEquivalent;
+ const displayUsage = displayMicroW * (1 - usage.percentAsleep);
+
+ totalUsage += displayUsage;
+ powerUsage.push({
+ title: "Display",
+ usage: displayUsage,
+ });
+ }
+
+ // Calculate the average minutes of use
+ const estimatedAvgEffectiveMicroWH =
+ batteryMilliAh * batVolt * lithiumIonDischargeEfficiency * 1000;
+
+ const estimatedAvgMinutes = Math.round(
+ (estimatedAvgEffectiveMicroWH / totalUsage) * 60
+ );
+
+ // Calculate worst case for battery life
+ const worstLithiumIonDischargeEfficiency =
+ lithiumIonDischargeEfficiency - lithiumIonDischargeEfficiencyRange;
+
+ const estimatedWorstEffectiveMicroWH =
+ batteryMilliAh * batVolt * worstLithiumIonDischargeEfficiency * 1000;
+
+ const highestTotalUsage = totalUsage * (1 + measurementAccuracy);
+
+ const estimatedWorstMinutes = Math.round(
+ (estimatedWorstEffectiveMicroWH / highestTotalUsage) * 60
+ );
+
+ // Calculate range (+-) of minutes using average - worst
+ const estimatedRange = estimatedAvgMinutes - estimatedWorstMinutes;
+
+ return (
+ <div className="powerEstimate">
+ <h3>
+ <span>{splitType !== "standalone" ? splitType + ": " : " "}</span>
+ {formatMinutes(estimatedAvgMinutes, 2, true)} (±
+ {formatMinutes(estimatedRange, 1, false).trim()})
+ </h3>
+ <div className="powerEstimateBar">
+ {powerUsage.map((p, i) => (
+ <div
+ key={p.title}
+ className={
+ "powerEstimateBarSection" + (i > 1 ? " rightSection" : "")
+ }
+ style={{
+ width: (p.usage / totalUsage) * 100 + "%",
+ background: palette[i],
+ }}
+ >
+ <div className="powerEstimateTooltipWrap">
+ <div className="powerEstimateTooltip">
+ <div>
+ {p.title} - {Math.round((p.usage / totalUsage) * 100)}%
+ </div>
+ <div style={{ fontSize: ".875rem" }}>
+ ~{formatUsage(p.usage)} estimated avg. consumption
+ </div>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+}
+
+PowerEstimate.propTypes = {
+ board: PropTypes.Object,
+ splitType: PropTypes.string,
+ batteryMilliAh: PropTypes.number,
+ usage: PropTypes.Object,
+ underglow: PropTypes.Object,
+ display: PropTypes.Object,
+};
+
+export default PowerEstimate;