Add REST/Webhook smart plug type (#472)

This commit is contained in:
maziggy
2026-04-01 10:06:01 +02:00
parent f228a8b549
commit 914adde5aa
25 changed files with 1430 additions and 213 deletions
+46 -3
View File
@@ -1072,7 +1072,7 @@ export interface CloudDevice {
export interface SmartPlug {
id: number;
name: string;
plug_type: 'tasmota' | 'homeassistant' | 'mqtt';
plug_type: 'tasmota' | 'homeassistant' | 'mqtt' | 'rest';
ip_address: string | null; // Required for Tasmota
ha_entity_id: string | null; // Required for Home Assistant (e.g., "switch.printer_plug", "script.turn_on_printer")
// Home Assistant energy sensor entities (optional)
@@ -1095,6 +1095,18 @@ export interface SmartPlug {
mqtt_state_topic: string | null; // Topic for state data
mqtt_state_path: string | null; // e.g., "state_l1" for ON/OFF
mqtt_state_on_value: string | null; // What value means "ON" (e.g., "ON", "true", "1")
// REST/Webhook fields (required when plug_type="rest")
rest_on_url: string | null;
rest_on_body: string | null;
rest_off_url: string | null;
rest_off_body: string | null;
rest_method: string | null;
rest_headers: string | null;
rest_status_url: string | null;
rest_status_path: string | null;
rest_status_on_value: string | null;
rest_power_path: string | null;
rest_energy_path: string | null;
printer_id: number | null;
enabled: boolean;
auto_on: boolean;
@@ -1127,7 +1139,7 @@ export interface SmartPlug {
export interface SmartPlugCreate {
name: string;
plug_type?: 'tasmota' | 'homeassistant' | 'mqtt';
plug_type?: 'tasmota' | 'homeassistant' | 'mqtt' | 'rest';
ip_address?: string | null; // Required for Tasmota
ha_entity_id?: string | null; // Required for Home Assistant
// Home Assistant energy sensor entities (optional)
@@ -1150,6 +1162,18 @@ export interface SmartPlugCreate {
mqtt_state_topic?: string | null;
mqtt_state_path?: string | null;
mqtt_state_on_value?: string | null;
// REST fields
rest_on_url?: string | null;
rest_on_body?: string | null;
rest_off_url?: string | null;
rest_off_body?: string | null;
rest_method?: string | null;
rest_headers?: string | null;
rest_status_url?: string | null;
rest_status_path?: string | null;
rest_status_on_value?: string | null;
rest_power_path?: string | null;
rest_energy_path?: string | null;
printer_id?: number | null;
enabled?: boolean;
auto_on?: boolean;
@@ -1175,7 +1199,7 @@ export interface SmartPlugCreate {
export interface SmartPlugUpdate {
name?: string;
plug_type?: 'tasmota' | 'homeassistant' | 'mqtt';
plug_type?: 'tasmota' | 'homeassistant' | 'mqtt' | 'rest';
ip_address?: string | null;
ha_entity_id?: string | null;
// Home Assistant energy sensor entities (optional)
@@ -1197,6 +1221,18 @@ export interface SmartPlugUpdate {
mqtt_state_topic?: string | null;
mqtt_state_path?: string | null;
mqtt_state_on_value?: string | null;
// REST fields
rest_on_url?: string | null;
rest_on_body?: string | null;
rest_off_url?: string | null;
rest_off_body?: string | null;
rest_method?: string | null;
rest_headers?: string | null;
rest_status_url?: string | null;
rest_status_path?: string | null;
rest_status_on_value?: string | null;
rest_power_path?: string | null;
rest_energy_path?: string | null;
printer_id?: number | null;
enabled?: boolean;
auto_on?: boolean;
@@ -3362,6 +3398,13 @@ export const api = {
getHASensorEntities: () =>
request<HASensorEntity[]>('/smart-plugs/ha/sensors'),
// REST smart plug
testRESTConnection: (url: string, method: string = 'GET', headers?: string | null) =>
request<{ success: boolean; error: string | null }>('/smart-plugs/rest/test-connection', {
method: 'POST',
body: JSON.stringify({ url, method, headers }),
}),
// Print Queue
getQueue: (printerId?: number, status?: string, targetModel?: string) => {
const params = new URLSearchParams();
+227 -2
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
import { X, Save, Loader2, Wifi, WifiOff, CheckCircle, Bell, Clock, LayoutGrid, Search, Plug, Power, Home, Radio, Eye } from 'lucide-react';
import { X, Save, Loader2, Wifi, WifiOff, CheckCircle, Bell, Clock, LayoutGrid, Search, Plug, Power, Home, Radio, Eye, Globe } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { api } from '../api/client';
import type { SmartPlug, SmartPlugCreate, SmartPlugUpdate, DiscoveredTasmotaDevice } from '../api/client';
@@ -17,7 +17,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
const isEditing = !!plug;
// Plug type selection
const [plugType, setPlugType] = useState<'tasmota' | 'homeassistant' | 'mqtt'>(plug?.plug_type || 'tasmota');
const [plugType, setPlugType] = useState<'tasmota' | 'homeassistant' | 'mqtt' | 'rest'>(plug?.plug_type || 'tasmota');
const [name, setName] = useState(plug?.name || '');
// Tasmota fields
@@ -42,6 +42,18 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
const [mqttStateTopic, setMqttStateTopic] = useState(plug?.mqtt_state_topic || '');
const [mqttStatePath, setMqttStatePath] = useState(plug?.mqtt_state_path || '');
const [mqttStateOnValue, setMqttStateOnValue] = useState(plug?.mqtt_state_on_value || '');
// REST fields
const [restOnUrl, setRestOnUrl] = useState(plug?.rest_on_url || '');
const [restOnBody, setRestOnBody] = useState(plug?.rest_on_body || '');
const [restOffUrl, setRestOffUrl] = useState(plug?.rest_off_url || '');
const [restOffBody, setRestOffBody] = useState(plug?.rest_off_body || '');
const [restMethod, setRestMethod] = useState(plug?.rest_method || 'POST');
const [restHeaders, setRestHeaders] = useState(plug?.rest_headers || '');
const [restStatusUrl, setRestStatusUrl] = useState(plug?.rest_status_url || '');
const [restStatusPath, setRestStatusPath] = useState(plug?.rest_status_path || '');
const [restStatusOnValue, setRestStatusOnValue] = useState(plug?.rest_status_on_value || '');
const [restPowerPath, setRestPowerPath] = useState(plug?.rest_power_path || '');
const [restEnergyPath, setRestEnergyPath] = useState(plug?.rest_energy_path || '');
// HA energy sensor entities (optional)
const [haPowerEntity, setHaPowerEntity] = useState(plug?.ha_power_entity || '');
const [haEnergyTodayEntity, setHaEnergyTodayEntity] = useState(plug?.ha_energy_today_entity || '');
@@ -321,6 +333,13 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
}
}
if (plugType === 'rest') {
if (!restOnUrl.trim() && !restOffUrl.trim()) {
setError(t('smartPlugs.restUrlRequired'));
return;
}
}
const data = {
name: name.trim(),
plug_type: plugType,
@@ -342,6 +361,18 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
mqtt_state_topic: plugType === 'mqtt' ? (mqttStateTopic.trim() || null) : null,
mqtt_state_path: plugType === 'mqtt' ? (mqttStatePath.trim() || null) : null,
mqtt_state_on_value: plugType === 'mqtt' ? (mqttStateOnValue.trim() || null) : null,
// REST fields
rest_on_url: plugType === 'rest' ? (restOnUrl.trim() || null) : null,
rest_on_body: plugType === 'rest' ? (restOnBody.trim() || null) : null,
rest_off_url: plugType === 'rest' ? (restOffUrl.trim() || null) : null,
rest_off_body: plugType === 'rest' ? (restOffBody.trim() || null) : null,
rest_method: plugType === 'rest' ? restMethod : null,
rest_headers: plugType === 'rest' ? (restHeaders.trim() || null) : null,
rest_status_url: plugType === 'rest' ? (restStatusUrl.trim() || null) : null,
rest_status_path: plugType === 'rest' ? (restStatusPath.trim() || null) : null,
rest_status_on_value: plugType === 'rest' ? (restStatusOnValue.trim() || null) : null,
rest_power_path: plugType === 'rest' ? (restPowerPath.trim() || null) : null,
rest_energy_path: plugType === 'rest' ? (restEnergyPath.trim() || null) : null,
username: plugType === 'tasmota' ? (username.trim() || null) : null,
password: plugType === 'tasmota' ? (password.trim() || null) : null,
printer_id: printerId,
@@ -448,6 +479,22 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
<Radio className="w-4 h-4" />
MQTT
</button>
<button
type="button"
onClick={() => {
setPlugType('rest');
setTestResult(null);
setError(null);
}}
className={`flex-1 flex items-center justify-center gap-2 px-3 py-2.5 rounded-lg font-medium transition-colors ${
plugType === 'rest'
? 'bg-bambu-green text-white'
: 'bg-bambu-dark text-bambu-gray hover:text-white border border-bambu-dark-tertiary'
}`}
>
<Globe className="w-4 h-4" />
REST
</button>
</div>
)}
@@ -1074,6 +1121,184 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
</div>
)}
{/* REST API Section */}
{plugType === 'rest' && (
<div className="space-y-3">
{/* Control Section */}
<div className="space-y-3 p-3 bg-bambu-dark rounded-lg border border-bambu-dark-tertiary">
<p className="text-white font-medium text-sm">{t('smartPlugs.restControl')}</p>
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restMethod')}</label>
<select
value={restMethod}
onChange={(e) => setRestMethod(e.target.value)}
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="PATCH">PATCH</option>
</select>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restOnUrl')}</label>
<input
type="text"
value={restOnUrl}
onChange={(e) => { setRestOnUrl(e.target.value); setTestResult(null); }}
placeholder="http://openhab:8080/rest/items/MyPlug"
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restOnBody')} <span className="text-bambu-gray font-normal">({t('smartPlugs.optional')})</span></label>
<input
type="text"
value={restOnBody}
onChange={(e) => setRestOnBody(e.target.value)}
placeholder={t('smartPlugs.restBodyHint')}
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restOffUrl')}</label>
<input
type="text"
value={restOffUrl}
onChange={(e) => { setRestOffUrl(e.target.value); setTestResult(null); }}
placeholder="http://openhab:8080/rest/items/MyPlug"
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restOffBody')} <span className="text-bambu-gray font-normal">({t('smartPlugs.optional')})</span></label>
<input
type="text"
value={restOffBody}
onChange={(e) => setRestOffBody(e.target.value)}
placeholder={t('smartPlugs.restBodyHint')}
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
</div>
{/* Headers Section */}
<div className="space-y-3 p-3 bg-bambu-dark rounded-lg border border-bambu-dark-tertiary">
<p className="text-white font-medium text-sm">{t('smartPlugs.restHeaders')} <span className="text-bambu-gray font-normal">({t('smartPlugs.optional')})</span></p>
<div>
<textarea
value={restHeaders}
onChange={(e) => setRestHeaders(e.target.value)}
placeholder={t('smartPlugs.restHeadersHint')}
rows={2}
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none font-mono text-sm"
/>
</div>
</div>
{/* Status Polling Section (optional) */}
<div className="space-y-3 p-3 bg-bambu-dark rounded-lg border border-bambu-dark-tertiary">
<p className="text-white font-medium text-sm">{t('smartPlugs.stateMonitoring')} <span className="text-bambu-gray font-normal">({t('smartPlugs.optional')})</span></p>
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restStatusUrl')}</label>
<input
type="text"
value={restStatusUrl}
onChange={(e) => setRestStatusUrl(e.target.value)}
placeholder={t('smartPlugs.restStatusHint')}
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restStatusPath')}</label>
<input
type="text"
value={restStatusPath}
onChange={(e) => setRestStatusPath(e.target.value)}
placeholder={t('smartPlugs.restPathHint')}
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restStatusOnValue')}</label>
<input
type="text"
value={restStatusOnValue}
onChange={(e) => setRestStatusOnValue(e.target.value)}
placeholder="ON"
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
</div>
</div>
{/* Energy Monitoring (optional) */}
<div className="space-y-3 p-3 bg-bambu-dark rounded-lg border border-bambu-dark-tertiary">
<p className="text-white font-medium text-sm">{t('smartPlugs.energyMonitoring')} <span className="text-bambu-gray font-normal">({t('smartPlugs.optional')})</span></p>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restPowerPath')}</label>
<input
type="text"
value={restPowerPath}
onChange={(e) => setRestPowerPath(e.target.value)}
placeholder={t('smartPlugs.restPathHint')}
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
<div>
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restEnergyPath')}</label>
<input
type="text"
value={restEnergyPath}
onChange={(e) => setRestEnergyPath(e.target.value)}
placeholder={t('smartPlugs.restPathHint')}
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
/>
</div>
</div>
<p className="text-xs text-bambu-gray">
{t('smartPlugs.restEnergyHint')}
</p>
</div>
{/* Test Connection */}
{(restOnUrl.trim() || restOffUrl.trim()) && (
<div className="flex gap-2">
<Button
type="button"
variant="secondary"
onClick={async () => {
setTestResult(null);
try {
const url = restOnUrl.trim() || restOffUrl.trim();
const result = await api.testRESTConnection(url, restMethod, restHeaders.trim() || null);
setTestResult({ success: result.success });
if (!result.success) {
setError(result.error || t('smartPlugs.addSmartPlug.connectionFailed'));
}
} catch {
setTestResult({ success: false });
setError(t('smartPlugs.addSmartPlug.connectionFailed'));
}
}}
className="w-full"
>
<Wifi className="w-4 h-4" />
{t('smartPlugs.testConnection')}
</Button>
</div>
)}
{testResult && (
<div className={`flex items-center gap-2 text-sm ${testResult.success ? 'text-green-400' : 'text-red-400'}`}>
{testResult.success ? <CheckCircle className="w-4 h-4" /> : <WifiOff className="w-4 h-4" />}
{testResult.success ? t('smartPlugs.connectionSuccess') : t('smartPlugs.addSmartPlug.connectionFailed')}
</div>
)}
</div>
)}
{/* IP Address - only show for Tasmota */}
{plugType === 'tasmota' && (
<div>
+12 -3
View File
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
import { Plug, Power, PowerOff, Loader2, Trash2, Settings2, Thermometer, Clock, Wifi, WifiOff, Edit2, Bell, Calendar, LayoutGrid, ExternalLink, Home, Radio, Eye } from 'lucide-react';
import { Plug, Power, PowerOff, Loader2, Trash2, Settings2, Thermometer, Clock, Wifi, WifiOff, Edit2, Bell, Calendar, LayoutGrid, ExternalLink, Home, Radio, Eye, Globe } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { api } from '../api/client';
import type { SmartPlug, SmartPlugUpdate } from '../api/client';
@@ -133,6 +133,8 @@ export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) {
<Radio className={`w-5 h-5 ${isReachable ? 'text-teal-400' : 'text-red-400'}`} />
) : plug.plug_type === 'homeassistant' ? (
<Home className={`w-5 h-5 ${isReachable ? (isOn ? 'text-bambu-green' : 'text-bambu-gray') : 'text-red-400'}`} />
) : plug.plug_type === 'rest' ? (
<Globe className={`w-5 h-5 ${isReachable ? (isOn ? 'text-bambu-green' : 'text-bambu-gray') : 'text-red-400'}`} />
) : (
<Plug className={`w-5 h-5 ${isReachable ? (isOn ? 'text-bambu-green' : 'text-bambu-gray') : 'text-red-400'}`} />
)}
@@ -141,9 +143,9 @@ export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) {
<h3 className="font-medium text-white truncate">{plug.name}</h3>
<p
className="text-sm text-bambu-gray truncate"
title={plug.plug_type === 'mqtt' ? plug.mqtt_topic ?? undefined : plug.plug_type === 'homeassistant' ? plug.ha_entity_id ?? undefined : plug.ip_address ?? undefined}
title={plug.plug_type === 'mqtt' ? plug.mqtt_topic ?? undefined : plug.plug_type === 'homeassistant' ? plug.ha_entity_id ?? undefined : plug.plug_type === 'rest' ? plug.rest_on_url ?? plug.rest_off_url ?? undefined : plug.ip_address ?? undefined}
>
{plug.plug_type === 'mqtt' ? plug.mqtt_topic : plug.plug_type === 'homeassistant' ? plug.ha_entity_id : plug.ip_address}
{plug.plug_type === 'mqtt' ? plug.mqtt_topic : plug.plug_type === 'homeassistant' ? plug.ha_entity_id : plug.plug_type === 'rest' ? (plug.rest_on_url || plug.rest_off_url) : plug.ip_address}
</p>
</div>
</div>
@@ -165,6 +167,13 @@ export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) {
{isReachable ? (status?.state || '?') : t('smartPlugs.offline')}
</span>
</div>
) : plug.plug_type === 'rest' ? (
<div className="flex items-center gap-1 text-sm">
<span className="px-1 py-0.5 bg-purple-500/20 text-purple-400 text-[10px] font-medium rounded">REST</span>
<span className={isReachable ? (isOn ? 'text-status-ok' : 'text-bambu-gray') : 'text-status-error'}>
{isReachable ? (status?.state || '?') : t('smartPlugs.offline')}
</span>
</div>
) : isReachable ? (
<div className="flex items-center gap-1 text-sm">
<Wifi className="w-4 h-4 text-status-ok" />
+21
View File
@@ -3837,6 +3837,27 @@ export default {
mqttPowerHint: 'JSON-Pfad extrahiert Wert aus JSON-Payload (z.B. "power_l1"). Leer lassen wenn Topic rohe numerische Werte sendet.\nMultiplikator 0.001 für mW→W, 1000 für kW→W verwenden.',
mqttEnergyHint: 'JSON-Pfad extrahiert Wert aus JSON-Payload. Leer lassen für rohe Werte.\nMultiplikator 0.001 für Wh→kWh, 1000 für MWh→kWh verwenden.',
mqttStateHint: 'JSON-Pfad extrahiert Wert aus JSON-Payload. Leer lassen für rohe Werte.\nEIN-Wert: der genaue String der "EIN" bedeutet. Leer lassen für Auto-Erkennung (ON, true, 1).',
// REST smart plug
restControl: 'Control',
restOnUrl: 'Turn ON URL',
restOffUrl: 'Turn OFF URL',
restOnBody: 'ON Request Body',
restOffBody: 'OFF Request Body',
restMethod: 'HTTP Method',
restHeaders: 'Custom Headers (JSON)',
restStatusUrl: 'Status URL',
restStatusPath: 'State JSON Path',
restStatusOnValue: 'ON Value',
restPowerPath: 'Power JSON Path',
restEnergyPath: 'Energy JSON Path',
restUrlRequired: 'At least one URL (ON or OFF) is required for REST plugs',
restHeadersHint: 'e.g. {"Authorization": "Bearer your-token"}',
restBodyHint: 'e.g. ON, {"state": "on"}',
restStatusHint: 'URL to poll for current state',
restPathHint: 'e.g. state or data.power.status',
restEnergyHint: 'JSON paths to extract power (watts) and energy (kWh) from the status response.',
testConnection: 'Test Connection',
connectionSuccess: 'Connection successful',
noSwitchesInSwitchbar: 'Keine Schalter in der Schaltleiste',
enableSwitchbarHint: '"In Schaltleiste anzeigen" unter Einstellungen > Smart Plugs aktivieren',
},
+21
View File
@@ -3843,6 +3843,27 @@ export default {
mqttPowerHint: 'JSON path extracts value from JSON payload (e.g., "power_l1"). Leave empty if topic publishes raw numeric values.\nUse multiplier 0.001 for mW→W, 1000 for kW→W.',
mqttEnergyHint: 'JSON path extracts value from JSON payload. Leave empty for raw values.\nUse multiplier 0.001 for Wh→kWh, 1000 for MWh→kWh.',
mqttStateHint: 'JSON path extracts value from JSON payload. Leave empty for raw values.\nON value: the exact string that means "ON". Leave empty for auto-detect (ON, true, 1).',
// REST smart plug
restControl: 'Control',
restOnUrl: 'Turn ON URL',
restOffUrl: 'Turn OFF URL',
restOnBody: 'ON Request Body',
restOffBody: 'OFF Request Body',
restMethod: 'HTTP Method',
restHeaders: 'Custom Headers (JSON)',
restStatusUrl: 'Status URL',
restStatusPath: 'State JSON Path',
restStatusOnValue: 'ON Value',
restPowerPath: 'Power JSON Path',
restEnergyPath: 'Energy JSON Path',
restUrlRequired: 'At least one URL (ON or OFF) is required for REST plugs',
restHeadersHint: 'e.g. {"Authorization": "Bearer your-token"}',
restBodyHint: 'e.g. ON, {"state": "on"}',
restStatusHint: 'URL to poll for current state',
restPathHint: 'e.g. state or data.power.status',
restEnergyHint: 'JSON paths to extract power (watts) and energy (kWh) from the status response.',
testConnection: 'Test Connection',
connectionSuccess: 'Connection successful',
noSwitchesInSwitchbar: 'No switches in switchbar',
enableSwitchbarHint: 'Enable "Show in Switchbar" in Settings > Smart Plugs',
},
+21
View File
@@ -3829,6 +3829,27 @@ export default {
mqttPowerHint: 'Le chemin JSON extrait la valeur du payload JSON (ex: "power_l1"). Laisser vide si le topic publie des valeurs numériques brutes.\nUtiliser le multiplicateur 0.001 pour mW→W, 1000 pour kW→W.',
mqttEnergyHint: 'Le chemin JSON extrait la valeur du payload JSON. Laisser vide pour les valeurs brutes.\nUtiliser le multiplicateur 0.001 pour Wh→kWh, 1000 pour MWh→kWh.',
mqttStateHint: 'Le chemin JSON extrait la valeur du payload JSON. Laisser vide pour les valeurs brutes.\nValeur ON : la chaîne exacte signifiant "ON". Laisser vide pour la détection auto (ON, true, 1).',
// REST smart plug
restControl: 'Control',
restOnUrl: 'Turn ON URL',
restOffUrl: 'Turn OFF URL',
restOnBody: 'ON Request Body',
restOffBody: 'OFF Request Body',
restMethod: 'HTTP Method',
restHeaders: 'Custom Headers (JSON)',
restStatusUrl: 'Status URL',
restStatusPath: 'State JSON Path',
restStatusOnValue: 'ON Value',
restPowerPath: 'Power JSON Path',
restEnergyPath: 'Energy JSON Path',
restUrlRequired: 'At least one URL (ON or OFF) is required for REST plugs',
restHeadersHint: 'e.g. {"Authorization": "Bearer your-token"}',
restBodyHint: 'e.g. ON, {"state": "on"}',
restStatusHint: 'URL to poll for current state',
restPathHint: 'e.g. state or data.power.status',
restEnergyHint: 'JSON paths to extract power (watts) and energy (kWh) from the status response.',
testConnection: 'Test Connection',
connectionSuccess: 'Connection successful',
noSwitchesInSwitchbar: 'Aucun commutateur dans la barre',
enableSwitchbarHint: 'Activez "Afficher dans la barre de commutateurs" dans Paramètres > Smart Plugs',
},
+21
View File
@@ -3828,6 +3828,27 @@ export default {
mqttPowerHint: 'Il percorso JSON estrae il valore dal payload JSON (es. "power_l1"). Lascia vuoto se il topic pubblica valori numerici grezzi.\nUsa moltiplicatore 0.001 per mW→W, 1000 per kW→W.',
mqttEnergyHint: 'Il percorso JSON estrae il valore dal payload JSON. Lascia vuoto per valori grezzi.\nUsa moltiplicatore 0.001 per Wh→kWh, 1000 per MWh→kWh.',
mqttStateHint: 'Il percorso JSON estrae il valore dal payload JSON. Lascia vuoto per valori grezzi.\nValore ON: la stringa esatta che significa "ON". Lascia vuoto per rilevamento auto (ON, true, 1).',
// REST smart plug
restControl: 'Control',
restOnUrl: 'Turn ON URL',
restOffUrl: 'Turn OFF URL',
restOnBody: 'ON Request Body',
restOffBody: 'OFF Request Body',
restMethod: 'HTTP Method',
restHeaders: 'Custom Headers (JSON)',
restStatusUrl: 'Status URL',
restStatusPath: 'State JSON Path',
restStatusOnValue: 'ON Value',
restPowerPath: 'Power JSON Path',
restEnergyPath: 'Energy JSON Path',
restUrlRequired: 'At least one URL (ON or OFF) is required for REST plugs',
restHeadersHint: 'e.g. {"Authorization": "Bearer your-token"}',
restBodyHint: 'e.g. ON, {"state": "on"}',
restStatusHint: 'URL to poll for current state',
restPathHint: 'e.g. state or data.power.status',
restEnergyHint: 'JSON paths to extract power (watts) and energy (kWh) from the status response.',
testConnection: 'Test Connection',
connectionSuccess: 'Connection successful',
noSwitchesInSwitchbar: 'Nessun interruttore nella barra',
enableSwitchbarHint: 'Abilita "Mostra nella barra interruttori" in Impostazioni > Smart Plugs',
},
+21
View File
@@ -3841,6 +3841,27 @@ export default {
mqttPowerHint: 'JSONパスはJSONペイロードから値を抽出します(例: "power_l1")。トピックが生の数値を送信する場合は空のままにしてください。\n乗数: mW→Wは0.001、kW→Wは1000を使用。',
mqttEnergyHint: 'JSONパスはJSONペイロードから値を抽出します。生の値の場合は空のままにしてください。\n乗数: Wh→kWhは0.001、MWh→kWhは1000を使用。',
mqttStateHint: 'JSONパスはJSONペイロードから値を抽出します。生の値の場合は空のままにしてください。\nON値: "ON"を意味する正確な文字列。自動検出(ON、true、1)の場合は空のままにしてください。',
// REST smart plug
restControl: 'Control',
restOnUrl: 'Turn ON URL',
restOffUrl: 'Turn OFF URL',
restOnBody: 'ON Request Body',
restOffBody: 'OFF Request Body',
restMethod: 'HTTP Method',
restHeaders: 'Custom Headers (JSON)',
restStatusUrl: 'Status URL',
restStatusPath: 'State JSON Path',
restStatusOnValue: 'ON Value',
restPowerPath: 'Power JSON Path',
restEnergyPath: 'Energy JSON Path',
restUrlRequired: 'At least one URL (ON or OFF) is required for REST plugs',
restHeadersHint: 'e.g. {"Authorization": "Bearer your-token"}',
restBodyHint: 'e.g. ON, {"state": "on"}',
restStatusHint: 'URL to poll for current state',
restPathHint: 'e.g. state or data.power.status',
restEnergyHint: 'JSON paths to extract power (watts) and energy (kWh) from the status response.',
testConnection: 'Test Connection',
connectionSuccess: 'Connection successful',
noSwitchesInSwitchbar: 'スイッチバーにスイッチがありません',
enableSwitchbarHint: '設定 > スマートプラグで「スイッチバーに表示」を有効にしてください',
},
+21
View File
@@ -3828,6 +3828,27 @@ export default {
mqttPowerHint: 'O caminho JSON extrai o valor do payload JSON (ex: "power_l1"). Deixe vazio se o tópico publica valores numéricos brutos.\nUse multiplicador 0.001 para mW→W, 1000 para kW→W.',
mqttEnergyHint: 'O caminho JSON extrai o valor do payload JSON. Deixe vazio para valores brutos.\nUse multiplicador 0.001 para Wh→kWh, 1000 para MWh→kWh.',
mqttStateHint: 'O caminho JSON extrai o valor do payload JSON. Deixe vazio para valores brutos.\nValor ON: a string exata que significa "ON". Deixe vazio para detecção automática (ON, true, 1).',
// REST smart plug
restControl: 'Control',
restOnUrl: 'Turn ON URL',
restOffUrl: 'Turn OFF URL',
restOnBody: 'ON Request Body',
restOffBody: 'OFF Request Body',
restMethod: 'HTTP Method',
restHeaders: 'Custom Headers (JSON)',
restStatusUrl: 'Status URL',
restStatusPath: 'State JSON Path',
restStatusOnValue: 'ON Value',
restPowerPath: 'Power JSON Path',
restEnergyPath: 'Energy JSON Path',
restUrlRequired: 'At least one URL (ON or OFF) is required for REST plugs',
restHeadersHint: 'e.g. {"Authorization": "Bearer your-token"}',
restBodyHint: 'e.g. ON, {"state": "on"}',
restStatusHint: 'URL to poll for current state',
restPathHint: 'e.g. state or data.power.status',
restEnergyHint: 'JSON paths to extract power (watts) and energy (kWh) from the status response.',
testConnection: 'Test Connection',
connectionSuccess: 'Connection successful',
noSwitchesInSwitchbar: 'Nenhum interruptor na barra',
enableSwitchbarHint: 'Ative "Mostrar na barra de interruptores" em Configurações > Smart Plugs',
},
+21
View File
@@ -3828,6 +3828,27 @@ export default {
mqttPowerHint: 'JSON路径从JSON负载中提取值(例如"power_l1")。如果主题发布原始数值,请留空。\n乘数:mW→W使用0.001,kW→W使用1000。',
mqttEnergyHint: 'JSON路径从JSON负载中提取值。原始值请留空。\n乘数:Wh→kWh使用0.001,MWh→kWh使用1000。',
mqttStateHint: 'JSON路径从JSON负载中提取值。原始值请留空。\nON值:表示"ON"的确切字符串。留空以自动检测(ON、true、1)。',
// REST smart plug
restControl: 'Control',
restOnUrl: 'Turn ON URL',
restOffUrl: 'Turn OFF URL',
restOnBody: 'ON Request Body',
restOffBody: 'OFF Request Body',
restMethod: 'HTTP Method',
restHeaders: 'Custom Headers (JSON)',
restStatusUrl: 'Status URL',
restStatusPath: 'State JSON Path',
restStatusOnValue: 'ON Value',
restPowerPath: 'Power JSON Path',
restEnergyPath: 'Energy JSON Path',
restUrlRequired: 'At least one URL (ON or OFF) is required for REST plugs',
restHeadersHint: 'e.g. {"Authorization": "Bearer your-token"}',
restBodyHint: 'e.g. ON, {"state": "on"}',
restStatusHint: 'URL to poll for current state',
restPathHint: 'e.g. state or data.power.status',
restEnergyHint: 'JSON paths to extract power (watts) and energy (kWh) from the status response.',
testConnection: 'Test Connection',
connectionSuccess: 'Connection successful',
noSwitchesInSwitchbar: '开关栏中没有开关',
enableSwitchbarHint: '在设置 > 智能插座中启用"在开关栏显示"',
},