scrypted-rachio3-plugin
Version:
A Scrypted plugin that controls Rachio 3 Smart Sprinkler Controller via the Rachio Cloud API.
256 lines • 11 kB
JavaScript
;
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