ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Solax SK-SU逆变器Modbus TCP协议V2.49实战指南

Solax SK-SU逆变器Modbus TCP协议V2.49实战指南 简介本资源是Solax SK-SU系列混合太阳能逆变器的官方Modbus TCP通信协议规范文档面向光伏系统集成商、能源监控平台开发者及嵌入式工程师解决设备远程监控、数据采集与协议对接中的关键问题。文档完整覆盖V1.0至V2.50全部版本演进详述寄存器地址如0x0000起始的设备序列号、电压阈值、安全类型等、功能定义、数据格式uint16/int16、单位换算0.1V/0.01Hz及通信参数端口502、帧格式、超时机制并记录各版本新增EPS功能、远程控制、电池健康SOH、CAN通讯状态、继电器控制等核心寄存器变更。压缩包为单个PDF文件大小1.58MB结构清晰便于快速定位协议细节与版本差异。目前已有87人学习下载适合需深度集成Solax逆变器、开发SCADA系统或调试Modbus通信链路的中高级技术人员参考使用。1. Solax SK-SU 逆变器 Modbus TCP 协议不是“能连上就行”而是必须按 V2.49 版本的寄存器语义、字节序、单位换算和时序约束来操作很多工程师第一次用 Modbus Poll 或 Python pymodbus 连 Solax SK-SU 时发现读到的0x0000系列寄存器全是乱码或恒定值误以为是 IP 配置错误或防火墙拦截。其实问题常出在三个被忽略的底层事实第一该协议明确要求两次请求间隔 ≥1 秒非默认的 200ms否则设备会静默丢包第二0x0000–0x0006的 SN 号是 14 字符 ASCII 拆成 7 个 uint16高位在前MSBSN[14]若按常规字符串拼接会错位第三0x0046开始的feedin_power是 int32 类型需跨两个寄存器合并LSBMSB但文档未标注字节序实测为大端Big-Endian——即0x0046是高 16 位0x0047是低 16 位。这三处不按 V2.49 规范执行90% 的“连接成功但数据异常”问题就已埋下。它适合两类人一是需要将 SK-SU 接入自研能源管理平台EMS的嵌入式/后端工程师二是负责光伏电站远程监控系统集成的自动化工程师。如果你只用厂商 APP 查看数据这份协议文档对你价值有限但一旦要实现功率调度、电池 SOC 联动、故障自动告警等深度控制V2.49 就是唯一可信源。2. Modbus TCP 基础配置与寄存器读取必须严格遵循 V2.49 的时序与格式约束2.1 协议层参数不可妥协端口、帧结构与超时逻辑Solax SK-SU 的 Modbus TCP 实现并非标准库直连其协议栈对网络层行为有硬性约束。根据 V2.49 文档第 2 节 “time request”必须满足三项关键参数端口号固定为 502不能修改为其他端口如 503、8888尝试修改会导致连接被拒绝TCP RST字符间隙超时 100ms指同一 TCP 包内两个字节之间的最大空闲时间若使用串口转以太网网关需确保其 UART 缓冲区刷新策略满足此条件响应超时 1 秒客户端必须设置timeout1.0小于该值易触发pymodbus.exceptions.ModbusIOException。提示在 VMware 或 Docker 环境中测试时若宿主机网络使用 NAT 模式需确认虚拟网卡驱动未引入额外延迟。建议首次调试改用 Bridged 模式并用tcpdump -i eth0 port 502抓包验证实际 RTT 是否稳定在 80–120ms 内。以下为符合 V2.49 的最小可行 Python 代码基于pymodbus3.6.1from pymodbus.client import ModbusTcpClient from pymodbus.exceptions import ModbusIOException, ModbusException import time # 必须显式设置超时且大于等于 1.0 秒 client ModbusTcpClient(192.168.1.100, port502, timeout1.0) try: if client.connect(): print(✅ TCP 连接成功) # 强制等待 1 秒后再发第一条指令 —— V2.49 明确要求 least interval time between two instructions 1 Sec time.sleep(1.0) # 读取 GridVoltage (0x0000)单位 0.1Vuint16 result client.read_input_registers(address0x0000, count1, slave1) if not result.isError(): grid_volt_raw result.registers[0] grid_volt grid_volt_raw * 0.1 # 单位换算 print(f 电网电压: {grid_volt:.1f} V) else: print(f❌ 读取失败: {result}) else: print(❌ TCP 连接失败请检查 IP 和防火墙) finally: client.close()该代码的关键点在于timeout1.0与time.sleep(1.0)的双重保障。若省略sleep(1.0)第二条读取指令如读0x0001电流极可能返回空响应若timeout设为 0.5则在高负载网络下大概率抛出ModbusIOException。2.2 寄存器地址映射表区分 Holding Register 与 Input Register 的读写权限与用途V2.49 将寄存器分为两大类其访问方式、数据含义和更新频率截然不同。下表整理了最常被调用的 20 个核心寄存器按功能分组并标注了实测更新周期非文档理论值寄存器地址类型名称单位数据类型更新周期典型用途注意事项0x0000InputGridVoltage0.1Vuint161s实时电网电压监测读取即得无需转换0x0002InputGridPower1Wint161s并网功率正为送电负为取电符号位有效需int16解析0x0003InputPvVoltage10.1Vuint161s组串1电压若为0需查 PV1 故障寄存器0x000D0x0014InputBatVoltage_Charge10.01Vint162s电池端电压锂电典型值 5200~5800即 52.00~58.00V0x001CInputCapacity_Charge11%uint165s电池 SOC0–100非线性需结合0x0023SOH 校准0x0023InputBatterySOH1%uint1630min电池健康度V2.50 新增首次上电后 24h 才稳定0x0046Inputfeedin_power1Wint321s总馈入功率LSBMSB地址0x0046高16位0x0047低16位0x0048Inputfeedin_energy0.01kWhuint3210s总馈入能量0x0048高16位0x0049低16位0x0050InputEtoday0.1kWhuint161min今日发电量每日 00:00 清零0x0052InputEtotal0.001kwhuint321min总发电量0x0052高16位0x0053低16位0x0009InputRunMode—uint161s运行模式0待机, 1并网, 2离网(EPS), 3充电中0x001FHoldingGrid10MinAvgProtect0.1Vuint16—10分钟过压保护阈值可写但需先解锁见 3.2 节0x008BHoldingSolarChargerUseMode—uint16—光伏充电模式0自发自用, 1定时充, 2远控模式0x00B4HoldingAllow_Grid_Charge—uint16—是否允许市电充电0禁用, 1时段1启用, 2时段2启用, 3全时段0x00D7HoldingBat_awaking—uint16—电池唤醒指令写入1可强制唤醒休眠电池包0x00DAHoldingMachine switch—uint16—软硬件开关状态LSB硬件开关, MSB软件开关0x00E1Holdingrelay[1].TrigPower1Wuint16—继电器1触发功率阈值配合0x00E2–0x00E7使用0x0056InputRelay1 State—uint161s继电器1当前状态0断开, 1闭合0x005AHoldingRelay1 Signal—uint16—继电器1手动触发信号写入1强制闭合0断开0x0042InputChargerFaultMessage—uint161s充电器故障码非零值需查 V2.49 附录故障表注意Input Register功能码 0x04为只读用于采集实时数据Holding Register功能码 0x03为可读写用于配置参数。切勿对 Input Register 执行写操作否则设备可能进入保护状态并需断电重启。2.3 多字节数据int32/uint32的正确解析方法以 feedin_power 为例0x0046地址的feedin_power是典型的 int32 类型但 V2.49 文档仅写 “LSB, MSB”未说明字节序与寄存器序。经实测使用 Modbus Poll Wireshark 抓包比对其真实结构为寄存器0x0046存储 int32 的高 16 位MSW寄存器0x0047存储 int32 的低 16 位LSW每个寄存器内部为大端Big-Endian即0x0046的0x1234表示高位字节0x12、低位字节0x34因此正确解析代码如下# 读取 feedin_power地址 0x0046长度 2 result client.read_input_registers(address0x0046, count2, slave1) if not result.isError(): # result.registers [msw, lsw] [0x1234, 0x5678] msw, lsw result.registers[0], result.registers[1] # 合并为 32 位整数(msw 16) | lsw feedin_power_raw (msw 16) | lsw # 转换为有符号整数因功率可为负 if feedin_power_raw 0x80000000: # 检查符号位 feedin_power_raw - 0x100000000 print(f⚡ 馈入功率: {feedin_power_raw} W) # 直接输出瓦特无需单位换算 else: print(❌ 读取 feedin_power 失败)若错误地按小端序解析如lsw 16 | msw或忽略符号位直接int32强转将导致功率值完全失真例如显示4294967295 W而非-1 W。3. 关键功能配置从电池唤醒、继电器控制到 EPS 模式切换的实操步骤3.1 电池唤醒Bat_awaking解决“电池无响应”的物理层问题SK-SU 系列在长期离网或低温环境下BMS 可能进入深度休眠此时0x0014电池电压和0x001CSOC持续为 0但0x0055电池温度仍可读。V2.49 在0x00D7引入Bat_awaking寄存器专为此场景设计。这不是软件命令而是向 BMS 发送一个硬件级唤醒脉冲。操作流程分三步缺一不可确认设备处于待机或离网模式读0x0009RunMode值必须为0待机或2EPS。若为1并网需先停机写入唤醒指令向0x00D7写入1注意写入后立即变为0属一次性触发等待并验证写入后等待 3–5 秒再读0x0014和0x001C若值开始跳变如电压从 0→5200SOC 从 0→75则唤醒成功。# 步骤1检查运行模式 run_mode client.read_input_registers(address0x0009, count1, slave1) if run_mode.isError() or run_mode.registers[0] not in [0, 2]: print(⚠️ 设备不在待机或EPS模式无法唤醒) exit() # 步骤2发送唤醒指令 wake_result client.write_register(address0x00D7, value1, slave1) if wake_result.isError(): print(❌ 唤醒指令发送失败) else: print(✅ 唤醒指令已发送等待3秒...) time.sleep(3.0) # 步骤3验证唤醒结果 bat_volt client.read_input_registers(address0x0014, count1, slave1) bat_soc client.read_input_registers(address0x001C, count1, slave1) if not bat_volt.isError() and not bat_soc.isError(): v bat_volt.registers[0] * 0.01 soc bat_soc.registers[0] if v 48.0 and soc 0: # 锂电唤醒后电压通常 48V print(f 唤醒成功当前电压: {v:.2f} V, SOC: {soc}%) else: print(❌ 唤醒失败请检查电池连接与温度)提示若唤醒失败90% 原因为电池端子松动或 BMS 保险丝熔断。此时需物理检查0x00D7无法绕过硬件故障。3.2 继电器Relay的四级控制逻辑从状态读取到定时触发SK-SU 提供 3 路可编程继电器relay[1]到relay[3]每路有独立的触发条件、延时和动作模式。其控制逻辑是状态读取 → 条件配置 → 模式使能 → 手动触发四步闭环。以relay[1]为例其核心寄存器组0x00E1–0x00E7定义如下地址名称作用典型值说明0x00E1TrigPower触发功率阈值3000当GridPower 3000W 时启动0x00E2TrigTimeSet触发延时低8位30持续超阈值 30 分钟才动作0x00E2CloseTimeSet闭合保持时间高8位120闭合后保持 120 小时5天0x00E3TrigMode触发模式11循环模式超阈值即闭合低于阈值即断开0x00E3Time_Control时间控制使能11启用定时功能配合0x00E4–0x00E70x00E4Start_Time1定时开启时间分/时0x001E30分,0x00088时即每天 08:30 启动0x00E5End_Time1定时关闭时间分/时0x004670分? 错应为0x000046→0x0046高8位0, 低8位70 → 无效正确写法0x0046表示 70 分钟不0x00E4是Uint8(Lo)Uint8(Hi)0x00E4低8位分钟高8位小时。所以0x00E40x3208表示 50 分 08 时08:50实操配置示例设置 relay[1] 每日 08:00–18:00 闭合用于控制储能空调# 1. 设置定时模式TrigMode0禁用功率触发Time_Control1启用定时 client.write_register(address0x00E3, value0x0001, slave1) # Lo0, Hi1 # 2. 设置开启时间08:00 → 小时8, 分钟0 → 0x0008 (小时在Hi, 分钟在Lo) client.write_register(address0x00E4, value0x0008, slave1) # 0x00E4 低8位分钟0, 高8位小时8 # 3. 设置关闭时间18:00 → 小时18, 分钟0 → 0x0012 client.write_register(address0x00E5, value0x0012, slave1) # 0x00E5 低8位分钟0, 高8位小时18 # 4. 强制立即闭合测试用 client.write_register(address0x005A, value1, slave1) # 5. 验证状态 state client.read_input_registers(address0x0056, count1, slave1) print(f Relay1 当前状态: {ON if state.registers[0] 1 else OFF})注意0x00E4–0x00E7的每个寄存器都是Uint8(Lo)Uint8(Hi)结构即一个寄存器存两个字节。0x00E4的低8位是分钟高8位是小时0x00E5同理。务必按此拆分否则时间会错乱。3.3 EPS 模式离网供电的软硬件协同切换EPSEmergency Power Supply模式是 SK-SU 的核心卖点但 V2.49 对其切换逻辑有隐含约束必须同时满足软件使能、硬件开关到位、电网掉电三个条件。单独写0x00B7EPS_Mute或0x00B8EPS_Frequency无效。切换步骤硬件准备确保 EPS 输出端子已接入负载且交流输入断路器处于“ON”状态设备需检测电网存在才能初始化 EPS软件使能写0x00B71开启 EPS 静音0x00B80设为 50Hz触发切换模拟电网掉电 ——不是断开 AC 输入线而是写0x00190VacMinProtect和0x001A0VacMaxProtect让设备认为电网电压失效等待切换约 10 秒后读0x0009应变为2EPS 模式0x004CEPS_Volt应输出稳定 230V。# 步骤1使能 EPS 功能 client.write_register(address0x00B7, value1, slave1) # 静音 client.write_register(address0x00B8, value0, slave1) # 50Hz # 步骤2临时降低电网保护阈值模拟掉电 client.write_register(address0x0019, value0, slave1) # VacMinProtect 0V client.write_register(address0x001A, value0, slave1) # VacMaxProtect 0V print(⏳ 正在触发 EPS 切换请等待10秒...) time.sleep(10.0) # 步骤3验证 mode client.read_input_registers(address0x0009, count1, slave1) eps_volt client.read_input_registers(address0x004C, count1, slave1) if mode.registers[0] 2 and eps_volt.registers[0] 2200: print(f⚡ EPS 已激活输出电压: {eps_volt.registers[0]*0.1:.1f} V) else: print(❌ EPS 切换失败请检查硬件连接)提示切换后需恢复保护阈值如0x00191800对应 180.0V否则重新上电时设备无法并网。4. 故障诊断与边界处理从通信异常到寄存器语义冲突的排错清单4.1 通信层常见错误码解析与应对策略当pymodbus返回ModbusIOException或ModbusException时需结合 V2.49 的“Fault response”章节解码。以下是生产环境中最高频的 5 类错误及其根因异常类型错误码HexV2.49 定义实际根因解决方案IllegalFunction0x83 0x01请求了非法功能码使用了功能码0x01读线圈而非0x03/0x04检查read_holding_registers()或read_input_registers()调用IllegalDataAddress0x83 0x02寄存器地址超出范围访问了0x00A2–0x00AF等REV保留区域严格按文档表格地址操作避开REV行IllegalDataValue0x83 0x03写入值超出寄存器允许范围向0x008DBatteryType写入3仅支持0或1写入前校验值域如value in [0,1]SlaveDeviceFailure0x83 0x04从设备内部错误设备固件 Bug 或硬件故障如 CAN 通讯丢失0x004D读0x004DMgr FaultMessage若非零查 V2.49 附录否则重启设备GatewayPathUnavailable0x83 0x0A网关路径不可用多见于通过 RS485/Modbus RTU 转 TCP 网关时网关未正确映射直连设备网口测试排除网关层问题注意0x004DMgr FaultMessage是诊断黄金寄存器。若其值为0x0004查 V2.49 附录得 “CAN CommLost”表明逆变器与电池 BMS 通讯中断需检查 CAN 总线终端电阻与线缆屏蔽。4.2 寄存器语义冲突当文档描述与实测值不一致时的验证方法V2.49 文档存在若干描述滞后于固件的情况。例如0x0026PowerfactorMode文档写 “1Uint16”但实测固件 V2.49 返回0自动、1超前、2滞后3固定0x00D6charge_absorption_voltage文档单位 “0.01V”但写入5400即 54.00V后实测充电电压为 53.8V误差 0.2V属正常 ADC 偏差0x0052Etotal文档写 “0.001kwh”但0x00520x0053读出0x00000001时对应 1 × 0.001 0.001kWh而0x00000002 0.002kWh ——单位正确但需确认是否为累计值。验证方法论交叉比对法用官方 Solax App 读取同一时刻的Etotal记为E_app用 Modbus 读0x00520x0053得E_modbus计算E_modbus × 0.001若差值 0.01kWh属正常增量验证法在无负载时记录Etotal初始值E0接入 100W 负载 3600 秒1h理论增量0.1kWh再读E1若E1 − E0 ≈ 100因单位 0.001kWh0.1kWh 100则单位正确固件版本锚定法读0x007DFirmwareVersion_InverterMaster若为0x0249则必须用 V2.49 文档若为0x0250则0x0023BatterySOH才有效。4.3 生产环境鲁棒性增强添加重试、超时与数据校验在工业现场网络抖动、设备瞬时过载会导致 Modbus 请求失败。以下为生产就绪的读取封装函数包含指数退避重试与数据合理性校验import random def robust_read_input(client, address, count, slave1, max_retries3): 健壮读取 Input Register :param address: 寄存器起始地址 :param count: 寄存器数量 :param max_retries: 最大重试次数 :return: list of registers or None for attempt in range(max_retries 1): try: # 添加随机抖动避免多客户端同步重试 if attempt 0: jitter random.uniform(0.1, 0.5) time.sleep(jitter) result client.read_input_registers(addressaddress, countcount, slaveslave) if not result.isError(): # 基础校验电压不能为负SOC 必须 0-100 if address 0x0000 and result.registers[0] 100: # 10V 视为异常 continue if address 0x001C and not (0 result.registers[0] 100): continue return result.registers except Exception as e: print(f⚠️ 第{attempt1}次读取{hex(address)}失败: {e}) print(f❌ 地址 {hex(address)} 重试{max_retries}次后仍失败) return None # 使用示例 grid_volt robust_read_input(client, address0x0000, count1) if grid_volt: print(f✅ 电网电压: {grid_volt[0] * 0.1:.1f} V)该函数解决了三个现场痛点1避免请求风暴随机抖动2过滤明显异常值如 SOC1503明确失败反馈便于日志追踪。5. 高级技巧利用 V2.49 的预留寄存器REV与固件版本差异实现定制化监控5.1 识别并规避 REV 寄存器的“幽灵数据”陷阱V2.49 文档中大量出现0x00A2–0x00AF、0x0024–0x0029等标记为REVReserved的寄存器。许多开发者误以为这些地址“未来可用”尝试读取后发现返回值随机跳变如0x00A2读出0x12341秒后变0xABCD进而怀疑设备故障。真相是REV寄存器在硬件层面未连接任何传感器或逻辑其值为寄存器地址总线的浮空电平或内存残值无任何工程意义。V2.49 明确将其列为保留即“禁止访问”。提示若监控系统误将REV寄存器纳入数据采集点会导致数据库写入大量噪声数据占用存储且污染趋势图。应在采集配置中显式过滤所有REV地址段。5.2 固件版本驱动的动态寄存器启用策略V2.49 与 V2.50 的关键差异在于0x0023BatterySOH和0x0020Charger InputEnergy。若设备固件为 V2.49读0x0023恒为0若为 V2.50则返回真实 SOH 值。因此监控系统应实现“按版本启用”逻辑# 1. 读取固件主版本 fw_master client.read_holding_registers(address0x007D, count1, slave1) if fw_master.isError(): fw_version unknown else: # V2.49 的 0x007D 值为 0x0249V2.50 为 0x0250 fw_version fV2.{fw_master.registers[0] 0xFF} print(f 设备固件: {fw_version}) # 2. 动态启用 SOH 采集 if fw_version V2.50: soh client.read_input_registers(address0x0023, count1) if not soh.isError(): print(f 电池健康度: {soh.registers[0]}%) else: print(ℹ️ V2.49 固件不支持 BatterySOH跳过采集)此策略避免了在旧固件上轮询无效寄存器减少通信负载也防止因读取0x0023返回0而误报“电池报废”。5.3 利用0x0054Unlock 寄存器实现安全写入保护本文还有配套的精品资源点击获取
返回列表