I use the Vera to log my energy usage, using the scripts for the dutch smart meter. However, we also have some solar panels thus the actual usage off all equipment can not be derived from those scripts. (since it only shows the net result, for example when using a light with 100w and the solar panels generate at that moment also 100w the smartmeter would show 0w incoming and 0w outgoing)
Since the inverter has an UTP connection, and there’s some documentation and projects on it ([url=https://smaspot.codeplex.com/]https://smaspot.codeplex.com/[/url]) I tried to get some info directly to the Vera so I would be able to calculate the actual usage.
The inverter used is an SMA Tripower 6000TL-20
Here is the first working script, it isn’t nice but it gets the job done and that’s good enough for me now. It now runs for over 2 months and the data I retrieve is 100% equal to the data when seen on the sunny portal.
Hope someone has some use for it, when I find some time perhaps I’ll try to generate a device for it but for now, this is it.
The only thing to change is the text:
typeyourownpasswordhere
It will do a broadcast to find the IP-address for your inverter, then logon and then send the command to get the totalenergy amount. I store the result in a variablecontainer, but that’s commented out.
--current position in buffer string(and length)
local smaPacketposition=1
--buffer string
local packetBuffer = string.rep (string.char(0x00), 520)
--require bitwise operations
local bitw = require("bit")
--broadcast ip, do not change
local smaBroadcastIP = "239.12.255.254"
--inverter ip
local smaInverterIP
--total output
local totalOutputWh = 0
--app ids
local AppSUSyID, AppSerial, AppSerialSeed
AppSUSyID = 125
math.randomseed(os.time())
AppSerial = 900000000 + ((bitw.lshift(math.random(1,32767),16)) + math.random(1,32767)) % 100000000;
--packet ids, not used
local smaPacketID =1
--generic functions used
--replace char in string function
function replace_char(pos, str, r)
return str:sub(1, pos-1) .. r .. str:sub(pos+1)
end
--replace string in string function
function replace_string(pos, str, r)
return str:sub(1, pos-1) .. r .. str:sub(pos+string.len(r))
end
--write a short
function smaWriteShort(v)
smaWriteByte(string.char(bitw.band(bitw.rshift(v,0),0xFF)))
smaWriteByte(string.char(bitw.band(bitw.rshift(v,8),0xFF)))
end
--write a long
function smaWriteLong(v)
smaWriteByte(string.char(bitw.band(bitw.rshift(v,0),0xFF)))
smaWriteByte(string.char(bitw.band(bitw.rshift(v,8),0xFF)))
smaWriteByte(string.char(bitw.band(bitw.rshift(v,16),0xFF)))
smaWriteByte(string.char(bitw.band(bitw.rshift(v,24),0xFF)))
end
--write sma packet
function smaWritePacket(longwords, ctrl, ctrl2, dstSUSyID, dstSerial)
smaWriteLong(0x65601000)
smaWriteByte(string.char(longwords))
smaWriteByte(string.char(ctrl))
smaWriteShort(dstSUSyID)
smaWriteLong(dstSerial)
smaWriteShort(ctrl2)
smaWriteShort(AppSUSyID)
smaWriteLong( AppSerial)
smaWriteShort(ctrl2)
smaWriteShort(0)
smaWriteShort(0)
smaWriteShort(bitw.bor(smaPacketID,0x8000))
end
--write sma packet header
function smaWritePacketHeader(pckLngth)
packetBuffer = string.rep (string.char(0x00), 520)
local packetHeader = string.char(0x53,0x4d,0x41,0x00,0x00,0x04,0x02,0xa0,0x00,0x00,0x00,0x01,0x00) .. string.char(pckLngth)
packetBuffer=replace_string(1, packetBuffer, packetHeader)
smaPacketposition = packetHeader:len()+1
end
--write packet trailer
function smaWritePacketTrailer()
smaWriteLong(0)
end
--add character at specific location in buffer
function smaWriteByte(iChar)
packetBuffer=replace_char(smaPacketposition,packetBuffer,iChar)
smaPacketposition=smaPacketposition+1
end
--write string to buffer
function smaWriteString(strIn)
for i=1,strIn:len() do
smaWriteByte(strIn:sub(i , i+1))
end
end
--log the outcome (hex)
function smaLogBytePackages(str)
local strLine=""
for i=1,str:len() do
strLine = strLine .. string.format("%02X",str:byte(i)) .. " "
if (i % 10 == 0) or (i == str:len()) then
strLine = ""
end
end
end
--we broadcast to get the ip for the inverter
local broadcastPacket = string.char(0x53,0x4d,0x41,0x00,0x00,0x04,0x02,0xa0,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00)
local socket = require("socket")
local udp = socket.udp()
if (udp ~= nil) then
packetBuffer = replace_string(1,packetBuffer,broadcastPacket)
local result, message = udp:sendto(packetBuffer, smaBroadcastIP, 9522)
if (result ~= nil) then
udp:settimeout(5)
result, message = udp:receivefrom()
if (result == nil) then
luup.log("no result from inverter", message)
else
smaInverterIP = message
end
end
end
--try to logon
smaPacketID = smaPacketID+2
local pwNormal, pwEnc, pwEncChar
pwNormal = "typeyourownpasswordhere"
pwEnc = string.rep(string.char(0x88),12)
for i=1,string.len(pwNormal) do
pwEncChar = string.char(string.byte(string.sub(pwNormal,i,i+1)) + 0x88)
pwEnc = replace_char(i, pwEnc, pwEncChar)
end
--password encrypted
packetBuffer = string.rep (string.char(0x00), 520)
smaWritePacketHeader(0x3a)
smaWritePacket(0x0e, 0xa0, 0x0100, 0xffff, 0xffffffff)
smaWriteLong(0xfffd040c)
smaWriteLong(0x00000007)
smaWriteLong(0x00000384)
smaWriteLong(os.difftime(os.time(),os.time{year=1970, month=1, day=1, hour=0, sec=1}))
smaWriteLong(0)
smaWriteString(pwEnc)
smaWritePacketTrailer()
--packetlenght for now fixed in header
local result, message = udp:sendto(string.sub(packetBuffer,1,smaPacketposition-1), smaInverterIP, 9522)
if (result ~= nil) then
udp:settimeout(5)
result =""
result, message = udp:receivefrom()
if (result == nil) then
luup.log("no result from inverter for logon", message)
else
smaInverterIP = message
end
end
luup.log("****logged on to SMA inverter****")
--now command for totalenergy
smaWritePacketHeader(0x26)
smaWritePacket(0x09, 0xa0, 0, 0xffff, 0xffffffff)
smaWriteLong(0x54000200)
smaWriteLong(0x00260100)
smaWriteLong(0x002622FF)
smaWritePacketTrailer()
result, message = udp:sendto(string.sub(packetBuffer,1,smaPacketposition-1), smaInverterIP, 9522)
if (result ~= nil) then
udp:settimeout(5)
result =""
result, message = udp:receivefrom()
if (result == nil) then
luup.log("no result totalenery", message)
else
smaInverterIP = message
if string.len(result) >= 66 then
totalOutputWh = (result:byte(63)+(result:byte(64))*256+(result:byte(65))*256^2+(result:byte(66))*256^3)/1000
luup.log("Total Solar output:" .. tostring(totalOutputWh))
-- luup.variable_set("urn:upnp-org:serviceId:VContainer1","Variable5",totalOutputWh,21)
end
end
end
--now command for logoff
smaWritePacketHeader(0x22)
smaWritePacket(0x08, 0xa0, 0x0300, 0xffff, 0xffffffff)
smaWriteLong(0xfffd010e)
smaWriteLong(0xffffffff)
smaWritePacketTrailer()
result, message = udp:sendto(string.sub(packetBuffer,1,smaPacketposition-1), smaInverterIP, 9522)
udp:close()