Page 1 of 2
Omnik Export Script
Posted: Sunday 21 February 2016 14:49
by Noppie
Can anybody help me:
I used the script form the wiki Omnik Inverter, but I keep getting errors when I run the script.
traceback (most recent call last):
File "OmnikMessage.py", line 83, in <module>
s.sendall(InverterMsg.generate_string(wifi_serial))
AttributeError: 'module' object has no attribute 'generate_string'
I'm a newbie so it's difficult for me.
Thanx!
Re: Omnik Export Script
Posted: Saturday 26 March 2016 15:45
by edsirtan
Hello Noppy, I'm also struggling to solve this issue. Do you have al solution yet?
I do get the same error when using the OmikExport script:
pi@raspberrypi:~/domoticz/Omnik-Data-Logger$ python OmnikExport.py
Traceback (most recent call last):
File "OmnikExport.py", line 83, in <module>
s.sendall(InverterMsg.generate_string(wifi_serial))
AttributeError: 'module' object has no attribute 'generate_string'
pi@raspberrypi:
Probably something to do with the generate_string not able to find in the script.
Can anybody help????
Thnx
Re: Omnik Export Script
Posted: Sunday 27 March 2016 14:31
by sincze
Can you share the first 3 digits of the serial number of the Omnik inverter?
It seems the script is not always working for all the Omnik inverters.
Re: Omnik Export Script
Posted: Tuesday 29 March 2016 22:24
by jbr79
Also struggling with this particular script.
Since LiveStats.py is functioning properly (generating output), I am not expecting that the following error:
Code: Select all
Traceback (most recent call last):
File "Omnik-Export.py", line 83, in <module>
s.sendall(InverterMsg.generate_string(wifi_serial))
AttributeError: 'module' object has no attribute 'generate_string'
....has anything to do with the device serial number, but rather with the python configuration (more specifically the InverterMsg.py file)
I am running python version 2.7.9 on a RBPi3.
Re: Omnik Export Script
Posted: Tuesday 29 March 2016 22:48
by sincze
That would have been indeed my next question.
It is known that Omnik Export script doesn't work for all Omnik Inverter.
However if LiveStats is producing output... the rest should work for you as well.
Mine is on Python version 2.7.3.
Code: Select all
root@PC:/home/domoticz/Omnik-Data-Logger# python OmnikExport.py
root@PC:/home/domoticz/Omnik-Data-Logger#
I copy pasted the script from the wiki into a temp.py file and executed that one. No Errors.
The code in the wiki is still okay.
Have to keep digging some more.
Re: Omnik Export Script
Posted: Wednesday 30 March 2016 12:54
by jbr79
I copy pasted the script from the wiki into a temp.py file and executed that one. No Errors.
The code in the wiki is still okay.
Curious: I had to do several adjustments to align the variable names in the config.cfg file (which was pulled in via a Git clone of the source files -
git clone https://github.com/Woutrrr/Omnik-Data-Logger.git) and the Omnik-export.py (the code in the wiki) file.
Were you able to use both files without adjustment?
Re: Omnik Export Script
Posted: Wednesday 30 March 2016 13:34
by sincze
you got me there.
I did not overwrite the .cfg file indeed. I used my original one.
Code: Select all
################
### Settings ###
################
[inverter]
# IP address of your Omnik inverter
ip = ***.***.***.***
# Default for a Omnik with Wifi module
port = 8899
# S/N of the wifi kit
wifi_sn = 1602******
#use temperature of inverter for pvoutput
use_temperature = true
[mysql]
# Enable for exporting to a mysql database
mysql_enabled = false
# Host where the mysql server is active
mysql_host = 127.0.0.1
mysql_user =
mysql_pass =
mysql_db =
[domoticz]
domoticz_enabled = true
domoticz_host = ***.***.***.***
domoticz_port = 8080
domoticz_url = json.htm
# Provide IDX here of the specific devices
domoticz_temp = ***
domoticz_Input_PV1 = ***
domoticz_Input_PV2 = ***
domoticz_AC_Output = ***
domoticz_Cur_Total = ***
domoticz_AMP_1_2_3 = ***
[pvout]
# Enable or disable uploading to PVoutput
pvout_enabled =false
# These two can be found at http://pvoutput.org/account.jsp
pvout_apikey = NOTAREALAPIKEY86e2258d4e29169fb79cf18b00
pvout_sysid = 12345
[log]
log_enabled = true
log_filename = omnik-export.log
Re: Omnik Export Script
Posted: Wednesday 30 March 2016 15:35
by jbr79
I see you disabled mysql and pv_output - I am wondering if that might change the outcome
I will try this evening.
Re: Omnik Export Script
Posted: Wednesday 30 March 2016 16:48
by sincze
Jups. I don't use MYSQL to separately store the data, so disabled it.
I enabled PVOutput "true" and data is now being exported to PVOutput. Works nicely.
Normally I would have used the wiki for PVOutput upload, but since daylight save change that does not work for me anymore.
http://www.domoticz.com/wiki/Upload_ene ... o_PVoutput
Did not yet figure out why. I do recveive the correct data, however PVOutput doesn't seem to like it anymore.
Code: Select all
2016-03-29 23:25:00.770 LUA: ---- The total generated energy is 14310.0 Wh
2016-03-29 23:25:00.770 LUA: ---- The current generated power is 0 W
2016-03-29 23:25:00.770 LUA: ---- The voltage of the inverter is 0.0 V
2016-03-29 23:25:00.770 LUA: ---- The outside temperature is 9.1 C.
2016-03-29 23:25:00.770 LUA: ---- The total consumed energy is 10761371 Wh
2016-03-29 23:25:00.770 LUA: ---- The current consumed power is 650 W
A well back to the original topic. I issued a python3 command..
Code: Select all
root@PC:/home/domoticz/Omnik-Data-Logger# python3 LiveStats.py
File "LiveStats.py", line 52
print 'could not open socket'
^
SyntaxError: invalid syntax
root@PC:/home/domoticz/Omnik-Data-Logger# python LiveStats.py
Code: Select all
connecting to ***.***.***.*** port 8899
ID: NL**************
E Today: 14.03 Total: 6623.6
H Total: 0 Temp: 38.5
PV1 V: 268.6 I: 3.0
PV2 V: 266.4 I: 3.0
PV3 V: 0.0 I: 0.0
L1 P: 1424 V: 238.8 I: 5.9 F: 50.0
L2 P: 0 V: 0.0 I: 0.0 F: 0.0
L3 P: 0 V: 0.0 I: 0.0 F: 0.0
So some errors do occur when using a different python version.
At least so it seams....
Regular python is version: Python 2.7.3, can you try that one as well?
Re: Omnik Export Script
Posted: Wednesday 30 March 2016 22:00
by jbr79
I know from my previous encounter with Python that Python3 is a different ballgame. Anyway, having access to the file contents now that it is evening, disabling mysql and pvoutput obviously did not do the trick.
I am however curious as to what the contents of your InverterMsg.py file is. Mine does not hold any reference to 'generate_string'. With my limited programming experience I would say there is another clue hidden in that file.
Re: Omnik Export Script
Posted: Wednesday 30 March 2016 22:09
by sincze
Line 78 generate_string of the file InverterMsg.py
Code: Select all
import struct # Converting bytes to numbers
class InverterMsg:
'Class for Inverter message'
rawmsg = ""
def __init__(self, msg, offset=0):
self.rawmsg = msg
self.offset = offset
def __getString(self, begin, end):
return self.rawmsg[begin:end]
def __getShort(self, begin, devider=10):
num = struct.unpack('!H', self.rawmsg[begin:begin+2])[0]
if num == 65535:
return -1
else:
return float(num)/devider
def __getLong(self, begin, devider=10):
return float(struct.unpack('!I', self.rawmsg[begin:begin+4])[0])/devider
def getID(self):
return self.__getString(15,31)
def getTemp(self):
return self.__getShort(31)
def getPower(self):
print self.__getShort(59)
def getETotal(self):
return self.__getLong(71)
def getVPV(self, i=1):
if i not in range(1, 4):
i = 1
num = 33 + (i-1)*2
return self.__getShort(num)
def getIPV(self, i=1):
if i not in range(1, 4):
i=1
num = 39 + (i-1)*2
return self.__getShort(num)
def getIAC(self, i=1):
if i not in range(1, 4):
i=1
num = 45 + (i-1)*2
return self.__getShort(num)
def getVAC(self, i=1):
if i not in range(1, 4):
i=1
num = 51 + (i-1)*2
return self.__getShort(num)
def getFAC(self, i=1):
if i not in range(1, 4):
i=1
num = 57 + (i-1)*4
return self.__getShort(num, 100)
def getPAC(self, i=1):
if i not in range(1, 4):
i=1
num = 59 + (i-1)*4
return int(self.__getShort(num, 1)) # Don't divide
def getEToday(self):
return self.__getShort(69, 100) # Devide by 100
def getHTotal(self):
return int(self.__getLong(75, 1)) # Don't divide
def generate_string(ser):
'''
The request string is build from several parts. The first part is a
fixed 4 char string; the second part is the reversed hex notation of
the s/n twice; then again a fixed string of two chars; a checksum of
the double s/n with an offset; and finally a fixed ending char.
'''
responseString = '\x68\x02\x40\x30';
doublehex = hex(ser)[2:]*2
hexlist = [ doublehex[i:i+2].decode('hex') for i in
reversed(range(0, len(doublehex), 2))]
cs_count = 115 + sum([ ord(c) for c in hexlist])
cs = hex(cs_count)[-2:].decode('hex')
responseString += ''.join(hexlist) + '\x01\x00'+cs+'\x16'
return responseString
Re: Omnik Export Script
Posted: Thursday 31 March 2016 9:06
by jbr79
Is that the entire contents?
Mine is different (see below).Where did you get your file from?
pi@raspberrypi:~/Omnik-Data-Logger $ cat InverterMsg.py
Code: Select all
import struct # Converting bytes to numbers
class InverterMsg(object):
"""Decode the response message from an omniksol inverter."""
raw_msg = ""
def __init__(self, msg, offset=0):
self.raw_msg = msg
self.offset = offset
def __get_string(self, begin, end):
"""Extract string from message.
Args:
begin (int): starting byte index of string
end (int): end byte index of string
Returns:
str: String in the message from start to end
"""
return self.raw_msg[begin:end]
def __get_short(self, begin, divider=10):
"""Extract short from message.
The shorts in the message could actually be a decimal number. This is
done by storing the number multiplied in the message. So by dividing
the short the original decimal number can be retrieved.
Args:
begin (int): index of short in message
divider (int): divider to change short to float. (Default: 10)
Returns:
int or float: Value stored at location `begin`
"""
num = struct.unpack('!H', self.raw_msg[begin:begin + 2])[0]
if num == 65535:
return -1
else:
return float(num) / divider
def __get_long(self, begin, divider=10):
"""Extract long from message.
The longs in the message could actually be a decimal number. By
dividing the long, the original decimal number can be extracted.
Args:
begin (int): index of long in message
divider (int): divider to change long to float. (Default : 10)
Returns:
int or float: Value stored at location `begin`
"""
return float(
struct.unpack('!I', self.raw_msg[begin:begin + 4])[0]) / divider
@property
def id(self):
"""ID of the inverter."""
return self.__get_string(15, 31)
@property
def temperature(self):
"""Temperature recorded by the inverter."""
return self.__get_short(31)
@property
def power(self):
"""Power output"""
print self.__get_short(59)
@property
def e_total(self):
"""Total energy generated by inverter in kWh"""
return self.__get_long(71)
def v_pv(self, i=1):
"""Voltage of PV input channel.
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): input channel (valid values: 1, 2, 3)
Returns:
float: PV voltage of channel i
"""
if i not in range(1, 4):
i = 1
num = 33 + (i - 1) * 2
return self.__get_short(num)
def i_pv(self, i=1):
"""Current of PV input channel.
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): input channel (valid values: 1, 2, 3)
Returns:
float: PV current of channel i
"""
if i not in range(1, 4):
i = 1
num = 39 + (i - 1) * 2
return self.__get_short(num)
def i_ac(self, i=1):
"""Current of the Inverter output channel
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): output channel (valid values: 1, 2, 3)
Returns:
float: AC current of channel i
"""
if i not in range(1, 4):
i = 1
num = 45 + (i - 1) * 2
return self.__get_short(num)
def v_ac(self, i=1):
"""Voltage of the Inverter output channel
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): output channel (valid values: 1, 2, 3)
Returns:
float: AC voltage of channel i
"""
if i not in range(1, 4):
i = 1
num = 51 + (i - 1) * 2
return self.__get_short(num)
def f_ac(self, i=1):
"""Frequency of the output channel
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): output channel (valid values: 1, 2, 3)
Returns:
float: AC frequency of channel i
"""
if i not in range(1, 4):
i = 1
num = 57 + (i - 1) * 4
return self.__get_short(num, 100)
def p_ac(self, i=1):
"""Power output of the output channel
Available channels are 1, 2 or 3; if no tin this range the function will
default to channel 1.
Args:
i (int): output channel (valid values: 1, 2, 3)
Returns:
float: Power output of channel i
"""
if i not in range(1, 4):
i = 1
num = 59 + (i - 1) * 4
return int(self.__get_short(num, 1)) # Don't divide
@property
def e_today(self):
"""Energy generated by inverter today in kWh"""
return self.__get_short(69, 100) # Divide by 100
@property
def h_total(self):
"""Hours the inverter generated electricity"""
return int(self.__get_long(75, 1)) # Don't divide
Re: Omnik Export Script
Posted: Thursday 31 March 2016 13:36
by sincze
Euh indeed that is my entire content

of my document indeed.
However it seems to work.
Maybe exchange yours for mine and see what happens ??
Re: Omnik Export Script
Posted: Thursday 31 March 2016 22:39
by jbr79
That would have been too easy; failure at first sight:
pi@RPi3:~/domoticz/scripts/python/Omnik-Data-Logger $ python LiveStats.py
Traceback (most recent call last):
File "LiveStats.py", line 7, in <module>
import OmnikExport
ImportError: No module named OmnikExport
I saw there was a staticmethod available in the original OmnikExport.py module. Tried copying that to the InverterMSg.py file, but without success. Same error message:
Code: Select all
pi@RPi3:~/domoticz/scripts/python/Omnik-Data-Logger $ python Export.py
Traceback (most recent call last):
File "Export.py", line 83, in <module>
s.sendall(InverterMsg.generate_string(wifi_serial))
AttributeError: 'module' object has no attribute 'generate_string'
I did not get around to interchanging the generate_string def from your file pasted above with my own InverterMsg.py - will have to find time somewhere this weekend.
Re: Omnik Export Script
Posted: Tuesday 05 April 2016 19:45
by jbr79
Seems there is a lot more to be done to get this script up and running. I find myself replacing one function after another, but without success. It keeps producing a new error every time. Call me naive, but I fail to see why there seem to be two versions of the same file out in the open. One being completely incompatible.
Re: Omnik Export Script
Posted: Tuesday 05 April 2016 23:46
by edsirtan
I also was struggling to get this InverterMsg.py working. I am not a programmer merely I cutted, pasted, trial and errored a lot and I have now a working solution for my situation. Maybe someone with more python skills can make it work for all situations, because with this script the Livestats do not work anymore. In short I added the config of the old file in the new one
And the virtual sensors are now filled with data.
Code: Select all
pi@raspberrypi:~/domoticz/Omnik-Data-Logger$ cat InverterMsg.py
import struct # Converting bytes to numbers
class InverterMsg(object):
"""Decode the response message from an omniksol inverter."""
raw_msg = ""
def __init__(self, msg, offset=0):
self.raw_msg = msg
self.offset = offset
def __get_string(self, begin, end):
"""Extract string from message.
Args:
begin (int): starting byte index of string
end (int): end byte index of string
Returns:
str: String in the message from start to end
"""
return self.raw_msg[begin:end]
def __get_short(self, begin, divider=10):
"""Extract short from message.
The shorts in the message could actually be a decimal number. This is
done by storing the number multiplied in the message. So by dividing
the short the original decimal number can be retrieved.
Args:
begin (int): index of short in message
divider (int): divider to change short to float. (Default: 10)
Returns:
int or float: Value stored at location `begin`
"""
num = struct.unpack('!H', self.raw_msg[begin:begin + 2])[0]
if num == 65535:
return -1
else:
return float(num) / divider
def __get_long(self, begin, divider=10):
"""Extract long from message.
The longs in the message could actually be a decimal number. By
dividing the long, the original decimal number can be extracted.
Args:
begin (int): index of long in message
divider (int): divider to change long to float. (Default : 10)
Returns:
int or float: Value stored at location `begin`
"""
return float(
struct.unpack('!I', self.raw_msg[begin:begin + 4])[0]) / divider
@property
def id(self):
"""ID of the inverter."""
return self.__get_string(15, 31)
@property
def temperature(self):
"""Temperature recorded by the inverter."""
return self.__get_short(31)
@property
def power(self):
"""Power output"""
print self.__get_short(59)
@property
def e_total(self):
"""Total energy generated by inverter in kWh"""
return self.__get_long(71)
def v_pv(self, i=1):
"""Voltage of PV input channel.
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): input channel (valid values: 1, 2, 3)
Returns:
float: PV voltage of channel i
"""
if i not in range(1, 4):
i = 1
num = 33 + (i - 1) * 2
return self.__get_short(num)
def i_pv(self, i=1):
"""Current of PV input channel.
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): input channel (valid values: 1, 2, 3)
Returns:
float: PV current of channel i
"""
if i not in range(1, 4):
i = 1
num = 39 + (i - 1) * 2
return self.__get_short(num)
def i_ac(self, i=1):
"""Current of the Inverter output channel
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): output channel (valid values: 1, 2, 3)
Returns:
float: AC current of channel i
"""
if i not in range(1, 4):
i = 1
num = 45 + (i - 1) * 2
return self.__get_short(num)
def v_ac(self, i=1):
"""Voltage of the Inverter output channel
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): output channel (valid values: 1, 2, 3)
Returns:
float: AC voltage of channel i
"""
if i not in range(1, 4):
i = 1
num = 51 + (i - 1) * 2
return self.__get_short(num)
def f_ac(self, i=1):
"""Frequency of the output channel
Available channels are 1, 2 or 3; if not in this range the function will
default to channel 1.
Args:
i (int): output channel (valid values: 1, 2, 3)
Returns:
float: AC frequency of channel i
"""
if i not in range(1, 4):
i = 1
num = 57 + (i - 1) * 4
return self.__get_short(num, 100)
def p_ac(self, i=1):
"""Power output of the output channel
Available channels are 1, 2 or 3; if no tin this range the function will
default to channel 1.
Args:
i (int): output channel (valid values: 1, 2, 3)
Returns:
float: Power output of channel i
"""
if i not in range(1, 4):
i = 1
num = 59 + (i - 1) * 4
return int(self.__get_short(num, 1)) # Don't divide
@property
def e_today(self):
"""Energy generated by inverter today in kWh"""
return self.__get_short(69, 100) # Divide by 100
@property
def h_total(self):
"""Hours the inverter generated electricity"""
return int(self.__get_long(75, 1)) # Don't divide
def __init__(self, msg, offset=0):
self.rawmsg = msg
self.offset = offset
def __getString(self, begin, end):
return self.rawmsg[begin:end]
def __getShort(self, begin, devider=10):
num = struct.unpack('!H', self.rawmsg[begin:begin+2])[0]
if num == 65535:
return -1
else:
return float(num)/devider
def __getLong(self, begin, devider=10):
return float(struct.unpack('!I', self.rawmsg[begin:begin+4])[0])/devider
def getID(self):
return self.__getString(15,31)
def getTemp(self):
return self.__getShort(31)
def getPower(self):
print self.__getShort(59)
def getETotal(self):
return self.__getLong(71)
def getVPV(self, i=1):
if i not in range(1, 4):
i = 1
num = 33 + (i-1)*2
return self.__getShort(num)
def getIPV(self, i=1):
if i not in range(1, 4):
i=1
num = 39 + (i-1)*2
return self.__getShort(num)
def getIAC(self, i=1):
if i not in range(1, 4):
i=1
num = 45 + (i-1)*2
return self.__getShort(num)
def getVAC(self, i=1):
if i not in range(1, 4):
i=1
num = 51 + (i-1)*2
return self.__getShort(num)
def getFAC(self, i=1):
if i not in range(1, 4):
i=1
num = 57 + (i-1)*4
return self.__getShort(num, 100)
def getPAC(self, i=1):
if i not in range(1, 4):
i=1
num = 59 + (i-1)*4
return int(self.__getShort(num, 1)) # Don't divide
def getEToday(self):
return self.__getShort(69, 100) # Devide by 100
def getHTotal(self):
return int(self.__getLong(75, 1)) # Don't divide
def generate_string(ser):
'''
The request string is build from several parts. The first part is a
fixed 4 char string; the second part is the reversed hex notation of
the s/n twice; then again a fixed string of two chars; a checksum of
the double s/n with an offset; and finally a fixed ending char.
'''
responseString = '\x68\x02\x40\x30';
doublehex = hex(ser)[2:]*2
hexlist = [ doublehex[i:i+2].decode('hex') for i in
reversed(range(0, len(doublehex), 2))]
cs_count = 115 + sum([ ord(c) for c in hexlist])
cs = hex(cs_count)[-2:].decode('hex')
responseString += ''.join(hexlist) + '\x01\x00'+cs+'\x16'
return responseString
Re: Omnik Export Script
Posted: Tuesday 19 April 2016 21:02
by jbr79
@sincze: would you be able to share your InverterMsg.py , Export.py and LiveStats.py files?
( or for that matter, your anonimized folder) I wonder if I have any luck with them instead of trying to alter my current ones.
Re: Omnik Export Script
Posted: Thursday 19 May 2016 21:02
by sincze
No Problem.
Please find attached the files you requested.
Let me know the results please

Re: Omnik Export Script
Posted: Sunday 22 May 2016 13:59
by jbr79
FINALLY: SUCCESS!!!
Big Thanks goes out to
sincze for sharing the files!

The solution was not in them alone, but they got me started.
So we've established there were differences in the python files. My starting point was that I could get LiveStats.py to work perfectly in console, but OmnikExport.py would generate new errors every time (so you fix, you fall right in the other).
I found out there are two isolated incidents:
- first, the git cloned files have different function names (especially in InverterMsg.py)
- there is (was - I corrected it to prevent more grey hair) an awfully crappy error in the instructions on the Wiki. The config file holds an extra space that is preventing the correct encoding of the URL!
My final solution:
- git clone the files from git clone https://github.com/Woutrrr/Omnik-Data-Logger.git
- generate the config.cfg file with my settings, and add the lines from the config in the wiki
- test the LiveStats.py file. This should generate output (otherwise it won't work at all with your Converter)
- Create a file called Export.py with the contents of the instructions for the OmnikExport.py file on the wiki, but initially also added "import OmnikExport" at the top (this was removed later)
- used the InverterMsg.py file from sincze to copy all missing code (so what is not originally in the git clone)
- do a a little cleanup of the InverterMsg.py file. There's functions that expect for example getString while it is called get_string in the file.
Keep checking after each edit that LiveStats.py is still working and run Export.py.
I can't believe it actually works!

Re: Omnik Export Script
Posted: Sunday 22 May 2016 15:30
by sincze