UNPKG

scrypted-rachio3-plugin

Version:

A Scrypted plugin that controls Rachio 3 Smart Sprinkler Controller via the Rachio Cloud API.

256 lines 11 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RachioPlugin = exports.RachioZoneDevice = void 0; const sdk_1 = __importStar(require("@scrypted/sdk")); const { deviceManager } = sdk_1.default; /////////////////////////////////////////////////////////////////////////// // RachioZoneDevice /////////////////////////////////////////////////////////////////////////// class RachioZoneDevice extends sdk_1.ScryptedDeviceBase { constructor(plugin, nativeId, zoneName, durationSeconds) { super(nativeId); this.running = false; this.plugin = plugin; this.zoneId = nativeId; this.durationSeconds = durationSeconds; this.console.log(`RachioZoneDevice created: ${zoneName} (zoneId=${this.zoneId}), default watering duration=${durationSeconds}s`); } // StartStop interface property from Scrypted is "running" // We won't redefine get/set 'running' to avoid conflicts. // Instead, we simply set this.binaryState = true/false as needed. async start() { try { this.console.log(`Starting zone ${this.zoneId} for ${this.durationSeconds} seconds.`); const url = "https://api.rach.io/1/public/zone/start"; const body = { id: this.zoneId, duration: this.durationSeconds, }; await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.plugin.rachioAuthToken}`, }, body: JSON.stringify(body), }); // Mark the zone as running this.running = true; this.console.log(`Zone ${this.zoneId} started.`); } catch (e) { this.console.error(`Error starting zone ${this.zoneId}: ${e}`); } } async stop() { // Rachio does not offer "stop single zone by zoneID"; // Instead "stop_water" stops all running zones on the device try { this.console.log(`Stopping watering for all zones (including ${this.zoneId}).`); const url = "https://api.rach.io/1/public/device/stop_water"; await fetch(url, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.plugin.rachioAuthToken}`, }, }); // Mark the zone as no longer running this.running = false; this.console.log(`Zone ${this.zoneId} stopped.`); } catch (e) { this.console.error(`Error stopping zone ${this.zoneId}: ${e}`); } } } exports.RachioZoneDevice = RachioZoneDevice; /////////////////////////////////////////////////////////////////////////// // RachioPlugin - main plugin /////////////////////////////////////////////////////////////////////////// class RachioPlugin extends sdk_1.ScryptedDeviceBase { // Rachio Auth Token stored in plugin settings get rachioAuthToken() { return this.storage.getItem("rachioAuthToken") || ""; } set rachioAuthToken(token) { this.storage.setItem("rachioAuthToken", token); } constructor(nativeId) { super(nativeId); // Keep a local map of discovered zones this.devices = new Map(); // If there's already a token, try discovering on startup if (this.rachioAuthToken) { // "scan" parameter is optional, so pass 'false' or 'true' or nothing this.discoverDevices(false); } } async adoptDevice(device) { this.console.log('adoptDevice not implemented.'); // Return a valid nativeId string. return device.nativeId || ''; } async releaseDevice(id) { this.console.log('releaseDevice not implemented.'); } /************************************************************************ * Scrypted Settings ************************************************************************/ async getSettings() { return [ { key: "rachioAuthToken", title: "Rachio API Token", description: "Enter your Rachio Personal Access Token here", value: this.rachioAuthToken || "", }, ]; } async putSetting(key, value) { if (key === "rachioAuthToken") { this.rachioAuthToken = value.trim(); this.console.log(`Rachio API token updated. Discovering devices...`); // re-discover after token changes await this.discoverDevices(false); } } /************************************************************************ * DeviceProvider ************************************************************************/ // Must be async and return a Promise async getDevice(nativeId) { return nativeId ? this.devices.get(nativeId) : undefined; } /************************************************************************ * DeviceDiscovery ************************************************************************/ // Must return a Promise of DiscoveredDevice[] and accept an optional boolean async discoverDevices(scan) { var _a; this.console.log(`Discovering Rachio devices. scan=${scan !== null && scan !== void 0 ? scan : false}`); const discoveredDevices = []; if (!this.rachioAuthToken) { this.console.warn("No Rachio auth token set, cannot discover devices."); return discoveredDevices; // empty array } try { // Step 1: Get Person info to retrieve userId let url = "https://api.rach.io/1/public/person/info"; let resp = await fetch(url, { method: "GET", headers: { Authorization: `Bearer ${this.rachioAuthToken}`, }, }); if (!resp.ok) { throw new Error(`Failed fetching /person/info: ${resp.statusText}`); } const infoData = await resp.json(); const personId = infoData.id; // Step 2: With personId, fetch /person/:id to get devices url = `https://api.rach.io/1/public/person/${personId}`; resp = await fetch(url, { method: "GET", headers: { Authorization: `Bearer ${this.rachioAuthToken}`, }, }); if (!resp.ok) { throw new Error(`Failed fetching /person/${personId}: ${resp.statusText}`); } const personDetail = await resp.json(); if (!personDetail.devices || !Array.isArray(personDetail.devices)) { this.console.warn("No Rachio devices found in this account."); return discoveredDevices; } // Each device can have multiple "zones" for (const device of personDetail.devices) { const deviceName = device.name; const deviceId = device.id; this.console.log(`Found Rachio controller: ${deviceName}, id=${deviceId}`); const zones = device.zones || []; for (const zone of zones) { if (!zone.enabled) { this.console.log(`Skipping disabled zone: ${zone.name} (id=${zone.id})`); continue; } const zoneId = zone.id; const zoneName = zone.name; // Build a discovered device descriptor const discoveredDevice = { name: zoneName, nativeId: zoneId, description: `Rachio Zone: ${zoneName}`, type: sdk_1.ScryptedDeviceType.Valve, interfaces: [sdk_1.ScryptedInterface.StartStop], info: { manufacturer: "Rachio", model: "Rachio 3", firmware: device.firmwareVersion || "", serialNumber: zoneId, }, }; discoveredDevices.push(discoveredDevice); } } // Inform Scrypted about the discovered devices deviceManager.onDevicesChanged({ devices: discoveredDevices.map(d => ({ nativeId: d.nativeId, name: d.name, description: d.description, type: d.type, interfaces: d.interfaces || [], info: d.info, })), }); // Create or update local device wrappers for (const d of discoveredDevices) { if (!d.nativeId) { this.console.error(`Discovered device has no nativeId, skipping: ${d.name}`); continue; } const sid = d.nativeId; // safe string let existing = this.devices.get(sid); if (!existing) { existing = new RachioZoneDevice(this, sid, (_a = d.name) !== null && _a !== void 0 ? _a : 'Unknown Zone', 60); this.devices.set(sid, existing); } } this.console.log("Rachio device discovery complete."); } catch (e) { this.console.error(`Rachio discovery error: ${e}`); } // Return the discovered devices return discoveredDevices; } } exports.RachioPlugin = RachioPlugin; // Default export for Scrypted exports.default = RachioPlugin; //# sourceMappingURL=index.js.map