and to top it of this one.
Code: Select all
-- Verbeterd script met functies in de hoofdscope
local CONSTANTS = {
BATTERY_CAPACITY = 79, -- kWh
TARGET_BATTERY_PERCENTAGE = 82, -- Target percentage for normalization
PRICE_PER_KWH = 0.25, -- euro/kWh
CSV_FILE_PATH = "/home/hein/csvData/EV_Consumption.csv", -- Path to the CSV file
MAX_TEMP_ARRAY_SIZE = 1440 -- Max number of temperature measurements (1 day with 1 measurement per minute)
}
--[[
Certainly! Here's a concise description of the script and the involved sensors:
### **Script Purpose**
This script tracks and calculates electric vehicle (EV) consumption statistics, including:
- Monthly distance traveled (km)
- Energy usage (kWh) and associated costs (€)
- Battery level normalization (adjusting kWh to a target charge level)
- Average temperature impact
- EV efficiency (kWh/100km)
Data is logged to a **CSV file** and displayed on a **Domoticz text sensor** for easy monitoring.
---
### **Involved Sensors**
1. **`Charging Level`** (Selector switch) – Triggers calculations when charging stops.
2. **`XC40-ChargeLevel`** (Battery level %) – Monitors the EV's current charge state.
3. **`Volvo-Odometer`** (Odometer in km) – Tracks total distance driven.
4. **`Laadpaal`** (Energy meter in kWh) – Measures total energy consumed while charging.
5. **`Avg buitentemp`** (Temperature sensor) – Records ambient temperature for efficiency analysis.
6. **`EV Consumption Facts`** (Text sensor) – Displays summarized EV data.
7. **`Reset EV Data`** (Virtual switch) – Manually resets monthly data (if configured).
---
### **Key Features**
- **Normalization**: Adjusts kWh usage to a target battery level (82%) for fair comparisons.
- **Monthly Reset**: Auto-resets metrics at month-end (runs at **23:53 daily**).
- **CSV Logging**: Stores historical data for long-term analysis.
- **Cost Calculation**: Estimates charging costs based on a fixed rate (**€0.25/kWh**).
This script helps EV owners monitor efficiency, costs, and driving habits in Domoticz.
Let me know if you'd like any modifications! 🚗⚡
]]--
-- Helper functions
local function spaces(count)
return string.rep(" ", count)
end
local function round(num, decimals)
if num == nil then
domoticz.log('Fout: num is nil in round functie', domoticz.LOG_ERROR)
return 0
end
local mult = 10^(decimals or 0)
return math.floor(num * mult + 0.5) / mult
end
-- Check if today is the last day of the month
local function isLastDayOfMonth(domoticz)
-- Haal de huidige datum op
local today = domoticz.time
-- Bereken de datum van morgen
local tomorrow = domoticz.time.addDays(1)
-- Vergelijk de maand van vandaag en morgen
return today.month ~= tomorrow.month
end
-- Format display text for the text device
local function formatDisplayText(domoticz, data, currentMonth)
return string.format(
"Last Update : %s\n" ..
"Current Odometer : %s%d km\n" ..
"Battery Level : %s%d %% \n" ..
"Distance %s : %s%d km\n" ..
"kWh %s : %s%.2f kWh\n" ..
spaces(15) .. "Costs %s : %s%.2f €\n" ..
spaces(15) .. "Average Temp : %s%.1f°C\n" ..
spaces(15) .. "EV Consumption : %.2f kWh/100km\n",
data.lastUpdate or "N/A",
spaces(5), tonumber(data.currentOdometer) or 0,
spaces(14), tonumber(data.batteryLevel) or 0,
currentMonth, spaces(9), tonumber(data.distanceThisMonth) or 0,
currentMonth, spaces(16), tonumber(data.kWhThisMonth) or 0,
currentMonth, spaces(14), tonumber(data.euro) or 0,
spaces(12), tonumber(data.avgTemp) or 0,
tonumber(data.EVConsumption) or 0
)
end
-- Update the text device with the latest data
local function updateDisplay(domoticz, displayText)
local displayDevice = domoticz.devices('EV Consumption Facts')
if displayDevice then
displayDevice.updateText(displayText)
domoticz.log('Text sensor updated with new data', domoticz.LOG_DEBUG)
else
domoticz.log('Text sensor "EV Consumption Facts" not found', domoticz.LOG_ERROR)
end
end
-- Normalize battery consumption to target percentage
local function normalizeToTargetLevel(domoticz, currentLevel, kWhUsed)
local kWhPerPercent = CONSTANTS.BATTERY_CAPACITY / 100
local difference = CONSTANTS.TARGET_BATTERY_PERCENTAGE - currentLevel
local kWhAdjustment = difference * kWhPerPercent
local normalizedKWh = kWhUsed + kWhAdjustment
domoticz.log('Battery normalization calculation:', domoticz.LOG_DEBUG)
domoticz.log(string.format('Current level: %.1f%%', currentLevel), domoticz.LOG_DEBUG)
domoticz.log(string.format('Difference to target: %.1f%%', difference), domoticz.LOG_DEBUG)
domoticz.log(string.format('kWh adjustment: %.2f kWh', kWhAdjustment), domoticz.LOG_DEBUG)
domoticz.log(string.format('Original kWh: %.2f, Normalized kWh: %.2f', kWhUsed, normalizedKWh), domoticz.LOG_DEBUG)
return normalizedKWh
end
-- Save data to CSV file
local function saveToCSV(domoticz, data)
local file = io.open(CONSTANTS.CSV_FILE_PATH, "a")
if not file then
domoticz.log('Kon het CSV-bestand niet openen voor schrijven', domoticz.LOG_ERROR)
return false
end
local currentTime = os.date("%Y-%m-%d %H:%M:%S")
local fields = {
currentTime,
math.floor(data.currentOdometer or 0),
math.floor(data.batteryLevel or 0),
math.floor(data.distanceThisMonth or 0),
round(data.kWhTotal, 2),
round(data.kWhThisMonth, 2),
round(data.EVConsumption, 2),
round(data.euro, 2),
round(data.avgTemp, 2)
}
local csvLine = table.concat(fields, ";") .. "\n"
file:write(csvLine)
file:close()
domoticz.log('Data succesvol weggeschreven naar CSV bestand', domoticz.LOG_DEBUG)
return true
end
-- Load data from CSV file
local function loadDataFromCSV(domoticz)
local file = io.open(CONSTANTS.CSV_FILE_PATH, "r")
if not file then
domoticz.log('CSV file not found or could not be opened', domoticz.LOG_ERROR)
return nil
end
-- Skip the header line
file:read("*l")
local lastLine
for line in file:lines() do
lastLine = line
end
file:close()
if not lastLine then
domoticz.log('CSV file is empty', domoticz.LOG_ERROR)
return nil
end
domoticz.log('Last line in CSV: ' .. lastLine, domoticz.LOG_DEBUG)
local fields = {}
for field in string.gmatch(lastLine, "([^;]+)") do
table.insert(fields, field)
end
domoticz.log('Parsed fields: ' .. table.concat(fields, ', '), domoticz.LOG_DEBUG)
-- Ensure there are enough fields in the CSV
if #fields < 9 then
domoticz.log('CSV file does not contain enough fields', domoticz.LOG_ERROR)
return nil
end
return {
lastUpdate = fields[1],
beginningOfTheMonthOdometer = tonumber(fields[2]) or 0,
beginningOfTheMonthBatteryLevel = tonumber(fields[3]) or 0,
beginningOfTheMonthkWhTotal = tonumber(fields[5]) or 0,
avgTemp = tonumber(fields[9]) or 0
}
end
-- Process EV data and update display
local function processEVData(domoticz, sensorData, csvData)
local kWhThisMonth = sensorData.currentkWhTotal - csvData.beginningOfTheMonthkWhTotal
local euro = kWhThisMonth * CONSTANTS.PRICE_PER_KWH
local distanceThisMonth = sensorData.currentOdometer - csvData.beginningOfTheMonthOdometer
local normalizedKWh = normalizeToTargetLevel(domoticz, sensorData.batteryLevel, kWhThisMonth)
local EVConsumption = (distanceThisMonth > 0) and ((normalizedKWh / distanceThisMonth) * 100) or 0
-- Update global evConsumption
domoticz.globalData.evConsumption = round(EVConsumption, 2)
domoticz.log('Updated global evConsumption to: ' .. domoticz.globalData.evConsumption, domoticz.LOG_DEBUG)
-- Prepare display data
local displayData = {
currentOdometer = sensorData.currentOdometer,
batteryLevel = sensorData.batteryLevel,
distanceThisMonth = distanceThisMonth,
kWhThisMonth = kWhThisMonth,
kWhTotal = sensorData.currentkWhTotal,
euro = euro,
avgTemp = sensorData.temperature or 0,
EVConsumption = EVConsumption,
normalizedKWh = normalizedKWh,
lastUpdate = os.date("%Y-%m-%d %H:%M:%S")
}
-- Update display
local currentMonth = domoticz.time.monthName
local displayText = formatDisplayText(domoticz, displayData, currentMonth)
updateDisplay(domoticz, displayText)
end
-- Main script
return {
on = {
devices = {'Charging Level', 'Reset EV Data'},
timer = {'at 23:53'}
},
logging = {
level = domoticz.LOG_ERROR,
marker = "----- EV Consumption -----"
},
data = {
previousSelectorLevel = { initial = 0 },
scriptExecutionTime_EVConsumption = { initial = 0 }
},
execute = function(domoticz, device)
local startTime = os.clock()
domoticz.log('Start tijdsmeting EV Consumption script', domoticz.LOG_DEBUG)
local csvData = loadDataFromCSV(domoticz)
if not csvData then
domoticz.log('Failed to load data from CSV', domoticz.LOG_ERROR)
return
end
local sensorData = {
selectorLevel = domoticz.devices('Charging Level').levelVal,
batteryLevel = domoticz.devices('XC40-ChargeLevel').nValue,
currentOdometer = domoticz.devices('Volvo-Odometer').nValue,
currentkWhTotal = domoticz.devices('Laadpaal').WhTotal / 1000,
temperature = tonumber(domoticz.devices('Avg buitentemp').sValue)
}
if sensorData.selectorLevel == 0 and domoticz.data.previousSelectorLevel > 0 then
processEVData(domoticz, sensorData, csvData)
end
domoticz.data.previousSelectorLevel = sensorData.selectorLevel
if isLastDayOfMonth(domoticz) and device.isTimer then
local displayData = {
currentOdometer = sensorData.currentOdometer,
batteryLevel = sensorData.batteryLevel,
distanceThisMonth = sensorData.currentOdometer - csvData.beginningOfTheMonthOdometer,
kWhThisMonth = sensorData.currentkWhTotal - csvData.beginningOfTheMonthkWhTotal,
kWhTotal = sensorData.currentkWhTotal,
euro = (sensorData.currentkWhTotal - csvData.beginningOfTheMonthkWhTotal) * CONSTANTS.PRICE_PER_KWH,
avgTemp = sensorData.temperature or 0,
EVConsumption = (sensorData.currentOdometer - csvData.beginningOfTheMonthOdometer > 0) and (((sensorData.currentkWhTotal - csvData.beginningOfTheMonthkWhTotal) / (sensorData.currentOdometer - csvData.beginningOfTheMonthOdometer)) * 100) or 0,
normalizedKWh = normalizeToTargetLevel(domoticz, sensorData.batteryLevel, sensorData.currentkWhTotal - csvData.beginningOfTheMonthkWhTotal),
lastUpdate = os.date("%Y-%m-%d %H:%M:%S")
}
saveToCSV(domoticz, displayData)
end
local executionTime = os.clock() - startTime
if domoticz.globalData.scriptExecutionTime_EVConsumption then
domoticz.globalData.scriptExecutionTime_EVConsumption.add(executionTime)
domoticz.log('Gemiddelde uitvoeringstijd EV Consumption: ' ..
domoticz.globalData.scriptExecutionTime_EVConsumption.avg() .. ' seconden', domoticz.LOG_DEBUG)
else
domoticz.log('Waarschuwing: globalData.scriptExecutionTime_EVConsumption niet gevonden', domoticz.LOG_DEBUG)
end
domoticz.log('Uitvoeringstijd EV Consumption script: ' .. executionTime .. ' seconden', domoticz.LOG_DEBUG)
end
}
Right now I asked DeepSeek to write a concise descritpion about what this script does and the sensors involved. Plus I asked it to write it in English. (Me and DeepSeek prefer to conversate in Dutch)
The comment on top is nothing HvdW, it's just DeepSeek.
DeepSeek had problems coping with global data, so halfway I asked Claude to interfere and after some modification, DeepSeek continued the coding.