@jjlmoya/utils-hardware 1.25.0 → 1.27.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.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +7 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/locale_completeness.test.ts +1 -1
  6. package/src/tests/tool_validation.test.ts +1 -1
  7. package/src/tool/usbPowerBudgetCalculator/bibliography.astro +14 -0
  8. package/src/tool/usbPowerBudgetCalculator/bibliography.ts +16 -0
  9. package/src/tool/usbPowerBudgetCalculator/component.astro +266 -0
  10. package/src/tool/usbPowerBudgetCalculator/entry.ts +29 -0
  11. package/src/tool/usbPowerBudgetCalculator/i18n/de.ts +169 -0
  12. package/src/tool/usbPowerBudgetCalculator/i18n/en.ts +169 -0
  13. package/src/tool/usbPowerBudgetCalculator/i18n/es.ts +169 -0
  14. package/src/tool/usbPowerBudgetCalculator/i18n/fr.ts +169 -0
  15. package/src/tool/usbPowerBudgetCalculator/i18n/id.ts +169 -0
  16. package/src/tool/usbPowerBudgetCalculator/i18n/it.ts +169 -0
  17. package/src/tool/usbPowerBudgetCalculator/i18n/ja.ts +169 -0
  18. package/src/tool/usbPowerBudgetCalculator/i18n/ko.ts +169 -0
  19. package/src/tool/usbPowerBudgetCalculator/i18n/nl.ts +169 -0
  20. package/src/tool/usbPowerBudgetCalculator/i18n/pl.ts +169 -0
  21. package/src/tool/usbPowerBudgetCalculator/i18n/pt.ts +169 -0
  22. package/src/tool/usbPowerBudgetCalculator/i18n/ru.ts +169 -0
  23. package/src/tool/usbPowerBudgetCalculator/i18n/sv.ts +169 -0
  24. package/src/tool/usbPowerBudgetCalculator/i18n/tr.ts +169 -0
  25. package/src/tool/usbPowerBudgetCalculator/i18n/zh.ts +169 -0
  26. package/src/tool/usbPowerBudgetCalculator/index.ts +11 -0
  27. package/src/tool/usbPowerBudgetCalculator/logic.ts +81 -0
  28. package/src/tool/usbPowerBudgetCalculator/seo.astro +15 -0
  29. package/src/tool/usbPowerBudgetCalculator/ui.ts +32 -0
  30. package/src/tool/usbPowerBudgetCalculator/usb-power-budget-calculator.css +484 -0
  31. package/src/tool/webUsbSerialMonitor/bibliography.astro +15 -0
  32. package/src/tool/webUsbSerialMonitor/bibliography.ts +18 -0
  33. package/src/tool/webUsbSerialMonitor/component.astro +356 -0
  34. package/src/tool/webUsbSerialMonitor/entry.ts +30 -0
  35. package/src/tool/webUsbSerialMonitor/i18n/de.ts +241 -0
  36. package/src/tool/webUsbSerialMonitor/i18n/en.ts +241 -0
  37. package/src/tool/webUsbSerialMonitor/i18n/es.ts +241 -0
  38. package/src/tool/webUsbSerialMonitor/i18n/fr.ts +241 -0
  39. package/src/tool/webUsbSerialMonitor/i18n/id.ts +241 -0
  40. package/src/tool/webUsbSerialMonitor/i18n/it.ts +241 -0
  41. package/src/tool/webUsbSerialMonitor/i18n/ja.ts +241 -0
  42. package/src/tool/webUsbSerialMonitor/i18n/ko.ts +241 -0
  43. package/src/tool/webUsbSerialMonitor/i18n/nl.ts +241 -0
  44. package/src/tool/webUsbSerialMonitor/i18n/pl.ts +241 -0
  45. package/src/tool/webUsbSerialMonitor/i18n/pt.ts +241 -0
  46. package/src/tool/webUsbSerialMonitor/i18n/ru.ts +241 -0
  47. package/src/tool/webUsbSerialMonitor/i18n/sv.ts +241 -0
  48. package/src/tool/webUsbSerialMonitor/i18n/tr.ts +241 -0
  49. package/src/tool/webUsbSerialMonitor/i18n/zh.ts +241 -0
  50. package/src/tool/webUsbSerialMonitor/index.ts +12 -0
  51. package/src/tool/webUsbSerialMonitor/logic.ts +44 -0
  52. package/src/tool/webUsbSerialMonitor/seo.astro +16 -0
  53. package/src/tool/webUsbSerialMonitor/ui.ts +51 -0
  54. package/src/tool/webUsbSerialMonitor/web-usb-serial-monitor.css +415 -0
  55. package/src/tools.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-hardware",
3
- "version": "1.25.0",
3
+ "version": "1.27.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -15,10 +15,12 @@ import { upsRuntimeCalculator } from '../tool/upsRuntimeCalculator/index';
15
15
  import { stereoAudioTest } from '../tool/stereoAudioTest/index';
