Skip to content

Instantly share code, notes, and snippets.

@orcunbaslak
Created October 24, 2020 19:32
Show Gist options
  • Save orcunbaslak/3cf12a4596e971e0f5a98012dcceaa62 to your computer and use it in GitHub Desktop.
Save orcunbaslak/3cf12a4596e971e0f5a98012dcceaa62 to your computer and use it in GitHub Desktop.
abb_pvs800_tcp_modbustk.py
import os
import time
import struct
from datetime import datetime
from collections import OrderedDict
import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_tcp
import logging
log = logging.getLogger('solarian-datalogger')
DRIVER_NAME = 'ABB_PVS800_TCP'
DRIVER_VERSION = '0.1'
MODBUS_TIMEOUT = 3
TRY_AMOUNT = 10
module_name = os.path.splitext(os.path.basename(__file__))[0]
def get_data(ip_address, port, slave_id, device_name):
"""Instrument class for ABB PVS800 Inverter.
Args:
* ip_address (str): ip address of the TCP device
* port (int): port
Modbus Addresses of the device are as follows:
42496 Heartbeat uint16 0
42497 Inverter main status word 1 bf16
42498 Active power 2 int16 1 kW
42499 Reactive power * 3 int16 1 kVAR
42500 Grid voltage 4 uint16 0.1 V
42501 Grid frequency 5 uint16 0.01 Hz
42502 Power factor 6 int16 0.001
42545 Code of the active fault 0 uint16 1
42546 Main voltage, U-V 1 uint16 0.1 V
42547 Main voltage, V-W 2 uint16 0.1 V
42548 Main voltage W-U 3 uint16 0.1 V
42549 Grid current 4 uint16 0.1 V
42550 DC input voltage 5 uint16 0.1 V
42551 DC bus voltage 6 uint16 0.1 V
42552 DC input current 7 uint16 0.1 A
42553 Grounding current3 8 uint16 1 mA
42554 Isolation resistance3 9 uint16 1 kOhm
42555 Ambient temperature 10 int16 0.1 °C
42556 Highest IGBT temperature, PU1 11 int16 0.1 °C
42557 Highest IGBT temperature, PU21 12 int16 0.1 °C
42558 Highest IGBT temperature, PU31 13 int16 0.1 °C
42559 Highest IGBT temperature, PU41 14 int16 0.1 °C
42560 Control section temperature 15 int16 0.1 °C
42561 UNUSED 16
42562 UNUSED 17
42563 UNUSED 18
42564 Daily kWh supplied2, HIGH ORDER BIT 19 uint16 0.1 kWh
42565 Daily kWh supplied2, LOW ORDER BIT 20 uint16 0.1 kWh -USE THIS-
42566 Total kWh supplied2, HIGH ORDER BIT 21 uint16 1 kWh
42567 Total kWh supplied2, LOW ORDER BIT 22 uint16 1 kWh -USE THIS-
42568 Daily kVAh supplied2, HIGH ORDER BIT 23 uint16 0.1 kVAh
42569 Daily kVAh supplied2, LOW ORDER BIT 24 uint16 0.1 kVAh -USE THIS-
42570 Total kVAh supplied2, HIGH ORDER BIT 25 uint16 1 kVAh
42571 Total kVAh supplied2, LOW ORDER BIT 26 uint16 1 kVAh -USE THIS-
42599 Inverter main Status Word 0 bf16
42600 Limitation status word 1 bf16
42601 UNUSED 2
42602 MPPT Status Word 3 bf16
42603 Grid Status Word 4 bf16
42604 Fan Status Word 5 bf16
42605 UNUSED 6
42606 Environmental Status Word 7 bf16
========= INVERTER MAIN STATUS WORD (42497) =========
0 Ready to switch on
1 Faulted
2 Warning
3 MPPT Enabled
4 Grid Stable
5 DC Voltage within running limits
6 Start inhibited
7 Reduced run
8 Redundant run
9 Q-Compensation
10 Limited
11 Grid Connected
========= LIMITATION STATUS WORD (42600) =========
0 IGBT Temp current limitation
1 P(f) limitation
2 P(U) limitation
3 Grid fault and connect limitation
4 External power limitation
5 FRT recovery limitation
6 Shutdown ramp limitation
7 Power gradient limitation
8 FRT Interaction
9 Ambient temperature current limitation
10 RESERVED
11 Power section temperature limitation
========= MPPT STATUS WORD (42602) =========
0 MPPT Mode (0:LowPower - 1:NormalOperation)
1 Power Limitation Active
2 Minimum voltage limit active
3 Maximum voltage limit active
========= GRID STATUS WORD (42603) =========
0 Undervoltage
1 Overvoltage
2 Underfrequency
3 Overfrequency
4 Anti-Islanding Trip
5 RoCoF Trip
6 Combinatory Trip
7 Moving average trip
8 Zero crossing trip
9 LVRT Trip
10 HVRT Trip
11 External monitor trip
========= FAN STATUS WORD (42604) =========
0 Power Unit 1
1 Power Unit 2
2 Power Unit 3
3 Power Unit 4
4 ISU1 Fan
5 ISU2 Fan
6 Door fan circuit breaker
========= ENVIRONMENTAL STATUS WORD (42606) =========
0 AC busbar thermal protection active
1 DC busbar thermal protection active
2 Cold ambient temp warning
3 Cold ambient temp fault
4 Hot ambient temp warning
5 Hot ambient temp fault
6 IGBT temp warning
7 IGBT temp fault
"""
#Start timer to test for execution time
start_time = time.time()
#Prepare the modbus stack
masterTCP = modbus_tcp.TcpMaster(host=ip_address,port=port)
masterTCP.set_timeout(MODBUS_TIMEOUT)
masterTCP.set_verbose(True)
#Set logging
log.debug('Module: %s - Driver: %s - Reading device %s:%s', module_name, DRIVER_NAME, ip_address,port)
#Create an ordered list to store the values
values = OrderedDict()
#Append devicename and timestamp for InfluxDB
values['Device_Name'] = device_name
values['Date'] = get_timestamp_for_influxdb()
# Read first part
x = 0
while x < TRY_AMOUNT:
try:
read1 = masterTCP.execute(slave_id, cst.READ_HOLDING_REGISTERS, 42496, 7)
log.debug('Module: %s - Read 1 Successful : %s - %s:%s - TRIES:%s', module_name, DRIVER_NAME, ip_address, port, x)
x = TRY_AMOUNT
except Exception as e:
log.error('Module: %s - Read 1 Error : %s - %s:%s - TRIES:%s', module_name, DRIVER_NAME, ip_address, port, x)
x += 1
time.sleep(0.5)
#Read second part
x = 0
while x < TRY_AMOUNT:
try:
read2 = masterTCP.execute(slave_id, cst.READ_HOLDING_REGISTERS, 42545, 27)
log.debug('Module: %s - Read 2 Successful : %s - %s:%s - TRIES:%s', module_name, DRIVER_NAME, ip_address, port, x)
x = TRY_AMOUNT
except Exception as e:
log.error('Module: %s - Read 3 Error : %s - %s:%s - TRIES:%s', module_name, DRIVER_NAME, ip_address, port, x)
x += 1
time.sleep(0.5)
#Read third part
x = 0
while x < TRY_AMOUNT:
try:
read3 = masterTCP.execute(slave_id, cst.READ_HOLDING_REGISTERS, 42599, 8)
log.debug('Module: %s - Read 3 Successful : %s - %s:%s - TRIES:%s', module_name, DRIVER_NAME, ip_address, port, x)
x = TRY_AMOUNT
except Exception as e:
log.error('Module: %s - Read 3 Error : %s - %s:%s - TRIES:%s', module_name, DRIVER_NAME, ip_address, port, x)
x += 1
time.sleep(0.5)
#Parse the data for read 1
values['Active_Power'] = float(signed(read1[2]))
values['Reactive_Power'] = float(signed(read1[3]))
values['Grid_Voltage'] = float(read1[4]) / 10
values['Grid_Frequency'] = float(read1[5]) / 100
values['PowerFactor'] = float(signed(read1[6])) / 1000
#Parse the data for read 2
values['L1_Voltage'] = float(read2[1]) / 10
values['L2_Voltage'] = float(read2[2]) / 10
values['L3_Voltage'] = float(read2[3]) / 10
values['Grid_Current'] = float(read2[4]) / 10
values['DC_Input_Voltage'] = float(read2[5]) / 10
values['DC_Bus_Voltage'] = float(read2[6]) / 10
values['DC_Input_Current'] = float(read2[7]) / 10
values['Grounding_Current'] = float(read2[8]) / 1
values['Isolation_Resistance'] = float(read2[9]) / 1
values['Inverter_Ambient_Temp'] = float(signed(read2[10])) / 10
values['Highest_IGBT_Temp_PU1'] = float(signed(read2[11])) / 10
values['Highest_IGBT_Temp_PU21'] = float(signed(read2[12])) / 10
values['Highest_IGBT_Temp_PU31'] = float(signed(read2[13])) / 10
values['Highest_IGBT_Temp_PU41'] = float(signed(read2[14])) / 10
values['Control_Section_Temp'] = float(signed(read2[15])) / 10
values['Daily_kWh'] = float(read2[20]) / 10
values['Total_kWh'] = float(read2[22]) / 1
values['Daily_kVAh'] = float(read2[24]) / 10
values['Total_kVAh'] = float(read2[26]) / 1
#Extract Status-Limiting-Grid-Env-Fan Words
main_status_word = int(read1[1])
limiting_status_word = int(read3[1])
mppt_status_word = int(read3[3])
grid_status_word = int(read3[4])
fan_status_word = int(read3[5])
environmental_status_word = int(read3[7])
#Extract bits from words MAIN
values['Status_Main_ReadyToSwitchOn'] = float(main_status_word >> 0 & 1)
values['Status_Main_Faulted'] = float(main_status_word >> 1 & 1)
values['Status_Main_Warning'] = float(main_status_word >> 2 & 1)
values['Status_Main_MPPTEnabled'] = float(main_status_word >> 3 & 1)
values['Status_Main_GridStable'] = float(main_status_word >> 4 & 1)
values['Status_Main_DCVoltageWithinLimits'] = float(main_status_word >> 5 & 1)
values['Status_Main_StartInhibited'] = float(main_status_word >> 6 & 1)
values['Status_Main_ReducedRun'] = float(main_status_word >> 7 & 1)
values['Status_Main_RedundantRun'] = float(main_status_word >> 8 & 1)
values['Status_Main_QCompansation'] = float(main_status_word >> 9 & 1)
values['Status_Main_Limited'] = float(main_status_word >> 10 & 1)
values['Status_Main_GridConnected'] = float(main_status_word >> 11 & 1)
#Extract bits from words LIMITING
values['Status_Limiting_IGBTTempCurrentLimitation'] = float(limiting_status_word >> 0 & 1)
values['Status_Limiting_PfLimitation'] = float(limiting_status_word >> 1 & 1)
values['Status_Limiting_PuLimitation'] = float(limiting_status_word >> 2 & 1)
values['Status_Limiting_GridFaultLimitation'] = float(limiting_status_word >> 3 & 1)
values['Status_Limiting_ExternalPowerLimit'] = float(limiting_status_word >> 4 & 1)
values['Status_Limiting_FRTRecoveryLimit'] = float(limiting_status_word >> 5 & 1)
values['Status_Limiting_ShutdownRampLimit'] = float(limiting_status_word >> 6 & 1)
values['Status_Limiting_PowerGradientLimit'] = float(limiting_status_word >> 7 & 1)
values['Status_Limiting_FRTInteraction'] = float(limiting_status_word >> 8 & 1)
values['Status_Limiting_AmbientTempLimitation'] = float(limiting_status_word >> 9 & 1)
values['Status_Limiting_PowerSectionTempLimitation'] = float(limiting_status_word >> 11 & 1)
#Extract bits from words MPPT
values['Status_MPPT_MPPTMode'] = float(mppt_status_word >> 0 & 1)
values['Status_MPPT_PowerLimitationActive'] = float(mppt_status_word >> 1 & 1)
values['Status_MPPT_MinVoltageLimitActive'] = float(mppt_status_word >> 2 & 1)
values['Status_MPPT_MaxVoltageLimitActive'] = float(mppt_status_word >> 3 & 1)
#Extract bits from words GRID
values['Status_Grid_Undervoltage'] = float(grid_status_word >> 0 & 1)
values['Status_Grid_Overvoltage'] = float(grid_status_word >> 1 & 1)
values['Status_Grid_Underfrequency'] = float(grid_status_word >> 2 & 1)
values['Status_Grid_Overfrequency'] = float(grid_status_word >> 3 & 1)
values['Status_Grid_AntiIslandingTrip'] = float(grid_status_word >> 4 & 1)
values['Status_Grid_RoCoFTrip'] = float(grid_status_word >> 5 & 1)
values['Status_Grid_CombinatoryTrip'] = float(grid_status_word >> 6 & 1)
values['Status_Grid_MovingAverageTrip'] = float(grid_status_word >> 7 & 1)
values['Status_Grid_ZeroCrossingTrip'] = float(grid_status_word >> 8 & 1)
values['Status_Grid_LVRTTrip'] = float(grid_status_word >> 9 & 1)
values['Status_Grid_HVRTTrip'] = float(grid_status_word >> 10 & 1)
values['Status_Grid_ExternalMonitorTrip'] = float(grid_status_word >> 11 & 1)
#Extract bits from words FAN
values['Status_Fan_PowerUnit1'] = float(fan_status_word >> 0 & 1)
values['Status_Fan_PowerUnit2'] = float(fan_status_word >> 1 & 1)
values['Status_Fan_PowerUnit3'] = float(fan_status_word >> 2 & 1)
values['Status_Fan_PowerUnit4'] = float(fan_status_word >> 3 & 1)
values['Status_Fan_ISU1Fan'] = float(fan_status_word >> 4 & 1)
values['Status_Fan_ISU2Fan'] = float(fan_status_word >> 5 & 1)
values['Status_Fan_DoorFanCircuitBreaker'] = float(fan_status_word >> 6 & 1)
#Extract bits from words ENVIRONMENT
values['Status_Environment_ACBusbarThermalProtection'] = float(environmental_status_word >> 0 & 1)
values['Status_Environment_DCBusbarThermalProtection'] = float(environmental_status_word >> 1 & 1)
values['Status_Environment_ColdAmbientTempWarning'] = float(environmental_status_word >> 2 & 1)
values['Status_Environment_ColdAmbientTempFault'] = float(environmental_status_word >> 3 & 1)
values['Status_Environment_HotAmbientTempWarning'] = float(environmental_status_word >> 4 & 1)
values['Status_Environment_HotAmbientTempFault'] = float(environmental_status_word >> 5 & 1)
values['Status_Environment_IGBTTempWarning'] = float(environmental_status_word >> 6 & 1)
values['Status_Environment_IGBTTempFault'] = float(environmental_status_word >> 7 & 1)
log.debug('Modbus Scan Completed in : %.4f (DRIVER: %s - UNIT: %s:%s)',(time.time() - start_time),DRIVER_NAME, ip_address, port)
return values
def convert_registers_to_long(start_bit, stop_bit, signed, decimals=0, data=[]):
decimal = {0: 1,1: 10, 2: 100, 3: 1000}
mypack = pack('>HH',data[start_bit],data[stop_bit])
if signed:
format = '>l'
else:
format = '>L'
long_data = unpack(format, mypack)
final_data = float(long_data[0]) / decimal[decimals]
return final_data
def get_timestamp_for_influxdb():
return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:00Z")
def signed(value):
try:
packval = struct.pack('<H',value)
return struct.unpack('<h',packval)[0]
except Exception as e:
log.error("Error in signed-unsigned conversion. "+str(e))
return 0
def get_version():
return DRIVER_NAME+" v"+DRIVER_VERSION
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment