Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transfer sensor data via WebRTC #1

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 50 additions & 56 deletions public/controller.html
Original file line number Diff line number Diff line change
@@ -1,60 +1,54 @@
<html>

<head>
<title>Mobile Laser Pointer - Controller</title>
</head>

<body>
<script type="module">
function toEuler(q) {
let sinr_cosp = 2 * (q[3] * q[0] + q[1] * q[2]);
let cosr_cosp = 1 - 2 * (q[0] * q[0] + q[1] * q[1]);
let roll = Math.atan2(sinr_cosp, cosr_cosp);

let siny_cosp = 2 * (q[3] * q[2] + q[0] * q[1]);
let cosy_cosp = 1 - 2 * (q[1] * q[1] + q[2] * q[2]);
let yaw = Math.atan2(siny_cosp, cosy_cosp);

return [yaw, roll];
}

const options = { frequency: 10, referenceFrame: "device" };
const sensor = new RelativeOrientationSensor(options);

const scheme = location.protocol === "https:" ? "wss" : "ws"
const ws = new WebSocket(`${scheme}://${location.host}/`);
await new Promise((res) => {
ws.addEventListener("open", (event) => {
res()
});
})

const factor = 50;
let firstRead;
sensor.addEventListener("reading", (e) => {
const [yaw, roll] = toEuler(e.target.quaternion);
if (!firstRead) {
firstRead = { yaw, roll };
<head>
<title>Mobile Laser Pointer - Controller</title>
</head>

<body>
<script type="module">
import { pageReady, start } from "./webrtc.js";
await pageReady();
const dataChannel = await start(true);

function toEuler(q) {
let sinr_cosp = 2 * (q[3] * q[0] + q[1] * q[2]);
let cosr_cosp = 1 - 2 * (q[0] * q[0] + q[1] * q[1]);
let roll = Math.atan2(sinr_cosp, cosr_cosp);

let siny_cosp = 2 * (q[3] * q[2] + q[0] * q[1]);
let cosy_cosp = 1 - 2 * (q[1] * q[1] + q[2] * q[2]);
let yaw = Math.atan2(siny_cosp, cosy_cosp);

return [yaw, roll];
}
const deltaYaw = firstRead.yaw - yaw;
const deltaRoll = firstRead.roll - roll;

ws.send(JSON.stringify({ deltaYaw, deltaRoll }))

console.log({
yaw: yaw.toFixed(4),
deltaYaw: deltaYaw.toFixed(4),
roll: roll.toFixed(4),
deltaRoll: deltaRoll.toFixed(4),
const options = { frequency: 30, referenceFrame: "device" };
const sensor = new AbsoluteOrientationSensor(options);

let firstRead;
sensor.addEventListener("reading", (e) => {
const [yaw, roll] = toEuler(e.target.quaternion);
if (!firstRead) {
firstRead = { yaw, roll };
}
const deltaYaw = firstRead.yaw - yaw;
const deltaRoll = firstRead.roll - roll;
console.log("reading", [deltaYaw, deltaRoll]);

dataChannel.send(JSON.stringify({ deltaYaw, deltaRoll }));

console.log({
yaw: yaw.toFixed(4),
deltaYaw: deltaYaw.toFixed(4),
roll: roll.toFixed(4),
deltaRoll: deltaRoll.toFixed(4),
});
});
});
sensor.addEventListener("error", (error) => {
if (event.error.name === "NotReadableError") {
console.log("Sensor is not available.");
}
});
sensor.start();
</script>
</body>

</html>
sensor.addEventListener("error", (error) => {
if (event.error.name === "NotReadableError") {
console.log("Sensor is not available.");
}
});
sensor.start();
</script>
</body>
</html>
43 changes: 18 additions & 25 deletions public/display.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
<html>
<head>
<title>Mobile Laser Pointer - Display</title>
</head>

<head>
<title>Mobile Laser Pointer - Display</title>
</head>
<body style="margin: 0">
<svg style="width: 100vw; height: 100vh" viewBox="0 0 100 100">
<circle id="circle" r="10" fill="red" cx="50" cy="50" />
</svg>

<body style="margin: 0">
<svg style="width: 100vw; height: 100vh" viewBox="0 0 100 100">
<circle id="circle" r="10" fill="red" cx="50" cy="50" />
</svg>
<script type="module">
import { pageReady } from "./webrtc.js";
// TODO: get data channel or pass callback
await pageReady();

<script type="module">
const point = document.getElementById("circle");
const factor = 50;
const point = document.getElementById("circle");
const factor = 50;

const scheme = location.protocol === "https:" ? "wss" : "ws"
const ws = new WebSocket(`${scheme}://${location.host}/`);
ws.addEventListener("open", () => {
console.log("socket open")
ws.addEventListener("message", (ev) => {
const { deltaYaw, deltaRoll } = JSON.parse(ev.data);

point.setAttributeNS(null, "cx", 50 + factor * deltaYaw);
point.setAttributeNS(null, "cy", 50 + factor * deltaRoll);
})
})
</script>
</body>

</html>
// point.setAttributeNS(null, "cx", 50 + factor * deltaYaw);
// point.setAttributeNS(null, "cy", 50 + factor * deltaRoll);
</script>
</body>
</html>
108 changes: 108 additions & 0 deletions public/webrtc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
let peerConnection;
let uuid;
let ws;

const peerConnectionConfig = {
iceServers: [
{ urls: "stun:stun.stunprotocol.org:3478" },
{ urls: "stun:stun.l.google.com:19302" },
],
};

export const pageReady = () => {
console.log("page ready");

uuid = createUUID();

const scheme = location.protocol === "https:" ? "wss" : "ws";
ws = new WebSocket(`${scheme}://${location.host}`);

return new Promise((resolve) => {
ws.addEventListener("open", () => {
resolve();
console.log("socket open");
ws.addEventListener("message", async (message) => {
if (!peerConnection) {
start(false);
}

const signal = JSON.parse(message.data);

if (signal.uuid === uuid) {
return;
}

if (signal.sdp) {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(signal.sdp)
);
if (signal.sdp.type === "offer") {
const description = await peerConnection.createAnswer();
createdDescription(description, ws);
}
} else if (signal.ice) {
await peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice));
}
});
});
});
};

export const start = async (isCaller) => {
peerConnection = new RTCPeerConnection(peerConnectionConfig);

peerConnection.addEventListener("icecandidate", (event) => {
console.log("icecandidate");
if (event.candidate != null) {
ws.send(JSON.stringify({ ice: event.candidate, uuid: uuid }));
}
});

if (isCaller) {
const dataChannel = peerConnection.createDataChannel("angles");

return new Promise(async (resolve) => {
dataChannel.addEventListener("open", () => {
console.log("data channel open");
resolve(dataChannel);
});

const description = await peerConnection.createOffer();
createdDescription(description, ws);
});
} else {
peerConnection.addEventListener("datachannel", (event) => {
event.channel.addEventListener("message", (event) => {
console.log("data channel message", event.data);

const { deltaYaw, deltaRoll } = JSON.parse(event.data);

const point = document.getElementById("circle");
const factor = 50;

point.setAttributeNS(null, "cx", 50 + factor * deltaYaw);
point.setAttributeNS(null, "cy", 50 + factor * deltaRoll);
});
});
}
};

const createdDescription = async (description, ws) => {
await peerConnection.setLocalDescription(description);
ws.send(
JSON.stringify({
sdp: peerConnection.localDescription,
uuid: uuid,
})
);
};

function createUUID() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}

return `${s4() + s4()}-${s4()}-${s4()}-${s4()}-${s4() + s4() + s4()}`;
}