History
0.201909210900 -- Start design / coding
0.201909251100 -- First version for forum
0.201909261300 -- Add automated change of inverter device Type to delivery
0.201910122300 -- Prepared for windows
As always please feel free to comment, ask for clarification, report bugs etc.. by replying here on the forum.
Have Fun !
Code: Select all
--[[
This dzVents script is one approach to collect data from your local Enphase system
It is inspired by the various postings on the domoticz forum about getting details
from your own solar panels.
The script uses the installer login for which an Android password generator tool
is available. Full process description about the development of this tool can be found at
https://thecomputerperson.wordpress.com/2016/08/28/reverse-engineering-the-enphase-installer-toolkit/
the Android tool described can be downloaded from
https://www.dropbox.com/s/xc40op8eqfrykaa/AndroidXam.AndroidXam.Signed2019.apk?dl=0
The script uses wget calls and interprets the JSON returns.
for Windows you can get a wget executable via https://eternallybored.org/misc/wget/
If the createDevices switch is set to true. The script will create ALL the individual devices
if they do not exist on the system yet. (1 total and 1 per panel (so if you own 20 panels it creates 21 devices)
Before activating the script:
Please read the GETTING STARTED section of the dzVents wiki.
For windows users download and install the wget utility
Define a dummy hardware in domoticz if not already done
Enter your settings at the appropriate place
Have fun !!
Please use the domoticz forum fore reporting bugs, asking questions for clarification, tips for improvement, etc..
History
0.201909210900 -- Start design / coding
0.201909251100 -- First version for forum
0.201909261300 -- Add automated change of inverter device Type to delivery
0.201910122300 -- Prepared for windows
]]--
local scriptVersion = '0.201910122300'
local scriptVar = 'Enphase_' .. scriptVersion
local frequency = 15 -- -- As far as I know Enphase reporting is only once / 15 minutes
return
{
on =
{
timer = { 'every ' .. frequency .. ' minutes between sunrise and 45 minutes after sunset' },
},
logging =
{
level = domoticz.LOG_DEBUG, -- Change to info or Error if everything works as expected.
marker = scriptVar,
},
data = { inverters = { initial = {} }},
execute = function(dz)
-- ==============================
-- Your setting here
-- ==============================
-- Change to reflect location of your wget and remove -- ( comment ) in front of the declaration for your OS
-- wgetExecutable = '/usr/bin/wget' -- Linux
-- wgetExecutable = 'c:\\"Program files"\\wget.exe' -- Windows: double back slashes where one is in the path
local Enphase = {
ip = 'xxx.xxx.xxx.xxx', -- IP address of your local enphase hub
password = 'a2B4bdA', -- Change to your calculated password. See description above on how to get this.
dummyHardwareIDX = XX, -- Change to ID of your virtual hardware
useProduction = true,
productionDeviceName = 'Enphase production',
useInverters = true,
inverterDevicePrefix = 'micro inverter: ', -- script use this prefix + serial number of inverter.
createDevices = true, -- Should the script create devices if they are not defined yet.
-- please note that the counter display will only show a rough estimate
-- of the produced energy over the period the device lives in domoticz and the script is active.
-- =========================================
-- == No changes required below this line ==
-- ==========================================
user = 'installer', -- I think this is a fixed username
inverterDeviceType = 'Electric (instant+Counter)', -- script use this type for inverters
productionDeviceType = 'Electric (instant+Counter)', -- script use this type for total production
productionAPI = '/api/v1/production',
invertersAPI = '/api/v1/production/inverters',
}
local function createEnphaseDevice(deviceType, deviceName)
local sensorMappedTypes = {
['Electric (instant+Counter)'] = '0xF31D',
['Youless'] = '0xFC01',
['Usage (electric)'] = '0xF801',
}
Enphase.openURLDelay = Enphase.openURLDelay or 0 -- This wil lprevent domoticz to get overloaded by repetative createDevice calls
local deviceName = dz.utils.urlEncode(deviceName)
url = dz.settings['Domoticz url'] .. '/json.htm?type=createdevice&idx=' ..
Enphase.dummyHardwareIDX .. '&sensorname=' ..
deviceName .. '&sensormappedtype='.. sensorMappedTypes[deviceType]
dz.openURL(url).afterSec(Enphase.openURLDelay)
Enphase.openURLDelay = Enphase.openURLDelay + 1
end
local function changeInverter2DeliveryType(inverter)
Enphase.openURLDelay = Enphase.openURLDelay or 0
url = dz.settings['Domoticz url'] .. '/json.htm?type=setused&description=&switchtype=4&EnergyMeterMode=0&used=true' ..
'&idx=' .. inverter.id ..
'&name=' .. dz.utils.urlEncode(inverter.name)
dz.openURL(url).afterSec(Enphase.openURLDelay)
dz.log('Changing inverter ' .. inverter.name ..' to delivery type: \n' .. url,dz.LOG_FORCE)
Enphase.openURLDelay = Enphase.openURLDelay + 1
end
local function osCommand(cmd)
dz.log(cmd,LOG_DEBUG)
local fileHandle = assert(io.popen(cmd, 'r'))
local commandOutput = assert(fileHandle:read('*a'))
local returnTable = {fileHandle:close()}
if returnTable[3] ~= 0 then
dz.log("ReturnCode: " .. returnTable[3] .. "\ncommandOutput:\n" .. commandOutput, dz.LOG_ERROR)
else
return commandOutput
end
dz.log("ReturnCode: " .. returnTable[3] .. "\ncommandOutput:\n" .. commandOutput, dz.LOG_ERROR)
end
local function getEnphaseData(api)
local baseCommand = wgetExecutable .. ' -T 3 --user ' .. Enphase.user .. ' --password ' .. Enphase.password ..' -O - '
local cmd = baseCommand .. Enphase.ip .. api
return osCommand(cmd)
end
local function updateInverter(device, newInverter, deviceName )
if device then
for _, oldInverter in ipairs(dz.data.inverters) do
if oldInverter['serialNumber'] == newInverter['serialNumber'] then -- ignore inverter if no history (will be there next run)
device.updateElectricity(newInverter['lastReportWatts'],device.WhTotal + tonumber(newInverter['lastReportWatts']) / ( 60 / frequency ) )
if device.switchTypeValue ~= 4 then changeInverter2DeliveryType(device) end
if dz.time.matchesRule('between 25 minutes after sunset and 23:59') then device.updateElectricity(0,device.WhTotal) end
end
end
else
dz.log('Device ' .. deviceName .. ' does not exist. ', dz.LOG_ERROR)
end
end
local function getInverters()
return dz.utils.fromJSON(getEnphaseData(Enphase.invertersAPI) or {})
end
local function procesInverters(inverterTable)
if inverterTable[1] == nil then
dz.log('Empty return from Enphase. Go check it out', dz.LOG_ERROR)
return
end
for _, inverter in ipairs(inverterTable) do
local deviceName = Enphase.inverterDevicePrefix .. inverter.serialNumber
if not(dz.utils.deviceExists(deviceName)) and Enphase.createDevices then
dz.log('Device ' .. deviceName .. ' will be created. ', dz.LOG_FORCE)
createEnphaseDevice(Enphase.inverterDeviceType, deviceName)
else
updateInverter(dz.devices(deviceName), inverter, deviceName )
end
end
dz.data.inverters = inverterTable -- store data for next run
end
local function getProduction()
return dz.utils.fromJSON(getEnphaseData(Enphase.productionAPI) or {})
end
local function procesProduction(productionTable)
if productionTable.wattsNow == nil then
dz.log('Empty return from Enphase. Go check it out', dz.LOG_ERROR)
return
end
local noDevice = not(dz.utils.deviceExists(Enphase.productionDeviceName))
if noDevice and Enphase.createDevices then
dz.log('Device ' .. Enphase.productionDeviceName .. ' will be created. ', dz.LOG_FORCE)
createEnphaseDevice(Enphase.productionDeviceType, Enphase.productionDeviceName)
elseif noDevice then
dz.log('Device ' .. Enphase.productionDeviceName .. ' does not exist. ', dz.LOG_ERROR)
else
dz.devices(Enphase.productionDeviceName).updateElectricity(productionTable.wattsNow, productionTable.wattHoursLifetime )
end
end
-- main
if Enphase.useProduction then procesProduction(getProduction()) end
if Enphase.useInverters then procesInverters(getInverters()) end
end
}