Skip to content

Instantly share code, notes, and snippets.

@mettamatt
Created April 4, 2025 05:15
Show Gist options
  • Save mettamatt/570b7f7a839b5fca4778b624ead985be to your computer and use it in GitHub Desktop.
Save mettamatt/570b7f7a839b5fca4778b624ead985be to your computer and use it in GitHub Desktop.
/**
* sunset_light_control.js
*
* Purpose: Automatically controls a Shelly switch based on sunset/sunrise times.
*
* Behavior:
* - Checks if the light is already on - if yes, does nothing
* - If the light is off, checks if it's between sunset and sunrise
* - If it's nighttime, turns on the light with a 5-minute timer
* - Caches sunrise/sunset times to reduce API calls and ensures they are for the current date
*
* Requirements:
* - Needs internet access to fetch sunrise/sunset times
* - Uses Shelly's location detection
* - Controls Switch.ID 0 (modify LIGHT_SWITCH_ID in config if different)
*
* Endpoint URL Format:
* - http://<shelly_ip>/script/1/trigger_event?event_type=<event_type_value>
* - `<shelly_ip>`: The IP address of this Shelly
* - `<event_type_value>` is optional.
*/
// Configuration
var CONFIG = {
LIGHT_SWITCH_ID: 0,
TIMER_DURATION: 300 // 5 minutes in seconds
};
/**
* Main execution function that first checks light status before proceeding
*/
function main() {
// Check if the light is on
Shelly.call("Switch.GetStatus", { id: CONFIG.LIGHT_SWITCH_ID }, function(result, error_code) {
if (error_code !== 0) {
print("Error checking light status");
return;
}
if (result.output === true) {
print("Light is already on, skipping all operations");
return;
}
// Light is off, proceed with the rest of the logic
checkSunTimes();
});
}
/**
* Checks cached times or initiates the process to get new times
*/
function checkSunTimes() {
// Fixed cache keys
var sunriseKey = "sun_sunrise";
var sunsetKey = "sun_sunset";
var timestampKey = "sun_timestamp";
// Get cached timestamp to check cache validity
Shelly.call("KVS.Get", { key: timestampKey }, function(result, error_code, error_message) {
if (error_code === 0) {
var cacheData = JSON.parse(result.value);
var cachedDate = cacheData.date;
var todayDate = new Date().toISOString().split('T')[0];
if (cachedDate === todayDate) {
// Cache is valid for today, retrieve sunrise and sunset times
var sunriseTime = cacheData.sunrise;
var sunsetTime = cacheData.sunset;
var currentTime = Math.floor(Date.now() / 1000);
handleLightControl(currentTime, sunriseTime, sunsetTime);
} else {
// Cached data is not for today, fetch new sun times
getLocationAndProcess(sunriseKey, sunsetKey, timestampKey);
}
} else {
// No valid cached data, fetch new sun times
getLocationAndProcess(sunriseKey, sunsetKey, timestampKey);
}
});
}
/**
* Gets location and processes sun times
* @param {string} sunriseKey - The key for caching sunrise time
* @param {string} sunsetKey - The key for caching sunset time
* @param {string} timestampKey - The key for caching timestamp and date
*/
function getLocationAndProcess(sunriseKey, sunsetKey, timestampKey) {
// Get location
Shelly.call("Shelly.DetectLocation", {}, function(locationResponse) {
if (!locationResponse || !locationResponse.lat || !locationResponse.lon) {
print("Error: Could not detect location");
return;
}
fetchAndProcessSunTimes(locationResponse.lat, locationResponse.lon, sunriseKey, sunsetKey, timestampKey);
});
}
/**
* Fetches and processes sun times data
* @param {number} lat - Latitude
* @param {number} lng - Longitude
* @param {string} sunriseKey - The key for caching sunrise time
* @param {string} sunsetKey - The key for caching sunset time
* @param {string} timestampKey - The key for caching timestamp and date
*/
function fetchAndProcessSunTimes(lat, lng, sunriseKey, sunsetKey, timestampKey) {
var todayDate = new Date().toISOString().split('T')[0];
var url = "https://api.sunrise-sunset.org/json?lat=" + lat + "&lng=" + lng + "&formatted=0&date=" + todayDate;
Shelly.call("http.request", {
method: "GET",
url: url,
headers: { "Content-Type": "application/json" }
}, function(response) {
if (!response || !response.body) {
print("Error: No response from sunrise-sunset API");
return;
}
try {
var data = JSON.parse(response.body);
if (!data.results) {
print("Error: Invalid sun times data");
return;
}
var sunrise = isoToUnix(data.results.sunrise);
var sunset = isoToUnix(data.results.sunset);
var timestamp = Math.floor(Date.now() / 1000);
// Store the data in a single JSON object
var cacheData = {
date: todayDate,
sunrise: sunrise,
sunset: sunset
};
// Save to KVS using the fixed key
Shelly.call("KVS.Set", { key: timestampKey, value: JSON.stringify(cacheData) });
handleLightControl(timestamp, sunrise, sunset);
} catch (e) {
print("Error parsing API response: " + e.message);
}
});
}
/**
* Converts ISO string to Unix timestamp
* @param {string} isoString - ISO formatted date string
* @returns {number} Unix timestamp in seconds
*/
function isoToUnix(isoString) {
return Math.floor(new Date(isoString).getTime() / 1000);
}
/**
* Handles the light control logic
* @param {number} currentTime - Current Unix timestamp
* @param {number} sunriseTime - Sunrise Unix timestamp
* @param {number} sunsetTime - Sunset Unix timestamp
*/
function handleLightControl(currentTime, sunriseTime, sunsetTime) {
print("Current Time: " + new Date(currentTime * 1000).toISOString());
print("Sunrise Time: " + new Date(sunriseTime * 1000).toISOString());
print("Sunset Time: " + new Date(sunsetTime * 1000).toISOString());
/**
* Important Logic:
* Nighttime is defined as the period from today's sunset to tomorrow's sunrise.
* This logic accounts for the time crossing over midnight.
*
* We check:
* - If current time is after today's sunset (evening/night)
* - Or if current time is before today's sunrise (early morning)
*
* This approach ensures accurate identification of nighttime across midnight.
*/
if (currentTime >= sunsetTime) {
// After today's sunset - It's nighttime
print("It's nighttime; the light will be turned on.");
turnOnLight();
} else if (currentTime <= sunriseTime) {
// Before today's sunrise (after midnight) - It's nighttime
print("It's nighttime; the light will be turned on.");
turnOnLight();
} else {
// It's daytime
print("It's daytime; the light will not be turned on.");
}
}
/**
* Turns on the light with a timer
*/
function turnOnLight() {
Shelly.call("Switch.Set", {
id: CONFIG.LIGHT_SWITCH_ID,
on: true,
toggle_after: CONFIG.TIMER_DURATION
}, function(res, ec, em) {
if (ec === 0) {
print("Light turned on with a " + (CONFIG.TIMER_DURATION / 60) + "-minute timer.");
} else {
print("Error turning on light: " + em);
}
});
}
/**
* Function to be triggered by external events
* @param {string} eventType - Type of the event triggering the function (optional)
*/
function onTriggerEvent(eventType) {
if (eventType) {
print("Trigger event received: " + eventType);
} else {
print("Trigger event received with no event_type.");
}
main();
}
// Register custom HTTP endpoint using HTTPServer.registerEndpoint
HTTPServer.registerEndpoint("trigger_event", function(req, res) {
// Parse the query string to get event_type
var query = req.query || "";
var params = parseQuery(query);
var eventType = params.event_type;
onTriggerEvent(eventType);
// Send HTTP response
res.code = 200;
res.body = "Event received";
if (eventType) {
res.body += ": " + eventType;
}
res.send();
});
// Helper function to parse query string into an object
function parseQuery(query) {
var params = {};
if (query === "") {
return params;
}
var pairs = query.split("&");
for (var i = 0; i < pairs.length; i++) {
var kv = pairs[i].split("=");
var key = kv[0] || "";
var value = kv[1] || "";
params[key] = value;
}
return params;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment