@demirdeniz/node-red-contrib-tuya-kepler-device
Version:
A node-red module to interact with the tuya smart devices (updated with Tuya protocol 3.5)
573 lines (547 loc) • 17.8 kB
HTML
<script type="text/javascript">
RED.nodes.registerType('tuya-kepler-device', {
category: 'Kepler Home',
color: '#fc8144',
credentials: {
secretConfig: {
value: '',
},
},
defaults: {
deviceName: { value: '', required: true },
disableAutoStart: { value: false, required: true },
deviceId: {
value: '',
required: false,
validate: function (v) {
let value = v;
let credsOffline = false;
const storeAsCreds = this.storeAsCreds || false;
if (storeAsCreds) {
if (typeof this.credentials !== 'object') {
credsOffline = true;
} else {
const secretConfig = this.credentials.secretConfig || '{}';
const secret = JSON.parse(secretConfig);
value = secret.deviceId;
}
}
return typeof this.deviceIp === 'string' &&
this.deviceIp.trim().length == 0
? storeAsCreds && credsOffline
? true
: value.trim().length > 0
: true;
},
},
deviceKey: {
value: '',
required: false,
validate: function (v) {
let value = v;
let credsOffline = false;
const storeAsCreds = this.storeAsCreds || false;
if (storeAsCreds) {
if (typeof this.credentials !== 'object') {
credsOffline = true;
} else {
const secretConfig = this.credentials.secretConfig || '{}';
const secret = JSON.parse(secretConfig);
value = secret.deviceKey;
}
}
return storeAsCreds && credsOffline ? true : value.trim().length > 0;
},
},
storeAsCreds: {
value: false,
},
deviceIp: {
value: '',
required: false,
validate: function (v) {
return $('#node-input-deviceId').val() &&
$('#node-input-deviceId').val().trim().length == 0
? v.trim().length > 0
: true;
},
},
retryTimeout: {
value: 1000,
required: true,
validate: RED.validators.number(),
},
findTimeout: {
value: 10000,
required: true,
validate: RED.validators.number(),
},
tuyaVersion: { value: '3.3', required: false },
eventMode: { value: 'event-both', required: true },
enableKeepAlive: { value: false, required: true },
logLevel: { value: 'log-level-disable', required: false },
issueRefreshOnConnect: {
value: false,
required: true,
validate: function(v) {
if(typeof(v) == "boolean")
return !(($('#node-input-issueRefreshOnConnect').is(':checked')) &&
($('#node-input-issueGetOnConnect').is(':checked')));
else
return true;
}
},
issueGetOnConnect: {
value: false,
required: true,
validate: function(v) {
if(typeof(v) == "boolean")
return !(($('#node-input-issueRefreshOnConnect').is(':checked')) &&
($('#node-input-issueGetOnConnect').is(':checked')));
else
return true;
}
},
initialDelay: {
value: 10000,
required: true,
validate: RED.validators.number()
},
socketTimeout: {
value: 5000,
required: true,
validate: RED.validators.number()
},
HeartBeatInterval: {
value: 25,
required: true,
validate: RED.validators.number()
}
},
inputs: 1,
outputs: 2,
inputLabels: 'Command for the device',
outputLabels: ['Data from the device', 'Client State'],
icon: 'tuya_smart_48x48.png',
paletteLabel: 'tuya device',
label: function () {
return this.deviceName || 'tuya kepler device';
},
////////// onEditPrepare ///////////////
oneditprepare: function () {
let eventMode = this.eventMode || 'event-both';
$("select[name='node-input-eventMode']")
.find(`option[value='${eventMode}']`)
.attr('selected', true);
let logLevel = this.logLevel || 'log-level-disable';
$("select[name='node-input-logLevel']")
.find(`option[value='${logLevel}']`)
.attr('selected', true);
const storeAsCreds = this.storeAsCreds || false;
if (storeAsCreds) {
const secretConfig = this.credentials.secretConfig || '{}';
const secret = JSON.parse(secretConfig);
console.log('Loading config from secret');
$('#node-input-deviceId').val(secret.deviceId);
$('#node-input-deviceKey').val(secret.deviceKey);
}
$('#node-input-findTimeout').val(this.findTimeout || 1000);
$('#node-input-retryTimeout').val(this.retryTimeout || 1000);
$('#node-input-initialDelay').val(this.initialDelay || 10000);
$('#node-input-socketTimeout').val(this.socketTimeout || 5000);
$('#node-input-HeartBeatInterval').val(this.HeartBeatInterval || 25);
if (
this.tuyaVersion == null ||
typeof this.tuyaVersion == 'undefined' ||
this.tuyaVersion.trim().length == 0
) {
$('#node-input-tuyaVersion').val(3.1);
}
},
////////// onEditSave ///////////////
oneditsave: function () {
const storeAsCreds =
$('#node-input-storeAsCreds').is(':checked') || false;
if (storeAsCreds) {
const secret = {
deviceId: $('#node-input-deviceId').val(),
deviceKey: $('#node-input-deviceKey').val(),
};
console.log('Saving secret');
$('#node-input-secretConfig').val(JSON.stringify(secret));
$('#node-input-deviceId').val('');
$('#node-input-deviceKey').val('');
} else {
$('#node-input-secretConfig').val('{}');
};
//if($('#node-input-issueGetOnConnect').is(':checked'))
// $('#node-input-issueRefreshOnConnect').prop("checked", false);
}
});
</script>
<script type="text/html" data-template-name="tuya-kepler-device">
<input
type="hidden"
id="node-input-secretConfig"
placeholder="Device Secret"
/>
<div class="form-row">
<label style="width:100%" for="node-input-deviceName"
><i class="fa fa-tag"></i> Device Name</label
>
<input type="text" id="node-input-deviceName" placeholder="Bulb" />
</div>
<hr />
<div class="form-row" style="font-weight: bold;">Connection Details</div>
<div class="form-row">
<label style="width:100%" for="node-input-deviceId"
><i class="fa fa-id-card-o"></i> Device Virtual ID</label
>
<input
type="text"
id="node-input-deviceId"
placeholder="Device Virtual ID"
/>
</div>
<div class="form-row">
<label style="width:100%" for="node-input-deviceKey"
><i class="fa fa-key"></i> Device Key</label
>
<input type="text" id="node-input-deviceKey" placeholder="Device Key" />
</div>
<div class="form-row">
<label style="width:100%" for="node-input-storeAsCreds">
<input
type="checkbox"
style="width: auto;"
id="node-input-storeAsCreds"
/>
Store Device Id and Device Key as credentials (Credentials will not appear
in the flow export)</label
>
</div>
<br />
<div class="form-row">
<label style="width:100%" for="node-input-deviceIp"
><i class="fa fa-info"></i> Device IP - for faster connections (Use Static IP)
</label>
<input
type="text"
id="node-input-deviceIp"
placeholder="The ip of the device"
/>
</div>
<div
style="margin-bottom:10px;font-style: italic;color: brown;font-size: smaller;"
>
Device IP is optional (i.e. if not known, will try use Device Virtual ID to find it but will be slow and may be unsuccessful)
</div>
<hr />
<div class="form-row" style="font-weight: bold;">Advanced Options</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-disableAutoStart"
placeholder=""
style="width:auto"
/>
<label for="node-input-disableAutoStart" style="width:auto">
Disable auto connect on start
</label>
<br />
(You have to manually issue the CONNECT CONTROL COMMAND. Refer the help
panel)
<br />
</div>
<div class="form-row">
<label style="width:100%" for="node-input-retryTimeout"
><i class="fa fa-clock-o"></i> Interval for retry connection incase of
error (milliseconds)</label
>
</div>
<div class="form-row">
<input
type="number"
id="node-input-retryTimeout"
placeholder=""
value="1000"
/>
</div>
<div class="form-row">
<label style="width:100%" for="node-input-findTimeout"
><i class="fa fa-clock-o"></i> Interval for find operation incase of error
(milliseconds)</label
>
<input
type="number"
id="node-input-findTimeout"
placeholder=""
value="1000"
/>
</div>
<div class="form-row">
<label style="width:100%" for="node-input-tuyaVersion"
><i class="fa fa-lock"></i> Tuya Protocol Version, Default : 3.1 (don't
change if you are not sure)</label
>
<input type="text" id="node-input-tuyaVersion" placeholder="" value="3.1" />
</div>
<div class="form-row">
<label style="width:100%" for="node-input-subscribeMode"
><i class="fa fa-wrench"></i> Listen to tuya events</label
>
</div>
<div class="form-row">
<select name="node-input-eventMode" id="node-input-eventMode">
<option value="event-data">Data Event</option>
<option value="event-dp-refresh">DP-Refresh Event</option>
<option value="event-both">Both Events</option>
</select>
</div>
<hr />
<div class="form-row" style="font-weight: bold;">Kepler Options</div>
<div class="form-row">
<label style="width:100%" for="node-input-loglevel">
<i class="fa fa-file"></i> Log Level (Tuya API Logging)
</label>
</div>
<div class="form-row">
<select name="node-input-logLevel" id="node-input-logLevel">
<option value="log-level-debug">Debug</option>
<option value="log-level-disable">Disable</option>
</select>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-enableKeepAlive"
placeholder=""
style="width:auto"
/>
<label for="node-input-enableKeepAlive" style="width:auto">
Enable TCP KeepAlive
</label>
</div>
<div class="form-row">
<label style="width:100%" for="node-input-initialDelay">
<i class="fa fa-clock-o"></i> Interval after last packet to send KeepAlive (milliseconds)
</label>
</div>
<div class="form-row">
<input
type="number"
id="node-input-initialDelay"
placeholder=""
value="10000"
/>
</div>
<div class="form-row">
<label style="width:100%" for="node-input-socketTimeout"
><i class="fa fa-clock-o"></i> Socket Connection Timeout (milliseconds)
</label>
<input
type="number"
id="node-input-socketTimeout"
placeholder=""
value="5000"
/>
</div>
<div class="form-row">
<label style="width:100%" for="node-input-HeartBeatInterval"
><i class="fa fa-clock-o"></i> Interval between Heartbeat messages (in seconds, minimum 5 seconds)
</label>
<input
type="number"
id="node-input-HeartBeatInterval"
placeholder=""
value="25"
/>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-issueGetOnConnect"
placeholder=""
style="width:auto"
/>
<label for="node-input-issueGetOnConnect" style="width:auto">
Issue Get Command upon device connect (cannot be combined with RefreshOnConnect)
</label>
</div>
<div class="form-row">
<input
type="checkbox"
id="node-input-issueRefreshOnConnect"
placeholder=""
style="width:auto"
/>
<label for="node-input-issueRefreshOnConnect" style="width:auto">
Issue Refresh Command upon device connect (cannot be combined with GetOnConnect)
</label>
</div>
</script>
<script type="text/html" data-help-name="tuya-kepler-device">
<p>This node will help you to configure any tuya kepler device.</p>
<h3>Details</h3>
<p>
Instructions for getting the device id is available
<a href=""
>here</a
>
</p>
<code> You can get the device id and the key once you follow any of the resources available in youtube</code>
<p>
The node takes the input which needs to be sent to the device and outouts
the message if the state of the device changes.
</p>
<h3>Input</h3>
<h4>payload</h4>
<dl class="message-properties">
<dt class="optional">
operation
<span class="property-type">SET/GET</span>
</dt>
<dd>
<p>
<b>SET</b> (default): Use SET the run a set operation in the device
<a href=""
>SET documentation</a
>
</p>
<p>
<b>GET</b>: Use GET the run a get operation in the device
<a href=""
>GET documentation</a
>
</p>
<p>
<b>REFRESH</b>: Use REFRESH to refresh a device status
<a
href=""
>REFRESH documentation</a
>
</p>
<p>
<b>CONTROL</b>: Send the control events to the node. This can be used to
issue actions like connect, disconnect etc.
</p>
Any output of the operation will be triggered on the output of the node.
</dd>
<dt class="">dps <span class="property-type">Number</span></dt>
<dd>The dps index you want to update or query</dd>
<dt class="">set <span class="property-type">Any</span></dt>
<dd>The value you want to set for the dps index</dd>
<dt class="optional">
multiple <span class="property-type">Boolean</span>
</dt>
<dd>
Incase you want to send multiple dps index for a SET operation. Use the
data argument in that case.
</dd>
<dt class="optional">data <span class="property-type">Object</span></dt>
<dd>
Object of the below format with multiple dps index and the values
<code>
payload = { "multiple": true, "data": { "20": true, "24": "00e203e803de"
} }</code
>
</dd>
<dt class="">action <span class="property-type">string</span></dt>
<dd>
This property works only with operation is set as "CONTROL"
<table border>
<tr>
<td>Action</td>
<td>Purpose</td>
</tr>
<tr>
<td>CONNECT</td>
<td>Connects to the device</td>
</tr>
<tr>
<td>DISCONNECT</td>
<td>Disconnect from the device</td>
</tr>
<tr>
<td>RECONNECT</td>
<td>Reconnects to the device</td>
</tr>
<tr>
<td>SET_FIND_TIMEOUT</td>
<td>
Sets the find timeout dynamically. This is not saved in the config
</td>
</tr>
<tr>
<td>SET_RETRY_TIMEOUT</td>
<td>
Sets the retry timeout dynamically. This is not saved in the config
</td>
</tr>
<tr>
<td>SET_EVENT_MODE</td>
<td>
Sets the event mode dynamically. This is not saved in the config.
<br />
<br />
value = event-data //for subscribing to only data event
<br />
<br />
value = event-dp-refresh //for subscribing to only dp-refresh event
<br />
<br />
value = both //for subscribing to both events
</td>
</tr>
</table>
example :
<div>
action : CONNECT
<code> payload = { "operation": "CONTROL", "action": "CONNECT" } </code>
</div>
<div>
action : SET_EVENT_MODE
<code>
payload = { "operation": "CONTROL", "action": "SET_EVENT_MODE",
"value": "event-data" }
</code>
</div>
</dd>
</dl>
<h3>Output</h3>
<ol class="node-ports">
<li>
Data from device
<dl class="message-properties">
<dt class="">
deviceId
<span class="property-type">String</span>
</dt>
<dd>Device Id</dd>
<dt class="">deviceName <span class="property-type">String</span></dt>
<dd>Device Name</dd>
<dt class="optional">data <span class="property-type">Object</span></dt>
<dd>
The response from the Tuya device. Sample response is shown below.
<code>
data: { dps : { 1 : false }, t: 1600955043 }, deviceId:
"39390e7421ia", deviceName: "Monitor Plug"
</code>
</dd>
</dl>
</li>
<li>
Client state
<dl class="message-properties">
<dt class="">
state
<span class="property-type">String</span>
</dt>
<dd>
Shows the current status of the node (CONNECTED, CONNECTING,
DISCONNECTED,ERROR)
</dd>
</dl>
</li>
</ol>
</script>