Skip to content

Commit

Permalink
Merge pull request #569 from ggardet/master
Browse files Browse the repository at this point in the history
Add WiFi Dual Meter device
  • Loading branch information
jasonacox authored Dec 18, 2024
2 parents e72b7ee + 7ea70cb commit 3fbbffc
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 0 deletions.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,51 @@ thermo = ThermostatDevice( 'abcdefghijklmnop123456', '172.28.321.475', '12345678
For info on the Sensor Data lists, see https://github.com/jasonacox/tinytuya/discussions/139
#### Version 3.4 - WIFI Dual Meter (i.e. PJ1103A)
| DP ID | Function Point | Type | Range | Units |
| ------------- | ------------- | ------------- | ------------- |------------- |
|1|Forward energy total|integer||kWh x 100|
|2|Reverse energy total|integer||kWh x 100|
|101|Power (A)|integer||W x 10|
|102|Direction of current flow (A)| enum | <ul><li>FORWARD</li><li>REVERSE</li></ul> ||
|104|Direction of current flow (B)| enum | <ul><li>FORWARD</li><li>REVERSE</li></ul> ||
|105|Power (B)|integer||W x 10|
|106|Energy forward (A)|integer||kWh x 100|
|107|Energy reverse (A)|integer||kWh x 100|
|108|Energy forward (B)|integer||kWh x 100|
|109|Energy reverse (B)|integer||kWh x 100|
|110|Power Factor (A)|integer||value x 100|
|111|AC frequency|integer||Hz x 100|
|112|AC voltage|integer||V x 10|
|113|Current (A)|integer||mA|
|114|Current (B)|integer||mA|
|115|Total power|integer||W x 10|
|116|Voltage Calibration|integer|800-1200|value * 1000|
|117|Current Calibration (A)|integer|800-1200|value * 1000|
|118|Power Calibration (A)|integer|800-1200|value * 1000|
|119|Energy Calibration forward (A)|integer|800-1200|value * 1000|
|121|Power Factor (B)|integer||value x 100|
|122|Frequency Calibration|integer|800-1200|value * 1000|
|123|Current Calibration (B)|integer|800-1200|value * 1000|
|124|Power Calibration (B)|integer|800-1200|value * 1000|
|125|Energy Calibration forward (B)|integer|800-1200|value * 1000|
|127|Energy Calibration reverse (A)|integer|800-1200|value * 1000|
|128|Energy Calibration reverse (B)|integer|800-1200|value * 1000|
|129|Report Rate Control|integer|3-60|s|
Note: (A) or (B) means channel A or channel B
A user contributed module is available for this device in the [Contrib library](https://github.com/jasonacox/tinytuya/tree/master/tinytuya/Contrib):
```python from tinytuya.Contrib import WiFiDualMeterDevice
wdm = WiFiDualMeterDevice.WiFiDualMeterDevice(
dev_id='abcdefghijklmnop123456',
address='192.168.0.29', # Or set to 'Auto' to auto-discover IP address
local_key='1234567890123abc',
version=3.4)
```
### Tuya References
* Tuya Hardware Development - Protocol: https://developer.tuya.com/en/docs/iot/device-development/embedded-software-development/mcu-development-access/wifi-mcu-sdk-solution/tuya-cloud-universal-serial-port-access-protocol?id=K9hhi0xxtn9cb
Expand Down
22 changes: 22 additions & 0 deletions tinytuya/Contrib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,28 @@ In addition to the built-in `OutletDevice`, `BulbDevice` and `CoverDevice` devic

```

### WiFiDualMeterDevice

* WiFiDualMeterDevice - A community-contributed Python module to add support for Tuya WiFi Dual Meter device
* Author: [Guillaume Gardet](https://github.com/ggardet)

```
from tinytuya.Contrib import WiFiDualMeterDevice

wdm = WiFiDualMeterDevice.WiFiDualMeterDevice(
dev_id='YOUR_DEV_ID',
address='192.168.XX.YY', # Or set to 'Auto' to auto-discover IP address
local_key='LOCAL_KEY',
version=3.4)

# Print all known values
wdm.print_all()

# Only print Voltage and frequency
print(wdm.get_freq())
print(wdm.get_voltage())


## Submit Your Device

* We welcome new device modules!
Expand Down
243 changes: 243 additions & 0 deletions tinytuya/Contrib/WiFiDualMeterDevice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# TinyTuya WiFi Dual Meter Device
# -*- coding: utf-8 -*-
"""
Python module to interface with Tuya WiFi Dual Meter Devices
Author: Guillaume Gardet
Local Control Classes
WiFiDualMeterDevice(...)
See OutletDevice() for constructor arguments
Functions
WiFiDualMeterDevice:
get_current_b()
get_total_power()
get_voltage_calibration()
get_current_calibration_a()
get_power_calibration_a()
get_energy_calibration_a()
get_power_factor_b()
get_current_calibration_b()
get_power_calibration_b()
get_energy_calibration_b()
get_energy_reverse_calibration_a()
get_energy_reverse_calibration_b()
get_report_rate()
Inherited
json = status() # returns json payload
set_version(version) # 3.1 [default] or 3.3
set_socketPersistent(False/True) # False [default] or True
set_socketNODELAY(False/True) # False or True [default]
set_socketRetryLimit(integer) # retry count limit [default 5]
set_socketTimeout(timeout) # set connection timeout in seconds [default 5]
set_dpsUsed(dps_to_request) # add data points (DPS) to request
add_dps_to_request(index) # add data point (DPS) index set to None
set_retry(retry=True) # retry if response payload is truncated
set_status(on, switch=1, nowait) # Set status of switch to 'on' or 'off' (bool)
set_value(index, value, nowait) # Set int value of any index.
heartbeat(nowait) # Send heartbeat to device
updatedps(index=[1], nowait) # Send updatedps command to device
turn_on(switch=1, nowait) # Turn on device / switch #
turn_off(switch=1, nowait) # Turn off
set_timer(num_secs, nowait) # Set timer for num_secs
set_debug(toggle, color) # Activate verbose debugging output
set_sendWait(num_secs) # Time to wait after sending commands before pulling response
detect_available_dps() # Return list of DPS available from device
generate_payload(command, data) # Generate TuyaMessage payload for command with data
send(payload) # Send payload to device (do not wait for response)
receive()
"""

from ..core import Device

class WiFiDualMeterDevice(Device):

DPS_FORWARD_ENERGY_TOTAL = '1'
DPS_REVERSE_ENERGY_TOTAL = '2'
DPS_POWER_A = '101'
DPS_DIR_CUR_A = '102'
DPS_DIR_CUR_B = '104'
DPS_POWER_B = '105'
DPS_ENERGY_FORWARD_A = '106'
DPS_ENERGY_REVERSE_A = '107'
DPS_ENERGY_FORWARD_B = '108'
DPS_ENERGY_REVERSE_B = '109'
DPS_POWER_FACTOR_A = '110'
DPS_FREQ = '111'
DPS_VOLTAGE = '112'
DPS_CURRENT_A = '113'
DPS_CURRENT_B = '114'
DPS_TOTAL_POWER = '115'
DPS_VOLTAGE_CALIBRATION = '116'
DPS_CURRENT_CALIBRATION_A = '117'
DPS_POWER_CALIBRATION_A = '118'
DPS_ENERGY_CALIBRATION_A = '119'
DPS_POWER_FACTOR_B = '121'
DPS_FREQUENCY_CALIBRATION = '122'
DPS_CURRENT_CALIBRATION_B = '123'
DPS_POWER_CALIBRATION_B = '124'
DPS_ENERGY_CALIBRATION_B = '125'
DPS_ENERGY_CALIBRATION_REVERSE_A = '127'
DPS_ENERGY_CALIBRATION_REVERSE_B = '128'
DPS_REPORT_RATE = '129'

dps_data = {
DPS_FORWARD_ENERGY_TOTAL: { 'name': 'forward_energy_total', 'unit': 'kWh', 'scale': 100 },
DPS_REVERSE_ENERGY_TOTAL: { 'name': 'reverse_energy_total', 'unit': 'kWh', 'scale': 100 },
DPS_POWER_A: { 'name': 'power_a', 'unit': 'W', 'scale': 10 },
DPS_DIR_CUR_A: { 'name': 'dir_curent_a', 'enum': ['FORWARD', 'REVERSE'] },
DPS_DIR_CUR_B: { 'name': 'dir_current_b', 'enum': ['FORWARD', 'REVERSE'] },
DPS_POWER_B: { 'name': 'power_b', 'unit': 'W', 'scale': 10 },
DPS_ENERGY_FORWARD_A: { 'name': 'forward_energy_a', 'unit': 'kWh', 'scale': 100 },
DPS_ENERGY_REVERSE_A: { 'name': 'reverse_energy_a', 'unit': 'kWh', 'scale': 100 },
DPS_ENERGY_FORWARD_B: { 'name': 'forward_energy_b', 'unit': 'kWh', 'scale': 100 },
DPS_ENERGY_REVERSE_B: { 'name': 'reverse_energy_b', 'unit': 'kWh', 'scale': 100 },
DPS_POWER_FACTOR_A: { 'name': 'power_factor_a', 'scale': 100 },
DPS_FREQ: { 'name': 'ac_frequency', 'unit': 'Hz', 'scale': 100 },
DPS_VOLTAGE: { 'name': 'ac_voltage', 'unit': 'V', 'scale': 10 },
DPS_CURRENT_A: { 'name': 'current_a', 'unit': 'mA'},
DPS_CURRENT_B: { 'name': 'current_b', 'unit': 'mA'},
DPS_TOTAL_POWER: { 'name': 'total_power', 'unit': 'W', 'scale': 10 },
DPS_VOLTAGE_CALIBRATION : { 'name': 'voltage_calibration', 'scale': 1000 },
DPS_CURRENT_CALIBRATION_A: { 'name': 'current_calibration_a', 'scale': 1000 },
DPS_POWER_CALIBRATION_A: { 'name': 'power_calibration_a', 'scale': 1000 },
DPS_ENERGY_CALIBRATION_A: { 'name': 'energy_calibration_a', 'scale': 1000 },
DPS_POWER_FACTOR_B: { 'name': 'power_factor_b', 'scale': 100 },
DPS_CURRENT_CALIBRATION_B: { 'name': 'current_calibration_b', 'scale': 1000 },
DPS_POWER_CALIBRATION_B: { 'name': 'power_calibration_b', 'scale': 1000 },
DPS_ENERGY_CALIBRATION_B: { 'name': 'energy_calibration_b', 'scale': 1000 },
DPS_ENERGY_CALIBRATION_REVERSE_A: { 'name': 'energy_calibration_reverse_a', 'scale': 1000 },
DPS_ENERGY_CALIBRATION_REVERSE_B: { 'name': 'energy_calibration_reverse_b', 'scale': 1000 },
DPS_REPORT_RATE: { 'name': 'report_rate', 'unit': 's' },
}

def get_value(self, dps_code, status_data=None):
if status_data is None:
status_data = self.status()
name = self.dps_data[dps_code]['name']
try:
scale = self.dps_data[dps_code]['scale']
except KeyError:
scale = 1
try:
unit = self.dps_data[dps_code]['unit']
except KeyError:
unit = ""
val = status_data['dps'][dps_code]
if isinstance(val, int):
val = val / scale
return {name+'_raw': val,
name+'_fmt': str(val) + ' '+ unit}

def get_forward_energy_total(self, status_data=None):
return self.get_value(dps_code=self.DPS_FORWARD_ENERGY_TOTAL)

def get_reverse_energy_total(self, status_data=None):
return self.get_value(dps_code=self.DPS_REVERSE_ENERGY_TOTAL)

def get_power_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_POWER_A)

def get_dir_cur_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_DIR_CUR_A)

