noe.sh/libs/shock.js
2024-10-18 22:43:21 -07:00

174 lines
5 KiB
JavaScript

window.__ARMED__ = false;
window.__SAFETY__ = true;
window.__CONNECTED__ = false;
const COLLAR_BT_NAME = "PetSafe Smart Dog Trainer";
const COLLAR_COMMAND_SERVICE = "0bd51666-e7cb-469b-8e4d-2742f1ba77cc";
const COLLAR_COMMAND_CHARACTERISTIC = "e7add780-b042-4876-aae1-112855353cc1";
const COLLAR_AUTH_CHARACTERISTIC = "0e7ad781-b043-4877-aae2-112855353cc2";
const COLLAR_TONE_COMMAND = [0x55, 0x36, 0x31, 0x31, 0x31, 0x30];
const COLLAR_VIBRATE_COMMAND = [0x55, 0x36, 0x31, 0x33, 0x33, 0x30];
// Last byte is command strength (0-15) + 0x48. We set it to 0 here.
const COLLAR_STATIC_COMMAND = [0x55, 0x36, 0x31, 0x32, 0x33, 0x30];
// Last four bytes are the ASCII values of the PIN digits
const COLLAR_AUTH_COMMAND = [0x55, 0x37, 0x37, 0x30, 0x30, 0x30, 0x30];
class PetsafeSmartDogTrainingCollar {
constructor() {
this.device = null;
this.service = null;
this.tx = null;
}
async connect() {
console.log("Starting connect");
if (!navigator.bluetooth) {
addLogMessage("No WebBluetooth support; bailing.");
throw new Error("No WebBluetooth; bailing.");
}
this.device = await navigator.bluetooth.requestDevice({
filters: [
{
name: COLLAR_BT_NAME,
},
],
optionalServices: [COLLAR_COMMAND_SERVICE],
});
let server = await this.device.gatt.connect();
this.service = await server.getPrimaryService(COLLAR_COMMAND_SERVICE);
this.tx = await this.service.getCharacteristic(
COLLAR_COMMAND_CHARACTERISTIC
);
this.auth = await this.service.getCharacteristic(
COLLAR_AUTH_CHARACTERISTIC
);
console.log("Connected");
}
async disconnect() {
await this.device.gatt.disconnect();
}
async authorize(d1, d2, d3, d4) {
console.log("Authenticating with ", d1, d2, d3, d4);
let r = new Uint8Array(COLLAR_AUTH_COMMAND);
r[3] += d1;
r[4] += d2;
r[5] += d3;
r[6] += d4;
console.log(r);
this.auth.writeValue(r);
}
async runTone() {
console.log("Running tone");
addLogMessage("[TONE]");
this.tx.writeValue(new Uint8Array(COLLAR_TONE_COMMAND));
}
async runVibrate() {
console.log("Running vibration");
addLogMessage("[VIBRATE]");
this.tx.writeValue(new Uint8Array(COLLAR_VIBRATE_COMMAND));
}
async runStatic(power) {
if (window.__SAFETY__) {
addLogMessage("SAFETY ON; runStatic REJECTED");
return;
}
console.log("Running static");
addLogMessage("[SHOCK] Power = " + power);
if (power < 0 || power > 15) {
throw Error("power not in range 0-15");
}
let r = new Uint8Array(COLLAR_STATIC_COMMAND);
r[5] += power;
console.log(r);
this.tx.writeValue(r);
}
}
const collar = new PetsafeSmartDogTrainingCollar();
const addLogMessage = (message) => {
const logEl = document.querySelector("#log");
logEl.innerHTML = `${new Date().toISOString()} :: ${message}\n${
logEl.innerHTML
}`;
};
const setIntensityReadout = (value) => {
document.querySelector("#intensity-readout").innerHTML = value;
};
window.getIntensity = () => document.querySelector("#intensity").value;
const setStatus = (color, text) => {
document.querySelector("#dot").style.backgroundColor = color;
document.querySelector("#dotText").innerHTML = text;
};
const setSafety = (color, text) => {
document.querySelector("#safetyDot").style.backgroundColor = color;
document.querySelector("#safetyDotText").innerHTML = text;
};
const connectCollar = async (pin) => {
addLogMessage("Connecting to collar...");
await collar.connect();
addLogMessage("Collar connected; running vibration test.");
await collar.runVibrate();
};
const fire = (level) => {
if (window.__SAFETY__ || !window.__ARMED__) {
addLogMessage("SAFETY ON or NOT ARMED -- FIRE REJECTED.");
return;
}
collar.runStatic(level);
};
window.shockInit = () => {
document.querySelector("#arm").addEventListener("change", (event) => {
window.__ARMED__ = event.target.checked;
if (event.target.checked) {
addLogMessage("ARMED");
setStatus("yellow", "ARMED");
} else {
addLogMessage("Disarmed.");
setStatus("red", "Not Armed");
}
});
document.querySelector("#safety").addEventListener("change", (event) => {
window.__SAFETY__ = event.target.checked;
if (event.target.checked) {
addLogMessage("Safety is on; shock commands will NOT be sent.");
setSafety("red", "Safety ON (commands will not be sent)");
} else {
addLogMessage("Safety is off. Good luck, dolly.");
setSafety("green", "Safety OFF");
}
});
document.querySelector("#pair").addEventListener("click", () => {
// const pin = document.querySelector("#pin").value;
connectCollar();
});
document.querySelector("#intensity").addEventListener("input", (event) => {
setIntensityReadout(event.target.value);
});
setIntensityReadout(getIntensity());
document.querySelector("#safety").checked = true;
document.querySelector("#arm").checked = false;
addLogMessage("Safety is on; shock commands will NOT be sent.");
addLogMessage("Ready");
};