-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bugfix bei Headerlänge / Neu: FunctionCall für Energiebilanz #4
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,88 +21,108 @@ | |
import logging | ||
|
||
|
||
class viCommandException(Exception): | ||
pass | ||
# Commandsets for different heatings | ||
# YOU MUST CHANGE below in class viCommand the attribute "commandset" for your heating | ||
|
||
# Predifined Commandsets for Heatings: | ||
|
||
class viCommand(bytearray): | ||
# the commands | ||
# viCommand object value is a bytearray of addr and len | ||
VITOCAL_WO1C = { | ||
# All Parameters are tested and working on Vitocal 200S WO1C (Baujahr 2019) | ||
# All Parameters are tested and working on Vitocal 333G (Baujahr 2014) | ||
|
||
# TODO: statt 'write':False besser mode:rw/w verwenden | ||
# ------ Statusinfos (read only) ------ | ||
|
||
commandset = { | ||
# All Parameters are tested and working on Vitocal 200S WO1C (Baujahr 2019) | ||
# Warmwasser: Warmwassertemperatur oben (0..95) | ||
'Warmwassertemperatur': {'addr': '010d', 'len': 2, 'unit': 'IS10'}, | ||
|
||
# ------ Statusinfos (read only) ------ | ||
# Aussentemperatur (-40..70) | ||
'Aussentemperatur': {'addr': '0101', 'len': 2, 'unit': 'IS10'}, | ||
|
||
# Warmwasser: Warmwassertemperatur oben (0..95) | ||
'Warmwassertemperatur': {'addr': '010d', 'len': 2, 'unit': 'IS10', 'write': False}, | ||
# Heizkreis HK1: Vorlauftemperatur Sekundaer 1 (0..95) | ||
'VorlauftempSek': {'addr': '0105', 'len': 2, 'unit': 'IS10'}, | ||
|
||
# Aussentemperatur (-40..70) | ||
'Aussentemperatur': {'addr': '0101', 'len': 2, 'unit': 'IS10', 'write': False}, | ||
# Ruecklauftemperatur Sekundaer 1 (0..95) | ||
'RuecklauftempSek': {'addr': '0106', 'len': 2, 'unit': 'IS10'}, | ||
|
||
# Heizkreis HK1: Vorlauftemperatur Sekundaer 1 (0..95) | ||
'VorlauftempSek': {'addr': '0105', 'len': 2, 'unit': 'IS10', 'write': False}, | ||
# Sekundaerpumpe [%] (including one status byte) | ||
'Sekundaerpumpe': {'addr': 'B421', 'len': 2, 'unit': 'IUNON'}, | ||
|
||
# Ruecklauftemperatur Sekundaer 1 (0..95) | ||
'RuecklauftempSek': {'addr': '0106', 'len': 2, 'unit': 'IS10', 'write': False}, | ||
# Faktor Energiebilanz(1 = 0.1kWh, 10 = 1kWh, 100 = 10kWh) | ||
'FaktorEnergiebilanz': {'addr': '163F', 'len': 1, 'unit': 'IUNON'}, | ||
|
||
# Sekundaerpumpe [%] (including one status byte) | ||
'Sekundaerpumpe': {'addr': 'B421', 'len': 2, 'unit': 'IUNON', 'write': False}, | ||
# Heizwärme "Heizbetrieb", Verdichter 1 | ||
'Heizwaerme': {'addr': '1640', 'len': 4, 'unit': 'IUNON'}, | ||
|
||
# Faktor Energiebilanz(1 = 0.1kWh, 10 = 1kWh, 100 = 10kWh) | ||
'FaktorEnergiebilanz': {'addr': '163F', 'len': 1, 'unit': 'IUNON', 'write': False}, | ||
# Elektroenergie "Heizbetrieb", Verdichter 1 | ||
'Heizenergie': {'addr': '1660', 'len': 4, 'unit': 'IUNON'}, | ||
|
||
# Heizwärme "Heizbetrieb", Verdichter 1 | ||
'Heizwaerme': {'addr': '1640', 'len': 4, 'unit': 'IUNON', 'write': False}, | ||
# Heizwärme "WW-Betrieb", Verdichter 1 | ||
'WWwaerme': {'addr': '1650', 'len': 4, 'unit': 'IUNON'}, | ||
|
||
# Elektroenergie "Heizbetrieb", Verdichter 1 | ||
'Heizenergie': {'addr': '1660', 'len': 4, 'unit': 'IUNON', 'write': False}, | ||
# Elektroenergie "WW-Betrieb", Verdichter 1 | ||
'WWenergie': {'addr': '1670', 'len': 4, 'unit': 'IUNON'}, | ||
|
||
# Heizwärme "WW-Betrieb", Verdichter 1 | ||
'WWwaerme': {'addr': '1650', 'len': 4, 'unit': 'IUNON', 'write': False}, | ||
# Verdichter [%] (including one status byte) | ||
'Verdichter': {'addr': 'B423', 'len': 4, 'unit': 'IUNON'}, | ||
|
||
# Elektroenergie "WW-Betrieb", Verdichter 1 | ||
'WWenergie': {'addr': '1670', 'len': 4, 'unit': 'IUNON', 'write': False}, | ||
# Druck Sauggas [bar] (including one status byte) - Kühlmittel | ||
'DruckSauggas': {'addr': 'B410', 'len': 3, 'unit': 'IS10'}, | ||
|
||
# Verdichter [%] (including one status byte) | ||
'Verdichter': {'addr': 'B423', 'len': 4, 'unit': 'IUNON', 'write': False}, | ||
# Druck Heissgas [bar] (including one status byte)- Kühlmittel | ||
'DruckHeissgas': {'addr': 'B411', 'len': 3, 'unit': 'IS10'}, | ||
|
||
# Druck Sauggas [bar] (including one status byte) - Kühlmittel | ||
'DruckSauggas': {'addr': 'B410', 'len': 3, 'unit': 'IS10', 'write': False}, | ||
# Temperatur Sauggas [bar] (including one status byte)- Kühlmittel | ||
'TempSauggas': {'addr': 'B409', 'len': 3, 'unit': 'IS10'}, | ||
|
||
# Druck Heissgas [bar] (including one status byte)- Kühlmittel | ||
'DruckHeissgas': {'addr': 'B411', 'len': 3, 'unit': 'IS10', 'write': False}, | ||
# Temperatur Heissgas [bar] (including one status byte)- Kühlmittel | ||
'TempHeissgas': {'addr': 'B40A', 'len': 3, 'unit': 'IS10'}, | ||
|
||
# Temperatur Sauggas [bar] (including one status byte)- Kühlmittel | ||
'TempSauggas': {'addr': 'B409', 'len': 3, 'unit': 'IS10', 'write': False}, | ||
# Anlagentyp (muss 204D sein) | ||
'Anlagentyp': {'addr': '00F8', 'len': 2, 'unit': 'DT'}, | ||
|
||
# Temperatur Heissgas [bar] (including one status byte)- Kühlmittel | ||
'TempHeissgas': {'addr': 'B40A', 'len': 3, 'unit': 'IS10', 'write': False}, | ||
# --------- Menüebene ------- | ||
|
||
# Anlagentyp (muss 204D sein) | ||
'Anlagentyp': {'addr': '00F8', 'len': 4, 'unit': 'DT', 'write': False}, | ||
# getManuell / setManuell -- 0 = normal, 1 = manueller Heizbetrieb, 2 = 1x Warmwasser auf Temp2 | ||
'WWeinmal': {'addr': 'B020', 'len': 1, 'unit': 'OO', 'write': True}, | ||
|
||
# --------- Menüebene ------- | ||
# Warmwassersolltemperatur (10..60 (95)) | ||
'SolltempWarmwasser': {'addr': '6000', 'len': 2, 'unit': 'IS10', 'write': True, 'min_value': 10, | ||
'max_value': 60}, | ||
|
||
# getManuell / setManuell -- 0 = normal, 1 = manueller Heizbetrieb, 2 = 1x Warmwasser auf Temp2 | ||
'WWeinmal': {'addr': 'B020', 'len': 1, 'unit': 'OO', 'write': True}, | ||
# --------- Codierebene 2 --------- | ||
|
||
# Warmwassersolltemperatur (10..60 (95)) | ||
'SolltempWarmwasser': {'addr': '6000', 'len': 2, 'unit': 'IS10', 'write': True, 'min_value': 10, | ||
'max_value': 60}, | ||
# Hysterese Vorlauf ein: Verdichter schaltet im Heizbetrieb ein | ||
'Hysterese_Vorlauf_ein': {'addr': '7304', 'len': 2, 'unit': 'IU10', 'write': True}, | ||
|
||
# --------- Codierebene 2 --------- | ||
# Hysterese Vorlauf aus: Verdichter schaltet im Heizbetrieb ab | ||
'Hysterese_Vorlauf_aus': {'addr': '7313', 'len': 2, 'unit': 'IU10', 'write': True}, | ||
|
||
# Hysterese Vorlauf ein: Verdichter schaltet im Heizbetrieb ein | ||
'Hysterese_Vorlauf_ein': {'addr': '7304', 'len': 2, 'unit': 'IU10', 'write': True}, | ||
|
||
# Hysterese Vorlauf aus: Verdichter schaltet im Heizbetrieb ab | ||
'Hysterese_Vorlauf_aus': {'addr': '7313', 'len': 2, 'unit': 'IU10', 'write': True} | ||
# Funktion Call für Energiebilanz | ||
'Energiebilanz': {'addr': 'B800', 'len': 16, 'unit': 'F_E', 'func': True} | ||
|
||
# vo = viControl(port='/dev/optolink') | ||
# vo.initComm() | ||
# print( vo.execFunctionCall('Energiebilanz', 2,2).value ) | ||
|
||
} | ||
|
||
class viCommandException(Exception): | ||
pass | ||
|
||
|
||
class viCommand(bytearray): | ||
# the commands | ||
# viCommand object value is a bytearray of addr and len | ||
|
||
# TODO: statt 'write':False besser mode:rw/w verwenden -- verbessert: es gibt read, write oder func, wenn nichts vorhanden ist | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vorschlag: 'Hysterese_Vorlauf_aus': {'addr': '7313', 'len': 2, 'unit': 'IU10', 'accessmode': 'write'}, |
||
# ist es immer read, bei Write ist es read und Write und bei Func nur Func | ||
|
||
# ============================================================= | ||
# CHANGE YOUR COMMANDSET HERE: | ||
commandset = VITOCAL_WO1C | ||
# ============================================================= | ||
|
||
} | ||
|
||
def __init__(self, cmdname): | ||
# FIXME: uniform naming of private and public properties | ||
|
@@ -114,11 +134,16 @@ def __init__(self, cmdname): | |
self.__cmdcode__ = cs['addr'] | ||
self.__valuebytes__ = cs['len'] | ||
self.unit = cs['unit'] | ||
self.write = cs['write'] | ||
self.write = cs.get('write', False) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. self.accessmode = cs.get('accessmode') |
||
self.function= cs.get('func', False) | ||
self.cmdname = cmdname | ||
|
||
# create bytearray representation | ||
b = bytes.fromhex(self.__cmdcode__) + self.__valuebytes__.to_bytes(1, 'big') | ||
b = bytes.fromhex(self.__cmdcode__) | ||
if not self.function: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if self.accessmode != "function" |
||
b = b + self.__valuebytes__.to_bytes(1, 'big') | ||
# else: function call don't have length bytes! | ||
|
||
super().__init__(b) | ||
|
||
@classmethod | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ | |
# - execWriteCmd: execute write Command | ||
# viSerial: Low-level interface | ||
|
||
from ast import arg | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wo wird arg verwendet? |
||
from pyvcontrol.viCommand import viCommand | ||
from pyvcontrol.viTelegram import viTelegram | ||
from pyvcontrol.viData import viData | ||
|
@@ -65,12 +66,18 @@ def __del__(self): | |
# destructor, releases serial port | ||
self.vs.disconnect() | ||
|
||
# FIXME: die 3 exec-Methoden refaktorieren ... das sie sehr ähnlich sind | ||
|
||
def execReadCmd(self, cmdname) -> viData: | ||
# sends a read command and gets the response. | ||
vc = viCommand(cmdname) # create command | ||
|
||
if vc.function: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if vc.accessmode == 'function': |
||
raise viControlException(f'command {cmdname} is not readable, because it is a function call') | ||
|
||
vt = viTelegram(vc, 'read') # create read Telegram | ||
|
||
logging.debug(f'Send telegram {vt.hex()}') | ||
logging.debug(f'Send telegram {vt.hex(" ")}') | ||
self.vs.send(vt) # send Telegram | ||
|
||
# Check if sending was successfull | ||
|
@@ -122,6 +129,48 @@ def execWriteCmd(self, cmdname, value) -> viData: | |
|
||
return viData.create(vt.vicmd.unit, vt.payload) # return viData object from payload | ||
|
||
def execFunctionCall(self, cmdname, *function_args) -> viData: | ||
# call function see: https://github.com/openv/openv/wiki/FunctionCalls-WO1C | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was wird über die function_args an die Anlage geschickt? |
||
# not tested ... does not work with Vitocal333G | ||
|
||
vc = viCommand(cmdname) | ||
if not vc.function: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if vc.accessmode != 'function': |
||
raise viControlException(f'command {cmdname} is not a function call') | ||
|
||
# create viData object | ||
vd = viData.create(vc.unit) | ||
# create write Telegram | ||
vt = viTelegram(vc, 'call', 'Request', bytearray((len(function_args),*function_args))) | ||
# send Telegram | ||
logging.debug(f'Send telegram {vt.hex(" ")}') | ||
self.vs.send(vt) | ||
|
||
# Check if sending was successfull | ||
ack = self.vs.read(1) | ||
logging.debug(f'Received {ack.hex()}') | ||
if ack != ctrlcode['acknowledge']: | ||
raise viControlException(f'Expected acknowledge byte, received {ack}') | ||
|
||
# Receive response and evaluate data | ||
header = self.vs.read(2) #read the header ... the read the length received in the header | ||
logging.debug(f'Requested 2 (0x41 + length) bytes. Received telegram {header.hex()}') | ||
|
||
l = viTelegram.checkStartByteAnGetLength(header) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vorschlag: viTelegramLength=viTelegram.length() |
||
body = self.vs.read(l+1) #read the response (length + checksum) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l und 1 sehen nahezu gleich aus. |
||
|
||
vr = header+body | ||
|
||
# receive response | ||
logging.debug(f'Requested {l+1} bytes. Received telegram {vr.hex()}') | ||
|
||
self.vs.send(ctrlcode['acknowledge']) # send acknowledge | ||
|
||
vt = viTelegram.frombytes(vr) # create response Telegram | ||
if vt.tType == viTelegram.tTypes['error']: | ||
raise viControlException('Function Call returned ERROR') | ||
|
||
return viData.create(vt.vicmd.unit, vt.payload) # return viData object from payload | ||
|
||
def initComm(self): | ||
logging.debug('Init Communication to viControl....') | ||
self.isInitialized = False | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,10 @@ | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
# ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## | ||
|
||
from email import header | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wo wird header verwendet? |
||
import logging | ||
from collections import namedtuple | ||
from struct import unpack | ||
|
||
class viDataException(Exception): | ||
def __init__(self,msg): | ||
|
@@ -94,7 +97,7 @@ def create(cls,type,*args): | |
logging.debug(f'Data factory: request to produce Data type {type} with args {args}') | ||
datatype_object={'BA':viDataBA, 'DT':viDataDT, 'IS10':viDataIS10,'IU10':viDataIU10, | ||
'IU3600':viDataIU3600,'IUNON':viDataIUNON, 'RT':viDataRT, 'OO':viDataOO, | ||
'ES':viDataES, | ||
'ES':viDataES, 'F_E': viDataEnergy, | ||
} | ||
if type in datatype_object.keys(): | ||
return datatype_object[type](*args) | ||
|
@@ -246,6 +249,7 @@ class viDataDT(viData): | |
0x2094: 'V200KW1, Protokoll: KW2', | ||
0x209F: 'V200KO1B, Protokoll: P300, KW2', | ||
0x204D: 'V200WO1C, Protokoll: P300', | ||
0x204B: 'Vitocal 333G, Protokoll: P300', | ||
0x20B8: 'V333MW1, Protokoll: ', | ||
0x20A0: 'V100GC1, Protokoll: ', | ||
0x20C2: 'VDensHO1, Protokoll: ', | ||
|
@@ -417,6 +421,54 @@ def __fromraw__(self,value): | |
def value(self): | ||
return self.OnOff[int.from_bytes(self, 'big')] | ||
|
||
|
||
# holder type for Energy | ||
Energy = namedtuple('Energy', 'byte1 day year week heating_energy heating_electrical_energy water_energy water_electrical_energy, cooling_energy cooling_electrical_energy total_energy total_eletrical_energy') | ||
|
||
class viDataEnergy(viData): | ||
#Energy-Type ... Return from Function-Call B800 | ||
# see https://github.com/openv/openv/issues/480#issuecomment-712235475 | ||
# see https://github.com/openv/openv/wiki/FunctionCalls-WO1C | ||
unit={'code':'F_E','description': 'returns Named Tuple with enery Data','unit':''}, | ||
|
||
def __init__(self, value=b'\x00\x00',len=16): | ||
#sets int representation based on input value | ||
self.len=len #length in bytes | ||
super().__init__(value) | ||
|
||
def __fromtype__(self,value): | ||
raise viDataException(f'Value not setable from Type') | ||
|
||
@property | ||
def value(self) -> Energy: | ||
# decode the Result Record an return a named tuple | ||
# see links above | ||
|
||
# example 02 02 16 09 92 03 aa 00 99 00 2d 00 00 00 00 00 | ||
raw = unpack('<4B6H', self) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wofür steht '<4B6H' |
||
|
||
res = Energy( | ||
raw[0],raw[1],2000+raw[2],raw[3], | ||
raw[4]*0.1, raw[5]*0.1, #heating | ||
raw[6]*0.1, raw[7]*0.1, #water | ||
raw[8]*0.1, raw[9]*0.1, #cooling ??? | ||
raw[4]*0.1+raw[6]*0.1+raw[8]*0.1, raw[5]*0.1+raw[7]*0.1+raw[9]*0.1 #total | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Woher kommen die Konstanten? |
||
|
||
return res | ||
|
||
@property | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Diese Funktion gehört m.E. thematisch zu viTools, nicht zu viData, da sie nur in dem speziellen Kontext verwendet wird. |
||
def valueScan(self): | ||
# special property for scanning a function Call, see viTools | ||
# for function call B800: it seems that the first for bytes are a header | ||
# extract the header an try to decode the body as signed short | ||
header = unpack('<4B', self[0:4]) | ||
body = unpack(f'<{len(self[4:])//2}h', self[4:]) | ||
bodyDiv10 = [x*0.1 for x in body] | ||
|
||
return f"{self.hex(' ')} -- {header} - {body} / {bodyDiv10}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vorschlag: Selbsterklärender Ausgabetext |
||
|
||
|
||
systemschemes = { | ||
'01': 'WW', | ||
'02': 'HK + WW', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vorschlag: Änderungshistorie nur in den Commit-Messages pflegen, mögliche Erweiterungen als github issues.