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"); };