diff options
Diffstat (limited to 'docs/src/components')
-rw-r--r-- | docs/src/components/custom-board-form.js | 100 | ||||
-rw-r--r-- | docs/src/components/power-estimate.js | 266 |
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; |