{
  "spec_version": 1,
  "slug": "tesvolt-energy-manager",
  "manufacturer": "TESVOLT",
  "model": "TESVOLT Energy Manager 2.0 marketer interface (TS HV storage systems)",
  "category": "Battery storage controller",
  "supplier_url": "https://www.tesvolt.com/en/service/downloads.html",
  "default_configuration": {
    "protocol": "modbus_tcp",
    "port": 502,
    "unit_id": 1,
    "addressing": "zero_based",
    "byte_order": "BigEndian",
    "word_order": "LowFirst"
  },
  "coils": [],
  "discrete_inputs": [],
  "input_registers": [
    {
      "address": 0,
      "count": 2,
      "name": "Instantaneous power at the grid connection point",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "32-bit value, low word first: address 0 is the low word, 1 the high word."
    },
    {
      "address": 3,
      "count": 2,
      "name": "Maximum power value (nominal power) specified by the grid operator",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first (3 = low, 4 = high)."
    },
    {
      "address": 5,
      "count": 2,
      "name": "Instantaneous PV power",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first."
    },
    {
      "address": 7,
      "count": 2,
      "name": "Instantaneous self-consumption power",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first."
    },
    {
      "address": 9,
      "count": 2,
      "name": "Battery state of charge",
      "data_type": "Float32",
      "units": "%",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "IEEE-754 single, low word first."
    },
    {
      "address": 11,
      "count": 2,
      "name": "Battery charge/discharge power",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Positive = charging, negative = discharging; low word first."
    },
    {
      "address": 20,
      "count": 2,
      "name": "Mirrored desired value for PV power",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Value the energy manager has accepted as PV setpoint; low word first."
    },
    {
      "address": 22,
      "count": 2,
      "name": "Mirrored desired value for grid-connection power",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first."
    },
    {
      "address": 24,
      "count": 1,
      "name": "Mirrored control mode of the grid connection",
      "data_type": "UInt16",
      "units": "",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "0 = local control, 1 = control by the marketer."
    },
    {
      "address": 25,
      "count": 2,
      "name": "Mirrored desired value for the battery power",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first."
    },
    {
      "address": 30,
      "count": 2,
      "name": "Power of the PV plant",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first."
    },
    {
      "address": 32,
      "count": 2,
      "name": "Maximum possible charging power",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first."
    },
    {
      "address": 34,
      "count": 2,
      "name": "Maximum possible discharging power",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first."
    },
    {
      "address": 36,
      "count": 2,
      "name": "Storage capacity of the battery storage",
      "data_type": "UInt32",
      "units": "Wh",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Low word first."
    },
    {
      "address": 100,
      "count": 1,
      "name": "System error flags",
      "data_type": "UInt16",
      "units": "",
      "scale_factor": 1,
      "offset": 0,
      "access": "R",
      "description": "Bit field: 0x0000 none, 0x0001 PV system error, 0x0002 battery storage system error, 0x0004 grid-connection activity recording error, 0x0008 PV activity recording error, 0x0010 self-consumption activity recording error, 0x0020 SOC activity recording error."
    }
  ],
  "holding_registers": [
    {
      "address": 0,
      "count": 2,
      "name": "Maximum power value (nominal power) of the PV plant of the virtual power plant",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "RW",
      "description": "Setpoint; low word first."
    },
    {
      "address": 2,
      "count": 1,
      "name": "Watchdog register (PV curtailment)",
      "data_type": "UInt16",
      "units": "",
      "scale_factor": 1,
      "offset": 0,
      "access": "RW",
      "description": "Write a new value about once a minute during curtailment; after 5 minutes without a new value the curtail signal is no longer respected."
    },
    {
      "address": 5,
      "count": 2,
      "name": "Nominal power at the grid connection point of the virtual power plant",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "RW",
      "description": "Low word first."
    },
    {
      "address": 7,
      "count": 1,
      "name": "Watchdog register (grid connection)",
      "data_type": "UInt16",
      "units": "",
      "scale_factor": 1,
      "offset": 0,
      "access": "RW",
      "description": "Same watchdog rule as register 2: after 5 minutes without a new value the local control takes over."
    },
    {
      "address": 10,
      "count": 2,
      "name": "Nominal power of the battery storage of the virtual power plant",
      "data_type": "Int32",
      "units": "W",
      "scale_factor": 1,
      "offset": 0,
      "access": "RW",
      "description": "Low word first."
    },
    {
      "address": 12,
      "count": 1,
      "name": "Watchdog register (battery storage)",
      "data_type": "UInt16",
      "units": "",
      "scale_factor": 1,
      "offset": 0,
      "access": "RW",
      "description": "Same watchdog rule as register 2."
    },
    {
      "address": 15,
      "count": 1,
      "name": "Power control active/inactive",
      "data_type": "UInt16",
      "units": "",
      "scale_factor": 1,
      "offset": 0,
      "access": "RW",
      "description": "0 = inactive (local control operating), 1 = active (desired value set by the marketer, taken from register 16)."
    },
    {
      "address": 16,
      "count": 1,
      "name": "Power-control target",
      "data_type": "UInt16",
      "units": "",
      "scale_factor": 1,
      "offset": 0,
      "access": "RW",
      "description": "1 = grid connection, 2 = battery storage."
    }
  ],
  "combined_registers": [],
  "notes": "Source: TESVOLT AG \"Marketer interface - ModBus specifications\" for the TESVOLT Energy Manager, revision H.01, 03/2025 (applies to TESVOLT Energy Manager version 2.0 or higher), vendor-hosted at https://www.tesvolt.com/_media/05%20SERVICE/03%20Downloads/Energiemanagementsystem/EN_TESVOLT_Energy_Manager_MarketerInterfaceModbusSpecifications_20250320.pdf. The document gives two address columns, ADDRESS (0-based) and REGISTRY (ADDRESS + 1); the ADDRESS column is stored here and the map is declared zero_based. Every 32-bit row is one value split over two holding/input registers, and the document states in each description which register carries the low and which the high word - low word first - so the seed declares word_order LowFirst. Single-register rows follow standard big-endian. The power values are signed in practice (charge/discharge, import/export), hence Int32; the storage capacity is unsigned. Placeholders, because this document does not state them: the Modbus TCP port (502 assumed) and the unit identifier (1 assumed) - the server is the Energy Manager, so both must be set to whatever the device's Modbus TCP server is configured with. The interface described is the marketer/remote-control interface (PV curtailment, grid-connection and battery setpoints plus measured values), not the complete device map; TESVOLT's internal registers are not published. The watchdog registers 2, 7 and 12 are writable and are listed with their operating rule. Error register 100 is a bit field, documented in its description."
}
