kopia lustrzana https://github.com/weetmuts/wmbusmeters
Merge branch 'master' into master
commit
3001e4fabe
23
CHANGES
23
CHANGES
|
@ -1,4 +1,27 @@
|
|||
|
||||
ATTENTION! The topaseskr driver has been refactored to the new driver format.
|
||||
The field "battery_life_days_remaining_remaining" has been changed to "battery_life_y".
|
||||
|
||||
ATTENTION! When values are missing in the fields output, they were previously
|
||||
reported as "nan" but they are now reported as "null".
|
||||
|
||||
ATTENTION! The unismart driver has been refactored to the new driver format.
|
||||
Several fields were unknown before and are still unknown and their content
|
||||
have slightly changed: status and version.
|
||||
|
||||
The field "other_counter" is replaced with just "other" and its content changed.
|
||||
The field "suppler_info" is replaced with "supplier_info".
|
||||
The field "device_date_time" is replaced with "device_timestamp" and now contains seconds
|
||||
and a timezone.
|
||||
|
||||
ATTENTION! A bug was fixed where the on_time_h and operating_time_h
|
||||
(and related error times) were wrong.
|
||||
|
||||
ATTENTION! The em24 driver has been refactored to the new driver
|
||||
format and some errors in the fields were discovered! The reactive
|
||||
consumption and production values were negative. This was in error,
|
||||
and due to a missing scale factor for the kvarh unit, they will now be positive.
|
||||
|
||||
ATTENTION! The q400 driver has been refactored to the new driver
|
||||
format and some errors in the fields were discovered! Since the new driver
|
||||
format cannot generate these wrong json fields, the new q400 driver
|
||||
|
|
14
Makefile
14
Makefile
|
@ -248,12 +248,24 @@ $(BUILD)/short_manual.h: README.md
|
|||
| grep -v '```' >> $(BUILD)/short_manual.h
|
||||
echo ')MANUAL";' >> $(BUILD)/short_manual.h
|
||||
|
||||
$(BUILD)/testinternals: $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/testinternals.o
|
||||
testinternals: $(BUILD)/testinternals
|
||||
|
||||
$(BUILD)/testinternals.o: $(PROG_OBJS) $(DRIVER_OBJS) $(wildcard src/*.h)
|
||||
|
||||
$(BUILD)/testinternals: $(BUILD)/testinternals.o
|
||||
$(CXX) -o $(BUILD)/testinternals $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/testinternals.o $(LDFLAGS) -lrtlsdr $(USBLIB) -lpthread
|
||||
|
||||
$(BUILD)/fuzz: $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/fuzz.o
|
||||
$(CXX) -o $(BUILD)/fuzz $(PROG_OBJS) $(DRIVER_OBJS) $(BUILD)/fuzz.o $(LDFLAGS) -lrtlsdr -lpthread
|
||||
|
||||
clean_executables:
|
||||
rm -rf build/wmbusmeters* build_arm/wmbusmeters* build_debug/wmbusmeters* build_arm_debug/wmbusmeters* *~
|
||||
rm -rf build/testinternal* build_arm/testinternal* build_debug/testinternal* build_arm_debug/testinternal*
|
||||
$(RM) testaes/test_input.txt testaes/test_stderr.txt
|
||||
$(RM) testoutput/test_expected.txt testoutput/test_input.txt \
|
||||
testoutput/test_response.txt testoutput/test_responses.txt \
|
||||
testoutput/test_stderr.txt
|
||||
|
||||
clean:
|
||||
rm -rf build/* build_arm/* build_debug/* build_arm_debug/* *~
|
||||
$(RM) testaes/test_input.txt testaes/test_stderr.txt
|
||||
|
|
|
@ -125,6 +125,10 @@ bus the mbus poll request should be sent to.
|
|||
wmbusmeters --pollinterval=60s MAIN=/dev/ttyUSB0:mbus:2400 MyTempMeter piigth:MAIN:mbus 12001932 NOKEY
|
||||
```
|
||||
|
||||
If you want to poll an mbus meter using the primary address, just use
|
||||
a number between 0 and 250 instead of the full 8 digit secondary
|
||||
address.
|
||||
|
||||
# Example wmbusmeter.conf file
|
||||
|
||||
```ini
|
||||
|
@ -351,7 +355,7 @@ rtlsdr dongle like this `rtlwmbus[1234]`.
|
|||
|
||||
`/dev/ttyUSB0:amb8465`, if you have an amb8465 dongle assigned to ttyUSB0. Other suffixes are im871a,cul.
|
||||
|
||||
`/dev/ttyUSB0`, to have wmbusmeters auto-detect amb8465, amb3665, im871a, rc1180 or cul device.
|
||||
(Note that a plain `/dev/ttyUSB0` no longer works, you have to specify the device expected on the device.)
|
||||
|
||||
`/dev/ttyUSB0:38400`, to have wmbusmeters set the baud rate to 38400 and listen for raw wmbus telegrams.
|
||||
These telegrams are expected to have the data link layer crc bytes removed already!
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
{
|
||||
"total_m3": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"json_attributes_topic": "wmbusmeters/{name}",
|
||||
"state_class": "total",
|
||||
"device_class": "water",
|
||||
"name": "{name} total",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "m³",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:gauge"
|
||||
}
|
||||
},
|
||||
"target_m3": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "total",
|
||||
"device_class": "water",
|
||||
"name": "{name} target",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "m³",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:gauge"
|
||||
}
|
||||
},
|
||||
"flow_m3h": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "measurement",
|
||||
"name": "{name} flow",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "m³/h",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:waves-arrow-right"
|
||||
}
|
||||
},
|
||||
"min_flow_m3h": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": false,
|
||||
"state_class": "measurement",
|
||||
"name": "{name} min flow",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "m³/h",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:waves-arrow-right"
|
||||
}
|
||||
},
|
||||
"max_flow_m3h": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": false,
|
||||
"state_class": "measurement",
|
||||
"name": "{name} max flow",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "m³/h",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:waves-arrow-right"
|
||||
}
|
||||
},
|
||||
"min_flow_temperature_c": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": false,
|
||||
"state_class": "measurement",
|
||||
"device_class": "temperature",
|
||||
"name": "{name} min water temperature",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "°C",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:thermometer-water"
|
||||
}
|
||||
},
|
||||
"max_flow_temperature_c": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": false,
|
||||
"state_class": "measurement",
|
||||
"device_class": "temperature",
|
||||
"name": "{name} max water temperature",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "°C",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:thermometer-water"
|
||||
}
|
||||
},
|
||||
"min_external_temperature_c": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": false,
|
||||
"state_class": "measurement",
|
||||
"device_class": "temperature",
|
||||
"name": "{name} min ambient temperature",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "°C",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:thermometer"
|
||||
}
|
||||
},
|
||||
"status_dry": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status dry",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'DRY' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
"status_reversed": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status reversed",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'REVERSED' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
"status_leak": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status leak",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'LEAK' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
"status_burst": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status burst",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'BURST' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
"rssi_dbm": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Kamstrup",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": false,
|
||||
"entity_category": "diagnostic",
|
||||
"device_class": "signal_strength",
|
||||
"state_class": "measurement",
|
||||
"name": "{name} rssi",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "dbm",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:signal"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
"enabled_by_default": true,
|
||||
"json_attributes_topic": "wmbusmeters/{name}",
|
||||
"state_class": "total",
|
||||
"device_class": "water",
|
||||
"name": "{name} total",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
|
@ -33,6 +34,7 @@
|
|||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "total",
|
||||
"device_class": "water",
|
||||
"name": "{name} target",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
|
|
|
@ -0,0 +1,407 @@
|
|||
{
|
||||
"total_water_m3": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "total",
|
||||
"name": "{name} total water volume",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "m³",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:water"
|
||||
}
|
||||
},
|
||||
|
||||
"flow_water_m3h": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "measurement",
|
||||
"name": "{name} water volume flow",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "m³/h",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:waves-arrow-right"
|
||||
}
|
||||
},
|
||||
|
||||
"total_kwh": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"json_attributes_topic": "wmbusmeters/{name}",
|
||||
"state_class": "total",
|
||||
"device_class": "energy",
|
||||
"name": "{name} total energy consumption",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "kWh",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:home-lightning-bolt-outline"
|
||||
}
|
||||
},
|
||||
|
||||
"power_kw": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "measurement",
|
||||
"name": "{name} current power consumption",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "kW",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:waves-arrow-right"
|
||||
}
|
||||
},
|
||||
|
||||
"forward_c": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "measurement",
|
||||
"device_class": "temperature",
|
||||
"name": "{name} forward water temperature",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "°C",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:thermometer-water"
|
||||
}
|
||||
},
|
||||
|
||||
"return_c": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "measurement",
|
||||
"device_class": "temperature",
|
||||
"name": "{name} return water temperature",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "°C",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:thermometer-water"
|
||||
}
|
||||
},
|
||||
|
||||
"difference_c": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"state_class": "measurement",
|
||||
"device_class": "temperature",
|
||||
"name": "{name} water temperature difference forward-return",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "°C",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:thermometer-chevron-down"
|
||||
}
|
||||
},
|
||||
|
||||
"status_OK": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status OK",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'OK' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"status_ERROR_TEMP_SENSOR_1_CABLE_BREAK": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status error (Temperature Sensor 1 Cable Break)",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'ERROR_TEMP_SENSOR_1_CABLE_BREAK' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"status_ERROR_TEMP_SENSOR_1_SHORT_CIRCUIT": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status error (Temperature Sensor 1 Short Circuit)",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'ERROR_TEMP_SENSOR_1_SHORT_CIRCUIT' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"status_ERROR_TEMP_SENSOR_2_CABLE_BREAK": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status error (Temperature Sensor 2 Cable Break)",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'ERROR_TEMP_SENSOR_2_CABLE_BREAK' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"status_ERROR_TEMP_SENSOR_2_SHORT_CIRCUIT": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status error (Temperature Sensor 2 Short Circuit)",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'ERROR_TEMP_SENSOR_2_SHORT_CIRCUIT' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"status_ERROR_FLOW_MEASUREMENT_SYSTEM_ERROR": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status error (Flow Management System)",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'ERROR_FLOW_MEASUREMENT_SYSTEM_ERROR' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"status_ERROR_ELECTRONICS_DEFECT": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status error (Electronics Defective)",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'ERROR_ELECTRONICS_DEFECT' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"status_OK_INSTRUMENT_RESET": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status OK (instrument reset)",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'OK_INSTRUMENT_RESET' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"status_OK_BATTERY_LOW": {
|
||||
"component": "binary_sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"device_class": "problem",
|
||||
"name": "{name} status OK (battery low)",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ 'OK_BATTERY_LOW' in value_json.status }}",
|
||||
"payload_on": "True",
|
||||
"payload_off": "False"
|
||||
}
|
||||
},
|
||||
|
||||
"meter_timestamp": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": false,
|
||||
"state_class": "measurement",
|
||||
"name": "{name} meter timestamp",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:timeline-clock"
|
||||
}
|
||||
},
|
||||
|
||||
"timestamp": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": ["wmbusmeters_{id}"],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": false,
|
||||
"state_class": "measurement",
|
||||
"name": "{name} timestamp",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:clock"
|
||||
}
|
||||
},
|
||||
|
||||
"rssi_dbm": {
|
||||
"component": "sensor",
|
||||
"discovery_payload": {
|
||||
"device": {
|
||||
"identifiers": [
|
||||
"wmbusmeters_{id}"
|
||||
],
|
||||
"manufacturer": "Engelmann",
|
||||
"model": "{driver}",
|
||||
"name": "{name}",
|
||||
"sw_version": "{id}"
|
||||
},
|
||||
"enabled_by_default": true,
|
||||
"entity_category": "diagnostic",
|
||||
"device_class": "signal_strength",
|
||||
"state_class": "measurement",
|
||||
"name": "{name} rssi",
|
||||
"state_topic": "wmbusmeters/{name}",
|
||||
"unique_id": "wmbusmeters_{id}_{attribute}",
|
||||
"unit_of_measurement": "dbm",
|
||||
"value_template": "{{ value_json.{attribute} }}",
|
||||
"icon": "mdi:signal"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,7 +49,7 @@ cat > /wmbusmeters/mosquitto_pub.sh << EOL
|
|||
#!/usr/bin/with-contenv bashio
|
||||
TOPIC=\$1
|
||||
MESSAGE=\$2
|
||||
/usr/bin/mosquitto_pub ${pub_args_quoted[@]} -t \$TOPIC -m "\$MESSAGE"
|
||||
/usr/bin/mosquitto_pub ${pub_args_quoted[@]} -r -t \$TOPIC -m "\$MESSAGE"
|
||||
EOL
|
||||
chmod a+x /wmbusmeters/mosquitto_pub.sh
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
# Test different kinds of electricity meters with radio converters.
|
||||
# The meters might report the same mfct/media/version but contains
|
||||
# quite different content.
|
||||
|
||||
# This is eBZ DD3 meter with a wMB-E01 wmbus radio converter module.
|
||||
|
||||
telegram=|4B445A146450000002378C20F3900F002C25F38B00006CBB07CEB215901172635213015A140102F3002007102F2F#04039D3E03000429E82201000DFD110E333632353331313031305A424531|
|
||||
{"media":"electricity","meter":"ehzp","name":"MyEl","id":"01135263","total_energy_consumption_kwh":212.637,"current_power_consumption_kw":0.74472,"total_energy_production_kwh":0,"on_time_h":0,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|MyEl;01135263;212.637000;0.744720;0.000000;1111-11-11 11:11.11
|
|
@ -0,0 +1 @@
|
|||
telegram=|23442D2C998734761B168D2087D19EAD214bc479EDA86AB6_710008190000081900003213|
|
|
@ -29,14 +29,14 @@ telegram=|1844AE4C4455223368077A55000000_041389E20100023B0000|
|
|||
# Test amiplus/apator electricity meter
|
||||
|
||||
telegram=|4E4401061010101002027A00004005_2F2F0E035040691500000B2B300300066D00790C7423400C78371204860BABC8FC100000000E833C8074000000000BAB3C0000000AFDC9FC0136022F2F2F2F2F|
|
||||
{"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":null,"voltage_at_phase_3_v":null,"device_date_time":"2019-03-20 12:57","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|MyElectricity1;10101010;15694.050000;0.330000;7.480000;0.000000;236.000000;nan;nan;nan;nan;nan;nan;nan;nan;1111-11-11 11:11.11
|
||||
{"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":null,"voltage_at_phase_3_v":null,"device_date_time":"2019-03-20 12:57:00","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|MyElectricity1;10101010;15694.05;0.33;7.48;0;236;null;null;null;null;null;null;null;null;1111-11-11 11:11.11
|
||||
|
||||
# Test amiplus/apator electricity meter with three phase voltages
|
||||
|
||||
telegram=|5E44B6105843250000027A2A005005_2F2F0C7835221400066D404708AC2A400E032022650900000E833C0000000000001B2B9647000B2B5510000BAB3C0000000AFDC9FC0135020AFDC9FC0245020AFDC9FC0339020BABC8FC100000002F2F|
|
||||
{"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|MyElectricity2;00254358;9652.220000;1.055000;0.000000;0.000000;235.000000;245.000000;239.000000;nan;nan;nan;nan;nan;nan;1111-11-11 11:11.11
|
||||
{"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07:00","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|MyElectricity2;00254358;9652.22;1.055;0;0;235;245;239;null;null;null;null;null;null;1111-11-11 11:11.11
|
||||
|
||||
|
||||
# Test MKRadio3 T1 telegrams
|
||||
|
@ -164,19 +164,19 @@ telegram=|5B445A149922992202378C20F6900F002C25BC9E0000BF48954821BC508D7299229922
|
|||
|
||||
# static telegram
|
||||
telegram=|7B4479169977997730378C208B900F002C25E4EF0A002EA98E7D58B3ADC57299779977991611028B005087102F2F#0DFD090F34302e3030562030303030303030300D790E31323334353637383839595345310DFD100AAAAAAAAAAAAAAAAAAAAA0D780E31323334353637383930594553312F2F2F2F2F2F2F2F2F2F2F|
|
||||
{"media":"electricity","meter":"esyswm","name":"Elen2","id":"77997799","total_energy_consumption_kwh":0,"current_power_consumption_kw":0,"total_energy_production_kwh":0,"total_energy_consumption_tariff1_kwh":0,"total_energy_consumption_tariff2_kwh":0,"current_power_consumption_phase1_kw":0,"current_power_consumption_phase2_kw":0,"current_power_consumption_phase3_kw":0,"enhanced_id":"1ESY9887654321","version":"00000000 V00.04","location_hex":"AAAAAAAAAAAAAAAAAAAA","fabrication_no":"1SEY0987654321","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Elen2;77997799;0.000000;0.000000;0.000000;0.000000;0.000000;0.000000;0.000000;0.000000;1ESY9887654321;1111-11-11 11:11.11
|
||||
{"media":"electricity","meter":"esyswm","name":"Elen2","id":"77997799","fabrication_no":"1SEY0987654321","enhanced_id":"1ESY9887654321","location":"AAAAAAAAAAAAAAAAAAAA","location_hex":"AAAAAAAAAAAAAAAAAAAA","total_energy_consumption_kwh":null,"current_power_consumption_kw":null,"total_energy_production_kwh":null,"total_energy_consumption_tariff1_kwh":null,"total_energy_consumption_tariff2_kwh":null,"current_power_consumption_phase1_kw":null,"current_power_consumption_phase2_kw":null,"current_power_consumption_phase3_kw":null,"version":"00000000 V00.04","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Elen2;77997799;null;null;null;null;null;null;null;null;1ESY9887654321;1111-11-11 11:11.11
|
||||
|
||||
# dynamic telegram
|
||||
telegram=|7B4479169977997730378C20F0900F002C2549EE0A0077C19D3D1A08ABCD729977997779161102F0005007102F2F#0702F5C3FA000000000007823C5407000000000000841004E081020084200415000000042938AB000004A9FF01FA0A000004A9FF02050A000004A9FF03389600002F2F2F2F2F2F2F2F2F2F2F2F2F|
|
||||
{"media":"electricity","meter":"esyswm","name":"Elen2","id":"77997799","total_energy_consumption_kwh":1643.4165,"current_power_consumption_kw":0.43832,"total_energy_production_kwh":0.1876,"total_energy_consumption_tariff1_kwh":1643.2,"total_energy_consumption_tariff2_kwh":0.21,"current_power_consumption_phase1_kw":0.0281,"current_power_consumption_phase2_kw":0.02565,"current_power_consumption_phase3_kw":0.38456,"enhanced_id":"1ESY9887654321","version":"00000000 V00.04","location_hex":"AAAAAAAAAAAAAAAAAAAA","fabrication_no":"1SEY0987654321","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Elen2;77997799;1643.416500;0.438320;0.187600;1643.200000;0.210000;0.028100;0.025650;0.384560;1ESY9887654321;1111-11-11 11:11.11
|
||||
{"media":"electricity","meter":"esyswm","name":"Elen2","id":"77997799","fabrication_no":"1SEY0987654321","enhanced_id":"1ESY9887654321","location":"AAAAAAAAAAAAAAAAAAAA","location_hex":"AAAAAAAAAAAAAAAAAAAA","total_energy_consumption_kwh":1643.4165,"current_power_consumption_kw":0.43832,"total_energy_production_kwh":0.1876,"total_energy_consumption_tariff1_kwh":1643.2,"total_energy_consumption_tariff2_kwh":0.21,"current_power_consumption_phase1_kw":0.0281,"current_power_consumption_phase2_kw":0.02565,"current_power_consumption_phase3_kw":0.38456,"version":"00000000 V00.04","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Elen2;77997799;1643.4165;0.43832;0.1876;1643.2;0.21;0.0281;0.02565;0.38456;1ESY9887654321;1111-11-11 11:11.11
|
||||
|
||||
# Test electricity meter eHZ-P
|
||||
|
||||
telegram=|5344A8159955995502028C201D900F002C250C390000ED176BBBB1591ADB7A1D003007102F2F_0700583B74020000000007803CBCD70200000000000728B070200000000000042092A406002F2F2F2F2F2F2F2F2F|
|
||||
{"media":"electricity","meter":"ehzp","name":"Elen3","id":"55995599","total_energy_consumption_kwh":41.1718,"current_power_consumption_kw":2.126,"total_energy_production_kwh":0.1863,"on_time_h":120.929444,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Elen3;55995599;41.171800;2.126000;0.186300;1111-11-11 11:11.11
|
||||
{"media":"electricity","meter":"ehzp","name":"Elen3","id":"55995599","status":"OK","on_time_h":120.929444,"total_energy_consumption_kwh":41.1718,"current_power_consumption_kw":2.126,"total_energy_production_kwh":0.1863,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Elen3;55995599;41.1718;2.126;0.1863;1111-11-11 11:11.11
|
||||
|
||||
# Test water meter apator08
|
||||
|
||||
|
@ -199,8 +199,8 @@ telegram=|3944FA122162092002067A3600202567C94D48D00DC47B11213E23383DB51968A705AA
|
|||
# Test topaseskr water meter
|
||||
|
||||
telegram=|4E44B40512345678F1077A310040052F2F_01FD08040C13991848004C1359423500CC101300000000CC201359423500426C7F2C0B3B00000002FD74DA10025AD300C4016D3B179F27CC011387124600|
|
||||
{"media":"water","meter":"topaseskr","name":"Witer","id":"78563412","total_m3":481.899,"temperature_c":21.1,"current_flow_m3h":0,"battery_life_days_remaining_remaining":"4314","volume_year_period_m3":354.259,"reverse_volume_year_period_m3":0,"meter_year_period_start_date":"2019-12-31","volume_month_period_m3":461.287,"meter_month_period_start_datetime":"2020-07-31 23:59","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Witer;78563412;481.899000;21.100000;0.000000;354.259000;0.000000;2019-12-31;461.287000;2020-07-31 23:59;1111-11-11 11:11.11
|
||||
{"media":"water","meter":"topaseskr","name":"Witer","id":"78563412","total_m3":481.899,"access_counter":4,"temperature_c":21.1,"current_flow_m3h":0,"volume_year_period_m3":354.259,"reverse_volume_year_period_m3":0,"meter_year_period_start_date":"2019-12-31","volume_month_period_m3":461.287,"meter_month_period_start_datetime":"2020-07-31 23:59","battery_y":11.811331,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Witer;78563412;481.899;21.1;0;354.259;0;2019-12-31;461.287;2020-07-31 23:59;1111-11-11 11:11.11
|
||||
|
||||
# Test Ultrimis water meter
|
||||
telegram=|2E4401069897969501167A4B0320052F2F_0413320C000003FD1700000044132109000004933C000000002F2F2F2F2F|+0
|
||||
|
@ -264,7 +264,7 @@ telegram=|534424232004256092687A370045752235854DEEEA5939FAD81C25FEEF5A23C38FB916
|
|||
|
||||
# Test Apator Elf Heat meter
|
||||
telegram=|51440186010905001837721956880101064004DA000020026CA9220E017799241103000C13641320000A2D00000A5A90060A5E800544050E77000001FD0C010A6564370AFD4731030A274907047F00000002|
|
||||
{"media":"heat","meter":"elf","name":"Hetta","id":"01885619","status":"ERROR_FLAGS_2000000","meter_date":"2021-02-09","total_energy_consumption_kwh":3112.49977,"current_power_consumption_kw":0,"total_volume_m3":201.364,"total_energy_consumption_at_date_kwh":3047.8,"flow_temperature_c":69,"return_temperature_c":58,"external_temperature_c":37.64,"operating_time_h":4.993333,"version":"01","battery_v":3.31,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
{"media":"heat","meter":"elf","name":"Hetta","id":"01885619","status":"ERROR_FLAGS_2000000","meter_date":"2021-02-09","total_energy_consumption_kwh":3112.49977,"current_power_consumption_kw":0,"total_volume_m3":201.364,"total_energy_consumption_at_date_kwh":3047.8,"flow_temperature_c":69,"return_temperature_c":58,"external_temperature_c":37.64,"operating_time_h":17976,"version":"01","battery_v":3.31,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|Hetta;01885619;3112.49977;0;201.364;69;58;37.64;ERROR_FLAGS_2000000;1111-11-11 11:11.11
|
||||
|
||||
# Test Diehl IZAR RC I G4 water meter
|
||||
|
@ -294,8 +294,8 @@ telegram=76442104710007612507727100076121042507B5006005E2E95A3C2A1279A5415E67326
|
|||
|
||||
# Test Unismart Gas Meter
|
||||
telegram=|6044B8059430040001037A1D005085E2B670BCF1A5C87E0C1A51DA18924EF984613DA2A9CD39D8F4C7208326C76D42DBEADF80D574192B71BD7C4F56A7F1513151768A9DB804883B28CB085CA2D0F7438C361CB9E2734712ED9BFBB2A14EF55208|
|
||||
{"media":"gas","meter":"unismart","name":"GasMeter","id":"00043094","fabrication_no":"3162296","total_date_time":"2021-09-15 13:18","total_m3":917,"target_date_time":"2021-09-01 06:00","target_m3":911.32,"version":"UGG4","device_date_time":"2021-09-15 13:18","suppler_info":"00","status":"F00C","parameter_set":"02","other_counter":20,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|GasMeter;00043094;917.000000;911.320000;1111-11-11 11:11.11
|
||||
{"media":"gas","meter":"unismart","name":"GasMeter","id":"00043094","fabrication_no":"03162296","status":"STATUS_FLAGS_CF0","other":"OTHER_FLAGS_14","total_date_time":"2021-09-15 13:18","total_m3":917,"target_date_time":"2021-09-01 06:00","target_m3":911.32,"version":" 4GGU","supplier_info":"00","parameter_set":"02","meter_timestamp":"2021-09-15 13:18:30","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|GasMeter;00043094;917;911.32;1111-11-11 11:11.11
|
||||
|
||||
# Test Hydrocal M3 heat/cooling meter
|
||||
telegram=|8E44B409747372710B0D7A798080052F2F_0C0E59600100046D1D36B9290C13679947000C0E000000000C13590000000C13000000000C13000000000A5A18020A5E11020F823D06003D06003D06003D0600140600620500480400E402001601000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002F2F|
|
||||
|
@ -309,8 +309,8 @@ telegram=|2E44B05C82340100021B7A460000002F2F0A6601020AFB1A570602FD971D00002F2F2F
|
|||
|
||||
# Test QHeat heat meter
|
||||
telegram=|3C449344957002372337725880226793442304DC0000200C05043900004C0500000000426C9F2CCC080551070000C2086CBE29326CFFFF046D280DB62A|
|
||||
{"media":"heat","meter":"qheat","name":"QHeato","id":"67228058","total_energy_consumption_kwh":390.4,"last_month_date":"2021-09-30","last_month_energy_consumption_kwh":75.1,"last_year_date":"2020-12-31","last_year_energy_consumption_kwh":0,"device_date_time":"2021-10-22 13:40","device_error_date":"2127-15-31","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|QHeato;67228058;390.400000;2021-09-30;75.100000;1111-11-11 11:11.11
|
||||
{"media":"heat","meter":"qheat","name":"QHeato","id":"67228058","status":"OK","total_energy_consumption_kwh":390.4,"last_month_date":"2021-09-30","last_month_energy_consumption_kwh":75.1,"last_year_date":"2020-12-31","last_year_energy_consumption_kwh":0,"device_date_time":"2021-10-22 13:40","device_error_date":"2127-15-31","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|QHeato;67228058;390.4;2021-09-30;75.1;1111-11-11 11:11.11
|
||||
|
||||
# Test QSmoke smoke detector this one is not yet understood.
|
||||
telegram=|3E44934486707945211A7801FD08F081027C034955230082026CFFFF81037C034C41230082036CFFFF03FD17000000326CFFFF046D0F0ABC2B02FDAC7E1100|
|
||||
|
|
|
@ -485,6 +485,18 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
|
|||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--calculate_", 12))
|
||||
{
|
||||
// For example: --calculate_adjusted_kwh='total_kwh + 12345 kwh'
|
||||
string extra_calculated_field = string(argv[i]+12);
|
||||
if (extra_calculated_field == "") {
|
||||
error("The calculated field command cannot be empty.\n");
|
||||
}
|
||||
debug("(cmdline) add calculated field %s\n", extra_calculated_field.c_str());
|
||||
c->extra_calculated_fields.push_back(extra_calculated_field);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--listenvs=", 11)) {
|
||||
c->list_shell_envs = true;
|
||||
c->list_meter = string(argv[i]+11);
|
||||
|
@ -738,14 +750,20 @@ shared_ptr<Configuration> parseCommandLineWithUseConfig(Configuration *c, int ar
|
|||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--device=", 9) || // Deprecated
|
||||
!strncmp(argv[i], "--overridedevice=", 17))
|
||||
if (!strncmp(argv[i], "--device=", 9)) // Deprecated
|
||||
{
|
||||
c->overrides.device_override = string(argv[i]+9);
|
||||
debug("(useconfig) device override \"%s\"\n", c->overrides.device_override.c_str());
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--overridedevice=", 17))
|
||||
{
|
||||
c->overrides.device_override = string(argv[i]+17);
|
||||
debug("(useconfig) device override \"%s\"\n", c->overrides.device_override.c_str());
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!strncmp(argv[i], "--listento=", 11))
|
||||
{
|
||||
c->overrides.listento_override = string(argv[i]+11);
|
||||
|
|
|
@ -59,6 +59,7 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
|
|||
vector<string> telegram_shells;
|
||||
vector<string> alarm_shells;
|
||||
vector<string> extra_constant_fields;
|
||||
vector<string> extra_calculated_fields;
|
||||
vector<string> selected_fields;
|
||||
|
||||
debug("(config) loading meter file %s\n", file.c_str());
|
||||
|
@ -152,6 +153,12 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
|
|||
string keyvalue = p.first.substr(off)+"="+p.second;
|
||||
extra_constant_fields.push_back(keyvalue);
|
||||
}
|
||||
else
|
||||
if (startsWith(p.first, "calculate_"))
|
||||
{
|
||||
string keyvalue = p.first.substr(10)+"="+p.second;
|
||||
extra_calculated_fields.push_back(keyvalue);
|
||||
}
|
||||
else
|
||||
warning("Found invalid key \"%s\" in meter config file\n", p.first.c_str());
|
||||
|
||||
|
@ -195,6 +202,7 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
|
|||
}
|
||||
if (use) {
|
||||
mi.extra_constant_fields = extra_constant_fields;
|
||||
mi.extra_calculated_fields = extra_calculated_fields;
|
||||
mi.shells = telegram_shells;
|
||||
mi.idsc = toIdsCommaSeparated(mi.ids);
|
||||
mi.selected_fields = selected_fields;
|
||||
|
@ -661,6 +669,11 @@ void handleExtraConstantField(Configuration *c, string field)
|
|||
c->extra_constant_fields.push_back(field);
|
||||
}
|
||||
|
||||
void handleExtraCalculatedField(Configuration *c, string field)
|
||||
{
|
||||
c->extra_calculated_fields.push_back(field);
|
||||
}
|
||||
|
||||
shared_ptr<Configuration> loadConfiguration(string root, ConfigOverrides overrides)
|
||||
{
|
||||
Configuration *c = new Configuration;
|
||||
|
@ -736,6 +749,12 @@ shared_ptr<Configuration> loadConfiguration(string root, ConfigOverrides overrid
|
|||
handleExtraConstantField(c, keyvalue);
|
||||
}
|
||||
else
|
||||
if (startsWith(p.first, "calculate_"))
|
||||
{
|
||||
string keyvalue = p.first.substr(10)+"="+p.second;
|
||||
handleExtraCalculatedField(c, keyvalue);
|
||||
}
|
||||
else
|
||||
{
|
||||
warning("No such key: %s\n", p.first.c_str());
|
||||
}
|
||||
|
@ -808,7 +827,7 @@ LinkModeCalculationResult calculateLinkModes(Configuration *config, BusDevice *d
|
|||
if (n == 0) num = "any combination";
|
||||
string dongles = device->supportedLinkModes().hr();
|
||||
string dongle;
|
||||
strprintf(dongle, "%s of %s", num.c_str(), dongles.c_str());
|
||||
strprintf(&dongle, "%s of %s", num.c_str(), dongles.c_str());
|
||||
|
||||
// Calculate the possible listen_to linkmodes for this collection of meters.
|
||||
LinkModeSet meters_union = UNKNOWN_bit;
|
||||
|
@ -825,7 +844,7 @@ LinkModeCalculationResult calculateLinkModes(Configuration *config, BusDevice *d
|
|||
if (link_modes_matter && config->all_device_linkmodes_specified.empty())
|
||||
{
|
||||
string msg;
|
||||
strprintf(msg,"(config) No meters supplied. You must supply which link modes to listen to. 22 Eg. auto:t1");
|
||||
strprintf(&msg,"(config) No meters supplied. You must supply which link modes to listen to. 22 Eg. auto:t1");
|
||||
debug("%s\n", msg.c_str());
|
||||
return { LinkModeCalculationResultType::NoMetersMustSupplyModes , msg};
|
||||
}
|
||||
|
@ -859,7 +878,7 @@ LinkModeCalculationResult calculateLinkModes(Configuration *config, BusDevice *d
|
|||
if (!config->all_device_linkmodes_specified.hasAll(meters_union))
|
||||
{
|
||||
string msg;
|
||||
strprintf(msg, "(config) You have specified to listen to the link modes: %s but the meters might transmit on: %s\n"
|
||||
strprintf(&msg, "(config) You have specified to listen to the link modes: %s but the meters might transmit on: %s\n"
|
||||
"(config) Therefore you might miss telegrams! Please specify the expected transmit mode for the meters, eg: apator162:t1\n"
|
||||
"(config) Or use a dongle that can listen to all the required link modes at the same time.",
|
||||
all_lms.c_str(), metersu.c_str());
|
||||
|
|
|
@ -136,6 +136,7 @@ struct Configuration
|
|||
std::vector<std::string> selected_fields;
|
||||
std::vector<MeterInfo> meters;
|
||||
std::vector<std::string> extra_constant_fields; // Additional constant fields to always add to json.
|
||||
std::vector<std::string> extra_calculated_fields; // Additional calculated fields to always add to json.
|
||||
// These extra constant fields can also be part of selected with selectfields.
|
||||
std::vector<SendBusContent> send_bus_content; // Telegrams used to wake up a meter for reading or mbus read-out requests.
|
||||
std::set<BusDeviceType> probe_for; // Which devices should be probed for? DEVICE_AUTO means all.
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace
|
|||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("amiplus");
|
||||
di.setDefaultFields("name,id,total_energy_consumption_kwh,current_power_consumption_kw,total_energy_production_kwh,current_power_production_kw,voltage_at_phase_1_v,voltage_at_phase_2_v,voltage_at_phase_3_v,total_energy_consumption_tariff_1_kwh,total_energy_consumption_tariff_2_kwh,total_energy_consumption_tariff_3_kwh,total_energy_production_tariff_1_kwh,total_energy_production_tariff_2_kwh,total_energy_production_tariff_3_kwh,timestamp");
|
||||
di.setMeterType(MeterType::ElectricityMeter);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(MANUFACTURER_APA, 0x02, 0x02);
|
||||
|
@ -188,19 +189,19 @@ namespace
|
|||
|
||||
// Test: MyElectricity1 amiplus 10101010 NOKEY
|
||||
// telegram=|4E4401061010101002027A00004005_2F2F0E035040691500000B2B300300066D00790C7423400C78371204860BABC8FC100000000E833C8074000000000BAB3C0000000AFDC9FC0136022F2F2F2F2F|
|
||||
// {"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":null,"voltage_at_phase_3_v":null,"device_date_time":"2019-03-20 12:57","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |MyElectricity1;10101010;15694.050000;0.330000;7.480000;0.000000;236.000000;nan;nan;nan;nan;nan;nan;nan;nan;1111-11-11 11:11.11
|
||||
// {"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":null,"voltage_at_phase_3_v":null,"device_date_time":"2019-03-20 12:57:00","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |MyElectricity1;10101010;15694.05;0.33;7.48;0;236;null;null;null;null;null;null;null;null;1111-11-11 11:11.11
|
||||
|
||||
// Test: MyElectricity2 amiplus 00254358 NOKEY
|
||||
// amiplus/apator electricity meter with three phase voltages
|
||||
|
||||
// telegram=|5E44B6105843250000027A2A005005_2F2F0C7835221400066D404708AC2A400E032022650900000E833C0000000000001B2B9647000B2B5510000BAB3C0000000AFDC9FC0135020AFDC9FC0245020AFDC9FC0339020BABC8FC100000002F2F|
|
||||
// {"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |MyElectricity2;00254358;9652.220000;1.055000;0.000000;0.000000;235.000000;245.000000;239.000000;nan;nan;nan;nan;nan;nan;1111-11-11 11:11.11
|
||||
// {"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07:00","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |MyElectricity2;00254358;9652.22;1.055;0;0;235;245;239;null;null;null;null;null;null;1111-11-11 11:11.11
|
||||
|
||||
// Test: MyElectricity3 amiplus 86064864 NOKEY
|
||||
// amiplus/apator electricity meter with three phase voltages and 2 tariffs
|
||||
|
||||
// telegram=|804401066448068602027A000070052F2F_066D1E5C11DA21400C78644806868E10036110012500008E20038106531800008E10833C9949000000008E20833C8606000000001B2B5228020B2B3217000BAB3C0000000AFDC9FC0131020AFDC9FC0225020AFDC9FC0331020BABC8FC100000002F2F2F2F2F2F2F2F2F2F2F2F2FDE47|
|
||||
// {"media":"electricity","meter":"amiplus","name":"MyElectricity3","id":"86064864","total_energy_consumption_kwh":null,"current_power_consumption_kw":1.732,"total_energy_production_kwh":null,"current_power_production_kw":0,"voltage_at_phase_1_v":231,"voltage_at_phase_2_v":225,"voltage_at_phase_3_v":231,"device_date_time":"2022-01-26 17:28","total_energy_consumption_tariff_1_kwh":25011.061,"total_energy_consumption_tariff_2_kwh":18530.681,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":4.999,"total_energy_production_tariff_2_kwh":0.686,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |MyElectricity3;86064864;nan;1.732000;nan;0.000000;231.000000;225.000000;231.000000;25011.061000;18530.681000;nan;4.999000;0.686000;nan;1111-11-11 11:11.11
|
||||
// {"media":"electricity","meter":"amiplus","name":"MyElectricity3","id":"86064864","total_energy_consumption_kwh":null,"current_power_consumption_kw":1.732,"total_energy_production_kwh":null,"current_power_production_kw":0,"voltage_at_phase_1_v":231,"voltage_at_phase_2_v":225,"voltage_at_phase_3_v":231,"device_date_time":"2022-01-26 17:28:30","total_energy_consumption_tariff_1_kwh":25011.061,"total_energy_consumption_tariff_2_kwh":18530.681,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":4.999,"total_energy_production_tariff_2_kwh":0.686,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |MyElectricity3;86064864;null;1.732;null;0;231;225;231;25011.061;18530.681;null;4.999;0.686;null;1111-11-11 11:11.11
|
||||
|
|
|
@ -64,7 +64,7 @@ void MeterApator08::processContent(Telegram *t)
|
|||
map<string,pair<int,DVEntry>> vendor_values;
|
||||
|
||||
string total;
|
||||
strprintf(total, "%02x%02x%02x%02x", content[0], content[1], content[2], content[3]);
|
||||
strprintf(&total, "%02x%02x%02x%02x", content[0], content[1], content[2], content[3]);
|
||||
|
||||
vendor_values["0413"] = { 25, DVEntry(25, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, 0, 0, 0, total) };
|
||||
int offset;
|
||||
|
|
|
@ -102,7 +102,7 @@ namespace
|
|||
{
|
||||
// We found the register representing the total
|
||||
string total;
|
||||
strprintf(total, "%02x%02x%02x%02x", content[i+0], content[i+1], content[i+2], content[i+3]);
|
||||
strprintf(&total, "%02x%02x%02x%02x", content[i+0], content[i+1], content[i+2], content[i+3]);
|
||||
int offset = i-1+t->header_size;
|
||||
vendor_values["0413"] = {offset, DVEntry(offset, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, 0, 0, 0, total) };
|
||||
extractDVdouble(&vendor_values, "0413", &offset, &total_water_consumption_m3_);
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace
|
|||
{
|
||||
// We found the register representing the total
|
||||
string total;
|
||||
strprintf(total, "%02x%02x%02x%02x", content[i+0], content[i+1], content[i+2], content[i+3]);
|
||||
strprintf(&total, "%02x%02x%02x%02x", content[i+0], content[i+1], content[i+2], content[i+3]);
|
||||
int offset = i-1+t->header_size;
|
||||
vendor_values["0413"] = {offset, DVEntry(offset, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, 0, 0, 0, total) };
|
||||
double tmp = 0;
|
||||
|
|
|
@ -72,8 +72,8 @@ MeterAventiesHCA::MeterAventiesHCA(MeterInfo &mi, DriverInfo &di) : MeterCommonI
|
|||
for (int i=2; i<=17; ++i)
|
||||
{
|
||||
string key, info;
|
||||
strprintf(key, "consumption_at_set_date_%d", i);
|
||||
strprintf(info, "Heat cost allocation at the %d billing period date.", i);
|
||||
strprintf(&key, "consumption_at_set_date_%d", i);
|
||||
strprintf(&info, "Heat cost allocation at the %d billing period date.", i);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
key,
|
||||
|
|
|
@ -60,8 +60,8 @@ MeterAventiesWM::MeterAventiesWM(MeterInfo &mi, DriverInfo &di) :
|
|||
for (int i=1; i<=14; ++i)
|
||||
{
|
||||
string msg, info;
|
||||
strprintf(msg, "consumption_at_set_date_%d", i);
|
||||
strprintf(info, "Water consumption at the %d billing period date.", i);
|
||||
strprintf(&msg, "consumption_at_set_date_%d", i);
|
||||
strprintf(&info, "Water consumption at the %d billing period date.", i);
|
||||
addNumericFieldWithExtractor(
|
||||
msg,
|
||||
Quantity::Volume,
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace
|
|||
int getHistoric(int n, vector<uchar> &content)
|
||||
{
|
||||
assert(n >= 0 && n < 18);
|
||||
assert(content.size() == 40);
|
||||
assert(content.size() >= 40);
|
||||
|
||||
int offset = (n*12)/8;
|
||||
int remainder = (n*12)%8;
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields();
|
||||
addOptionalCommonFields("software_version");
|
||||
|
||||
addStringField(
|
||||
"status",
|
||||
|
|
|
@ -82,7 +82,7 @@ namespace
|
|||
double prev = (256.0*prev_hi+prev_lo);
|
||||
|
||||
string prevs;
|
||||
strprintf(prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
int offset = t->parsed.size()+3;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) };
|
||||
Explanation pe(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL);
|
||||
|
@ -94,7 +94,7 @@ namespace
|
|||
double curr = (256.0*curr_hi+curr_lo);
|
||||
|
||||
string currs;
|
||||
strprintf(currs, "%02x%02x", curr_lo, curr_hi);
|
||||
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
|
||||
offset = t->parsed.size()+7;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) };
|
||||
Explanation ce(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL);
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace
|
|||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption_phase1",
|
||||
"Current power consumption at phase 1.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
|
@ -60,7 +60,7 @@ namespace
|
|||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption_phase2",
|
||||
"Current power consumption at phase 2.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
|
@ -70,7 +70,7 @@ namespace
|
|||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption_phase3",
|
||||
"Current power consumption at phase 3.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
|
@ -86,13 +86,27 @@ namespace
|
|||
.set(VIFRange::Customer)
|
||||
);
|
||||
|
||||
/*
|
||||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption",
|
||||
"Calculated sum of power consumption of all phases.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::AnyPowerVIF)
|
||||
);
|
||||
*/
|
||||
|
||||
addNumericFieldWithCalculator(
|
||||
"current_power_consumption",
|
||||
"Calculated sum of power consumption of all phases.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL,
|
||||
Quantity::Power,
|
||||
"current_power_consumption_phase1_kw + current_power_consumption_phase2_kw + current_power_consumption_phase3_kw"
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,3 +114,9 @@ namespace
|
|||
// telegram=|5B445A149922992202378C20F6900F002C25BC9E0000BF48954821BC508D72992299225A140102F6003007102F2F040330F92A0004A9FF01FF24000004A9FF026A29000004A9FF03460600000DFD11063132333435362F2F2F2F2F2F|
|
||||
// {"media":"electricity","meter":"ebzwmbe","name":"Elen1","id":"22992299","total_energy_consumption_kwh":2816.304,"current_power_consumption_phase1_kw":0.09471,"current_power_consumption_phase2_kw":0.10602,"current_power_consumption_phase3_kw":0.01606,"customer":"654321","current_power_consumption_kw":0.21679,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Elen1;22992299;2816.304;0.21679;0.09471;0.10602;0.01606;1111-11-11 11:11.11
|
||||
|
||||
// Test: MyEl ebzwmbe 01135263 NOKEY
|
||||
// Comment: This is eBZ DD3 meter with a wMB-E01 wmbus radio converter module.
|
||||
// telegram=|4B445A146450000002378C20F3900F002C25F38B00006CBB07CEB215901172635213015A140102F3002007102F2F#04039D3E03000429E82201000DFD110E333632353331313031305A424531|
|
||||
// {"media":"electricity","meter":"ebzwmbe","name":"MyEl","id":"01135263","total_energy_consumption_kwh":212.637,"customer":"1EBZ0101135263","current_power_consumption_kw":null,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |MyEl;01135263;212.637;null;null;null;null;1111-11-11 11:11.11
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright (C) 2020-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("ehzp");
|
||||
di.setDefaultFields("name,id,total_energy_consumption_kwh,current_power_consumption_kw,total_energy_production_kwh,timestamp");
|
||||
di.setMeterType(MeterType::ElectricityMeter);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(MANUFACTURER_EMH, 0x02, 0x02);
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addStringField(
|
||||
"status",
|
||||
"Meter status. Includes both meter error field and tpl status field.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT |
|
||||
PrintProperty::STATUS | PrintProperty::JOIN_TPL_STATUS);
|
||||
|
||||
addOptionalCommonFields("on_time_h");
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_consumption",
|
||||
"The total energy consumption recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Energy,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::AnyEnergyVIF)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption",
|
||||
"Current power consumption.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::AnyPowerVIF)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_production",
|
||||
"The total energy production recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Energy,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::AnyEnergyVIF)
|
||||
.add(VIFCombinable::BackwardFlow)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Elen3 ehzp 55995599 NOKEY
|
||||
// telegram=|5344A8159955995502028C201D900F002C250C390000ED176BBBB1591ADB7A1D003007102F2F_0700583B74020000000007803CBCD70200000000000728B070200000000000042092A406002F2F2F2F2F2F2F2F2F|
|
||||
// {"media":"electricity","meter":"ehzp","name":"Elen3","id":"55995599","status":"OK","on_time_h":120.929444,"total_energy_consumption_kwh":41.1718,"current_power_consumption_kw":2.126,"total_energy_production_kwh":0.1863,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Elen3;55995599;41.1718;2.126;0.1863;1111-11-11 11:11.11
|
|
@ -192,5 +192,5 @@ namespace
|
|||
|
||||
// Test: Hetta elf 01885619 NOKEY
|
||||
// telegram=|51440186010905001837721956880101064004DA000020026CA9220E017799241103000C13641320000A2D00000A5A90060A5E800544050E77000001FD0C010A6564370AFD4731030A274907047F00000002|
|
||||
// {"media":"heat","meter":"elf","name":"Hetta","id":"01885619","status":"ERROR_FLAGS_2000000","meter_date":"2021-02-09","total_energy_consumption_kwh":3112.49977,"current_power_consumption_kw":0,"total_volume_m3":201.364,"total_energy_consumption_at_date_kwh":3047.8,"flow_temperature_c":69,"return_temperature_c":58,"external_temperature_c":37.64,"operating_time_h":4.993333,"version":"01","battery_v":3.31,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"heat","meter":"elf","name":"Hetta","id":"01885619","status":"ERROR_FLAGS_2000000","meter_date":"2021-02-09","total_energy_consumption_kwh":3112.49977,"current_power_consumption_kw":0,"total_volume_m3":201.364,"total_energy_consumption_at_date_kwh":3047.8,"flow_temperature_c":69,"return_temperature_c":58,"external_temperature_c":37.64,"operating_time_h":17976,"version":"01","battery_v":3.31,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Hetta;01885619;3112.49977;0;201.364;69;58;37.64;ERROR_FLAGS_2000000;1111-11-11 11:11.11
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
Copyright (C) 2017-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("em24");
|
||||
di.setDefaultFields(
|
||||
"name,id,"
|
||||
"total_energy_consumption_kwh,total_energy_production_kwh,"
|
||||
"total_reactive_energy_consumption_kvarh,total_reactive_energy_production_kvarh,"
|
||||
"total_apparent_energy_consumption_kvah,total_apparent_energy_production_kvah,"
|
||||
"timestamp");
|
||||
di.setMeterType(MeterType::ElectricityMeter);
|
||||
di.addLinkMode(LinkMode::C1);
|
||||
di.addDetection(MANUFACTURER_KAM, 0x02, 0x33);
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"status",
|
||||
"Status of meter.",
|
||||
PrintProperty::JSON | PrintProperty::IMPORTANT | PrintProperty::STATUS,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ErrorFlags),
|
||||
{
|
||||
{
|
||||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xff,
|
||||
"OK",
|
||||
{
|
||||
{ 0x01, "V_1_OVERFLOW" },
|
||||
{ 0x02, "V_2_OVERFLOW" },
|
||||
{ 0x04, "V_2_OVERFLOW" },
|
||||
{ 0x08, "I_1_OVERFLOW" },
|
||||
{ 0x10, "I_2_OVERFLOW" },
|
||||
{ 0x20, "I_3_OVERFLOW" },
|
||||
{ 0x40, "FREQUENCY" },
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"error",
|
||||
"Any errors currently being reported, this field is deprecated and replaced by the status field.",
|
||||
PrintProperty::JSON | PrintProperty::IMPORTANT | PrintProperty::DEPRECATED,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ErrorFlags),
|
||||
{
|
||||
{
|
||||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xff,
|
||||
"",
|
||||
{
|
||||
{ 0x01, "V_1_OVERFLOW" },
|
||||
{ 0x02, "V_2_OVERFLOW" },
|
||||
{ 0x04, "V_2_OVERFLOW" },
|
||||
{ 0x08, "I_1_OVERFLOW" },
|
||||
{ 0x10, "I_2_OVERFLOW" },
|
||||
{ 0x20, "I_3_OVERFLOW" },
|
||||
{ 0x40, "FREQUENCY" },
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_consumption",
|
||||
"The total energy consumption recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Energy,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::AnyEnergyVIF)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_production",
|
||||
"The total energy backward (production) recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Energy,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::AnyEnergyVIF)
|
||||
.add(VIFCombinable::BackwardFlow)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_reactive_energy_consumption",
|
||||
"The reactive total energy consumption recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Reactive_Energy,
|
||||
VifScaling::None,
|
||||
FieldMatcher::build()
|
||||
.set(DifVifKey("04FB8275"))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_reactive_energy_production",
|
||||
"The total reactive energy backward (production) recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Reactive_Energy,
|
||||
VifScaling::None,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(DifVifKey("04FB82F53C"))
|
||||
);
|
||||
|
||||
addNumericFieldWithCalculator(
|
||||
"total_apparent_energy_consumption",
|
||||
"Calculated: the total apparent energy consumption.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Apparent_Energy,
|
||||
"sqrt("
|
||||
" (total_energy_consumption_kwh * total_energy_consumption_kwh) "
|
||||
" +"
|
||||
" (total_reactive_energy_consumption_kvarh * total_reactive_energy_consumption_kvarh)"
|
||||
" )");
|
||||
|
||||
addNumericFieldWithCalculator(
|
||||
"total_apparent_energy_production",
|
||||
"Calculated: the total apparent energy production.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Apparent_Energy,
|
||||
R"STR(
|
||||
sqrt( (total_energy_production_kwh * total_energy_production_kwh) +
|
||||
(total_reactive_energy_production_kvarh * total_reactive_energy_production_kvarh) )
|
||||
)STR");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Elen em24 66666666 NOKEY
|
||||
// telegram=|35442D2C6666666633028D2070806A0520B4D378_0405F208000004FB82753F00000004853C0000000004FB82F53CCA01000001FD1722|
|
||||
// {"media":"electricity","meter":"em24","name":"Elen","id":"66666666","status":"I_3_OVERFLOW V_2_OVERFLOW","error":"V_2_OVERFLOW I_3_OVERFLOW","total_energy_consumption_kwh":229,"total_energy_production_kwh":0,"total_reactive_energy_consumption_kvarh":63,"total_reactive_energy_production_kvarh":458,"total_apparent_energy_consumption_kvah":237.507895,"total_apparent_energy_production_kvah":458,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Elen;66666666;229;0;63;458;237.507895;458;1111-11-11 11:11.11
|
|
@ -237,12 +237,12 @@ namespace
|
|||
.set(StorageNr(1))
|
||||
);
|
||||
|
||||
addOptionalCommonFields();
|
||||
addOptionalCommonFields("operating_time_h,on_time_h,meter_datetime");
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Heat enercal 00309924 NOKEY
|
||||
// Comment:
|
||||
// telegram=|688e8e6808017224993000e61e080406000000040681460600041488f2350084401426e02600025B2600025f220002622a00042247880200042647880200043B00000000042c00000000046d2d0ede2784406e000000008480406e00000000c4800006B1450600c48000143Be43500c4c000142fd22600c4c0006e00000000c480406e00000000c280006cc1271f00000000f416|
|
||||
// {"media":"heat","meter":"enercal","name":"Heat","id":"00309924","status":"OK","total_kwh":411265,"target_kwh":411057,"power_kw":0,"flow_m3h":0,"forward_c":38,"return_c":34,"difference_c":4.2,"total_m3":35354.96,"target_m3":35318.35,"subunit1_m3":25477.5,"subunit1_target_m3":25441.75,"subunit1_hca":0,"subunit1_target_hca":0,"subunit2_hca":0,"subunit2_target_hca":0,"target_date":"2022-07-01","operating_time_h":46.099722,"on_time_h":46.099722,"meter_datetime":"2022-07-30 14:45","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"heat","meter":"enercal","name":"Heat","id":"00309924","status":"OK","total_kwh":411265,"target_kwh":411057,"power_kw":0,"flow_m3h":0,"forward_c":38,"return_c":34,"difference_c":4.2,"total_m3":35354.96,"target_m3":35318.35,"subunit1_m3":25477.5,"subunit1_target_m3":25441.75,"subunit1_hca":0,"subunit1_target_hca":0,"subunit2_hca":0,"subunit2_target_hca":0,"target_date":"2022-07-01","operating_time_h":165959,"on_time_h":165959,"meter_datetime":"2022-07-30 14:45","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Heat;00309924;OK;411265.000000;411057.000000;35354.960000;35318.350000;1111-11-11 11:11.11
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
Copyright (C) 2020-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("esyswm");
|
||||
di.setDefaultFields("name,id,total_energy_consumption_kwh,current_power_consumption_kw,total_energy_production_kwh,total_energy_consumption_tariff1_kwh,total_energy_consumption_tariff2_kwh,current_power_consumption_phase1_kw,current_power_consumption_phase2_kw,current_power_consumption_phase3_kw,enhanced_id,timestamp");
|
||||
di.setMeterType(MeterType::ElectricityMeter);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(MANUFACTURER_ESY, 0x37, 0x30); // Wireless adapter for electricity meter.
|
||||
di.addDetection(MANUFACTURER_ESY, 0x02, 0x11);
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields("fabrication_no,enhanced_id,location");
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"location_hex",
|
||||
"Meter installed at this customer location. Deprecate field.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL | PrintProperty::DEPRECATED,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Location)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_consumption",
|
||||
"The total energy consumption recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Energy,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::EnergyWh)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption",
|
||||
"Calculated sum of power consumption of all phases.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::AnyPowerVIF)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_production",
|
||||
"The total energy production recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Energy,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::EnergyWh)
|
||||
.add(VIFCombinable::BackwardFlow)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_consumption_tariff1",
|
||||
"The total energy consumption recorded by this meter on tariff 1.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Energy,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::EnergyWh)
|
||||
.set(TariffNr(1))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_consumption_tariff2",
|
||||
"The total energy consumption recorded by this meter on tariff 2.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Energy,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::EnergyWh)
|
||||
.set(TariffNr(2))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption_phase1",
|
||||
"Current power consumption phase 1.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(DifVifKey("04A9FF01"))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption_phase2",
|
||||
"Current power consumption phase 2.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(DifVifKey("04A9FF02"))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"current_power_consumption_phase3",
|
||||
"Current power consumption phase 3.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD,
|
||||
Quantity::Power,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(DifVifKey("04A9FF03"))
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"version",
|
||||
"Static version information.",
|
||||
PrintProperty::JSON,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(DifVifKey("0DFD09"))
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Elen2 esyswm 77997799 NOKEY
|
||||
// Comment: Static telegram
|
||||
// telegram=|7B4479169977997730378C208B900F002C25E4EF0A002EA98E7D58B3ADC57299779977991611028B005087102F2F#0DFD090F34302e3030562030303030303030300D790E31323334353637383839595345310DFD100AAAAAAAAAAAAAAAAAAAAA0D780E31323334353637383930594553312F2F2F2F2F2F2F2F2F2F2F|
|
||||
// {"media":"electricity","meter":"esyswm","name":"Elen2","id":"77997799","fabrication_no":"1SEY0987654321","enhanced_id":"1ESY9887654321","location":"AAAAAAAAAAAAAAAAAAAA","location_hex":"AAAAAAAAAAAAAAAAAAAA","total_energy_consumption_kwh":null,"current_power_consumption_kw":null,"total_energy_production_kwh":null,"total_energy_consumption_tariff1_kwh":null,"total_energy_consumption_tariff2_kwh":null,"current_power_consumption_phase1_kw":null,"current_power_consumption_phase2_kw":null,"current_power_consumption_phase3_kw":null,"version":"00000000 V00.04","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Elen2;77997799;null;null;null;null;null;null;null;null;1ESY9887654321;1111-11-11 11:11.11
|
||||
|
||||
// Comment: Dynamic telegram
|
||||
// telegram=|7B4479169977997730378C20F0900F002C2549EE0A0077C19D3D1A08ABCD729977997779161102F0005007102F2F#0702F5C3FA000000000007823C5407000000000000841004E081020084200415000000042938AB000004A9FF01FA0A000004A9FF02050A000004A9FF03389600002F2F2F2F2F2F2F2F2F2F2F2F2F|
|
||||
// {"media":"electricity","meter":"esyswm","name":"Elen2","id":"77997799","fabrication_no":"1SEY0987654321","enhanced_id":"1ESY9887654321","location":"AAAAAAAAAAAAAAAAAAAA","location_hex":"AAAAAAAAAAAAAAAAAAAA","total_energy_consumption_kwh":1643.4165,"current_power_consumption_kw":0.43832,"total_energy_production_kwh":0.1876,"total_energy_consumption_tariff1_kwh":1643.2,"total_energy_consumption_tariff2_kwh":0.21,"current_power_consumption_phase1_kw":0.0281,"current_power_consumption_phase2_kw":0.02565,"current_power_consumption_phase3_kw":0.38456,"version":"00000000 V00.04","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Elen2;77997799;1643.4165;0.43832;0.1876;1643.2;0.21;0.0281;0.02565;0.38456;1ESY9887654321;1111-11-11 11:11.11
|
|
@ -43,8 +43,8 @@ namespace
|
|||
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
addOptionalCommonFields();
|
||||
addOptionalFlowRelatedFields();
|
||||
addOptionalCommonFields("enhanced_id,meter_datetime");
|
||||
addOptionalFlowRelatedFields("total_m3,total_backward_m3,volume_flow_m3h");
|
||||
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"status",
|
||||
|
@ -136,10 +136,10 @@ namespace
|
|||
// Test: SomeWater itron 12345698 NOKEY
|
||||
// Comment: Test ITRON T1 telegram not encrypted, which has no 2f2f markers.
|
||||
// telegram=|384497269856341203077AD90000A0#0413FD110000066D2C1AA1D521004413300F0000426CBF2C047F0000060C027F862A0E79678372082100|
|
||||
// {"media":"water","meter":"itron","name":"SomeWater","id":"12345698","enhanced_id":"002108728367","meter_datetime":"2022-01-21 01:26","total_m3":4.605,"status":"OK","target_m3":3.888,"target_date":"2021-12-31","unknown_a":"WOOTA_C060000","unknown_b":"WOOTB_2A86","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"water","meter":"itron","name":"SomeWater","id":"12345698","enhanced_id":"002108728367","meter_datetime":"2022-01-21 01:26:44","total_m3":4.605,"status":"OK","target_m3":3.888,"target_date":"2021-12-31","unknown_a":"WOOTA_C060000","unknown_b":"WOOTB_2A86","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |SomeWater;12345698;4.605;3.888;1111-11-11 11:11.11
|
||||
|
||||
// Test: MoreWater itron 18000056 NOKEY
|
||||
// telegram=|46449726560000183307725600001897263307AF0030052F2F_066D0E1015C82A000C13771252000C933C000000000B3B0400004C1361045200426CC12A03FD971C0000002F2F2F|
|
||||
// {"media":"water","meter":"itron","name":"MoreWater","id":"18000056","meter_datetime":"2022-10-08 21:16","total_m3":521.277,"total_backward_m3":0,"volume_flow_m3h":0.004,"status":"OK","target_m3":520.461,"target_date":"2022-10-01","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"water","meter":"itron","name":"MoreWater","id":"18000056","meter_datetime":"2022-10-08 21:16:14","total_m3":521.277,"total_backward_m3":0,"volume_flow_m3h":0.004,"status":"OK","target_m3":520.461,"target_date":"2022-10-01","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |MoreWater;18000056;521.277;520.461;1111-11-11 11:11.11
|
||||
|
|
|
@ -37,8 +37,8 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields();
|
||||
addOptionalFlowRelatedFields();
|
||||
addOptionalCommonFields("meter_date,fabrication_no,operating_time_h,on_time_h,on_time_at_error_h,meter_datetime");
|
||||
addOptionalFlowRelatedFields("total_m3,total_backward_m3,volume_flow_m3h");
|
||||
|
||||
/* If the meter is recently commissioned, the target water consumption value is bogus.
|
||||
The bits store 0xffffffff. Should we deal with this? Now a very large value is printed in the json.
|
||||
|
@ -210,5 +210,5 @@ namespace
|
|||
|
||||
// Test: Minowired minomess 57575757 NOKEY
|
||||
// telegram=|6874746808007257575757496A000712000000_0C7857575757046D2414DE280413000000000C943C000000004413FFFFFFFF426CFFFF840113FFFFFFFF82016CFFFFC40113FFFFFFFFC2016CFFFF840213FFFFFFFF82026CFFFF043B000000000422E62F000004260000000034220000000002FD1700001F5716|
|
||||
// {"media":"water","meter":"minomess","name":"Minowired","id":"57575757","fabrication_no":"57575757","operating_time_h":0,"on_time_h":3.406111,"on_time_at_error_h":0,"meter_datetime":"2022-08-30 20:36","total_m3":0,"total_backward_m3":0,"volume_flow_m3h":0,"target_m3":4294967.295,"target_date":"2127-15-31","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"water","meter":"minomess","name":"Minowired","id":"57575757","fabrication_no":"57575757","operating_time_h":0,"on_time_h":12262,"on_time_at_error_h":0,"meter_datetime":"2022-08-30 20:36","total_m3":0,"total_backward_m3":0,"volume_flow_m3h":0,"target_m3":4294967.295,"target_date":"2127-15-31","status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Minowired;57575757;0;4294967.295;OK;1111-11-11 11:11.11
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields();
|
||||
addOptionalCommonFields("on_time_h");
|
||||
|
||||
// Technical Description Multical 603 page 116 section 7.7.2 Information code types on serial communication.
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
|
@ -331,5 +331,5 @@ namespace
|
|||
|
||||
// Test: HeatInlet multical603 66666666 NOKEY
|
||||
// telegram=|5A442D2C66666666350C8D2066D0E16420C6A178_0406051C000004FF07393D000004FF08AE2400000414F7680000043B47000000042D1600000002596D14025DFD0804FF22000000000422E61A0000143B8C010000142D7C000000|
|
||||
// {"media":"heat volume at inlet","meter":"multical603","name":"HeatInlet","id":"66666666","on_time_h":1.912778,"status":"OK","total_energy_consumption_kwh":7173,"total_volume_m3":268.71,"volume_flow_m3h":0.071,"power_kw":2.2,"max_power_kw":12.4,"t1_temperature_c":52.29,"t2_temperature_c":23.01,"max_flow_m3h":0.396,"current_status":"","forward_energy_m3c":15673,"return_energy_m3c":9390,"energy_forward_kwh":15673,"energy_returned_kwh":9390,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"heat volume at inlet","meter":"multical603","name":"HeatInlet","id":"66666666","on_time_h":6886,"status":"OK","total_energy_consumption_kwh":7173,"total_volume_m3":268.71,"volume_flow_m3h":0.071,"power_kw":2.2,"max_power_kw":12.4,"t1_temperature_c":52.29,"t2_temperature_c":23.01,"max_flow_m3h":0.396,"current_status":"","forward_energy_m3c":15673,"return_energy_m3c":9390,"energy_forward_kwh":15673,"energy_returned_kwh":9390,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HeatInlet;66666666;7173;268.71;0.071;52.29;23.01;;1111-11-11 11:11.11
|
||||
|
|
|
@ -36,8 +36,6 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields();
|
||||
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"status",
|
||||
"Meter status. Reports OK if neither tpl sts nor error flags have bits set.",
|
||||
|
@ -59,11 +57,6 @@ namespace
|
|||
},
|
||||
}));
|
||||
|
||||
/*
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ErrorFlags)
|
||||
.add(VIFCombinable::StandardConformantDataContent)
|
||||
*/
|
||||
addNumericFieldWithExtractor(
|
||||
"current_temperature",
|
||||
"The current temperature.",
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields();
|
||||
addOptionalCommonFields("fabrication_no,software_version");
|
||||
|
||||
addStringField(
|
||||
"status",
|
||||
|
|
|
@ -36,8 +36,8 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields();
|
||||
addOptionalFlowRelatedFields();
|
||||
addOptionalCommonFields("meter_datetime");
|
||||
addOptionalFlowRelatedFields("total_m3,total_forward_m3,total_backward_m3,flow_temperature_c,volume_flow_m3h");
|
||||
|
||||
addStringField(
|
||||
"status",
|
||||
|
|
|
@ -27,15 +27,54 @@ namespace
|
|||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("qheat");
|
||||
di.setDefaultFields("name,id,total_energy_consumption_kwh,last_month_date,last_month_energy_consumption_kwh,timestamp");
|
||||
di.setMeterType(MeterType::HeatMeter);
|
||||
di.addLinkMode(LinkMode::C1);
|
||||
di.addDetection(MANUFACTURER_QDS, 0x04, 0x23);
|
||||
di.addDetection(MANUFACTURER_QDS, 0x04, 0x46);
|
||||
// MANUFACTURER_QDS, 0x37, 0x23 waiting for telegram for test-suite.
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"status",
|
||||
"Meter status.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::STATUS | PrintProperty::JOIN_TPL_STATUS,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ErrorFlags),
|
||||
{
|
||||
{
|
||||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
"OK",
|
||||
{
|
||||
// !!! Uncertain !!!
|
||||
// The documentation links to some intranet site, Qundis Error Codes Specification v1.7
|
||||
// at https://base/svn/sys/Meter/Gen/G5-5/PUBL/Gen55_SysSpec_Error-Codes_EN_v1.6_any.pdf
|
||||
|
||||
// Here is a table that might apply:
|
||||
// https://www.manualslib.com/manual/2046543/Qundis-Q-Heat-5-5-Us.html?page=5
|
||||
|
||||
{ 0x01, "NO_FLOW" }, // F0
|
||||
{ 0x02, "SUPPLY_SENSOR_INTERRUPTED" }, // F1
|
||||
{ 0x04, "RETURN_SENSOR_INTERRUPTED" }, // F2
|
||||
{ 0x08, "TEMPERATURE_ELECTRONICS_ERROR" }, // F3
|
||||
{ 0x10, "BATTERY_VOLTAGE_ERROR" }, // F4
|
||||
{ 0x20, "SHORT_CIRCUIT_SUPPLY_SENSOR" }, // F5
|
||||
{ 0x40, "SHORT_CIRCUIT_RETURN_SENSOR" }, // F6
|
||||
{ 0x80, "MEMORY_ERROR" }, // F7
|
||||
{ 0x100, "SABOTAGE" }, // F8 - F1,2,3,5,6 longer than 8 hours, latching error, no more measurements performed.
|
||||
{ 0x200, "ELECTRONICS_ERROR" }, // F9
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_energy_consumption",
|
||||
"The total energy consumption recorded by this meter.",
|
||||
|
@ -113,5 +152,11 @@ namespace
|
|||
|
||||
// Test: QHeato qheat 67228058 NOKEY
|
||||
// telegram=|3C449344957002372337725880226793442304DC0000200C05043900004C0500000000426C9F2CCC080551070000C2086CBE29326CFFFF046D280DB62A|
|
||||
// {"media":"heat","meter":"qheat","name":"QHeato","id":"67228058","total_energy_consumption_kwh":390.4,"last_month_date":"2021-09-30","last_month_energy_consumption_kwh":75.1,"last_year_date":"2020-12-31","last_year_energy_consumption_kwh":0,"device_date_time":"2021-10-22 13:40","device_error_date":"2127-15-31","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |QHeato;67228058;390.400000;2021-09-30;75.100000;1111-11-11 11:11.11
|
||||
// {"media":"heat","meter":"qheat","name":"QHeato","id":"67228058","status":"OK","total_energy_consumption_kwh":390.4,"last_month_date":"2021-09-30","last_month_energy_consumption_kwh":75.1,"last_year_date":"2020-12-31","last_year_energy_consumption_kwh":0,"device_date_time":"2021-10-22 13:40","device_error_date":"2127-15-31","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |QHeato;67228058;390.4;2021-09-30;75.1;1111-11-11 11:11.11
|
||||
|
||||
|
||||
// Test: Qheatoo qheat 67506579 NOKEY
|
||||
// telegram=|41449344796550674637727965506793444604dc0000200c0d000000004c0d00000000426cffffcc080d00000000c2086cdf2802fd170000326cffff046d3a0ddb29|
|
||||
// {"media":"heat","meter":"qheat","name":"Qheatoo","id":"67506579","status":"OK","total_energy_consumption_kwh":0,"last_month_date":"2022-08-31","last_month_energy_consumption_kwh":0,"last_year_date":"2127-15-31","last_year_energy_consumption_kwh":0,"device_date_time":"2022-09-27 13:58","device_error_date":"2127-15-31","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Qheatoo;67506579;0;2022-08-31;0;1111-11-11 11:11.11
|
||||
|
|
|
@ -112,8 +112,8 @@ namespace
|
|||
for (int i = 1; i <= 13; ++i)
|
||||
{
|
||||
string key, info;
|
||||
strprintf(key, "prev_%d_month", i);
|
||||
strprintf(info, "Energy consumption %d months back.", i);
|
||||
strprintf(&key, "prev_%d_month", i);
|
||||
strprintf(&info, "Energy consumption %d months back.", i);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
key,
|
||||
|
|
|
@ -35,8 +35,8 @@ namespace
|
|||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields();
|
||||
addOptionalFlowRelatedFields();
|
||||
addOptionalCommonFields("fabrication_no,operating_time_h,on_time_h,meter_datetime,meter_datetime_at_error");
|
||||
addOptionalFlowRelatedFields("total_m3,flow_temperature_c,return_temperature_c,flow_return_temperature_difference_c,volume_flow_m3h");
|
||||
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"status",
|
||||
|
@ -137,5 +137,5 @@ namespace
|
|||
|
||||
// Test: qualco qualcosonic 03016408 NOKEY
|
||||
// telegram=|76440907086401030B0d7a78000000046d030fB726346d0000010134fd170000000004200f13cf0104240f13cf0104863B0000000004863cdc0000000413B5150600042B86f1ffff043B030200000259c002025d2c05026194fd0c780864010384086d3B17Bf258408863B000000008408863c0B000000|
|
||||
// {"media":"heat/cooling load","meter":"qualcosonic","name":"qualco","id":"03016408","fabrication_no":"03016408","operating_time_h":2.34167,"on_time_h":2.34167,"meter_datetime":"2021-06-23 15:03","meter_datetime_at_error":"2000-01-01 00:00","total_m3":398.773,"flow_temperature_c":7.04,"return_temperature_c":13.24,"flow_return_temperature_difference_c":-6.2,"volume_flow_m3h":0.515,"status":"OK","total_heat_energy_kwh":0,"total_cooling_energy_kwh":220,"power_kw":-3.706,"target_datetime":"2021-05-31 23:59","target_heat_energy_kwh":0,"target_cooling_energy_kwh":11,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// {"media":"heat/cooling load","meter":"qualcosonic","name":"qualco","id":"03016408","fabrication_no":"03016408","operating_time_h":8430.013056,"on_time_h":8430.013056,"meter_datetime":"2021-06-23 15:03","meter_datetime_at_error":"2000-01-01 00:00","total_m3":398.773,"flow_temperature_c":7.04,"return_temperature_c":13.24,"flow_return_temperature_difference_c":-6.2,"volume_flow_m3h":0.515,"status":"OK","total_heat_energy_kwh":0,"total_cooling_energy_kwh":220,"power_kw":-3.706,"target_datetime":"2021-05-31 23:59","target_heat_energy_kwh":0,"target_cooling_energy_kwh":11,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |qualco;03016408;OK;0.000000;220.000000;-3.706000;2021-05-31 23:59;0.000000;11.000000;1111-11-11 11:11.11
|
||||
|
|
|
@ -160,14 +160,15 @@ namespace
|
|||
0xff,
|
||||
"OK",
|
||||
{
|
||||
{ 0x01, "TEMPSENSOR_1_BROKEN_CABLE" },
|
||||
{ 0x02, "TEMPSENSOR_1_SHORTCUT" },
|
||||
{ 0x04, "TEMPSENSOR_2_BROKEN_CABLE" },
|
||||
{ 0x08, "TEMPSENSOR_2_SHORTCUT" },
|
||||
{ 0x10, "FLOW_METER_FAILURE" },
|
||||
{ 0x20, "ELECTRONICS_DEFECT" },
|
||||
{ 0x40, "RESET" },
|
||||
{ 0x80, "BATTERY_WEAK" },
|
||||
// based on information published here: https://www.engelmann.de/wp-content/uploads/2022/10/1080621004_2022-10-12_BA_S3_ES_Comm_en.pdf
|
||||
{ 0x01 , "ERROR_TEMP_SENSOR_1_CABLE_BREAK" },
|
||||
{ 0x02 , "ERROR_TEMP_SENSOR_1_SHORT_CIRCUIT" },
|
||||
{ 0x04 , "ERROR_TEMP_SENSOR_2_CABLE_BREAK" },
|
||||
{ 0x08 , "ERROR_TEMP_SENSOR_2_SHORT_CIRCUIT" },
|
||||
{ 0x10 , "ERROR_FLOW_MEASUREMENT_SYSTEM_ERROR" },
|
||||
{ 0x20 , "ERROR_ELECTRONICS_DEFECT" },
|
||||
{ 0x40 , "OK_INSTRUMENT_RESET" },
|
||||
{ 0x80 , "OK_BATTERY_LOW" }
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -222,11 +223,10 @@ namespace
|
|||
// Comment:
|
||||
// telegram=|68B3B36808007257004820c51400046c100000047839803801040600000000041300000000042B00000000142B00000000043B00000000143B00000000025B1400025f15000261daff02235c00046d2c2ddc24440600000000441300000000426c000001fd171003fd0c05000084200600000000c420060000000084300600000000c430060000000084401300000000c44013000000008480401300000000c48040130000000084c0401300000000c4c0401300000000a216|
|
||||
// {"media":"heat","meter":"sensostar","name":"Heat","id":"20480057","meter_timestamp":"2022-04-28 13:44","total_kwh":0,"power_kw":0,"power_max_kw":0,"flow_water_m3h":0,"flow_water_max_m3h":0,"forward_c":20,"return_c":21,"difference_c":-0.38,"total_water_m3":0,"current_status":"FLOW_METER_FAILURE","reporting_date":"2000-00-00","energy_consumption_at_reporting_date_kwh":0,"consumption_1_months_ago_kwh":0,"consumption_2_months_ago_kwh":0,"consumption_3_months_ago_kwh":0,"consumption_4_months_ago_kwh":0,"consumption_5_months_ago_kwh":0,"consumption_6_months_ago_kwh":0,"consumption_7_months_ago_kwh":0,"consumption_8_months_ago_kwh":0,"consumption_9_months_ago_kwh":0,"consumption_10_months_ago_kwh":0,"consumption_11_months_ago_kwh":0,"consumption_12_months_ago_kwh":0,"consumption_13_months_ago_kwh":0,"consumption_14_months_ago_kwh":0,"consumption_15_months_ago_kwh":0,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Heat;20480057;0.000000;0.000000;FLOW_METER_FAILURE;2000-00-00;0.000000;1111-11-11 11:11.11
|
||||
// |Heat;20480057;0.000000;0.000000;ERROR_FLOW_MEASUREMENT_SYSTEM_ERROR;2000-00-00;0.000000;1111-11-11 11:11.11
|
||||
|
||||
// Test: WMZ sensostar 02752560 NOKEY
|
||||
// Comment: from "Sensostar U"
|
||||
//telegram=a444c5146025750200047ac20000202f2f046d2e26c62a040643160000041310f0050001fd1700426cbf2c4406570e00008401061f160000840206f6150000840306f5150000840406f3150000840506ea150000840606bf1500008407065214000084080692120000840906c5100000840a06570e0000840b06ca0b0000840c06da090000840d06ca080000840e06c8080000840f06c608000003fd0c05010002fd0b2111
|
||||
//{"media":"heat","meter":"sensostar","name":"WMZ","id":"02752560","meter_timestamp":"2022-10-06 06:46","total_kwh":5699,"total_water_m3":389.136,"current_status":"OK","reporting_date":"2021-12-31","energy_consumption_at_reporting_date_kwh":3671,"consumption_1_months_ago_kwh":5663,"consumption_2_months_ago_kwh":5622,"consumption_3_months_ago_kwh":5621,"consumption_4_months_ago_kwh":5619,"consumption_5_months_ago_kwh":5610,"consumption_6_months_ago_kwh":5567,"consumption_7_months_ago_kwh":5202,"consumption_8_months_ago_kwh":4754,"consumption_9_months_ago_kwh":4293,"consumption_10_months_ago_kwh":3671,"consumption_11_months_ago_kwh":3018,"consumption_12_months_ago_kwh":2522,"consumption_13_months_ago_kwh":2250,"consumption_14_months_ago_kwh":2248,"consumption_15_months_ago_kwh":2246,"timestamp":"1111-11-11 11:11.11"}
|
||||
//WMZ;02752560;5699.000000;389.136000;OK;1111-11-11 11:11.11
|
||||
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
Copyright (C) 2017-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2020 Avandorp (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
AquaMetro / Integra water meter "TOPAS ES KR"
|
||||
Models TOPAS ES KR 95077 95056 95345 95490 95373 95059 95065 95068 95071 95074 should be compatible. Only 95059 in one configuration tested.
|
||||
Identifies itself with Manufacturer "AMT" and Version "f1"
|
||||
Product leaflet and observation says the following values are sent:
|
||||
Current total volume
|
||||
Total volume at end of year-period day (that means: current total volume - total volume at end of year-period day = current year-periods volume up until now)
|
||||
Total backward volume on end of year-period day or total backward volume in current year-period. Backward volume remains untested (luckily only 0 values encountered).
|
||||
Date of end of last year-period day
|
||||
Total volume at end of last month-period dateTime
|
||||
DateTime of end of last month-period
|
||||
Current flow rate
|
||||
Battery life (days left)
|
||||
Water temperature
|
||||
|
||||
Example telegram:
|
||||
telegram=|4E44B40512345678F1077A310040052F2F|01FD08040C13991848004C1359423500CC101300000000CC201359423500426C7F2C0B3B00000002FD74DA10025AD300C4016D3B179F27CC011387124600|+2
|
||||
*/
|
||||
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("topaseskr");
|
||||
di.setDefaultFields(
|
||||
"name,id,total_m3,temperature_c,current_flow_m3h,volume_year_period_m3,"
|
||||
"reverse_volume_year_period_m3,meter_year_period_start_date,volume_month_period_m3,"
|
||||
"meter_month_period_start_datetime,timestamp");
|
||||
di.setMeterType(MeterType::WaterMeter);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(MANUFACTURER_AMT, 0x06, 0xf1);
|
||||
di.addDetection(MANUFACTURER_AMT, 0x07, 0xf1);
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalFlowRelatedFields("total_m3,access_counter");
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"temperature",
|
||||
"Current water temperature recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Temperature,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::FlowTemperature));
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"current_flow",
|
||||
"The current water flow.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Flow,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::VolumeFlow));
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"volume_year_period",
|
||||
"Volume up to end of last year-period.",
|
||||
PrintProperty::JSON | PrintProperty::IMPORTANT,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.set(StorageNr(1))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"reverse_volume_year_period",
|
||||
"Reverse volume in this year-period (?)",
|
||||
PrintProperty::JSON | PrintProperty::IMPORTANT,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.set(StorageNr(1))
|
||||
.set(TariffNr(1))
|
||||
);
|
||||
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"meter_year_period_start_date",
|
||||
"Meter date for year-period start.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Date)
|
||||
.set(StorageNr(1))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"volume_month_period",
|
||||
"Volume up to end of last month-period.",
|
||||
PrintProperty::JSON | PrintProperty::IMPORTANT,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.set(StorageNr(3))
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"meter_month_period_start_datetime",
|
||||
"Meter timestamp for month-period start.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::DateTime)
|
||||
.set(StorageNr(3))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"battery",
|
||||
"Remaining battery life in years.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::RemainingBattery),
|
||||
Unit::Year);
|
||||
}
|
||||
}
|
||||
|
||||
// Test: Witer topaseskr 78563412 NOKEY
|
||||
// telegram=|4E44B40512345678F1077A310040052F2F_01FD08040C13991848004C1359423500CC101300000000CC201359423500426C7F2C0B3B00000002FD74DA10025AD300C4016D3B179F27CC011387124600|
|
||||
// {"media":"water","meter":"topaseskr","name":"Witer","id":"78563412","total_m3":481.899,"access_counter":4,"temperature_c":21.1,"current_flow_m3h":0,"volume_year_period_m3":354.259,"reverse_volume_year_period_m3":0,"meter_year_period_start_date":"2019-12-31","volume_month_period_m3":461.287,"meter_month_period_start_datetime":"2020-07-31 23:59","battery_y":11.811331,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |Witer;78563412;481.899;21.1;0;354.259;0;2019-12-31;461.287;2020-07-31 23:59;1111-11-11 11:11.11
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
Copyright (C) 2021-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("unismart");
|
||||
di.setDefaultFields("name,id,total_m3,target_m3,timestamp");
|
||||
di.setMeterType(MeterType::GasMeter);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(MANUFACTURER_AMX, 0x03, 0x01);
|
||||
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addOptionalCommonFields("fabrication_no");
|
||||
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"status",
|
||||
"Status of meter?",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::STATUS,
|
||||
FieldMatcher::build()
|
||||
.set(DifVifKey("02FD74")),
|
||||
{
|
||||
{
|
||||
{
|
||||
"STATUS_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
"OK",
|
||||
{
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addStringFieldWithExtractorAndLookup(
|
||||
"other",
|
||||
"Other status of meter?",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::STATUS,
|
||||
FieldMatcher::build()
|
||||
.set(DifVifKey("017F")),
|
||||
{
|
||||
{
|
||||
{
|
||||
"OTHER_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xff,
|
||||
"",
|
||||
{
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"total_date_time",
|
||||
"Timestamp for this total measurement.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::DateTime)
|
||||
.set(IndexNr(1))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total",
|
||||
"The total gas consumption recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.add(VIFCombinable::UncorrectedMeterUnit)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"target_date_time",
|
||||
"Timestamp for gas consumption recorded at the beginning of this month.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::DateTime)
|
||||
.set(StorageNr(1))
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"target",
|
||||
"The total gas consumption recorded by this meter at the beginning of this month.",
|
||||
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.set(StorageNr(1))
|
||||
.add(VIFCombinable::UncorrectedMeterUnit)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"version",
|
||||
"Model version.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ModelVersion)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"supplier_info",
|
||||
"Supplier info?",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::SpecialSupplierInformation)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"parameter_set",
|
||||
"Meter configued with this parameter set?",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ParameterSet)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"meter_timestamp",
|
||||
"Timestamp when this measurement was sent.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::DateTime)
|
||||
.set(IndexNr(2))
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Test: GasMeter unismart 00043094 00000000000000000000000000000000
|
||||
// telegram=|6044B8059430040001037A1D005085E2B670BCF1A5C87E0C1A51DA18924EF984613DA2A9CD39D8F4C7208326C76D42DBEADF80D574192B71BD7C4F56A7F1513151768A9DB804883B28CB085CA2D0F7438C361CB9E2734712ED9BFBB2A14EF55208|
|
||||
// {"media":"gas","meter":"unismart","name":"GasMeter","id":"00043094","fabrication_no":"03162296","status":"STATUS_FLAGS_CF0","other":"OTHER_FLAGS_14","total_date_time":"2021-09-15 13:18","total_m3":917,"target_date_time":"2021-09-01 06:00","target_m3":911.32,"version":" 4GGU","supplier_info":"00","parameter_set":"02","meter_timestamp":"2021-09-15 13:18:30","timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |GasMeter;00043094;917;911.32;1111-11-11 11:11.11
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Copyright (C) 2019-2022 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"meters_common_implementation.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Driver : public virtual MeterCommonImplementation
|
||||
{
|
||||
Driver(MeterInfo &mi, DriverInfo &di);
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_energy_gj_ {};
|
||||
double curr_energy_gj_ {};
|
||||
double prev_energy_gj_ {};
|
||||
};
|
||||
|
||||
static bool ok = registerDriver([](DriverInfo&di)
|
||||
{
|
||||
di.setName("vario451");
|
||||
di.setMeterType(MeterType::WaterMeter);
|
||||
di.addLinkMode(LinkMode::C1);
|
||||
di.addLinkMode(LinkMode::T1);
|
||||
di.addDetection(MANUFACTURER_TCH, 0x04, 0x27);
|
||||
di.addDetection(MANUFACTURER_TCH, 0xc3, 0x27);
|
||||
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
|
||||
});
|
||||
|
||||
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
|
||||
{
|
||||
addNumericField("total",
|
||||
Quantity::Energy,
|
||||
PrintProperty::FIELD | PrintProperty::JSON,
|
||||
"The total energy consumption recorded by this meter.",
|
||||
SET_FUNC(total_energy_gj_, Unit::GJ),
|
||||
GET_FUNC(total_energy_gj_, Unit::GJ));
|
||||
|
||||
addNumericField("current",
|
||||
Quantity::Energy,
|
||||
PrintProperty::FIELD | PrintProperty::JSON,
|
||||
"Energy consumption so far in this billing period.",
|
||||
SET_FUNC(curr_energy_gj_, Unit::GJ),
|
||||
GET_FUNC(curr_energy_gj_, Unit::GJ));
|
||||
|
||||
addNumericField("previous",
|
||||
Quantity::Energy,
|
||||
PrintProperty::FIELD | PrintProperty::JSON,
|
||||
"Energy consumption in previous billing period.",
|
||||
SET_FUNC(prev_energy_gj_, Unit::GJ),
|
||||
GET_FUNC(prev_energy_gj_, Unit::GJ));
|
||||
|
||||
}
|
||||
|
||||
void Driver::processContent(Telegram *t)
|
||||
{
|
||||
// Unfortunately, the Techem Vario 4 Typ 4.5.1 is mostly a proprieatary protocol
|
||||
// simple wrapped inside a wmbus telegram since the ci-field is 0xa2.
|
||||
// Which means that the entire payload is manufacturer specific.
|
||||
|
||||
map<string,pair<int,DVEntry>> vendor_values;
|
||||
vector<uchar> content;
|
||||
|
||||
t->extractPayload(&content);
|
||||
if (content.size() < 9) return;
|
||||
|
||||
uchar prev_lo = content[3];
|
||||
uchar prev_hi = content[4];
|
||||
double prev = (256.0*prev_hi+prev_lo)/1000;
|
||||
|
||||
string prevs;
|
||||
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
int offset = t->parsed.size()+3;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " energy used in previous billing period (%f GJ)", prev);
|
||||
|
||||
uchar curr_lo = content[7];
|
||||
uchar curr_hi = content[8];
|
||||
double curr = (256.0*curr_hi+curr_lo)/1000;
|
||||
|
||||
string currs;
|
||||
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
|
||||
offset = t->parsed.size()+7;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " energy used in current billing period (%f GJ)", curr);
|
||||
|
||||
total_energy_gj_ = prev+curr;
|
||||
curr_energy_gj_ = curr;
|
||||
prev_energy_gj_ = prev;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Test: HeatMeter vario451 58234965 NOKEY
|
||||
// telegram=|374468506549235827C3A2_129F25383300A8622600008200800A2AF862115175552877A36F26C9AB1CB24400000004000000000004908002|
|
||||
// {"media":"heat","meter":"vario451","name":"HeatMeter","id":"58234965","total_kwh":6371.666667,"current_kwh":2729.444444,"previous_kwh":3642.222222,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
// |HeatMeter;58234965;6371.666667;2729.444444;3642.222222;1111-11-11 11:11.11
|
|
@ -411,9 +411,9 @@ bool parseDV(Telegram *t,
|
|||
|
||||
int count = ++dv_count[dv];
|
||||
if (count > 1) {
|
||||
strprintf(key, "%s_%d", dv.c_str(), count);
|
||||
strprintf(&key, "%s_%d", dv.c_str(), count);
|
||||
} else {
|
||||
strprintf(key, "%s", dv.c_str());
|
||||
strprintf(&key, "%s", dv.c_str());
|
||||
}
|
||||
DEBUG_PARSER("(dvparser debug) DifVif key is %s\n", key.c_str());
|
||||
|
||||
|
@ -1168,7 +1168,10 @@ bool DVEntry::extractDate(struct tm *out)
|
|||
// ..ss ssss
|
||||
int sec = (0x3f) & v[0];
|
||||
out->tm_sec = sec;
|
||||
// some daylight saving time decoding needed here....
|
||||
// There are also bits for day of week, week of year.
|
||||
// A bit for if daylight saving is in use or not and its offset.
|
||||
// A bit if it is a leap year.
|
||||
// I am unsure how to deal with this here..... TODO
|
||||
}
|
||||
|
||||
return ok;
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
|
||||
#define LIST_OF_VIF_RANGES \
|
||||
X(Volume,0x10,0x17,Quantity::Volume,Unit::M3) \
|
||||
X(OnTime,0x20,0x23, Quantity::Time, Unit::Second) \
|
||||
X(OperatingTime,0x24,0x27, Quantity::Time, Unit::Second) \
|
||||
X(OnTime,0x20,0x23, Quantity::Time, Unit::Hour) \
|
||||
X(OperatingTime,0x24,0x27, Quantity::Time, Unit::Hour) \
|
||||
X(VolumeFlow,0x38,0x3F, Quantity::Flow, Unit::M3H) \
|
||||
X(FlowTemperature,0x58,0x5B, Quantity::Temperature, Unit::C) \
|
||||
X(ReturnTemperature,0x5C,0x5F, Quantity::Temperature, Unit::C) \
|
||||
|
@ -41,7 +41,7 @@
|
|||
X(HeatCostAllocation,0x6E,0x6E, Quantity::HCA, Unit::HCA) \
|
||||
X(Date,0x6C,0x6C, Quantity::PointInTime, Unit::DateTimeLT) \
|
||||
X(DateTime,0x6D,0x6D, Quantity::PointInTime, Unit::DateTimeLT) \
|
||||
X(EnergyMJ,0x0E,0x0F, Quantity::Energy, Unit::MJ) \
|
||||
X(EnergyMJ,0x08,0x0F, Quantity::Energy, Unit::MJ) \
|
||||
X(EnergyWh,0x00,0x07, Quantity::Energy, Unit::KWH) \
|
||||
X(PowerW,0x28,0x2f, Quantity::Power, Unit::KW) \
|
||||
X(ActualityDuration,0x74,0x77, Quantity::Time, Unit::Hour) \
|
||||
|
@ -52,6 +52,7 @@
|
|||
X(ParameterSet,0x7D0B,0x7D0B, Quantity::Text, Unit::TXT) \
|
||||
X(ModelVersion,0x7D0C,0x7D0C, Quantity::Text, Unit::TXT) \
|
||||
X(SoftwareVersion,0x7D0F,0x7D0F, Quantity::Text, Unit::TXT) \
|
||||
X(Location,0x7D10,0x7D10, Quantity::Text, Unit::TXT) \
|
||||
X(Customer,0x7D11,0x7D11, Quantity::Text, Unit::TXT) \
|
||||
X(ErrorFlags,0x7D17,0x7D17, Quantity::Text, Unit::TXT) \
|
||||
X(DigitalInput,0x7D1B,0x7D1B, Quantity::Text, Unit::TXT) \
|
||||
|
@ -61,6 +62,7 @@
|
|||
X(Current,0x7D50,0x7D5F, Quantity::Current, Unit::Ampere) \
|
||||
X(ResetCounter,0x7D60,0x7D60, Quantity::Counter, Unit::COUNTER) \
|
||||
X(CumulationCounter,0x7D61,0x7D61, Quantity::Counter, Unit::COUNTER) \
|
||||
X(SpecialSupplierInformation,0x7D67,0x7D67, Quantity::Text, Unit::TXT) \
|
||||
X(RemainingBattery,0x7D74,0x7D74, Quantity::Time, Unit::Day) \
|
||||
X(DurationSinceReadout,0x7DAC,0x7DAC, Quantity::Time, Unit::Hour) \
|
||||
X(AnyVolumeVIF,0x00,0x00, Quantity::Volume, Unit::Unknown) \
|
||||
|
@ -279,6 +281,7 @@ struct IndexNr
|
|||
IndexNr(int n) : nr_(n) {}
|
||||
int intValue() { return nr_; }
|
||||
bool operator==(IndexNr s) { return nr_ == s.nr_; }
|
||||
bool operator!=(IndexNr s) { return nr_ != s.nr_; }
|
||||
|
||||
private:
|
||||
int nr_;
|
||||
|
@ -380,8 +383,9 @@ struct FieldMatcher
|
|||
SubUnitNr subunit_nr_from { 0 };
|
||||
SubUnitNr subunit_nr_to { 0 };
|
||||
|
||||
// If the telegram has multiple identical difvif entries, use entry with this index nr.
|
||||
// First entry has nr 1, which is the default value.
|
||||
// If the telegram has multiple identical difvif entries matching this field
|
||||
// and you want to catch the second matching entry, then set the index nr to 2.
|
||||
// The default is 1.
|
||||
IndexNr index_nr { 1 };
|
||||
|
||||
FieldMatcher() : active(false) { }
|
||||
|
|
604
src/formula.cc
604
src/formula.cc
|
@ -23,40 +23,112 @@
|
|||
#include<cmath>
|
||||
#include<string.h>
|
||||
|
||||
NumericFormula::~NumericFormula()
|
||||
NumericFormula::~NumericFormula() { }
|
||||
NumericFormulaConstant::~NumericFormulaConstant() { }
|
||||
NumericFormulaField::~NumericFormulaField() { }
|
||||
NumericFormulaPair::~NumericFormulaPair() { }
|
||||
NumericFormulaAddition::~NumericFormulaAddition() { }
|
||||
NumericFormulaSubtraction::~NumericFormulaSubtraction() { }
|
||||
NumericFormulaMultiplication::~NumericFormulaMultiplication() { }
|
||||
NumericFormulaDivision::~NumericFormulaDivision() { }
|
||||
NumericFormulaExponentiation::~NumericFormulaExponentiation() { }
|
||||
NumericFormulaSquareRoot::~NumericFormulaSquareRoot() { }
|
||||
|
||||
double NumericFormulaConstant::calculate(SIUnit to)
|
||||
{
|
||||
return siunit().convertTo(constant_, to);
|
||||
}
|
||||
|
||||
NumericFormulaConstant::~NumericFormulaConstant()
|
||||
double NumericFormulaField::calculate(SIUnit to_si_unit)
|
||||
{
|
||||
Unit field_unit = field_info_->defaultUnit();
|
||||
double val = meter_->getNumericValue(field_info_, field_unit);
|
||||
|
||||
const SIUnit& field_si_unit = toSIUnit(field_unit);
|
||||
|
||||
return field_si_unit.convertTo(val, to_si_unit);
|
||||
}
|
||||
|
||||
NumericFormulaField::~NumericFormulaField()
|
||||
double NumericFormulaAddition::calculate(SIUnit to)
|
||||
{
|
||||
double l = left_->calculate(to);
|
||||
double r = right_->calculate(to);
|
||||
|
||||
return l+r;
|
||||
}
|
||||
|
||||
NumericFormulaAddition::~NumericFormulaAddition()
|
||||
double NumericFormulaSubtraction::calculate(SIUnit to)
|
||||
{
|
||||
double l = left_->calculate(to);
|
||||
double r = right_->calculate(to);
|
||||
|
||||
return l-r;
|
||||
}
|
||||
|
||||
double NumericFormulaConstant::calculate(Unit to)
|
||||
double NumericFormulaMultiplication::calculate(SIUnit to)
|
||||
{
|
||||
return convert(constant_, unit(), to);
|
||||
double l = left_->calculate(left_->siunit());
|
||||
double r = right_->calculate(right_->siunit());
|
||||
double m = l*r;
|
||||
double v = siunit().convertTo(m, to);
|
||||
|
||||
if (isDebugEnabled())
|
||||
{
|
||||
debug("(formula) MUL %g (%s) %g (%s) --> %g --> %g %s\n",
|
||||
l, left_->siunit().info().c_str(),
|
||||
r, right_->siunit().info().c_str(),
|
||||
m,
|
||||
v, to.info().c_str());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
double NumericFormulaField::calculate(Unit to)
|
||||
double NumericFormulaDivision::calculate(SIUnit to)
|
||||
{
|
||||
return meter_->getNumericValue(field_info_, to);
|
||||
double l = left_->calculate(left_->siunit());
|
||||
double r = right_->calculate(right_->siunit());
|
||||
double d = l/r;
|
||||
double v = siunit().convertTo(d, to);
|
||||
|
||||
if (isDebugEnabled())
|
||||
{
|
||||
debug("(formula) DIV %g (%s) %g (%s) --> %g --> %g %s\n",
|
||||
l, left_->siunit().info().c_str(),
|
||||
r, right_->siunit().info().c_str(),
|
||||
d,
|
||||
v,
|
||||
to.info().c_str());
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
double NumericFormulaAddition::calculate(Unit to)
|
||||
double NumericFormulaExponentiation::calculate(SIUnit to)
|
||||
{
|
||||
double sum = 0;
|
||||
double l = left_->calculate(to);
|
||||
double r = right_->calculate(to);
|
||||
double p = pow(l,r);
|
||||
double v = siunit().convertTo(p, to);
|
||||
|
||||
sum += left_->calculate(to);
|
||||
sum += right_->calculate(to);
|
||||
debug("(formula) %g <-- %g <-- pow %g ^ %g\n", v, p, l, r);
|
||||
return v;
|
||||
}
|
||||
|
||||
return sum;
|
||||
double NumericFormulaSquareRoot::calculate(SIUnit to)
|
||||
{
|
||||
double i = inner_->calculate(inner_->siunit());
|
||||
double s = sqrt(i);
|
||||
double v = siunit().convertTo(s, to);
|
||||
|
||||
if (isDebugEnabled())
|
||||
{
|
||||
debug("(formula) SQRT %g (%s) --> %g --> %g %s\n",
|
||||
i, inner_->siunit().info().c_str(),
|
||||
s,
|
||||
v,
|
||||
to.info().c_str());
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const char *toString(TokenType tt)
|
||||
|
@ -67,6 +139,11 @@ const char *toString(TokenType tt)
|
|||
case TokenType::LPAR: return "LPAR";
|
||||
case TokenType::RPAR: return "RPAR";
|
||||
case TokenType::PLUS: return "PLUS";
|
||||
case TokenType::MINUS: return "MINUS";
|
||||
case TokenType::TIMES: return "TIMES";
|
||||
case TokenType::DIV: return "DIV";
|
||||
case TokenType::EXP: return "EXP";
|
||||
case TokenType::SQRT: return "SQRT";
|
||||
case TokenType::UNIT: return "UNIT";
|
||||
case TokenType::FIELD: return "FIELD";
|
||||
}
|
||||
|
@ -99,12 +176,27 @@ Unit Token::unit(const string &s)
|
|||
void FormulaImplementation::clear()
|
||||
{
|
||||
valid_ = true;
|
||||
while (!ops_.empty()) ops_.pop();
|
||||
op_stack_.clear();
|
||||
tokens_.clear();
|
||||
formula_ = "";
|
||||
meter_ = NULL;
|
||||
}
|
||||
|
||||
bool is_letter(char c)
|
||||
{
|
||||
return c >= 'a' && c <= 'z';
|
||||
}
|
||||
|
||||
bool is_letter_or_underscore(char c)
|
||||
{
|
||||
return c == '_' || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
bool is_letter_digit_or_underscore(char c)
|
||||
{
|
||||
return c == '_' || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
size_t FormulaImplementation::findSpace(size_t i)
|
||||
{
|
||||
size_t len = 0;
|
||||
|
@ -150,6 +242,61 @@ size_t FormulaImplementation::findPlus(size_t i)
|
|||
return 0;
|
||||
}
|
||||
|
||||
size_t FormulaImplementation::findMinus(size_t i)
|
||||
{
|
||||
if (i >= formula_.length()) return 0;
|
||||
|
||||
char c = formula_[i];
|
||||
if (c == '-') return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t FormulaImplementation::findTimes(size_t i)
|
||||
{
|
||||
if (i >= formula_.length()) return 0;
|
||||
|
||||
char c = formula_[i];
|
||||
if (c == '*') return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t FormulaImplementation::findDiv(size_t i)
|
||||
{
|
||||
if (i >= formula_.length()) return 0;
|
||||
|
||||
char c = formula_[i];
|
||||
if (c == '/') return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t FormulaImplementation::findExp(size_t i)
|
||||
{
|
||||
return 0;
|
||||
/*
|
||||
if (i >= formula_.length()) return 0;
|
||||
|
||||
char c = formula_[i];
|
||||
if (c == '^') return 1;
|
||||
|
||||
return 0;
|
||||
*/
|
||||
}
|
||||
|
||||
size_t FormulaImplementation::findSqrt(size_t i)
|
||||
{
|
||||
if (i+4 >= formula_.length()) return 0;
|
||||
|
||||
if (!strncmp(&formula_[i], "sqrt", 4) && !is_letter(formula_[i+4]))
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t FormulaImplementation::findLPar(size_t i)
|
||||
{
|
||||
if (i >= formula_.length()) return 0;
|
||||
|
@ -170,21 +317,6 @@ size_t FormulaImplementation::findRPar(size_t i)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool is_letter(char c)
|
||||
{
|
||||
return c >= 'a' && c <= 'z';
|
||||
}
|
||||
|
||||
bool is_letter_or_underscore(char c)
|
||||
{
|
||||
return c == '_' || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
bool is_letter_digit_or_underscore(char c)
|
||||
{
|
||||
return c == '_' || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
size_t FormulaImplementation::findUnit(size_t i)
|
||||
{
|
||||
if (i >= formula_.length()) return 0;
|
||||
|
@ -195,9 +327,9 @@ size_t FormulaImplementation::findUnit(size_t i)
|
|||
// All units start with a lower case a-z, followed by more letters and _ underscores.
|
||||
if (!is_letter(c)) return 0;
|
||||
|
||||
#define X(cname,lcname,hrname,quantity,explanation) \
|
||||
if ( (i+sizeof(#lcname)-1 <= len) && \
|
||||
!is_letter_or_underscore(formula_[i+sizeof(#lcname)-1]) && \
|
||||
#define X(cname,lcname,hrname,quantity,explanation) \
|
||||
if ( (i+sizeof(#lcname)-1 <= len) && \
|
||||
!is_letter_or_underscore(formula_[i+sizeof(#lcname)-1]) && \
|
||||
!strncmp(#lcname, formula_.c_str()+i, sizeof(#lcname)-1)) return sizeof(#lcname)-1;
|
||||
LIST_OF_UNITS
|
||||
#undef X
|
||||
|
@ -249,6 +381,21 @@ bool FormulaImplementation::tokenize()
|
|||
len = findPlus(i);
|
||||
if (len > 0) { tokens_.push_back(Token(TokenType::PLUS, i, len)); i+=len; continue; }
|
||||
|
||||
len = findMinus(i);
|
||||
if (len > 0) { tokens_.push_back(Token(TokenType::MINUS, i, len)); i+=len; continue; }
|
||||
|
||||
len = findTimes(i);
|
||||
if (len > 0) { tokens_.push_back(Token(TokenType::TIMES, i, len)); i+=len; continue; }
|
||||
|
||||
len = findDiv(i);
|
||||
if (len > 0) { tokens_.push_back(Token(TokenType::DIV, i, len)); i+=len; continue; }
|
||||
|
||||
len = findExp(i);
|
||||
if (len > 0) { tokens_.push_back(Token(TokenType::EXP, i, len)); i+=len; continue; }
|
||||
|
||||
len = findSqrt(i);
|
||||
if (len > 0) { tokens_.push_back(Token(TokenType::SQRT, i, len)); i+=len; continue; }
|
||||
|
||||
len = findUnit(i);
|
||||
if (len > 0) { tokens_.push_back(Token(TokenType::UNIT, i, len)); i+=len; continue; }
|
||||
|
||||
|
@ -279,7 +426,42 @@ size_t FormulaImplementation::parseOps(size_t i)
|
|||
if (tok->type == TokenType::PLUS)
|
||||
{
|
||||
size_t next = parseOps(i+1);
|
||||
handleAddition();
|
||||
handleAddition(tok);
|
||||
return next;
|
||||
}
|
||||
|
||||
if (tok->type == TokenType::MINUS)
|
||||
{
|
||||
size_t next = parseOps(i+1);
|
||||
handleSubtraction(tok);
|
||||
return next;
|
||||
}
|
||||
|
||||
if (tok->type == TokenType::TIMES)
|
||||
{
|
||||
size_t next = parseOps(i+1);
|
||||
handleMultiplication(tok);
|
||||
return next;
|
||||
}
|
||||
|
||||
if (tok->type == TokenType::DIV)
|
||||
{
|
||||
size_t next = parseOps(i+1);
|
||||
handleDivision(tok);
|
||||
return next;
|
||||
}
|
||||
|
||||
if (tok->type == TokenType::EXP)
|
||||
{
|
||||
size_t next = parseOps(i+1);
|
||||
handleExponentiation(tok);
|
||||
return next;
|
||||
}
|
||||
|
||||
if (tok->type == TokenType::SQRT)
|
||||
{
|
||||
size_t next = parseOps(i+1);
|
||||
handleSquareRoot(tok);
|
||||
return next;
|
||||
}
|
||||
|
||||
|
@ -322,13 +504,15 @@ size_t FormulaImplementation::parsePar(size_t i)
|
|||
|
||||
if (tok == NULL)
|
||||
{
|
||||
warning("No closing parenthesis found!\n");
|
||||
errors_.push_back(tostrprintf("Missing closing parenthesis at end of formula!\n"));
|
||||
valid_ = false;
|
||||
return i;
|
||||
}
|
||||
|
||||
if (tok->type != TokenType::RPAR)
|
||||
{
|
||||
warning("Expected parenthesis but got xx intead.\n");
|
||||
errors_.push_back("Expected closing parenthesis!\n"+tok->withMarker(formula_));
|
||||
valid_ = false;
|
||||
return i;
|
||||
}
|
||||
return i+1;
|
||||
|
@ -341,50 +525,105 @@ void FormulaImplementation::handleConstant(Token *number, Token *unit)
|
|||
|
||||
if (u == Unit::Unknown)
|
||||
{
|
||||
warning("Unknown unit \"%s\" in formula:\n%s\n", unit->vals(formula_).c_str(), formula_.c_str());
|
||||
errors_.push_back(tostrprintf("Unknown unit \"%s\"!\n%s",
|
||||
unit->vals(formula_).c_str(),
|
||||
unit->withMarker(formula_).c_str()));
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
//debug("(formula) push constant %f %s\n", c, unitToStringLowerCase(u).c_str());
|
||||
|
||||
doConstant(u, c);
|
||||
}
|
||||
|
||||
void FormulaImplementation::handleAddition()
|
||||
void FormulaImplementation::handleAddition(Token *tok)
|
||||
{
|
||||
//debug("(formula) push addition\n");
|
||||
SIUnit right_siunit = topOp()->siunit();
|
||||
SIUnit left_siunit = top2Op()->siunit();
|
||||
|
||||
if (!left_siunit.canConvertTo(right_siunit))
|
||||
{
|
||||
string lsis = left_siunit.str();
|
||||
string rsis = right_siunit.str();
|
||||
errors_.push_back(tostrprintf("Cannot add %s to %s!\n%s",
|
||||
left_siunit.info().c_str(),
|
||||
right_siunit.info().c_str(),
|
||||
tok->withMarker(formula_).c_str()));
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
doAddition();
|
||||
}
|
||||
|
||||
void FormulaImplementation::handleSubtraction(Token *tok)
|
||||
{
|
||||
SIUnit right_siunit = topOp()->siunit();
|
||||
SIUnit left_siunit = top2Op()->siunit();
|
||||
|
||||
if (!left_siunit.canConvertTo(right_siunit))
|
||||
{
|
||||
string lsis = left_siunit.str();
|
||||
string rsis = right_siunit.str();
|
||||
errors_.push_back(tostrprintf("Cannot subtract %s to %s!\n%s",
|
||||
left_siunit.info().c_str(),
|
||||
right_siunit.info().c_str(),
|
||||
tok->withMarker(formula_).c_str()));
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
doSubtraction();
|
||||
}
|
||||
|
||||
void FormulaImplementation::handleMultiplication(Token *tok)
|
||||
{
|
||||
// Any two units can be multiplied! You might not like the answer thought....
|
||||
doMultiplication();
|
||||
}
|
||||
|
||||
void FormulaImplementation::handleDivision(Token *tok)
|
||||
{
|
||||
// Any two units can be multiplied! You might not like the answer thought....
|
||||
doDivision();
|
||||
}
|
||||
|
||||
void FormulaImplementation::handleExponentiation(Token *tok)
|
||||
{
|
||||
// You can only exponentiate to a number.
|
||||
doExponentiation();
|
||||
}
|
||||
|
||||
void FormulaImplementation::handleSquareRoot(Token *tok)
|
||||
{
|
||||
doSquareRoot();
|
||||
}
|
||||
|
||||
void FormulaImplementation::handleField(Token *field)
|
||||
{
|
||||
string field_name = field->vals(formula_); // Full field: total_m3
|
||||
//debug("(formula) push field %s\n", field_name.c_str());
|
||||
string vname; // Without unit: total
|
||||
Unit unit;
|
||||
bool ok = extractUnit(field_name, &vname, &unit);
|
||||
string vname; // Without unit: total
|
||||
Unit named_unit; // The extracted unit: m3
|
||||
bool ok = extractUnit(field_name, &vname, &named_unit);
|
||||
|
||||
debug("(formula) handle field %s into %s %s\n", field_name.c_str(), vname.c_str(), unitToStringLowerCase(unit).c_str());
|
||||
debug("(formula) handle field %s into %s %s\n", field_name.c_str(), vname.c_str(), unitToStringLowerCase(named_unit).c_str());
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
warning("Could not extract a valid unit from field name \"%s\"\n", field_name.c_str());
|
||||
errors_.push_back("Cannot extra a valid unit from field name \""+field_name+"\"\n"+field->withMarker(formula_));
|
||||
return;
|
||||
}
|
||||
|
||||
Quantity q = toQuantity(unit);
|
||||
Quantity q = toQuantity(named_unit);
|
||||
FieldInfo *f = meter_->findFieldInfo(vname, q);
|
||||
|
||||
if (f == NULL)
|
||||
{
|
||||
warning("No such field found \"%s\" (%s %s)\n", field_name.c_str(), vname.c_str(), toString(q));
|
||||
errors_.push_back("No such field found \""+field_name+"\"\n"+field->withMarker(formula_));
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ok = doField(unit, meter_, f);
|
||||
if (!ok)
|
||||
{
|
||||
warning("Could not use field \"%s\" (%s %s)\n", field_name.c_str(), vname.c_str(), toString(q));
|
||||
return;
|
||||
}
|
||||
doField(named_unit, meter_, f);
|
||||
}
|
||||
|
||||
bool FormulaImplementation::go()
|
||||
|
@ -436,70 +675,160 @@ bool FormulaImplementation::parse(Meter *m, const string &f)
|
|||
{
|
||||
debug("(formula) %s\n", tree().c_str());
|
||||
}
|
||||
return true;
|
||||
return valid_;
|
||||
}
|
||||
|
||||
bool FormulaImplementation::valid()
|
||||
{
|
||||
return valid_ == true && ops_.size() == 1;
|
||||
return valid_ == true && op_stack_.size() == 1;
|
||||
}
|
||||
|
||||
string FormulaImplementation::errors()
|
||||
{
|
||||
string s;
|
||||
for (string& e : errors_) s += e;
|
||||
return s;
|
||||
}
|
||||
|
||||
double FormulaImplementation::calculate(Unit to)
|
||||
{
|
||||
if (!valid_)
|
||||
{
|
||||
warning("(formula) not valid returning nan!\n");
|
||||
return std::nan("");
|
||||
}
|
||||
if (ops_.size() != 1)
|
||||
{
|
||||
warning("(formula) does not have a single op (has %zu), not valid returning nan!\n", ops_.size());
|
||||
string t = tree();
|
||||
warning("Warning! Formula is not valid! Returning nan!\n%s\n", t.c_str());
|
||||
return std::nan("");
|
||||
}
|
||||
|
||||
return ops_.top().get()->calculate(to);
|
||||
}
|
||||
|
||||
bool FormulaImplementation::doConstant(Unit u, double c)
|
||||
{
|
||||
ops_.push(unique_ptr<NumericFormula>(new NumericFormulaConstant(u, c)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FormulaImplementation::doAddition()
|
||||
{
|
||||
if (ops_.size() < 2) { valid_ = false; return false;}
|
||||
|
||||
Unit right_unit = ops_.top()->unit();
|
||||
|
||||
unique_ptr<NumericFormula> right_node = std::move(ops_.top());
|
||||
ops_.pop();
|
||||
|
||||
Unit left_unit = ops_.top()->unit();
|
||||
|
||||
unique_ptr<NumericFormula> left_node = std::move(ops_.top());
|
||||
ops_.pop();
|
||||
|
||||
ops_.push(unique_ptr<NumericFormula>(new NumericFormulaAddition(left_unit, left_node, right_node)));
|
||||
|
||||
if (!canConvert(left_unit, right_unit))
|
||||
if (op_stack_.size() != 1)
|
||||
{
|
||||
valid_ = false;
|
||||
return false;
|
||||
string t = tree();
|
||||
warning("Warning! Formula is not valid! Multiple ops on stack! Returning nan!\n%s\n", t.c_str());
|
||||
return std::nan("");
|
||||
}
|
||||
|
||||
return true;
|
||||
return topOp()->calculate(toSIUnit(to));
|
||||
}
|
||||
|
||||
bool FormulaImplementation::doField(Unit u, Meter *m, FieldInfo *fi)
|
||||
void FormulaImplementation::doConstant(Unit u, double c)
|
||||
{
|
||||
if (!canConvert(u, fi->defaultUnit()))
|
||||
{
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
ops_.push(unique_ptr<NumericFormula>(new NumericFormulaField(u, m, fi)));
|
||||
return true;
|
||||
pushOp(new NumericFormulaConstant(u, c));
|
||||
}
|
||||
|
||||
void FormulaImplementation::doAddition()
|
||||
{
|
||||
assert(op_stack_.size() >= 2);
|
||||
|
||||
SIUnit right_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> right_node = popOp();
|
||||
|
||||
SIUnit left_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> left_node = popOp();
|
||||
|
||||
pushOp(new NumericFormulaAddition(left_siunit, left_node, right_node));
|
||||
|
||||
assert(left_siunit.canConvertTo(right_siunit));
|
||||
}
|
||||
|
||||
void FormulaImplementation::doSubtraction()
|
||||
{
|
||||
assert(op_stack_.size() >= 2);
|
||||
|
||||
SIUnit right_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> right_node = popOp();
|
||||
|
||||
SIUnit left_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> left_node = popOp();
|
||||
|
||||
pushOp(new NumericFormulaSubtraction(left_siunit, left_node, right_node));
|
||||
|
||||
assert(left_siunit.canConvertTo(right_siunit));
|
||||
}
|
||||
|
||||
void FormulaImplementation::doMultiplication()
|
||||
{
|
||||
assert(op_stack_.size() >= 2);
|
||||
|
||||
SIUnit right_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> right_node = popOp();
|
||||
|
||||
SIUnit left_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> left_node = popOp();
|
||||
|
||||
SIUnit mul_siunit = left_siunit.mul(right_siunit);
|
||||
|
||||
string lsis = left_siunit.info();
|
||||
string rsis = right_siunit.info();
|
||||
string msis = mul_siunit.info();
|
||||
|
||||
pushOp(new NumericFormulaMultiplication(mul_siunit, left_node, right_node));
|
||||
}
|
||||
|
||||
void FormulaImplementation::doDivision()
|
||||
{
|
||||
assert(op_stack_.size() >= 2);
|
||||
|
||||
SIUnit right_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> right_node = popOp();
|
||||
|
||||
SIUnit left_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> left_node = popOp();
|
||||
|
||||
SIUnit div_siunit = left_siunit.div(right_siunit);
|
||||
|
||||
string lsis = left_siunit.info();
|
||||
string rsis = right_siunit.info();
|
||||
string dsis = div_siunit.info();
|
||||
|
||||
debug("(formula) unit %s DIV %s ==> %s\n", lsis.c_str(), rsis.c_str(), dsis.c_str());
|
||||
|
||||
pushOp(new NumericFormulaDivision(div_siunit, left_node, right_node));
|
||||
}
|
||||
|
||||
void FormulaImplementation::doExponentiation()
|
||||
{
|
||||
assert(op_stack_.size() >= 2);
|
||||
|
||||
// SIUnit right_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> right_node = popOp();
|
||||
|
||||
SIUnit left_siunit = topOp()->siunit();
|
||||
|
||||
unique_ptr<NumericFormula> left_node = popOp();
|
||||
|
||||
pushOp(new NumericFormulaDivision(left_siunit, left_node, right_node));
|
||||
|
||||
// assert(canConvert(left_siunit, right_siunit));
|
||||
}
|
||||
|
||||
void FormulaImplementation::doSquareRoot()
|
||||
{
|
||||
assert(op_stack_.size() >= 1);
|
||||
|
||||
SIUnit inner_siunit = topOp()->siunit();
|
||||
|
||||
SIUnit siunit = inner_siunit.sqrt();
|
||||
|
||||
unique_ptr<NumericFormula> inner_node = popOp();
|
||||
|
||||
pushOp(new NumericFormulaSquareRoot(siunit, inner_node));
|
||||
}
|
||||
|
||||
|
||||
void FormulaImplementation::doField(Unit u, Meter *m, FieldInfo *fi)
|
||||
{
|
||||
SIUnit from_si_unit = toSIUnit(fi->defaultUnit());
|
||||
SIUnit to_si_unit = toSIUnit(u);
|
||||
assert(from_si_unit.canConvertTo(to_si_unit));
|
||||
pushOp(new NumericFormulaField(u, m, fi));
|
||||
}
|
||||
|
||||
Formula *newFormula()
|
||||
|
@ -517,38 +846,60 @@ FormulaImplementation::~FormulaImplementation()
|
|||
|
||||
string FormulaImplementation::str()
|
||||
{
|
||||
return ops_.top()->str();
|
||||
return topOp()->str();
|
||||
}
|
||||
|
||||
string FormulaImplementation::tree()
|
||||
{
|
||||
string t = ops_.top()->tree();
|
||||
if (t.back() == ' ') t.pop_back();
|
||||
return t;
|
||||
string s;
|
||||
|
||||
for (auto &op : op_stack_)
|
||||
{
|
||||
if (s.length() > 0) s += " | ";
|
||||
s += op->tree();
|
||||
if (s.back() == ' ') s.pop_back();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
string NumericFormulaConstant::str()
|
||||
{
|
||||
return tostrprintf("%.15g %s", constant_, unit());
|
||||
return tostrprintf("%.17g %s", constant_, siunit());
|
||||
}
|
||||
|
||||
string NumericFormulaConstant::tree()
|
||||
{
|
||||
return tostrprintf("<CONST %.15g %s> ", constant_, unitToStringLowerCase(unit()).c_str());
|
||||
Quantity q = siunit().quantity();
|
||||
Unit nearest = siunit().asUnit(q);
|
||||
string sis = siunit().str();
|
||||
|
||||
return tostrprintf("<CONST %.17g %s[%s]%s> ", constant_, unitToStringLowerCase(nearest).c_str(), sis.c_str(), toString(q));
|
||||
}
|
||||
|
||||
string NumericFormulaAddition::str()
|
||||
string NumericFormulaPair::str()
|
||||
{
|
||||
string left = left_->tree();
|
||||
string right = right_->tree();
|
||||
return left+" + "+right;
|
||||
return left+" "+op_+" "+right;
|
||||
}
|
||||
|
||||
string NumericFormulaAddition::tree()
|
||||
string NumericFormulaPair::tree()
|
||||
{
|
||||
string left = left_->tree();
|
||||
string right = right_->tree();
|
||||
return "<ADD "+left+right+"> ";
|
||||
return "<"+name_+" "+left+right+"> ";
|
||||
}
|
||||
|
||||
string NumericFormulaSquareRoot::str()
|
||||
{
|
||||
string inner = inner_->str();
|
||||
return "sqrt("+inner+")";
|
||||
}
|
||||
|
||||
string NumericFormulaSquareRoot::tree()
|
||||
{
|
||||
string inner = inner_->tree();
|
||||
return "<SQRT "+inner+"> ";
|
||||
}
|
||||
|
||||
string NumericFormulaField::str()
|
||||
|
@ -560,3 +911,40 @@ string NumericFormulaField::tree()
|
|||
{
|
||||
return "<FIELD "+field_info_->vname()+"_"+unitToStringLowerCase(field_info_->defaultUnit())+"> ";
|
||||
}
|
||||
|
||||
void FormulaImplementation::pushOp(NumericFormula *nf)
|
||||
{
|
||||
op_stack_.push_back(unique_ptr<NumericFormula>(nf));
|
||||
}
|
||||
|
||||
unique_ptr<NumericFormula> FormulaImplementation::popOp()
|
||||
{
|
||||
assert(op_stack_.size() > 0);
|
||||
unique_ptr<NumericFormula> nf = std::move(op_stack_.back());
|
||||
op_stack_.pop_back();
|
||||
return nf;
|
||||
}
|
||||
|
||||
NumericFormula * FormulaImplementation::topOp()
|
||||
{
|
||||
assert(op_stack_.size() > 0);
|
||||
return op_stack_.back().get();
|
||||
}
|
||||
|
||||
NumericFormula * FormulaImplementation::top2Op()
|
||||
{
|
||||
assert(op_stack_.size() > 1);
|
||||
return op_stack_[op_stack_.size()-2].get();
|
||||
}
|
||||
|
||||
string Token::withMarker(const string& formula)
|
||||
{
|
||||
string indent;
|
||||
size_t n = start;
|
||||
while (n > 0)
|
||||
{
|
||||
indent += " ";
|
||||
n--;
|
||||
}
|
||||
return formula+"\n"+indent+"^~~~~\n";
|
||||
}
|
||||
|
|
|
@ -28,9 +28,15 @@ struct FieldInfo;
|
|||
|
||||
struct Formula
|
||||
{
|
||||
// Parse and return false if parse failed.
|
||||
virtual bool parse(Meter *m, const std::string &f) = 0;
|
||||
// Returns false if the parse has failed and the formula is invalid.
|
||||
virtual bool valid() = 0;
|
||||
// Returns lines of error messages.
|
||||
virtual std::string errors() = 0;
|
||||
// Calculate the formula. Returns nan if it could not be calculated.
|
||||
virtual double calculate(Unit to) = 0;
|
||||
// Clear the formula, ie drop any parsed tree.
|
||||
virtual void clear() = 0;
|
||||
// Return a regenerated formula string.
|
||||
virtual std::string str() = 0;
|
||||
|
|
|
@ -25,22 +25,23 @@
|
|||
|
||||
struct NumericFormula
|
||||
{
|
||||
NumericFormula(Unit u) : unit_(u) { }
|
||||
Unit unit() { return unit_; }
|
||||
virtual double calculate(Unit to) = 0;
|
||||
NumericFormula(SIUnit u) : siunit_(u) { }
|
||||
SIUnit &siunit() { return siunit_; }
|
||||
// Calculate the formula and return the value in the given "to" unit.
|
||||
virtual double calculate(SIUnit to) = 0;
|
||||
virtual string str() = 0;
|
||||
virtual string tree() = 0;
|
||||
virtual ~NumericFormula() = 0;
|
||||
|
||||
private:
|
||||
|
||||
Unit unit_;
|
||||
SIUnit siunit_;
|
||||
};
|
||||
|
||||
struct NumericFormulaConstant : public NumericFormula
|
||||
{
|
||||
NumericFormulaConstant(Unit u, double c) : NumericFormula(u), constant_(c) {}
|
||||
double calculate(Unit to);
|
||||
double calculate(SIUnit to);
|
||||
string str();
|
||||
string tree();
|
||||
~NumericFormulaConstant();
|
||||
|
@ -53,7 +54,7 @@ struct NumericFormulaConstant : public NumericFormula
|
|||
struct NumericFormulaField : public NumericFormula
|
||||
{
|
||||
NumericFormulaField(Unit u, Meter *m, FieldInfo *fi) : NumericFormula(u), meter_(m), field_info_(fi) {}
|
||||
double calculate(Unit to);
|
||||
double calculate(SIUnit to);
|
||||
string str();
|
||||
string tree();
|
||||
~NumericFormulaField();
|
||||
|
@ -64,23 +65,106 @@ struct NumericFormulaField : public NumericFormula
|
|||
FieldInfo *field_info_;
|
||||
};
|
||||
|
||||
struct NumericFormulaAddition : public NumericFormula
|
||||
struct NumericFormulaPair : public NumericFormula
|
||||
{
|
||||
NumericFormulaAddition(Unit u,
|
||||
unique_ptr<NumericFormula> &a,
|
||||
unique_ptr<NumericFormula> &b)
|
||||
: NumericFormula(u), left_(std::move(a)), right_(std::move(b)) {}
|
||||
NumericFormulaPair(SIUnit siu,
|
||||
unique_ptr<NumericFormula> &a,
|
||||
unique_ptr<NumericFormula> &b,
|
||||
string name, string op)
|
||||
: NumericFormula(siu),
|
||||
left_(std::move(a)),
|
||||
right_(std::move(b)),
|
||||
name_(name),
|
||||
op_(op)
|
||||
{}
|
||||
|
||||
double calculate(Unit to);
|
||||
string str();
|
||||
string tree();
|
||||
~NumericFormulaPair();
|
||||
|
||||
~NumericFormulaAddition();
|
||||
|
||||
private:
|
||||
protected:
|
||||
|
||||
std::unique_ptr<NumericFormula> left_;
|
||||
std::unique_ptr<NumericFormula> right_;
|
||||
std::string name_;
|
||||
std::string op_;
|
||||
};
|
||||
|
||||
struct NumericFormulaAddition : public NumericFormulaPair
|
||||
{
|
||||
NumericFormulaAddition(SIUnit siu,
|
||||
unique_ptr<NumericFormula> &a,
|
||||
unique_ptr<NumericFormula> &b)
|
||||
: NumericFormulaPair(siu, a, b, "ADD", "+") {}
|
||||
|
||||
double calculate(SIUnit to);
|
||||
|
||||
~NumericFormulaAddition();
|
||||
};
|
||||
|
||||
struct NumericFormulaSubtraction : public NumericFormulaPair
|
||||
{
|
||||
NumericFormulaSubtraction(SIUnit siu,
|
||||
unique_ptr<NumericFormula> &a,
|
||||
unique_ptr<NumericFormula> &b)
|
||||
: NumericFormulaPair(siu, a, b, "SUB", "-") {}
|
||||
|
||||
double calculate(SIUnit to);
|
||||
|
||||
~NumericFormulaSubtraction();
|
||||
};
|
||||
|
||||
struct NumericFormulaMultiplication : public NumericFormulaPair
|
||||
{
|
||||
NumericFormulaMultiplication(SIUnit siu,
|
||||
unique_ptr<NumericFormula> &a,
|
||||
unique_ptr<NumericFormula> &b)
|
||||
: NumericFormulaPair(siu, a, b, "TIMES", "×") {}
|
||||
|
||||
double calculate(SIUnit to);
|
||||
|
||||
~NumericFormulaMultiplication();
|
||||
};
|
||||
|
||||
struct NumericFormulaDivision : public NumericFormulaPair
|
||||
{
|
||||
NumericFormulaDivision(SIUnit siu,
|
||||
unique_ptr<NumericFormula> &a,
|
||||
unique_ptr<NumericFormula> &b)
|
||||
: NumericFormulaPair(siu, a, b, "DIV", "÷") {}
|
||||
|
||||
double calculate(SIUnit to);
|
||||
|
||||
~NumericFormulaDivision();
|
||||
};
|
||||
|
||||
struct NumericFormulaExponentiation : public NumericFormulaPair
|
||||
{
|
||||
NumericFormulaExponentiation(SIUnit siu,
|
||||
unique_ptr<NumericFormula> &a,
|
||||
unique_ptr<NumericFormula> &b)
|
||||
: NumericFormulaPair(siu, a, b, "EXP", "^") {}
|
||||
|
||||
double calculate(SIUnit to);
|
||||
|
||||
~NumericFormulaExponentiation();
|
||||
};
|
||||
|
||||
struct NumericFormulaSquareRoot : public NumericFormula
|
||||
{
|
||||
NumericFormulaSquareRoot(SIUnit siu,
|
||||
unique_ptr<NumericFormula> &inner)
|
||||
: NumericFormula(siu), inner_(std::move(inner)) {}
|
||||
|
||||
double calculate(SIUnit to);
|
||||
string str();
|
||||
string tree();
|
||||
|
||||
~NumericFormulaSquareRoot();
|
||||
|
||||
private:
|
||||
|
||||
std::unique_ptr<NumericFormula> inner_;
|
||||
};
|
||||
|
||||
enum class TokenType
|
||||
|
@ -90,6 +174,11 @@ enum class TokenType
|
|||
RPAR,
|
||||
NUMBER,
|
||||
PLUS,
|
||||
MINUS,
|
||||
TIMES,
|
||||
DIV,
|
||||
EXP,
|
||||
SQRT,
|
||||
UNIT,
|
||||
FIELD
|
||||
};
|
||||
|
@ -108,25 +197,42 @@ struct Token
|
|||
string vals(const string &s);
|
||||
double val(const string &s);
|
||||
Unit unit(const string &s);
|
||||
|
||||
string withMarker(const string &s);
|
||||
};
|
||||
|
||||
struct FormulaImplementation : public Formula
|
||||
{
|
||||
bool parse(Meter *m, const string &f);
|
||||
bool valid();
|
||||
string errors();
|
||||
double calculate(Unit to);
|
||||
void clear();
|
||||
string str();
|
||||
string tree();
|
||||
|
||||
// Pushes a constant on the stack.
|
||||
bool doConstant(Unit u, double c);
|
||||
// Pushes a field read on the stack. Returns false if the field is not found in the meter.
|
||||
bool doField(Unit u, Meter *m, FieldInfo *fi);
|
||||
// Pops the two top nodes of the stack and pushes an addition (using these members) on the stack.
|
||||
// The target unit will be the first unit of the two operands. If incompatible units, then it will
|
||||
// return false.
|
||||
bool doAddition();
|
||||
void doConstant(Unit u, double c);
|
||||
// Pushes a field read on the stack.
|
||||
void doField(Unit u, Meter *m, FieldInfo *fi);
|
||||
// Pops the two top nodes of the stack and pushes an addition on the stack.
|
||||
// The target unit will be the first unit of the two operands.
|
||||
void doAddition();
|
||||
// Pops the two top nodes of the stack and pushes a subtraction on the stack.
|
||||
// The target unit will be the first unit of the two operands.
|
||||
void doSubtraction();
|
||||
// Pops the two top nodes of the stack and pushes a multiplication on the stack.
|
||||
// The target unit will be multiplication of the SI Units.
|
||||
void doMultiplication();
|
||||
// Pops the two top nodes of the stack and pushes a division on the stack.
|
||||
// The target unit will be first SIUnit divided by the second SIUnit.
|
||||
void doDivision();
|
||||
// Pops the two top nodes of the stack and pushes an exponentiation on the stack.
|
||||
// The target unit will be first SIUnit exponentiated.
|
||||
void doExponentiation();
|
||||
// Pops the single top node of the stack and pushes an squareroot on the stack.
|
||||
// The target unit will be SIUnit square rooted.
|
||||
void doSquareRoot();
|
||||
|
||||
~FormulaImplementation();
|
||||
|
||||
|
@ -138,6 +244,11 @@ private:
|
|||
size_t findNumber(size_t i);
|
||||
size_t findUnit(size_t i);
|
||||
size_t findPlus(size_t i);
|
||||
size_t findMinus(size_t i);
|
||||
size_t findTimes(size_t i);
|
||||
size_t findDiv(size_t i);
|
||||
size_t findExp(size_t i);
|
||||
size_t findSqrt(size_t i);
|
||||
size_t findLPar(size_t i);
|
||||
size_t findRPar(size_t i);
|
||||
size_t findField(size_t i);
|
||||
|
@ -147,14 +258,27 @@ private:
|
|||
size_t parsePar(size_t i);
|
||||
|
||||
void handleConstant(Token *number, Token *unit);
|
||||
void handleAddition();
|
||||
void handleAddition(Token *add);
|
||||
void handleSubtraction(Token *add);
|
||||
void handleMultiplication(Token *add);
|
||||
void handleDivision(Token *add);
|
||||
void handleExponentiation(Token *add);
|
||||
void handleSquareRoot(Token *add);
|
||||
void handleField(Token *field);
|
||||
|
||||
void pushOp(NumericFormula *nf);
|
||||
std::unique_ptr<NumericFormula> popOp();
|
||||
NumericFormula *topOp();
|
||||
NumericFormula *top2Op();
|
||||
|
||||
bool valid_ = true;
|
||||
std::stack<std::unique_ptr<NumericFormula>> ops_;
|
||||
std::vector<std::unique_ptr<NumericFormula>> op_stack_;
|
||||
std::vector<Token> tokens_;
|
||||
std::string formula_; // To be parsed.
|
||||
Meter *meter_; // To be referenced when parsing.
|
||||
|
||||
// Any errors during parsing are store here.
|
||||
std::vector<std::string> errors_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
28
src/main.cc
28
src/main.cc
|
@ -318,8 +318,9 @@ LIST_OF_METERS
|
|||
|
||||
struct TmpUnit
|
||||
{
|
||||
string suff, expl, name, quantity;
|
||||
string suff, expl, name, quantity, si;
|
||||
};
|
||||
|
||||
void list_units()
|
||||
{
|
||||
vector<TmpUnit> units;
|
||||
|
@ -327,12 +328,15 @@ void list_units()
|
|||
|
||||
// X(KVARH,kvarh,"kVARh",Reactive_Energy,"kilo volt amperes reactive hour")
|
||||
int width = 1;
|
||||
int total = 1;
|
||||
int total = 50;
|
||||
int tmp = 0;
|
||||
#define X(upn,lcn,name,quantity,expl) \
|
||||
#define X(upn,lcn,name,quantity,expl) \
|
||||
tmp = strlen(#lcn); if (tmp > width) { width = tmp; } \
|
||||
tmp = strlen(#lcn)+strlen(name)+strlen(expl)+3; if (tmp > total) { total = tmp; } \
|
||||
units.push_back( { #lcn, expl, name, #quantity } ); \
|
||||
{ \
|
||||
SIUnit si(Unit::upn); \
|
||||
string sis = si.str(); \
|
||||
units.push_back( { #lcn, expl, name, #quantity, sis } ); \
|
||||
} \
|
||||
quantities.insert(#quantity);
|
||||
LIST_OF_UNITS
|
||||
#undef X
|
||||
|
@ -354,7 +358,14 @@ LIST_OF_UNITS
|
|||
{
|
||||
if (u.quantity == q)
|
||||
{
|
||||
printf("%-*s %s (%s)\n", width, u.suff.c_str(), u.expl.c_str(), u.name.c_str());
|
||||
string s = tostrprintf("%-*s %s (%s)", width, u.suff.c_str(), u.expl.c_str(), u.name.c_str());
|
||||
int left = strlen_utf8(s.c_str());
|
||||
string si = tostrprintf("%s", u.si.c_str());
|
||||
int right = strlen_utf8(si.c_str());
|
||||
int space = 50-left-right;
|
||||
printf("%s", s.c_str());
|
||||
while (space > 0 && space < 100) { printf(" "); space--; }
|
||||
printf("%s\n", si.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -443,9 +454,12 @@ void setup_log_file(Configuration *config)
|
|||
|
||||
void setup_meters(Configuration *config, MeterManager *manager)
|
||||
{
|
||||
for (auto &m : config->meters)
|
||||
for (MeterInfo &m : config->meters)
|
||||
{
|
||||
m.conversions = config->conversions;
|
||||
m.extra_calculated_fields.insert(m.extra_calculated_fields.end(),
|
||||
config->extra_calculated_fields.begin(),
|
||||
config->extra_calculated_fields.end());
|
||||
|
||||
if (m.usesPolling() || driverNeedsPolling(m.driver, m.driver_name))
|
||||
{
|
||||
|
|
|
@ -29,9 +29,6 @@
|
|||
#define METER_DETECTION \
|
||||
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
|
||||
X(EURISII, MANUFACTURER_INE, 0x08, 0x55) \
|
||||
X(EHZP, MANUFACTURER_EMH, 0x02, 0x02) \
|
||||
X(ESYSWM, MANUFACTURER_ESY, 0x37, 0x30) \
|
||||
X(EM24, MANUFACTURER_KAM, 0x02, 0x33) \
|
||||
X(EVO868, MANUFACTURER_MAD, 0x07, 0x50) \
|
||||
X(FHKVDATAIII,MANUFACTURER_TCH, 0x80, 0x69) \
|
||||
X(FHKVDATAIII,MANUFACTURER_TCH, 0x80, 0x94) \
|
||||
|
@ -76,11 +73,6 @@
|
|||
X(RFMTX1, MANUFACTURER_BMT, 0x07, 0x05) \
|
||||
X(TSD2, MANUFACTURER_TCH, 0xf0, 0x76) \
|
||||
X(SONTEX868, MANUFACTURER_SON, 0x08, 0x16) \
|
||||
X(TOPASESKR, MANUFACTURER_AMT, 0x06, 0xf1) \
|
||||
X(TOPASESKR, MANUFACTURER_AMT, 0x07, 0xf1) \
|
||||
X(UNISMART, MANUFACTURER_AMX, 0x03, 0x01) \
|
||||
X(VARIO451, MANUFACTURER_TCH, 0x04, 0x27) \
|
||||
X(VARIO451, MANUFACTURER_TCH, 0xc3, 0x27) \
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"dvparser.h"
|
||||
#include"meters.h"
|
||||
#include"meters_common_implementation.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"util.h"
|
||||
|
||||
struct MeterEHZP : public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterEHZP(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double currentPowerConsumption(Unit u);
|
||||
double totalEnergyProduction(Unit u);
|
||||
double currentPowerProduction(Unit u);
|
||||
|
||||
private:
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_energy_kwh_ {};
|
||||
double current_power_kw_ {};
|
||||
double total_energy_returned_kwh_ {};
|
||||
double current_power_returned_kw_ {};
|
||||
double on_time_h_ {};
|
||||
};
|
||||
|
||||
MeterEHZP::MeterEHZP(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, "ehzp")
|
||||
{
|
||||
setMeterType(MeterType::ElectricityMeter);
|
||||
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_NO_IV);
|
||||
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
addPrint("total_energy_consumption", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyConsumption(u); },
|
||||
"The total energy consumption recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("current_power_consumption", Quantity::Power,
|
||||
[&](Unit u){ return currentPowerConsumption(u); },
|
||||
"Current power consumption.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_energy_production", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyProduction(u); },
|
||||
"The total energy production recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("on_time", Quantity::Time,
|
||||
[&](Unit u){ assertQuantity(u, Quantity::Time);
|
||||
return convert(on_time_h_, Unit::Hour, u); },
|
||||
"Device on time.",
|
||||
PrintProperty::JSON);
|
||||
}
|
||||
|
||||
shared_ptr<Meter> createEHZP(MeterInfo &mi)
|
||||
{
|
||||
return shared_ptr<Meter>(new MeterEHZP(mi));
|
||||
}
|
||||
|
||||
double MeterEHZP::totalEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_energy_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterEHZP::currentPowerConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Power);
|
||||
return convert(current_power_kw_, Unit::KW, u);
|
||||
}
|
||||
|
||||
double MeterEHZP::totalEnergyProduction(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_energy_returned_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterEHZP::currentPowerProduction(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Power);
|
||||
return convert(current_power_returned_kw_, Unit::KW, u);
|
||||
}
|
||||
|
||||
void MeterEHZP::processContent(Telegram *t)
|
||||
{
|
||||
/*
|
||||
(ehzp) 26: 07 dif (64 Bit Integer/Binary Instantaneous value)
|
||||
(ehzp) 27: 00 vif (Energy mWh)
|
||||
(ehzp) 28: * 583B740200000000 total energy (41.171800 kwh)
|
||||
(ehzp) 30: 07 dif (64 Bit Integer/Binary Instantaneous value)
|
||||
(ehzp) 31: 80 vif (Energy mWh)
|
||||
(ehzp) 32: 3C vife (backward flow)
|
||||
(ehzp) 33: * BCD7020000000000 total energy returned (0.186300 kwh)
|
||||
(ehzp) 3b: 07 dif (64 Bit Integer/Binary Instantaneous value)
|
||||
(ehzp) 3c: 28 vif (Power mW)
|
||||
(ehzp) 3d: * B070200000000000 current power (2.126000 kw)
|
||||
(ehzp) 45: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(ehzp) 46: 20 vif (On time seconds)
|
||||
(ehzp) 47: * 92A40600 on time (120.929444 h)
|
||||
*/
|
||||
int offset;
|
||||
string key;
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::EnergyWh, 0, 0, &key, &t->dv_entries))
|
||||
{
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_energy_kwh_);
|
||||
t->addMoreExplanation(offset, " total energy (%f kwh)", total_energy_kwh_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::PowerW, 0, 0, &key, &t->dv_entries))
|
||||
{
|
||||
extractDVdouble(&t->dv_entries, key, &offset, ¤t_power_kw_);
|
||||
t->addMoreExplanation(offset, " current power (%f kw)", current_power_kw_);
|
||||
}
|
||||
|
||||
extractDVdouble(&t->dv_entries, "07803C", &offset, &total_energy_returned_kwh_);
|
||||
t->addMoreExplanation(offset, " total energy returned (%f kwh)", total_energy_returned_kwh_);
|
||||
|
||||
extractDVdouble(&t->dv_entries, "0420", &offset, &on_time_h_);
|
||||
t->addMoreExplanation(offset, " on time (%f h)", on_time_h_);
|
||||
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"dvparser.h"
|
||||
#include"meters.h"
|
||||
#include"meters_common_implementation.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"util.h"
|
||||
|
||||
#include<cmath>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
constexpr uint8_t ERROR_CODE_VOLTAGE_PHASE_1_OVERFLOW=0x01;
|
||||
constexpr uint8_t ERROR_CODE_VOLTAGE_PHASE_2_OVERFLOW=0x02;
|
||||
constexpr uint8_t ERROR_CODE_VOLTAGE_PHASE_3_OVERFLOW=0x04;
|
||||
|
||||
constexpr uint8_t ERROR_CODE_CURRENT_PHASE_1_OVERFLOW=0x08;
|
||||
constexpr uint8_t ERROR_CODE_CURRENT_PHASE_2_OVERFLOW=0x10;
|
||||
constexpr uint8_t ERROR_CODE_CURRENT_PHASE_3_OVERFLOW=0x20;
|
||||
|
||||
constexpr uint8_t ERROR_CODE_FREQUENCY_OUT_OF_RANGE=0x40;
|
||||
|
||||
|
||||
struct MeterEM24 : public virtual MeterCommonImplementation {
|
||||
MeterEM24(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double totalEnergyProduction(Unit u);
|
||||
|
||||
double totalReactiveEnergyConsumption(Unit u);
|
||||
double totalReactiveEnergyProduction(Unit u);
|
||||
|
||||
double totalApparentEnergyConsumption(Unit u);
|
||||
double totalApparentEnergyProduction(Unit u);
|
||||
|
||||
string status();
|
||||
|
||||
private:
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_true_energy_consumption_kwh_ {};
|
||||
double total_true_energy_production_kwh_ {};
|
||||
|
||||
double total_reactive_energy_consumption_kvarh_ {};
|
||||
double total_reactive_energy_production_kvarh_ {};
|
||||
|
||||
uint8_t error_codes_ {};
|
||||
};
|
||||
|
||||
shared_ptr<Meter> createEM24(MeterInfo &mi)
|
||||
{
|
||||
return shared_ptr<Meter>(new MeterEM24(mi));
|
||||
}
|
||||
|
||||
MeterEM24::MeterEM24(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, "em24")
|
||||
{
|
||||
setMeterType(MeterType::ElectricityMeter);
|
||||
|
||||
setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
|
||||
|
||||
addLinkMode(LinkMode::C1);
|
||||
|
||||
addPrint("total_energy_consumption", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyConsumption(u); },
|
||||
"The total energy consumption recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_energy_production", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyProduction(u); },
|
||||
"The total energy production recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_reactive_energy_consumption", Quantity::Reactive_Energy,
|
||||
[&](Unit u){ return totalReactiveEnergyConsumption(u); },
|
||||
"The total reactive energy consumption recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_reactive_energy_production", Quantity::Reactive_Energy,
|
||||
[&](Unit u){ return totalReactiveEnergyProduction(u); },
|
||||
"The total reactive energy production recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_apparent_energy_consumption", Quantity::Apparent_Energy,
|
||||
[&](Unit u){ return totalApparentEnergyConsumption(u); },
|
||||
"The total apparent energy consumption by calculation.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_apparent_energy_production", Quantity::Apparent_Energy,
|
||||
[&](Unit u){ return totalApparentEnergyProduction(u); },
|
||||
"The total apparent energy production by calculation.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("errors", Quantity::Text,
|
||||
[&](){ return status(); },
|
||||
"Any errors currently being reported.",
|
||||
PrintProperty::JSON);
|
||||
}
|
||||
|
||||
double MeterEM24::totalEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_true_energy_consumption_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterEM24::totalEnergyProduction(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_true_energy_production_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterEM24::totalReactiveEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Reactive_Energy);
|
||||
return convert(total_reactive_energy_consumption_kvarh_, Unit::KVARH, u);
|
||||
}
|
||||
|
||||
double MeterEM24::totalReactiveEnergyProduction(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Reactive_Energy);
|
||||
return convert(total_reactive_energy_production_kvarh_, Unit::KVARH, u);
|
||||
}
|
||||
|
||||
double MeterEM24::totalApparentEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Apparent_Energy);
|
||||
return convert(
|
||||
sqrt(
|
||||
pow(total_true_energy_consumption_kwh_, 2) +
|
||||
pow(total_reactive_energy_consumption_kvarh_, 2)
|
||||
)
|
||||
, Unit::KVAH, u);
|
||||
}
|
||||
|
||||
double MeterEM24::totalApparentEnergyProduction(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Apparent_Energy);
|
||||
return convert(
|
||||
sqrt(
|
||||
pow(total_true_energy_production_kwh_, 2) +
|
||||
pow(total_reactive_energy_production_kvarh_, 2)
|
||||
)
|
||||
, Unit::KVAH, u);
|
||||
}
|
||||
|
||||
void MeterEM24::processContent(Telegram *t)
|
||||
{
|
||||
int offset;
|
||||
|
||||
// 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
// 05 vif (Energy 10² Wh)
|
||||
extractDVdouble(&t->dv_entries, "0405", &offset, &total_true_energy_consumption_kwh_);
|
||||
t->addMoreExplanation(offset, " total power (%f kwh)", total_true_energy_consumption_kwh_);
|
||||
|
||||
// 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
// FB vif (First extension of VIF-codes)
|
||||
// 82 vife (Reserved)
|
||||
// 75 vife (Cold / Warm Temperature Limit 10^-2 Celsius)
|
||||
extractDVdouble(&t->dv_entries, "04FB8275", &offset, &total_reactive_energy_consumption_kvarh_);
|
||||
t->addMoreExplanation(offset, " total reactive power (%f kvarh)", total_reactive_energy_consumption_kvarh_);
|
||||
|
||||
// 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
// 85 vif (Energy 10² Wh)
|
||||
// 3C vife (backward flow)
|
||||
extractDVdouble(&t->dv_entries, "04853C", &offset, &total_true_energy_production_kwh_);
|
||||
t->addMoreExplanation(offset, " total power (%f kwh)", total_true_energy_production_kwh_);
|
||||
|
||||
// 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
// FB vif (First extension of VIF-codes)
|
||||
// 82 vife (Reserved)
|
||||
// F5 vife (Cold / Warm Temperature Limit 10^-2 Celsius)
|
||||
// 3C vife (Reserved)
|
||||
extractDVdouble(&t->dv_entries, "04FB82F53C", &offset, &total_reactive_energy_production_kvarh_);
|
||||
t->addMoreExplanation(offset, " total reactive power (%f kvarh)", total_reactive_energy_production_kvarh_);
|
||||
|
||||
// 01 dif (8 Bit Integer/Binary Instantaneous value)
|
||||
// FD vif (Second extension of VIF-codes)
|
||||
// 17 vife (Error flags (binary))
|
||||
extractDVuint8(&t->dv_entries, "01FD17", &offset, &error_codes_);
|
||||
t->addMoreExplanation(offset, " error codes (%s)", status().c_str());
|
||||
}
|
||||
|
||||
string MeterEM24::status()
|
||||
{
|
||||
string s;
|
||||
if (error_codes_ & ERROR_CODE_VOLTAGE_PHASE_1_OVERFLOW) s.append("V 1 OVERFLOW ");
|
||||
if (error_codes_ & ERROR_CODE_VOLTAGE_PHASE_2_OVERFLOW) s.append("V 2 OVERFLOW ");
|
||||
if (error_codes_ & ERROR_CODE_VOLTAGE_PHASE_3_OVERFLOW) s.append("V 3 OVERFLOW ");
|
||||
if (error_codes_ & ERROR_CODE_CURRENT_PHASE_1_OVERFLOW) s.append("I 1 OVERFLOW ");
|
||||
if (error_codes_ & ERROR_CODE_CURRENT_PHASE_2_OVERFLOW) s.append("I 2 OVERFLOW ");
|
||||
if (error_codes_ & ERROR_CODE_CURRENT_PHASE_3_OVERFLOW) s.append("I 3 OVERFLOW ");
|
||||
if (error_codes_ & ERROR_CODE_FREQUENCY_OUT_OF_RANGE) s.append("FREQUENCY ");
|
||||
if (s.length() > 0) {
|
||||
s.pop_back(); // Remove final space
|
||||
return s;
|
||||
}
|
||||
return s;
|
||||
}
|
|
@ -1,300 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2020 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"dvparser.h"
|
||||
#include"meters.h"
|
||||
#include"meters_common_implementation.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"util.h"
|
||||
|
||||
#include<algorithm>
|
||||
|
||||
struct MeterESYSWM : public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterESYSWM(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double totalEnergyConsumptionTariff1(Unit u);
|
||||
double totalEnergyConsumptionTariff2(Unit u);
|
||||
double currentPowerConsumption(Unit u);
|
||||
double currentPowerConsumptionPhase1(Unit u);
|
||||
double currentPowerConsumptionPhase2(Unit u);
|
||||
double currentPowerConsumptionPhase3(Unit u);
|
||||
double totalEnergyProduction(Unit u);
|
||||
double currentPowerProduction(Unit u);
|
||||
|
||||
private:
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_energy_kwh_ {};
|
||||
double total_energy_tariff1_kwh_ {};
|
||||
double total_energy_tariff2_kwh_ {};
|
||||
double current_power_kw_ {};
|
||||
double current_power_phase1_kw_ {};
|
||||
double current_power_phase2_kw_ {};
|
||||
double current_power_phase3_kw_ {};
|
||||
double total_energy_returned_kwh_ {};
|
||||
double current_power_returned_kw_ {};
|
||||
string device_date_time_;
|
||||
|
||||
// Information sent more rarely and is static.
|
||||
|
||||
string version_;
|
||||
string enhanced_id_;
|
||||
string location_hex_;
|
||||
string fabrication_no_;
|
||||
};
|
||||
|
||||
MeterESYSWM::MeterESYSWM(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, "esyswm")
|
||||
{
|
||||
setMeterType(MeterType::ElectricityMeter);
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_NO_IV);
|
||||
|
||||
// The ESYS-WM-20 and ESYS-WM15 are addons to the electricity meters.
|
||||
// media 0x37 Radio converter (meter side)
|
||||
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
addPrint("total_energy_consumption", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyConsumption(u); },
|
||||
"The total energy consumption recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("current_power_consumption", Quantity::Power,
|
||||
[&](Unit u){ return currentPowerConsumption(u); },
|
||||
"Current power consumption.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_energy_production", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyProduction(u); },
|
||||
"The total energy production recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_energy_consumption_tariff1", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyConsumptionTariff1(u); },
|
||||
"The total energy consumption recorded by this meter on tariff 1.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("total_energy_consumption_tariff2", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyConsumptionTariff2(u); },
|
||||
"The total energy consumption recorded by this meter on tariff 2.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("current_power_consumption_phase1", Quantity::Power,
|
||||
[&](Unit u){ return currentPowerConsumptionPhase1(u); },
|
||||
"Current power consumption phase 1.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("current_power_consumption_phase2", Quantity::Power,
|
||||
[&](Unit u){ return currentPowerConsumptionPhase2(u); },
|
||||
"Current power consumption phase 2.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("current_power_consumption_phase3", Quantity::Power,
|
||||
[&](Unit u){ return currentPowerConsumptionPhase3(u); },
|
||||
"Current power consumption phase 3.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("enhanced_id", Quantity::Text,
|
||||
[&](){ return enhanced_id_; },
|
||||
"Static enhanced id information.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("version", Quantity::Text,
|
||||
[&](){ return version_; },
|
||||
"Static version information.",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("location_hex", Quantity::Text,
|
||||
[&](){ return location_hex_; },
|
||||
"Static location information.",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("fabrication_no", Quantity::Text,
|
||||
[&](){ return fabrication_no_; },
|
||||
"Static fabrication no information.",
|
||||
PrintProperty::JSON);
|
||||
}
|
||||
|
||||
shared_ptr<Meter> createESYSWM(MeterInfo &mi)
|
||||
{
|
||||
return shared_ptr<Meter>(new MeterESYSWM(mi));
|
||||
}
|
||||
|
||||
double MeterESYSWM::totalEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_energy_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterESYSWM::totalEnergyConsumptionTariff1(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_energy_tariff1_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterESYSWM::totalEnergyConsumptionTariff2(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_energy_tariff2_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterESYSWM::currentPowerConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Power);
|
||||
return convert(current_power_kw_, Unit::KW, u);
|
||||
}
|
||||
|
||||
double MeterESYSWM::currentPowerConsumptionPhase1(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Power);
|
||||
return convert(current_power_phase1_kw_, Unit::KW, u);
|
||||
}
|
||||
|
||||
double MeterESYSWM::currentPowerConsumptionPhase2(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Power);
|
||||
return convert(current_power_phase2_kw_, Unit::KW, u);
|
||||
}
|
||||
|
||||
double MeterESYSWM::currentPowerConsumptionPhase3(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Power);
|
||||
return convert(current_power_phase3_kw_, Unit::KW, u);
|
||||
}
|
||||
|
||||
double MeterESYSWM::totalEnergyProduction(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_energy_returned_kwh_, Unit::KWH, u);
|
||||
}
|
||||
|
||||
double MeterESYSWM::currentPowerProduction(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Power);
|
||||
return convert(current_power_returned_kw_, Unit::KW, u);
|
||||
}
|
||||
|
||||
void MeterESYSWM::processContent(Telegram *t)
|
||||
{
|
||||
/*
|
||||
(esyswm) 2e: 07 dif (64 Bit Integer/Binary Instantaneous value)
|
||||
(esyswm) 2f: 02 vif (Energy 10⁻¹ Wh)
|
||||
(esyswm) 30: * F5C3FA0000000000 total energy (1643.416500 kwh)
|
||||
(esyswm) 38: 07 dif (64 Bit Integer/Binary Instantaneous value)
|
||||
(esyswm) 39: 82 vif (Energy 10⁻¹ Wh)
|
||||
(esyswm) 3a: 3C vife (backward flow)
|
||||
(esyswm) 3b: * 5407000000000000 total energy returned (0.187600 kwh)
|
||||
(esyswm) 43: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(esyswm) 44: 10 dife (subunit=0 tariff=1 storagenr=0)
|
||||
(esyswm) 45: 04 vif (Energy 10¹ Wh)
|
||||
(esyswm) 46: * E0810200 total energy tariff 1 (1643.200000 kwh)
|
||||
(esyswm) 4a: 84 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(esyswm) 4b: 20 dife (subunit=0 tariff=2 storagenr=0)
|
||||
(esyswm) 4c: 04 vif (Energy 10¹ Wh)
|
||||
(esyswm) 4d: * 15000000 total energy tariff 2 (0.210000 kwh)
|
||||
(esyswm) 51: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(esyswm) 52: 29 vif (Power 10⁻² W)
|
||||
(esyswm) 53: * 38AB0000 current power (0.438320 kw)
|
||||
(esyswm) 57: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(esyswm) 58: A9 vif (Power 10⁻² W)
|
||||
(esyswm) 59: FF vife (additive correction constant: unit of VIF * 10^0)
|
||||
(esyswm) 5a: 01 vife (?)
|
||||
(esyswm) 5b: * FA0A0000 current power phase 1 (0.028100 kwh)
|
||||
(esyswm) 5f: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(esyswm) 60: A9 vif (Power 10⁻² W)
|
||||
(esyswm) 61: FF vife (additive correction constant: unit of VIF * 10^0)
|
||||
(esyswm) 62: 02 vife (?)
|
||||
(esyswm) 63: * 050A0000 current power phase 2 (0.000000 kwh)
|
||||
(esyswm) 67: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(esyswm) 68: A9 vif (Power 10⁻² W)
|
||||
(esyswm) 69: FF vife (additive correction constant: unit of VIF * 10^0)
|
||||
(esyswm) 6a: 03 vife (?)
|
||||
(esyswm) 6b: * 38960000 current power phase 3 (0.000000 kwh)
|
||||
*/
|
||||
|
||||
/*
|
||||
(esyswm) 2e: 0D dif (variable length Instantaneous value)
|
||||
(esyswm) 2f: FD vif (Second extension of VIF-codes)
|
||||
(esyswm) 30: 09 vife (Medium (as in fixed header))
|
||||
(esyswm) 31: 0F varlen=15
|
||||
(esyswm) 32:
|
||||
(esyswm) 41: 0D dif (variable length Instantaneous value)
|
||||
(esyswm) 42: 79 vif (Enhanced identification)
|
||||
(esyswm) 43: 0E varlen=14
|
||||
(esyswm) 44:
|
||||
(esyswm) 52: 0D dif (variable length Instantaneous value)
|
||||
(esyswm) 53: FD vif (Second extension of VIF-codes)
|
||||
(esyswm) 54: 10 vife (Customer location)
|
||||
(esyswm) 55: 0A varlen=10
|
||||
(esyswm) 56:
|
||||
(esyswm) 60: 0D dif (variable length Instantaneous value)
|
||||
(esyswm) 61: 78 vif (Fabrication no)
|
||||
(esyswm) 62: 0E varlen=14
|
||||
(esyswm) 63:
|
||||
*/
|
||||
|
||||
int offset;
|
||||
string key;
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::EnergyWh, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_energy_kwh_);
|
||||
t->addMoreExplanation(offset, " total energy (%f kwh)", total_energy_kwh_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::EnergyWh, 0, 1, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_energy_tariff1_kwh_);
|
||||
t->addMoreExplanation(offset, " total energy tariff 1 (%f kwh)", total_energy_tariff1_kwh_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::EnergyWh, 0, 2, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_energy_tariff2_kwh_);
|
||||
t->addMoreExplanation(offset, " total energy tariff 2 (%f kwh)", total_energy_tariff2_kwh_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::PowerW, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, ¤t_power_kw_);
|
||||
t->addMoreExplanation(offset, " current power (%f kw)", current_power_kw_);
|
||||
}
|
||||
|
||||
extractDVdouble(&t->dv_entries, "07823C", &offset, &total_energy_returned_kwh_);
|
||||
t->addMoreExplanation(offset, " total energy returned (%f kwh)", total_energy_returned_kwh_);
|
||||
|
||||
extractDVdouble(&t->dv_entries, "04A9FF01", &offset, ¤t_power_phase1_kw_);
|
||||
t->addMoreExplanation(offset, " current power phase 1 (%f kwh)", current_power_phase1_kw_);
|
||||
|
||||
extractDVdouble(&t->dv_entries, "04A9FF02", &offset, ¤t_power_phase2_kw_);
|
||||
t->addMoreExplanation(offset, " current power phase 2 (%f kwh)", current_power_phase2_kw_);
|
||||
|
||||
extractDVdouble(&t->dv_entries, "04A9FF03", &offset, ¤t_power_phase3_kw_);
|
||||
t->addMoreExplanation(offset, " current power phase 3 (%f kwh)", current_power_phase3_kw_);
|
||||
|
||||
extractDVReadableString(&t->dv_entries, "0DFD09", &offset, &version_);
|
||||
t->addMoreExplanation(offset, " version (%s)", version_.c_str());
|
||||
|
||||
extractDVReadableString(&t->dv_entries, "0D79", &offset, &enhanced_id_);
|
||||
t->addMoreExplanation(offset, " enhanced id (%s)", enhanced_id_.c_str());
|
||||
|
||||
extractDVHexString(&t->dv_entries, "0DFD10", &offset, &location_hex_);
|
||||
t->addMoreExplanation(offset, " location (%s)", location_hex_.c_str());
|
||||
|
||||
extractDVReadableString(&t->dv_entries, "0D78", &offset, &fabrication_no_);
|
||||
t->addMoreExplanation(offset, " fabrication no (%s)", fabrication_no_.c_str());
|
||||
}
|
|
@ -68,8 +68,8 @@ MeterEurisII::MeterEurisII(MeterInfo &mi) :
|
|||
for (int i=1; i<=17; ++i)
|
||||
{
|
||||
string msg, info;
|
||||
strprintf(msg, "consumption_at_set_date_%d", i);
|
||||
strprintf(info, "Heat cost allocation at the %d billing period date.", i);
|
||||
strprintf(&msg, "consumption_at_set_date_%d", i);
|
||||
strprintf(&info, "Heat cost allocation at the %d billing period date.", i);
|
||||
addPrint(msg, Quantity::HCA,
|
||||
[this,i](Unit u){ return consumption_at_set_date_hca_[i-1]; },
|
||||
info,
|
||||
|
@ -119,7 +119,7 @@ string MeterEurisII::errorFlagsHumanReadable()
|
|||
|
||||
if (error_flags_ != 0 && s.length() == 0) {
|
||||
// Some higher bits are set that we do not know about! Fall back to printing the number!
|
||||
strprintf(s, "0x%04X", error_flags_);
|
||||
strprintf(&s, "0x%04X", error_flags_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ void MeterEurisII::processContent(Telegram *t)
|
|||
if (findKey(MeasurementType::Instantaneous, VIFRange::HeatCostAllocation, i, 0, &key, &t->dv_entries))
|
||||
{
|
||||
string info;
|
||||
strprintf(info, " consumption at set date %d (%%f hca)", i);
|
||||
strprintf(&info, " consumption at set date %d (%%f hca)", i);
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &consumption_at_set_date_hca_[i-1]);
|
||||
t->addMoreExplanation(offset, info.c_str(), consumption_at_set_date_hca_[i-1]);
|
||||
}
|
||||
|
|
|
@ -366,6 +366,6 @@ string MeterEvo868::status()
|
|||
|
||||
// How do we decode these?
|
||||
string info;
|
||||
strprintf(info, "ERROR bits %08x", error_flags_);
|
||||
strprintf(&info, "ERROR bits %08x", error_flags_);
|
||||
return info;
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ void MeterFHKVDataIII::processContent(Telegram *t)
|
|||
prev_energy_hca_ = prev;
|
||||
|
||||
string prevs;
|
||||
strprintf(prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
//t->addMoreExplanation(14, " energy used in previous billing period (%f HCA)", prevs);
|
||||
|
||||
// Previous Date
|
||||
|
@ -172,7 +172,7 @@ void MeterFHKVDataIII::processContent(Telegram *t)
|
|||
curr_energy_hca_ = curr;
|
||||
|
||||
string currs;
|
||||
strprintf(currs, "%02x%02x", curr_lo, curr_hi);
|
||||
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
|
||||
//t->addMoreExplanation(offset, " energy used in current billing period (%f HCA)", currs);
|
||||
|
||||
// Current Date
|
||||
|
@ -216,7 +216,7 @@ void MeterFHKVDataIII::processContent(Telegram *t)
|
|||
temp_room_ = room_t;
|
||||
|
||||
string room_ts;
|
||||
strprintf(room_ts, "%02x%02x", room_tlo, room_thi);
|
||||
strprintf(&room_ts, "%02x%02x", room_tlo, room_thi);
|
||||
// t->addMoreExplanation(offset, " current room temparature (%f °C)", room_ts);
|
||||
|
||||
// Radiator Temperature
|
||||
|
@ -224,7 +224,7 @@ void MeterFHKVDataIII::processContent(Telegram *t)
|
|||
temp_radiator_ = radiator_t;
|
||||
|
||||
string radiator_ts;
|
||||
strprintf(radiator_ts, "%02x%02x", radiator_tlo, radiator_thi);
|
||||
strprintf(&radiator_ts, "%02x%02x", radiator_tlo, radiator_thi);
|
||||
// t->addMoreExplanation(offset, " current radiator temparature (%f °C)", radiator_ts);
|
||||
}
|
||||
|
||||
|
|
|
@ -108,23 +108,23 @@ MeterGransystemsCCx01::MeterGransystemsCCx01(MeterInfo &mi) :
|
|||
"Voltage at phase L3.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("currrent_at_phase_1", Quantity::Current,
|
||||
addPrint("currrent_at_phase_1", Quantity::Amperage,
|
||||
[&](Unit u){ return convert(current_L_[0], Unit::Ampere, u); },
|
||||
"Current at phase L1.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("currrent_at_phase_2", Quantity::Current,
|
||||
addPrint("currrent_at_phase_2", Quantity::Amperage,
|
||||
[&](Unit u){ return convert(current_L_[1], Unit::Ampere, u); },
|
||||
"Current at phase L2.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("currrent_at_phase_3", Quantity::Current,
|
||||
addPrint("currrent_at_phase_3", Quantity::Amperage,
|
||||
[&](Unit u){ return convert(current_L_[2], Unit::Ampere, u); },
|
||||
"Current at phase L3.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("frequency", Quantity::Frequency,
|
||||
[&](Unit u){ return convert(frequency_, Unit::Hz, u); },
|
||||
[&](Unit u){ return convert(frequency_, Unit::HZ, u); },
|
||||
"Frequency.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
|
|
|
@ -159,14 +159,14 @@ double MeterIzar::lastMonthTotalWaterConsumption(Unit u)
|
|||
string MeterIzar::setH0Date()
|
||||
{
|
||||
string date;
|
||||
strprintf(date, "%d-%02d-%02d", h0_year, h0_month%99, h0_day%99);
|
||||
strprintf(&date, "%d-%02d-%02d", h0_year, h0_month%99, h0_day%99);
|
||||
return date;
|
||||
}
|
||||
|
||||
string MeterIzar::serialNumber()
|
||||
{
|
||||
string result;
|
||||
strprintf(result, "%06d", serial_number);
|
||||
strprintf(&result, "%06d", serial_number);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -263,7 +263,7 @@ void MeterIzar::processContent(Telegram *t)
|
|||
uchar meter_type = '@' + ((origin[8] & 0x7C) >> 2);
|
||||
uchar diameter = '@' + (((origin[8] & 0x03) << 3) | (origin[7] >> 5));
|
||||
// build the prefix
|
||||
strprintf(prefix, "%c%02d%c%c", supplier_code, yy, meter_type, diameter);
|
||||
strprintf(&prefix, "%c%02d%c%c", supplier_code, yy, meter_type, diameter);
|
||||
}
|
||||
|
||||
// get the remaining battery life (in year) and transmission period (in seconds)
|
||||
|
|
|
@ -95,10 +95,10 @@ void MKRadio3::processContent(Telegram *t)
|
|||
uint prev_date_day = (prev_date >> 0) & 0x1F;
|
||||
uint prev_date_month = (prev_date >> 5) & 0x0F;
|
||||
uint prev_date_year = (prev_date >> 9) & 0x3F;
|
||||
strprintf(previous_date_, "%d-%02d-%02dT02:00:00Z", prev_date_year+2000, prev_date_month, prev_date_day);
|
||||
strprintf(&previous_date_, "%d-%02d-%02dT02:00:00Z", prev_date_year+2000, prev_date_month, prev_date_day);
|
||||
|
||||
string prev_date_str;
|
||||
strprintf(prev_date_str, "%04x", prev_date);
|
||||
strprintf(&prev_date_str, "%04x", prev_date);
|
||||
uint offset = t->parsed.size() + 1;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x6c, {}, 0, 0, 0, prev_date_str) };
|
||||
t->explanations.push_back(Explanation(offset, 1, prev_date_str, KindOfData::CONTENT, Understanding::FULL));
|
||||
|
@ -110,7 +110,7 @@ void MKRadio3::processContent(Telegram *t)
|
|||
double prev = (256.0*prev_hi+prev_lo)/10.0;
|
||||
|
||||
string prevs;
|
||||
strprintf(prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
offset = t->parsed.size()+3;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL));
|
||||
|
@ -120,10 +120,10 @@ void MKRadio3::processContent(Telegram *t)
|
|||
uint16_t current_date = (content[6] << 8) | content[5];
|
||||
uint current_date_day = (current_date >> 4) & 0x1F;
|
||||
uint current_date_month = (current_date >> 9) & 0x0F;
|
||||
strprintf(current_date_, "%s-%02d-%02dT02:00:00Z", currentYear().c_str(), current_date_month, current_date_day);
|
||||
strprintf(¤t_date_, "%s-%02d-%02dT02:00:00Z", currentYear().c_str(), current_date_month, current_date_day);
|
||||
|
||||
string current_date_str;
|
||||
strprintf(current_date_str, "%04x", current_date);
|
||||
strprintf(¤t_date_str, "%04x", current_date);
|
||||
offset = t->parsed.size() + 5;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x6c, {}, 0, 0, 0, current_date_str) };
|
||||
t->explanations.push_back(Explanation(offset, 1, current_date_str, KindOfData::CONTENT, Understanding::FULL));
|
||||
|
@ -135,7 +135,7 @@ void MKRadio3::processContent(Telegram *t)
|
|||
double curr = (256.0*curr_hi+curr_lo)/10.0;
|
||||
|
||||
string currs;
|
||||
strprintf(currs, "%02x%02x", curr_lo, curr_hi);
|
||||
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
|
||||
offset = t->parsed.size()+7;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL));
|
||||
|
|
|
@ -81,7 +81,7 @@ void MKRadio4::processContent(Telegram *t)
|
|||
double prev = (256.0*prev_hi+prev_lo)/10.0;
|
||||
|
||||
string prevs;
|
||||
strprintf(prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
strprintf(&prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
int offset = t->parsed.size()+3;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL));
|
||||
|
@ -92,7 +92,7 @@ void MKRadio4::processContent(Telegram *t)
|
|||
double curr = (256.0*curr_hi+curr_lo)/10.0;
|
||||
|
||||
string currs;
|
||||
strprintf(currs, "%02x%02x", curr_lo, curr_hi);
|
||||
strprintf(&currs, "%02x%02x", curr_lo, curr_hi);
|
||||
offset = t->parsed.size()+7;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL));
|
||||
|
|
|
@ -102,7 +102,7 @@ void MeterRfmTX1::processContent(Telegram *t)
|
|||
int m = bcd2bin(frame[o+1]);
|
||||
int s = bcd2bin(frame[o+0]);
|
||||
|
||||
strprintf(meter_datetime_, "%d-%02d-%02d %02d:%02d:%02d",
|
||||
strprintf(&meter_datetime_, "%d-%02d-%02d %02d:%02d:%02d",
|
||||
y, M%99, d%99, H%99, m%99, s%99);
|
||||
|
||||
return;
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2017-2020 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
Copyright (C) 2020 Avandorp (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"dvparser.h"
|
||||
#include"meters.h"
|
||||
#include"meters_common_implementation.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"util.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
AquaMetro / Integra water meter "TOPAS ES KR"
|
||||
Models TOPAS ES KR 95077 95056 95345 95490 95373 95059 95065 95068 95071 95074 should be compatible. Only 95059 in one configuration tested.
|
||||
Identifies itself with Manufacturer "AMT" and Version "f1"
|
||||
Product leaflet and observation says the following values are sent:
|
||||
Current total volume
|
||||
Total volume at end of year-period day (that means: current total volume - total volume at end of year-period day = current year-periods volume up until now)
|
||||
Total backward volume on end of year-period day or total backward volume in current year-period. Backward volume remains untested (luckily only 0 values encountered).
|
||||
Date of end of last year-period day
|
||||
Total volume at end of last month-period dateTime
|
||||
DateTime of end of last month-period
|
||||
Current flow rate
|
||||
Battery life (days left)
|
||||
Water temperature
|
||||
|
||||
Example telegram:
|
||||
telegram=|4E44B40512345678F1077A310040052F2F|01FD08040C13991848004C1359423500CC101300000000CC201359423500426C7F2C0B3B00000002FD74DA10025AD300C4016D3B179F27CC011387124600|+2
|
||||
*/
|
||||
struct MeterTopasEsKr : public virtual MeterCommonImplementation {
|
||||
MeterTopasEsKr(MeterInfo &mi);
|
||||
|
||||
double totalWaterConsumption(Unit u);
|
||||
bool hasTotalWaterConsumption();
|
||||
|
||||
double flowTemperature(Unit u);
|
||||
bool hasFlowTemperature();
|
||||
|
||||
private:
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_water_consumption_m3_ {};
|
||||
double flow_temperature_ {};
|
||||
double current_flow_m3h_ {};
|
||||
string battery_life_days_remaining_ {};
|
||||
double volume_year_period_m3_ {};
|
||||
double reverse_volume_year_period_m3_ {};
|
||||
double volume_month_period_m3_ {};
|
||||
string meter_yearly_period_date_;
|
||||
string meter_month_period_datetime_;
|
||||
};
|
||||
|
||||
shared_ptr<Meter> createTopasEsKr(MeterInfo &mi)
|
||||
{
|
||||
return shared_ptr<Meter>(new MeterTopasEsKr(mi));
|
||||
}
|
||||
|
||||
MeterTopasEsKr::MeterTopasEsKr(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, "topaseskr")
|
||||
{
|
||||
setMeterType(MeterType::WaterMeter);
|
||||
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
// media 0x06 specified temperature range is 0°C to 50 °C, not sure it ever reports 0x06 for warm water, possibly configurable
|
||||
// media 0x07 used
|
||||
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumption(u); },
|
||||
"The total water consumption recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
addPrint("temperature", Quantity::Temperature,
|
||||
[&](Unit u){ return flowTemperature(u); },
|
||||
"Current water temperature recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
addPrint("current_flow", Quantity::Flow,
|
||||
[&](Unit u){ return current_flow_m3h_; },
|
||||
"Current flow.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
addPrint("battery_life_days_remaining_remaining", Quantity::Text,
|
||||
[&](){ return battery_life_days_remaining_; },
|
||||
"Battery life [days remaining].",
|
||||
PrintProperty::JSON);
|
||||
addPrint("volume_year_period", Quantity::Volume,
|
||||
[&](Unit u){ return volume_year_period_m3_; },
|
||||
"Volume up to end of last year-period.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
addPrint("reverse_volume_year_period", Quantity::Volume,
|
||||
[&](Unit u){ return reverse_volume_year_period_m3_; },
|
||||
"Reverse volume in this year-period (?).",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
addPrint("meter_year_period_start_date", Quantity::Text,
|
||||
[&](){ return meter_yearly_period_date_; },
|
||||
"Meter date for year-period start.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
addPrint("volume_month_period", Quantity::Volume,
|
||||
[&](Unit u){ return volume_month_period_m3_; },
|
||||
"Volume up to end of last month-period.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
addPrint("meter_month_period_start_datetime", Quantity::Text,
|
||||
[&](){ return meter_month_period_datetime_; },
|
||||
"Meter timestamp for month-period start.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
}
|
||||
|
||||
void MeterTopasEsKr::processContent(Telegram *t)
|
||||
{
|
||||
int offset;
|
||||
string key;
|
||||
|
||||
if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_water_consumption_m3_);
|
||||
t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_m3_);
|
||||
}
|
||||
if(findKey(MeasurementType::Instantaneous, VIFRange::FlowTemperature, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &flow_temperature_);
|
||||
t->addMoreExplanation(offset, " water temperature (%f °C)", flow_temperature_);
|
||||
}
|
||||
if(findKey(MeasurementType::Instantaneous, VIFRange::VolumeFlow, 0, 0, &key, &t->dv_entries)) {
|
||||
extractDVdouble(&t->dv_entries, key, &offset, ¤t_flow_m3h_);
|
||||
t->addMoreExplanation(offset, " current flow (%f m3/h)", current_flow_m3h_);
|
||||
}
|
||||
|
||||
extractDVdouble(&t->dv_entries, "4C13", &offset, &volume_year_period_m3_);
|
||||
t->addMoreExplanation(offset, " volume up to end of last year-period (%f m3)", volume_year_period_m3_);
|
||||
|
||||
extractDVdouble(&t->dv_entries, "CC1013", &offset, &reverse_volume_year_period_m3_);
|
||||
t->addMoreExplanation(offset, " reverse volume in this year-period (?) (%f m3)", reverse_volume_year_period_m3_);
|
||||
|
||||
struct tm date;
|
||||
extractDVdate(&t->dv_entries, "426C", &offset, &date);
|
||||
meter_yearly_period_date_ = strdate(&date);
|
||||
t->addMoreExplanation(offset, " meter_start_year_period_date (%s)", meter_yearly_period_date_.c_str());
|
||||
|
||||
extractDVdouble(&t->dv_entries, "CC0113", &offset, &volume_month_period_m3_);
|
||||
t->addMoreExplanation(offset, " volume up to end of last month-period (%f m3)", volume_month_period_m3_);
|
||||
|
||||
struct tm datetime;
|
||||
extractDVdate(&t->dv_entries, "C4016D", &offset, &datetime);
|
||||
meter_month_period_datetime_ = strdatetime(&datetime);
|
||||
t->addMoreExplanation(offset, " meter_start_month_period_datetime (%s)", meter_month_period_datetime_.c_str());
|
||||
|
||||
uint16_t tmp16;
|
||||
extractDVuint16(&t->dv_entries, "02FD74", &offset, &tmp16);
|
||||
strprintf(battery_life_days_remaining_, "%u", (unsigned int)tmp16);
|
||||
t->addMoreExplanation(offset, " battery life (%s days remaining)", battery_life_days_remaining_.c_str());
|
||||
|
||||
vector<uchar> data;
|
||||
t->extractMfctData(&data);
|
||||
}
|
||||
|
||||
double MeterTopasEsKr::totalWaterConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
bool MeterTopasEsKr::hasTotalWaterConsumption()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
double MeterTopasEsKr::flowTemperature(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Temperature);
|
||||
return convert(flow_temperature_, Unit::C, u);
|
||||
}
|
||||
|
||||
bool MeterTopasEsKr::hasFlowTemperature()
|
||||
{
|
||||
return true;
|
||||
}
|
|
@ -92,10 +92,10 @@ void MeterTSD2::processContent(Telegram *t)
|
|||
uint prev_date_day = (prev_date >> 0) & 0x1F;
|
||||
uint prev_date_month = (prev_date >> 5) & 0x0F;
|
||||
uint prev_date_year = (prev_date >> 9) & 0x3F;
|
||||
strprintf(previous_date_, "%d-%02d-%02dT02:00:00Z", prev_date_year+2000, prev_date_month, prev_date_day);
|
||||
strprintf(&previous_date_, "%d-%02d-%02dT02:00:00Z", prev_date_year+2000, prev_date_month, prev_date_day);
|
||||
|
||||
string prev_date_str;
|
||||
strprintf(prev_date_str, "%04x", prev_date);
|
||||
strprintf(&prev_date_str, "%04x", prev_date);
|
||||
uint offset = t->parsed.size() + 1;
|
||||
t->explanations.push_back(Explanation(offset, 1, prev_date_str, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " previous date (%s)", previous_date_.c_str());
|
||||
|
|
|
@ -1,272 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2021 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"dvparser.h"
|
||||
#include"meters.h"
|
||||
#include"meters_common_implementation.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"util.h"
|
||||
|
||||
#include<algorithm>
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct MeterUnismart : public virtual MeterCommonImplementation {
|
||||
MeterUnismart(MeterInfo &mi);
|
||||
|
||||
// Total gas counted through the meter
|
||||
double totalGasConsumption(Unit u);
|
||||
bool hasTotalGasConsumption();
|
||||
// Consumption at the beginning of this month.
|
||||
double targetGasConsumption(Unit u);
|
||||
|
||||
private:
|
||||
void processContent(Telegram *t);
|
||||
|
||||
string fabrication_no_;
|
||||
string total_date_time_;
|
||||
double total_gas_consumption_m3_ {};
|
||||
string target_date_time_;
|
||||
double target_gas_consumption_m3_ {};
|
||||
string version_;
|
||||
string device_date_time_;
|
||||
|
||||
string supplier_info_;
|
||||
string status_;
|
||||
string parameter_set_;
|
||||
uint8_t other_;
|
||||
};
|
||||
|
||||
shared_ptr<Meter> createUnismart(MeterInfo &mi)
|
||||
{
|
||||
return shared_ptr<Meter>(new MeterUnismart(mi));
|
||||
}
|
||||
|
||||
MeterUnismart::MeterUnismart(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, "unismart")
|
||||
{
|
||||
setMeterType(MeterType::GasMeter);
|
||||
|
||||
setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV);
|
||||
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
addPrint("fabrication_no", Quantity::Text,
|
||||
[&](){ return fabrication_no_; },
|
||||
"Static fabrication no information.",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("total_date_time", Quantity::Text,
|
||||
[&](){ return total_date_time_; },
|
||||
"Timestamp for this total measurement.",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return totalGasConsumption(u); },
|
||||
"The total gas consumption recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("target_date_time", Quantity::Text,
|
||||
[&](){ return target_date_time_; },
|
||||
"Timestamp for gas consumption recorded at the beginning of this month.",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("target", Quantity::Volume,
|
||||
[&](Unit u){ return targetGasConsumption(u); },
|
||||
"The total gas consumption recorded by this meter at the beginning of this month.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("version", Quantity::Text,
|
||||
[&](){ return version_; },
|
||||
"Model/version a reported by meter.",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("device_date_time", Quantity::Text,
|
||||
[&](){ return device_date_time_; },
|
||||
"Device date time? Seems to be the same as total date time.",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("suppler_info", Quantity::Text,
|
||||
[&](){ return supplier_info_; },
|
||||
"?",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("status", Quantity::Text,
|
||||
[&](){ return status_; },
|
||||
"?",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("parameter_set", Quantity::Text,
|
||||
[&](){ return parameter_set_; },
|
||||
"?",
|
||||
PrintProperty::JSON);
|
||||
|
||||
addPrint("other", Quantity::Counter,
|
||||
[&](Unit u){ return other_; },
|
||||
"?",
|
||||
PrintProperty::JSON);
|
||||
|
||||
}
|
||||
|
||||
void MeterUnismart::processContent(Telegram *t)
|
||||
{
|
||||
/*
|
||||
(unismart) 11: 0C dif (8 digit BCD Instantaneous value)
|
||||
(unismart) 12: 78 vif (Fabrication no)
|
||||
(unismart) 13: 96221603
|
||||
(unismart) 17: 04 dif (32 Bit Integer/Binary Instantaneous value)
|
||||
(unismart) 18: 6D vif (Date and time type)
|
||||
(unismart) 19: 122DAF29
|
||||
(unismart) 1d: 0C dif (8 digit BCD Instantaneous value)
|
||||
(unismart) 1e: 94 vif (Volume 10⁻² m³)
|
||||
(unismart) 1f: 3A vife (uncorrected meter unit)
|
||||
(unismart) 20: * 00170900 total consumption (917.000000 m3)
|
||||
(unismart) 24: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1)
|
||||
(unismart) 25: 6D vif (Date and time type)
|
||||
(unismart) 26: 0026A129
|
||||
(unismart) 2a: 4C dif (8 digit BCD Instantaneous value storagenr=1)
|
||||
(unismart) 2b: 94 vif (Volume 10⁻² m³)
|
||||
(unismart) 2c: 3A vife (uncorrected meter unit)
|
||||
(unismart) 2d: 32110900
|
||||
(unismart) 31: 01 dif (8 Bit Integer/Binary Instantaneous value)
|
||||
(unismart) 32: FD vif (Second extension FD of VIF-codes)
|
||||
(unismart) 33: 67 vife (Special supplier information)
|
||||
(unismart) 34: 00
|
||||
(unismart) 35: 02 dif (16 Bit Integer/Binary Instantaneous value)
|
||||
(unismart) 36: FD vif (Second extension FD of VIF-codes)
|
||||
(unismart) 37: 74 vife (Reserved)
|
||||
(unismart) 38: F00C
|
||||
(unismart) 3a: 0D dif (variable length Instantaneous value)
|
||||
(unismart) 3b: FD vif (Second extension FD of VIF-codes)
|
||||
(unismart) 3c: 0C vife (Model/Version)
|
||||
(unismart) 3d: 06 varlen=6
|
||||
(unismart) 3e: 554747342020
|
||||
(unismart) 44: 01 dif (8 Bit Integer/Binary Instantaneous value)
|
||||
(unismart) 45: FD vif (Second extension FD of VIF-codes)
|
||||
(unismart) 46: 0B vife (Parameter set identification)
|
||||
(unismart) 47: 02
|
||||
(unismart) 48: 01 dif (8 Bit Integer/Binary Instantaneous value)
|
||||
(unismart) 49: 7F vif (Manufacturer specific)
|
||||
(unismart) 4a: 14
|
||||
(unismart) 4b: 06 dif (48 Bit Integer/Binary Instantaneous value)
|
||||
(unismart) 4c: 6D vif (Date and time type)
|
||||
(unismart) 4d: 1E120DAF296D
|
||||
(unismart) 53: 2F skip
|
||||
(unismart) 54: 2F skip
|
||||
(unismart) 55: 2F skip
|
||||
(unismart) 56: 2F skip
|
||||
(unismart) 57: 2F skip
|
||||
(unismart) 58: 2F skip
|
||||
(unismart) 59: 2F skip
|
||||
(unismart) 5a: 2F skip
|
||||
(unismart) 5b: 2F skip
|
||||
(unismart) 5c: 2F skip
|
||||
(unismart) 5d: 2F skip
|
||||
(unismart) 5e: 2F skip
|
||||
|
||||
*/
|
||||
int offset;
|
||||
string key;
|
||||
|
||||
uint64_t v {};
|
||||
if (extractDVlong(&t->dv_entries, "0C78", &offset, &v))
|
||||
{
|
||||
fabrication_no_ = to_string(v);
|
||||
t->addMoreExplanation(offset, " fabrication no (%zu)", v);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 0, 0, &key, &t->dv_entries)) {
|
||||
struct tm datetime;
|
||||
extractDVdate(&t->dv_entries, key, &offset, &datetime);
|
||||
total_date_time_ = strdatetime(&datetime);
|
||||
t->addMoreExplanation(offset, " total datetime (%s)", total_date_time_.c_str());
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &t->dv_entries))
|
||||
{
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &total_gas_consumption_m3_);
|
||||
t->addMoreExplanation(offset, " total consumption (%f m3)", total_gas_consumption_m3_);
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 1, 0, &key, &t->dv_entries)) {
|
||||
struct tm datetime;
|
||||
extractDVdate(&t->dv_entries, key, &offset, &datetime);
|
||||
target_date_time_ = strdatetime(&datetime);
|
||||
t->addMoreExplanation(offset, " target datetime (%s)", target_date_time_.c_str());
|
||||
}
|
||||
|
||||
if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 1, 0, &key, &t->dv_entries))
|
||||
{
|
||||
extractDVdouble(&t->dv_entries, key, &offset, &target_gas_consumption_m3_);
|
||||
t->addMoreExplanation(offset, " target consumption (%f m3)", target_gas_consumption_m3_);
|
||||
}
|
||||
|
||||
string tmp;
|
||||
if (extractDVHexString(&t->dv_entries, "0DFD0C", &offset, &tmp))
|
||||
{
|
||||
vector<uchar> bin;
|
||||
hex2bin(tmp, &bin);
|
||||
version_ = safeString(bin);
|
||||
trimWhitespace(&version_);
|
||||
t->addMoreExplanation(offset, " version (%s)", version_.c_str());
|
||||
}
|
||||
|
||||
struct tm datetime;
|
||||
if (extractDVdate(&t->dv_entries, "066D", &offset, &datetime))
|
||||
{
|
||||
device_date_time_ = strdatetime(&datetime);
|
||||
t->addMoreExplanation(offset, " device datetime (%s)", device_date_time_.c_str());
|
||||
}
|
||||
|
||||
if (extractDVHexString(&t->dv_entries, "01FD67", &offset, &supplier_info_))
|
||||
{
|
||||
t->addMoreExplanation(offset, " suppler info (%s)", supplier_info_.c_str());
|
||||
}
|
||||
|
||||
if (extractDVHexString(&t->dv_entries, "02FD74", &offset, &status_))
|
||||
{
|
||||
t->addMoreExplanation(offset, " status (%s)", status_.c_str());
|
||||
}
|
||||
|
||||
if (extractDVHexString(&t->dv_entries, "01FD0B", &offset, ¶meter_set_))
|
||||
{
|
||||
t->addMoreExplanation(offset, " parameter set (%s)", parameter_set_.c_str());
|
||||
}
|
||||
|
||||
if (extractDVuint8(&t->dv_entries, "017F", &offset, &other_))
|
||||
{
|
||||
t->addMoreExplanation(offset, " status2 (%d)", other_);
|
||||
}
|
||||
}
|
||||
|
||||
double MeterUnismart::totalGasConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_gas_consumption_m3_, Unit::M3, u);
|
||||
}
|
||||
|
||||
bool MeterUnismart::hasTotalGasConsumption()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
double MeterUnismart::targetGasConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(target_gas_consumption_m3_, Unit::M3, u);
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2019-2020 Fredrik Öhrström (gpl-3.0-or-later)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"dvparser.h"
|
||||
#include"meters.h"
|
||||
#include"meters_common_implementation.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"units.h"
|
||||
#include"util.h"
|
||||
|
||||
struct MeterVario451 : public virtual MeterCommonImplementation
|
||||
{
|
||||
MeterVario451(MeterInfo &mi);
|
||||
|
||||
double totalEnergyConsumption(Unit u);
|
||||
double currentPeriodEnergyConsumption(Unit u);
|
||||
double previousPeriodEnergyConsumption(Unit u);
|
||||
|
||||
private:
|
||||
|
||||
void processContent(Telegram *t);
|
||||
|
||||
double total_energy_gj_ {};
|
||||
double curr_energy_gj_ {};
|
||||
double prev_energy_gj_ {};
|
||||
};
|
||||
|
||||
shared_ptr<Meter> createVario451(MeterInfo &mi)
|
||||
{
|
||||
return shared_ptr<Meter>(new MeterVario451(mi));
|
||||
}
|
||||
|
||||
MeterVario451::MeterVario451(MeterInfo &mi) :
|
||||
MeterCommonImplementation(mi, "vario451")
|
||||
{
|
||||
setMeterType(MeterType::HeatMeter);
|
||||
|
||||
// media 0x04 C telegrams
|
||||
// media 0xC3 T telegrams
|
||||
|
||||
addLinkMode(LinkMode::C1);
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
addPrint("total", Quantity::Energy,
|
||||
[&](Unit u){ return totalEnergyConsumption(u); },
|
||||
"The total energy consumption recorded by this meter.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("current", Quantity::Energy,
|
||||
[&](Unit u){ return currentPeriodEnergyConsumption(u); },
|
||||
"Energy consumption so far in this billing period.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
|
||||
addPrint("previous", Quantity::Energy,
|
||||
[&](Unit u){ return previousPeriodEnergyConsumption(u); },
|
||||
"Energy consumption in previous billing period.",
|
||||
PrintProperty::FIELD | PrintProperty::JSON);
|
||||
}
|
||||
|
||||
double MeterVario451::totalEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(total_energy_gj_, Unit::GJ, u);
|
||||
}
|
||||
|
||||
double MeterVario451::currentPeriodEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(curr_energy_gj_, Unit::GJ, u);
|
||||
}
|
||||
|
||||
double MeterVario451::previousPeriodEnergyConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Energy);
|
||||
return convert(prev_energy_gj_, Unit::GJ, u);
|
||||
}
|
||||
|
||||
void MeterVario451::processContent(Telegram *t)
|
||||
{
|
||||
// Unfortunately, the Techem Vario 4 Typ 4.5.1 is mostly a proprieatary protocol
|
||||
// simple wrapped inside a wmbus telegram since the ci-field is 0xa2.
|
||||
// Which means that the entire payload is manufacturer specific.
|
||||
|
||||
map<string,pair<int,DVEntry>> vendor_values;
|
||||
vector<uchar> content;
|
||||
|
||||
t->extractPayload(&content);
|
||||
if (content.size() < 9) return;
|
||||
|
||||
uchar prev_lo = content[3];
|
||||
uchar prev_hi = content[4];
|
||||
double prev = (256.0*prev_hi+prev_lo)/1000;
|
||||
|
||||
string prevs;
|
||||
strprintf(prevs, "%02x%02x", prev_lo, prev_hi);
|
||||
int offset = t->parsed.size()+3;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " energy used in previous billing period (%f GJ)", prev);
|
||||
|
||||
uchar curr_lo = content[7];
|
||||
uchar curr_hi = content[8];
|
||||
double curr = (256.0*curr_hi+curr_lo)/1000;
|
||||
|
||||
string currs;
|
||||
strprintf(currs, "%02x%02x", curr_lo, curr_hi);
|
||||
offset = t->parsed.size()+7;
|
||||
vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) };
|
||||
t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL));
|
||||
t->addMoreExplanation(offset, " energy used in current billing period (%f GJ)", curr);
|
||||
|
||||
total_energy_gj_ = prev+curr;
|
||||
curr_energy_gj_ = curr;
|
||||
prev_energy_gj_ = prev;
|
||||
}
|
633
src/meters.cc
633
src/meters.cc
|
@ -148,7 +148,7 @@ bool registerDriver(function<void(DriverInfo&)> setup)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool lookupDriverInfo(string& driver, DriverInfo *out_di)
|
||||
bool lookupDriverInfo(const string& driver, DriverInfo *out_di)
|
||||
{
|
||||
DriverInfo *di = lookupDriver(driver);
|
||||
if (di == NULL)
|
||||
|
@ -156,7 +156,10 @@ bool lookupDriverInfo(string& driver, DriverInfo *out_di)
|
|||
return false;
|
||||
}
|
||||
|
||||
*out_di = *di;
|
||||
if (out_di != NULL)
|
||||
{
|
||||
*out_di = *di;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -735,10 +738,12 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
|
|||
{
|
||||
hex2bin(mi.key, &meter_keys_.confidentiality_key);
|
||||
}
|
||||
for (auto s : mi.shells) {
|
||||
for (auto s : mi.shells)
|
||||
{
|
||||
addShell(s);
|
||||
}
|
||||
for (auto j : mi.extra_constant_fields) {
|
||||
for (auto j : mi.extra_constant_fields)
|
||||
{
|
||||
addExtraConstantField(j);
|
||||
}
|
||||
}
|
||||
|
@ -762,10 +767,12 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
|
|||
{
|
||||
hex2bin(mi.key, &meter_keys_.confidentiality_key);
|
||||
}
|
||||
for (auto s : mi.shells) {
|
||||
for (auto s : mi.shells)
|
||||
{
|
||||
addShell(s);
|
||||
}
|
||||
for (auto j : mi.extra_constant_fields) {
|
||||
for (auto j : mi.extra_constant_fields)
|
||||
{
|
||||
addExtraConstantField(j);
|
||||
}
|
||||
|
||||
|
@ -791,6 +798,39 @@ void MeterCommonImplementation::addExtraConstantField(string ecf)
|
|||
extra_constant_fields_.push_back(ecf);
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addExtraCalculatedField(string ecf)
|
||||
{
|
||||
verbose("(meter) Adding calculated field: %s\n", ecf.c_str());
|
||||
|
||||
vector<string> parts = splitString(ecf, '=');
|
||||
|
||||
if (parts.size() != 2)
|
||||
{
|
||||
warning("Invalid formula for calculated field. %s\n", ecf.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
string vname;
|
||||
Unit unit;
|
||||
|
||||
bool ok = extractUnit(parts[0], &vname, &unit);
|
||||
if (!ok)
|
||||
{
|
||||
warning("Could not extract a valid unit from calculated field name %s\n", parts[0].c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
Quantity quantity = toQuantity(unit);
|
||||
|
||||
addNumericFieldWithCalculator(
|
||||
vname,
|
||||
"Calculated: "+ecf,
|
||||
PrintProperty::JSON | PrintProperty::FIELD,
|
||||
quantity,
|
||||
parts[1]
|
||||
);
|
||||
}
|
||||
|
||||
vector<string> &MeterCommonImplementation::shellCmdlines()
|
||||
{
|
||||
return shell_cmdlines_;
|
||||
|
@ -962,6 +1002,15 @@ void MeterCommonImplementation::addNumericFieldWithCalculator(string vname,
|
|||
{
|
||||
Formula *f = newFormula();
|
||||
bool ok = f->parse(this, formula);
|
||||
if (!ok)
|
||||
{
|
||||
string err = f->errors();
|
||||
warning("Warning! Ignoring calculated field %s because parse failed:\n%s",
|
||||
vname.c_str(),
|
||||
err.c_str());
|
||||
delete f;
|
||||
return;
|
||||
}
|
||||
assert(ok);
|
||||
|
||||
field_infos_.push_back(
|
||||
|
@ -1393,7 +1442,7 @@ string toString(DriverInfo &di)
|
|||
return di.name().str();
|
||||
}
|
||||
|
||||
MeterDriver toMeterDriver(string& t)
|
||||
MeterDriver toMeterDriver(const string& t)
|
||||
{
|
||||
#define X(mname,linkmodes,info,type,cname) if (t == #mname) return MeterDriver::type;
|
||||
LIST_OF_METERS
|
||||
|
@ -1401,7 +1450,7 @@ LIST_OF_METERS
|
|||
return MeterDriver::UNKNOWN;
|
||||
}
|
||||
|
||||
LinkModeSet toMeterLinkModeSet(string& t)
|
||||
LinkModeSet toMeterLinkModeSet(const string& t)
|
||||
{
|
||||
#define X(mname,linkmodes,info,type,cname) if (t == #mname) return LinkModeSet(linkmodes);
|
||||
LIST_OF_METERS
|
||||
|
@ -1824,6 +1873,8 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
|
|||
{
|
||||
map<FieldInfo*,DVEntry*> found;
|
||||
|
||||
// Sort the dv_entries based on their offset in the telegram.
|
||||
// I.e. restore the ordering that was implicit in the telegram.
|
||||
vector<DVEntry*> sorted_entries;
|
||||
|
||||
for (auto &p : t->dv_entries)
|
||||
|
@ -1833,30 +1884,32 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
|
|||
sort(sorted_entries.begin(), sorted_entries.end(),
|
||||
[](const DVEntry* a, const DVEntry *b) -> bool { return a->offset < b->offset; });
|
||||
|
||||
// Iterate through the data content (dv_entries) in the telegram.
|
||||
for (DVEntry *dve : sorted_entries)
|
||||
// Now go through each field_info defined by the driver.
|
||||
for (FieldInfo &fi : field_infos_)
|
||||
{
|
||||
// We have telegram content, a dif-vif-value entry.
|
||||
// Now check for a field info that wants to handle this telegram content entry.
|
||||
for (FieldInfo &fi : field_infos_)
|
||||
int current_match_nr = 0;
|
||||
|
||||
// This field_info has not been matched to a dv_entry before!
|
||||
debug("(meters) trying field info %s(%s)[%d]...\n",
|
||||
fi.vname().c_str(),
|
||||
toString(fi.xuantity()),
|
||||
fi.index());
|
||||
|
||||
// Iterate through dv_entries in the telegram in the same order the telegram presented them.
|
||||
for (DVEntry *dve : sorted_entries)
|
||||
{
|
||||
if (fi.hasMatcher() && fi.matches(dve))
|
||||
{
|
||||
if (found.count(&fi) != 0)
|
||||
{
|
||||
DVEntry *old = found[&fi];
|
||||
current_match_nr++;
|
||||
|
||||
verbose("(meter) while processing field extractors ignoring dventry %s at offset %d matching since "
|
||||
"field %s was already matched against dventry %s at offset %d !\n",
|
||||
dve->dif_vif_key.str().c_str(),
|
||||
dve->offset,
|
||||
fi.vname().c_str(),
|
||||
old->dif_vif_key.str().c_str(),
|
||||
old->offset);
|
||||
}
|
||||
else
|
||||
if (fi.matcher().index_nr != IndexNr(current_match_nr))
|
||||
{
|
||||
// We have field that wants to handle this entry!
|
||||
// This field info did match, but requires another index nr!
|
||||
// Increment the current index nr and look for the next match.
|
||||
}
|
||||
else if (found.count(&fi) == 0)
|
||||
{
|
||||
// This field_info has not been matched to a dv_entry before!
|
||||
debug("(meters) using field info %s(%s)[%d] to extract %s at offset %d\n",
|
||||
fi.vname().c_str(),
|
||||
toString(fi.xuantity()),
|
||||
|
@ -1868,6 +1921,18 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t)
|
|||
fi.performExtraction(this, t, dve);
|
||||
found[&fi] = dve;
|
||||
}
|
||||
else
|
||||
{
|
||||
DVEntry *old = found[&fi];
|
||||
|
||||
verbose("(meter) while processing field extractors ignoring dventry %s at offset %d matching since "
|
||||
"field %s was already matched against dventry %s at offset %d !\n",
|
||||
dve->dif_vif_key.str().c_str(),
|
||||
dve->offset,
|
||||
fi.vname().c_str(),
|
||||
old->dif_vif_key.str().c_str(),
|
||||
old->offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1999,7 +2064,6 @@ string MeterCommonImplementation::getStringValue(FieldInfo *fi)
|
|||
{
|
||||
string more = getStringValue(&f);
|
||||
string joined = joinStatusStrings(value, more);
|
||||
//printf("JOINING >%s< >%s< into >%s<\n", value.c_str(), more.c_str(), joined.c_str());
|
||||
value = joined;
|
||||
}
|
||||
}
|
||||
|
@ -2186,8 +2250,8 @@ void MeterCommonImplementation::printMeter(Telegram *t,
|
|||
if (found.count(&fi) != 0)
|
||||
{
|
||||
DVEntry *dve = found[&fi];
|
||||
debug("(meters) render field %s(%s)[%d] with dventry @%d key %s data %s\n",
|
||||
fi.vname().c_str(), toString(fi.xuantity()), fi.index(),
|
||||
debug("(meters) render field %s(%s %s)[%d] with dventry @%d key %s data %s\n",
|
||||
fi.vname().c_str(), toString(fi.xuantity()), unitToStringLowerCase(fi.defaultUnit()).c_str(), fi.index(),
|
||||
dve->offset,
|
||||
dve->dif_vif_key.str().c_str(),
|
||||
dve->value.c_str());
|
||||
|
@ -2415,6 +2479,10 @@ shared_ptr<Meter> createMeter(MeterInfo *mi)
|
|||
{
|
||||
shared_ptr<Meter> newm = di->construct(*mi);
|
||||
newm->addConversions(mi->conversions);
|
||||
for (string &j : mi->extra_calculated_fields)
|
||||
{
|
||||
newm->addExtraCalculatedField(j);
|
||||
}
|
||||
newm->setPollInterval(mi->poll_interval);
|
||||
if (mi->selected_fields.size() > 0)
|
||||
{
|
||||
|
@ -2451,7 +2519,7 @@ LIST_OF_METERS
|
|||
return newm;
|
||||
}
|
||||
|
||||
bool is_driver_and_extras(string t, MeterDriver *out_driver, DriverName *out_driver_name, string *out_extras)
|
||||
bool is_driver_and_extras(const string& t, MeterDriver *out_driver, DriverName *out_driver_name, string *out_extras)
|
||||
{
|
||||
// piigth(jump=foo)
|
||||
// multical21
|
||||
|
@ -2596,11 +2664,10 @@ bool MeterInfo::usesPolling()
|
|||
link_modes.has(LinkMode::S2);
|
||||
}
|
||||
|
||||
bool isValidKey(string& key, MeterDriver mt)
|
||||
bool isValidKey(const string& key, MeterDriver mt)
|
||||
{
|
||||
if (key.length() == 0) return true;
|
||||
if (key == "NOKEY") {
|
||||
key = "";
|
||||
return true;
|
||||
}
|
||||
if (mt == MeterDriver::IZAR ||
|
||||
|
@ -2724,8 +2791,23 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve)
|
|||
matcher_.vif_range != VIFRange::None)
|
||||
{
|
||||
decoded_unit = toDefaultUnit(matcher_.vif_range);
|
||||
debug("(meter) NormalVif(%s) %s decoded %s default %s value %g\n",
|
||||
toString(matcher_.vif_range),
|
||||
field_name.c_str(),
|
||||
unitToStringLowerCase(decoded_unit).c_str(),
|
||||
unitToStringLowerCase(default_unit_).c_str(),
|
||||
extracted_double_value);
|
||||
}
|
||||
m->setNumericValue(this, decoded_unit, extracted_double_value);
|
||||
else
|
||||
{
|
||||
debug("(meter) AnyVif(%s) %s decoded %s default %s value %g\n",
|
||||
toString(matcher_.vif_range),
|
||||
field_name.c_str(),
|
||||
unitToStringLowerCase(decoded_unit).c_str(),
|
||||
unitToStringLowerCase(default_unit_).c_str(),
|
||||
extracted_double_value);
|
||||
}
|
||||
m->setNumericValue(this, default_unit_, convert(extracted_double_value, decoded_unit, default_unit_));
|
||||
t->addMoreExplanation(dve->offset, renderJson(m, &m->conversions()));
|
||||
found = true;
|
||||
}
|
||||
|
@ -2846,7 +2928,17 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
|
|||
{
|
||||
struct tm datetime;
|
||||
dve->extractDate(&datetime);
|
||||
string extracted_device_date_time = strdatetime(&datetime);
|
||||
string extracted_device_date_time;
|
||||
|
||||
if (dve->value.size() == 12)
|
||||
{
|
||||
// A long date time sec + timezone field. TODO add timezone data.
|
||||
extracted_device_date_time = strdatetimesec(&datetime);
|
||||
}
|
||||
else
|
||||
{
|
||||
extracted_device_date_time = strdatetime(&datetime);
|
||||
}
|
||||
m->setStringValue(this, extracted_device_date_time);
|
||||
t->addMoreExplanation(dve->offset, renderJsonText(m));
|
||||
found = true;
|
||||
|
@ -2860,11 +2952,14 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
|
|||
t->addMoreExplanation(dve->offset, renderJsonText(m));
|
||||
found = true;
|
||||
}
|
||||
else if (matcher_.vif_range == VIFRange::EnhancedIdentification ||
|
||||
else if (matcher_.vif_range == VIFRange::Any ||
|
||||
matcher_.vif_range == VIFRange::EnhancedIdentification ||
|
||||
matcher_.vif_range == VIFRange::FabricationNo ||
|
||||
matcher_.vif_range == VIFRange::ModelVersion ||
|
||||
matcher_.vif_range == VIFRange::SoftwareVersion ||
|
||||
matcher_.vif_range == VIFRange::Customer ||
|
||||
matcher_.vif_range == VIFRange::Location ||
|
||||
matcher_.vif_range == VIFRange::SpecialSupplierInformation ||
|
||||
matcher_.vif_range == VIFRange::ParameterSet)
|
||||
{
|
||||
string extracted_id;
|
||||
|
@ -2962,186 +3057,316 @@ bool Address::parse(string &s)
|
|||
return true;
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addOptionalCommonFields()
|
||||
bool checkIf(set<string> &fields, const char *s)
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"fabrication_no",
|
||||
"Fabrication number.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::FabricationNo)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"enhanced_id",
|
||||
"Enhanced identification number.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::EnhancedIdentification)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"software_version",
|
||||
"Software version.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::SoftwareVersion)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"operating_time",
|
||||
"How long the meter has been collecting data.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::OperatingTime)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"on_time",
|
||||
"How long the meter has been powered up.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::OnTime)
|
||||
);
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"on_time_at_error",
|
||||
"How long the meter has been in an error state while powered up.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::AtError)
|
||||
.set(VIFRange::OnTime)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"meter_date",
|
||||
"Date when the meter sent the telegram.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Date)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"meter_date_at_error",
|
||||
"Date when the meter was in error.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::AtError)
|
||||
.set(VIFRange::Date)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"meter_datetime",
|
||||
"Date and time when the meter sent the telegram.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::DateTime)
|
||||
);
|
||||
|
||||
addStringFieldWithExtractor(
|
||||
"meter_datetime_at_error",
|
||||
"Date and time when the meter was in error.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::AtError)
|
||||
.set(VIFRange::DateTime)
|
||||
);
|
||||
if (fields.count(s) > 0)
|
||||
{
|
||||
fields.erase(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addOptionalFlowRelatedFields()
|
||||
void checkFieldsEmpty(set<string> &fields, string name)
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"total",
|
||||
"The total media volume consumption recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
);
|
||||
if (fields.size() > 0)
|
||||
{
|
||||
string info;
|
||||
for (auto &s : fields) { info += s+" "; }
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_forward",
|
||||
"The total media volume flowing forward.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.add(VIFCombinable::ForwardFlow)
|
||||
);
|
||||
warning("(meter) when adding common fields to driver %s, these fields were not found: %s\n",
|
||||
name.c_str(),
|
||||
info.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"total_backward",
|
||||
"The total media volume flowing backward.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.add(VIFCombinable::BackwardFlow)
|
||||
);
|
||||
void MeterCommonImplementation::addOptionalCommonFields(string field_names)
|
||||
{
|
||||
set<string> fields = splitStringIntoSet(field_names, ',');
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"flow_temperature",
|
||||
"Forward media temperature.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Temperature,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::FlowTemperature)
|
||||
);
|
||||
if (checkIf(fields, "fabrication_no"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"fabrication_no",
|
||||
"Fabrication number.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::FabricationNo)
|
||||
);
|
||||
}
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"return_temperature",
|
||||
"Return media temperature.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Temperature,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ReturnTemperature)
|
||||
);
|
||||
if (checkIf(fields,"enhanced_id"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"enhanced_id",
|
||||
"Enhanced identification number.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::EnhancedIdentification)
|
||||
);
|
||||
}
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"flow_return_temperature_difference",
|
||||
"The difference between flow and return media temperatures.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Temperature,
|
||||
VifScaling::AutoSigned,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::TemperatureDifference)
|
||||
);
|
||||
if (checkIf(fields,"software_version"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"software_version",
|
||||
"Software version.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::SoftwareVersion)
|
||||
);
|
||||
}
|
||||
|
||||
addNumericFieldWithExtractor(
|
||||
"volume_flow",
|
||||
"Media volume flow.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Flow,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::VolumeFlow)
|
||||
);
|
||||
if (checkIf(fields,"model_version"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"model_version",
|
||||
"Meter model version.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ModelVersion)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"customer"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"customer",
|
||||
"Customer name.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Customer)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"location"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"location",
|
||||
"Meter installed at this customer location.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Location)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"operating_time_h"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"operating_time",
|
||||
"How long the meter has been collecting data.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::OperatingTime)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"on_time_h"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"on_time",
|
||||
"How long the meter has been powered up.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::OnTime)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"on_time_at_error_h"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"on_time_at_error",
|
||||
"How long the meter has been in an error state while powered up.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Time,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::AtError)
|
||||
.set(VIFRange::OnTime)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"meter_date"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"meter_date",
|
||||
"Date when the meter sent the telegram.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Date)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"meter_date_at_error"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"meter_date_at_error",
|
||||
"Date when the meter was in error.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::AtError)
|
||||
.set(VIFRange::Date)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"meter_datetime"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"meter_datetime",
|
||||
"Date and time when the meter sent the telegram.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::DateTime)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"meter_datetime_at_error"))
|
||||
{
|
||||
addStringFieldWithExtractor(
|
||||
"meter_datetime_at_error",
|
||||
"Date and time when the meter was in error.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::AtError)
|
||||
.set(VIFRange::DateTime)
|
||||
);
|
||||
}
|
||||
|
||||
checkFieldsEmpty(fields, name());
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::addOptionalFlowRelatedFields(string field_names)
|
||||
{
|
||||
set<string> fields = splitStringIntoSet(field_names, ',');
|
||||
|
||||
if (checkIf(fields,"total_m3"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"total",
|
||||
"The total media volume consumption recorded by this meter.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"total_forward_m3"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"total_forward",
|
||||
"The total media volume flowing forward.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.add(VIFCombinable::ForwardFlow)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"total_backward_m3"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"total_backward",
|
||||
"The total media volume flowing backward.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Volume,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::Volume)
|
||||
.add(VIFCombinable::BackwardFlow)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"flow_temperature_c"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"flow_temperature",
|
||||
"Forward media temperature.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Temperature,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::FlowTemperature)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"return_temperature_c"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"return_temperature",
|
||||
"Return media temperature.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Temperature,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::ReturnTemperature)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"flow_return_temperature_difference_c"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"flow_return_temperature_difference",
|
||||
"The difference between flow and return media temperatures.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Temperature,
|
||||
VifScaling::AutoSigned,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::TemperatureDifference)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"volume_flow_m3h"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"volume_flow",
|
||||
"Media volume flow.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Flow,
|
||||
VifScaling::Auto,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::VolumeFlow)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkIf(fields,"access_counter"))
|
||||
{
|
||||
addNumericFieldWithExtractor(
|
||||
"access",
|
||||
"Meter access counter.",
|
||||
PrintProperty::JSON | PrintProperty::OPTIONAL,
|
||||
Quantity::Counter,
|
||||
VifScaling::None,
|
||||
FieldMatcher::build()
|
||||
.set(MeasurementType::Instantaneous)
|
||||
.set(VIFRange::AccessNumber)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
23
src/meters.h
23
src/meters.h
|
@ -62,9 +62,6 @@ LIST_OF_METER_TYPES
|
|||
X(auto, 0, AutoMeter, AUTO, Auto) \
|
||||
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
|
||||
X(eurisii, T1_bit, HeatCostAllocationMeter, EURISII, EurisII) \
|
||||
X(ehzp, T1_bit, ElectricityMeter, EHZP, EHZP) \
|
||||
X(esyswm, T1_bit, ElectricityMeter, ESYSWM, ESYSWM) \
|
||||
X(em24, C1_bit, ElectricityMeter, EM24, EM24) \
|
||||
X(evo868, T1_bit, WaterMeter, EVO868, EVO868) \
|
||||
X(fhkvdataiii,T1_bit, HeatCostAllocationMeter, FHKVDATAIII, FHKVDataIII) \
|
||||
X(fhkvdataiv, T1_bit, HeatCostAllocationMeter, FHKVDATAIV, FHKVDataIV) \
|
||||
|
@ -84,10 +81,7 @@ LIST_OF_METER_TYPES
|
|||
X(rfmtx1, T1_bit, WaterMeter, RFMTX1, RfmTX1) \
|
||||
X(tsd2, T1_bit, SmokeDetector, TSD2, TSD2) \
|
||||
X(sontex868, T1_bit, HeatCostAllocationMeter, SONTEX868, Sontex868) \
|
||||
X(topaseskr, T1_bit, WaterMeter, TOPASESKR, TopasEsKr) \
|
||||
X(vario451, T1_bit, HeatMeter, VARIO451, Vario451) \
|
||||
X(lse_08, S1_bit|C1_bit, HeatCostAllocationMeter, LSE_08, LSE_08) \
|
||||
X(unismart, T1_bit, GasMeter, UNISMART, Unismart) \
|
||||
|
||||
|
||||
enum class MeterDriver {
|
||||
|
@ -123,7 +117,7 @@ bool isMeterDriverValid(MeterDriver type, int manufacturer, int media, int versi
|
|||
// Ie. do not try to decode a door sensor telegram with a water meter driver.
|
||||
bool isMeterDriverReasonableForMedia(MeterDriver type, string driver_name, int media);
|
||||
|
||||
bool isValidKey(string& key, MeterDriver mt);
|
||||
bool isValidKey(const string& key, MeterDriver mt);
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -160,6 +154,7 @@ struct MeterInfo
|
|||
int bps {}; // For mbus communication you need to know the baud rate.
|
||||
vector<string> shells;
|
||||
vector<string> extra_constant_fields; // Additional static fields that are added to each message.
|
||||
vector<string> extra_calculated_fields; // Additional field calculated using formulas.
|
||||
vector<Unit> conversions; // Additional units desired in json.
|
||||
vector<string> selected_fields; // Usually set to the default fields, but can be override in meter config.
|
||||
|
||||
|
@ -173,7 +168,7 @@ struct MeterInfo
|
|||
string str();
|
||||
DriverName driverName();
|
||||
|
||||
MeterInfo(string b, string n, MeterDriver d, string e, vector<string> i, string k, LinkModeSet lms, int baud, vector<string> &s, vector<string> &j)
|
||||
MeterInfo(string b, string n, MeterDriver d, string e, vector<string> i, string k, LinkModeSet lms, int baud, vector<string> &s, vector<string> &j, vector<string> &calcfs)
|
||||
{
|
||||
bus = b;
|
||||
name = n;
|
||||
|
@ -184,6 +179,7 @@ struct MeterInfo
|
|||
key = k;
|
||||
shells = s;
|
||||
extra_constant_fields = j;
|
||||
extra_calculated_fields = calcfs;
|
||||
link_modes = lms;
|
||||
bps = baud;
|
||||
}
|
||||
|
@ -198,6 +194,7 @@ struct MeterInfo
|
|||
key = "";
|
||||
shells.clear();
|
||||
extra_constant_fields.clear();
|
||||
extra_calculated_fields.clear();
|
||||
link_modes.clear();
|
||||
bps = 0;
|
||||
}
|
||||
|
@ -263,7 +260,7 @@ public:
|
|||
};
|
||||
|
||||
bool registerDriver(function<void(DriverInfo&di)> setup);
|
||||
bool lookupDriverInfo(string& driver, DriverInfo *di);
|
||||
bool lookupDriverInfo(const string& driver, DriverInfo *di = NULL);
|
||||
// Return the best driver match for a telegram.
|
||||
DriverInfo pickMeterDriver(Telegram *t);
|
||||
// Return true for mbus and S2/C2/T2 drivers.
|
||||
|
@ -293,7 +290,8 @@ enum PrintProperty
|
|||
DEPRECATED = 32, // This field is about to be removed or changed in a newer driver, which will have a new name.
|
||||
STATUS = 64, // This is >the< status field and it should read OK of not error flags are set.
|
||||
JOIN_TPL_STATUS = 128, // This text field also includes the tpl status decoding. multiple OK:s collapse to a single OK.
|
||||
JOIN_INTO_STATUS = 256 // This text field is injected into the already defined status field. multiple OK:s collapse.
|
||||
JOIN_INTO_STATUS = 256, // This text field is injected into the already defined status field. multiple OK:s collapse.
|
||||
OFFICIAL = 512 // This field is listed as an official field for the driver.
|
||||
};
|
||||
|
||||
struct PrintProperties
|
||||
|
@ -472,6 +470,7 @@ struct Meter
|
|||
virtual MeterKeys *meterKeys() = 0;
|
||||
|
||||
virtual void addConversions(std::vector<Unit> cs) = 0;
|
||||
virtual void addExtraCalculatedField(std::string ecf) = 0;
|
||||
virtual vector<Unit>& conversions() = 0;
|
||||
virtual void addShell(std::string cmdline) = 0;
|
||||
virtual vector<string> &shellCmdlines() = 0;
|
||||
|
@ -507,8 +506,8 @@ shared_ptr<MeterManager> createMeterManager(bool daemon);
|
|||
const char *toString(MeterType type);
|
||||
string toString(MeterDriver driver);
|
||||
string toString(DriverInfo &driver);
|
||||
MeterDriver toMeterDriver(string& driver);
|
||||
LinkModeSet toMeterLinkModeSet(string& driver);
|
||||
MeterDriver toMeterDriver(const string& driver);
|
||||
LinkModeSet toMeterLinkModeSet(const string& driver);
|
||||
LinkModeSet toMeterLinkModeSet(MeterDriver driver);
|
||||
|
||||
#define X(mname,linkmode,info,type,cname) shared_ptr<Meter> create##cname(MeterInfo &m);
|
||||
|
|
|
@ -26,6 +26,13 @@
|
|||
#include<map>
|
||||
#include<set>
|
||||
|
||||
|
||||
// Values in a meter are stored based on vname + Quantity.
|
||||
// I.e. you can have a total_m3 and a total_kwh even though they share the same "total" vname
|
||||
// since they have two different quantities (Volume and Energy).
|
||||
// The field total_l refers to the same field storage in the meter as total_m3.
|
||||
// If a wacko meter sends different values, one m3 and one l. then you
|
||||
// have to name the fields using different vnames.
|
||||
struct NumericField
|
||||
{
|
||||
Unit unit {};
|
||||
|
@ -67,6 +74,7 @@ struct MeterCommonImplementation : public virtual Meter
|
|||
void setPollInterval(time_t interval);
|
||||
time_t pollInterval();
|
||||
bool usesPolling();
|
||||
void addExtraCalculatedField(std::string ef);
|
||||
|
||||
void onUpdate(function<void(Telegram*,Meter*)> cb);
|
||||
int numUpdates();
|
||||
|
@ -95,7 +103,6 @@ protected:
|
|||
void setMeterType(MeterType mt);
|
||||
void addLinkMode(LinkMode lm);
|
||||
void addMfctTPLStatusBits(Translate::Lookup lookup);
|
||||
void setDefaultFields(string f);
|
||||
|
||||
// Print with the default unit for this quantity.
|
||||
void addPrint(string vname, Quantity vquantity,
|
||||
|
@ -249,8 +256,8 @@ protected:
|
|||
|
||||
std::string decodeTPLStatusByte(uchar sts);
|
||||
|
||||
void addOptionalCommonFields();
|
||||
void addOptionalFlowRelatedFields();
|
||||
void addOptionalCommonFields(string fields);
|
||||
void addOptionalFlowRelatedFields(string fields);
|
||||
|
||||
vector<string> &selectedFields() { return selected_fields_; }
|
||||
void setSelectedFields(vector<string> &f) { selected_fields_ = f; }
|
||||
|
|
|
@ -1544,7 +1544,7 @@ string showSpecialChars(struct termios *tios)
|
|||
if (c != 0)
|
||||
{
|
||||
string cc;
|
||||
strprintf(cc, "%u", c);
|
||||
strprintf(&cc, "%u", c);
|
||||
s += cc+",";
|
||||
}
|
||||
}
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -65,7 +65,7 @@ void handleBitToString(Rule& rule, string &out_s, uint64_t bits)
|
|||
{
|
||||
// Oups, there are set bits that we have not handled....
|
||||
string tmp;
|
||||
strprintf(tmp, "%s_%X", rule.name.c_str(), bits);
|
||||
strprintf(&tmp, "%s_%X", rule.name.c_str(), bits);
|
||||
s += tmp+" ";
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ void handleIndexToString(Rule& rule, string &out_s, uint64_t bits)
|
|||
if ((~rule.mask & m.from) != 0)
|
||||
{
|
||||
string tmp;
|
||||
strprintf(tmp, "BAD_RULE_%s(from=0x%x mask=0x%x)", rule.name.c_str(), m.from, rule.mask);
|
||||
strprintf(&tmp, "BAD_RULE_%s(from=0x%x mask=0x%x)", rule.name.c_str(), m.from, rule.mask);
|
||||
s += tmp+" ";
|
||||
}
|
||||
uint64_t from = m.from & rule.mask; // Better safe than sorry.
|
||||
|
@ -104,7 +104,7 @@ void handleIndexToString(Rule& rule, string &out_s, uint64_t bits)
|
|||
{
|
||||
// Oups, this index has not been found.
|
||||
string tmp;
|
||||
strprintf(tmp, "%s_%X", rule.name.c_str(), bits);
|
||||
strprintf(&tmp, "%s_%X", rule.name.c_str(), bits);
|
||||
s += tmp+" ";
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ void handleDecimalsToString(Rule& rule, string &out_s, uint64_t bits)
|
|||
if ((m.from - (m.from % rule.mask)) != 0)
|
||||
{
|
||||
string tmp;
|
||||
strprintf(tmp, "BAD_RULE_%s(from=%d modulomask=%d)", rule.name.c_str(), m.from, rule.mask);
|
||||
strprintf(&tmp, "BAD_RULE_%s(from=%d modulomask=%d)", rule.name.c_str(), m.from, rule.mask);
|
||||
s += tmp+" ";
|
||||
}
|
||||
int num = m.from % rule.mask; // Better safe than sorry.
|
||||
|
@ -142,7 +142,7 @@ void handleDecimalsToString(Rule& rule, string &out_s, uint64_t bits)
|
|||
{
|
||||
// Oups, this number has not been fully understood.
|
||||
string tmp;
|
||||
strprintf(tmp, "%s_%d", rule.name.c_str(), number);
|
||||
strprintf(&tmp, "%s_%d", rule.name.c_str(), number);
|
||||
s += tmp+" ";
|
||||
}
|
||||
|
||||
|
|
451
src/units.cc
451
src/units.cc
|
@ -20,6 +20,7 @@
|
|||
#include<assert.h>
|
||||
#include<math.h>
|
||||
#include<string.h>
|
||||
#include<limits>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -54,8 +55,74 @@ using namespace std;
|
|||
X(K, C, {vto=vfrom-273.15;}) \
|
||||
X(C, F, {vto=(vfrom*9.0/5.0)+32.0;}) \
|
||||
X(F, C, {vto=(vfrom-32)*5.0/9.0;}) \
|
||||
X(PA, BAR,{vto=vfrom/100000.0;}) \
|
||||
X(BAR, PA, {vto=vfrom*100000.0;}) \
|
||||
|
||||
|
||||
#define LIST_OF_SI_CONVERSIONS \
|
||||
X(Second, 1.0, SIExp().s(1)) \
|
||||
X(M, 1.0, SIExp().m(1)) \
|
||||
X(KG, 1.0, SIExp().kg(1)) \
|
||||
X(Ampere, 1.0, SIExp().a(1)) \
|
||||
X(K, 1.0, SIExp().k(1)) \
|
||||
X(MOL, 1.0, SIExp().mol(1)) \
|
||||
X(CD, 1.0, SIExp().cd(1)) \
|
||||
\
|
||||
X(KWH, 3.6e+06, SIExp().kg(1).m(2).s(-2)) \
|
||||
X(MJ, 1.0e+06, SIExp().kg(1).m(2).s(-2)) \
|
||||
X(GJ, 1.0e+09, SIExp().kg(1).m(2).s(-2)) \
|
||||
X(KVARH, 3.6e+06, SIExp().kg(1).m(2).s(-2)) \
|
||||
X(KVAH, 3.6e+06, SIExp().kg(1).m(2).s(-2)) \
|
||||
X(M3C, 1.0, SIExp().m(3).c(1)) \
|
||||
\
|
||||
X(KW, 1000.0, SIExp().kg(1).m(2).s(-3)) \
|
||||
\
|
||||
X(M3, 1.0, SIExp().m(3)) \
|
||||
X(L, 1.0/1000.0, SIExp().m(3)) \
|
||||
X(M3H, 3600.0, SIExp().m(3).s(-1)) \
|
||||
X(LH, 3.600, SIExp().m(3).s(-1)) \
|
||||
\
|
||||
X(C, 1.0, SIExp().c(1)) \
|
||||
X(F, 1.0, SIExp().f(1)) \
|
||||
\
|
||||
X(Volt, 1.0, SIExp().kg(1).m(2).s(-3).a(-1)) \
|
||||
X(HZ, 1.0, SIExp().s(-1)) \
|
||||
X(PA, 1.0, SIExp().kg(1).m(-1).s(-2)) \
|
||||
X(BAR, 100000.0, SIExp().kg(1).m(-1).s(-2)) \
|
||||
\
|
||||
X(Minute, 60.0, SIExp().s(1)) \
|
||||
X(Hour, 3600.0, SIExp().s(1)) \
|
||||
X(Day, 3600.0*24, SIExp().s(1)) \
|
||||
X(Year, 3600.0*24*365.2425, SIExp().s(1)) \
|
||||
X(DateTimeUT, 1.0, SIExp().s(1)) \
|
||||
X(DateTimeUTC, 1.0, SIExp().s(1)) \
|
||||
X(DateTimeLT, 1.0, SIExp().s(1)) \
|
||||
\
|
||||
X(RH, 1.0, SIExp()) \
|
||||
X(HCA, 1.0, SIExp()) \
|
||||
X(COUNTER, 1.0, SIExp()) \
|
||||
X(TXT, 1.0, SIExp()) \
|
||||
|
||||
|
||||
#define X(cname,lcname,hrname,quantity,explanation) const SIUnit SI_##cname(Unit::cname);
|
||||
LIST_OF_UNITS
|
||||
#undef X
|
||||
|
||||
const SIUnit SI_Unknown(Unit::Unknown);
|
||||
|
||||
const SIUnit &toSIUnit(Unit u)
|
||||
{
|
||||
switch (u)
|
||||
{
|
||||
#define X(cname,lcname,hrname,quantity,explanation) case Unit::cname: return SI_##cname;
|
||||
LIST_OF_UNITS
|
||||
#undef X
|
||||
default: break;
|
||||
}
|
||||
|
||||
return SI_Unknown;
|
||||
}
|
||||
|
||||
bool canConvert(Unit ufrom, Unit uto)
|
||||
{
|
||||
if (ufrom == uto) return true;
|
||||
|
@ -82,12 +149,140 @@ LIST_OF_CONVERSIONS
|
|||
return 0;
|
||||
}
|
||||
|
||||
Unit whenMultiplied(Unit left, Unit right)
|
||||
bool SIUnit::canConvertTo(const SIUnit &uto) const
|
||||
{
|
||||
// Same exponents! Then we can always convert!
|
||||
if (exponents_ == uto.exponents_) return true;
|
||||
|
||||
// Now the special cases. K-C-F
|
||||
if ((exponents_ == SI_K.exponents_ ||
|
||||
exponents_ == SI_C.exponents_ ||
|
||||
exponents_ == SI_F.exponents_) &&
|
||||
(uto.exponents_ == SI_K.exponents_ ||
|
||||
uto.exponents_ == SI_C.exponents_ ||
|
||||
uto.exponents_ == SI_F.exponents_))
|
||||
{
|
||||
// We are converting between the K,C,F temperatures only!
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isKCF(const SIExp &e)
|
||||
{
|
||||
return
|
||||
e == SI_K.exp() ||
|
||||
e == SI_C.exp() ||
|
||||
e == SI_F.exp();
|
||||
}
|
||||
|
||||
void getScaleOffset(const SIExp &e, double *scale, double *offset)
|
||||
{
|
||||
if (e == SI_K.exp())
|
||||
{
|
||||
*scale = 1.0;
|
||||
*offset = 0.0;
|
||||
return;
|
||||
}
|
||||
if (e == SI_C.exp())
|
||||
{
|
||||
*scale = 1.0;
|
||||
*offset = 273.15;
|
||||
return;
|
||||
}
|
||||
if (e == SI_F.exp())
|
||||
{
|
||||
*scale = 5.0/9.0;
|
||||
*offset = -32.0+(273.15*9.0/5.0);
|
||||
return;
|
||||
}
|
||||
assert(0);
|
||||
}
|
||||
|
||||
double SIUnit::convertTo(double val, const SIUnit &uto) const
|
||||
{
|
||||
if (exp() == uto.exp())
|
||||
{
|
||||
return (val*scale_)/uto.scale_;
|
||||
}
|
||||
|
||||
// Now the special cases. K-C-F
|
||||
if (isKCF(exp()) && isKCF(uto.exp()))
|
||||
{
|
||||
double from_scale {};
|
||||
double from_offset {};
|
||||
|
||||
getScaleOffset(exp(), &from_scale, &from_offset);
|
||||
from_scale *= scale();
|
||||
|
||||
double to_offset {};
|
||||
double to_scale {};
|
||||
|
||||
getScaleOffset(uto.exp(), &to_scale, &to_offset);
|
||||
to_scale *= uto.scale();
|
||||
|
||||
return ((val+from_offset)*from_scale)/to_scale-to_offset;
|
||||
}
|
||||
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
SIUnit SIUnit::mul(const SIUnit &m) const
|
||||
{
|
||||
// Multipliying the SIUnits adds the exponents.
|
||||
SIExp exps = exponents_.mul(m.exponents_);
|
||||
|
||||
double new_scale = scale_*m.scale_;
|
||||
SIUnit tmp(Quantity::Unknown,
|
||||
new_scale,
|
||||
exps);
|
||||
|
||||
Unit u = tmp.asUnit(Quantity::Unknown);
|
||||
Quantity q = toQuantity(u);
|
||||
|
||||
return SIUnit(q, new_scale, exps);
|
||||
}
|
||||
|
||||
SIUnit SIUnit::div(const SIUnit &m) const
|
||||
{
|
||||
// Dividing with a SIUnit subtracts the exponents.
|
||||
SIExp exps = exponents_.div(m.exponents_);
|
||||
|
||||
double new_scale = scale_/m.scale_;
|
||||
|
||||
SIUnit tmp(Quantity::Unknown,
|
||||
new_scale,
|
||||
exps);
|
||||
|
||||
Unit u = tmp.asUnit(Quantity::Unknown);
|
||||
Quantity q = toQuantity(u);
|
||||
|
||||
return SIUnit(q, new_scale, exps);
|
||||
}
|
||||
|
||||
SIUnit SIUnit::sqrt() const
|
||||
{
|
||||
// Square rooting SIUnit halfs the exponents.
|
||||
SIExp exps = exponents_.sqrt();
|
||||
|
||||
double new_scale = ::sqrt(scale_);
|
||||
SIUnit tmp(Quantity::Unknown,
|
||||
new_scale,
|
||||
exps);
|
||||
|
||||
Unit u = tmp.asUnit(Quantity::Unknown);
|
||||
Quantity q = toQuantity(u);
|
||||
|
||||
return SIUnit(q, new_scale, exps);
|
||||
}
|
||||
|
||||
SIUnit whenMultiplied(SIUnit left, SIUnit right)
|
||||
{
|
||||
return Unit::Unknown;
|
||||
}
|
||||
|
||||
double multiply(double l, Unit left, double r, Unit right)
|
||||
double multiply(double l, SIUnit left, double r, SIUnit right)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -103,10 +298,14 @@ LIST_OF_UNITS
|
|||
|
||||
Quantity toQuantity(Unit u)
|
||||
{
|
||||
#define X(cname,lcname,hrname,quantity,explanation) if (u == Unit::cname) return Quantity::quantity;
|
||||
switch(u)
|
||||
{
|
||||
#define X(cname,lcname,hrname,quantity,explanation) case Unit::cname: return Quantity::quantity;
|
||||
LIST_OF_UNITS
|
||||
#undef X
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return Quantity::Unknown;
|
||||
}
|
||||
|
||||
|
@ -237,3 +436,247 @@ err:
|
|||
*u = Unit::Unknown;
|
||||
return false;
|
||||
}
|
||||
|
||||
SIUnit::SIUnit(Unit u)
|
||||
{
|
||||
quantity_ = toQuantity(u);
|
||||
|
||||
switch (u)
|
||||
{
|
||||
#define X(cname,si_scale,si_exponents) \
|
||||
case Unit::cname: scale_ = si_scale; exponents_ = si_exponents; break;
|
||||
LIST_OF_SI_CONVERSIONS
|
||||
#undef X
|
||||
default:
|
||||
quantity_ = Quantity::Unknown;
|
||||
scale_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
SIUnit::SIUnit(string s)
|
||||
{
|
||||
}
|
||||
|
||||
Unit SIUnit::asUnit() const
|
||||
{
|
||||
#define X(cname,si_scale,si_exponents) \
|
||||
if ((scale_ == si_scale) && (exponents_ == (si_exponents)) && quantity_ == toQuantity(Unit::cname)) return Unit::cname;
|
||||
LIST_OF_SI_CONVERSIONS
|
||||
#undef X
|
||||
|
||||
return Unit::Unknown;
|
||||
}
|
||||
|
||||
Unit SIUnit::asUnit(Quantity q) const
|
||||
{
|
||||
#define X(cname,si_scale,si_exponents) \
|
||||
if ((scale_ == si_scale) && (exponents_ == (si_exponents)) && q == toQuantity(Unit::cname)) return Unit::cname;
|
||||
LIST_OF_SI_CONVERSIONS
|
||||
#undef X
|
||||
|
||||
return Unit::Unknown;
|
||||
}
|
||||
|
||||
string super(uchar c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '-': return "⁻";
|
||||
case '+': return "⁺";
|
||||
case '0': return "⁰";
|
||||
case '1': return "¹";
|
||||
case '2': return "²";
|
||||
case '3': return "³";
|
||||
case '4': return "⁴";
|
||||
case '5': return "⁵";
|
||||
case '6': return "⁶";
|
||||
case '7': return "⁷";
|
||||
case '8': return "⁸";
|
||||
case '9': return "⁹";
|
||||
}
|
||||
assert(false);
|
||||
return "?";
|
||||
}
|
||||
|
||||
string to_superscript(int8_t n)
|
||||
{
|
||||
string out;
|
||||
|
||||
char buf[5];
|
||||
sprintf(buf, "%d", n);
|
||||
|
||||
for (int i=0; i<5; ++i)
|
||||
{
|
||||
if (buf[i] == 0) break;
|
||||
out += super(buf[i]);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
string to_superscript(string &s)
|
||||
{
|
||||
string out;
|
||||
|
||||
size_t i = 0;
|
||||
size_t len = s.length();
|
||||
|
||||
// Skip non-superscript number.
|
||||
while (i<len && (s[i] == '-' || s[i] == '.' || (s[i] >= '0' && s[i] <= '9')))
|
||||
{
|
||||
out += s[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
while (i<len && (s[i] == 'e' || s[i] == 'E'))
|
||||
{
|
||||
i++;
|
||||
out += "×10";
|
||||
}
|
||||
|
||||
bool found_start = false;
|
||||
while (i<len)
|
||||
{
|
||||
// Remove leading +0
|
||||
if (!found_start && s[i] != '+' && s[i] != '0') found_start = true;
|
||||
|
||||
if (found_start)
|
||||
{
|
||||
out += super(s[i]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
string SIUnit::str() const
|
||||
{
|
||||
string r = exponents_.str();
|
||||
|
||||
string num = tostrprintf("%g", scale_);
|
||||
|
||||
num = to_superscript(num);
|
||||
return num+r;
|
||||
}
|
||||
|
||||
|
||||
string SIUnit::info() const
|
||||
{
|
||||
Unit u = asUnit();
|
||||
string unit = unitToStringLowerCase(u)+"|";
|
||||
if (unit == "?|") unit = "";
|
||||
string quantity = toString(quantity_)+string("|");
|
||||
if (quantity == "?|") quantity = "";
|
||||
|
||||
return tostrprintf("[%s%s%s]",
|
||||
unit.c_str(),
|
||||
quantity.c_str(),
|
||||
str().c_str());
|
||||
}
|
||||
|
||||
int8_t SIExp::safe_add(int8_t a, int8_t b)
|
||||
{
|
||||
int sum = a+b;
|
||||
if (sum > 127) invalid_ = true;
|
||||
return sum;
|
||||
}
|
||||
|
||||
int8_t SIExp::safe_sub(int8_t a, int8_t b)
|
||||
{
|
||||
int diff = a-b;
|
||||
if (diff < -128) invalid_ = true;
|
||||
return diff;
|
||||
}
|
||||
|
||||
int8_t SIExp::safe_div2(int8_t a)
|
||||
{
|
||||
int8_t d = a/2;
|
||||
if (d*2 != a) invalid_ = true;
|
||||
return d;
|
||||
}
|
||||
|
||||
bool SIExp::operator==(const SIExp &e) const
|
||||
{
|
||||
return
|
||||
s_ == e.s_ &&
|
||||
m_ == e.m_ &&
|
||||
kg_ == e.kg_ &&
|
||||
a_ == e.a_ &&
|
||||
mol_ == e.mol_ &&
|
||||
cd_ == e.cd_ &&
|
||||
k_ == e.k_ &&
|
||||
c_ == e.c_ &&
|
||||
f_ == e.f_;
|
||||
}
|
||||
|
||||
SIExp SIExp::mul(const SIExp &e) const
|
||||
{
|
||||
SIExp ee;
|
||||
|
||||
ee .s(ee.safe_add(s(),e.s()))
|
||||
.m(ee.safe_add(m(),e.m()))
|
||||
.kg(ee.safe_add(kg(),e.kg()))
|
||||
.a(ee.safe_add(a(),e.a()))
|
||||
.mol(ee.safe_add(mol(),e.mol()))
|
||||
.cd(ee.safe_add(cd(),e.cd()))
|
||||
.k(ee.safe_add(k(),e.k()))
|
||||
.c(ee.safe_add(c(),e.c()))
|
||||
.f(ee.safe_add(f(),e.f()));
|
||||
|
||||
return ee;
|
||||
}
|
||||
|
||||
SIExp SIExp::div(const SIExp &e) const
|
||||
{
|
||||
SIExp ee;
|
||||
ee .s(ee.safe_sub(s(),e.s()))
|
||||
.m(ee.safe_sub(m(),e.m()))
|
||||
.kg(ee.safe_sub(kg(),e.kg()))
|
||||
.a(ee.safe_sub(a(),e.a()))
|
||||
.mol(ee.safe_sub(mol(),e.mol()))
|
||||
.cd(ee.safe_sub(cd(),e.cd()))
|
||||
.k(ee.safe_sub(k(),e.k()))
|
||||
.c(ee.safe_sub(c(),e.c()))
|
||||
.f(ee.safe_sub(f(),e.f()));
|
||||
|
||||
return ee;
|
||||
}
|
||||
|
||||
SIExp SIExp::sqrt() const
|
||||
{
|
||||
SIExp ee;
|
||||
|
||||
ee .s(ee.safe_div2(s()))
|
||||
.m(ee.safe_div2(m()))
|
||||
.kg(ee.safe_div2(kg()))
|
||||
.a(ee.safe_div2(a()))
|
||||
.mol(ee.safe_div2(mol()))
|
||||
.cd(ee.safe_div2(cd()))
|
||||
.k(ee.safe_div2(k()))
|
||||
.c(ee.safe_div2(c()))
|
||||
.f(ee.safe_div2(f()));
|
||||
|
||||
return ee;
|
||||
}
|
||||
|
||||
#define DO_UNIT_SIEXP(var, name) if (var != 0) { if (r.length()>0) { } r += #name; if (var != 1) { r += to_superscript(var); } }
|
||||
|
||||
string SIExp::str() const
|
||||
{
|
||||
string r;
|
||||
|
||||
DO_UNIT_SIEXP(mol_, mol);
|
||||
DO_UNIT_SIEXP(cd_, cd);
|
||||
DO_UNIT_SIEXP(kg_, kg);
|
||||
DO_UNIT_SIEXP(m_, m);
|
||||
DO_UNIT_SIEXP(k_, k);
|
||||
DO_UNIT_SIEXP(c_, c);
|
||||
DO_UNIT_SIEXP(f_, f);
|
||||
DO_UNIT_SIEXP(s_, s);
|
||||
DO_UNIT_SIEXP(a_, a);
|
||||
|
||||
if (invalid_) r = "!"+r+"-Invalid!";
|
||||
|
||||
return r;
|
||||
}
|
||||
|
|
266
src/units.h
266
src/units.h
|
@ -21,64 +21,39 @@
|
|||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
#define LIST_OF_QUANTITIES \
|
||||
X(Energy,KWH) \
|
||||
X(Reactive_Energy,KVARH) \
|
||||
X(Apparent_Energy,KVAH) \
|
||||
X(Power,KW) \
|
||||
X(Volume,M3) \
|
||||
X(Flow,M3H) \
|
||||
X(Temperature,C) \
|
||||
X(RelativeHumidity,RH) \
|
||||
X(HCA,HCA) \
|
||||
X(Text,TXT) \
|
||||
X(Counter,COUNTER) \
|
||||
X(Time,Hour) \
|
||||
// A named quantity has a preferred unit,
|
||||
// ie Volume has m3 (cubic meters) Energy has kwh, Power has kw.
|
||||
// We search for quantities in the mbus telegrams instead of
|
||||
// hard coding a specific unit. This is useful since some
|
||||
// meters can send either mj or kwh depending on their configuration.
|
||||
|
||||
#define LIST_OF_QUANTITIES \
|
||||
X(Time,Hour) \
|
||||
X(Length,M) \
|
||||
X(Mass,KG) \
|
||||
X(Amperage,Ampere) \
|
||||
X(Temperature,C) \
|
||||
X(AmountOfSubstance,MOL) \
|
||||
X(LuminousIntensity,CD) \
|
||||
\
|
||||
X(Energy,KWH) \
|
||||
X(Reactive_Energy,KVARH) \
|
||||
X(Apparent_Energy,KVAH) \
|
||||
X(Power,KW) \
|
||||
\
|
||||
X(Volume,M3) \
|
||||
X(Flow,M3H) \
|
||||
\
|
||||
X(Voltage,Volt) \
|
||||
X(Frequency,HZ) \
|
||||
X(Pressure,BAR) \
|
||||
\
|
||||
X(PointInTime,DateTimeLT) \
|
||||
X(Voltage,Volt) \
|
||||
X(Current,Ampere) \
|
||||
X(Frequency,Hz) \
|
||||
X(Pressure,BAR)
|
||||
|
||||
#define LIST_OF_UNITS \
|
||||
X(KWH,kwh,"kWh",Energy,"kilo Watt hour") \
|
||||
X(MJ,mj,"MJ",Energy,"Mega Joule") \
|
||||
X(GJ,gj,"GJ",Energy,"Giga Joule") \
|
||||
X(KVARH,kvarh,"kVARh",Reactive_Energy,"kilo volt amperes reactive hour") \
|
||||
X(KVAH,kvah,"kVAh",Apparent_Energy,"kilo volt amperes hour") \
|
||||
X(M3C,m3c,"m³°C",Energy,"cubic meter celsius") \
|
||||
X(M3,m3,"m³",Volume,"cubic meter") \
|
||||
X(L,l,"l",Volume,"litre") \
|
||||
X(KW,kw,"kW",Power,"kilo Watt") \
|
||||
X(M3H,m3h,"m³/h",Flow,"cubic meters per hour") \
|
||||
X(LH,lh,"l/h",Flow,"liters per hour") \
|
||||
X(C,c,"°C",Temperature,"celsius") \
|
||||
X(F,f,"°F",Temperature,"fahrenheit") \
|
||||
X(K,k,"K",Temperature,"kelvin") \
|
||||
X(RH,rh,"RH",RelativeHumidity,"relative humidity") \
|
||||
X(HCA,hca,"hca",HCA,"heat cost allocation") \
|
||||
X(TXT,txt,"txt",Text,"text") \
|
||||
X(COUNTER,counter,"counter",Counter,"counter") \
|
||||
X(Second,s,"s",Time,"second") \
|
||||
X(Minute,m,"m",Time,"minute") \
|
||||
X(Hour,h,"h",Time,"hour") \
|
||||
X(Day,d,"d",Time,"day") \
|
||||
X(Year,y,"y",Time,"year") \
|
||||
X(DateTimeUT,ut,"ut",PointInTime,"unix timestamp") \
|
||||
X(DateTimeUTC,utc,"utc",PointInTime,"coordinated universal time") \
|
||||
X(DateTimeLT,lt,"lt",PointInTime,"local time") \
|
||||
X(Volt,v,"V",Voltage,"volt") \
|
||||
X(Ampere,a,"A",Current,"ampere") \
|
||||
X(Hz,hz,"Hz",Frequency,"hz") \
|
||||
X(BAR,bar,"bar",Pressure,"bar")
|
||||
|
||||
enum class Unit
|
||||
{
|
||||
#define X(cname,lcname,hrname,quantity,explanation) cname,
|
||||
LIST_OF_UNITS
|
||||
#undef X
|
||||
Unknown
|
||||
};
|
||||
\
|
||||
X(RelativeHumidity,RH) \
|
||||
X(HCA,HCA) \
|
||||
X(Text,TXT) \
|
||||
X(Counter,COUNTER) \
|
||||
|
||||
enum class Quantity
|
||||
{
|
||||
|
@ -88,6 +63,181 @@ LIST_OF_QUANTITIES
|
|||
Unknown
|
||||
};
|
||||
|
||||
// A named unit (Unit) is a recurring unit that is useful to represent values
|
||||
// sent in mbus telegrams. A named unit can be translated into an SIUnit
|
||||
// which is unnamed and can encode any combination of SI units & scale.
|
||||
// The SIUnits are used inside formulas. Eventually a successful formula
|
||||
// calculation will map the final SI Unit to a named unit.
|
||||
#define LIST_OF_UNITS \
|
||||
X(Second,s,"s",Time,"second") \
|
||||
X(M,m,"m",Length,"meter") \
|
||||
X(KG,kg,"kg",Mass,"kilogram") \
|
||||
X(Ampere,a,"A",Amperage,"ampere") \
|
||||
X(K,k,"K",Temperature,"kelvin") \
|
||||
X(MOL,mol,"mol",AmountOfSubstance,"mole") \
|
||||
X(CD,cd,"cd",LuminousIntensity,"candela") \
|
||||
\
|
||||
X(KWH,kwh,"kWh",Energy,"kilo Watt hour") \
|
||||
X(MJ,mj,"MJ",Energy,"Mega Joule") \
|
||||
X(GJ,gj,"GJ",Energy,"Giga Joule") \
|
||||
X(KVARH,kvarh,"kVARh",Reactive_Energy,"kilo volt amperes reactive hour") \
|
||||
X(KVAH,kvah,"kVAh",Apparent_Energy,"kilo volt amperes hour") \
|
||||
X(M3C,m3c,"m³°C",Energy,"cubic meter celsius") \
|
||||
\
|
||||
X(KW,kw,"kW",Power,"kilo Watt") \
|
||||
\
|
||||
X(M3,m3,"m³",Volume,"cubic meter") \
|
||||
X(L,l,"l",Volume,"litre") \
|
||||
X(M3H,m3h,"m³/h",Flow,"cubic meters per hour") \
|
||||
X(LH,lh,"l/h",Flow,"liters per hour") \
|
||||
\
|
||||
X(C,c,"°C",Temperature,"celsius") \
|
||||
X(F,f,"°F",Temperature,"fahrenheit") \
|
||||
\
|
||||
X(Volt,v,"V",Voltage,"volt") \
|
||||
X(HZ,hz,"Hz",Frequency,"hz") \
|
||||
X(PA,pa,"pa",Pressure,"pascal") \
|
||||
X(BAR,bar,"bar",Pressure,"bar") \
|
||||
\
|
||||
X(Minute,min,"min",Time,"minute") \
|
||||
X(Hour,h,"h",Time,"hour") \
|
||||
X(Day,d,"d",Time,"day") \
|
||||
X(Year,y,"y",Time,"year") \
|
||||
X(DateTimeUT,ut,"ut",PointInTime,"unix timestamp") \
|
||||
X(DateTimeUTC,utc,"utc",PointInTime,"coordinated universal time") \
|
||||
X(DateTimeLT,lt,"lt",PointInTime,"local time") \
|
||||
\
|
||||
X(RH,rh,"RH",RelativeHumidity,"relative humidity") \
|
||||
X(HCA,hca,"hca",HCA,"heat cost allocation") \
|
||||
X(TXT,txt,"txt",Text,"text") \
|
||||
X(COUNTER,counter,"counter",Counter,"counter") \
|
||||
|
||||
enum class Unit
|
||||
{
|
||||
#define X(cname,lcname,hrname,quantity,explanation) cname,
|
||||
LIST_OF_UNITS
|
||||
#undef X
|
||||
Unknown
|
||||
};
|
||||
|
||||
// The SIUnit is used inside formulas and for conversion between units.
|
||||
// Any numeric named Unit can be expressed using an SIUnit.
|
||||
// Most SIUnits do not have a corresponding named Unit.
|
||||
// However the result of a formula calculation or conversion will
|
||||
// be eventually converted into a named unit. So even if you
|
||||
// are allowed to write a formula: 9 kwh * 6 kwh which generates
|
||||
// the unit kwh² which is not explicitly named above,
|
||||
// you cannot assign this value to any calculated field since kwh^2
|
||||
// is not named. However you can do: sqrt(10 kwh * 10 kwh) which
|
||||
// will generated an SIUnit which is equivalent to kwh.
|
||||
//
|
||||
// Other valid formulas are for example:
|
||||
// energy_kwh = 100 kw * 22 h
|
||||
// flow_m3h = 100 m3 / 5 h
|
||||
//
|
||||
// We can only track units raised to the power of 127 or -128.
|
||||
// The struct SIExp below is used to track the exponents of the units.
|
||||
//
|
||||
struct SIExp
|
||||
{
|
||||
SIExp &s(int8_t i) { s_ = i; return *this; }
|
||||
SIExp &m(int8_t i) { m_ = i; return *this; }
|
||||
SIExp &kg(int8_t i) { kg_ = i; return *this; }
|
||||
SIExp &a(int8_t i) { a_ = i; return *this; }
|
||||
SIExp &mol(int8_t i) { mol_ = i; return *this; }
|
||||
SIExp &cd(int8_t i) { cd_ = i; return *this; }
|
||||
SIExp &k(int8_t i) { k_ = i; if (k_ != 0 && (c_ != 0 || f_ != 0)) { invalid_ = true; } return *this; }
|
||||
SIExp &c(int8_t i) { c_ = i; if (c_ != 0 && (k_ != 0 || f_ != 0)) { invalid_ = true; } return *this; }
|
||||
SIExp &f(int8_t i) { f_ = i; if (f_ != 0 && (k_ != 0 || c_ != 0)) { invalid_ = true; } return *this; }
|
||||
|
||||
int8_t s() const { return s_; }
|
||||
int8_t m() const { return m_; }
|
||||
int8_t kg() const { return kg_; }
|
||||
int8_t a() const { return a_; }
|
||||
int8_t mol() const { return mol_; }
|
||||
int8_t cd() const { return cd_; }
|
||||
int8_t k() const { return k_; }
|
||||
int8_t c() const { return c_; }
|
||||
int8_t f() const { return f_; }
|
||||
SIExp mul(const SIExp &e) const;
|
||||
SIExp div(const SIExp &e) const;
|
||||
SIExp sqrt() const;
|
||||
int8_t safe_add(int8_t a, int8_t b);
|
||||
int8_t safe_sub(int8_t a, int8_t b);
|
||||
int8_t safe_div2(int8_t a);
|
||||
bool operator==(const SIExp &e) const;
|
||||
|
||||
std::string str() const;
|
||||
|
||||
static SIExp build() { return SIExp(); }
|
||||
|
||||
private:
|
||||
|
||||
int8_t s_ {};
|
||||
int8_t m_ {};
|
||||
int8_t kg_ {};
|
||||
int8_t a_ {};
|
||||
int8_t mol_ {};
|
||||
int8_t cd_ {};
|
||||
int8_t k_ {};
|
||||
int8_t c_ {}; // Why c and f here? Because they are offset against K.
|
||||
int8_t f_ {}; // Since SIUnits are also used for conversions of values, not just unit tracking,
|
||||
// this means that the offset units (c,f) must be treated as distinct units.
|
||||
// E.g. if you calculated m3*k (and forget the m3 and k value) then you
|
||||
// cannot convert this value into m3*c since the offset makes the calculation
|
||||
// depend on the k value, which we no longer know.
|
||||
// But kw*h can be translated into kw*s since scaling (*3600) can be done on the
|
||||
// calculated value without knowing the h value. Therefore we have to
|
||||
// treat k, c and f as distinct units. I.e. you cannot add m3*k+m3*f+m3*c.
|
||||
|
||||
// If exponents have over/underflowed or if multiple of k,c,f are set, then the SIExp is not valid any more!
|
||||
bool invalid_ = false;
|
||||
};
|
||||
|
||||
struct SIUnit
|
||||
{
|
||||
// Transform a double,double,uint64_t into an SIUnit.
|
||||
// The exp can be created compile time like this: SIUNIT(3.6E6, 0, SI_KG(1)|SI_M(2)|SI_S(-2)) which is kwh.
|
||||
SIUnit(Quantity q, double scale, SIExp exponents) :
|
||||
quantity_(q), scale_(scale), exponents_(exponents) {}
|
||||
// Transform a named unit into an SIUnit.
|
||||
SIUnit(Unit);
|
||||
// Parse string like: 3.6E106*kg*m^2*s^-3*a^−1 (ie volt)
|
||||
SIUnit(std::string s);
|
||||
// Return the known unit that best matches the SIUnit, or Unit::Unknown if none.
|
||||
Unit asUnit() const ;
|
||||
// Return the known unit that best matches the SIUnit and the quantity, or Unit::Unknown if none.
|
||||
Unit asUnit(Quantity q) const;
|
||||
// Return the quantity that this unit is used for.
|
||||
Quantity quantity() const { return quantity_; }
|
||||
// Get the scale.
|
||||
double scale() const { return scale_; }
|
||||
// Get the exponents.
|
||||
const SIExp &exp() const { return exponents_; }
|
||||
// Return a string like 3.6⁶s⁻²m²kg
|
||||
std::string str() const;
|
||||
// Return a detailed string like: kwh[3.6⁶s⁻²m²kg]Energy
|
||||
std::string info() const;
|
||||
// Check if the exponents (ie units) are the same.
|
||||
bool sameExponents(SIUnit &to) const { return exponents_ == to.exponents_; }
|
||||
// Check if this unit can be converted to the other unit.
|
||||
bool canConvertTo(const SIUnit &to) const;
|
||||
// Convert value from this unit to another unit.
|
||||
double convertTo(double val, const SIUnit &to) const;
|
||||
// Multiply this unit with another unit.
|
||||
SIUnit mul(const SIUnit &m) const ;
|
||||
// Dividethis unit with another unit.
|
||||
SIUnit div(const SIUnit &m) const ;
|
||||
// Square root this unit.
|
||||
SIUnit sqrt() const ;
|
||||
|
||||
private:
|
||||
|
||||
Quantity quantity_;
|
||||
double scale_;
|
||||
SIExp exponents_;
|
||||
};
|
||||
|
||||
bool canConvert(Unit from, Unit to);
|
||||
double convert(double v, Unit from, Unit to);
|
||||
Unit whenMultiplied(Unit left, Unit right);
|
||||
|
@ -95,6 +245,7 @@ double multiply(double l, Unit left, double r, Unit right);
|
|||
|
||||
// Either uppercase KWH or lowercase kwh works here.
|
||||
Unit toUnit(std::string s);
|
||||
const SIUnit &toSIUnit(Unit u);
|
||||
const char *toString(Quantity q);
|
||||
bool isQuantity(Unit u, Quantity q);
|
||||
Quantity toQuantity(Unit u);
|
||||
|
@ -109,4 +260,5 @@ Unit replaceWithConversionUnit(Unit u, std::vector<Unit> cs);
|
|||
|
||||
bool extractUnit(const std::string &s, std::string *vname, Unit *u);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
161
src/util.cc
161
src/util.cc
|
@ -197,7 +197,7 @@ bool isHexStringFlex(const char* txt, bool *invalid)
|
|||
return isHexString(txt, invalid, false);
|
||||
}
|
||||
|
||||
bool isHexStringFlex(const std::string &txt, bool *invalid)
|
||||
bool isHexStringFlex(const string &txt, bool *invalid)
|
||||
{
|
||||
return isHexString(txt.c_str(), invalid, false);
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ bool isHexStringStrict(const char* txt, bool *invalid)
|
|||
return isHexString(txt, invalid, true);
|
||||
}
|
||||
|
||||
bool isHexStringStrict(const std::string &txt, bool *invalid)
|
||||
bool isHexStringStrict(const string &txt, bool *invalid)
|
||||
{
|
||||
return isHexString(txt.c_str(), invalid, true);
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ bool hex2bin(const char* src, vector<uchar> *target)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool hex2bin(string &src, vector<uchar> *target)
|
||||
bool hex2bin(const string &src, vector<uchar> *target)
|
||||
{
|
||||
return hex2bin(src.c_str(), target);
|
||||
}
|
||||
|
@ -251,8 +251,8 @@ bool hex2bin(vector<uchar> &src, vector<uchar> *target)
|
|||
|
||||
char const hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A','B','C','D','E','F'};
|
||||
|
||||
std::string bin2hex(const vector<uchar> &target) {
|
||||
std::string str;
|
||||
string bin2hex(const vector<uchar> &target) {
|
||||
string str;
|
||||
for (size_t i = 0; i < target.size(); ++i) {
|
||||
const char ch = target[i];
|
||||
str.append(&hex[(ch & 0xF0) >> 4], 1);
|
||||
|
@ -261,8 +261,8 @@ std::string bin2hex(const vector<uchar> &target) {
|
|||
return str;
|
||||
}
|
||||
|
||||
std::string bin2hex(vector<uchar>::iterator data, vector<uchar>::iterator end, int len) {
|
||||
std::string str;
|
||||
string bin2hex(vector<uchar>::iterator data, vector<uchar>::iterator end, int len) {
|
||||
string str;
|
||||
while (data != end && len-- > 0) {
|
||||
const char ch = *data;
|
||||
data++;
|
||||
|
@ -272,8 +272,8 @@ std::string bin2hex(vector<uchar>::iterator data, vector<uchar>::iterator end, i
|
|||
return str;
|
||||
}
|
||||
|
||||
std::string bin2hex(vector<uchar> &data, int offset, int len) {
|
||||
std::string str;
|
||||
string bin2hex(vector<uchar> &data, int offset, int len) {
|
||||
string str;
|
||||
vector<uchar>::iterator i = data.begin();
|
||||
i += offset;
|
||||
while (i != data.end() && len-- > 0) {
|
||||
|
@ -285,8 +285,8 @@ std::string bin2hex(vector<uchar> &data, int offset, int len) {
|
|||
return str;
|
||||
}
|
||||
|
||||
std::string safeString(vector<uchar> &target) {
|
||||
std::string str;
|
||||
string safeString(vector<uchar> &target) {
|
||||
string str;
|
||||
for (size_t i = 0; i < target.size(); ++i) {
|
||||
const char ch = target[i];
|
||||
if (ch >= 32 && ch < 127 && ch != '<' && ch != '>') {
|
||||
|
@ -307,20 +307,35 @@ string tostrprintf(const char* fmt, ...)
|
|||
char buf[4096];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, 4095, fmt, args);
|
||||
size_t n = vsnprintf(buf, 4096, fmt, args);
|
||||
assert(n < 4096);
|
||||
va_end(args);
|
||||
s = buf;
|
||||
return s;
|
||||
}
|
||||
|
||||
void strprintf(std::string &s, const char* fmt, ...)
|
||||
string tostrprintf(const string& fmt, ...)
|
||||
{
|
||||
string s;
|
||||
char buf[4096];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
size_t n = vsnprintf(buf, 4096, fmt.c_str(), args);
|
||||
assert(n < 4096);
|
||||
va_end(args);
|
||||
s = buf;
|
||||
return s;
|
||||
}
|
||||
|
||||
void strprintf(string *s, const char* fmt, ...)
|
||||
{
|
||||
char buf[4096];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, 4095, fmt, args);
|
||||
size_t n = vsnprintf(buf, 4096, fmt, args);
|
||||
assert(n < 4096);
|
||||
va_end(args);
|
||||
s = buf;
|
||||
*s = buf;
|
||||
}
|
||||
|
||||
void xorit(uchar *srca, uchar *srcb, uchar *dest, int len)
|
||||
|
@ -344,7 +359,7 @@ void shiftLeft(uchar *srca, uchar *srcb, int len)
|
|||
string format3fdot3f(double v)
|
||||
{
|
||||
string r;
|
||||
strprintf(r, "%3.3f", v);
|
||||
strprintf(&r, "%3.3f", v);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -370,7 +385,7 @@ void enableSyslog() {
|
|||
syslog_enabled_ = true;
|
||||
}
|
||||
|
||||
bool enableLogfile(string logfile, bool daemon)
|
||||
bool enableLogfile(const string& logfile, bool daemon)
|
||||
{
|
||||
log_file_ = logfile;
|
||||
logfile_enabled_ = true;
|
||||
|
@ -613,7 +628,7 @@ bool is_ascii(char c)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool isValidAlias(string alias)
|
||||
bool isValidAlias(const string& alias)
|
||||
{
|
||||
if (alias.length() == 0) return false;
|
||||
|
||||
|
@ -627,8 +642,10 @@ bool isValidAlias(string alias)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool isValidMatchExpression(string me, bool non_compliant)
|
||||
bool isValidMatchExpression(const string& s, bool non_compliant)
|
||||
{
|
||||
string me = s;
|
||||
|
||||
// Examples of valid match expressions:
|
||||
// 12345678
|
||||
// *
|
||||
|
@ -690,7 +707,7 @@ bool isValidMatchExpression(string me, bool non_compliant)
|
|||
return count <= 7;
|
||||
}
|
||||
|
||||
bool isValidMatchExpressions(string mes, bool non_compliant)
|
||||
bool isValidMatchExpressions(const string& mes, bool non_compliant)
|
||||
{
|
||||
vector<string> v = splitMatchExpressions(mes);
|
||||
|
||||
|
@ -701,7 +718,7 @@ bool isValidMatchExpressions(string mes, bool non_compliant)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool isValidId(string id, bool accept_non_compliant)
|
||||
bool isValidId(const string& id, bool accept_non_compliant)
|
||||
{
|
||||
|
||||
for (size_t i=0; i<id.length(); ++i)
|
||||
|
@ -717,8 +734,9 @@ bool isValidId(string id, bool accept_non_compliant)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool doesIdMatchExpression(string id, string match)
|
||||
bool doesIdMatchExpression(const string& s, string match)
|
||||
{
|
||||
string id = s;
|
||||
if (id.length() == 0) return false;
|
||||
|
||||
// Here we assume that the match expression has been
|
||||
|
@ -763,7 +781,7 @@ bool doesIdMatchExpression(string id, string match)
|
|||
return can_match;
|
||||
}
|
||||
|
||||
bool hasWildCard(string &mes)
|
||||
bool hasWildCard(const string& mes)
|
||||
{
|
||||
return mes.find('*') != string::npos;
|
||||
}
|
||||
|
@ -783,7 +801,7 @@ bool doesIdsMatchExpressions(vector<string> &ids, vector<string>& mes, bool *use
|
|||
return match;
|
||||
}
|
||||
|
||||
bool doesIdMatchExpressions(string id, vector<string>& mes, bool *used_wildcard)
|
||||
bool doesIdMatchExpressions(const string& id, vector<string>& mes, bool *used_wildcard)
|
||||
{
|
||||
bool found_match = false;
|
||||
bool found_negative_match = false;
|
||||
|
@ -850,7 +868,7 @@ bool doesIdMatchExpressions(string id, vector<string>& mes, bool *used_wildcard)
|
|||
return false;
|
||||
}
|
||||
|
||||
string toIdsCommaSeparated(std::vector<std::string> &ids)
|
||||
string toIdsCommaSeparated(vector<string> &ids)
|
||||
{
|
||||
string cs;
|
||||
for (string& s: ids)
|
||||
|
@ -862,7 +880,7 @@ string toIdsCommaSeparated(std::vector<std::string> &ids)
|
|||
return cs;
|
||||
}
|
||||
|
||||
bool isFrequency(std::string& fq)
|
||||
bool isFrequency(const string& fq)
|
||||
{
|
||||
int len = fq.length();
|
||||
if (len == 0) return false;
|
||||
|
@ -874,7 +892,7 @@ bool isFrequency(std::string& fq)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool isNumber(std::string& fq)
|
||||
bool isNumber(const string& fq)
|
||||
{
|
||||
int len = fq.length();
|
||||
if (len == 0) return false;
|
||||
|
@ -884,7 +902,7 @@ bool isNumber(std::string& fq)
|
|||
return true;
|
||||
}
|
||||
|
||||
vector<string> splitMatchExpressions(string& mes)
|
||||
vector<string> splitMatchExpressions(const string& mes)
|
||||
{
|
||||
vector<string> r;
|
||||
bool eof, err;
|
||||
|
@ -991,7 +1009,7 @@ bool checkIfDirExists(const char *dir)
|
|||
return false;
|
||||
}
|
||||
|
||||
void debugPayload(string intro, vector<uchar> &payload)
|
||||
void debugPayload(const string& intro, vector<uchar> &payload)
|
||||
{
|
||||
if (isDebugEnabled())
|
||||
{
|
||||
|
@ -1000,7 +1018,7 @@ void debugPayload(string intro, vector<uchar> &payload)
|
|||
}
|
||||
}
|
||||
|
||||
void debugPayload(string intro, vector<uchar> &payload, vector<uchar>::iterator &pos)
|
||||
void debugPayload(const string& intro, vector<uchar> &payload, vector<uchar>::iterator &pos)
|
||||
{
|
||||
if (isDebugEnabled())
|
||||
{
|
||||
|
@ -1082,7 +1100,7 @@ void padWithZeroesTo(vector<uchar> *content, size_t len, vector<uchar> *full_con
|
|||
}
|
||||
|
||||
static string space = " ";
|
||||
string padLeft(string input, int width)
|
||||
string padLeft(const string& input, int width)
|
||||
{
|
||||
int w = width-input.size();
|
||||
if (w < 0) return input;
|
||||
|
@ -1090,7 +1108,9 @@ string padLeft(string input, int width)
|
|||
return space.substr(0, w)+input;
|
||||
}
|
||||
|
||||
int parseTime(string time) {
|
||||
int parseTime(const string& s)
|
||||
{
|
||||
string time = s;
|
||||
int mul = 1;
|
||||
if (time.back() == 'h') {
|
||||
time.pop_back();
|
||||
|
@ -1174,7 +1194,7 @@ bool crc16_CCITT_check(uchar *data, uint16_t length)
|
|||
return crc == CRC16_GOOD_VALUE;
|
||||
}
|
||||
|
||||
bool listFiles(string dir, vector<string> *files)
|
||||
bool listFiles(const string& dir, vector<string> *files)
|
||||
{
|
||||
DIR *dp = NULL;
|
||||
struct dirent *dptr = NULL;
|
||||
|
@ -1204,7 +1224,7 @@ bool listFiles(string dir, vector<string> *files)
|
|||
return true;
|
||||
}
|
||||
|
||||
int loadFile(string file, vector<string> *lines)
|
||||
int loadFile(const string& file, vector<string> *lines)
|
||||
{
|
||||
char block[32768+1];
|
||||
vector<uchar> buf;
|
||||
|
@ -1246,7 +1266,7 @@ int loadFile(string file, vector<string> *lines)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool loadFile(string file, vector<char> *buf)
|
||||
bool loadFile(const string& file, vector<char> *buf)
|
||||
{
|
||||
int blocksize = 1024;
|
||||
char block[blocksize];
|
||||
|
@ -1412,7 +1432,7 @@ void addMonths(struct tm *date, int months)
|
|||
}
|
||||
else
|
||||
{
|
||||
day = std::min(date->tm_mday, get_days_in_month(year, month));
|
||||
day = min(date->tm_mday, get_days_in_month(year, month));
|
||||
}
|
||||
|
||||
date->tm_year = year;
|
||||
|
@ -1433,7 +1453,7 @@ const char* toString(AccessCheck ac)
|
|||
return "?";
|
||||
}
|
||||
|
||||
AccessCheck checkIfExistsAndHasAccess(string device)
|
||||
AccessCheck checkIfExistsAndHasAccess(const string& device)
|
||||
{
|
||||
struct stat device_sb;
|
||||
|
||||
|
@ -1498,12 +1518,12 @@ int countSetBits(int v)
|
|||
return n;
|
||||
}
|
||||
|
||||
bool startsWith(string &s, string &prefix)
|
||||
bool startsWith(const string& s, string &prefix)
|
||||
{
|
||||
return startsWith(s, prefix.c_str());
|
||||
}
|
||||
|
||||
bool startsWith(string &s, const char *prefix)
|
||||
bool startsWith(const string& s, const char *prefix)
|
||||
{
|
||||
size_t len = strlen(prefix);
|
||||
if (s.length() < len) return false;
|
||||
|
@ -1511,7 +1531,7 @@ bool startsWith(string &s, const char *prefix)
|
|||
return !strncmp(&s[0], prefix, len);
|
||||
}
|
||||
|
||||
string makeQuotedJson(string &s)
|
||||
string makeQuotedJson(const string& s)
|
||||
{
|
||||
size_t p = s.find('=');
|
||||
string key, value;
|
||||
|
@ -1608,7 +1628,7 @@ bool hasBytes(int n, vector<uchar>::iterator &pos, vector<uchar> &frame)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool startsWith(string s, std::vector<uchar> &data)
|
||||
bool startsWith(const string& s, vector<uchar> &data)
|
||||
{
|
||||
if (s.length() > data.size()) return false;
|
||||
|
||||
|
@ -1719,7 +1739,7 @@ bool extract_single_period(char *tok, TimePeriod *tp)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool extract_periods(string periods, vector<TimePeriod> *period_structs)
|
||||
bool extract_periods(const string& periods, vector<TimePeriod> *period_structs)
|
||||
{
|
||||
if (periods.length() == 0) return false;
|
||||
|
||||
|
@ -1750,14 +1770,14 @@ bool extract_periods(string periods, vector<TimePeriod> *period_structs)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool isValidTimePeriod(std::string periods)
|
||||
bool isValidTimePeriod(const string& periods)
|
||||
{
|
||||
vector<TimePeriod> period_structs;
|
||||
bool ok = extract_periods(periods, &period_structs);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool isInsideTimePeriod(time_t now, std::string periods)
|
||||
bool isInsideTimePeriod(time_t now, string periods)
|
||||
{
|
||||
struct tm nowt {};
|
||||
localtime_r(&now, &nowt);
|
||||
|
@ -1819,13 +1839,15 @@ void setAlarmShells(vector<string> &alarm_shells)
|
|||
alarm_shells_ = alarm_shells;
|
||||
}
|
||||
|
||||
bool stringFoundCaseIgnored(string haystack, string needle)
|
||||
bool stringFoundCaseIgnored(const string& h, const string& n)
|
||||
{
|
||||
string haystack = h;
|
||||
string needle = n;
|
||||
// Modify haystack and needle, in place, to become lowercase.
|
||||
std::for_each(haystack.begin(), haystack.end(), [](char & c) {
|
||||
for_each(haystack.begin(), haystack.end(), [](char & c) {
|
||||
c = ::tolower(c);
|
||||
});
|
||||
std::for_each(needle.begin(), needle.end(), [](char & c) {
|
||||
for_each(needle.begin(), needle.end(), [](char & c) {
|
||||
c = ::tolower(c);
|
||||
});
|
||||
|
||||
|
@ -1833,12 +1855,12 @@ bool stringFoundCaseIgnored(string haystack, string needle)
|
|||
return haystack.find(needle) != string::npos;
|
||||
}
|
||||
|
||||
vector<string> splitString(string &s, char c)
|
||||
vector<string> splitString(const string &s, char c)
|
||||
{
|
||||
auto end = s.cend();
|
||||
auto start = end;
|
||||
|
||||
std::vector<std::string> v;
|
||||
vector<string> v;
|
||||
for (auto i = s.cbegin(); i != end; ++i)
|
||||
{
|
||||
if (*i != c)
|
||||
|
@ -1862,8 +1884,16 @@ vector<string> splitString(string &s, char c)
|
|||
return v;
|
||||
}
|
||||
|
||||
vector<string> splitDeviceString(string &s)
|
||||
set<string> splitStringIntoSet(const string &s, char c)
|
||||
{
|
||||
vector<string> v = splitString(s, c);
|
||||
set<string> words(v.begin(), v.end());
|
||||
return words;
|
||||
}
|
||||
|
||||
vector<string> splitDeviceString(const string& ds)
|
||||
{
|
||||
string s = ds;
|
||||
string cmd;
|
||||
|
||||
// The CMD(...) might have colons inside.
|
||||
|
@ -1886,7 +1916,7 @@ vector<string> splitDeviceString(string &s)
|
|||
return r;
|
||||
}
|
||||
|
||||
uint32_t indexFromRtlSdrName(string &s)
|
||||
uint32_t indexFromRtlSdrName(const string& s)
|
||||
{
|
||||
size_t p = s.find('_');
|
||||
if (p == string::npos) return -1;
|
||||
|
@ -1981,7 +2011,7 @@ bool check_if_rtlsdr_exists_in_path()
|
|||
return found;
|
||||
}
|
||||
|
||||
std::string currentProcessExe()
|
||||
string currentProcessExe()
|
||||
{
|
||||
char buf[1024];
|
||||
memset(buf, 0, 1024);
|
||||
|
@ -2011,14 +2041,14 @@ std::string currentProcessExe()
|
|||
#endif
|
||||
}
|
||||
|
||||
string dirname(string p)
|
||||
string dirname(const string& p)
|
||||
{
|
||||
size_t s = p.rfind('/');
|
||||
if (s == string::npos) return "";
|
||||
return p.substr(0, s);
|
||||
}
|
||||
|
||||
string lookForExecutable(string prog, string bin_dir, string default_dir)
|
||||
string lookForExecutable(const string& prog, string bin_dir, string default_dir)
|
||||
{
|
||||
string tmp = bin_dir+"/"+prog;
|
||||
if (checkFileExists(tmp.c_str()))
|
||||
|
@ -2033,7 +2063,7 @@ string lookForExecutable(string prog, string bin_dir, string default_dir)
|
|||
return "";
|
||||
}
|
||||
|
||||
bool parseExtras(string s, map<string,string> *extras)
|
||||
bool parseExtras(const string& s, map<string,string> *extras)
|
||||
{
|
||||
vector<string> parts = splitString(s, ' ');
|
||||
|
||||
|
@ -2071,7 +2101,7 @@ void checkIfMultipleWmbusMetersRunning()
|
|||
}
|
||||
}
|
||||
|
||||
bool isValidBps(string b)
|
||||
bool isValidBps(const string& b)
|
||||
{
|
||||
if (b == "300") return true;
|
||||
if (b == "600") return true;
|
||||
|
@ -2103,7 +2133,7 @@ size_t findBytes(vector<uchar> &v, uchar a, uchar b, uchar c)
|
|||
return (size_t)-1;
|
||||
}
|
||||
|
||||
string reverseBCD(string v)
|
||||
string reverseBCD(const string& v)
|
||||
{
|
||||
int vlen = v.length();
|
||||
if (vlen % 2 != 0)
|
||||
|
@ -2120,7 +2150,7 @@ string reverseBCD(string v)
|
|||
return n;
|
||||
}
|
||||
|
||||
string reverseBinaryAsciiSafeToString(string v)
|
||||
string reverseBinaryAsciiSafeToString(const string& v)
|
||||
{
|
||||
vector<uchar> bytes;
|
||||
bool ok = hex2bin(v, &bytes);
|
||||
|
@ -2207,7 +2237,7 @@ void removeSlipFraming(vector<uchar>& from, size_t *frame_length, vector<uchar>
|
|||
}
|
||||
|
||||
// Check if hex string is likely to be ascii
|
||||
bool isLikelyAscii(std::string v)
|
||||
bool isLikelyAscii(const string& v)
|
||||
{
|
||||
vector<uchar> val;
|
||||
bool ok = hex2bin(v, &val);
|
||||
|
@ -2291,11 +2321,22 @@ string sortStatusString(const string &a)
|
|||
|
||||
while (result.size() > 0 && result.back() == ' ') result.pop_back();
|
||||
|
||||
// This feature is only used for the em24 deprecated backwards compatible error field.
|
||||
// This should go away in the future.
|
||||
replace(result.begin(), result.end(), '~', ' ');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uchar *safeButUnsafeVectorPtr(std::vector<uchar> &v)
|
||||
uchar *safeButUnsafeVectorPtr(vector<uchar> &v)
|
||||
{
|
||||
if (v.size() == 0) return NULL;
|
||||
return &v[0];
|
||||
}
|
||||
|
||||
int strlen_utf8(const char *s)
|
||||
{
|
||||
int len = 0;
|
||||
for (; *s; ++s) if ((*s & 0xC0) != 0x80) ++len;
|
||||
return len;
|
||||
}
|
||||
|
|
79
src/util.h
79
src/util.h
|
@ -23,6 +23,7 @@
|
|||
#include<string>
|
||||
#include<functional>
|
||||
#include<map>
|
||||
#include<set>
|
||||
#include<vector>
|
||||
|
||||
void onExit(std::function<void()> cb);
|
||||
|
@ -46,11 +47,11 @@ uchar bcd2bin(uchar c);
|
|||
uchar revbcd2bin(uchar c);
|
||||
uchar reverse(uchar c);
|
||||
// A BCD string 102030405060 is reversed to 605040302010
|
||||
std::string reverseBCD(std::string v);
|
||||
std::string reverseBCD(const std::string& v);
|
||||
// A hex string encoding ascii chars is reversed and safely translated into a readble string.
|
||||
std::string reverseBinaryAsciiSafeToString(std::string v);
|
||||
std::string reverseBinaryAsciiSafeToString(const std::string& v);
|
||||
// Check if hex string is likely to be ascii
|
||||
bool isLikelyAscii(std::string v);
|
||||
bool isLikelyAscii(const std::string& v);
|
||||
|
||||
bool isHexChar(uchar c);
|
||||
|
||||
|
@ -62,14 +63,15 @@ bool isHexStringStrict(const char* txt, bool *invalid);
|
|||
bool isHexStringStrict(const std::string &txt, bool *invalid);
|
||||
int char2int(char input);
|
||||
bool hex2bin(const char* src, std::vector<uchar> *target);
|
||||
bool hex2bin(std::string &src, std::vector<uchar> *target);
|
||||
bool hex2bin(const std::string &src, std::vector<uchar> *target);
|
||||
bool hex2bin(std::vector<uchar> &src, std::vector<uchar> *target);
|
||||
std::string bin2hex(const std::vector<uchar> &target);
|
||||
std::string bin2hex(std::vector<uchar>::iterator data, std::vector<uchar>::iterator end, int len);
|
||||
std::string bin2hex(std::vector<uchar> &data, int offset, int len);
|
||||
std::string safeString(std::vector<uchar> &target);
|
||||
void strprintf(std::string &s, const char* fmt, ...);
|
||||
void strprintf(std::string *s, const char* fmt, ...);
|
||||
std::string tostrprintf(const char* fmt, ...);
|
||||
std::string tostrprintf(const std::string &fmt, ...);
|
||||
|
||||
// Return for example: 2010-03-21
|
||||
std::string strdate(struct tm *date);
|
||||
|
@ -79,12 +81,12 @@ std::string strdatetime(struct tm *date);
|
|||
std::string strdatetimesec(struct tm *date);
|
||||
void addMonths(struct tm* date, int m);
|
||||
|
||||
bool stringFoundCaseIgnored(std::string haystack, std::string needle);
|
||||
bool stringFoundCaseIgnored(const std::string& haystack, const std::string& needle);
|
||||
|
||||
void xorit(uchar *srca, uchar *srcb, uchar *dest, int len);
|
||||
void shiftLeft(uchar *srca, uchar *srcb, int len);
|
||||
std::string format3fdot3f(double v);
|
||||
bool enableLogfile(std::string logfile, bool daemon);
|
||||
bool enableLogfile(const std::string& logfile, bool daemon);
|
||||
void disableLogfile();
|
||||
void enableSyslog();
|
||||
void error(const char* fmt, ...);
|
||||
|
@ -118,8 +120,8 @@ bool isDebugEnabled();
|
|||
bool isTraceEnabled();
|
||||
bool isLogTelegramsEnabled();
|
||||
|
||||
void debugPayload(std::string intro, std::vector<uchar> &payload);
|
||||
void debugPayload(std::string intro, std::vector<uchar> &payload, std::vector<uchar>::iterator &pos);
|
||||
void debugPayload(const std::string& intro, std::vector<uchar> &payload);
|
||||
void debugPayload(const std::string& intro, std::vector<uchar> &payload, std::vector<uchar>::iterator &pos);
|
||||
void logTelegram(std::vector<uchar> &original, std::vector<uchar> &parsed, int header_size, int suffix_size);
|
||||
|
||||
enum class Alarm
|
||||
|
@ -134,26 +136,28 @@ const char* toString(Alarm type);
|
|||
void logAlarm(Alarm type, std::string info);
|
||||
void setAlarmShells(std::vector<std::string> &alarm_shells);
|
||||
|
||||
bool isValidAlias(std::string alias);
|
||||
bool isValidBps(std::string b);
|
||||
bool isValidMatchExpression(std::string id, bool non_compliant);
|
||||
bool isValidMatchExpressions(std::string ids, bool non_compliant);
|
||||
bool doesIdMatchExpression(std::string id, std::string match_rule);
|
||||
bool doesIdMatchExpressions(std::string id, std::vector<std::string>& match_rules, bool *used_wildcard);
|
||||
bool isValidAlias(const std::string& alias);
|
||||
bool isValidBps(const std::string& b);
|
||||
bool isValidMatchExpression(const std::string& s, bool non_compliant);
|
||||
bool isValidMatchExpressions(const std::string& s, bool non_compliant);
|
||||
bool doesIdMatchExpression(const std::string& id, std::string match_rule);
|
||||
bool doesIdMatchExpressions(const std::string& id, std::vector<std::string>& match_rules, bool *used_wildcard);
|
||||
bool doesIdsMatchExpressions(std::vector<std::string> &ids, std::vector<std::string>& match_rules, bool *used_wildcard);
|
||||
std::string toIdsCommaSeparated(std::vector<std::string> &ids);
|
||||
|
||||
bool isValidId(std::string id, bool accept_non_compliant);
|
||||
bool isValidId(const std::string& id, bool accept_non_compliant);
|
||||
|
||||
bool isFrequency(std::string& fq);
|
||||
bool isNumber(std::string& fq);
|
||||
bool isFrequency(const std::string& fq);
|
||||
bool isNumber(const std::string& fq);
|
||||
|
||||
std::vector<std::string> splitMatchExpressions(std::string& mes);
|
||||
std::vector<std::string> splitMatchExpressions(const std::string& mes);
|
||||
// Split s into strings separated by c.
|
||||
std::vector<std::string> splitString(std::string &s, char c);
|
||||
std::vector<std::string> splitString(const std::string &s, char c);
|
||||
// Split s into strings separated by c and store inte set.
|
||||
std::set<std::string> splitStringIntoSet(const std::string &s, char c);
|
||||
// Split device string cul:c1:CMD(bar 1:2) into cul c1 CMD(bar 1:2)
|
||||
// I.e. the : colon inside CMD is not used for splitting.
|
||||
std::vector<std::string> splitDeviceString(std::string &s);
|
||||
std::vector<std::string> splitDeviceString(const std::string &s);
|
||||
|
||||
void incrementIV(uchar *iv, size_t len);
|
||||
|
||||
|
@ -161,24 +165,24 @@ bool checkCharacterDeviceExists(const char *tty, bool fail_if_not);
|
|||
bool checkFileExists(const char *file);
|
||||
bool checkIfSimulationFile(const char *file);
|
||||
bool checkIfDirExists(const char *dir);
|
||||
bool listFiles(std::string dir, std::vector<std::string> *files);
|
||||
int loadFile(std::string file, std::vector<std::string> *lines);
|
||||
bool loadFile(std::string file, std::vector<char> *buf);
|
||||
bool listFiles(const std::string& dir, std::vector<std::string> *files);
|
||||
int loadFile(const std::string& file, std::vector<std::string> *lines);
|
||||
bool loadFile(const std::string& file, std::vector<char> *buf);
|
||||
|
||||
std::string eatTo(std::vector<uchar> &v, std::vector<uchar>::iterator &i, int c, size_t max, bool *eof, bool *err);
|
||||
|
||||
void padWithZeroesTo(std::vector<uchar> *content, size_t len, std::vector<uchar> *full_content);
|
||||
std::string padLeft(std::string input, int width);
|
||||
std::string padLeft(const std::string& input, int width);
|
||||
|
||||
// Parse text string into seconds, 5h = (3600*5) 2m = (60*2) 1s = 1
|
||||
int parseTime(std::string time);
|
||||
int parseTime(const std::string& time);
|
||||
|
||||
// Test if current time is inside any of the specified periods.
|
||||
// For example: mon-sun(00-24) is always true!
|
||||
// mon-fri(08-20) is true monday to friday from 08.00 to 19.59
|
||||
// tue(09-10),sat(00-24) is true tuesday 09.00 to 09.59 and whole of saturday.
|
||||
bool isInsideTimePeriod(time_t now, std::string periods);
|
||||
bool isValidTimePeriod(std::string periods);
|
||||
bool isValidTimePeriod(const std::string& periods);
|
||||
|
||||
uint16_t crc16_EN13757(uchar *data, size_t len);
|
||||
|
||||
|
@ -209,15 +213,15 @@ void trimWhitespace(std::string *s);
|
|||
// NoProperResponse means that we talked to something, but we do not know what it is.
|
||||
enum class AccessCheck { NoSuchDevice, NoProperResponse, NoPermission, NotSameGroup, AccessOK };
|
||||
const char* toString(AccessCheck ac);
|
||||
AccessCheck checkIfExistsAndHasAccess(std::string device);
|
||||
AccessCheck checkIfExistsAndHasAccess(const std::string& device);
|
||||
// Count the number of 1:s in the binary number v.
|
||||
int countSetBits(int v);
|
||||
|
||||
bool startsWith(std::string &s, const char *prefix);
|
||||
bool startsWith(std::string &s, std::string &prefix);
|
||||
bool startsWith(const std::string &s, const char *prefix);
|
||||
bool startsWith(const std::string &s, std::string &prefix);
|
||||
|
||||
// Given alfa=beta it returns "alfa":"beta"
|
||||
std::string makeQuotedJson(std::string &s);
|
||||
std::string makeQuotedJson(const std::string &s);
|
||||
|
||||
std::string currentYear();
|
||||
std::string currentDay();
|
||||
|
@ -230,14 +234,14 @@ std::string currentMicros();
|
|||
|
||||
bool hasBytes(int n, std::vector<uchar>::iterator &pos, std::vector<uchar> &frame);
|
||||
|
||||
bool startsWith(std::string s, std::vector<uchar> &data);
|
||||
bool startsWith(const std::string& s, std::vector<uchar> &data);
|
||||
|
||||
// Sum the memory used by the heap and stack.
|
||||
size_t memoryUsage();
|
||||
|
||||
std::string humanReadableTwoDecimals(size_t s);
|
||||
|
||||
uint32_t indexFromRtlSdrName(std::string &s);
|
||||
uint32_t indexFromRtlSdrName(const std::string& s);
|
||||
|
||||
bool check_if_rtlwmbus_exists_in_path();
|
||||
bool check_if_rtlsdr_exists_in_path();
|
||||
|
@ -245,12 +249,12 @@ bool check_if_rtlsdr_exists_in_path();
|
|||
// Return the actual executable binary that is running.
|
||||
std::string currentProcessExe();
|
||||
|
||||
std::string dirname(std::string p);
|
||||
std::string dirname(const std::string& p);
|
||||
|
||||
std::string lookForExecutable(std::string prog, std::string bin_dir, std::string default_dir);
|
||||
std::string lookForExecutable(const std::string& prog, std::string bin_dir, std::string default_dir);
|
||||
|
||||
// Extract from "ppm=5 radix=7" and put key values into map.
|
||||
bool parseExtras(std::string s, std::map<std::string,std::string> *extras);
|
||||
bool parseExtras(const std::string& s, std::map<std::string,std::string> *extras);
|
||||
void checkIfMultipleWmbusMetersRunning();
|
||||
|
||||
size_t findBytes(std::vector<uchar> &v, uchar a, uchar b, uchar c);
|
||||
|
@ -272,6 +276,7 @@ std::string joinStatusStrings(const std::string &a, const std::string &b);
|
|||
|
||||
// Sort the words in a status string: "GAMMA BETA ALFA" --> "ALFA BETA GAMMA"
|
||||
// Also identical flags are merged: "BETA ALFA ALFA" --> "ALFA BETA"
|
||||
// Finally ~ is replaced with a space, this is only used for backwards compatibilty for deprecated fields.
|
||||
std::string sortStatusString(const std::string &a);
|
||||
|
||||
// If a vector is empty, then there will be an assert (with some compiler flags) if we use &v[0]
|
||||
|
@ -279,6 +284,8 @@ std::string sortStatusString(const std::string &a);
|
|||
// So provide safeVectorPtr which will return NULL instead of assert-crashing.
|
||||
uchar *safeButUnsafeVectorPtr(std::vector<uchar> &v);
|
||||
|
||||
// Count utf8 unicode code points.
|
||||
int strlen_utf8(const char *s);
|
||||
|
||||
#ifndef FUZZING
|
||||
#define FUZZING false
|
||||
|
|
60
src/wmbus.cc
60
src/wmbus.cc
|
@ -3223,7 +3223,7 @@ string vif_7B_FirstExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7e) == 0x00) {
|
||||
int n = vife & 0x01;
|
||||
string s;
|
||||
strprintf(s, "10^%d MWh", n-1);
|
||||
strprintf(&s, "10^%d MWh", n-1);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3235,7 +3235,7 @@ string vif_7B_FirstExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7e) == 0x08) {
|
||||
int n = vife & 0x01;
|
||||
string s;
|
||||
strprintf(s, "10^%d GJ", n-1);
|
||||
strprintf(&s, "10^%d GJ", n-1);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3247,7 +3247,7 @@ string vif_7B_FirstExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7e) == 0x10) {
|
||||
int n = vife & 0x01;
|
||||
string s;
|
||||
strprintf(s, "10^%d m3", n+2);
|
||||
strprintf(&s, "10^%d m3", n+2);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3259,14 +3259,14 @@ string vif_7B_FirstExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7e) == 0x18) {
|
||||
int n = vife & 0x01;
|
||||
string s;
|
||||
strprintf(s, "10^%d ton", n+2);
|
||||
strprintf(&s, "10^%d ton", n+2);
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x7e) == 0x1a) {
|
||||
int n = vife & 0x01;
|
||||
string s;
|
||||
strprintf(s, "Relative Humidity 10^%d %%", n-1);
|
||||
strprintf(&s, "Relative Humidity 10^%d %%", n-1);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3315,7 +3315,7 @@ string vif_7B_FirstExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
// output from a nuclear power plant?
|
||||
int n = vife & 0x01;
|
||||
string s;
|
||||
strprintf(s, "10^%d MW", n-1);
|
||||
strprintf(&s, "10^%d MW", n-1);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3327,7 +3327,7 @@ string vif_7B_FirstExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7e) == 0x30) {
|
||||
int n = vife & 0x01;
|
||||
string s;
|
||||
strprintf(s, "10^%d GJ/h", n-1);
|
||||
strprintf(&s, "10^%d GJ/h", n-1);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3338,28 +3338,28 @@ string vif_7B_FirstExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7c) == 0x58) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Flow temperature 10^%d Fahrenheit", nn-3);
|
||||
strprintf(&s, "Flow temperature 10^%d Fahrenheit", nn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x7c) == 0x5c) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Return temperature 10^%d Fahrenheit", nn-3);
|
||||
strprintf(&s, "Return temperature 10^%d Fahrenheit", nn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x7c) == 0x60) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Temperature difference 10^%d Fahrenheit", nn-3);
|
||||
strprintf(&s, "Temperature difference 10^%d Fahrenheit", nn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x7c) == 0x64) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "External temperature 10^%d Fahrenheit", nn-3);
|
||||
strprintf(&s, "External temperature 10^%d Fahrenheit", nn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3370,21 +3370,21 @@ string vif_7B_FirstExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7c) == 0x70) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Cold / Warm Temperature Limit 10^%d Fahrenheit", nn-3);
|
||||
strprintf(&s, "Cold / Warm Temperature Limit 10^%d Fahrenheit", nn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x7c) == 0x74) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Cold / Warm Temperature Limit 10^%d Celsius", nn-3);
|
||||
strprintf(&s, "Cold / Warm Temperature Limit 10^%d Celsius", nn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x78) == 0x78) {
|
||||
int nnn = vife & 0x07;
|
||||
string s;
|
||||
strprintf(s, "Cumulative count max power 10^%d W", nnn-3);
|
||||
strprintf(&s, "Cumulative count max power 10^%d W", nnn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3398,14 +3398,14 @@ string vif_7D_SecondExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7c) == 0x00) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Credit of 10^%d of the nominal local legal currency units", nn-3);
|
||||
strprintf(&s, "Credit of 10^%d of the nominal local legal currency units", nn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x7c) == 0x04) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Debit of 10^%d of the nominal local legal currency units", nn-3);
|
||||
strprintf(&s, "Debit of 10^%d of the nominal local legal currency units", nn-3);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3524,7 +3524,7 @@ string vif_7D_SecondExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7c) == 0x24) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Storage interval [%s]", timeNN(nn));
|
||||
strprintf(&s, "Storage interval [%s]", timeNN(nn));
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3547,7 +3547,7 @@ string vif_7D_SecondExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7c) == 0x2c) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Duration since last readout [%s]", timeNN(nn));
|
||||
strprintf(&s, "Duration since last readout [%s]", timeNN(nn));
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3559,14 +3559,14 @@ string vif_7D_SecondExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
int nn = vife & 0x03;
|
||||
string s;
|
||||
// nn == 0 (seconds) is not expected here! According to m-bus spec.
|
||||
strprintf(s, "Duration of tariff [%s]", timeNN(nn));
|
||||
strprintf(&s, "Duration of tariff [%s]", timeNN(nn));
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x7c) == 0x34) {
|
||||
int nn = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Period of tariff [%s]", timeNN(nn));
|
||||
strprintf(&s, "Period of tariff [%s]", timeNN(nn));
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3594,14 +3594,14 @@ string vif_7D_SecondExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x70) == 0x40) {
|
||||
int nnnn = vife & 0x0f;
|
||||
string s;
|
||||
strprintf(s, "10^%d Volts", nnnn-9);
|
||||
strprintf(&s, "10^%d Volts", nnnn-9);
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x70) == 0x50) {
|
||||
int nnnn = vife & 0x0f;
|
||||
string s;
|
||||
strprintf(s, "10^%d Ampere", nnnn-12);
|
||||
strprintf(&s, "10^%d Ampere", nnnn-12);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3640,14 +3640,14 @@ string vif_7D_SecondExtensionType(uchar dif, uchar vif, uchar vife)
|
|||
if ((vife & 0x7c) == 0x68) {
|
||||
int pp = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Duration since last cumulation [%s]", timePP(pp));
|
||||
strprintf(&s, "Duration since last cumulation [%s]", timePP(pp));
|
||||
return s;
|
||||
}
|
||||
|
||||
if ((vife & 0x7c) == 0x6c) {
|
||||
int pp = vife & 0x03;
|
||||
string s;
|
||||
strprintf(s, "Operating time battery [%s]", timePP(pp));
|
||||
strprintf(&s, "Operating time battery [%s]", timePP(pp));
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -3969,7 +3969,7 @@ string formatData(int dif, int vif, int vife, string data)
|
|||
if (t >= 0 && t <= 0x77 && !(t >= 0x6c && t<=0x6f)) {
|
||||
// These are vif codes with an understandable key and unit.
|
||||
double val = dataAsDouble(dif, vif, vife, data);
|
||||
strprintf(r, "%d", val);
|
||||
strprintf(&r, "%d", val);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -4313,7 +4313,7 @@ void BusDeviceCommonImplementation::checkStatus()
|
|||
bool ok = reset();
|
||||
if (ok) return;
|
||||
string msg;
|
||||
strprintf(msg, "failed regular reset of %s %s", device().c_str(), toString(type()));
|
||||
strprintf(&msg, "failed regular reset of %s %s", device().c_str(), toString(type()));
|
||||
logAlarm(Alarm::RegularResetFailure, msg);
|
||||
return;
|
||||
}
|
||||
|
@ -4321,7 +4321,7 @@ void BusDeviceCommonImplementation::checkStatus()
|
|||
if (protocol_error_count_ >= 20)
|
||||
{
|
||||
string msg;
|
||||
strprintf(msg, "too many protocol errors(%d) resetting %s %s", protocol_error_count_, device().c_str(), toString(type()));
|
||||
strprintf(&msg, "too many protocol errors(%d) resetting %s %s", protocol_error_count_, device().c_str(), toString(type()));
|
||||
logAlarm(Alarm::DeviceFailure, msg);
|
||||
bool ok = reset();
|
||||
if (ok)
|
||||
|
@ -4331,7 +4331,7 @@ void BusDeviceCommonImplementation::checkStatus()
|
|||
return;
|
||||
}
|
||||
|
||||
strprintf(msg, "failed to reset wmbus device %s %s exiting wmbusmeters", device().c_str(), toString(type()));
|
||||
strprintf(&msg, "failed to reset wmbus device %s %s exiting wmbusmeters", device().c_str(), toString(type()));
|
||||
logAlarm(Alarm::DeviceFailure, msg);
|
||||
manager_->stop();
|
||||
return;
|
||||
|
@ -4370,7 +4370,7 @@ void BusDeviceCommonImplementation::checkStatus()
|
|||
string nowtxt = strdatetime(&nowtm);
|
||||
|
||||
string msg;
|
||||
strprintf(msg, "%d seconds of inactivity resetting %s %s "
|
||||
strprintf(&msg, "%d seconds of inactivity resetting %s %s "
|
||||
"(timeout %ds expected %s now %s)",
|
||||
since, device().c_str(), toString(type()),
|
||||
timeout_, expected_activity_.c_str(), nowtxt.c_str());
|
||||
|
@ -4384,7 +4384,7 @@ void BusDeviceCommonImplementation::checkStatus()
|
|||
}
|
||||
else
|
||||
{
|
||||
strprintf(msg, "failed to reset wmbus device %s %s exiting wmbusmeters", device().c_str(), toString(type()));
|
||||
strprintf(&msg, "failed to reset wmbus device %s %s exiting wmbusmeters", device().c_str(), toString(type()));
|
||||
logAlarm(Alarm::DeviceFailure, msg);
|
||||
manager_->stop();
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ struct IM871ADeviceInfo
|
|||
else s+="unknown_mode("+to_string(device_mode)+") ";
|
||||
|
||||
string ss;
|
||||
strprintf(ss, "firmware=%02x hci=%02x uid=%08x", firmware_version, hci_version, uid);
|
||||
strprintf(&ss, "firmware=%02x hci=%02x uid=%08x", firmware_version, hci_version, uid);
|
||||
return s+ss;
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ struct ConfigIM871AIM170A
|
|||
string dongleId()
|
||||
{
|
||||
string s;
|
||||
strprintf(s, "%08x", id);
|
||||
strprintf(&s, "%08x", id);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ struct ConfigIM871AIM170A
|
|||
s += "link_mode="+toString(LinkModeIM871A(link_mode));
|
||||
|
||||
string ids;
|
||||
strprintf(ids, " id=%08x media=%02x version=%02x c_field=%02x auto_rssi=%02x", id, media, version, c_field, auto_rssi);
|
||||
strprintf(&ids, " id=%08x media=%02x version=%02x c_field=%02x auto_rssi=%02x", id, media, version, c_field, auto_rssi);
|
||||
|
||||
return s+ids;
|
||||
}
|
||||
|
|
6
test.sh
6
test.sh
|
@ -24,9 +24,6 @@ if [ "$?" != "0" ]; then RC="1"; fi
|
|||
tests/test_s1_meters.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_electricity_meters.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_mbus.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
|
@ -84,6 +81,9 @@ if [ "$?" != "0" ]; then RC="1"; fi
|
|||
tests/test_conversions.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_formulas.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
tests/test_fields.sh $PROG
|
||||
if [ "$?" != "0" ]; then RC="1"; fi
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
format=fields
|
||||
device=rtlwmbus
|
||||
selectfields=total_m3,gurka_m3,sum_c
|
||||
calculate_gurka_m3=total_m3+55l
|
|
@ -0,0 +1,4 @@
|
|||
name=Vatten
|
||||
driver=multical21
|
||||
id=76348799
|
||||
calculate_sum_c=flow_temperature_c+external_temperature_c
|
|
@ -13,11 +13,7 @@ cat > $TEST/test_expected.txt <<EOF
|
|||
(dvparser) warning: unexpected end of data
|
||||
(dvparser) warning: unexpected end of data
|
||||
(dvparser) found new format "046D0406041301FD17426C4406840106840206840306840406840506840606840706840806840906C1337F47A64E0C062364" with hash b934, remembering!
|
||||
(meter) while processing field extractors ignoring dventry 0C06 at offset 129 matching since field total was already matched against dventry 0406 at offset 25 !
|
||||
(dvparser) found new format "046D0406041301FD17426C4406840106840206840306840406840506840606840706840806840906585D65E6958F6B5E93DBA60CD99D06EB27D97106000000840F060003620501" with hash 6c76, remembering!
|
||||
(meter) while processing field extractors ignoring dventry 0000 at offset 146 matching since field total was already matched against dventry 0406 at offset 25 !
|
||||
(meter) while processing field extractors ignoring dventry 0600 at offset 151 matching since field total was already matched against dventry 0406 at offset 25 !
|
||||
(meter) while processing field extractors ignoring dventry 0501 at offset 166 matching since field total was already matched against dventry 0406 at offset 25 !
|
||||
EOF
|
||||
|
||||
$PROG --format=fields --selectfields=id,current_consumption_hca,device_date_time --debug simulations/simulation_broken.txt \
|
||||
|
@ -35,5 +31,11 @@ then
|
|||
fi
|
||||
else
|
||||
echo "wmbusmeters returned error code: $?"
|
||||
echo "EXPECTED----------------------------------------------"
|
||||
cat $TEST/test_expected.txt
|
||||
echo "----------------------------------------------"
|
||||
echo "GOT----------------------------------------------"
|
||||
cat $TEST/test_output.txt
|
||||
echo "----------------------------------------------"
|
||||
|
||||
fi
|
||||
|
|
|
@ -41,6 +41,8 @@ do
|
|||
echo OK json: $TESTNAME
|
||||
TESTRESULT="OK"
|
||||
else
|
||||
jq --sort-keys . $TEST/test_expected_json.txt
|
||||
jq --sort-keys . $TEST/test_response_json.txt
|
||||
TESTRESULT="ERROR"
|
||||
fi
|
||||
else
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
PROG="$1"
|
||||
|
||||
mkdir -p testoutput
|
||||
TEST=testoutput
|
||||
|
||||
TESTNAME="Test electricity meters with radio converter plugins"
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
METERS="MyEl ehzp 01135263 NOKEY"
|
||||
|
||||
cat simulations/simulation_el_meters.txt | grep '^{' > $TEST/test_expected.txt
|
||||
$PROG --format=json simulations/simulation_el_meters.txt $METERS > $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
echo OK json: $TESTNAME
|
||||
TESTRESULT="OK"
|
||||
else
|
||||
TESTRESULT="ERROR"
|
||||
fi
|
||||
else
|
||||
echo "wmbusmeters returned error code: $?"
|
||||
cat $TEST/test_output.txt
|
||||
cat $TEST/test_stderr.txt
|
||||
fi
|
||||
|
||||
cat simulations/simulation_el_meters.txt | grep '^|' | sed 's/^|//' > $TEST/test_expected.txt
|
||||
$PROG --format=fields simulations/simulation_el_meters.txt $METERS > $TEST/test_output.txt 2> $TEST/test_stderr.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9].[0-9][0-9]$/1111-11-11 11:11.11/' > $TEST/test_responses.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
echo OK fields: $TESTNAME
|
||||
TESTRESULT="OK"
|
||||
else
|
||||
TESTRESULT="ERROR"
|
||||
fi
|
||||
else
|
||||
echo "wmbusmeters returned error code: $?"
|
||||
cat $TEST/test_output.txt
|
||||
cat $TEST/test_stderr.txt
|
||||
fi
|
||||
|
||||
|
||||
if [ "$TESTRESULT" = "ERROR" ]
|
||||
then
|
||||
echo ERROR: $TESTNAME
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,57 @@
|
|||
#!/bin/sh
|
||||
|
||||
PROG="$1"
|
||||
|
||||
rm -rf testoutput
|
||||
mkdir -p testoutput
|
||||
TEST=testoutput
|
||||
|
||||
TESTNAME="Test cmdline formulas --calculate_... "
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
$PROG --format=json \
|
||||
--calculate_sumtemp_c='external_temperature_c+flow_temperature_c' \
|
||||
--calculate_addtemp_c='external_temperature_c + 1100 c' \
|
||||
23442D2C998734761B168D2087D19EAD217F1779EDA86AB6_710008190000081900007F13 \
|
||||
MyTapWater multical21 76348799 "" \
|
||||
> $TEST/test_output.txt
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","status":"DRY","total_m3":6.408,"target_m3":6.408,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","sumtemp_c":146,"addtemp_c":1119,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
EOF
|
||||
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
echo "OK: $TESTNAME"
|
||||
TESTRESULT="OK"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi
|
||||
|
||||
TESTNAME="Test meter config formulas calculate_... "
|
||||
TESTRESULT="ERROR"
|
||||
|
||||
$PROG --useconfig=tests/config13 --overridedevice=simulations/simulation_formulas.txt \
|
||||
> $TEST/test_output.txt
|
||||
|
||||
cat > $TEST/test_expected.txt <<EOF
|
||||
6.408;6.463;69
|
||||
EOF
|
||||
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
if [ "$?" = "0" ]
|
||||
then
|
||||
echo "OK: $TESTNAME"
|
||||
TESTRESULT="OK"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi
|
|
@ -194,7 +194,7 @@ Received telegram from: 77997799
|
|||
manufacturer: (ESY) EasyMeter (0x1679)
|
||||
type: Electricity meter (0x02) encrypted
|
||||
ver: 0x11
|
||||
driver: esyswm
|
||||
driver: esyswm esyswm
|
||||
Received telegram from: 55995599
|
||||
manufacturer: (EMH) EMH metering formerly EMH Elektrizitatszahler (0x15a8)
|
||||
type: Electricity meter (0x02) encrypted
|
||||
|
|
|
@ -163,6 +163,8 @@ for e in xmldoc.getElementsByTagName('Meter'):
|
|||
wmbusmeters_driver = 'multical21'
|
||||
elif (meterName == 'MC603') and (meterModel.startswith('603')):
|
||||
wmbusmeters_driver = 'multical603'
|
||||
elif (meterName == 'KWM2210'):
|
||||
wmbusmeters_driver = 'flowiq2200'
|
||||
else:
|
||||
wmbusmeters_driver = None
|
||||
|
||||
|
@ -181,7 +183,7 @@ for e in xmldoc.getElementsByTagName('Meter'):
|
|||
try:
|
||||
f = open(args.config+meterSerial, 'w')
|
||||
f.write("name=%s\n" % (meterNum))
|
||||
f.write("type=%s\n" % (wmbusmeters_driver))
|
||||
f.write("driver=%s\n" % (wmbusmeters_driver))
|
||||
f.write("id=%s\n" % (meterSerial))
|
||||
f.write("key=%s\n" % (meterKey))
|
||||
f.close()
|
||||
|
|
Ładowanie…
Reference in New Issue