def get_dir_cur_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_DIR_CUR_B)

def get_power_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_POWER_B)

def get_energy_forward_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_ENERGY_FORWARD_A)

def get_energy_reverse_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_ENERGY_REVERSE_A)

def get_energy_forward_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_ENERGY_FORWARD_B)

def get_energy_reverse_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_ENERGY_REVERSE_B)

def get_power_factor_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_POWER_FACTOR_A)

def get_freq(self, status_data=None):
return self.get_value(dps_code=self.DPS_FREQ)

def get_voltage(self, status_data=None):
return self.get_value(dps_code=self.DPS_VOLTAGE)

def get_current_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_CURRENT_A)

def get_current_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_CURRENT_B)

def get_total_power(self, status_data=None):
return self.get_value(dps_code=self.DPS_TOTAL_POWER)

def get_voltage_calibration(self, status_data=None):
return self.get_value(dps_code=self.DPS_VOLTAGE_CALIBRATION)

def get_current_calibration_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_CURRENT_CALIBRATION_A)

def get_power_calibration_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_POWER_CALIBRATION_A)

def get_energy_calibration_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_ENERGY_CALIBRATION_A)

def get_power_factor_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_POWER_FACTOR_B)

def get_current_calibration_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_CURRENT_CALIBRATION_B)

