@jjlmoya/utils-hardware 1.24.0 → 1.25.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.
- package/package.json +2 -1
- package/src/category/index.ts +3 -1
- package/src/entries.ts +7 -1
- package/src/index.ts +2 -0
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/keyboardChatterTest/bibliography.astro +15 -0
- package/src/tool/keyboardChatterTest/bibliography.ts +20 -0
- package/src/tool/keyboardChatterTest/component.astro +353 -0
- package/src/tool/keyboardChatterTest/entry.ts +30 -0
- package/src/tool/keyboardChatterTest/i18n/de.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/en.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/es.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/fr.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/id.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/it.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/ja.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/ko.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/nl.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/pl.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/pt.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/ru.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/sv.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/tr.ts +232 -0
- package/src/tool/keyboardChatterTest/i18n/zh.ts +232 -0
- package/src/tool/keyboardChatterTest/index.ts +12 -0
- package/src/tool/keyboardChatterTest/keyboard-chatter-test.css +512 -0
- package/src/tool/keyboardChatterTest/logic.ts +23 -0
- package/src/tool/keyboardChatterTest/seo.astro +16 -0
- package/src/tool/keyboardChatterTest/ui.ts +34 -0
- package/src/tool/webBluetoothBleScanner/bibliography.astro +14 -0
- package/src/tool/webBluetoothBleScanner/bibliography.ts +16 -0
- package/src/tool/webBluetoothBleScanner/component.astro +339 -0
- package/src/tool/webBluetoothBleScanner/entry.ts +29 -0
- package/src/tool/webBluetoothBleScanner/i18n/de.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/en.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/es.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/fr.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/id.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/it.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/ja.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/ko.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/nl.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/pl.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/pt.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/ru.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/sv.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/tr.ts +233 -0
- package/src/tool/webBluetoothBleScanner/i18n/zh.ts +233 -0
- package/src/tool/webBluetoothBleScanner/index.ts +11 -0
- package/src/tool/webBluetoothBleScanner/logic.ts +79 -0
- package/src/tool/webBluetoothBleScanner/seo.astro +15 -0
- package/src/tool/webBluetoothBleScanner/ui.ts +41 -0
- package/src/tool/webBluetoothBleScanner/web-bluetooth-ble-scanner.css +406 -0
- package/src/tools.ts +3 -1
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Icon } from 'astro-icon/components';
|
|
3
|
+
import type { KnownLocale } from '../../types';
|
|
4
|
+
import type { WebBluetoothBleScannerUI } from './ui';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
ui?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { ui } = Astro.props;
|
|
12
|
+
const t = (ui ?? {}) as WebBluetoothBleScannerUI;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<div
|
|
16
|
+
class="wbb-root"
|
|
17
|
+
data-config={JSON.stringify({
|
|
18
|
+
unsupportedTitle: t.unsupportedTitle,
|
|
19
|
+
unsupportedBody: t.unsupportedBody,
|
|
20
|
+
secureContext: t.secureContext,
|
|
21
|
+
scanning: t.scanning,
|
|
22
|
+
reconnect: t.reconnect,
|
|
23
|
+
disconnect: t.disconnect,
|
|
24
|
+
deviceLabel: t.deviceLabel,
|
|
25
|
+
nameFallback: t.nameFallback,
|
|
26
|
+
idLabel: t.idLabel,
|
|
27
|
+
servicesLabel: t.servicesLabel,
|
|
28
|
+
noServices: t.noServices,
|
|
29
|
+
statusIdle: t.statusIdle,
|
|
30
|
+
statusPermission: t.statusPermission,
|
|
31
|
+
statusConnecting: t.statusConnecting,
|
|
32
|
+
statusConnected: t.statusConnected,
|
|
33
|
+
statusDisconnected: t.statusDisconnected,
|
|
34
|
+
statusCancelled: t.statusCancelled,
|
|
35
|
+
statusUnavailable: t.statusUnavailable,
|
|
36
|
+
statusError: t.statusError,
|
|
37
|
+
signalUnknown: t.signalUnknown,
|
|
38
|
+
gattUnavailable: t.gattUnavailable,
|
|
39
|
+
customServiceName: t.customServiceName,
|
|
40
|
+
serviceGenericAccess: t.serviceGenericAccess,
|
|
41
|
+
serviceGenericAttribute: t.serviceGenericAttribute,
|
|
42
|
+
serviceDeviceInformation: t.serviceDeviceInformation,
|
|
43
|
+
serviceHeartRate: t.serviceHeartRate,
|
|
44
|
+
serviceBattery: t.serviceBattery,
|
|
45
|
+
serviceHumanInterfaceDevice: t.serviceHumanInterfaceDevice,
|
|
46
|
+
serviceCyclingSpeedCadence: t.serviceCyclingSpeedCadence,
|
|
47
|
+
serviceEnvironmentalSensing: t.serviceEnvironmentalSensing,
|
|
48
|
+
serviceUserData: t.serviceUserData,
|
|
49
|
+
serviceFitnessMachine: t.serviceFitnessMachine,
|
|
50
|
+
uuidHelp: t.uuidHelp,
|
|
51
|
+
serviceCountSingular: t.serviceCountSingular,
|
|
52
|
+
serviceCountPlural: t.serviceCountPlural,
|
|
53
|
+
})}
|
|
54
|
+
>
|
|
55
|
+
<section class="wbb-shell">
|
|
56
|
+
<div class="wbb-radar" aria-hidden="true">
|
|
57
|
+
<span><Icon name="mdi:bluetooth" /></span>
|
|
58
|
+
<i></i>
|
|
59
|
+
<b></b>
|
|
60
|
+
<em></em>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="wbb-console">
|
|
64
|
+
<div class="wbb-status">
|
|
65
|
+
<span data-status-dot></span>
|
|
66
|
+
<strong data-status>{t.statusIdle}</strong>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<button type="button" class="wbb-scan" data-scan>
|
|
70
|
+
<Icon name="mdi:bluetooth-searching" />
|
|
71
|
+
<span>{t.scanButton}</span>
|
|
72
|
+
</button>
|
|
73
|
+
|
|
74
|
+
<aside class="wbb-privacy">
|
|
75
|
+
<Icon name="mdi:shield-lock-outline" />
|
|
76
|
+
<span>
|
|
77
|
+
<strong>{t.privacyTitle}</strong>
|
|
78
|
+
<small>{t.privacyBody}</small>
|
|
79
|
+
</span>
|
|
80
|
+
</aside>
|
|
81
|
+
|
|
82
|
+
<div class="wbb-device-list" data-device-list aria-live="polite"></div>
|
|
83
|
+
|
|
84
|
+
<p class="wbb-hint">{t.compatibilityHint}</p>
|
|
85
|
+
</div>
|
|
86
|
+
</section>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<link rel="stylesheet" href="./web-bluetooth-ble-scanner.css" />
|
|
90
|
+
|
|
91
|
+
<script>
|
|
92
|
+
interface Config {
|
|
93
|
+
unsupportedTitle: string;
|
|
94
|
+
unsupportedBody: string;
|
|
95
|
+
secureContext: string;
|
|
96
|
+
scanning: string;
|
|
97
|
+
reconnect: string;
|
|
98
|
+
disconnect: string;
|
|
99
|
+
deviceLabel: string;
|
|
100
|
+
nameFallback: string;
|
|
101
|
+
idLabel: string;
|
|
102
|
+
servicesLabel: string;
|
|
103
|
+
noServices: string;
|
|
104
|
+
statusIdle: string;
|
|
105
|
+
statusPermission: string;
|
|
106
|
+
statusConnecting: string;
|
|
107
|
+
statusConnected: string;
|
|
108
|
+
statusDisconnected: string;
|
|
109
|
+
statusCancelled: string;
|
|
110
|
+
statusUnavailable: string;
|
|
111
|
+
statusError: string;
|
|
112
|
+
signalUnknown: string;
|
|
113
|
+
gattUnavailable: string;
|
|
114
|
+
customServiceName: string;
|
|
115
|
+
serviceGenericAccess: string;
|
|
116
|
+
serviceGenericAttribute: string;
|
|
117
|
+
serviceDeviceInformation: string;
|
|
118
|
+
serviceHeartRate: string;
|
|
119
|
+
serviceBattery: string;
|
|
120
|
+
serviceHumanInterfaceDevice: string;
|
|
121
|
+
serviceCyclingSpeedCadence: string;
|
|
122
|
+
serviceEnvironmentalSensing: string;
|
|
123
|
+
serviceUserData: string;
|
|
124
|
+
serviceFitnessMachine: string;
|
|
125
|
+
uuidHelp: string;
|
|
126
|
+
serviceCountSingular: string;
|
|
127
|
+
serviceCountPlural: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type BluetoothRequestDeviceFilter = { services?: BluetoothServiceUUID[]; name?: string; namePrefix?: string };
|
|
131
|
+
interface BluetoothRemoteGATTServiceLike {
|
|
132
|
+
uuid: string;
|
|
133
|
+
}
|
|
134
|
+
interface BluetoothRemoteGATTServerLike {
|
|
135
|
+
connected: boolean;
|
|
136
|
+
connect: () => Promise<BluetoothRemoteGATTServerLike>;
|
|
137
|
+
disconnect: () => void;
|
|
138
|
+
getPrimaryServices: () => Promise<BluetoothRemoteGATTServiceLike[]>;
|
|
139
|
+
}
|
|
140
|
+
interface BluetoothDeviceLike extends EventTarget {
|
|
141
|
+
id: string;
|
|
142
|
+
name?: string;
|
|
143
|
+
gatt?: BluetoothRemoteGATTServerLike;
|
|
144
|
+
}
|
|
145
|
+
interface BluetoothNavigator {
|
|
146
|
+
bluetooth?: {
|
|
147
|
+
getAvailability?: () => Promise<boolean>;
|
|
148
|
+
requestDevice: (options: {
|
|
149
|
+
acceptAllDevices?: boolean;
|
|
150
|
+
filters?: BluetoothRequestDeviceFilter[];
|
|
151
|
+
optionalServices?: BluetoothServiceUUID[];
|
|
152
|
+
}) => Promise<BluetoothDeviceLike>;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const root = document.querySelector<HTMLElement>('.wbb-root');
|
|
157
|
+
const config = JSON.parse(root?.dataset.config ?? '{}') as Config;
|
|
158
|
+
const button = root?.querySelector<HTMLButtonElement>('[data-scan]');
|
|
159
|
+
const status = root?.querySelector<HTMLElement>('[data-status]');
|
|
160
|
+
const statusDot = root?.querySelector<HTMLElement>('[data-status-dot]');
|
|
161
|
+
const list = root?.querySelector<HTMLElement>('[data-device-list]');
|
|
162
|
+
const optionalServices: BluetoothServiceUUID[] = [
|
|
163
|
+
'generic_access',
|
|
164
|
+
'generic_attribute',
|
|
165
|
+
'device_information',
|
|
166
|
+
'battery_service',
|
|
167
|
+
'heart_rate',
|
|
168
|
+
'human_interface_device',
|
|
169
|
+
'cycling_speed_and_cadence',
|
|
170
|
+
'environmental_sensing',
|
|
171
|
+
'user_data',
|
|
172
|
+
'fitness_machine',
|
|
173
|
+
];
|
|
174
|
+
const serviceNames: Record<string, string> = {
|
|
175
|
+
'00001800-0000-1000-8000-00805f9b34fb': config.serviceGenericAccess,
|
|
176
|
+
'00001801-0000-1000-8000-00805f9b34fb': config.serviceGenericAttribute,
|
|
177
|
+
'0000180a-0000-1000-8000-00805f9b34fb': config.serviceDeviceInformation,
|
|
178
|
+
'0000180d-0000-1000-8000-00805f9b34fb': config.serviceHeartRate,
|
|
179
|
+
'0000180f-0000-1000-8000-00805f9b34fb': config.serviceBattery,
|
|
180
|
+
'00001812-0000-1000-8000-00805f9b34fb': config.serviceHumanInterfaceDevice,
|
|
181
|
+
'00001816-0000-1000-8000-00805f9b34fb': config.serviceCyclingSpeedCadence,
|
|
182
|
+
'0000181a-0000-1000-8000-00805f9b34fb': config.serviceEnvironmentalSensing,
|
|
183
|
+
'0000181c-0000-1000-8000-00805f9b34fb': config.serviceUserData,
|
|
184
|
+
'00001826-0000-1000-8000-00805f9b34fb': config.serviceFitnessMachine,
|
|
185
|
+
};
|
|
186
|
+
let activeDevice: BluetoothDeviceLike | undefined;
|
|
187
|
+
|
|
188
|
+
function hasBluetoothSupport() {
|
|
189
|
+
return Boolean((navigator as BluetoothNavigator).bluetooth?.requestDevice);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function hasAvailableBluetoothAdapter() {
|
|
193
|
+
const bluetooth = (navigator as BluetoothNavigator).bluetooth;
|
|
194
|
+
if (!bluetooth?.getAvailability) return true;
|
|
195
|
+
return bluetooth.getAvailability();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function setStatus(message: string, state: 'idle' | 'busy' | 'ok' | 'warn' | 'error' = 'idle') {
|
|
199
|
+
if (status) status.textContent = message;
|
|
200
|
+
statusDot?.setAttribute('data-state', state);
|
|
201
|
+
root?.setAttribute('data-state', state);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function serviceName(uuid: string) {
|
|
205
|
+
return serviceNames[uuid.toLowerCase()] ?? config.customServiceName;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function renderSupportWarning(title: string, body: string) {
|
|
209
|
+
if (!list) return;
|
|
210
|
+
list.innerHTML = `
|
|
211
|
+
<article class="wbb-card wbb-warning">
|
|
212
|
+
<div>
|
|
213
|
+
<strong>${title}</strong>
|
|
214
|
+
<p>${body}</p>
|
|
215
|
+
</div>
|
|
216
|
+
</article>
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function renderServiceRows(services: BluetoothRemoteGATTServiceLike[]) {
|
|
221
|
+
if (services.length === 0) return `<li class="wbb-empty">${config.noServices}</li>`;
|
|
222
|
+
|
|
223
|
+
return services.map((service) => `
|
|
224
|
+
<li>
|
|
225
|
+
<span>${serviceName(service.uuid)}</span>
|
|
226
|
+
<code>${service.uuid}</code>
|
|
227
|
+
</li>
|
|
228
|
+
`).join('');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function renderDeviceCard(device: BluetoothDeviceLike, services: BluetoothRemoteGATTServiceLike[], connected: boolean) {
|
|
232
|
+
const name = device.name || config.nameFallback;
|
|
233
|
+
const countLabel = services.length === 1 ? config.serviceCountSingular : config.serviceCountPlural;
|
|
234
|
+
|
|
235
|
+
return `
|
|
236
|
+
<article class="wbb-card">
|
|
237
|
+
<div class="wbb-card-head">
|
|
238
|
+
<span>${config.deviceLabel}</span>
|
|
239
|
+
<strong>${name}</strong>
|
|
240
|
+
</div>
|
|
241
|
+
<dl>
|
|
242
|
+
<div>
|
|
243
|
+
<dt>${config.idLabel}</dt>
|
|
244
|
+
<dd><code>${device.id}</code></dd>
|
|
245
|
+
</div>
|
|
246
|
+
<div>
|
|
247
|
+
<dt>${config.servicesLabel}</dt>
|
|
248
|
+
<dd>${services.length} ${countLabel}</dd>
|
|
249
|
+
</div>
|
|
250
|
+
</dl>
|
|
251
|
+
<ul>${renderServiceRows(services)}</ul>
|
|
252
|
+
<p>${config.uuidHelp}</p>
|
|
253
|
+
<button type="button" data-disconnect>${connected ? config.disconnect : config.reconnect}</button>
|
|
254
|
+
</article>
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function renderDevice(device: BluetoothDeviceLike, services: BluetoothRemoteGATTServiceLike[], connected: boolean) {
|
|
259
|
+
if (!list) return;
|
|
260
|
+
list.innerHTML = renderDeviceCard(device, services, connected);
|
|
261
|
+
|
|
262
|
+
list.querySelector<HTMLButtonElement>('[data-disconnect]')?.addEventListener('click', () => {
|
|
263
|
+
if (activeDevice?.gatt?.connected) {
|
|
264
|
+
activeDevice.gatt.disconnect();
|
|
265
|
+
setStatus(config.statusDisconnected, 'warn');
|
|
266
|
+
renderDevice(activeDevice, services, false);
|
|
267
|
+
} else {
|
|
268
|
+
scan();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function canStartScan() {
|
|
274
|
+
if (!hasBluetoothSupport()) {
|
|
275
|
+
setStatus(config.statusError, 'error');
|
|
276
|
+
renderSupportWarning(config.unsupportedTitle, config.unsupportedBody);
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
if (!window.isSecureContext) {
|
|
280
|
+
setStatus(config.statusError, 'error');
|
|
281
|
+
renderSupportWarning(config.unsupportedTitle, config.secureContext);
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
if (!(await hasAvailableBluetoothAdapter())) {
|
|
285
|
+
setStatus(config.statusUnavailable, 'error');
|
|
286
|
+
renderSupportWarning(config.unsupportedTitle, config.statusUnavailable);
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function requestSelectedDevice() {
|
|
294
|
+
setStatus(config.statusPermission, 'busy');
|
|
295
|
+
return (navigator as BluetoothNavigator).bluetooth!.requestDevice({
|
|
296
|
+
acceptAllDevices: true,
|
|
297
|
+
optionalServices,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function connectDevice(device: BluetoothDeviceLike) {
|
|
302
|
+
activeDevice = device;
|
|
303
|
+
device.addEventListener('gattserverdisconnected', () => {
|
|
304
|
+
setStatus(config.statusDisconnected, 'warn');
|
|
305
|
+
});
|
|
306
|
+
setStatus(config.statusConnecting, 'busy');
|
|
307
|
+
const server = await device.gatt?.connect();
|
|
308
|
+
if (!server) {
|
|
309
|
+
setStatus(config.gattUnavailable, 'warn');
|
|
310
|
+
renderDevice(device, [], false);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const services = await server.getPrimaryServices();
|
|
314
|
+
renderDevice(device, services, server.connected);
|
|
315
|
+
setStatus(config.statusConnected, 'ok');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function handleScanError(error: unknown) {
|
|
319
|
+
const isNotFound = error instanceof DOMException && error.name === 'NotFoundError';
|
|
320
|
+
setStatus(isNotFound ? config.statusCancelled : config.statusError, isNotFound ? 'warn' : 'error');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function scan() {
|
|
324
|
+
if (!(await canStartScan())) return;
|
|
325
|
+
try {
|
|
326
|
+
if (button) button.disabled = true;
|
|
327
|
+
await connectDevice(await requestSelectedDevice());
|
|
328
|
+
} catch (error) {
|
|
329
|
+
handleScanError(error);
|
|
330
|
+
} finally {
|
|
331
|
+
if (button) button.disabled = false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
button?.addEventListener('click', scan);
|
|
336
|
+
if (!hasBluetoothSupport()) {
|
|
337
|
+
renderSupportWarning(config.unsupportedTitle, config.unsupportedBody);
|
|
338
|
+
}
|
|
339
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HardwareToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
+
import type { WebBluetoothBleScannerUI } from './ui';
|
|
3
|
+
|
|
4
|
+
export type WebBluetoothBleScannerLocaleContent = ToolLocaleContent<WebBluetoothBleScannerUI>;
|
|
5
|
+
|
|
6
|
+
export const webBluetoothBleScanner: HardwareToolEntry<WebBluetoothBleScannerUI> = {
|
|
7
|
+
id: 'web-bluetooth-ble-scanner',
|
|
8
|
+
icons: {
|
|
9
|
+
bg: 'mdi:bluetooth',
|
|
10
|
+
fg: 'mdi:access-point-network',
|
|
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,233 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { WebBluetoothBleScannerUI } from '../ui';
|
|
4
|
+
import { bibliography } from '../bibliography';
|
|
5
|
+
|
|
6
|
+
const slug = 'web-bluetooth-ble-suche';
|
|
7
|
+
const title = 'Web Bluetooth BLE Suche';
|
|
8
|
+
const description = 'Scannen Sie Bluetooth Low Energy Geräte in der Nähe aus dem Browser, prüfen Sie die offengelegten GATT-Service-UUIDs und testen Sie, ob Ihre IoT- oder Wearable-Hardware auffindbar ist.';
|
|
9
|
+
|
|
10
|
+
const faqData = [
|
|
11
|
+
{
|
|
12
|
+
question: 'Kann eine Website ohne Erlaubnis Bluetooth-Geräte scannen?',
|
|
13
|
+
answer: 'Nein. Web Bluetooth erfordert immer eine Nutzergeste und eine Berechtigungsauswahl des Browsers. Dieses Tool sieht nur das Gerät, das Sie ausdrücklich auswählen, und speichert keine MAC-Adressen, Geräte-IDs oder Scanergebnisse.',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
question: 'Warum zeigt der Scanner nicht jedes BLE-Gerät in der Nähe an?',
|
|
17
|
+
answer: 'Browser stellen Bluetooth bewusst über eine Berechtigungsauswahl bereit, nicht als stillen Hintergrundscanner. Manche Geräte beenden zudem das Advertising, verbergen ihren Namen, erfordern Pairing oder legen Dienste erst nach einer Verbindung offen.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
question: 'Welche Browser unterstützen Web Bluetooth?',
|
|
21
|
+
answer: 'Web Bluetooth wird am besten in Chromium-basierten Desktop-Browsern wie Chrome und Edge unterstützt. Es erfordert in der Regel HTTPS oder localhost und ist in vielen Firefox-, Safari- und iOS-Browser-Konfigurationen nicht verfügbar.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
question: 'Kann dies private Sensordaten von einem Wearable lesen?',
|
|
25
|
+
answer: 'Nur wenn das Gerät kompatible GATT-Dienste bereitstellt und der Browser den Zugriff gewährt. Viele kommerzielle Wearables benötigen Hersteller-Apps, Verschlüsselung, Bonding oder proprietäre Eigenschaften, die ein generischer Browser-Scanner nicht entschlüsseln kann.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
question: 'Was sind GATT-Service-UUIDs?',
|
|
29
|
+
answer: 'Eine GATT-Service-UUID identifiziert eine Gruppe von Bluetooth-Low-Energy-Funktionen wie Batterie-Service, Herzfrequenz, Geräteinformationen oder einen benutzerdefinierten Herstellerdienst, der von Maker- und IoT-Hardware genutzt wird.',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const howToData = [
|
|
34
|
+
{
|
|
35
|
+
name: 'Kompatiblen Browser verwenden',
|
|
36
|
+
text: 'Öffnen Sie das Tool in Chrome oder Edge über HTTPS oder localhost und stellen Sie sicher, dass Bluetooth am Computer oder Telefon aktiviert ist.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Hardware in den Advertising-Modus versetzen',
|
|
40
|
+
text: 'Aktivieren Sie das BLE-Gerät, schalten Sie es aus und wieder ein, drücken Sie die Pairing-Taste oder öffnen Sie den Advertising-Modus, damit es in der Berechtigungsauswahl des Browsers erscheint.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Umgebung scannen',
|
|
44
|
+
text: 'Klicken Sie auf Umgebung scannen und wählen Sie das zu prüfende BLE-Gerät aus. Der Berechtigungsdialog des Browsers steuert genau, welches Gerät für die Seite sichtbar wird.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'GATT-Dienste auslesen',
|
|
48
|
+
text: 'Überprüfen Sie nach der Verbindung die Service-UUID-Karten, um Standard-Bluetooth-Profile, benutzerdefinierte Firmware-Dienste und die Frage zu identifizieren, ob das Gerät den erwarteten Datenpfad bereitstellt.',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
53
|
+
'@context': 'https://schema.org',
|
|
54
|
+
'@type': 'FAQPage',
|
|
55
|
+
mainEntity: faqData.map((item) => ({
|
|
56
|
+
'@type': 'Question',
|
|
57
|
+
name: item.question,
|
|
58
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const howToSchema: WithContext<HowTo> = {
|
|
63
|
+
'@context': 'https://schema.org',
|
|
64
|
+
'@type': 'HowTo',
|
|
65
|
+
name: title,
|
|
66
|
+
description,
|
|
67
|
+
step: howToData.map((step, i) => ({
|
|
68
|
+
'@type': 'HowToStep',
|
|
69
|
+
position: i + 1,
|
|
70
|
+
name: step.name,
|
|
71
|
+
text: step.text,
|
|
72
|
+
})),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
76
|
+
'@context': 'https://schema.org',
|
|
77
|
+
'@type': 'SoftwareApplication',
|
|
78
|
+
name: title,
|
|
79
|
+
description,
|
|
80
|
+
applicationCategory: 'DeveloperApplication',
|
|
81
|
+
operatingSystem: 'All',
|
|
82
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
|
83
|
+
inLanguage: 'de',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const content: ToolLocaleContent<WebBluetoothBleScannerUI> = {
|
|
87
|
+
slug,
|
|
88
|
+
title,
|
|
89
|
+
description,
|
|
90
|
+
faq: faqData,
|
|
91
|
+
bibliography,
|
|
92
|
+
howTo: howToData,
|
|
93
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
94
|
+
seo: [
|
|
95
|
+
{
|
|
96
|
+
type: 'title',
|
|
97
|
+
text: 'BLE-Tester online für IoT, Wearables und Maker-Hardware',
|
|
98
|
+
level: 2,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'paragraph',
|
|
102
|
+
html: 'Dieser Web-Bluetooth-Scanner ermöglicht es Ihnen zu testen, ob ein nahegelegenes Bluetooth-Low-Energy-Gerät aus einem Browser auffindbar ist und welche GATT-Dienste es nach erteilter Berechtigung bereitstellt. Er ist nützlich beim Debuggen von ESP32-Firmware, Arduino-BLE-Sketches, intelligenten Sensoren, Fitness-Wearables, Tastaturen, benutzerdefinierten Beacons, Umweltmonitoren und Prototyp-Hardware, bevor Sie eine native mobile App erstellen.',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'message',
|
|
106
|
+
title: 'Datenschutzmodell',
|
|
107
|
+
html: 'GameBob speichert keine MAC-Adressen, Geräte-IDs, Namen, UUIDs, Signaldaten oder Scanverläufe. Die Berechtigungsauswahl des Browsers bestimmt, auf welches einzelne Gerät die Seite zugreifen darf, und die Ergebnisse verbleiben in Ihrer aktuellen Browser-Sitzung.',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: 'table',
|
|
111
|
+
headers: ['Was Sie sehen', 'Was es bedeutet', 'Was als Nächstes zu prüfen ist'],
|
|
112
|
+
rows: [
|
|
113
|
+
['Gerätename', 'Der per Advertising übertragene Bluetooth-Name, sofern die Hardware einen sendet.', 'Falls leer, überprüfen Sie die Advertising-Daten der Firmware oder nutzen Sie während des Tests ein bekanntes Namenspräfix.'],
|
|
114
|
+
['Geräte-ID', 'Eine browserbezogene Kennung, nicht die tatsächliche öffentliche MAC-Adresse.', 'Nur für diese Browser-Sitzung verwenden; nicht als universelle Hardware-Seriennummer behandeln.'],
|
|
115
|
+
['GATT-Service-UUIDs', 'Die Dienstgruppen, die nach Annahme der Verbindung offengelegt werden.', 'Vergleichen Sie Standard-UUIDs mit der Bluetooth-SIG-Liste oder Ihrer Firmware-Servicetabelle.'],
|
|
116
|
+
['Benutzerdefinierter Dienst', 'Eine hersteller- oder projektspezifische UUID.', 'Öffnen Sie Ihre Firmware, das mobile App-Profil oder die BLE-Dokumentation, um Eigenschaften und Berechtigungen zuzuordnen.'],
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'title',
|
|
121
|
+
text: 'Warum sich browserbasiertes Bluetooth-Scannen unterscheidet',
|
|
122
|
+
level: 3,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: 'paragraph',
|
|
126
|
+
html: 'Native BLE-Scanner-Apps zeigen oft fortlaufende Advertisements vieler nahegelegener Geräte an. Web Bluetooth ist bewusst strenger: Die Seite muss in einem sicheren Kontext geöffnet werden, der Scan muss durch einen Nutzerklick gestartet werden und der Browser zeigt eine Berechtigungsauswahl an. Dies schützt Nutzer vor stillem Tracking und gibt Entwicklern dennoch einen praktischen Weg, um von JavaScript aus eine Verbindung zur ausgewählten BLE-Hardware herzustellen.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: 'comparative',
|
|
130
|
+
items: [
|
|
131
|
+
{
|
|
132
|
+
title: 'Web Bluetooth Scanner',
|
|
133
|
+
description: 'Am besten für schnelle Firmware-Validierung, Demos, Support-Abläufe, Unterrichtslabore und browserbasierte Diagnose geeignet, wenn Installationshürden eine Rolle spielen.',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
title: 'Native BLE App',
|
|
137
|
+
description: 'Besser für Hintergrundscans, RSSI-Protokollierung, Pairing-Workflows, verschlüsselte Herstellerprotokolle, große Eigenschaftsbäume und langfristige Felddiagnose.',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'title',
|
|
143
|
+
text: 'Häufige Gründe, warum ein BLE-Gerät nicht erscheint',
|
|
144
|
+
level: 3,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'list',
|
|
148
|
+
items: [
|
|
149
|
+
'Bluetooth ist auf Betriebssystemebene deaktiviert oder der Browser besitzt keine Bluetooth-Berechtigung.',
|
|
150
|
+
'Das Gerät ist mit einem anderen Telefon, Laptop, einer Hersteller-App oder einem Gateway verbunden und hat das Advertising eingestellt.',
|
|
151
|
+
'Die Hardware sendet Advertisements nur für ein kurzes Zeitfenster nach dem Start oder nach dem Drücken einer Pairing-Taste.',
|
|
152
|
+
'Der Browser ist nicht Chromium-basiert, die Seite wird nicht über HTTPS bereitgestellt oder die Plattform blockiert Web Bluetooth.',
|
|
153
|
+
'Die Firmware überträgt Herstellerdaten, verbirgt aber den lokalen Namen, sodass die Auswahl ein namenloses Gerät anzeigen kann.',
|
|
154
|
+
'Das Gerät erfordert Bonding, Verschlüsselung oder proprietäre Authentifizierung, bevor Dienste lesbar werden.',
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'title',
|
|
159
|
+
text: 'So verwenden Sie GATT-UUIDs beim Debugging',
|
|
160
|
+
level: 3,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: 'paragraph',
|
|
164
|
+
html: 'Eine erfolgreiche Verbindung mit Service-UUIDs zeigt Ihnen, dass der Browser das Peripheriegerät erreichen kann und dass das Peripheriegerät zumindest einen Teil seiner GATT-Tabelle offenlegt. Standarddienste wie Batterie-Service, Geräteinformationen, Herzfrequenz, Human Interface Device und Umweltsensorik sind leicht zu erkennen. Benutzerdefinierte UUIDs verweisen meist auf firmwarespezifische Funktionen und benötigen die Eigenschaftstabelle aus Ihrem Quellcode oder der Herstellerdokumentation.',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
type: 'table',
|
|
168
|
+
headers: ['Symptom', 'Wahrscheinliche Ursache', 'Praktische Abhilfe'],
|
|
169
|
+
rows: [
|
|
170
|
+
['Berechtigungsauswahl ist leer', 'Gerät sendet kein Advertising oder Browser-Unterstützung fehlt.', 'Gerät neu starten, Pairing-Modus aktivieren, näher herangehen oder in Chrome/Edge erneut versuchen.'],
|
|
171
|
+
['Verbindung schlägt sofort fehl', 'Gerät ist belegt, außer Reichweite oder lehnt die Browser-Verbindung ab.', 'Hersteller-Apps trennen und das Peripheriegerät in der Nähe des Computers halten.'],
|
|
172
|
+
['Keine Dienste aufgelistet', 'GATT ist nicht verfügbar, Dienste sind verborgen oder der Zugriff wurde für diese UUIDs nicht gewährt.', 'Bekannte optionale Dienste in Firmware-Tests hinzufügen oder mit einem nativen BLE-Tool prüfen.'],
|
|
173
|
+
['Nur benutzerdefinierte UUIDs erscheinen', 'Die Hardware verwendet herstellerspezifische Firmware-Dienste.', 'UUIDs Quellcode-Konstanten zuordnen und Lese-/Schreibberechtigungen der Eigenschaften dokumentieren.'],
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: 'title',
|
|
178
|
+
text: 'Sicherheits- und Datenschutzgrenzen',
|
|
179
|
+
level: 3,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
type: 'list',
|
|
183
|
+
items: [
|
|
184
|
+
'Die Seite kann nicht stillschweigend nahegelegene Bluetooth-Geräte im Hintergrund erfassen.',
|
|
185
|
+
'Der Browser kann echte MAC-Adressen verbergen und stattdessen eine sitzungsbezogene Geräte-ID bereitstellen.',
|
|
186
|
+
'Der Zugriff beginnt erst, nachdem der Nutzer auf die Scan-Schaltfläche klickt und ein Gerät auswählt.',
|
|
187
|
+
'Ergebnisse werden von GameBob weder hochgeladen noch gespeichert.',
|
|
188
|
+
'Sensible kommerzielle Geräte können eine Verschlüsselung oder einen Hersteller-Pairing-Ablauf erfordern, den dieser generische Scanner nicht umgehen kann.',
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
ui: {
|
|
193
|
+
unsupportedTitle: 'Web Bluetooth ist nicht verfügbar',
|
|
194
|
+
unsupportedBody: 'Versuchen Sie Chrome oder Edge auf dem Desktop oder unter Android, aktivieren Sie Bluetooth und öffnen Sie die Seite über HTTPS oder localhost.',
|
|
195
|
+
secureContext: 'Web Bluetooth erfordert eine sichere HTTPS-Seite oder localhost. Laden Sie das Tool von einer sicheren Quelle neu und versuchen Sie es erneut.',
|
|
196
|
+
scanButton: 'Umgebung scannen',
|
|
197
|
+
scanning: 'Scannen läuft',
|
|
198
|
+
reconnect: 'Erneut scannen',
|
|
199
|
+
disconnect: 'Trennen',
|
|
200
|
+
privacyTitle: 'Datenschutz durch Design',
|
|
201
|
+
privacyBody: 'GameBob speichert keine MAC-Adressen, Geräte-IDs, Namen, UUIDs oder Scanverläufe. Der Browser legt nur das von Ihnen gewählte Gerät offen.',
|
|
202
|
+
deviceLabel: 'Ausgewähltes Gerät',
|
|
203
|
+
nameFallback: 'Unbenanntes BLE-Gerät',
|
|
204
|
+
idLabel: 'Browser-Geräte-ID',
|
|
205
|
+
servicesLabel: 'GATT-Dienste',
|
|
206
|
+
noServices: 'Für diese Verbindung wurden keine lesbaren Primärdienste zurückgegeben.',
|
|
207
|
+
statusIdle: 'Bereit zum Scannen nahegelegener BLE-Hardware',
|
|
208
|
+
statusPermission: 'Warten auf die Berechtigungsauswahl des Browsers',
|
|
209
|
+
statusConnecting: 'Verbinden mit dem ausgewählten BLE-Gerät',
|
|
210
|
+
statusConnected: 'Verbunden und Dienste geladen',
|
|
211
|
+
statusDisconnected: 'Gerät getrennt',
|
|
212
|
+
statusCancelled: 'Kein BLE-Gerät ausgewählt oder Bluetooth ist auf diesem Gerät ausgeschaltet bzw. nicht verfügbar.',
|
|
213
|
+
statusUnavailable: 'Bluetooth scheint auf diesem Gerät ausgeschaltet, blockiert oder nicht vorhanden zu sein. Aktivieren Sie Bluetooth oder versuchen Sie es mit Hardware, die einen BLE-Adapter besitzt.',
|
|
214
|
+
statusError: 'Bluetooth-Scan fehlgeschlagen',
|
|
215
|
+
signalUnknown: 'Die Signalstärke wird durch die Browser-Auswahl gesteuert',
|
|
216
|
+
gattUnavailable: 'Dieses Gerät hat dem Browser keinen GATT-Server bereitgestellt',
|
|
217
|
+
customServiceName: 'Benutzerdefinierter oder herstellerspezifischer Dienst',
|
|
218
|
+
serviceGenericAccess: 'Generic Access',
|
|
219
|
+
serviceGenericAttribute: 'Generic Attribute',
|
|
220
|
+
serviceDeviceInformation: 'Geräteinformationen',
|
|
221
|
+
serviceHeartRate: 'Herzfrequenz',
|
|
222
|
+
serviceBattery: 'Batterie-Service',
|
|
223
|
+
serviceHumanInterfaceDevice: 'Human Interface Device',
|
|
224
|
+
serviceCyclingSpeedCadence: 'Radfahren Geschwindigkeit und Trittfrequenz',
|
|
225
|
+
serviceEnvironmentalSensing: 'Umweltsensorik',
|
|
226
|
+
serviceUserData: 'Nutzerdaten',
|
|
227
|
+
serviceFitnessMachine: 'Fitnessgerät',
|
|
228
|
+
uuidHelp: 'UUIDs identifizieren Bluetooth-Dienste. Standarddienste werden automatisch benannt; herstellerspezifische UUIDs benötigen Ihre Firmware- oder Gerätedokumentation.',
|
|
229
|
+
compatibilityHint: 'Funktioniert am besten in Chromium-basierten Browsern mit aktiviertem Bluetooth. Web Bluetooth ist bewusst berechtigungsbeschränkt und zeigt möglicherweise nicht jeden nahegelegenen Advertiser an.',
|
|
230
|
+
serviceCountSingular: 'Dienst',
|
|
231
|
+
serviceCountPlural: 'Dienste',
|
|
232
|
+
},
|
|
233
|
+
};
|