From 516cb0fd2923f54ac183d3e9aea10b0f482ebbe6 Mon Sep 17 00:00:00 2001 From: Tatyana Date: Mon, 3 Jul 2023 13:25:52 +0200 Subject: [PATCH] Add Zizm ZM194-D9Y power meter --- power_meters/zizm_ZM194-D9Y/README.md | 14 ++ power_meters/zizm_ZM194-D9Y/firmware.lua | 194 +++++++++++++++++++++++ power_meters/zizm_ZM194-D9Y/manifest.yml | 189 ++++++++++++++++++++++ 3 files changed, 397 insertions(+) create mode 100644 power_meters/zizm_ZM194-D9Y/README.md create mode 100644 power_meters/zizm_ZM194-D9Y/firmware.lua create mode 100644 power_meters/zizm_ZM194-D9Y/manifest.yml diff --git a/power_meters/zizm_ZM194-D9Y/README.md b/power_meters/zizm_ZM194-D9Y/README.md new file mode 100644 index 00000000..292a07db --- /dev/null +++ b/power_meters/zizm_ZM194-D9Y/README.md @@ -0,0 +1,14 @@ +# Zizm ZM194-D9Y + +This [Enapter Device Blueprint](https://go.enapter.com/marketplace-readme) integrates **Zizm ZM194-D9Y** - 3-Phase Multi-function Power Meter with [ModBus RTU](https://go.enapter.com/developers-enapter-modbus) over [RS-485 communication interface](https://go.enapter.com/developers-enapter-rs485). + +## Connect to Enapter + +- Sign up to the Enapter Cloud using the [Web](https://cloud.enapter.com/) or mobile app ([iOS](https://apps.apple.com/app/id1388329910), [Android](https://play.google.com/store/apps/details?id=com.enapter&hl=en)). +- Use the [Enapter ENP-RS485](https://go.enapter.com/handbook-enp-rs485) module for physical connection. See [connection instructions](https://go.enapter.com/handbook-enp-rs485-conn) in the module manual. +- [Add ENP-RS485 to your site](https://go.enapter.com/handbook-mobile-app) using the mobile app. +- [Upload](https://go.enapter.com/developers-upload-blueprint) this blueprint to ENP-RS485. +- Use the `Set Up Connection` command in the Enapter mobile or Web app to set up Modbus RTU communication parameters: + - Baudrate; + - Modbus address; + - Parity. diff --git a/power_meters/zizm_ZM194-D9Y/firmware.lua b/power_meters/zizm_ZM194-D9Y/firmware.lua new file mode 100644 index 00000000..45d0a982 --- /dev/null +++ b/power_meters/zizm_ZM194-D9Y/firmware.lua @@ -0,0 +1,194 @@ +local config = require('enapter.ucm.config') + +BAUDRATE = 'baudrate' +ADDRESS = 'address' +PARITY = 'parity' + +-- Default RS485 communication interface parameters +DATA_BITS = 8 +STOP_BITS = 1 + +function main() + config.init({ + [BAUDRATE] = { type = 'number', required = true, default = 9600 }, + [ADDRESS] = { type = 'number', required = true, default = 1 }, + [PARITY] = { type = 'string', required = true, default = 'N' }, + }) + + local values, err = config.read_all() + if err then + enapter.log('cannot read config: ' .. tostring(err), 'error') + return nil, 'cannot_read_config' + else + local baudrate, parity = values[BAUDRATE], values[PARITY] + + local result = rs485.init(baudrate, DATA_BITS, parity, STOP_BITS) + if result ~= 0 then + enapter.log('RS485 init error: ' .. rs485.err_to_str(result), error, true) + end + end + + scheduler.add(30000, send_properties) + scheduler.add(1000, send_telemetry) +end + +function send_properties() + enapter.send_properties({ + vendor = 'Zizm', + model = 'ZM194-D9Y', + }) +end + +function send_telemetry() + local telemetry = {} + local status = 'ok' + + local all_metrics = { + voltage_a = { + register_address = 0x0000, + register_count = 2, + fn = touint32, + }, + voltage_b = { + register_address = 0x0002, + register_count = 2, + fn = touint32, + }, + voltage_c = { + register_address = 0x0004, + register_count = 2, + fn = touint32, + }, + line_voltage_ab = { + register_address = 0x0006, + register_count = 2, + fn = touint32, + }, + line_voltage_bc = { + register_address = 0x0008, + register_count = 2, + fn = touint32, + }, + line_voltage_ca = { + register_address = 0x000A, + register_count = 2, + fn = touint32, + }, + current_a = { + register_address = 0x000C, + register_count = 2, + fn = touint32, + }, + current_b = { + register_address = 0x000E, + register_count = 2, + fn = touint32, + }, + current_c = { + register_address = 0x0010, + register_count = 2, + fn = touint32, + }, + active_power_a = { + register_address = 0x0012, + register_count = 2, + fn = touint32, + }, + active_power_b = { + register_address = 0x0014, + register_count = 2, + fn = touint32, + }, + active_power_c = { + register_address = 0x0016, + register_count = 2, + fn = touint32, + }, + total_active_power = { + register_address = 0x0018, + register_count = 2, + fn = touint32, + }, + reactive_power_a = { + register_address = 0x001A, + register_count = 2, + fn = touint32, + }, + reactive_power_b = { + register_address = 0x001C, + register_count = 2, + fn = touint32, + }, + reactive_power_c = { + register_address = 0x001E, + register_count = 2, + fn = touint32, + }, + total_reactive_power = { + register_address = 0x0020, + register_count = 2, + fn = touint32, + }, + frequency = { + register_address = 0x0032, + register_count = 2, + fn = touint32, + }, + total_active_electric_energy = { + register_address = 0x0038, + register_count = 2, + fn = touint32, + }, + forward_active_electric_energy = { + register_address = 0x003A, + register_count = 2, + fn = touint32, + }, + reverse_active_electric_energy = { + register_address = 0x003C, + register_count = 2, + fn = touint32, + }, + } + + for name, metric in pairs(all_metrics) do + if + not add_to_telemetry( + name, + telemetry, + metric.register_address, + metric.register_count, + metric.fn + ) + then + status = 'read_error' + end + end + + telemetry['status'] = status + enapter.send_telemetry(telemetry) +end + +function add_to_telemetry(metric_name, tbl, register_address, registers_count, fn) + local address, err = config.read(ADDRESS) + if err == nil then + local data, result = modbus.read_holdings(address, register_address, registers_count, 1000) + if data then + tbl[metric_name] = fn(data) / 1000.0 -- coefficient 0.001 for all metrics + return true + else + enapter.log( + 'Register ' .. register_address .. ' reading failed: ' .. modbus.err_to_str(result) + ) + end + end + return false +end + +function touint32(register) + local raw_str = + string.pack('BBBB', register[1] >> 8, register[1] & 0xff, register[2] >> 8, register[2] & 0xff) + return string.unpack('>I2', raw_str) +end + +main() diff --git a/power_meters/zizm_ZM194-D9Y/manifest.yml b/power_meters/zizm_ZM194-D9Y/manifest.yml new file mode 100644 index 00000000..fa21eab5 --- /dev/null +++ b/power_meters/zizm_ZM194-D9Y/manifest.yml @@ -0,0 +1,189 @@ +blueprint_spec: device/1.0 +display_name: Zizm ZM194-D9Y +description: 3-Phase Multi-function Power Meter. +icon: enapter-power-meter +vendor: zizm +author: enapter +support: + url: https://go.enapter.com/enapter-blueprint-support + email: support@enapter.com +license: MIT +verification_level: verified + +communication_module: + product: ENP-RS485 + lua: + file: firmware.lua + dependencies: + - enapter-ucm + +properties: + vendor: + type: string + display_name: Vendor + model: + type: string + display_name: Model + +telemetry: + status: + display_name: Modbus status + type: string + enum: + - ok + - read_error + total_active_power: + type: float + unit: kwatt + display_name: Total Active Power + total_active_electric_energy: + type: float + unit: kwatth + display_name: Total Active Electric Energy + active_power_a: + type: float + unit: kwatt + display_name: Active Power A + active_power_b: + type: float + unit: watt + display_name: Active Power B + active_power_c: + type: float + unit: kwatt + display_name: Active Power C + voltage_a: + type: float + unit: volt + display_name: Phase A Voltage + voltage_b: + type: float + unit: volt + display_name: Phase B Voltage + voltage_c: + type: float + unit: volt + display_name: Phase C Voltage + line_voltage_ab: + type: float + unit: volt + display_name: Line Voltage AB + line_voltage_bc: + type: float + unit: volt + display_name: Line Voltage BC + line_voltage_ca: + type: float + unit: volt + display_name: Line Voltage CA + current_a: + type: float + unit: amp + display_name: Phase A Current + current_b: + type: float + unit: amp + display_name: Phase B Current + current_c: + type: float + unit: amp + display_name: Phase C Current + frequency: + type: float + unit: hertz + display_name: Frequency + total_reactive_power: + type: float + unit: watt + display_name: Total Reactive Power + reactive_power_a: + type: float + unit: watt + display_name: Reactive Power A + reactive_power_b: + type: float + unit: watt + display_name: Reactive Power B + reactive_power_c: + type: float + unit: watt + display_name: Reactive Power C + forward_active_electric_energy: + type: float + unit: kwatth + display_name: Forward Active Electric Energy + reverse_active_electric_energy: + type: float + unit: kwatth + display_name: Reverse Active Electric Energy + +command_groups: + connection: + display_name: Connection + +commands: + # TODO: mark commands containing secrets + write_configuration: + display_name: Set Up Connection + description: Set Modbus RTU connection + group: connection + populate_values_command: read_configuration + ui: + icon: file-document-edit-outline + arguments: + baudrate: + display_name: Baudrate + type: integer + required: true + enum: + - 2400 + - 4800 + - 9600 + address: + display_name: Modbus address + type: integer + required: true + min: 1 + max: 247 + parity: + display_name: Parity + type: string + required: true + enum: + - N + - E + - O + read_configuration: + display_name: Read Connection Parameters + group: connection + ui: + icon: file-check-outline +.cloud: + category: power_meters + mobile_main_chart: total_active_power + mobile_telemetry: + - total_active_electric_energy + - total_active_power + - active_power_a + - active_power_b + - active_power_c + - voltage_a + - voltage_b + - voltage_c + - current_a + - current_b + - current_c + - frequency + mobile_charts: + - total_active_electric_energy + - total_active_power + - active_power_a + - active_power_b + - active_power_c + - voltage_a + - voltage_b + - voltage_c + - current_a + - current_b + - current_c + - frequency