# S0Meters Python program for logging data captured by ***DualImpCount*** (Arduino based S0 impulse counter module). Intended to run (but not limited to) on a Raspberry Pi. I developed this plus the hardware counter module because directly logging impulse counters with the Raspberry Pi was not reliable enough for me. Missed impulses, because the Pi was not running for some time, ghost impulses because the RasPi GPIOs seems somewhat unreliable (at least when reading them using Python), even when always using optocouplers on the impulse inputs. ## Functions - Data logging, implemented in 2 manners: - InfluxDB (current reading, current momentary power/usage) - file log (only daily minimum and total value) - calculation of momentary power (or usage, depending on meter type) from measured time between impulses received from capturing module - MQTT publishing of all current values - calculation of todays/yesterdays total usage and publishing it over MQTT (only if file logging is enabled) - configurable multiple InfluxDB instances (i.E. different DBs for a power meter and a gas meter and also for momentary (power) and long term (energy readings) data of the same meter) - additional interval for energy readings, not to flood InfluxDB with data every few seconds as the hardware outputs data on every detected impulse (not implemented for momentary readings as there a high resolution is desired in the logs) ## Installation This example describes my setup - running on a Raspberry Pi 3/4 as user "pi". 1. extract s0meters folder to /home/pi/s0meters 2. install prerequisites: - Python 3 - Python 3 modules: - pyserial - paho-mqtt - PyYAML - influxdb ```sh pip3 install pyserial paho-mqtt PyYAML influxdb ``` 3. copy or symlink s0meters.service to /etc/systemd/system, then reload systemctl and start service: ```sh sudo ln -s s0meters.service /etc/systemd/system or sudo cp s0meters.service /etc/systemd/system sudo systemctl daemon-reload sudo systemctl enable s0meters.service ``` 4. edit configuration to your needs (see below) 5. when configuration is finished (and tested in interactive mode), start service: ```sh sudo systemctl start s0meters.service ``` 6. check if it is still running after some time: ```sh sudo systemctl status s0meters.service ``` ## Configuration Configuration is split in 3 parts. Base configuration is in ini format - most parameters are self describing #### Main configuration config file: s0meters.ini ```ini [main] # add timestamps to console output in debug/verbose mode consoleAddTimestamp = true # additional YAML config files meters_config_yml = s0meters.yml influx_config_yml = s0meters_influxdb.yml [hardware] serialPort = /dev/ttyUSB0 serialBaud = 57600 serialTout = 1 [filelog] # needed for calculating today/yesterday totals, so only disable if that is not needed enable = True # storage path must be existing and writable for the user that runs s0meters.py (sub directories are created as needed) storage_path = /home/pi/s0meters [mqtt] enable = true server = mqtt.lan port = 1883 keepalive = 60 user = mqttuser password = ******* # MQTT topics # Note: this is only for status infos and commands, meters Out-Topics must be defined in the meters configuration! # In-Topic for sending commands to the device topic_cmd = Top5/ImpCount/cmd # Out-Topic topic_stat = Top5/ImpCount/stat # Out-Topic for response to commands (can be the same as topic_stat) topic_cmdresponse = Top5/ImpCount/cmdResponse ``` #### Meters configuration config file: meters.yml (unless differently defined in s0meters.ini) In this YAML styled config file, all meters/counters are declared. The main identifier is the counter number used in the hardware counter module, so for "C1" the identifier is 1 and so on. ```yaml 1: # meter ID as used in counter hardware (="C1") name: "Power" impPerUnit: 1000 unit: "kWh" digits: 3 momType: "power" #momUnit: "kW" #momDigits: 3 #momFactor: 1 momUnit: "W" momDigits: 0 momFactor: 1000 statTopic: "Powermeter" influxInstance_energy: "energymeters" influxInstance_mom: "energy_momentary" influxMeasurement_energy: "energy" influxMeasurement_mom: "energy" influxFieldName_energy: "Energy_Total__kWh" influxFieldName_mom: "Power__W" influxMinWriteInterval_energy: 300 2: # meter ID as used in counter hardware (="C2") name: "Gas" impPerUnit: 100 unit: "m3" digits: 2 type: "usage" momType: "momUsage" momUnit: "m3/h" momDigits: 2 momFactor: 1 statTopic: "Gasmeter" influxInstance_energy: "energymeters" influxInstance_mom: "energy_momentary" influxMeasurement_energy: "gas" influxMeasurement_mom: "gas" influxFieldName_energy: "Gas_Total__m3" influxFieldName_mom: "Gas_Usage__m3_h" influxMinWriteInterval_energy: 300 ``` - **name**: free text, used for path of file log and internally - **impPerUnit**: integer, normally 10, 100 or 1000, used for calculation of momentary units - **unit**: free text, currently only used in MQTT JSON output - **digits**: integer, number of digits in output of counter reading - **momType**: type of momentary reading. used as MQTT sub-topic - **momUnit**: free text, currently only used in MQTT JSON output - **momDigits**: integer, number of digits in output of momentary value - **momFactor**: factor for calculation of momentary value Formula for calculation: momValue = (3600000 / dTime / impPerUnit) * momFactor where dTime = time between impulses in milliseconds Normally 1, except if the unit of momentary value should be different - for example: - power meter, 1000 imp per unit = 1 kWh: momFactor=1000 -> output in Watts momFactor=1 -> output in kW - gas meter, 100 imp per unit = 1m³: momFactor=1 --> output in m³/h momFactor=1000 --> output in dm³/h (l/h) - **statTopic**: MQTT topic prefix to publish all values of this meter i.E. if statTopic = "Powermeter", following topics will be published to: - Powermeter/reading = current counter reading (total) - Powermeter/today = increase of counter reading today - Powermeter/yesterday = increase of counter reading yesterday - Powermeter/[momType] = current value of momentary value ("power" for example 1, "usage" for example 2) - Powermeter/json = JSON formatted output containing all values - **influxInstance**_energy: a valid InfluxDB instance from s0meters_influxdb.yml where energy/counter readings are written to - **influxInstance**_mom: a valid InfluxDB instance from s0meters_influxdb.yml where momentary values are written to - **influxMeasurement_energy**: InfluxDB measurement name for energy (total counter value) - **influxMeasurement_mom**: InfluxDB measurement name for momentary values - **influxFieldName_energy**: InfluxDB field name for energy (total counter value) - **influxFieldName_mom**: InfluxDB measurement name for momentary value - **influxMinWriteInterval**_energy: minimal InfluxDB write interval for energy readings If no interval is configured, EVERY impulse received will be written to InfluxDB! There is no such option for the momentary value, as there a high resolution is desired in the logs. #### InfluxDB configuration config file: s0meters_influx.yml (unless differently defined in s0meters.ini) - It is possible to define as many InfluxDB instances as needed - Every instance has its identifier which must be unique - It is only necessary to define different instances if the database name or server is different. If only the measurement name differs, that can be set in the meters configuration for each meter - defaultMeasurement is not really necessary as normally the used measurement name should be defined for each meter in the meters configuration YAML - All other parameters here are self describing. Comment out/remove username/password if the server does not use it. ```yaml energymeters: host: localhost port: 8086 username: s0meters password: ******** database: energymeters defaultMeasurement: energy energy_momentary: host: localhost port: 8086 username: s0meters password: ******** database: energy_momentary defaultMeasurement: energy ``` # controlarduino.py, flasharduino.sh Tools for serial monitor and to reset and flash Arduino board that supports it with ***reset [ms]***command. **controlarduino.py** is used by ***flasharduino.sh*** to reset the MCU. This alternate reset/flash method can be used for Arduino based projects with: - no direct USB connection - UART connection available, but without DTS line (used by Arduino for resetting into bootloader), regardless of the reason - when UART DTS is intentionally disconnected to prevent from sudden reset, which could lead to data loss (such as in this project with the counters eventually not yet stored to EEPROM) In this project a *reset [ms]* command is built into the Arduino software which resets the hardware by pulling /RESET to low using a dedicated (normally *INPUT*) GPIO pin. This resets the Arduino in such a way, that it will start up in bootloader mode and can be flashed using avrdude. This reset command also calls a save-all-data-to-EEPROM routine before doing the actual reset. **flasharduino.sh** sends the reset command using controlarduino.py, flashes the MCU using avrdude and then starts controlarduino.py in --monitor mode.