16
16
  import { webBluetoothBleScanner } from '../tool/webBluetoothBleScanner/index';
17
17
  import { keyboardChatterTest } from '../tool/keyboardChatterTest/index';
18
+ import { webUsbSerialMonitor } from '../tool/webUsbSerialMonitor/index';
19
+ import { usbPowerBudgetCalculator } from '../tool/usbPowerBudgetCalculator/index';
18
20
 
19
21
  export const hardwareCategory: HardwareCategoryEntry = {
20
22
  icon: 'mdi:memory',
21
- tools: [pixelesPantalla, testTeclado, keyboardChatterTest, testMando, probadorVibracionMando, testRaton, mouseDoubleClickTest, mouseScrollTest, estimadorSaludBateria, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, stereoAudioTest, webBluetoothBleScanner],
23
+ tools: [pixelesPantalla, testTeclado, keyboardChatterTest, testMando, probadorVibracionMando, testRaton, mouseDoubleClickTest, mouseScrollTest, estimadorSaludBateria, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, usbPowerBudgetCalculator, stereoAudioTest, webBluetoothBleScanner, webUsbSerialMonitor],
22
24
  i18n: {
23
25
  en: () => import('./i18n/en').then((m) => m.content),
24
26
  es: () => import('./i18n/es').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -30,6 +30,10 @@ export { webBluetoothBleScanner } from './tool/webBluetoothBleScanner/entry';
30
30
  export type { WebBluetoothBleScannerLocaleContent } from './tool/webBluetoothBleScanner/entry';
31
31
  export { keyboardChatterTest } from './tool/keyboardChatterTest/entry';
32
32
  export type { KeyboardChatterTestLocaleContent } from './tool/keyboardChatterTest/entry';
33
+ export { webUsbSerialMonitor } from './tool/webUsbSerialMonitor/entry';
34
+ export type { WebUsbSerialMonitorLocaleContent } from './tool/webUsbSerialMonitor/entry';
35
+ export { usbPowerBudgetCalculator } from './tool/usbPowerBudgetCalculator/entry';
36
+ export type { UsbPowerBudgetCalculatorLocaleContent } from './tool/usbPowerBudgetCalculator/entry';
33
37
  export { hardwareCategory } from './category';
34
38
  import { estimadorSaludBateria } from './tool/batteryHealthEstimator/entry';
35
39
  import { pixelesPantalla } from './tool/deadPixelTest/entry';
@@ -47,4 +51,6 @@ import { upsRuntimeCalculator } from './tool/upsRuntimeCalculator/entry';
47
51
  import { stereoAudioTest } from './tool/stereoAudioTest/entry';
48
52
  import { webBluetoothBleScanner } from './tool/webBluetoothBleScanner/entry';
49
53
  import { keyboardChatterTest } from './tool/keyboardChatterTest/entry';
50
- export const ALL_ENTRIES = [estimadorSaludBateria, pixelesPantalla, testMando, probadorVibracionMando, testTeclado, keyboardChatterTest, testRaton, mouseDoubleClickTest, mouseScrollTest, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, stereoAudioTest, webBluetoothBleScanner];
54
+ import { webUsbSerialMonitor } from './tool/webUsbSerialMonitor/entry';
55
+ import { usbPowerBudgetCalculator } from './tool/usbPowerBudgetCalculator/entry';
56
+ export const ALL_ENTRIES = [estimadorSaludBateria, pixelesPantalla, testMando, probadorVibracionMando, testTeclado, keyboardChatterTest, testRaton, mouseDoubleClickTest, mouseScrollTest, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, usbPowerBudgetCalculator, stereoAudioTest, webBluetoothBleScanner, webUsbSerialMonitor];
package/src/index.ts CHANGED
@@ -33,3 +33,5 @@ export { UPS_RUNTIME_CALCULATOR_TOOL } from './tool/upsRuntimeCalculator/index';
33
33
  export { STEREO_AUDIO_TEST_TOOL } from './tool/stereoAudioTest/index';
34
34
  export { WEB_BLUETOOTH_BLE_SCANNER_TOOL } from './tool/webBluetoothBleScanner/index';
35
35
  export { KEYBOARD_CHATTER_TEST_TOOL } from './tool/keyboardChatterTest/index';
36
+ export { WEB_USB_SERIAL_MONITOR_TOOL } from './tool/webUsbSerialMonitor/index';
37
+ export { USB_POWER_BUDGET_CALCULATOR_TOOL } from './tool/usbPowerBudgetCalculator/index';
@@ -22,7 +22,7 @@ describe('Locale Completeness Validation', () => {
22
22
  });
23
23
 
24
24
  it('all tools registered', () => {
25
- expect(ALL_TOOLS.length).toBe(16);
25
+ expect(ALL_TOOLS.length).toBe(18);
26
26
  });
27
27
  });
