Temp Stick API Documentation
https://tempstickapi.com/api/v1
Getting Started
The Temp Stick API is intended for developers who are comfortable making HTTP requests, working with JSON responses, and handling common API client behavior such as headers, authentication, compression, and form data.
Base URL#
https://tempstickapi.com/api/v1
Authentication#
Every request must include your account API key in the X-API-Key HTTP header.
X-API-Key: YOUR_API_KEY
You can find your API key in your Temp Stick account:
1. Log in to your Temp Stick account.
2. Go to the Account Page.
3. Select the Developers tab.
4. Click Show Key to reveal your API key.
Required and Recommended Headers#
All API clients should identify themselves clearly on every request. Do not send bare minimum requests that only include the API key.
At minimum, include these headers with every request:
X-API-Key: YOUR_API_KEY
Accept: application/json
User-Agent: YourAppName/1.0
X-Client-Name: YourAppName
X-Client-Version: 1.0
Recommended header details:
| Header | Required | Description |
|---|---|---|
X-API-Key |
Yes | Your Temp Stick account API key. |
Accept |
Recommended | Use application/json. |
User-Agent |
Recommended | A descriptive application name and version. Avoid generic defaults such as curl, python-requests, or unnamed HTTP clients in production. |
X-Client-Name |
Recommended | The name of your application, service, integration, or company. |
X-Client-Version |
Recommended | The version of your application or integration. |
Content-Type |
Required for POST | Use multipart/form-data for POST requests that update settings. |
Accept-Encoding |
Optional | Your HTTP client may include this automatically. The API may return gzip-compressed data. |
Example header set#
headers = {
"X-API-Key": "YOUR_API_KEY",
"Accept": "application/json",
"User-Agent": "MyTempStickApp/1.0",
"X-Client-Name": "MyTempStickApp",
"X-Client-Version": "1.0",
}
Avoid requests that only send the API key and no client identity information:
headers = {
"X-API-Key": "YOUR_API_KEY",
"Accept": "application/json",
}
Compression#
API responses may be compressed with gzip and returned as JSON.
Most modern HTTP clients automatically decompress gzip responses when configured correctly. Make sure your application supports compressed responses before parsing the JSON.
Temperature Units#
All temperature readings and temperature settings are returned in Celsius.
To convert Celsius to Fahrenheit:
(CELSIUS * 1.8) + 32
PHP conversion helper#
function celsius_to_fahrenheit($celsius, $round = 1) {
return round(($celsius * 1.8) + 32, $round);
}
Dates and Times#
All timestamps returned by the API use the UTC time zone.
UTC timestamps may be marked with Z or with the offset +00:00.
Examples:
2026-04-27T15:30:00Z
2026-04-27T15:30:00+00:00
Convert timestamps to the user's local time zone in your application when displaying dates and times.
GET Requests#
Use GET requests to retrieve data from the API.
GET requests should:
- Include all recommended identifying headers.
- Not include a request body.
- Use
Accept: application/json. - Use a descriptive
User-Agent.
PHP GET example#
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://tempstickapi.com/api/v1/sensors/all',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '', // Automatically decode gzip responses.
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Accept: application/json',
'User-Agent: MyTempStickApp/1.0',
'X-Client-Name: MyTempStickApp',
'X-Client-Version: 1.0',
],
]);
$response = curl_exec($curl);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException('Temp Stick API request failed: ' . $error);
}
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($http_code < 200 || $http_code >= 300) {
throw new RuntimeException('Temp Stick API returned HTTP ' . $http_code . ': ' . $response);
}
echo $response;
?>
JavaScript GET example#
const headers = new Headers();
headers.append("X-API-Key", "YOUR_API_KEY");
headers.append("Accept", "application/json");
headers.append("User-Agent", "MyTempStickApp/1.0");
headers.append("X-Client-Name", "MyTempStickApp");
headers.append("X-Client-Version", "1.0");
const requestOptions = {
method: "GET",
headers: headers,
redirect: "follow"
};
fetch("https://tempstickapi.com/api/v1/sensors/all", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error(`Temp Stick API returned HTTP ${response.status}`);
}
return response.json();
})
.then(result => console.log(result))
.catch(error => console.error("Temp Stick API request failed:", error));
> Note: Browser-based JavaScript may restrict setting certain headers, including User-Agent. For production integrations, server-side requests are recommended when you need full control over request headers.
cURL GET example#
curl --location --request GET 'https://tempstickapi.com/api/v1/sensors/all' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'Accept: application/json' \
--header 'User-Agent: MyTempStickApp/1.0' \
--header 'X-Client-Name: MyTempStickApp' \
--header 'X-Client-Version: 1.0'
Python GET example#
import requests
url = "https://tempstickapi.com/api/v1/sensors/all"
headers = {
"X-API-Key": "YOUR_API_KEY",
"Accept": "application/json",
"User-Agent": "MyTempStickApp/1.0",
"X-Client-Name": "MyTempStickApp",
"X-Client-Version": "1.0",
}
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
data = response.json()
print(data)
POST Requests#
Use POST requests when updating sensor settings.
POST requests should:
- Include all recommended identifying headers.
- Send data as
multipart/form-data. - Use field names exactly as documented for the endpoint.
- Send only supported fields for the endpoint you are calling.
When using libraries such as PHP cURL, Python requests, or JavaScript FormData, the client usually creates the correct multipart/form-data boundary automatically. Do not manually set the boundary unless your HTTP client requires it.
PHP POST example#
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => [
'sensor_name' => 'Test',
'send_interval' => '3600',
'alert_interval' => '1800',
'use_alert_interval' => '0',
'alert_temp_below' => '',
'alert_temp_above' => '',
'alert_humidity_below' => '',
'alert_humidity_above' => '',
'connection_sensitivity' => '3',
'use_offset' => '0',
'temp_offset' => '0',
'humidity_offset' => '0',
],
CURLOPT_HTTPHEADER => [
'X-API-Key: YOUR_API_KEY',
'Accept: application/json',
'User-Agent: MyTempStickApp/1.0',
'X-Client-Name: MyTempStickApp',
'X-Client-Version: 1.0',
],
]);
$response = curl_exec($curl);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException('Temp Stick API request failed: ' . $error);
}
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($http_code < 200 || $http_code >= 300) {
throw new RuntimeException('Temp Stick API returned HTTP ' . $http_code . ': ' . $response);
}
echo $response;
?>
JavaScript POST example#
const formData = new FormData();
formData.append("sensor_name", "Test");
formData.append("send_interval", "3600");
formData.append("alert_interval", "1800");
formData.append("use_alert_interval", "0");
formData.append("alert_temp_below", "");
formData.append("alert_temp_above", "");
formData.append("alert_humidity_below", "");
formData.append("alert_humidity_above", "");
formData.append("connection_sensitivity", "3");
formData.append("use_offset", "0");
formData.append("temp_offset", "0");
formData.append("humidity_offset", "0");
const headers = new Headers();
headers.append("X-API-Key", "YOUR_API_KEY");
headers.append("Accept", "application/json");
headers.append("User-Agent", "MyTempStickApp/1.0");
headers.append("X-Client-Name", "MyTempStickApp");
headers.append("X-Client-Version", "1.0");
const requestOptions = {
method: "POST",
headers: headers,
body: formData,
redirect: "follow"
};
fetch("https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID", requestOptions)
.then(response => {
if (!response.ok) {
throw new Error(`Temp Stick API returned HTTP ${response.status}`);
}
return response.json();
})
.then(result => console.log(result))
.catch(error => console.error("Temp Stick API request failed:", error));
> Note: When using FormData, do not manually set the Content-Type header in browser JavaScript. The browser will set multipart/form-data with the correct boundary.
cURL POST example#
curl --location --request POST 'https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID' \
--header 'X-API-Key: YOUR_API_KEY' \
--header 'Accept: application/json' \
--header 'User-Agent: MyTempStickApp/1.0' \
--header 'X-Client-Name: MyTempStickApp' \
--header 'X-Client-Version: 1.0' \
--form 'sensor_name=Test' \
--form 'send_interval=3600' \
--form 'alert_interval=1800' \
--form 'use_alert_interval=0' \
--form 'alert_temp_below=' \
--form 'alert_temp_above=' \
--form 'alert_humidity_below=' \
--form 'alert_humidity_above=' \
--form 'connection_sensitivity=3' \
--form 'use_offset=0' \
--form 'temp_offset=0' \
--form 'humidity_offset=0'
Python POST example#
import requests
url = "https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID"
headers = {
"X-API-Key": "YOUR_API_KEY",
"Accept": "application/json",
"User-Agent": "MyTempStickApp/1.0",
"X-Client-Name": "MyTempStickApp",
"X-Client-Version": "1.0",
}
data = {
"sensor_name": "Test",
"send_interval": "3600",
"alert_interval": "1800",
"use_alert_interval": "0",
"alert_temp_below": "",
"alert_temp_above": "",
"alert_humidity_below": "",
"alert_humidity_above": "",
"connection_sensitivity": "3",
"use_offset": "0",
"temp_offset": "0",
"humidity_offset": "0",
}
response = requests.post(url, headers=headers, files={}, data=data, timeout=30)
response.raise_for_status()
result = response.json()
print(result)
Common Issues#
406 Not Acceptable#
A 406 Not Acceptable response usually means the request format or headers were rejected before the API request could be processed.
Common causes include:
- Using a generic or default
User-Agent. - Sending a GET request with a request body.
- Sending malformed headers.
- Including blocked or suspicious keywords in headers.
- Using a client that does not identify the application clearly.
Recommended fixes:
- Use a descriptive
User-Agent, such asMyTempStickApp/1.0. - Include
X-Client-NameandX-Client-Version. - Do not send a body with GET requests.
- Remove unnecessary headers.
- Test the same request in Postman or another API client where headers are easy to inspect.
429 Too Many Requests#
A 429 Too Many Requests response means the client is sending too many requests or the request pattern looks automated without enough client identity.
Recommended fixes:
- Include all recommended identifying headers on every request.
- Use a descriptive and stable
User-Agent. - Include
X-Client-NameandX-Client-Version. - Avoid retry loops without delays.
- Add exponential backoff when retrying failed requests.
- Cache data in your own application when appropriate.
- Avoid polling endpoints more often than needed.
Recommended retry pattern:
First retry: wait 2 seconds
Second retry: wait 5 seconds
Third retry: wait 15 seconds
Additional retries: wait longer, or stop and alert the user
Testing with Postman#
Postman is recommended when testing or troubleshooting the API because it makes request headers, parameters, and responses easy to inspect.
When creating a request in Postman:
1. Set the method to GET or POST.
2. Enter the full API endpoint URL.
3. Add the recommended headers:
X-API-KeyAcceptUser-AgentX-Client-NameX-Client-Version
4. For POST requests, use form-data in the Body tab.
5. Send the request and inspect the response body and status code.
Request Checklist#
Before sending a request to the Temp Stick API, confirm that:
- The request includes
X-API-Key. - The request includes a descriptive
User-Agent. - The request includes
X-Client-Name. - The request includes
X-Client-Version. - GET requests do not include a request body.
- POST requests use
multipart/form-data. - Your client can handle gzip-compressed responses.
- Your application treats API timestamps as UTC.
- Your application treats API temperatures as Celsius.
Endpoints
12 endpoints documented from api\v1.
Get Alert
Get settings for a single alert
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
alert_id |
Number | Required | The ID of your alert |
Success Examples
Success Response
{
"type": "success",
"message": "get alert",
"data": {
"id": "alert_id",
"user_id": "user_id",
"alert_name": "Connection",
"reading_type": "connection",
"reading_condition": "lost",
"reading_value": "0.00",
"alert_message": "[sensor_name]: connection [trigger_value]",
"always_active": "1",
"sensors_global": "1",
"enabled": "1",
"push_notifications": "1",
"email_text_notifications": "0",
"connection_sensitivity": "0",
"sensors": [
"sensor_id"
],
"contacts": {
"contact_id": {
"id": "contact_id",
"send_email": 1,
"send_text": 1
}
},
"active_when": {
"0": {
"alert_day": "0",
"alert_time_start": 12,
"alert_time_end": 24
},
"2": {
"alert_day": "2",
"alert_time_start": 1,
"alert_time_end": 13
}
}
}
}
Get Alerts
Get all your alerts
Success Examples
Success Response
{
"type": "success",
"message": "get alerts",
"data": [
{
"id": "",
"user_id": "",
"alert_name": "Temperature",
"reading_type": "temperature",
"reading_condition": "above",
"reading_value": "7.20",
"alert_message": "[sensor_name]: [trigger_type] [trigger_condition] [trigger_value] ([sensor_value])",
"always_active": "1",
"sensors_global": "1",
"enabled": "1",
"active_when": [],
"contacts": [],
"sensors": [],
}
]
}
Get Sensor Notifications
Get the last seven days of alerts generated by the sensor
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
sensorId |
String | Required |
Success Examples
Success Response
{
"type": "success",
"message": "get sensor notifications",
"data": {
"items": [
{
"id": "",
"user_id": "",
"alert_id": "",
"event_type": "temperature",
"event_message": "above 45 F (71.8 F)",
"event_data": "{\"alert_condition\":\"above\",\"alert_value\":\"45 F\",\"reading_value\":\"71.8 F\"}",
"created": "2022-05-10 22:49:41Z"
}
],
"count": "350"
}
}
Get User Notifications
Get the last seven days of alerts generated by all sensors
Success Examples
Success Response
{
"type": "success",
"message": "get user notifications",
"data": {
"items": [
{
"id": "",
"user_id": "",
"sensor_id": "",
"alert_id": "",
"event_type": "temperature",
"event_message": "above 45 F (69.6 F)",
"event_data": "{\"alert_condition\":\"above\",\"alert_value\":\"45 F\",\"reading_value\":\"69.6 F\"}",
"created": "2022-05-11 17:37:21Z",
"sensor": {}
}
],
"count": "350"
}
}
Get Sensor
Get settings for a specific sensor, *note: readings are shown in Celsius*
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
sensor_id |
String | Required | The ID of the sensor you want to get readings from |
Success Examples
Success Response
{
"type": "success",
"message": "get sensor",
"data": {
"version": "",
"sensor_id": "",
"sensor_name": "",
"sensor_mac_addr": "",
"owner_id": "",
"type": "DHT",
"alert_interval": "1800",
"send_interval": "1800",
"last_temp": 21,
"last_humidity": 28.26,
"last_voltage": 3,
"battery_pct": 100,
"wifi_connect_time": 1,
"rssi": -37,
"last_checkin": "2022-05-12 19:09:41-00:00Z",
"next_checkin": "2022-05-12 19:39:41-00:00Z",
"ssid": "",
"offline": "0",
"alerts": [],
"use_sensor_settings": 0,
"temp_offset": "0",
"humidity_offset": "0",
"alert_temp_below": "",
"alert_temp_above": "",
"alert_humidity_below": "",
"alert_humidity_above": "",
"connection_sensitivity": "3",
"use_alert_interval": 0,
"use_offset": "0",
"last_messages": [
{
"temperature": 21,
"humidity": 28.26,
"voltage": "3.00",
"RSSI": "-37",
"time_to_connect": "1.0",
"sensor_time_utc": "2022-05-12 19:09:41"
}
]
}
}
Update Sensor Settings
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
sensor_id |
String | Required |
Success Examples
Success Example
{
"type": "success",
"message": "update sensor",
"data": {
"sensor_name": "sensor_name",
"send_interval": 1800,
"alert_interval": 1800,
"connection_sensitivity": "2",
"use_offset": "1",
"temp_offset": "1",
"humidity_offset": "0",
"use_sensor_settings": "0",
"use_alert_interval": 0,
"alert_temp_below": "",
"alert_temp_above": "",
"alert_humidity_below": "",
"alert_humidity_above": ""
}
}
Get Readings
Get the readings for a sensor given a specified period, *note: readings are shown in Celsius*
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
sensor_id |
String | Required | The ID of the sensor you want to get readings from |
Success Examples
Success Response
{
"type": "success",
"message": "get messages",
"data": {
"start": "2022-05-11 04:00:00Z",
"end": "2022-05-12 03:59:59Z",
"readings": [
{
"sensor_time": "2022-05-11 22:26:39Z",
"temperature": 22.06 (Celsius),
"humidity": 27.45,
"offline": 0
}
]
}
}
Get Sensors
Get all the sensors assigned to the account, *note: readings are shown in Celsius*
Success Examples
Success Response
{
"type": "success",
"message": "get sensors",
"data": {
"groups": [],
"items": [
{
"version": "",
"sensor_id": "",
"sensor_name": "",
"sensor_mac_addr": "",
"owner_id": "",
"type": "",
"alert_interval": "",
"send_interval": "",
"last_temp": 25.4 (Celsius),
"last_humidity": 21.77,
"last_voltage": "3.48",
"battery_pct": 100,
"wifi_connect_time": "1",
"rssi": "-57",
"last_checkin": "2021-10-25 20:04:25",
"next_checkin": "2021-10-25 21:04:25",
"ssid": "",
"offline": "1",
"group": 0,
"connection_sensitivity": "3",
"temp_offset": 0,
"humidity_offset": 0
}
]
}
}
Get Current User
Get your user information
Success Examples
Success Response
{
"type": "success",
"message": "Get user",
"data": {
"id": "",
"email": "",
"first_name": "",
"last_name": "",
"phone": "",
"contact_email": "",
"address_1": "",
"address_2": "",
"city": "",
"state": "",
"zip": "",
"temp_pref": "F",
"timezone": "America/New_York",
"use_sensor_groups": "0",
"chart_fill": "1",
"send_reports": "1",
"daily_reports": "1",
"weekly_reports": "1",
"monthly_reports": "1",
"reports_specific_sensors": "1",
"level": 10,
"weekly_report_day": "1",
"sensor_count": 1
}
}
Get Timezones
Get current list of supported timezones
Success Examples
Success Response
{
"type": "success",
"message": "get timezones",
"data": {
"America/New_York": "US Eastern",
"America/Chicago": "US Central",
"America/Denver": "US Mountain West",
"America/Phoenix": "US Mountain (No DST)",
"America/Los_Angeles": "US Pacific",
"America/Anchorage": "US Alaska",
"Pacific/Honolulu": "US Hawaii",
"America/Glace_Bay": "(GMT-04:00) Atlantic Time (Canada)",
"America/Argentina/Buenos_Aires": "(GMT-03:00) Buenos Aires",
"America/Noronha": "(GMT-02:00) Mid-Atlantic",
"Atlantic/Azores": "(GMT-01:00) Azores",
"Europe/London": "(GMT) London",
"Europe/Berlin": "(GMT+01:00) Europe (CET)",
"Africa/Algiers": "(GMT+01:00) West Africa",
"Africa/Cairo": "(GMT+02:00) Central Africa",
"Africa/Addis_Ababa": "(GMT+03:00) East Africa",
"Europe/Moscow": "(GMT+03:00) Moscow",
"Asia/Dubai": "(GMT+04:00) Abu Dhabi",
"Asia/Karachi": "(GMT+05:00) Pakistan",
"Asia/Calcutta": "(GMT+05:30) Calcutta",
"Asia/Dhaka": "(GMT+06:00) Dhaka",
"Asia/Bangkok": "(GMT+07:00) Bangkok",
"Asia/Shanghai": "(GMT+08:00) Asia",
"Asia/Tokyo": "(GMT+09:00) Tokyo",
"Australia/Brisbane": "(GMT+10:00) Brisbane",
"Asia/Magadan": "(GMT+11:00) Magadan",
"Pacific/Auckland": "(GMT+12:00) Auckland",
"Pacific/Tongatapu": "(GMT+13:00) Nuku alofa",
"Pacific/Kiritimati": "(GMT+14:00) Kiritimati"
}
}
Update User Display Preferences
Change display preferences including time zone and temperature format
Success Examples
Success Response
{
"type": "success",
"message": "updated display preferences",
"data": {
"timezone": "America/New_York",
"temp_pref": "F",
"chart_fill": "1"
}
}
Get Email Reports
Get email report settings and any downloadable reports
Success Examples
Success Response
{
"type": "success",
"message": "get email reports",
"data": {
"contacts": [],
"sensors": [],
"reports": {
"daily": [
{
"report_id": "",
"type": "daily",
"file": "daily-2022-05-12.zip",
"date_start": "2022-05-12 04:00:00",
"date_end": "2022-05-13 03:59:59",
"download": "URL_TO_DOWNLOAD"
}
],
"weekly": [],
"monthly": []
}
}
}
Examples
Examples and Useful Code#
Battery Percentage Calculation#
At times, you may want to calculate the battery percentage of your sensor from the voltage sent by the sensor. This is
most accurate with alkaline batteries which have a steady voltage decline while lithium / rechargeable batteries tend to
hold voltage until failure.
const getBatteryPercentage = voltage => {
// typical voltage range of sensors is 2.3v to 3.0v
// (0% to 100%)
const baseline = 2.30; // lowest allowed voltage
const remaining_voltage = voltage - baseline;
let voltagePct = remaining_voltage / 0.007;
if(voltagePct > 100){ voltagePct = 100; }
if(voltagePct < 0){ voltagePct = 0; }
return Math.round(voltagePct);
}
Saving Readings and Alerts to Google Sheets#
This NodeJS script uses the Temp Stick API to fetch data from your sensors and update the readings while observing each
sensor's next check-in timestamp. Readings will be saved to the "Readings" tab of your sheet.
If any alerts are triggered, they will be recorded in the "Alerts" tab of your Google spreadsheet.
Setup#
- Create a Service Account JSON key to access Google Sheets
- Create a new Google Sheet (get the ID from the URL)
- Create the tabs "Readings" and "Alerts"
- "Readings" should have the following columns: "Sensor", "Temp C", "Temp F", "Humidity", "RSSI", "Time to Connect", "Battery", "Offline", "Timestamp"
- "Alerts" should have the following columns: "Sensor", "Alert", "Timestamp"
To set up a service account for Google Services, go to this
guide for instructions
on obtaining a Service Key.
Keep in mind:
- Your Google keyfile JSON is sensitive information, similar to a password to your Google account. Do not expose it publicly.
- Similarly, your Temp Stick API key should be treated as confidential. It provides access to your Temp Stick account, hence don't include it directly in your code, especially if you're planning to share your code or upload it on platforms like Github. Always ensure your keys are securely stored and accessed.
Note
This is a basic example of integrating Google Sheets with Temp Stick. For a more robust application, you might want
to consider saving your readings and alerts to a dedicated database (such as SQL or MongoDB). Using a database could
provide more flexibility and efficiency than a spreadsheet for data management and retrieval.
This will not run in a web browser and requires NodeJS to use.
NodeJS Javascript
// Import necessary libraries
const {google} = require('googleapis'); // Google's Node.js client library
const axios = require('axios'); // HTTP client
const fs = require('fs'); // Node.js File System module
// Define the path for a JSON file where the application will store data.
// The application will remember the last 1000 readings per sensor.
const dataFile = __dirname + '/data.json';
// Provide the API key for the Temp Stick service. This is reading a txt file called "temp-stick-api-key" (no extension)
const TEMP_STICK_API_KEY = fs.readFileSync(__dirname + "/temp-stick-api-key");
// Configure the Google JWT (JSON Web Token) authorization object
const auth = new google.auth.JWT({
// Point to your Google service account file
keyFile: __dirname + '/config/temp-stick-datalogging.json',
// Specify the scope that the JWT is allowed to access.
// In this case, we're giving it access to Google Sheets.
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});
// Initialize the Google Sheets API client
const sheets = google.sheets({version: 'v4', auth});
// Define the ID of the Google Sheets spreadsheet to be accessed. Replace '' with your actual spreadsheet id.
const spreadsheetId = '';
// Create a variable to store historical data.
let dataHistory = null;
// Check if the file specified by dataFile already exists
if(fs.existsSync(dataFile)) {
// If it does, read the file and parse its JSON content into the dataHistory variable
dataHistory = JSON.parse(fs.readFileSync(dataFile));
} else {
// If it doesn't, initialize dataHistory with an object that has an empty 'alerts' array
dataHistory = {
alerts: [],
};
}
// Define a function to save the current state of dataHistory back into the dataFile
const saveData = () => {
fs.writeFileSync(dataFile, JSON.stringify(dataHistory));
}
// The getSensors function retrieves sensor data from the Temp Stick API
async function getSensors() {
try {
// Send a GET request to the Temp Stick API's 'sensors/all' endpoint
const response = await axios.get('https://tempstickapi.com/api/v1/sensors/all', {
headers: {
'X-API-KEY': TEMP_STICK_API_KEY // Include the Temp Stick API key in the request headers
},
timeout: 0, // Do not set a timeout for the request
});
const res = response.data; // Extract the data from the response
// If the API request was successful, return the list of sensor items
// If not, return false
if(res.type === 'success') {
return res.data.items;
} else {
return false;
}
} catch (error) { // If there's an error in the try block, log it
console.error(error);
}
}
// The getAlertNotifications function retrieves alert notifications from the Temp Stick API
async function getAlertNotifications() {
try {
// Send a GET request to the Temp Stick API's 'user/notifications' endpoint
const response = await axios.get('https://tempstickapi.com/api/v1/user/notifications', {
headers: {
'X-API-KEY': TEMP_STICK_API_KEY // Include the Temp Stick API key in the request headers
},
timeout: 0, // Do not set a timeout for the request
});
const res = response.data; // Extract the data from the response
// If the API request was successful, return the list of notification items
// If not, return false
if(res.type === 'success') {
return res.data.items;
} else {
return false;
}
} catch (error) { // If there's an error in the try block, log it
console.error(error);
}
}
// The getMessages function retrieves messages for a specific sensor from the Temp Stick API
async function getMessages(sensorId) {
try {
// Send a GET request to the Temp Stick API's 'sensor/{sensorId}/readings' endpoint
// The request will include a query string specifying the settings and offset for the readings
const response = await axios.get(`https://tempstickapi.com/api/v1/sensor/${sensorId}/readings?setting=24_hours&offset=21600`, {
headers: {
'X-API-KEY': TEMP_STICK_API_KEY // Include the Temp Stick API key in the request headers
},
timeout: 0, // Do not set a timeout for the request
});
const res = response.data; // Extract the data from the response
// If the API request was successful, return the list of readings
// If not, return false
if(res.type === 'success') {
return res.data.readings;
} else {
return false;
}
} catch (error) { // If there's an error in the try block, log it
console.error(error);
}
}
// Function to add alerts to a Google Spreadsheet
async function addAlertsToSheet(alerts) {
// Initialize an empty array to hold the data
let data = [];
// Loop through the alerts array
for(alert of alerts) {
// Combine the event type and event message into one string
let message = alert.event_type + " " + alert.event_message;
let created = alert.created;
// Convert the created timestamp into Ontario time
let ontarioTime = new Date(created).toLocaleString("en-US", {
timeZone: "America/Toronto",
});
// Add a new row of data for each alert
data.push([
alert.sensor_id,
message,
ontarioTime,
])
}
// Sort the data array by date
data = data.sort((a, b) => {
return new Date(b[2]) - new Date(a[2]);
});
const sheetName = 'Alerts'; // replace with your sheet name
// Get the spreadsheet details
const spreadsheet = await sheets.spreadsheets.get({
auth,
spreadsheetId,
});
// Find the sheetId of the sheet with the given name
const sheet = spreadsheet.data.sheets.find(sheet => sheet.properties.title === sheetName);
const sheetId = sheet.properties.sheetId;
// Insert rows into the spreadsheet
await sheets.spreadsheets.batchUpdate({
auth,
spreadsheetId,
resource: {
requests: [{
insertDimension: {
range: {
sheetId: sheetId,
dimension: 'ROWS',
startIndex: 1,
endIndex: 1 + data.length
},
inheritFromBefore: false
}
}]
}
});
// Update the spreadsheet with the new data
await sheets.spreadsheets.values.update({
auth,
spreadsheetId,
range: sheetName + '!A2', // assuming that your data starts from column A and the header is in the first row
valueInputOption: 'USER_ENTERED',
resource: { values: data },
});
}
// Function to add sensor readings to a Google Spreadsheet
async function addSensorValuesToSheet(newReadings) {
// Initialize an empty array to hold the data
let data = [];
// Loop through the readings array
for(reading of newReadings) {
// Grab the necessary values from the reading
let tempC = reading.temperature;
let tempF = (tempC * 9/5 + 32).toFixed(1); // Convert the temperature to Fahrenheit
let humidity = reading.humidity;
let checkin = reading.sensor_time;
// Convert the check-in time to Ontario time
let ontarioTime = new Date(checkin).toLocaleString("en-US", {
timeZone: "America/Toronto",
});
let rssi = reading.rssi;
let time_to_connect = reading.time_to_connect;
let battery_pct = getBatteryPercentage(reading.voltage); // Get the battery percentage
// Add a new row of data for each reading
data.push([
reading.sensor_id,
tempC,
tempF,
humidity,
rssi,
time_to_connect,
battery_pct,
reading.offline,
ontarioTime,
])
}
// Sort the data array by date
data = data.sort((a, b) => {
return new Date(b[8]) - new Date(a[8]);
});
const sheetName = 'Readings'; // replace with your sheet name
// Insert rows into the spreadsheet
await sheets.spreadsheets.batchUpdate({
auth,
spreadsheetId,
resource: {
requests: [{
insertDimension: {
range: {
sheetId: 0,
dimension: 'ROWS',
startIndex: 1,
endIndex: 1 + data.length
},
inheritFromBefore: false
}
}]
}
});
// Update the spreadsheet with the new data
await sheets.spreadsheets.values.update({
auth,
spreadsheetId,
range: sheetName + '!A2', // assuming that your data starts from column A and the header is in the first row
valueInputOption: 'USER_ENTERED',
resource: { values: data },
});
}
// Function to process the alerts
const processAlerts = async () => {
// Fetch alert notifications
const alerts = await getAlertNotifications();
// Initialize an empty array for new alerts
const newAlerts = [];
// Loop over all alerts
for(let alert of alerts) {
// Check if the alert already exists in the history
let alertExists = false;
for(let existingAlert of dataHistory.alerts) {
if(existingAlert.id === alert.id) {
alertExists = true;
break;
}
}
// If the alert doesn't exist, add it to the new alerts array
if (!alertExists) {
newAlerts.unshift(alert);
}
}
// Add new alerts to the history and limit history size to 1000 entries
dataHistory.alerts = [...newAlerts, ...dataHistory.alerts];
if(dataHistory.alerts.length > 1000) {
dataHistory.alerts = dataHistory.alerts.slice(0, 1000);
}
// Add new alerts to the Google Sheet and save data
await addAlertsToSheet(newAlerts);
saveData();
};
// Function to process sensor data
const processSensors = async () => {
// Fetch sensors data
const sensors = await getSensors();
// Process alerts after 60 seconds to allow for data to come in
setTimeout(processAlerts, 60000);
// If sensors are available
if (sensors && sensors.length > 0) {
let nextCheckIn = null;
const now = new Date();
const batchUpdate = [];
// Loop over all sensors
for (let sensor of sensors) {
// If there's no history for this sensor, create a new array for it
if(!dataHistory[sensor.sensor_id]) {
dataHistory[sensor.sensor_id] = [];
}
// Get messages for each sensor
const readings = await getMessages(sensor.sensor_id);
const newReadings = [];
// Loop over all readings
for(let reading of readings) {
reading.sensor_id = sensor.sensor_id;
let readingExists = false;
// Check if the reading already exists in the history
for(let existingReading of dataHistory[sensor.sensor_id]) {
if(existingReading.sensor_time === reading.sensor_time) {
readingExists = true;
break;
}
}
// If the reading doesn't exist, add it to the new readings array and batch update array
if (!readingExists) {
newReadings.unshift(reading);
batchUpdate.push(reading);
}
}
// Add new readings to the history and limit history size to 1000 entries
dataHistory[sensor.sensor_id] = [...newReadings, ...dataHistory[sensor.sensor_id]];
if(dataHistory[sensor.sensor_id].length > 1000) {
dataHistory[sensor.sensor_id] = dataHistory[sensor.sensor_id].slice(0, 1000);
}
// Calculate the next check-in time
let sensorNextCheckIn = new Date(sensor.next_checkin + "Z");
while (sensorNextCheckIn <= now) {
sensorNextCheckIn = new Date(sensorNextCheckIn.getTime() + 60000);
}
if (!nextCheckIn || sensorNextCheckIn < nextCheckIn) {
nextCheckIn = sensorNextCheckIn;
}
}
// If there are any new readings, add them to the Google Sheet
if(batchUpdate.length > 0) {
await addSensorValuesToSheet(batchUpdate);
}
// Save the data
saveData();
// Calculate sleep time until the next check-in
if (nextCheckIn) {
let sleepTime = nextCheckIn.getTime() - now.getTime() + 60 * 1000;
if(sleepTime < 0) {
sleepTime = 15000;
}
// Delay the next sensor process by sleep time
if (sleepTime > 0) {
console.log(`Sleeping for ${sleepTime / 1000} seconds...`);
setTimeout(processSensors, sleepTime);
} else {
// always sleep for 60 seconds although this should never process
setTimeout(processSensors, 60000);
}
} else {
console.log('No next check-in time. Exiting...');
}
} else {
console.log('No sensors. Exiting...');
}
};
// Start processing the sensors
processSensors();