def get_power_calibration_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_POWER_CALIBRATION_B)

def get_energy_calibration_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_ENERGY_CALIBRATION_B)

def get_energy_reverse_calibration_a(self, status_data=None):
return self.get_value(dps_code=self.DPS_ENERGY_CALIBRATION_REVERSE_A)

def get_energy_reverse_calibration_b(self, status_data=None):
return self.get_value(dps_code=self.DPS_ENERGY_CALIBRATION_REVERSE_B)

def get_report_rate(self, status_data=None):
return self.get_value(dps_code=self.DPS_REPORT_RATE)

def print_all(self, status_data=None):
if status_data is None:
status_data = self.status()
print(self.get_forward_energy_total(status_data))
print(self.get_reverse_energy_total(status_data))
print(self.get_power_a(status_data))
print(self.get_dir_cur_a(status_data))
print(self.get_dir_cur_b(status_data))
print(self.get_power_b(status_data))
print(self.get_energy_forward_a(status_data))
print(self.get_energy_reverse_a(status_data))
print(self.get_energy_forward_b(status_data))
print(self.get_energy_reverse_b(status_data))
print(self.get_power_factor_a(status_data))
print(self.get_freq(status_data))
print(self.get_voltage(status_data))
print(self.get_current_a(status_data))
print(self.get_current_b(status_data))
print(self.get_total_power(status_data))
print(self.get_voltage_calibration(status_data))
print(self.get_current_calibration_a(status_data))
print(self.get_power_calibration_a(status_data))
print(self.get_energy_calibration_a(status_data))
print(self.get_power_factor_b(status_data))
print(self.get_power_calibration_b(status_data))
print(self.get_energy_calibration_b(status_data))
print(self.get_energy_reverse_calibration_a(status_data))
print(self.get_energy_reverse_calibration_b(status_data))
print(self.get_report_rate(status_data))

0 comments on commit 3fbbffc

Please sign in to comment.