And could you make me prtscn of settings one of the row ? I mean:

And could you make me prtscn of settings one of the row ? I mean:

Uff I really donยดt know what is wrong.
Just for sure into Then what exactly do you insert ? Because it looks like you choose 3 values. But I am able to choose only one + Value.
Is it correct ? Do you use the same cards?
Virtual devices / Device / Cards
Thank you very much for your help!
Ok I have it! ![]()
Whole issue was about value - the best way is tested into the flow.
You have to add value through this icon ![]()
Hello!
I was inspired by everything that has been done here, and I created my own improved version of the script.
It dynamically retrieves all sensors (thus also compatible with CGS1) and also gathers some information about the device, such as the MAC address or the report interval setting:
/**
* Configuration: App Key & App Secret
* Used for API authentication.
* Get credentials: https://developer.qingping.co/personal/permissionApply
*/
const APP_KEY = 'XXXXXXXXXXX';
const APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const CREDENTIALS = Buffer.from(`${APP_KEY}:${APP_SECRET}`).toString('base64');
/**
* Perform an authenticated HTTP request.
* Uses Basic Auth (for token) or Bearer Token (for API calls).
*/
async function fetchWithAuth(url, method = 'GET', token = '', body = null) {
const headers = token
? { 'Authorization': `Bearer ${token}` }
: { 'Authorization': 'Basic ' + CREDENTIALS };
const options = { method, headers, ...(body ? { body } : {}) };
const response = await fetch(url, options);
if (!response.ok) throw new Error(`Error: ${response.status} - ${response.statusText}`);
return response.json();
}
/**
* Retrieve sensor data and store it as Homey tags.
*/
async function main() {
try {
console.log("๐ Starting data retrieval...");
// Step 1: Get Access Token
console.log("๐ Requesting access token...");
const params = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'device_full_access'
});
const authResponse = await fetch("https://oauth.cleargrass.com/oauth2/token", {
method: 'POST',
headers: {
'Authorization': 'Basic ' + CREDENTIALS,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params,
});
if (!authResponse.ok) throw new Error(`Auth Error: ${authResponse.status} - ${authResponse.statusText}`);
const { access_token: accessToken } = await authResponse.json();
console.log("โ
Access Token received");
// Step 2: Fetch Sensor Data
console.log("๐ก Fetching device data...");
const timestamp = Date.now().toString();
const deviceResponse = await fetchWithAuth(`https://apis.cleargrass.com/v1/apis/devices?timestamp=${timestamp}`, 'GET', accessToken);
if (!deviceResponse.devices || deviceResponse.devices.length === 0) throw new Error("No device data available");
// Extract and log device info
const { info: deviceInfo, data: deviceData } = deviceResponse.devices[0];
console.log("๐ Device Info:", deviceInfo);
console.log("๐ Device Data:", deviceData);
console.log("๐ท๏ธ Storing informations...");
// Store device metadata as tags
await Promise.all([
tag("qingping_mac", deviceInfo.mac),
tag("qingping_report_interval", deviceInfo.setting?.report_interval ?? "N/A"),
tag("qingping_collect_interval", deviceInfo.setting?.collect_interval ?? "N/A")
]);
console.log(`๐น Stored: qingping_mac = ${deviceInfo.mac}`);
console.log(`๐น Stored: qingping_report_interval = ${deviceInfo.setting?.report_interval ?? "N/A"}`);
console.log(`๐น Stored: qingping_collect_interval = ${deviceInfo.setting?.collect_interval ?? "N/A"}`);
// Store sensor readings
await Promise.all(Object.entries(deviceData).map(async ([key, { value }]) => {
if (value !== undefined) {
const tagName = `qingping_${key}`;
console.log(`๐น Stored: ${tagName} = ${value}`);
return tag(tagName, value);
}
}));
// Store an error tag with "No error" if everything succeeds
await tag("qingping_error", "No error");
console.log("โ
All device data stored successfully!");
} catch (error) {
console.error("โ Error:", error.message);
await tag("qingping_error", error.message);
}
}
// Execute the script
await main();
return "Script completed";
Also, I was very frustrated to only get a report every 15 minutes from my sensor.
So, I dug into the API documentation, ran some tests, and with the help of ChatGPT, I finally found the solution.
Here is the code that will allow you to reduce the interval from 900 seconds to the minimum allowed of 60 seconds:
/**
* Configuration: App Key & App Secret
* Used for API authentication.
* Get credentials: https://developer.qingping.co/personal/permissionApply
*/
const APP_KEY = 'XXXXXXXXXXX';
const APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const CREDENTIALS = Buffer.from(`${APP_KEY}:${APP_SECRET}`).toString('base64');
/**
* Function to make authenticated HTTP requests.
* Handles both Basic Authentication (for token request) and Bearer Authentication (for API calls).
*/
async function fetchWithAuth(url, method = 'GET', token = '', body = null) {
const headers = token
? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } // Bearer token for API requests
: { 'Authorization': 'Basic ' + CREDENTIALS, 'Content-Type': 'application/x-www-form-urlencoded' }; // Basic Auth for token request
const options = {
method,
headers,
...(body ? { body: body instanceof URLSearchParams ? body : JSON.stringify(body) } : {}) // Ensure proper encoding
};
console.log("๐ก Sending Request:", url, options);
const response = await fetch(url, options);
const responseText = await response.text();
if (!response.ok) {
throw new Error(`Error: ${response.status} - ${responseText}`);
}
return responseText ? JSON.parse(responseText) : {}; // Handle empty responses
}
/**
* Function to update device settings (set report & collect intervals to minimum)
*/
async function updateDeviceSettings(accessToken, macAddress) {
try {
const timestamp = Date.now();
const requestBody = {
mac: [macAddress],
report_interval: 60, // Minimum interval (60 seconds)
collect_interval: 60, // Minimum interval (60 seconds)
timestamp: timestamp
};
console.log("โ๏ธ Updating Device Settings:", JSON.stringify(requestBody, null, 2));
const response = await fetchWithAuth("https://apis.cleargrass.com/v1/apis/devices/settings", 'PUT', accessToken, requestBody);
if (response && Object.keys(response).length > 0) {
console.log("โ
Device settings updated successfully:", response);
} else {
console.log("โ
Device settings updated successfully, but API returned an empty response.");
}
} catch (error) {
console.error("โ Error updating device settings:", error.message);
}
}
/**
* Main function to fetch and display device information before and after settings update.
*/
async function main() {
try {
// 1๏ธโฃ Retrieve Access Token
const params = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'device_full_access'
});
const authResponse = await fetch("https://oauth.cleargrass.com/oauth2/token", {
method: 'POST',
headers: {
'Authorization': 'Basic ' + CREDENTIALS,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params,
});
if (!authResponse.ok) throw new Error(`Auth Error: ${authResponse.status} - ${authResponse.statusText}`);
const { access_token: accessToken } = await authResponse.json();
// 2๏ธโฃ Retrieve Device Information (Before Update)
const timestamp = Date.now().toString();
const deviceResponseBefore = await fetchWithAuth(`https://apis.cleargrass.com/v1/apis/devices?timestamp=${timestamp}`, 'GET', accessToken);
if (!deviceResponseBefore.devices || deviceResponseBefore.devices.length === 0) throw new Error("No device data available");
// Extract and display device information before update
const deviceInfo = deviceResponseBefore.devices[0].info;
console.log("๐ก Device Info (Before Update):", deviceInfo);
// 3๏ธโฃ Update Device Settings (Set report & collect intervals to minimum)
await updateDeviceSettings(accessToken, deviceInfo.mac);
// 4๏ธโฃ Retrieve Device Information (After Update)
const deviceResponseAfter = await fetchWithAuth(`https://apis.cleargrass.com/v1/apis/devices?timestamp=${timestamp}`, 'GET', accessToken);
if (!deviceResponseAfter.devices || deviceResponseAfter.devices.length === 0) throw new Error("No device data available");
// Extract and display device information after update
console.log("๐ก Device Info (After Update):", deviceResponseAfter.devices[0].info);
console.log("โ
Device settings update process completed!");
} catch (error) {
console.error("โ Error:", error.message);
}
}
// Execute the script
await main();
return "Script completed";
Here is my Device Capabilities TEF file (donโt forget to โSave asโ the custom icons):
[tef:AVD:"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":/tef]
Here is my flow:
Once everything is set up, here is the result (the MAC address has been blurred):
Hello,
Here is a V2 of my scripts.
This time, they dynamically retrieve all connected devices and process them one by one, using the MAC address as a unique identifier.
Here is the device update script:
/**
* Configuration: App Key & App Secret
* Required for API authentication.
* Credentials can be obtained from: https://developer.qingping.co/personal/permissionApply
*/
const APP_KEY = 'XXXXXXXXXXX';
const APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const CREDENTIALS = Buffer.from(`${APP_KEY}:${APP_SECRET}`).toString('base64');
/**
* Performs an authenticated HTTP request.
* Uses Basic Authentication for token requests and Bearer Authentication for API calls.
*/
async function fetchWithAuth(url, method = 'GET', token = '', body = null) {
const headers = token
? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
: { 'Authorization': 'Basic ' + CREDENTIALS, 'Content-Type': 'application/x-www-form-urlencoded' };
const options = { method, headers, ...(body ? { body: JSON.stringify(body) } : {}) };
const response = await fetch(url, options);
if (!response.ok) throw new Error(`Error: ${response.status} - ${response.statusText}`);
return response.json();
}
/**
* Retrieves sensor data and stores it as Homey tags.
*/
async function main() {
try {
console.log("๐ Starting data retrieval...");
console.log("๐ Requesting access token...");
// Access token retrieval
const params = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'device_full_access'
});
const authResponse = await fetch("https://oauth.cleargrass.com/oauth2/token", {
method: 'POST',
headers: {
'Authorization': 'Basic ' + CREDENTIALS,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params,
});
if (!authResponse.ok) throw new Error(`Auth Error: ${authResponse.status} - ${authResponse.statusText}`);
const { access_token: accessToken } = await authResponse.json();
console.log("โ
Access Token received");
console.log("\n๐ก Retrieving devices...");
const timestamp = Date.now().toString();
const deviceResponse = await fetchWithAuth(`https://apis.cleargrass.com/v1/apis/devices?timestamp=${timestamp}`, 'GET', accessToken);
if (!deviceResponse.devices || deviceResponse.devices.length === 0) throw new Error("No device data available");
console.log(`๐ก Found ${deviceResponse.devices.length} devices`);
// Iterates through each device and creates Homey tags
for (const device of deviceResponse.devices) {
const { info: deviceInfo, data: deviceData } = device;
const mac = deviceInfo.mac.replace(/:/g, "").toLowerCase(); // Normalizes the MAC address
console.log(`\n๐ Processing device: ${deviceInfo.name} (MAC: ${deviceInfo.mac})`);
// Stores device metadata as tags with the "qp_" prefix
await Promise.all([
tag(`qp_${mac}_mac`, deviceInfo.mac),
tag(`qp_${mac}_report_interval`, deviceInfo.setting?.report_interval ?? "N/A"),
tag(`qp_${mac}_collect_interval`, deviceInfo.setting?.collect_interval ?? "N/A")
]);
console.log(`๐ท๏ธ Stored metadata for ${deviceInfo.name}`);
console.log(`๐น Stored: qp_${mac}_mac = ${deviceInfo.mac}`);
console.log(`๐น Stored: qp_${mac}_report_interval = ${deviceInfo.setting?.report_interval ?? "N/A"}`);
console.log(`๐น Stored: qp_${mac}_collect_interval = ${deviceInfo.setting?.collect_interval ?? "N/A"}`);
// Stores sensor readings
await Promise.all(Object.entries(deviceData).map(async ([key, { value }]) => {
if (value !== undefined) {
const tagName = `qp_${mac}_${key}`;
console.log(`๐น Stored: ${tagName} = ${value}`);
return tag(tagName, value);
}
}));
}
// Creates a global tag to track error status
await tag("qp_error", "No error");
console.log("โ
All device data stored successfully!");
} catch (error) {
console.error("โ Error:", error.message);
await tag("qp_error", error.message);
}
}
// Executes the script
await main();
return "Script completed";
The output is as follows (always with the MAC address blurred):
Here is my script for modifying the report/collect interval:
/**
* Configuration: App Key & App Secret
* Required for API authentication.
* Credentials can be obtained from: https://developer.qingping.co/personal/permissionApply
*/
const APP_KEY = 'XXXXXXXXXXX';
const APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const CREDENTIALS = Buffer.from(`${APP_KEY}:${APP_SECRET}`).toString('base64');
/**
* Device settings configuration.
* These values define the report and collection intervals in seconds.
*/
const REPORT_INTERVAL = 60; // Interval for reporting sensor data
const COLLECT_INTERVAL = 60; // Interval for collecting sensor data
/**
* Performs an authenticated HTTP request.
* Uses Basic Authentication for token requests and Bearer Authentication for API calls.
*/
async function fetchWithAuth(url, method = 'GET', token = '', body = null) {
const headers = token
? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
: { 'Authorization': 'Basic ' + CREDENTIALS, 'Content-Type': 'application/x-www-form-urlencoded' };
const options = {
method,
headers,
...(body ? { body: JSON.stringify(body) } : {})
};
const response = await fetch(url, options);
const responseText = await response.text();
if (!response.ok) {
throw new Error(`Error: ${response.status} - ${responseText}`);
}
return responseText ? JSON.parse(responseText) : {};
}
/**
* Updates the settings of multiple devices.
* Applies the configured report and collect intervals to all specified devices.
*/
async function updateDeviceSettings(accessToken, macAddresses) {
try {
if (macAddresses.length === 0) {
console.log("โ ๏ธ No devices found to update.");
return;
}
const timestamp = Date.now();
const requestBody = {
mac: macAddresses,
report_interval: REPORT_INTERVAL,
collect_interval: COLLECT_INTERVAL,
timestamp: timestamp
};
console.log("\nโ๏ธ Updating device settings...");
const response = await fetchWithAuth("https://apis.cleargrass.com/v1/apis/devices/settings", 'PUT', accessToken, requestBody);
// ๐ The API returns an empty response upon success.
console.log("โ
Device settings update request sent.\n");
} catch (error) {
console.error("โ Error updating device settings:", error.message);
}
}
/**
* Main function to retrieve device data and update settings.
*/
async function main() {
try {
console.log("๐ Starting settings update...");
console.log("๐ Requesting access token...");
// Retrieve access token
const params = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'device_full_access'
});
const authResponse = await fetch("https://oauth.cleargrass.com/oauth2/token", {
method: 'POST',
headers: {
'Authorization': 'Basic ' + CREDENTIALS,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params,
});
if (!authResponse.ok) throw new Error(`Auth Error: ${authResponse.status} - ${authResponse.statusText}`);
const { access_token: accessToken } = await authResponse.json();
console.log("โ
Access Token received");
console.log("\n๐ก Retrieving devices...");
const timestamp = Date.now().toString();
const deviceResponse = await fetchWithAuth(`https://apis.cleargrass.com/v1/apis/devices?timestamp=${timestamp}`, 'GET', accessToken);
if (!deviceResponse.devices || deviceResponse.devices.length === 0) throw new Error("No devices found");
console.log(`๐ก Found ${deviceResponse.devices.length} devices`);
const macAddresses = deviceResponse.devices.map(device => device.info.mac);
console.log("\n๐ Current settings before update:");
deviceResponse.devices.forEach(device => {
console.log(` - ${device.info.name} (MAC: ${device.info.mac}) โ Report: ${device.info.setting?.report_interval ?? "N/A"}s, Collect: ${device.info.setting?.collect_interval ?? "N/A"}s`);
});
// Apply new settings to all devices
await updateDeviceSettings(accessToken, macAddresses);
// Retrieve devices again to confirm the update
console.log("\n๐ก Verifying updated settings...");
const updatedDeviceResponse = await fetchWithAuth(`https://apis.cleargrass.com/v1/apis/devices?timestamp=${timestamp}`, 'GET', accessToken);
console.log("\n๐ Updated settings:");
updatedDeviceResponse.devices.forEach(device => {
console.log(` - ${device.info.name} (MAC: ${device.info.mac}) โ Report: ${device.info.setting?.report_interval ?? "N/A"}s, Collect: ${device.info.setting?.collect_interval ?? "N/A"}s`);
});
console.log("\nโ
Device settings update completed.\n");
} catch (error) {
console.error("\nโ Error:", error.message);
}
}
// Execute the script
await main();
return "Script completed";
You just need to modify the time variables as desired. Here is the output:
Additionally, here is a script that displays all the parameters of each device, which can be very useful in certain cases:
/**
* Configuration: App Key & App Secret
* Required for API authentication.
* Credentials can be obtained from: https://developer.qingping.co/personal/permissionApply
*/
const APP_KEY = 'XXXXXXXXXXX';
const APP_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const CREDENTIALS = Buffer.from(`${APP_KEY}:${APP_SECRET}`).toString('base64');
/**
* Performs an authenticated HTTP request.
* Uses Basic Authentication for token requests and Bearer Authentication for API calls.
*/
async function fetchWithAuth(url, method = 'GET', token = '', body = null) {
const headers = token
? { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }
: { 'Authorization': 'Basic ' + CREDENTIALS, 'Content-Type': 'application/x-www-form-urlencoded' };
const options = {
method,
headers,
...(body ? { body: JSON.stringify(body) } : {})
};
const response = await fetch(url, options);
const responseText = await response.text();
if (!response.ok) {
throw new Error(`Error: ${response.status} - ${responseText}`);
}
return responseText ? JSON.parse(responseText) : {};
}
/**
* Retrieves sensor data and displays raw objects without parsing.
*/
async function main() {
try {
console.log("๐ Starting data retrieval...");
console.log("๐ Requesting access token...");
// Access token retrieval
const params = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'device_full_access'
});
const authResponse = await fetch("https://oauth.cleargrass.com/oauth2/token", {
method: 'POST',
headers: {
'Authorization': 'Basic ' + CREDENTIALS,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params,
});
if (!authResponse.ok) throw new Error(`Auth Error: ${authResponse.status} - ${authResponse.statusText}`);
const { access_token: accessToken } = await authResponse.json();
console.log("โ
Access Token received");
console.log("\n๐ก Retrieving devices...");
const timestamp = Date.now().toString();
const deviceResponse = await fetchWithAuth(`https://apis.cleargrass.com/v1/apis/devices?timestamp=${timestamp}`, 'GET', accessToken);
if (!deviceResponse.devices || deviceResponse.devices.length === 0) throw new Error("No devices found");
console.log(`๐ก Found ${deviceResponse.devices.length} devices`);
// Iterates through each device and displays raw JSON objects
deviceResponse.devices.forEach((device, index) => {
console.log(`\n๐ Device ${index + 1}:`);
// Displays raw `Device Info`
console.log("๐ก Device Info:", device.info);
// Displays raw `Sensor Data`
console.log("๐ Sensor Data:", device.data);
});
console.log("โ
All device data displayed successfully!");
} catch (error) {
console.error("โ Error:", error.message);
}
}
// Executes the script
await main();
return "Script completed";
And the output:
Everything is working perfectly on my end ![]()
I created an icon for the Qingping Air monitor 2 in three different versions for the virtual device.
I also share:
You just have to convert them on this site:
With these parameters:

Iโm using the script with virtual device for some weeks. Works fine, but I wanted to use a local solution which can scale up with multiple devices easily. So I tried to use MQTT. Maybe this is interesting for you too.
Here are the steps, I did to enable MQTT for Qingping AirMonitor 2:
Hereโs my flow, which replaces the old one:
The Qingping device sends multiple messages with different types. The type=12 marks messages with realtime data. Works fine for me.