joy-con-webhid
Version:
Joy-Con over WebHID
1,342 lines (1,341 loc) • 42 kB
JavaScript
const Et = async (t) => {
const e = ({
subcommand: u,
expectedReport: a,
timeoutErrorMessage: r = "timeout."
}) => (p) => new Promise((q, I) => {
const b = setTimeout(() => {
p.removeEventListener("inputreport", L), I(new Error(r));
}, 5e3), L = (C) => {
const O = C;
if (O.reportId !== 33)
return;
const A = new Uint8Array(O.data.buffer);
for (const [k, x] of Object.entries(a))
if (A[Number(k) - 1] !== x)
return;
p.removeEventListener("inputreport", L), clearTimeout(b), setTimeout(q, 50);
};
p.addEventListener("inputreport", L), (async () => await p.sendReport(
1,
new Uint8Array([
0,
0,
0,
0,
0,
0,
0,
0,
0,
...u
])
))();
}), n = e({
subcommand: [34, 1],
expectedReport: {
13: 128,
14: 34
}
}), o = e({
subcommand: [
33,
33,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
243
],
expectedReport: {
14: 33
}
}), l = e({
subcommand: [89],
expectedReport: {
14: 89,
16: 32
},
timeoutErrorMessage: "ring-con not found."
}), h = e({
subcommand: [
92,
6,
3,
37,
6,
0,
0,
0,
0,
28,
22,
237,
52,
54,
0,
0,
0,
10,
100,
11,
230,
169,
34,
0,
0,
4,
0,
0,
0,
0,
0,
0,
0,
144,
168,
225,
52,
54
],
expectedReport: {
14: 92
}
}), s = e({
subcommand: [90, 4, 1, 1, 2],
expectedReport: {
14: 90
}
});
await n(t), await o(t), await l(t), await h(t), await s(t);
};
function St(t) {
return t && t.__esModule && Object.prototype.hasOwnProperty.call(t, "default") ? t.default : t;
}
var pt, qt;
function At() {
return qt || (qt = 1, pt = function(e, n) {
n = n || {};
const o = n.kp || 1, l = n.ki || 0;
let s = 1 / (1e3 / e), u = n.doInitialisation !== !0, a = 2 * o;
const r = 2 * l;
let p = 1, q = 0, I = 0, b = 0, L = 0, C = 0, O = 0;
function A(i, c, d, M, f, w) {
let v, m, y, P, F, j, B;
M !== 0 && f !== 0 && w !== 0 && (v = (M * M + f * f + w * w) ** -0.5, M *= v, f *= v, w *= v, m = q * b - p * I, y = p * q + I * b, P = p * p - 0.5 + b * b, F = f * P - w * y, j = w * m - M * P, B = M * y - f * m, r > 0 ? (L += r * F * s, C += r * j * s, O += r * B * s, i += L, c += C, d += O) : (L = 0, C = 0, O = 0), i += a * F, c += a * j, d += a * B), i *= 0.5 * s, c *= 0.5 * s, d *= 0.5 * s;
const U = p, N = q, D = I;
p += -N * i - D * c - b * d, q += U * i + D * d - b * c, I += U * c - N * d + b * i, b += U * d + N * c - D * i, v = (p * p + q * q + I * I + b * b) ** -0.5, p *= v, q *= v, I *= v, b *= v;
}
function k(i, c, d, M, f, w) {
return {
x: c * w - d * f,
y: d * M - i * w,
z: i * f - c * M
};
}
function x(i, c, d, M, f, w) {
const v = -Math.atan2(i, Math.sqrt(c * c + d * d)), m = k(i, c, d, 1, 0, 0), y = k(1, 0, 0, m.x, m.y, m.z), P = Math.atan2(y.y, y.z), F = Math.cos(P), j = Math.sin(v), B = Math.sin(P), U = f * F - w * B, N = M * Math.cos(v) + f * B * j + w * F * j;
return {
heading: -Math.atan2(U, N),
pitch: v,
roll: P
};
}
function R(i) {
const c = Math.cos(i.heading * 0.5), d = Math.sin(i.heading * 0.5), M = Math.cos(i.pitch * 0.5), f = Math.sin(i.pitch * 0.5), w = Math.cos(i.roll * 0.5), v = Math.sin(i.roll * 0.5);
return {
w: w * M * c + v * f * d,
x: v * M * c - w * f * d,
y: w * f * c + v * M * d,
z: w * M * d - v * f * c
};
}
function _(i, c, d, M, f, w) {
const v = x(i, c, d, M, f, w), m = R(v), y = (m.w * m.w + m.x * m.x + m.y * m.y + m.z * m.z) ** -0.5;
p = m.w * y, q = m.x * y, I = m.y * y, b = m.z * y, u = !0;
}
function S(i, c, d, M, f, w, v, m, y, P) {
s = P || s, u || _(M, f, w, v, m, y);
let F, j, B, U, N, D, V, Q, $, g, E, W, lt, it, X, Y, at, Z, z, ct, G, K, tt, T;
if (v === void 0 || m === void 0 || y === void 0 || v === 0 && m === 0 && y === 0) {
A(i, c, d, M, f, w);
return;
}
M !== 0 && f !== 0 && w !== 0 && (F = (M * M + f * f + w * w) ** -0.5, M *= F, f *= F, w *= F, F = (v * v + m * m + y * y) ** -0.5, v *= F, m *= F, y *= F, j = p * p, B = p * q, U = p * I, N = p * b, D = q * q, V = q * I, Q = q * b, $ = I * I, g = I * b, E = b * b, W = 2 * (v * (0.5 - $ - E) + m * (V - N) + y * (Q + U)), lt = 2 * (v * (V + N) + m * (0.5 - D - E) + y * (g - B)), it = Math.sqrt(W * W + lt * lt), X = 2 * (v * (Q - U) + m * (g + B) + y * (0.5 - D - $)), Y = Q - U, at = B + g, Z = j - 0.5 + E, z = it * (0.5 - $ - E) + X * (Q - U), ct = it * (V - N) + X * (B + g), G = it * (U + Q) + X * (0.5 - D - $), K = f * Z - w * at + (m * G - y * ct), tt = w * Y - M * Z + (y * z - v * G), T = M * at - f * Y + (v * ct - m * z), r > 0 ? (L += r * K * s, C += r * tt * s, O += r * T * s, i += L, c += C, d += O) : (L = 0, C = 0, O = 0), i += a * K, c += a * tt, d += a * T), i *= 0.5 * s, c *= 0.5 * s, d *= 0.5 * s;
const et = p, J = q, H = I;
p += -J * i - H * c - b * d, q += et * i + H * d - b * c, I += et * c - J * d + b * i, b += et * d + J * c - H * i, F = (p * p + q * q + I * I + b * b) ** -0.5, p *= F, q *= F, I *= F, b *= F;
}
return {
update: S,
init: _,
getQuaternion() {
return {
w: p,
x: q,
y: I,
z: b
};
}
};
}), pt;
}
var ft, Mt;
function kt() {
return Mt || (Mt = 1, ft = function(e, n) {
n = n || {};
const o = 1e3 / e;
let l = n.beta || 0.4, h = n.doInitialisation !== !0, s = 1, u = 0, a = 0, r = 0, p = 1 / o;
function q(A, k, x, R, _, S) {
let i, c, d, M, f, w, v, m, y, P, F, j, B, U, N, D, V, Q, $, g, E, W;
w = 0.5 * (-u * A - a * k - r * x), v = 0.5 * (s * A + a * x - r * k), m = 0.5 * (s * k - u * x + r * A), y = 0.5 * (s * x + u * k - a * A), R === 0 && _ === 0 && S === 0 || (i = (R * R + _ * _ + S * S) ** -0.5, R *= i, _ *= i, S *= i, P = 2 * s, F = 2 * u, j = 2 * a, B = 2 * r, U = 4 * s, N = 4 * u, D = 4 * a, V = 8 * u, Q = 8 * a, $ = s * s, g = u * u, E = a * a, W = r * r, c = U * E + j * R + U * g - F * _, d = N * W - B * R + 4 * $ * u - P * _ - N + V * g + V * E + N * S, M = 4 * $ * a + P * R + D * W - B * _ - D + Q * g + Q * E + D * S, f = 4 * g * r - F * R + 4 * E * r - j * _, i = (c * c + d * d + M * M + f * f) ** -0.5, c *= i, d *= i, M *= i, f *= i, w -= l * c, v -= l * d, m -= l * M, y -= l * f), s += w * p, u += v * p, a += m * p, r += y * p, i = (s * s + u * u + a * a + r * r) ** -0.5, s *= i, u *= i, a *= i, r *= i;
}
function I(A, k, x, R, _, S) {
return {
x: k * S - x * _,
y: x * R - A * S,
z: A * _ - k * R
};
}
function b(A, k, x, R, _, S) {
const i = -Math.atan2(A, Math.sqrt(k * k + x * x)), c = I(A, k, x, 1, 0, 0), d = I(1, 0, 0, c.x, c.y, c.z), M = Math.atan2(d.y, d.z), f = Math.cos(M), w = Math.sin(i), v = Math.sin(M), m = _ * f - S * v, y = R * Math.cos(i) + _ * v * w + S * f * w;
return {
heading: -Math.atan2(m, y),
pitch: i,
roll: M
};
}
function L(A) {
const k = Math.cos(A.heading * 0.5), x = Math.sin(A.heading * 0.5), R = Math.cos(A.pitch * 0.5), _ = Math.sin(A.pitch * 0.5), S = Math.cos(A.roll * 0.5), i = Math.sin(A.roll * 0.5);
return {
w: S * R * k + i * _ * x,
x: i * R * k - S * _ * x,
y: S * _ * k + i * R * x,
z: S * R * x - i * _ * k
};
}
function C(A, k, x, R, _, S) {
const i = b(A, k, x, R, _, S), c = L(i), d = (c.w * c.w + c.x * c.x + c.y * c.y + c.z * c.z) ** -0.5;
s = c.w * d, u = c.x * d, a = c.y * d, r = c.z * d, h = !0;
}
function O(A, k, x, R, _, S, i, c, d, M) {
p = M || p, h || C(R, _, S, i, c, d);
let f, w, v, m, y, P, F, j, B, U, N, D, V, Q, $, g, E, W, lt, it, X, Y, at, Z, z, ct, G, K, tt, T, et, J, H, dt, rt;
if (i === void 0 || c === void 0 || d === void 0 || i === 0 && c === 0 && d === 0) {
q(A, k, x, R, _, S);
return;
}
P = 0.5 * (-u * A - a * k - r * x), F = 0.5 * (s * A + a * x - r * k), j = 0.5 * (s * k - u * x + r * A), B = 0.5 * (s * x + u * k - a * A), R === 0 && _ === 0 && S === 0 || (f = (R * R + _ * _ + S * S) ** -0.5, R *= f, _ *= f, S *= f, f = (i * i + c * c + d * d) ** -0.5, i *= f, c *= f, d *= f, D = 2 * s * i, V = 2 * s * c, Q = 2 * s * d, $ = 2 * u * i, it = 2 * s, X = 2 * u, Y = 2 * a, at = 2 * r, Z = 2 * s * a, z = 2 * a * r, ct = s * s, G = s * u, K = s * a, tt = s * r, T = u * u, et = u * a, J = u * r, H = a * a, dt = a * r, rt = r * r, U = i * ct - V * r + Q * a + i * T + X * c * a + X * d * r - i * H - i * rt, N = D * r + c * ct - Q * u + $ * a - c * T + c * H + Y * d * r - c * rt, g = Math.sqrt(U * U + N * N), E = -D * a + V * u + d * ct + $ * r - d * T + Y * c * r - d * H + d * rt, W = 2 * g, lt = 2 * E, w = -Y * (2 * J - Z - R) + X * (2 * G + z - _) - E * a * (g * (0.5 - H - rt) + E * (J - K) - i) + (-g * r + E * u) * (g * (et - tt) + E * (G + dt) - c) + g * a * (g * (K + J) + E * (0.5 - T - H) - d), v = at * (2 * J - Z - R) + it * (2 * G + z - _) - 4 * u * (1 - 2 * T - 2 * H - S) + E * r * (g * (0.5 - H - rt) + E * (J - K) - i) + (g * a + E * s) * (g * (et - tt) + E * (G + dt) - c) + (g * r - lt * u) * (g * (K + J) + E * (0.5 - T - H) - d), m = -it * (2 * J - Z - R) + at * (2 * G + z - _) - 4 * a * (1 - 2 * T - 2 * H - S) + (-W * a - E * s) * (g * (0.5 - H - rt) + E * (J - K) - i) + (g * u + E * r) * (g * (et - tt) + E * (G + dt) - c) + (g * s - lt * a) * (g * (K + J) + E * (0.5 - T - H) - d), y = X * (2 * J - Z - R) + Y * (2 * G + z - _) + (-W * r + E * u) * (g * (0.5 - H - rt) + E * (J - K) - i) + (-g * s + E * a) * (g * (et - tt) + E * (G + dt) - c) + g * u * (g * (K + J) + E * (0.5 - T - H) - d), f = (w * w + v * v + m * m + y * y) ** -0.5, w *= f, v *= f, m *= f, y *= f, P -= l * w, F -= l * v, j -= l * m, B -= l * y), s += P * p, u += F * p, a += j * p, r += B * p, f = (s * s + u * u + a * a + r * r) ** -0.5, s *= f, u *= f, a *= f, r *= f;
}
return {
update: O,
init: C,
getQuaternion() {
return {
w: s,
x: u,
y: a,
z: r
};
}
};
}), ft;
}
var vt, It;
function xt() {
if (It) return vt;
It = 1;
const t = 180 / Math.PI;
function e(n) {
n = n || {};
const o = n.sampleInterval || 20, l = n.algorithm || "Madgwick";
let h;
if (l === "Mahony")
h = At();
else if (l === "Madgwick")
h = kt();
else
throw new Error(`AHRS(): Algorithm not valid: ${l}`);
const s = h(o, n), u = this;
Object.keys(s).forEach((a) => u[a] = s[a]);
}
return e.prototype.toVector = function() {
const o = this.getQuaternion(), l = 2 * Math.acos(o.w), h = Math.sin(l / 2);
return {
angle: l,
x: o.x / h,
y: o.y / h,
z: o.z / h
};
}, e.prototype.getEulerAngles = function() {
const o = this.getQuaternion(), l = o.w * o.w, h = o.x * o.x, s = o.y * o.y, u = o.z * o.z;
return {
heading: Math.atan2(2 * (o.x * o.y + o.z * o.w), h - s - u + l),
pitch: -Math.asin(2 * (o.x * o.z - o.y * o.w)),
roll: Math.atan2(2 * (o.y * o.z + o.x * o.w), -h - s + u + l)
};
}, e.prototype.getEulerAnglesDegrees = function() {
const o = this.getEulerAngles();
return {
heading: o.heading * t,
pitch: o.pitch * t,
roll: o.roll * t
};
}, vt = e, vt;
}
var Ft = xt();
const Lt = /* @__PURE__ */ St(Ft);
function Bt(t, e) {
let n;
for (const o of t) {
const l = e(o);
l !== void 0 && (n = n === void 0 ? l : n + l);
}
return n;
}
function ut(t, e = (n) => n) {
const n = t == null ? 0 : t.length, o = Bt(t, e);
return n ? o / n : Number.NaN;
}
function Ut(t) {
let e;
switch (t[0]) {
case "8":
e = "full";
break;
case "4":
e = "medium";
break;
case "2":
e = "low";
break;
case "1":
e = "critical";
break;
case "0":
e = "empty";
break;
default:
e = "charging";
}
return e;
}
const Ct = {
// biome-ignore lint/complexity/useSimpleNumberKeys:
1: "Left Joy-Con",
// biome-ignore lint/complexity/useSimpleNumberKeys:
2: "Right Joy-Con",
// biome-ignore lint/complexity/useSimpleNumberKeys:
3: "Pro Controller"
}, ht = 0.75, Nt = 0.0125, _t = Math.PI / 2;
function Pt(t, e, n, o) {
const l = Date.now(), h = t.timestamp ? (l - t.timestamp) / 1e3 : 0;
t.timestamp = l;
const s = Math.sqrt(
n.x ** 2 + n.y ** 2 + n.z ** 2
);
return t.alpha = (1 - Nt) * (t.alpha + e.z * h), s !== 0 && (t.beta = ht * (t.beta + e.x * h) + (1 - ht) * (n.x * _t / s), t.gamma = ht * (t.gamma + e.y * h) + (1 - ht) * (n.y * -_t / s)), {
alpha: o === 8198 ? (-1 * (t.alpha * 180) / Math.PI * 430 % 90).toFixed(6) : (t.alpha * 180 / Math.PI * 430 % 360).toFixed(6),
beta: (-1 * (t.beta * 180) / Math.PI).toFixed(6),
gamma: o === 8198 ? (-1 * (t.gamma * 180) / Math.PI).toFixed(6) : (t.gamma * 180 / Math.PI).toFixed(6)
};
}
function Dt(t) {
const e = 180 / Math.PI, n = t.w * t.w, o = t.x * t.x, l = t.y * t.y, h = t.z * t.z;
return {
alpha: (e * Math.atan2(2 * (t.x * t.y + t.z * t.w), o - l - h + n)).toFixed(6),
beta: (e * -Math.asin(2 * (t.x * t.z - t.y * t.w))).toFixed(6),
gamma: (e * Math.atan2(2 * (t.y * t.z + t.x * t.w), -o - l + h + n)).toFixed(6)
};
}
function nt(t) {
const e = new DataView(t.buffer);
return Number.parseFloat((244e-6 * e.getInt16(0, !0)).toFixed(6));
}
function ot(t) {
const e = new DataView(t.buffer);
return Number.parseFloat((0.06103 * e.getInt16(0, !0)).toFixed(6));
}
function st(t) {
const e = new DataView(t.buffer);
return Number.parseFloat((1694e-7 * e.getInt16(0, !0)).toFixed(6));
}
function Ht(t) {
const e = t.slice(15, 26), n = e.slice(0, 1)[0], o = e.slice(1, 2)[0], l = e.slice(2, 3), h = e.slice(4, 10), s = [];
for (const r of h)
s.push(r.toString(16));
const u = e.slice(11, 12);
return {
_raw: e.slice(0, 12),
_hex: e.slice(0, 12),
firmwareVersion: {
major: n,
minor: o
},
type: Ct[l[0]],
macAddress: s.join(":"),
spiColorInUse: u[0] === 1
};
}
function Ot(t, e) {
return {
_raw: t.slice(0, 1),
// index 0
_hex: e.slice(0, 1)
};
}
function jt(t, e) {
return {
_raw: t.slice(1, 2),
// index 1
_hex: e.slice(1, 2)
};
}
function Jt(t, e) {
return {
_raw: t.slice(2, 3),
// high nibble
_hex: e.slice(2, 3),
level: Ut(e.slice(2, 3))
};
}
function Qt(t, e) {
return {
_raw: t.slice(2, 3),
// low nibble
_hex: e.slice(2, 3)
};
}
function Tt(t, e) {
return {
_raw: t.slice(1, 3),
// index 1,2
_hex: e.slice(1, 3)
};
}
function $t(t, e) {
return {
_raw: t.slice(3, 6),
// index 3,4,5
_hex: e.slice(3, 6),
// Byte 3 (Right Joy-Con)
y: !!(1 & t[3]),
x: !!(2 & t[3]),
b: !!(4 & t[3]),
a: !!(8 & t[3]),
r: !!(64 & t[3]),
zr: !!(128 & t[3]),
// Byte 5 (Left Joy-Con)
down: !!(1 & t[5]),
up: !!(2 & t[5]),
right: !!(4 & t[5]),
left: !!(8 & t[5]),
l: !!(64 & t[5]),
zl: !!(128 & t[5]),
// Byte 3,5 (Shared)
sr: !!(16 & t[3]) || !!(16 & t[5]),
sl: !!(32 & t[3]) || !!(32 & t[5]),
// Byte 4 (Shared)
minus: !!(1 & t[4]),
plus: !!(2 & t[4]),
rightStick: !!(4 & t[4]),
leftStick: !!(8 & t[4]),
home: !!(16 & t[4]),
capture: !!(32 & t[4]),
chargingGrip: !!(128 & t[4])
};
}
function Gt(t, e) {
return {
_raw: t.slice(3, 4),
// index 3
_hex: e.slice(3, 4)
};
}
function Kt(t, e) {
let n = t[6] | (t[7] & 15) << 8;
n = (n / 1995 - 1) * 2;
let o = (t[7] >> 4 | t[8] << 4) * -1;
return o = (o / 2220 + 1) * 2, {
_raw: t.slice(6, 9),
// index 6,7,8
_hex: e.slice(6, 9),
horizontal: n.toFixed(1),
vertical: o.toFixed(1)
};
}
function Vt(t, e) {
let n = t[9] | (t[10] & 15) << 8;
n = (n / 1995 - 1) * 2;
let o = (t[10] >> 4 | t[11] << 4) * -1;
return o = (o / 2220 + 1) * 2, {
_raw: t.slice(9, 12),
// index 9,10,11
_hex: e.slice(9, 12),
horizontal: n.toFixed(1),
vertical: o.toFixed(1)
};
}
function Wt(t, e) {
return {
_raw: t.slice(4),
// index 4
_hex: e.slice(4)
};
}
function Xt(t, e) {
return {
_raw: t.slice(12, 13),
// index 12
_hex: e.slice(12, 13)
};
}
function Yt(t, e) {
return {
_raw: t.slice(13, 14),
// index 13
_hex: e.slice(13, 14)
};
}
function Zt(t, e) {
return {
_raw: t.slice(14, 15),
// index 14
_hex: e.slice(14, 15)
};
}
function zt(t, e) {
return {
_raw: t.slice(15),
// index 15 ~
_hex: e.slice(15)
};
}
function te(t, e) {
return [
{
x: {
_raw: t.slice(13, 15),
// index 13,14
_hex: e.slice(13, 15),
acc: nt(t.slice(13, 15))
},
y: {
_raw: t.slice(15, 17),
// index 15,16
_hex: e.slice(15, 17),
acc: nt(t.slice(15, 17))
},
z: {
_raw: t.slice(17, 19),
// index 17,18
_hex: e.slice(17, 19),
acc: nt(t.slice(17, 19))
}
},
{
x: {
_raw: t.slice(25, 27),
// index 25,26
_hex: e.slice(25, 27),
acc: nt(t.slice(25, 27))
},
y: {
_raw: t.slice(27, 29),
// index 27,28
_hex: e.slice(27, 29),
acc: nt(t.slice(27, 29))
},
z: {
_raw: t.slice(29, 31),
// index 29,30
_hex: e.slice(29, 31),
acc: nt(t.slice(29, 31))
}
},
{
x: {
_raw: t.slice(37, 39),
// index 37,38
_hex: e.slice(37, 39),
acc: nt(t.slice(37, 39))
},
y: {
_raw: t.slice(39, 41),
// index 39,40
_hex: e.slice(39, 41),
acc: nt(t.slice(39, 41))
},
z: {
_raw: t.slice(41, 43),
// index 41,42
_hex: e.slice(41, 43),
acc: nt(t.slice(41, 43))
}
}
];
}
function ee(t, e) {
return [
[
{
_raw: t.slice(19, 21),
// index 19,20
_hex: e.slice(19, 21),
dps: ot(t.slice(19, 21)),
rps: st(t.slice(19, 21))
},
{
_raw: t.slice(21, 23),
// index 21,22
_hex: e.slice(21, 23),
dps: ot(t.slice(21, 23)),
rps: st(t.slice(21, 23))
},
{
_raw: t.slice(23, 25),
// index 23,24
_hex: e.slice(23, 25),
dps: ot(t.slice(23, 25)),
rps: st(t.slice(23, 25))
}
],
[
{
_raw: t.slice(31, 33),
// index 31,32
_hex: e.slice(31, 33),
dps: ot(t.slice(31, 33)),
rps: st(t.slice(31, 33))
},
{
_raw: t.slice(33, 35),
// index 33,34
_hex: e.slice(33, 35),
dps: ot(t.slice(33, 35)),
rps: st(t.slice(33, 35))
},
{
_raw: t.slice(35, 37),
// index 35,36
_hex: e.slice(35, 37),
dps: ot(t.slice(35, 37)),
rps: st(t.slice(35, 37))
}
],
[
{
_raw: t.slice(43, 45),
// index 43,44
_hex: e.slice(43, 45),
dps: ot(t.slice(43, 45)),
rps: st(t.slice(43, 45))
},
{
_raw: t.slice(45, 47),
// index 45,46
_hex: e.slice(45, 47),
dps: ot(t.slice(45, 47)),
rps: st(t.slice(45, 47))
},
{
_raw: t.slice(47, 49),
// index 47,48
_hex: e.slice(47, 49),
dps: ot(t.slice(47, 49)),
rps: st(t.slice(47, 49))
}
]
];
}
function ne(t) {
const e = 5e-3 * t.length;
return {
x: Number.parseFloat(
(ut(t.map(([o]) => o)) * e).toFixed(6)
),
y: Number.parseFloat(
(ut(t.map(([, o]) => o)) * e).toFixed(6)
),
z: Number.parseFloat(
(ut(t.map(([, , o]) => o)) * e).toFixed(6)
)
};
}
function Rt(t) {
const e = 5e-3 * t.length, n = [
ut(t.map((o) => o[0])),
ut(t.map((o) => o[1])),
ut(t.map((o) => o[2]))
].map((o) => Number.parseFloat((o * e).toFixed(6)));
return {
x: n[0],
y: n[1],
z: n[2]
};
}
function oe(t, e) {
return {
_raw: t.slice(38, 2),
_hex: e.slice(38, 2),
strain: new DataView(t.buffer, 39, 2).getInt16(0, !0)
};
}
function se(t, e) {
const n = new (Object.getPrototypeOf(t)).constructor(
t.length + e.length
);
return n.set(t, 0), n.set(e, t.length), n;
}
class bt extends EventTarget {
eventListenerAttached = !1;
quaternion;
madgwick;
device;
lastValues;
ledstate = 0;
/**
* Creates an instance of the JoyCon class.
*
* @param device - The HIDDevice instance representing the connected Joy-Con controller.
*
* Initializes the device and sets up the initial state for sensor values,
* including timestamp, alpha, beta, and gamma.
*/
constructor(e) {
super(), this.device = e, this.lastValues = {
timestamp: null,
alpha: 0,
beta: 0,
gamma: 0
}, (e.productId === 8198 || e.productId === 8199) && (this.madgwick = new Lt({ sampleInterval: 10, algorithm: "Madgwick" }), this.quaternion = this.madgwick.getQuaternion());
}
/**
* Registers an event listener for a specific JoyCon event type.
*
* @typeParam K - The type of the JoyCon event to listen for, constrained to the keys of `JoyConEvents`.
* @param type - The event type to listen for.
* @param listener - The callback function that will be invoked when the event is dispatched.
* The `this` context within the listener is bound to the current `JoyCon` instance,
* and the event object is of the type corresponding to the event type.
* @param options - Optional. An options object specifying characteristics about the event listener,
* or a boolean indicating whether events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree.
*/
on(e, n, o) {
super.addEventListener(e, n, o);
}
/**
* Opens a connection to the Joy-Con device if it is not already opened,
* and attaches an event listener for input reports.
*
* @returns {Promise<void>} A promise that resolves when the device is opened and the event listener is attached.
*/
async open() {
this.device.opened || await this.device.open(), this.device.addEventListener("inputreport", this._onInputReport.bind(this));
}
/**
* Sends a request to the Joy-Con device to retrieve device information.
*
* This method sends a specific output report to the device and listens for a
* "deviceinfo" event. When the event is received, it resolves with the device
* information, excluding any raw or hexadecimal data fields.
*
* @returns A promise that resolves with the cleaned device information object.
*/
async getRequestDeviceInfo() {
const o = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
...[2]
], l = new Promise((h) => {
const s = ({ detail: u }) => {
const { _raw: a, _hex: r, ...p } = u;
h(p);
};
this.addEventListener("deviceinfo", s, {
once: !0
});
});
return await this.device.sendReport(1, new Uint8Array(o)), l;
}
/**
* Requests the current battery level from the Joy-Con device.
*
* Sends a specific output report to the device to query the battery level,
* then listens for a "batterylevel" custom event. Once the event is received,
* it resolves with the battery level information, excluding any raw or hex data.
*
* @returns {Promise<unknown>} A promise that resolves with the cleaned battery level data.
*/
async getBatteryLevel() {
const o = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
...[80]
], l = new Promise((h) => {
const s = ({ detail: u }) => {
const { _raw: a, _hex: r, ...p } = u;
h(p);
};
this.addEventListener("batterylevel", s, {
once: !0
});
});
return await this.device.sendReport(1, new Uint8Array(o)), l;
}
/**
* Enables the Simple HID mode on the connected Joy-Con device.
*
* This method sends a specific output report to the device to switch it into
* Simple HID mode, which allows for basic input/output communication.
*
* @returns {Promise<void>} A promise that resolves once the command has been sent.
* @throws {DOMException} If the report cannot be sent to the device.
*/
async enableSimpleHIDMode() {
const o = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
...[3, 63]
];
await this.device.sendReport(1, new Uint8Array(o));
}
/**
* Enables the "Standard Full Mode" on the Joy-Con device by sending the appropriate subcommand.
*
* This mode allows the Joy-Con to report all standard input data, including button presses,
* analog stick positions, and sensor data. The method constructs the required data packet
* and sends it to the device using the HID report protocol.
*
* @returns {Promise<void>} A promise that resolves once the command has been sent.
* @throws {Error} If the device communication fails.
*/
async enableStandardFullMode() {
const o = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
...[3, 48]
];
await this.device.sendReport(1, new Uint8Array(o));
}
/**
* Enables the IMU (Inertial Measurement Unit) mode on the Joy-Con device.
*
* Sends a subcommand to the device to activate the IMU, which allows the Joy-Con
* to start reporting motion sensor data such as accelerometer and gyroscope readings.
*
* @returns A promise that resolves when the command has been sent to the device.
* @throws Will throw an error if sending the report to the device fails.
*/
async enableIMUMode() {
const o = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
...[64, 1]
];
await this.device.sendReport(1, new Uint8Array(o));
}
/**
* Disables the IMU (Inertial Measurement Unit) mode on the connected Joy-Con device.
*
* Sends a subcommand to the device to turn off IMU functionality, which includes
* the accelerometer and gyroscope sensors. This can be useful for reducing power
* consumption or when IMU data is not needed.
*
* @returns A promise that resolves when the command has been sent to the device.
* @throws Will throw an error if sending the report to the device fails.
*/
async disableIMUMode() {
const o = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
...[64, 0]
];
await this.device.sendReport(1, new Uint8Array(o));
}
/**
* Enables the vibration feature on the connected Joy-Con device.
*
* This method sends a specific output report to the device to activate vibration.
* It constructs the required data packet, including the subcommand for enabling vibration,
* and transmits it using the WebHID API.
*
* @returns A promise that resolves when the vibration command has been sent.
* @throws {DOMException} If sending the report to the device fails.
*/
async enableVibration() {
const o = [
0,
0,
1,
64,
64,
0,
1,
64,
64,
...[72, 1]
];
await this.device.sendReport(1, new Uint8Array(o));
}
/**
* Disables the vibration feature on the connected Joy-Con controller.
*
* Sends a specific output report to the device to turn off vibration.
* This method constructs the appropriate data packet and sends it using the WebHID API.
*
* @returns A promise that resolves when the vibration disable command has been sent.
*/
async disableVibration() {
const o = [
0,
0,
1,
64,
64,
0,
1,
64,
64,
...[72, 0]
];
await this.device.sendReport(1, new Uint8Array(o));
}
/**
* Enables RingCon.
*
* @memberof JoyCon
* @seeAlso https://github.com/mascii/demo-of-ring-con-with-web-hid
*/
async enableRingCon() {
await Et(this.device);
}
/**
* Enables the USB HID joystick report mode for the connected device.
*
* This method checks if the device supports a specific output report (with reportId 0x80).
* If supported, it sends a sequence of USB HID reports to the device to enable joystick reporting.
* The sequence of reports is required to properly initialize the device for joystick input over USB.
*
* @returns {Promise<void>} A promise that resolves once the reports have been sent.
*/
async enableUSBHIDJoystickReport() {
this.device.collections[0].outputReports?.find(
(n) => n.reportId === 128
) != null && (await this.device.sendReport(128, new Uint8Array([1])), await this.device.sendReport(128, new Uint8Array([2])), await this.device.sendReport(1, new Uint8Array([3])), await this.device.sendReport(128, new Uint8Array([4])));
}
/**
* Sends a rumble (vibration) command to the Joy-Con device with the specified frequency and amplitude parameters.
*
* @param lowFrequency - The low frequency value for the rumble effect (in Hz). Must be between 40.875885 and 626.286133.
* @param highFrequency - The high frequency value for the rumble effect (in Hz). Must be between 81.75177 and 1252.572266.
* @param amplitude - The amplitude (strength) of the rumble effect. Must be between 0 (off) and 1 (maximum).
* @returns A promise that resolves when the rumble command has been sent to the device.
*
* @remarks
* This method encodes the frequency and amplitude values into the format expected by the Joy-Con hardware,
* clamps the input values to their valid ranges, and sends the resulting data packet via HID.
* The rumble effect is applied to both left and right motors of the Joy-Con.
*/
async rumble(e, n, o) {
const l = (b, L, C) => Math.min(Math.max(b, L), C), s = new Uint8Array(9);
s[0] = 0;
let u = l(e, 40.875885, 626.286133), a = l(n, 81.75177, 1252.572266);
a = (Math.round(32 * Math.log2(a * 0.1)) - 96) * 4, u = Math.round(32 * Math.log2(u * 0.1)) - 64;
const r = l(o, 0, 1);
let p;
r === 0 ? p = 0 : r < 0.117 ? p = (Math.log2(r * 1e3) * 32 - 96) / (5 - r ** 2) - 1 : r < 0.23 ? p = Math.log2(r * 1e3) * 32 - 96 - 92 : p = (Math.log2(r * 1e3) * 32 - 96) * 2 - 246;
let q = Math.round(p) * 0.5;
const I = q % 2;
I > 0 && --q, q = q >> 1, q += 64, I > 0 && (q |= 32768), s[1] = a & 255, s[2] = p + (a >>> 8 & 255), s[3] = u + (q >>> 8 & 255), s[4] += q & 255;
for (let b = 0; b < 4; b++)
s[5 + b] = s[1 + b];
await this.device.sendReport(16, new Uint8Array(s));
}
/**
* Sets the blinking pattern for the Home LED on the Right Joy-Con device.
*
* Sends a subcommand to the device to control the Home LED.
*
* @param miniCycleDuration: Global mini cycle duration. 0-15. 0: off, 1: 8ms, ... , 15: 175ms
* @param numCycles: Number of full cycles. 0-15. 0: repeat forever.
* @param startIntensity: Initial LED intensity. 0-15.
* @param cycleData: Array of {@link HomeLEDpatterns}. The maximum count of the array is 15.
*/
/* Inspired by the JoyConSwift library */
async setHomeLEDPattern(e, n, o, l) {
const h = (L, C, O) => Math.min(Math.max(L, C), O), u = Math.min(l.length, 15), a = u << 4 | h(e, 0, 15), r = h(o, 0, 15) << 4 | h(n, 0, 15), p = [a, r], q = {
intensity: 15,
fadeDuration: 0,
duration: 0
}, I = l.slice(0, u).concat(Array(16 - u).fill(q));
for (let L = 0; L < 8; L++) {
const C = I[L * 2], O = I[L * 2 + 1], A = h(C.intensity, 0, 15), k = h(C.fadeDuration, 0, 15), x = h(C.duration, 0, 15), R = h(O.intensity, 0, 15), _ = h(O.fadeDuration, 0, 15), S = h(O.duration, 0, 15);
p.push(A << 4 | R), p.push(k << 4 | x), p.push(_ << 4 | S);
}
p.pop();
const b = [
0,
0,
0,
0,
0,
0,
0,
0,
0,
56,
...p
];
await this.device.sendReport(1, new Uint8Array(b));
}
/**
* Turn `on` or `off` the Home LED on the Right Joy-Con device.
*
* @param {boolean} on - If true, the LED will be turned on permanently. Turn the LED off otherwise.
*
*/
async setHomeLED(e) {
e === !0 ? await this.setHomeLEDPattern(1, 0, 15, []) : await this.setHomeLEDPattern(0, 1, 0, []);
}
/**
* Sets the LED state on the Joy-Con device.
*
* Sends a subcommand to the device to control the LED indicators.
*
* @param n - The LED state value to set. The value determines which LEDs are turned on or off.
* @returns A promise that resolves when the command has been sent to the device.
*/
async setLEDState(e) {
const n = [0, 0, 0, 0, 0, 0, 0, 0], o = [48, e];
await this.device.sendReport(
1,
new Uint8Array([...n, 0, ...o])
);
}
/**
* Sets the specified LED on the Joy-Con controller.
*
* Updates the internal LED state by turning on the LED at the given index `n`,
* then sends the updated state to the device.
*
* @param n - The index of the LED to turn on (0-based).
* @returns A promise that resolves when the LED state has been updated.
*/
async setLED(e) {
this.ledstate |= 1 << e, await this.setLEDState(this.ledstate);
}
/**
* Resets (turns off) the LED at the specified index by clearing its corresponding bits
* in the internal LED state and updates the device.
*
* @param n - The index of the LED to reset (0-based).
* @returns A promise that resolves when the LED state has been updated.
*/
async resetLED(e) {
this.ledstate &= ~(1 << e | 1 << 4 + e), await this.setLEDState(this.ledstate);
}
/**
* Blinks the specified LED on the Joy-Con controller.
*
* This method updates the internal LED state by first turning off the LED at position `n`,
* then setting the corresponding blink bit for that LED. It then sends the updated state
* to the controller.
*
* @param n - The index of the LED to blink (typically 0-3).
* @returns A promise that resolves when the LED state has been updated.
*/
async blinkLED(e) {
this.ledstate &= ~(1 << e), this.ledstate |= 1 << 4 + e, await this.setLEDState(this.ledstate);
}
/**
* Handles the HID input report event from a Joy-Con device, parses the incoming data,
* and emits structured input events based on the report type.
*
* @param event - The HID input report event containing the data, reportId, and device.
* @remarks
* This method processes different types of input reports (e.g., 0x3f, 0x21, 0x30) by parsing
* the raw data using various PacketParser methods. It extracts information such as button status,
* analog stick positions, battery level, accelerometer and gyroscope data, and device info.
* The parsed data is then dispatched to relevant handlers and listeners.
*
* @private
*/
_onInputReport({ data: e, reportId: n, device: o }) {
if (!e)
return;
const l = se(
new Uint8Array([n]),
new Uint8Array(e.buffer)
), h = Array.from(l).map((u) => u.toString(16).padStart(2, "0")).join("");
let s = {
inputReportID: Ot(l, h)
};
switch (n) {
case 63: {
s = {
...s,
buttonStatus: Tt(l, h),
analogStick: Gt(l, h),
filter: Wt(l, h)
};
break;
}
case 33:
case 48: {
if (s = {
...s,
timer: jt(l, h),
batteryLevel: Jt(l, h),
connectionInfo: Qt(l, h),
buttonStatus: $t(
l,
h
),
analogStickLeft: Kt(l, h),
analogStickRight: Vt(
l,
h
),
vibrator: Xt(l, h)
}, n === 33 && (s = {
...s,
ack: Yt(l, h),
subcommandID: Zt(l, h),
subcommandReplyData: zt(
l,
h
),
deviceInfo: Ht(l)
}), n === 48) {
const u = te(
l,
h
), a = ee(l, h), r = Rt(
a.map((I) => I.map((b) => b.rps ?? 0))
), p = Rt(
a.map((I) => I.map((b) => b.dps ?? 0))
), q = ne(
u.map((I) => [
I.x.acc ?? 0,
I.y.acc ?? 0,
I.z.acc ?? 0
])
);
this.madgwick.update(r.x, r.y, r.z, q.x, q.y, q.z), s = {
...s,
accelerometers: u,
gyroscopes: a,
actualAccelerometer: q,
actualGyroscope: { dps: p, rps: r },
actualOrientation: Pt(
this.lastValues,
r,
q,
o.productId
),
actualOrientationQuaternion: Dt(
this.quaternion
),
quaternion: this.quaternion,
ringCon: oe(l, h)
};
}
break;
}
}
s.deviceInfo?.type && this._receiveDeviceInfo(s.deviceInfo), s.batteryLevel?.level && this._receiveBatteryLevel(s.batteryLevel), this._receiveInputEvent(s);
}
/**
* Dispatches a "deviceinfo" custom event with the provided device information as its detail.
*
* @param deviceInfo - The information about the device to be included in the event detail.
*/
_receiveDeviceInfo(e) {
this.dispatchEvent(new CustomEvent("deviceinfo", { detail: e }));
}
/**
* Dispatches a "batterylevel" custom event with the provided battery level detail.
*
* @param batteryLevel - The battery level information to include in the event detail.
*/
_receiveBatteryLevel(e) {
this.dispatchEvent(
new CustomEvent("batterylevel", { detail: e })
);
}
// To be overridden by subclasses
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_receiveInputEvent(e) {
}
}
class ie extends bt {
/**
* Handles an input event packet by removing specific button statuses and dispatching a custom "hidinput" event.
*
* @param packet - The input event data containing button statuses and other information.
*
* The method sets the following button statuses to `undefined` in the `buttonStatus` object:
* - x
* - y
* - b
* - a
* - plus
* - r
* - zr
* - home
* - rightStick
*
* After modifying the packet, it dispatches a `CustomEvent` named "hidinput" with the modified packet as its detail.
*/
_receiveInputEvent(e) {
const n = e.buttonStatus;
n.x = void 0, n.y = void 0, n.b = void 0, n.a = void 0, n.plus = void 0, n.r = void 0, n.zr = void 0, n.home = void 0, n.rightStick = void 0, this.dispatchEvent(new CustomEvent("hidinput", { detail: e }));
}
}
class ce extends bt {
/**
* Handles an input event packet from the Joy-Con device, sanitizes specific button statuses by setting them to `undefined`,
* and dispatches a "hidinput" custom event with the modified packet as its detail.
*
* @param packet - The input event data received from the Joy-Con, expected to contain a `buttonStatus` property.
*/
_receiveInputEvent(e) {
const n = e.buttonStatus;
n.up = void 0, n.down = void 0, n.left = void 0, n.right = void 0, n.minus = void 0, n.l = void 0, n.zl = void 0, n.capture = void 0, n.leftStick = void 0, this.dispatchEvent(new CustomEvent("hidinput", { detail: e }));
}
}
class re extends bt {
/**
* Dispatches a "hidinput" custom event with the provided packet as its detail.
*
* @param packet - The input data received from the HID device.
*/
_receiveInputEvent(e) {
this.dispatchEvent(new CustomEvent("hidinput", { detail: e }));
}
}
const le = async (t) => {
let e = null;
return t.productId === 8198 ? e = new ie(t) : t.productId === 8199 && t.productName === "Joy-Con (R)" && (e = new ce(t)), e || (e = new re(t)), await e.open(), await e.enableUSBHIDJoystickReport(), await e.enableStandardFullMode(), await e.enableIMUMode(), e;
}, yt = /* @__PURE__ */ new Map(), mt = [], gt = (t) => {
const e = mt.indexOf(t);
return e >= 0 ? e : (mt.push(t), mt.length - 1);
}, wt = async (t) => {
const e = gt(t);
console.log(
`HID connected: ${e} ${t.productId.toString(16)} ${t.productName}`
), yt.set(e, await le(t));
}, ae = async (t) => {
const e = gt(t);
console.log(
`HID disconnected: ${e} ${t.productId.toString(16)} ${t.productName}`
), yt.delete(e);
};
navigator.hid.addEventListener("connect", async ({ device: t }) => {
wt(t);
});
navigator.hid.addEventListener("disconnect", ({ device: t }) => {
ae(t);
});
document.addEventListener("DOMContentLoaded", async () => {
const t = await navigator.hid.getDevices();
for (const e of t)
await wt(e);
});
const ue = async () => {
const t = [
{
vendorId: 1406
// Nintendo Co., Ltd
}
];
try {
const [e] = await navigator.hid.requestDevice({ filters: t });
if (!e)
return;
await wt(e);
} catch (e) {
e instanceof Error ? console.error(e.name, e.message) : console.error(e);
}
};
export {
re as GeneralController,
ie as JoyConLeft,
ce as JoyConRight,
ue as connectJoyCon,
yt as connectedJoyCons
};