Created
April 4, 2025 05:15
-
-
Save mettamatt/570b7f7a839b5fca4778b624ead985be to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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