dollseq
This commit is contained in:
parent
0697e5f55b
commit
095cf7f9b7
3 changed files with 399 additions and 0 deletions
174
libs/shock.js
Normal file
174
libs/shock.js
Normal file
|
@ -0,0 +1,174 @@
|
|||
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");
|
||||
};
|
18
libs/shocktools.css
Normal file
18
libs/shocktools.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
:root {
|
||||
font-family: "Atkinson Hyperlegible", sans-serif;
|
||||
background-color: black;
|
||||
color: #efefef;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#dot,
|
||||
#safetyDot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background-color: red;
|
||||
display: inline-block;
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue