Merge branch 'master' into master

pull/645/head
Fredrik Öhrström 2022-11-14 08:53:03 +01:00 zatwierdzone przez GitHub
commit 3001e4fabe
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
84 zmienionych plików z 4519 dodań i 2162 usunięć

23
CHANGES
Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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!

Wyświetl plik

@ -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"
}
}
}

Wyświetl plik

@ -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}",

Wyświetl plik

@ -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"
}
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -0,0 +1 @@
telegram=|23442D2C998734761B168D2087D19EAD214bc479EDA86AB6_710008190000081900003213|

Wyświetl plik

@ -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|

Wyświetl plik

@ -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);

Wyświetl plik

@ -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());

Wyświetl plik

@ -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.

Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -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_);

Wyświetl plik

@ -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;

Wyświetl plik

@ -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,

Wyświetl plik

@ -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,

Wyświetl plik

@ -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;

Wyświetl plik

@ -39,7 +39,7 @@ namespace
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
{
addOptionalCommonFields();
addOptionalCommonFields("software_version");
addStringField(
"status",

Wyświetl plik

@ -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);

Wyświetl plik

@ -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

86
src/driver_ehzp.cc 100644
Wyświetl plik

@ -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

Wyświetl plik

@ -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

169
src/driver_em24.cc 100644
Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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.",

Wyświetl plik

@ -36,7 +36,7 @@ namespace
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
{
addOptionalCommonFields();
addOptionalCommonFields("fabrication_no,software_version");
addStringField(
"status",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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

Wyświetl plik

@ -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,

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -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) { }

Wyświetl plik

@ -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";
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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

Wyświetl plik

@ -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))
{

Wyświetl plik

@ -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) \

Wyświetl plik

@ -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, &current_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_);
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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, &current_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, &current_power_phase1_kw_);
t->addMoreExplanation(offset, " current power phase 1 (%f kwh)", current_power_phase1_kw_);
extractDVdouble(&t->dv_entries, "04A9FF02", &offset, &current_power_phase2_kw_);
t->addMoreExplanation(offset, " current power phase 2 (%f kwh)", current_power_phase2_kw_);
extractDVdouble(&t->dv_entries, "04A9FF03", &offset, &current_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());
}

Wyświetl plik

@ -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]);
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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)

Wyświetl plik

@ -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(&current_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(&current_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));

Wyświetl plik

@ -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));

Wyświetl plik

@ -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;

Wyświetl plik

@ -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, &current_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;
}

Wyświetl plik

@ -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());

Wyświetl plik

@ -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, &parameter_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);
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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)
);
}
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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; }

Wyświetl plik

@ -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

Wyświetl plik

@ -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+" ";
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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,"",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,"",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

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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

Wyświetl plik

@ -0,0 +1,4 @@
format=fields
device=rtlwmbus
selectfields=total_m3,gurka_m3,sum_c
calculate_gurka_m3=total_m3+55l

Wyświetl plik

@ -0,0 +1,4 @@
name=Vatten
driver=multical21
id=76348799
calculate_sum_c=flow_temperature_c+external_temperature_c

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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()