Hi everyone,
im new to this community and i wanted to add my Jacuzzi Heater (Invergo App) to Homey. This was i big struggle but i managed to get it fixed with HomeyScript and would like to share this with everyone who might need it too!
So first i created a Cloud Project on the Tuya Developer Platform. (im not going into detail, if you have question for this step just ask me and i will explain it more detailled)
Then i linked my SmartLife app with the Tuya Cloud Project (again not going in detail)
When this is all setup and you made sure that you can control your devices and not only read then we are half way there.
So now i needed to send a command to the Tuya Cloud Project from my Homey script.
here is the code i used and it gets a token that refreshes every 2 hours by the request.
const clientId = 'Your Cloud Project ClientID';
const accessSecret = 'Your Access Secret (DONT SHARE!)';
const deviceId = 'Your Device ID';
// SHA256 and HMAC-SHA256 functions (your originals)
function sha256(ascii) {
function rightRotate(value, amount) {
return (value >>> amount) | (value << (32 - amount));
}
const maxWord = Math.pow(2, 32);
let result = '';
const words = [];
const asciiBitLength = ascii.length * 8;
const hash = sha256.h || [];
const k = sha256.k || [];
let primeCounter = k.length;
const isComposite = {};
for (let candidate = 2; primeCounter < 64; candidate++) {
if (!isComposite[candidate]) {
for (let i = 0; i < 313; i += candidate) {
isComposite[i] = candidate;
}
hash[primeCounter] = (Math.pow(candidate, .5) * maxWord) | 0;
k[primeCounter++] = (Math.pow(candidate, 1/3) * maxWord) | 0;
}
}
ascii += '\x80';
while (ascii.length % 64 - 56) ascii += '\x00';
for (let i = 0; i < ascii.length; i++) {
const j = ascii.charCodeAt(i);
if (j >> 8) return;
words[i >> 2] |= j << ((3 - i) % 4) * 8;
}
words[words.length] = (asciiBitLength / maxWord) | 0;
words[words.length] = (asciiBitLength);
for (let j = 0; j < words.length;) {
const w = words.slice(j, j += 16);
const oldHash = hash.slice(0, 8);
for (let i = 0; i < 64; i++) {
const w15 = w[i - 15], w2 = w[i - 2];
const a = hash[0], e = hash[4];
const temp1 = hash[7] +
(rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) +
((e & hash[5]) ^ (~e & hash[6])) +
k[i] +
(w[i] = (i < 16) ? w[i] : (
w[i - 16] +
(rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) +
w[i - 7] +
(rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))
) | 0
);
const temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) +
((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));
hash.unshift((temp1 + temp2) | 0);
hash[4] = (hash[4] + temp1) | 0;
}
for (let i = 0; i < 8; i++) {
hash[i] = (hash[i] + oldHash[i]) | 0;
}
}
for (let i = 0; i < 8; i++) {
for (let j = 3; j + 1; j--) {
const b = (hash[i] >> (j * 8)) & 255;
result += ((b < 16) ? '0' : '') + b.toString(16);
}
}
return result;
}
function hex2bin(hex) {
const bytes = [];
for (let i = 0; i < hex.length; i += 2) {
bytes.push(String.fromCharCode(parseInt(hex.substr(i, 2), 16)));
}
return bytes.join('');
}
function hmacSha256(key, message) {
const blocksize = 64;
if (key.length > blocksize) {
key = hex2bin(sha256(key));
}
if (key.length < blocksize) {
key += '\x00'.repeat(blocksize - key.length);
}
let o_key_pad = '', i_key_pad = '';
for (let i = 0; i < blocksize; i++) {
o_key_pad += String.fromCharCode(key.charCodeAt(i) ^ 0x5c);
i_key_pad += String.fromCharCode(key.charCodeAt(i) ^ 0x36);
}
const innerHash = sha256(i_key_pad + message);
const finalHash = sha256(o_key_pad + hex2bin(innerHash));
return finalHash;
}
// --- SAFE HomeyScript Compatible URL PATH Extractor ---
function extractPathAndQuery(fullUrl) {
const withoutProtocol = fullUrl.replace(/^https?:\/\//, '');
const pathIndex = withoutProtocol.indexOf('/');
if (pathIndex === -1) return '/';
return withoutProtocol.substring(pathIndex);
}
// --- Signature Generator ---
function generateSignature({ clientId, accessToken = '', secret, method, fullUrl, body = '' }) {
const t = Date.now().toString();
const pathAndQuery = extractPathAndQuery(fullUrl); // Safe extraction
const bodyHash = sha256(body || '');
const message = clientId + accessToken + t + method.toUpperCase() + "\n" + bodyHash + "\n" + "\n" + pathAndQuery;
const sign = hmacSha256(secret, message).toUpperCase();
return { sign, t };
}
// --- Get Access Token ---
async function getAccessToken() {
const fullUrl = 'https://openapi.tuyaeu.com/v1.0/token?grant_type=1';
const { sign, t } = generateSignature({
clientId,
secret: accessSecret,
method: 'GET',
fullUrl
});
const headers = {
'client_id': clientId,
'sign': sign,
'sign_method': 'HMAC-SHA256',
't': t
};
const response = await fetch(fullUrl, { method: 'GET', headers });
const data = await response.json();
console.log('Access Token Response:', data);
if (!data.success) {
throw new Error('Failed to get access token: ' + JSON.stringify(data));
}
return data.result.access_token;
}
// --- Get Device Status ---
async function getDeviceStatus() {
const accessToken = await getAccessToken();
const fullUrl = `https://openapi.tuyaeu.com/v1.0/devices/${deviceId}/status`;
const { sign, t } = generateSignature({
clientId,
accessToken,
secret: accessSecret,
method: 'GET',
fullUrl
});
const headers = {
"sign_method": "HMAC-SHA256",
"client_id": clientId,
"access_token": accessToken,
"t": t,
"sign": sign,
"Content-Type": "application/json"
};
const response = await fetch(fullUrl, {
method: 'GET',
headers
});
const result = await response.json();
console.log('Device Status Result:', result);
if (!result.success) {
throw new Error('Failed to get device status: ' + JSON.stringify(result));
}
// Find the "switch" status
const switchStatus = result.result.find(item => item.code === 'switch');
if (switchStatus) {
console.log('Jacuzzi Heater is', switchStatus.value ? 'ON' : 'OFF');
return switchStatus.value; // true or false
} else {
console.error('Switch status not found');
return false;
}
}
// Main runner
return (async () => {
try {
return await getDeviceStatus(); // <== PURE true or false
} catch (error) {
return false; // Failsafe: if error, assume false
}
})();
This wil get the status of the Device. I also made a script to Turn ON and turn OFF the device.
I only use the Homey Script to start a advanced flow, i dont know if its possible to create a virtual device to turn it on and off?
**Extra information
I used the IoT on the Tuya platform to retrieve the possible commands, for instance in the Smart Life App i can set Mode, Temperature but not with the Cloud Platform. The API only lets me Turn the device ON/OFF. So to make sure what the command is, check the IoT platform of Tuya.
If anyone is interested let me know.