28
28
 
@@ -5,7 +5,7 @@ import { hardwareCategory } from '../data';
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
7
  it('should have all tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(16);
8
+ expect(ALL_TOOLS.length).toBe(18);
9
9
  });
10
10
 
11
11
  it('hardwareCategory should be defined', () => {
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import type { KnownLocale } from '../../types';
4
+ import { usbPowerBudgetCalculator } from './index';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await usbPowerBudgetCalculator.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && content.bibliography.length > 0 && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,16 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'USB-IF - USB Type-C Cable and Connector Specification',
6
+ url: 'https://www.usb.org/document-library/usb-type-cr-cable-and-connector-specification-release-24',
7
+ },
8
+ {
9
+ name: 'USB-IF - USB Power Delivery Specification',
10
+ url: 'https://www.usb.org/document-library/usb-power-delivery',
11
+ },
12
+ {
13
+ name: 'IPC - Current carrying capacity and voltage drop fundamentals',
14
+ url: 'https://www.ipc.org/',
15
+ },
16
+ ];
@@ -0,0 +1,266 @@
1
+ ---
2
+ import type { KnownLocale } from '../../types';
3
+ import type { UsbPowerBudgetCalculatorUI } from './ui';
4
+ import { usbPowerProfiles } from './logic';
5
+ import './usb-power-budget-calculator.css';
6
+
7
+ interface Props {
8
+ locale?: KnownLocale;
9
+ ui?: Record<string, unknown>;
10
+ }
11
+
12
+ const { ui } = Astro.props;
13
+ const t = (ui ?? {}) as UsbPowerBudgetCalculatorUI;
14
+ const strings = JSON.stringify(t);
15
+ const profiles = JSON.stringify(usbPowerProfiles);
16
+ ---
17
+
18
+ <div class="upb-shell" id="upb-root" data-ui={strings} data-profiles={profiles} data-status="safe">
19
+ <div class="upb-board">
20
+ <div class="upb-board-top">
21
+ <span class="upb-eyebrow">{t.boardEyebrow}</span>
22
+ <div class="upb-unit-switch" role="group" aria-label="Unit system">
23
+ <button type="button" class="is-active" data-unit="metric">{t.metricUnits}</button>
24
+ <button type="button" data-unit="imperial">{t.imperialUnits}</button>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="upb-hero" aria-live="polite">
29
+ <div class="upb-socket upb-source">
30
+ <span>{t.sourceSocket}</span>
31
+ <strong id="upb-source-power">15.0 W</strong>
32
+ <small id="upb-available-power">12.0 W usable</small>
33
+ </div>
34
+
35
+ <div class="upb-cable-stage" aria-label="Animated USB power cable">
36
+ <svg class="upb-cable-art" viewBox="0 0 520 210" role="img" aria-label="USB cable carrying power from source to device">
37
+ <defs>
38
+ <filter id="upb-glow" x="-20%" y="-60%" width="140%" height="220%">
39
+ <feGaussianBlur stdDeviation="6" result="blur" />
40
+ <feMerge>
41
+ <feMergeNode in="blur" />
42
+ <feMergeNode in="SourceGraphic" />
43
+ </feMerge>
44
+ </filter>
45
+ </defs>
46
+ <path class="upb-cable-shadow" d="M58 118 C128 38 202 38 260 118 S394 198 462 118" />
47
+ <path class="upb-cable-body" d="M58 118 C128 38 202 38 260 118 S394 198 462 118" />
48
+ <path class="upb-cable-core" d="M58 118 C128 38 202 38 260 118 S394 198 462 118" />
49
+ <path class="upb-cable-spark" d="M58 118 C128 38 202 38 260 118 S394 198 462 118" />
50
+ <g class="upb-plug upb-plug-left">
51
+ <rect x="18" y="86" width="54" height="64" rx="14" />
52
+ <rect x="0" y="100" width="24" height="36" rx="7" />
53
+ <line x1="14" y1="109" x2="14" y2="127" />
54
+ </g>
55
+ <g class="upb-plug upb-plug-right">
56
+ <rect x="448" y="86" width="54" height="64" rx="14" />
57
+ <rect x="496" y="100" width="24" height="36" rx="7" />
58
+ <circle cx="506" cy="118" r="5" />
59
+ </g>
60
+ </svg>
61
+ <span class="upb-flow-chip" id="upb-drop-chip">0.00 V {t.cableLane}</span>
62
+ <span class="upb-flow-label">{t.energyFlow}</span>
63
+ </div>
64
+
65
+ <div class="upb-socket upb-load">
66
+ <span>{t.deviceSocket}</span>
67
+ <strong id="upb-required-power">15.0 W</strong>
68
+ <small id="upb-device-voltage">4.36 V</small>
69
+ </div>
70
+ </div>
71
+
72
+ <div class="upb-status-row">
73
+ <div class="upb-status" id="upb-status" data-status="safe">
74
+ <span id="upb-status-text">{t.safeStatus}</span>
75
+ <p id="upb-advice">{t.safeAdvice}</p>
76
+ </div>
77
+ <div class="upb-gauge">
78
+ <div class="upb-gauge-copy">
79
+ <span>{t.utilization}</span>
80
+ <strong id="upb-utilization">100%</strong>
81
+ </div>
82
+ <div class="upb-gauge-track"><i id="upb-utilization-bar"></i></div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <div class="upb-console">
88
+ <label class="upb-field upb-wide">
89
+ <span>{t.profileLabel}</span>
90
+ <select id="upb-profile">
91
+ {Object.entries(usbPowerProfiles).map(([key, profile]) => <option value={key}>{profile.label}</option>)}
92
+ </select>
93
+ </label>
94
+
95
+ <div class="upb-field-pair">
96
+ <label class="upb-field">
97
+ <span>{t.voltageLabel}</span>
98
+ <input id="upb-voltage" type="number" min="3" max="48" step="0.1" value="5" />
99
+ </label>
100
+ <label class="upb-field">
101
+ <span>{t.currentLabel}</span>
102
+ <input id="upb-current" type="number" min="0.1" max="10" step="0.1" value="3" />
103
+ </label>
104
+ </div>
105
+
106
+ <div class="upb-field-pair">
107
+ <label class="upb-field">
108
+ <span id="upb-length-label">{t.cableLengthLabel} (m)</span>
109
+ <input id="upb-length" type="number" min="0.1" max="10" step="0.1" value="1.5" />
110
+ </label>
111
+ <label class="upb-field">
112
+ <span>{t.wireGaugeLabel}</span>
113
+ <select id="upb-awg">
114
+ <option value="20">20 AWG</option>
115
+ <option value="22">22 AWG</option>
116
+ <option value="24">24 AWG</option>
117
+ <option value="26">26 AWG</option>
118
+ <option value="28" selected>28 AWG</option>
119
+ <option value="30">30 AWG</option>
120
+ </select>
121
+ </label>
122
+ </div>
123
+
124
+ <div class="upb-field-pair">
125
+ <label class="upb-field">
126
+ <span>{t.deviceLoadLabel}</span>
127
+ <input id="upb-load" type="number" min="0.1" max="240" step="0.1" value="7.5" />
128
+ </label>
129
+ <label class="upb-field">
130
+ <span>{t.devicesLabel}</span>
131
+ <input id="upb-devices" type="number" min="1" max="12" step="1" value="2" />
132
+ </label>
133
+ </div>
134
+
135
+ <label class="upb-field">
136
+ <span>{t.headroomLabel}</span>
137
+ <input id="upb-headroom" type="number" min="0" max="50" step="5" value="20" />
138
+ </label>
139
+
140
+ <div class="upb-metrics">
141
+ <div><span>{t.headroom}</span><strong id="upb-headroom-watts">-3.0 W</strong></div>
142
+ <div><span>{t.cableDrop}</span><strong id="upb-cable-drop">0.64 V</strong></div>
143
+ <div><span>{t.sourcePower}</span><strong id="upb-source-power-small">15.0 W</strong></div>
144
+ <div><span>{t.requiredPower}</span><strong id="upb-required-power-small">15.0 W</strong></div>
145
+ </div>
146
+ </div>
147
+
148
+ <script>
149
+ import { calculateUsbPowerBudget, feetToMeters, metersToFeet } from './logic';
150
+
151
+ const root = document.getElementById('upb-root') as HTMLElement | null;
152
+ const ui = JSON.parse(root?.dataset.ui ?? '{}') as Record<string, string>;
153
+ const profiles = JSON.parse(root?.dataset.profiles ?? '{}') as Record<string, { voltage: number; current: number }>;
154
+ const profile = document.getElementById('upb-profile') as HTMLSelectElement | null;
155
+ const voltage = document.getElementById('upb-voltage') as HTMLInputElement | null;
156
+ const current = document.getElementById('upb-current') as HTMLInputElement | null;
157
+ const length = document.getElementById('upb-length') as HTMLInputElement | null;
158
+ const lengthLabel = document.getElementById('upb-length-label');
159
+ const awg = document.getElementById('upb-awg') as HTMLSelectElement | null;
160
+ const load = document.getElementById('upb-load') as HTMLInputElement | null;
161
+ const devices = document.getElementById('upb-devices') as HTMLInputElement | null;
162
+ const headroom = document.getElementById('upb-headroom') as HTMLInputElement | null;
163
+ const unitButtons = Array.from(document.querySelectorAll<HTMLButtonElement>('.upb-unit-switch button'));
164
+ let unitSystem = 'metric';
165
+
166
+ const els = {
167
+ sourcePower: document.getElementById('upb-source-power'),
168
+ requiredPower: document.getElementById('upb-required-power'),
169
+ dropChip: document.getElementById('upb-drop-chip'),
170
+ status: document.getElementById('upb-status') as HTMLElement | null,
171
+ statusText: document.getElementById('upb-status-text'),
172
+ advice: document.getElementById('upb-advice'),
173
+ deviceVoltage: document.getElementById('upb-device-voltage'),
174
+ headroomWatts: document.getElementById('upb-headroom-watts'),
175
+ utilization: document.getElementById('upb-utilization'),
176
+ cableDrop: document.getElementById('upb-cable-drop'),
177
+ availablePower: document.getElementById('upb-available-power'),
178
+ sourcePowerSmall: document.getElementById('upb-source-power-small'),
179
+ requiredPowerSmall: document.getElementById('upb-required-power-small'),
180
+ utilizationBar: document.getElementById('upb-utilization-bar') as HTMLElement | null,
181
+ };
182
+
183
+ function numberValue(input: HTMLInputElement | HTMLSelectElement | null, fallback: number): number {
184
+ const value = Number(input?.value);
185
+ return Number.isFinite(value) ? value : fallback;
186
+ }
187
+
188
+ function setLengthLabel() {
189
+ if (!lengthLabel) return;
190
+ lengthLabel.textContent = `${ui.cableLengthLabel} (${unitSystem === 'metric' ? 'm' : 'ft'})`;
191
+ }
192
+
193
+ function formatWatts(value: number) {
194
+ return `${value.toFixed(1)} W`;
195
+ }
196
+
197
+ function setText(el: HTMLElement | null, value: string) {
198
+ if (el) el.textContent = value;
199
+ }
200
+
201
+ function applyResult(key: string, result: ReturnType<typeof calculateUsbPowerBudget>) {
202
+ setText(els.statusText, ui[`${key}Status`] ?? '');
203
+ setText(els.advice, ui[`${key}Advice`] ?? '');
204
+ setText(els.sourcePower, formatWatts(result.sourceWatts));
205
+ setText(els.sourcePowerSmall, formatWatts(result.sourceWatts));
206
+ setText(els.requiredPower, formatWatts(result.requiredWatts));
207
+ setText(els.requiredPowerSmall, formatWatts(result.requiredWatts));
208
+ setText(els.availablePower, `${formatWatts(result.availableWatts)} ${ui.reservedLabel}`);
209
+ setText(els.dropChip, `${result.cableDropVolts.toFixed(2)} V ${ui.cableLane}`);
210
+ setText(els.deviceVoltage, `${result.deviceEndVoltage.toFixed(2)} V`);
211
+ setText(els.headroomWatts, formatWatts(result.headroomWatts));
212
+ setText(els.utilization, `${Math.round(result.utilizationPercent)}%`);
213
+ setText(els.cableDrop, `${result.cableDropVolts.toFixed(2)} V`);
214
+ if (els.status) els.status.dataset.status = result.status;
215
+ if (root) root.dataset.status = result.status;
216
+ if (els.utilizationBar) els.utilizationBar.style.width = `${Math.min(140, Math.max(0, result.utilizationPercent))}%`;
217
+ }
218
+
219
+ function update() {
220
+ const cableLengthMeters = unitSystem === 'metric'
221
+ ? numberValue(length, 1.5)
222
+ : feetToMeters(numberValue(length, 5));
223
+ const result = calculateUsbPowerBudget({
224
+ sourceVoltage: numberValue(voltage, 5),
225
+ sourceCurrent: numberValue(current, 3),
226
+ cableLengthMeters,
227
+ wireGaugeAwg: numberValue(awg, 28),
228
+ deviceLoadWatts: numberValue(load, 7.5),
229
+ devices: numberValue(devices, 2),
230
+ headroomPercent: numberValue(headroom, 20),
231
+ });
232
+
233
+ let key = 'over';
234
+ if (result.status === 'safe') key = 'safe';
235
+ else if (result.status === 'tight') key = 'tight';
236
+ applyResult(key, result);
237
+ }
238
+
239
+ profile?.addEventListener('change', () => {
240
+ const selected = profiles[profile.value];
241
+ if (!selected || !voltage || !current) return;
242
+ voltage.value = String(selected.voltage);
243
+ current.value = String(selected.current);
244
+ update();
245
+ });
246
+
247
+ unitButtons.forEach((button) => {
248
+ button.addEventListener('click', () => {
249
+ const next = button.dataset.unit ?? 'metric';
250
+ if (next === unitSystem || !length) return;
251
+ const currentLength = numberValue(length, unitSystem === 'metric' ? 1.5 : 5);
252
+ length.value = next === 'metric'
253
+ ? feetToMeters(currentLength).toFixed(2)
254
+ : metersToFeet(currentLength).toFixed(1);
255
+ unitSystem = next;
256
+ unitButtons.forEach((item) => item.classList.toggle('is-active', item === button));
257
+ setLengthLabel();
258
+ update();
259
+ });
260
+ });
261
+
262
+ [voltage, current, length, awg, load, devices, headroom].forEach((input) => input?.addEventListener('input', update));
263
+ setLengthLabel();
264
+ update();
265
+ </script>
266
+ </div>
@@ -0,0 +1,29 @@
1
+ import type { HardwareToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { UsbPowerBudgetCalculatorUI } from './ui';
3
+
4
+ export type UsbPowerBudgetCalculatorLocaleContent = ToolLocaleContent<UsbPowerBudgetCalculatorUI>;
5
+
6
+ export const usbPowerBudgetCalculator: HardwareToolEntry<UsbPowerBudgetCalculatorUI> = {
7
+ id: 'usb-power-budget-calculator',
8
+ icons: {
9
+ bg: 'mdi:usb-port',
10
+ fg: 'mdi:lightning-bolt',
11
+ },
12
+ i18n: {
13
+ de: () => import('./i18n/de').then((m) => m.content),
14
+ en: () => import('./i18n/en').then((m) => m.content),
15
+ es: () => import('./i18n/es').then((m) => m.content),
16
+ fr: () => import('./i18n/fr').then((m) => m.content),
17
+ id: () => import('./i18n/id').then((m) => m.content),
18
+ it: () => import('./i18n/it').then((m) => m.content),
19
+ ja: () => import('./i18n/ja').then((m) => m.content),
20
+ ko: () => import('./i18n/ko').then((m) => m.content),
21
+ nl: () => import('./i18n/nl').then((m) => m.content),
22
+ pl: () => import('./i18n/pl').then((m) => m.content),
23
+ pt: () => import('./i18n/pt').then((m) => m.content),
24
+ ru: () => import('./i18n/ru').then((m) => m.content),
25
+ sv: () => import('./i18n/sv').then((m) => m.content),
26
+ tr: () => import('./i18n/tr').then((m) => m.content),
27
+ zh: () => import('./i18n/zh').then((m) => m.content),
28
+ },
29
+ };
@@ -0,0 +1,169 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { UsbPowerBudgetCalculatorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'usb-leistungsbudget-rechner';
7
+ const title = 'USB Leistungsbudget Rechner';
8
+ const description = 'Prüfen Sie, ob ein USB-Anschluss, Ladegerät, Hub, Kabel oder USB-C PD-Profil Ihre Geräte nach Reserve und Kabelspannungsabfall sicher mit Strom versorgen kann.';
9
+
10
+ const faqData = [
11
+ {
12
+ question: 'Wie erkenne ich, ob ein USB-Anschluss mein Gerät mit Strom versorgen kann?',
13
+ answer: 'Vergleichen Sie die Gesamtleistung des Geräts mit der USB-Quellleistung, reservieren Sie dann Sicherheitsmarge und schätzen Sie den Kabelspannungsabfall. Ein Setup kann scheitern, selbst wenn die nominale Wattzahl hoch erscheint, wenn das Kabel lang, dünn oder mit hohem Strom bei 5 Volt betrieben wird.',
14
+ },
15
+ {
16
+ question: 'Warum ist die Kabellänge für USB-Strom wichtig?',
17
+ answer: 'Strom, der durch Kupfer fließt, erzeugt Spannungsabfall. Lange Kabel und dünne Leiter haben mehr Widerstand, sodass das Gerät möglicherweise weniger Spannung erhält als das Ladegerät liefert. Dies ist besonders wichtig für Raspberry Pi-Boards, Festplatten, LED-Streifen, Docks und busbetriebene Hubs.',
18
+ },
19
+ {
20
+ question: 'Welche Sicherheitsmarge sollte ich verwenden?',
21
+ answer: 'Verwenden Sie mindestens 20 Prozent für normale Elektronik, 30 Prozent für Motoren, Laufwerke, Funkgeräte oder Boards mit Spitzenlasten und mehr, wenn die Adapterqualität unbekannt ist oder das Gerät kontinuierlich laufen muss.',
22
+ },
23
+ {
24
+ question: 'Kann dies USB-C PD-Verhandlungstests ersetzen?',
25
+ answer: 'Nein. Der Rechner prüft das elektrische Budget. Er verifiziert nicht, ob ein Ladegerät, Kabel-e-Marker, Gerät oder Hub tatsächlich ein bestimmtes Power-Delivery-Profil aushandelt.',
26
+ },
27
+ ];
28
+
29
+ const howToData = [
30
+ { name: 'Quellprofil wählen', text: 'Wählen Sie ein gängiges USB- oder USB-C PD-Profil oder bearbeiten Sie Spannung und Strom manuell.' },
31
+ { name: 'Kabel beschreiben', text: 'Geben Sie Kabellänge und Leiterquerschnitt ein. Dünne Drähte mit hoher AWG-Zahl verursachen mehr Spannungsabfall.' },
32
+ { name: 'Last hinzufügen', text: 'Geben Sie eine Gerätelast in Watt und die Anzahl der Geräte ein, die dieselbe Quelle teilen.' },
33
+ { name: 'Status ablesen', text: 'Verwenden Sie Status, Kabelabfall, Endspannung, Auslastung und Reserve, um zu entscheiden, ob das Setup sicher ist.' },
34
+ ];
35
+
36
+ const faqSchema: WithContext<FAQPage> = {
37
+ '@context': 'https://schema.org',
38
+ '@type': 'FAQPage',
39
+ mainEntity: faqData.map((item) => ({
40
+ '@type': 'Question',
41
+ name: item.question,
42
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
43
+ })),
44
+ };
45
+
46
+ const howToSchema: WithContext<HowTo> = {
47
+ '@context': 'https://schema.org',
48
+ '@type': 'HowTo',
49
+ name: title,
50
+ description,
51
+ step: howToData.map((step, i) => ({
52
+ '@type': 'HowToStep',
53
+ position: i + 1,
54
+ name: step.name,
55
+ text: step.text,
56
+ })),
57
+ };
58
+
59
+ const appSchema: WithContext<SoftwareApplication> = {
60
+ '@context': 'https://schema.org',
61
+ '@type': 'SoftwareApplication',
62
+ name: title,
63
+ description,
64
+ applicationCategory: 'UtilityApplication',
65
+ operatingSystem: 'All',
66
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
67
+ inLanguage: 'de',
68
+ };
69
+
70
+ export const content: ToolLocaleContent<UsbPowerBudgetCalculatorUI> = {
71
+ slug,
72
+ title,
73
+ description,
74
+ faq: faqData,
75
+ howTo: howToData,
76
+ schemas: [faqSchema, howToSchema, appSchema],
77
+ bibliography,
78
+ seo: [
79
+ { type: 'title', text: 'USB-Strom ist ein Budget, kein Etikett', level: 2 },
80
+ {
81
+ type: 'paragraph',
82
+ html: 'Ein USB-Ladegerät mit 15 W, 45 W oder 100 W beschreibt, was die Quelle unter den richtigen Bedingungen bieten kann. Ihr Gerät sieht nur das Ergebnis nach Protokollverhandlung, Strombegrenzungen, Kabelwiderstand, Steckerqualität, Hub-Verlusten und Lastspitzen. Dieser Rechner konzentriert sich auf die praktische elektrische Frage: Ist nach Kabelabfall und Reservemarge genug Strom für die Hardware vorhanden, die Sie betreiben möchten?',
83
+ },
84
+ {
85
+ type: 'stats',
86
+ items: [
87
+ { label: 'USB 2.0 Standardstrom', value: '0,5 A' },
88
+ { label: 'USB-C Standardmaximum bei 5 V', value: '3 A' },
89
+ { label: 'Empfohlene Reserve', value: '20%+' },
90
+ ],
91
+ },
92
+ { type: 'title', text: 'So interpretieren Sie das Ergebnis', level: 3 },
93
+ {
94
+ type: 'table',
95
+ headers: ['Status', 'Bedeutung', 'Bester nächster Schritt'],
96
+ rows: [
97
+ ['Sicher', 'Die Last passt nach der gewählten Reserve in die Quellleistung und die geschätzte Gerätespannung bleibt gesund.', 'Setup verwenden, aber auf Wärme achten, wenn der Adapter klein oder eingeschlossen ist.'],
98
+ ['Knapp', 'Die Last liegt nahe an der reservierten Grenze oder der Kabelabfall wird bedeutsam.', 'Kabel kürzen, dickere Leiter wählen, Last reduzieren oder auf ein höheres Leistungsprofil umsteigen.'],
99
+ ['Über Budget', 'Die Last überschreitet die nutzbare Quellleistung oder die geräteseitige Spannung ist wahrscheinlich zu niedrig.', 'Stärkeres Ladegerät, aktiven Hub, kürzeres Kabel oder ein Gerät verwenden, das eine höhere USB-C PD-Spannung aushandelt.'],
100
+ ],
101
+ },
102
+ {
103
+ type: 'diagnostic',
104
+ variant: 'warning',
105
+ title: 'Wenn die Wattzahl ausreicht, das Gerät aber trotzdem zurücksetzt',
106
+ html: '<p>Der Anlaufstrom kann viel höher sein als die durchschnittliche Wattzahl auf einem Geräteetikett. Eine 5-V-Versorgung verliert bei gleicher Wattzahl schneller Spannung als ein 20-V-PD-Profil, da sie mehr Strom führen muss. Viele billige Kabel verwenden dünne Stromleiter, selbst wenn der Außenmantel robust aussieht, und busbetriebene Hubs teilen ein Upstream-Budget auf alle nachgeschalteten Geräte auf.</p>',
107
+ },
108
+ { type: 'title', text: 'Kabelspannungsabfall einfach erklärt', level: 3 },
109
+ {
110
+ type: 'paragraph',
111
+ html: 'Spannungsabfall ist der Verlust, der entsteht, wenn Strom durch den Kabelwiderstand fließt. USB-Strom hat zwei Leiter im Strompfad, daher verwendet der Rechner die Hin- und Rückleitungslänge. Ein ein Meter langes Kabel ist elektrisch zwei Meter Kupfer für die Stromschleife. Niedrigere AWG-Zahlen sind dicker und in der Regel besser für hohe Stromlasten.',
112
+ },
113
+ {
114
+ type: 'comparative',
115
+ items: [
116
+ { title: 'Kurzes dickes Kabel', description: 'Am besten für Raspberry Pi-Boards, SSD-Gehäuse, Entwicklungskits und USB-C-Docks mit Spitzenstromaufnahme.' },
117
+ { title: 'Langes dünnes Kabel', description: 'Akzeptabel für Sensoren mit geringem Stromverbrauch oder langsames Laden, aber riskant für Laufwerke, LED-Lasten und Compute-Boards.' },
118
+ { title: 'Höhere Spannung PD', description: 'Reduziert den Strom für die gleiche Wattzahl, was den Kabelverlust senkt, aber nur, wenn Quelle, Kabel und Gerät dies aushandeln.' },
119
+ ],
120
+ },
121
+ {
122
+ type: 'tip',
123
+ title: 'Praktische Regel',
124
+ html: 'Wenn der Rechner sagt, dass das Setup knapp ist, behandeln Sie es als Feldwarnung. USB-Ausfälle erscheinen oft als zufällige Verbindungsabbrüche, Unterspannungsausfälle, langsames Laden, verrauschtes Audio oder Speicherfehler, bevor sie wie ein klares Stromproblem aussehen.',
125
+ },
126
+ {
127
+ type: 'summary',
128
+ title: 'Wofür dieser Rechner am besten geeignet ist',
129
+ items: [
130
+ 'Planung von USB-Hubs, Einplatinencomputern, externen Laufwerken, Entwicklungsboards, Leuchten, Lüftern und kleinen Laboraufbauten.',
131
+ 'Vergleich von USB 2.0-, USB 3.x-, USB-C- und USB-Power-Delivery-Quellprofilen.',
132
+ 'Abschätzung, ob ein Kabel zu lang oder zu dünn für eine Last ist.',
133
+ 'Wahl einer sinnvollen Reserve vor dem Kauf eines Ladegeräts oder aktiven Hubs.',
134
+ ],
135
+ },
136
+ ],
137
+ ui: {
138
+ profileLabel: 'USB-Quellprofil',
139
+ metricUnits: 'Metrisch',
140
+ imperialUnits: 'US',
141
+ voltageLabel: 'Quellspannung (V)',
142
+ currentLabel: 'Quellstrom (A)',
143
+ cableLengthLabel: 'Kabellänge',
144
+ wireGaugeLabel: 'Leiterquerschnitt',
145
+ deviceLoadLabel: 'Last pro Gerät (W)',
146
+ devicesLabel: 'Geräte',
147
+ headroomLabel: 'Reserve (%)',
148
+ sourcePower: 'Quellleistung',
149
+ requiredPower: 'Benötigte Leistung',
150
+ cableDrop: 'Kabelabfall',
151
+ deviceVoltage: 'Gerätespannung',
152
+ headroom: 'Reserve',
153
+ utilization: 'Auslastung',
154
+ safeStatus: 'Leistungsbudget sieht sicher aus',
155
+ tightStatus: 'Leistungsbudget ist knapp',
156
+ overStatus: 'Über Budget oder Spannungsabfall-Risiko',
157
+ safeAdvice: 'Die Last passt mit der gewählten Reserve. Qualitativ hochwertiges Kabel verwenden und Wärme bei langen Betriebszeiten prüfen.',
158
+ tightAdvice: 'Sie sind nahe am Limit. Kabellänge reduzieren, dickere Leiter verwenden, Last senken oder ein stärkeres Profil wählen.',
159
+ overAdvice: 'Dieses Setup wird wahrscheinlich unter Spannungseinbrüche oder Drosselung fallen. Aktiven Hub, stärkeres Netzteil oder ein USB-C PD-Profil mit höherer Spannung verwenden.',
160
+ busLane: 'USB-Quelle',
161
+ loadLane: 'Gerätelast',
162
+ cableLane: 'Abfall',
163
+ boardEyebrow: 'Live USB-Strompfad',
164
+ sourceSocket: 'Versorgungsbuchse',
165
+ deviceSocket: 'Hardware-Last',
166
+ energyFlow: 'Energiefluss',
167
+ reservedLabel: 'nutzbar nach Reserve',
168
+ },
169
+ };