diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..330adb8
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,36 @@
+# docker build --build-arg SENSORS="12 10000" \
+# --build-arg MQTT_HOST=192.168.x.x --build-arg MQTT_PORT=1883 \
+# --build-arg=MQTT_USERNAME=username --build-arg=MQTT_PASSWORD=hemligt
+
+FROM alpine:edge as BUILD_ENV
+
+COPY ./sparsnas_decode.cpp /build/
+
+RUN apk add --no-cache g++ mosquitto-dev && \
+ g++ -o /build/sparsnas_decode -O2 -Wall /build/sparsnas_decode.cpp -lmosquitto
+
+FROM alpine:edge
+
+ARG SENSORS
+ARG MQTT_HOST
+ARG MQTT_PORT
+ARG MQTT_USERNAME
+ARG MQTT_PASSWORD
+ENV MQTT_HOST=${MQTT_HOST:-localhost}
+ENV MQTT_PORT=${MQTT_PORT:-1883}
+ENV MQTT_USERNAME=$MQTT_USERNAME
+ENV MQTT_PASSWORD=$MQTT_PASSWORD
+
+RUN : "${SENSORS:?Build argument 'SENSORS' needs to be set and non-empty.}"
+
+RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ --allow-untrusted \
+ rtl-sdr \
+ mosquitto-libs++ \
+ zsh
+
+COPY --from=BUILD_ENV /build/sparsnas_decode /usr/bin/
+COPY sparsnas.sh /
+
+RUN sed -i "s/^SENSORS=.*/SENSORS=(${SENSORS})/" /sparsnas.sh
+
+ENTRYPOINT ["/sparsnas.sh"]
diff --git a/README.md b/README.md
index ea72780..99daad6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,21 @@
+**This repository has been retired. Please use alternative forks.**
+
+*******************************************************************
+
+
+A BIG thanks to the https://www.facebook.com/groups/SHgruppen on Facebook and a special superduper thanks to the core coder @strigeus!
+https://github.com/strigeus/sparsnas_decoder
+
+This is a fork is adapted for output in JSON format to a MQTT broker.
+
+[](https://www.buymeacoffee.com/tubalainen)
+
+If you want to implement the same thing as below into an ESP8266 please see the following project: https://github.com/bphermansson/EspSparsnasGateway
+
+If you want to get into the bits and bytes of the system please see:
+https://github.com/kodarn/Sparsnas
+
+Asif just made a HASSIO Addon based on this code, check out his repository here: https://github.com/asifma/hassio-addons
This is a decoder for IKEA SPARSNÄS.
===================================
@@ -6,80 +24,136 @@ It uses RTL-SDR running on a Raspberry Pi to demodulate the FSK signal, and deco
The packet data is encrypted using your sender's ID. The sender ID is the last 6 digits of the serial number located under the battery.
-How to build:
+Prerequisites:
-------------
+Tested on a Raspberry Pi 3 with Raspbian Stretch with a NooElec Nano 3 USB SDR dongle. You should be able to use any RTL-SDR compatible dongle. If you live in Sweden/EU the NooElec Micro 3 dongle can be bought from www.amazon.de (free shipping on orders over 25 EUR).
+Of get your self the "real deal", an original RTL-SDR v3 from https://www.lohelectronics.se/kommunikation/amatorradio/radio/rtl-sdr-r820t2-rtl2832u-1ppm-tcxo-sma-sdr . Dont forget to make your self an antenna, the IKEA Sparsnäs transmits on 868 MHz so a 1/2 wave lenght wire connected to the center pin in the antenna port should do the trick. Make the wire approximently 16 cm long.
-First edit sparsnas_decode.cpp to setup your sender id. Then:
-```g++ -o sparsnas_decode -O3 sparsnas_decode.cpp```
-
+An IKEA Sparsnäs unit (duuh)
-How to calibrate to your sender frequency:
-------------------------------------------
-```rtl_sdr -f 868000000 -s 1024000 -g 40 - > sparsnas.raw```
+An MQTT broker installed, see this video for support/help to get it going: https://www.youtube.com/watch?v=VaWdvVVYU3A
-Wait about 20 seconds. Press Ctrl-C.
+You can also pipe your MQTT data via node-red to InfluxDB for diplaying in for example Grafana, @naestrom have done a great job creating a node-red flow, please find more info here: https://github.com/Naesstrom/sparsnas_mqtt_nodered_influxdb
-```./sparsnas_decode sparsnas.raw --find-frequencies```
-
-Wait about a minute for it to finish. It prints something like:
+The installation:
+-------------
+It is highly recommended to use the [docker](https://docker.com) version described [here](docker.md).
-```#define FREQUENCIES {65050.000000, 105050.000000}```
+The following instructions are for you that for some reason don't want to use docker, and assumes a debian or ubuntu based environment.
-Edit your sparsnas_decode.cpp with those values and rebuild it according to the How to build step above.
+```sh
+sudo apt-get update && sudo apt-get install -y git g++ libmosquitto-dev libmosquitto1 rtl-sdr zsh busybox
+cd ~
+git clone https://github.com/tubalainen/sparsnas_decoder
+cd sparsnas_decoder
+git checkout dockerVersion
-How to run:
------------
-```rtl_sdr -f 868000000 -s 1024000 -g 40 - | ./sparsnas_decode```
+# Build sparsnas_decode and copy the binary to /usr/bin
+g++ -o sparsnas_decode -std=gnu++11 -O2 sparsnas_decode.cpp -lmosquitto
+sudo cp sparsnas_decode /usr/bin/
+```
+The decoder is now cloned, compiled and copied to `/usr/bin/sparsnas_decoder`
-Technical details:
-------------------
-SPARSNÄS uses a CC1101 chip configured for GFSK modulation at 868MHz. The two FSK symbol frequencies once downconverted to baseband are roughly 67kHz and 105kHz. (EDIT: It seems like with a different RTL-SDR the baseband frequencies are 12.5khz and 50khz. Maybe my first RTL-SDR was inaccurate). The packet length is 18 bytes, including the length byte, and a 16-bit CRC is appended to the end. The CC1101 sync word is 0xD201. The packet payload byte 5 to 17 are encrypted using a key derived from the sender device's ID.
+Using your favorite text editor, create a new file named /etc/modprobe.d/no-rtl.conf and put the following text in the file. You need to run that text editor as sudo (i.e. 'sudo vi' or 'sudo nano' etc) to write to the modprobe.d directory:
-The encryption key is a repeating XOR key:
-```
- const uint32_t sensor_id_sub = SENSOR_ID - 0x5D38E8CB;
- enc_key[0] = (uint8_t)(sensor_id_sub >> 24);
- enc_key[1] = (uint8_t)(sensor_id_sub);
- enc_key[2] = (uint8_t)(sensor_id_sub >> 8);
- enc_key[3] = 0x47;
- enc_key[4] = (uint8_t)(sensor_id_sub >> 16);
-````
-
-Packet format (big endian):
-```
-0: uint8_t length; // Always 0x11
-1: uint8_t sender_id_lo; // Lowest byte of sender ID
-2: uint8_t unknown; // Not sure
-3: uint8_t major_version; // Always 0x07 - the major version number of the sender.
-4: uint8_t minor_version; // Always 0x0E - the minor version number of the sender.
-5: uint32_t sender_id; // ID of sender
-9: uint16_t time; // Time in units of 15 seconds.
-11:uint16_t effect; // Current effect usage
-13:uint32_t pulses; // Total number of pulses
-17:uint8_t battery; // Battery level, 0-100.
+```sh
+sudo nano /etc/modprobe.d/no-rtl.conf
```
-This is how to convert the 'effect' field into Watt:
+
```
-float watt = (float)((3600000 / PULSES_PER_KWH) * 1024) / (effect);
+blacklist dvb_usb_rtl28xxu
+blacklist rtl2832
+blacklist rtl2830
```
-Note: Due to how the encryption works, it's possible for two different SPARSNÄS with unique sender_id to confuse each other. When decrypting a packet from sender B with the A key, it's possible that the resulting packet gets a sender_id equal to A, which means A will display incorrectly decrypted data from the B device.
+Reboot with the USB dongle installed in one of the USB ports.
-Example usage
--------------
+Test your dongle
+```
+rtl_test
```
-ludde@raspberrypi ~/sparsnas $ sudo rtl_sdr -f 868000000 -s 1024000 -g 40 - | ./sparsnas_decode
+Outcome should look something like this:
+```
+pi@raspberrypi:~ $ rtl_test
Found 1 device(s):
0: Realtek, RTL2838UHIDIR, SN: 00000001
Using device 0: Generic RTL2832U OEM
Found Rafael Micro R820T tuner
+Supported gain values (29): 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6
[R82XX] PLL not locked!
-Sampling at 1024000 S/s.
-Tuned to 868000000 Hz.
-Tuner gain set to 40.20 dB.
+Sampling at 2048000 S/s.
+
+Info: This tool will continuously read from the device, and report if
+samples get lost. If you observe no further output, everything is fine.
+
Reading samples in async mode...
-[2016-12-05 21:29:35] 12711: 362.3 W. 43.339 kWh. Batt 99%.
-[2016-12-05 21:29:50] 12712: 362.6 W. 43.341 kWh. Batt 99%.
+lost at least 24 bytes
```
+
+How to configure the decoder:
+-------------
+Take a photo/make a note of your transmitter ID number underneeth the battery lid of the transmitter (not the display. Obmit the 400 and take the remaining six numbers and make a note of them.
+
+Edit sparsnas.sh to setup your sender id. You are allowed to have unlimitied number of senders and the format is `transmitter_id pulses/kWh` followed by a space.
+
+This command will do it for you :smile: (adding the transmitters 722270 and 602064 with 1000/10000 pulses/kWh respectively).
+
+```sh
+SENSORS="722270 1000 602064 10000"; sed -i "s/^SENSORS=.*/SENSORS=(${SENSORS})/" sparsnas.sh
+```
+
+Test the script:
+```sh
+./sparsnas.sh
+```
+
+To make the decoder report values over MQTT the MQTT variables needs to be configured as follows:
+
+```sh
+MQTT_HOST=192.168.x.x MQTT_PORT=1883 MQTT_USERNAME=username MQTT_PASSWORD=password ./sparsnas.sh
+```
+
+
+To make `sparsnas_decoder` run on startup (modify the `MQTT_*` variables according to your configuration and make sure you get the right path (`/home/pi/`) to `sparsnas.sh`)
+
+```sudo nano /etc/rc.local```
+
+```sh
+#!/bin/sh -e
+#
+# rc.local
+#
+# This script is executed at the end of each multiuser runlevel.
+# Make sure that the script will "exit 0" on success or any other
+# value on error.
+#
+# In order to enable or disable this script just change the execution
+# bits.
+#
+# By default this script does nothing.
+
+# Print the IP address
+_IP=$(hostname -I) || true
+if [ "$_IP" ]; then
+ printf "My IP address is %s\n" "$_IP"
+fi
+
+MQTT_HOST=192.168.x.x MQTT_PORT=1883 MQTT_USERNAME=username MQTT_PASSWORD=password /home/pi/sparsnas_decoder/sparsnas.sh
+
+exit 0
+```
+
+Check status and traffic on your MQTT broker:
+```sh
+mosquitto_sub -v -h 192.168.x.x -u username -P password -t '#'
+```
+Example of outcome:
+```json
+core-ssh:~# mosquitto_sub -v -h 192.168.x.x -u username -P password -t '#'
+sparsnas/602064 {"Sequence": 47318,"Watt": 2424.00,"kWh": 4207.933,"battery": 100,"FreqErr": 0.70,"CRC":"ok","Sensor":602064}
+sparsnas/722270 {"Sequence": 77,"Watt": 3245.07,"kWh": 0.483,"battery": 100,"FreqErr": 0.40,"CRC":"ok","Sensor":722270}
+```
+
+To configure Home Assistant have a look in the [home-assistant](home_assistant) folder.
diff --git a/docker.md b/docker.md
new file mode 100644
index 0000000..a7115af
--- /dev/null
+++ b/docker.md
@@ -0,0 +1,22 @@
+# Docker support instructions
+
+
+1. Build docker container as follows. The `SENSORS` is required and is the unique
+ id of your sensors in the form `sensor_id pulses_per_kwh`. The various `MQTT_`args are
+ optional (but highly recommended to enable communication to your mqtt_broker).
+
+```
+docker build -t sparsnas --build-arg SENSORS="1234 1000" \
+ --build-arg MQTT_HOST=192.168.x.x --build-arg MQTT_PORT=1883 \
+ --build-arg MQTT_USERNAME=username --build-arg MQTT_PASSWORD=password \
+ https://github.com/tubalainen/sparsnas_decoder.git
+```
+
+2. Run the container (it is possible to set the arguments above at runtime as
+ `-e MQTT_HOST=192.168.1.2` if you like).
+
+```
+docker run -it --device=/dev/bus/usb --name=sparsnas --restart=always sparsnas:latest
+```
+
+3. Sit back and enjoy :smile:.
diff --git a/grafana/readme.md b/grafana/readme.md
new file mode 100644
index 0000000..463b266
--- /dev/null
+++ b/grafana/readme.md
@@ -0,0 +1,5 @@
+# Example panel for grafana
+
+The json-file will create a dashboard in grafana that expects data from indluxDB pushed by Home Assistant like the one below.
+
+
diff --git a/grafana/sparsnas.png b/grafana/sparsnas.png
new file mode 100644
index 0000000..71f014c
Binary files /dev/null and b/grafana/sparsnas.png differ
diff --git "a/grafana/sparsn\303\244s.json" "b/grafana/sparsn\303\244s.json"
new file mode 100644
index 0000000..4972c8f
--- /dev/null
+++ "b/grafana/sparsn\303\244s.json"
@@ -0,0 +1,371 @@
+{
+ "__inputs": [
+ {
+ "name": "DS_HOMEASSISTANT",
+ "label": "HomeAssistant",
+ "description": "",
+ "type": "datasource",
+ "pluginId": "influxdb",
+ "pluginName": "InfluxDB"
+ }
+ ],
+ "__requires": [
+ {
+ "type": "grafana",
+ "id": "grafana",
+ "name": "Grafana",
+ "version": "4.1.1"
+ },
+ {
+ "type": "panel",
+ "id": "graph",
+ "name": "Graph",
+ "version": ""
+ },
+ {
+ "type": "datasource",
+ "id": "influxdb",
+ "name": "InfluxDB",
+ "version": "1.0.0"
+ }
+ ],
+ "annotations": {
+ "list": []
+ },
+ "editable": true,
+ "gnetId": null,
+ "graphTooltip": 0,
+ "hideControls": false,
+ "id": null,
+ "links": [],
+ "rows": [
+ {
+ "collapse": false,
+ "height": 250,
+ "panels": [
+ {
+ "aliasColors": {
+ "Power usage": "#E24D42",
+ "Power usage (avg)": "#2F575E"
+ },
+ "bars": false,
+ "datasource": "${DS_HOMEASSISTANT}",
+ "fill": 0,
+ "height": "500px",
+ "hideTimeOverride": false,
+ "id": 1,
+ "interval": "",
+ "legend": {
+ "alignAsTable": false,
+ "avg": true,
+ "current": true,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [
+ {
+ "alias": "/kWh/",
+ "bars": true,
+ "lines": false,
+ "yaxis": 2
+ },
+ {
+ "alias": "Power usage (avg)",
+ "linewidth": 3
+ }
+ ],
+ "span": 12,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "alias": "Daily kWh",
+ "dsType": "influxdb",
+ "groupBy": [
+ {
+ "params": [
+ "1d"
+ ],
+ "type": "time"
+ },
+ {
+ "params": [
+ "0"
+ ],
+ "type": "fill"
+ }
+ ],
+ "measurement": "kWh",
+ "policy": "default",
+ "query": "SELECT non_negative_derivative(top(\"value\", 10), 1d) FROM \"kWh\" WHERE \"entity_id\" = 'sparsnas_energy_consumption_over_time' AND $timeFilter GROUP BY time(1d) fill(0)",
+ "rawQuery": true,
+ "refId": "A",
+ "resultFormat": "time_series",
+ "select": [
+ [
+ {
+ "params": [
+ "value"
+ ],
+ "type": "field"
+ },
+ {
+ "params": [
+ "10"
+ ],
+ "type": "top"
+ },
+ {
+ "params": [
+ "1d"
+ ],
+ "type": "non_negative_derivative"
+ }
+ ]
+ ],
+ "tags": [
+ {
+ "key": "entity_id",
+ "operator": "=",
+ "value": "sparsnas_energy_consumption_over_time"
+ }
+ ]
+ },
+ {
+ "alias": "Power usage",
+ "dsType": "influxdb",
+ "groupBy": [
+ {
+ "params": [
+ "$interval"
+ ],
+ "type": "time"
+ },
+ {
+ "params": [
+ "linear"
+ ],
+ "type": "fill"
+ }
+ ],
+ "hide": false,
+ "measurement": "W",
+ "policy": "default",
+ "refId": "B",
+ "resultFormat": "time_series",
+ "select": [
+ [
+ {
+ "params": [
+ "value"
+ ],
+ "type": "field"
+ },
+ {
+ "params": [],
+ "type": "mean"
+ },
+ {
+ "params": [
+ "alias"
+ ],
+ "type": "alias"
+ }
+ ]
+ ],
+ "tags": [
+ {
+ "key": "entity_id",
+ "operator": "=",
+ "value": "sparsnas_energy_consumption_momentary"
+ }
+ ]
+ },
+ {
+ "alias": "Power usage (avg)",
+ "dsType": "influxdb",
+ "groupBy": [
+ {
+ "params": [
+ "30m"
+ ],
+ "type": "time"
+ },
+ {
+ "params": [
+ "linear"
+ ],
+ "type": "fill"
+ }
+ ],
+ "hide": false,
+ "measurement": "W",
+ "policy": "default",
+ "refId": "C",
+ "resultFormat": "time_series",
+ "select": [
+ [
+ {
+ "params": [
+ "value"
+ ],
+ "type": "field"
+ },
+ {
+ "params": [],
+ "type": "mean"
+ }
+ ]
+ ],
+ "tags": [
+ {
+ "key": "entity_id",
+ "operator": "=",
+ "value": "sparsnas_energy_consumption_momentary"
+ }
+ ]
+ },
+ {
+ "alias": "Power usage (avg)",
+ "dsType": "influxdb",
+ "groupBy": [
+ {
+ "params": [
+ "30m"
+ ],
+ "type": "time"
+ },
+ {
+ "params": [
+ "linear"
+ ],
+ "type": "fill"
+ }
+ ],
+ "hide": false,
+ "measurement": "W",
+ "policy": "default",
+ "query": "SELECT mean(\"value\") FROM \"W\" WHERE \"entity_id\" = 'sparsnas_energy_consumption_momentary' AND $timeFilter GROUP BY time(30m) fill(linear)",
+ "rawQuery": true,
+ "refId": "D",
+ "resultFormat": "time_series",
+ "select": [
+ [
+ {
+ "params": [
+ "value"
+ ],
+ "type": "field"
+ },
+ {
+ "params": [],
+ "type": "mean"
+ }
+ ]
+ ],
+ "tags": [
+ {
+ "key": "entity_id",
+ "operator": "=",
+ "value": "sparsnas_energy_consumption_momentary"
+ }
+ ]
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": "1w",
+ "timeShift": null,
+ "title": "Power usage",
+ "tooltip": {
+ "shared": true,
+ "sort": 1,
+ "value_type": "individual"
+ },
+ "transparent": true,
+ "type": "graph",
+ "xaxis": {
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": [
+ "current"
+ ]
+ },
+ "yaxes": [
+ {
+ "format": "watt",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "watth",
+ "label": "",
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ]
+ }
+ ],
+ "repeat": null,
+ "repeatIteration": null,
+ "repeatRowId": null,
+ "showTitle": false,
+ "title": "Dashboard Row",
+ "titleSize": "h6"
+ }
+ ],
+ "schemaVersion": 14,
+ "style": "dark",
+ "tags": [],
+ "templating": {
+ "list": []
+ },
+ "time": {
+ "from": "now-6h",
+ "to": "now"
+ },
+ "timepicker": {
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "browser",
+ "title": "Sparsnäs",
+ "version": 2
+}
\ No newline at end of file
diff --git a/home-assistant/panel-focus.png b/home-assistant/panel-focus.png
new file mode 100644
index 0000000..570e391
Binary files /dev/null and b/home-assistant/panel-focus.png differ
diff --git a/home-assistant/panel.png b/home-assistant/panel.png
new file mode 100644
index 0000000..5a6a34c
Binary files /dev/null and b/home-assistant/panel.png differ
diff --git a/home-assistant/readme.md b/home-assistant/readme.md
new file mode 100644
index 0000000..fb3f6f0
--- /dev/null
+++ b/home-assistant/readme.md
@@ -0,0 +1,11 @@
+# Example package for [Home Assistant](https://home-assistant.io/)
+
+The file `sparsnas_mqtt.yaml` is a package for Home Assistant's [package configuration feature](https://home-assistant.io/docs/configuration/packages/). Requires [Custom UI](https://github.com/andrey-git/home-assistant-custom-ui) for optimal layout.
+
+Once loaded the package will create the following panel in Home Assistant.
+
+
+
+
+## Configuration
+Either replace all occurances of `!secret sparsnas_sensor` with the mqtt topic the decoder is sending on (typically `sparsnas/SENSOR_ID`), or (preferrable) configure Home Assistant to use [secrets](https://home-assistant.io/docs/configuration/secrets/) and set the secret `sparsnas_sensor` to the right mqtt topic (typically `sparsnas/SENSOR_ID`).
diff --git a/home-assistant/sparsnas_mqtt.yaml b/home-assistant/sparsnas_mqtt.yaml
new file mode 100644
index 0000000..7a48783
--- /dev/null
+++ b/home-assistant/sparsnas_mqtt.yaml
@@ -0,0 +1,170 @@
+homeassistant:
+ customize:
+ sensor.sparsnas_battery_remaining:
+ hidden: true
+ icon: mdi:battery-outline
+ sensor.sparsnas_frequency_error:
+ hidden: true
+ icon: mdi:signal
+ sensor.kwh_today:
+ group:
+ group.sparsnas:
+ friendly_name: Today
+ icon: mdi:flash-circle
+ sensor.kwh_current_month:
+ group:
+ group.sparsnas:
+ friendly_name: This Month
+ icon: mdi:flash-circle
+ sensor.sparsnas_template_kwh_sensor_day:
+ hidden: true
+ sensor.sparsnas_template_kwh_sensor_month:
+ hidden: true
+ sensor.sparsnas_energy_consumption_over_time:
+ hidden: true
+ icon: mdi:flash-circle
+ sensor.sparsnas_energy_consumption_momentary:
+ extra_data_template: '${"Battery: " + entities[''sensor.sparsnas_battery_remaining''].state
+ + "%, Signal: " + entities[''sensor.sparsnas_frequency_error''].state}'
+ group:
+ group.sparsnas:
+ friendly_name: Power usage
+ icon: mdi:flash-circle
+
+sensor:
+ - platform: mqtt
+ state_topic: !secret sparsnas_sensor
+ name: "Sparsnäs energy consumption momentary"
+ unit_of_measurement: "W"
+ value_template: '{{ value_json.Watt | round(1) }}'
+ icon: mdi:flash-circle
+# Note that the sensor below will be resetted if you remove/reinstall the batteries in the Sparsnäs transmitter
+ - platform: mqtt
+ state_topic: !secret sparsnas_sensor
+ name: "Sparsnäs energy consumption over time"
+ unit_of_measurement: "kWh"
+ value_template: '{{ value_json.kWh | round(1) }}'
+ icon: mdi:flash-circle
+ device_class: energy
+ state_class: total_increasing
+ - platform: mqtt
+ state_topic: !secret sparsnas_sensor
+ name: "Sparsnäs Battery remaining"
+ unit_of_measurement: "%"
+ value_template: '{{ value_json.battery | round(1) }}'
+ icon: mdi:battery-outline
+ - platform: mqtt
+ state_topic: !secret sparsnas_sensor
+ name: "Sparsnäs Frequency Error"
+ unit_of_measurement: "%"
+ value_template: '{{ value_json.FreqErr }}'
+ icon: mdi:signal
+ - platform: mqtt
+ name: "Sparsnäs template kwh sensor day"
+ state_topic: "template/kwh/day"
+ icon: mdi:flash-circle
+ - platform: mqtt
+ name: "Sparsnäs template kwh sensor month"
+ state_topic: "template/kwh/month"
+ icon: mdi:flash-circle
+ - platform: template
+ sensors:
+ kwh_current_month:
+ friendly_name: "Sparsnäs current month"
+ unit_of_measurement: "kWh"
+ value_template: >-
+ {{ (float(states.sensor.sparsnas_energy_consumption_over_time.state) - float(states.sensor.sparsnas_template_kwh_sensor_month.state)) | round(1) }}
+ kwh_today:
+ friendly_name: "Sparsnäs current day"
+ unit_of_measurement: "kWh"
+ value_template: >-
+ {{ (float(states.sensor.sparsnas_energy_consumption_over_time.state) - float(states.sensor.sparsnas_template_kwh_sensor_day.state)) | round(1) }}
+
+# Thanks to @bhaap and @naestrom for the monthly and daily automation below
+automation:
+- action:
+ - data:
+ payload_template: '{{ states(''sensor.sparsnas_energy_consumption_over_time'') }}'
+ retain: 'true'
+ topic: template/kwh/month
+ service: mqtt.publish
+ alias: Sparsnäs monthly consumption
+ trigger:
+ platform: time
+ at: '00:00:01'
+ condition:
+ condition: and
+ conditions:
+ - condition: template
+ value_template: '{{ now().day | string == "1" }}'
+ - condition: or
+ conditions:
+ - condition: template
+ value_template: '{{ now().month | string == "1" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "2" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "3" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "4" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "5" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "6" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "7" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "8" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "9" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "10" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "11" }}'
+ - condition: template
+ value_template: '{{ now().month | string == "12" }}'
+ id: '1516722867362'
+
+- action:
+ - data:
+ payload_template: "{{ states('sensor.sparsnas_energy_consumption_over_time')}}"
+ retain: 'true'
+ topic: template/kwh/day
+ service: mqtt.publish
+ alias: Sparsnäs daily consumption
+ condition: []
+ id: '1516806539856'
+ trigger:
+ - at: 00:00:02
+ platform: time
+
+- action:
+ - data:
+ entity_id: automation.sparsnas_daily_consumption
+ service: automation.trigger
+ - data:
+ entity_id: automation.sparsnas_monthly_consumption
+ service: automation.trigger
+ alias: trigger-sparsnas-if-unknown
+ condition:
+ - condition: state
+ entity_id: sensor.sparsnas_template_kwh_sensor_month
+ state: Unknown
+ id: '1519996400010'
+ trigger:
+ - entity_id: sensor.sparsnas_energy_consumption_over_time
+ from: unknown
+ platform: state
+
+
+group:
+ sparsnas:
+ name: Energy Consumption
+ icon: mdi:flash-circle
+ entities:
+ - sensor.sparsnas_energy_consumption_momentary
+ - sensor.sparsnas_energy_consumption_over_time
+ - sensor.sparsnas_battery_remaining
+ - sensor.sparsnas_frequency_error
+ - sensor.kwh_today
+ - sensor.kwh_current_month
diff --git a/oldDoc/README.md b/oldDoc/README.md
new file mode 100644
index 0000000..3f8aa74
--- /dev/null
+++ b/oldDoc/README.md
@@ -0,0 +1,330 @@
+A BIG thanks to the https://www.facebook.com/groups/SHgruppen on Facebook and a special superduper thanks to the core coder @strigeus!
+https://github.com/strigeus/sparsnas_decoder
+
+This is a fork is adapted for output in JSON format to a MQTT broker.
+
+If you want to implement the same thing as below into an ESP8266 please see the following project: https://github.com/bphermansson/EspSparsnasGateway
+
+This is a decoder for IKEA SPARSNÄS.
+===================================
+
+It uses RTL-SDR running on a Raspberry Pi to demodulate the FSK signal, and decodes the packet.
+
+The packet data is encrypted using your sender's ID. The sender ID is the last 6 digits of the serial number located under the battery.
+
+Prerequisites:
+-------------
+Tested on a Raspberry Pi 3 with Raspbian Stretch with a NooElec Micro 3 USB SDR dongle. You should be able to use any RTL-SDR compatible dongle.
+
+An IKEA Sparsnäs unit (duuh)
+
+An MQTT broker installed, see this video for support/help to get it going: https://www.youtube.com/watch?v=VaWdvVVYU3A
+
+You can also pipe your MQTT data via node-red to InfluxDB for diplaying in for example Grafana, @naestrom have done a great job creating a node-red flow, please find more info here: https://github.com/Naesstrom/sparsnas_mqtt_nodered_influxdb
+
+The installation:
+-------------
+Make sure you have updated your Raspbian installation, then
+```
+cd ~
+sudo apt-get install libusb-1.0 rtl-sdr g++ git mosquitto-clients
+git clone https://github.com/tubalainen/sparsnas_decoder
+```
+The decoder is now cloned into /home/pi/sparsnas_decoder
+
+Using your favorite text editor, create a new file named /etc/modprobe.d/no-rtl.conf and put the following text in the file. You need to run that text editor as sudo (i.e. 'sudo vi' or 'sudo nano' etc) to write to the modprobe.d directory:
+```
+sudo nano /etc/modprobe.d/no-rtl.conf
+```
+```
+blacklist dvb_usb_rtl28xxu
+blacklist rtl2832
+blacklist rtl2830
+```
+Reboot your pi with the USB dongle installed in one of the USB ports.
+
+Test your dongle
+```
+rtl_test
+```
+Outcome should look something like this:
+```
+pi@raspberrypi:~ $ rtl_test
+Found 1 device(s):
+ 0: Realtek, RTL2838UHIDIR, SN: 00000001
+
+Using device 0: Generic RTL2832U OEM
+Found Rafael Micro R820T tuner
+Supported gain values (29): 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6
+[R82XX] PLL not locked!
+Sampling at 2048000 S/s.
+
+Info: This tool will continuously read from the device, and report if
+samples get lost. If you observe no further output, everything is fine.
+
+Reading samples in async mode...
+lost at least 24 bytes
+```
+
+How to build the decoder:
+-------------
+Take a photo/make a note of your transmitter ID number underneeth the battery lid of the transmitter (not the display)
+
+First edit sparsnas_decode.cpp to setup your sender id.
+Then:
+```g++ -o sparsnas_decode -O3 sparsnas_decode.cpp```
+
+
+How to calibrate to your sender frequency:
+------------------------------------------
+```rtl_sdr -f 868000000 -s 1024000 -g 40 - > sparsnas.raw```
+
+Wait about 20 seconds. Press Ctrl-C.
+
+```./sparsnas_decode sparsnas.raw --find-frequencies```
+
+Wait about a minute for it to finish. It prints something like:
+
+```#define FREQUENCIES {65050.000000, 105050.000000}```
+
+Edit your sparsnas_decode.cpp with those values and rebuild it according to the How to build step above.
+
+Perform a testrun:
+-----------
+```rtl_sdr -f 868000000 -s 1024000 -g 40 - | ./sparsnas_decode```
+
+Create a bash script to send IKEA Sparsnäs output to MQTT broker:
+-----------
+The script will reside in /home/pi/
+
+```
+pi@rpitest:~ $ nano sparsnas.sh
+```
+```
+#!/bin/bash
+#To make sure that the mqtt broker is started, make the script wait 5 minutes.
+sleep 5m
+#Start sending information to mosquitto_pub
+rtl_sdr -f 868000000 -s 1024000 -g 40 - | sparsnas_decode 2>&1 | /usr/bin/mosquitto_pub -h 192.168.x.x -u username -P password -i sparsnas -l -t "home/sparsnas" &
+```
+
+Make the bash script executable
+```chmod +x sparsnas.sh```
+
+Copy the binary file to /usr/sbin
+```sudo cp /home/pi/sparsnas_decoder/sparsnas_decode /usr/sbin```
+
+Test the script:
+```./sparsnas.sh```
+
+Make it run on startup
+```sudo nano /etc/rc.local```
+```
+#!/bin/sh -e
+#
+# rc.local
+#
+# This script is executed at the end of each multiuser runlevel.
+# Make sure that the script will "exit 0" on success or any other
+# value on error.
+#
+# In order to enable or disable this script just change the execution
+# bits.
+#
+# By default this script does nothing.
+
+# Print the IP address
+_IP=$(hostname -I) || true
+if [ "$_IP" ]; then
+ printf "My IP address is %s\n" "$_IP"
+fi
+
+/home/pi/sparsnas.sh
+
+exit 0
+```
+
+Check status and traffic on your MQTT broker:
+```
+mosquitto_sub -v -h 192.168.x.x -u username -P password -t '#'
+```
+Example of outcome:
+```
+core-ssh:~# mosquitto_sub -v -h 192.168.x.x -u username -P password -t '#'
+home/sparsnas {"Sequence":35851,"Watt": 7372.8,"kWh":18889.219,"battery":100,"FreqErr":0.57}
+```
+
+Create a sensor and monthly automation for Home Assistant (example code, configuration.yaml):
+----------------------------------
+```
+mqtt:
+ broker: 127.0.0.1
+ port: 1883
+ client_id: home-assistant-1
+ username: username
+ password: password
+ discovery: true
+ discovery_prefix: homeassistant
+
+sensor:
+ - platform: mqtt
+ state_topic: "home/sparsnas"
+ name: "Sparsnäs energy consumption momentary"
+ unit_of_measurement: "W"
+ value_template: '{{ value_json.Watt | round(1) }}'
+# Note that the sensor below will be resetted if you remove/reinstall the batteries in the Sparsnäs transmitter
+ - platform: mqtt
+ state_topic: "home/sparsnas"
+ name: "Sparsnäs energy consumption over time"
+ unit_of_measurement: "kWh"
+ value_template: '{{ value_json.kWh | round(1) }}'
+ - platform: mqtt
+ state_topic: "home/sparsnas"
+ name: "Sparsnäs Battery remaining"
+ unit_of_measurement: "%"
+ value_template: '{{ value_json.battery | round(1) }}'
+ - platform: mqtt
+ state_topic: "home/sparsnas"
+ name: "Sparsnäs Frequency Error"
+ unit_of_measurement: "%"
+ value_template: '{{ value_json.FreqErr }}'
+ - platform: mqtt
+ name: "Sparsnäs template kwh sensor day"
+ state_topic: "template/kwh/day"
+ - platform: mqtt
+ name: "Sparsnäs template kwh sensor month"
+ state_topic: "template/kwh/month"
+ - platform: template
+ sensors:
+ kwh_current_month:
+ friendly_name: "Sparsnäs current month"
+ unit_of_measurement: "kWh"
+ value_template: >-
+ {{ (float(states.sensor.sparsnas_energy_consumption_over_time.state) - float(states.sensor.sparsnas_template_kwh_sensor_month.state)) | round(1) }}
+ kwh_today:
+ friendly_name: "Sparsnäs current day"
+ unit_of_measurement: "kWh"
+ value_template: >-
+ {{ (float(states.sensor.sparsnas_energy_consumption_over_time.state) - float(states.sensor.sparsnas_template_kwh_sensor_day.state)) | round(1) }}
+
+# Thanks to @bhaap and @naestrom for the monthly and daily automation below
+automation:
+- action:
+ - data:
+ payload_template: '{{ states(''sensor.sparsnas_energy_consumption_over_time'')
+ }}'
+ retain: 'true'
+ topic: template/kwh/month
+ service: mqtt.publish
+ alias: Sparsnäs monthly consumption
+ condition:
+ - condition: template
+ value_template: '{{ now().month() | string == "1" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "2" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "3" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "4" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "5" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "6" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "7" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "8" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "9" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "10" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "11" }}'
+ - condition: template
+ value_template: '{{ now().month() | string == "12" }}'
+ id: '1516722867362'
+ trigger:
+ - at: 00:00:01
+ platform: time
+- action:
+ - data:
+ payload_template: "{{ states('sensor.sparsnas_energy_consumption_over_time')}}"
+ retain: 'true'
+ topic: template/kwh/day
+ service: mqtt.publish
+ alias: Sparsnäs daily consumption
+ condition: []
+ id: '1516806539856'
+ trigger:
+ - at: 00:00:02
+ platform: time
+```
+
+Use two or more Sparsnäs at the same location (Experts only):
+------------------
+It is possible to use twoor more Sparsnäs at the same time.
+You will need to compile one sparsnas_decode per Sparsnäs Energy meter. The same sdr_rtl instance can be used.
+
+Edit your sparsnas.sh to look something like the following after you have compiled and copied all your instances to /usr/sbin/.
+```
+#!/bin/bash
+
+rtl_sdr -f 868000000 -s 1024000 -g 40 - | pee 'sparsnas_decode 2>&1' 'sparsnas_decode_bilen 2>&1' | /usr/bin/mosquitto_pub -h 192.168.xx.xx -u username -P password -i sparsnas -l -t "home/sparsnas" &
+```
+
+If the "#define frequencies" are too close you will run into problems.
+
+If you do not want to spam your MQTT with "BAD" messages you would need to remove or comment out the "BAD" statements in the .cpp file above the JSON printout "elseif"
+
+
+
+Technical details on the decoder:
+------------------
+SPARSNÄS uses a CC1101 chip configured for GFSK modulation at 868MHz. The two FSK symbol frequencies once downconverted to baseband are roughly 67kHz and 105kHz. (EDIT: It seems like with a different RTL-SDR the baseband frequencies are 12.5khz and 50khz. Maybe my first RTL-SDR was inaccurate). The packet length is 18 bytes, including the length byte, and a 16-bit CRC is appended to the end. The CC1101 sync word is 0xD201. The packet payload byte 5 to 17 are encrypted using a key derived from the sender device's ID.
+
+The encryption key is a repeating XOR key:
+```
+ const uint32_t sensor_id_sub = SENSOR_ID - 0x5D38E8CB;
+ enc_key[0] = (uint8_t)(sensor_id_sub >> 24);
+ enc_key[1] = (uint8_t)(sensor_id_sub);
+ enc_key[2] = (uint8_t)(sensor_id_sub >> 8);
+ enc_key[3] = 0x47;
+ enc_key[4] = (uint8_t)(sensor_id_sub >> 16);
+````
+
+Packet format (big endian):
+```
+0: uint8_t length; // Always 0x11
+1: uint8_t sender_id_lo; // Lowest byte of sender ID
+2: uint8_t unknown; // Not sure
+3: uint8_t major_version; // Always 0x07 - the major version number of the sender.
+4: uint8_t minor_version; // Always 0x0E - the minor version number of the sender.
+5: uint32_t sender_id; // ID of sender
+9: uint16_t time; // Time in units of 15 seconds.
+11:uint16_t effect; // Current effect usage
+13:uint32_t pulses; // Total number of pulses
+17:uint8_t battery; // Battery level, 0-100.
+```
+This is how to convert the 'effect' field into Watt:
+```
+float watt = (float)((3600000 / PULSES_PER_KWH) * 1024) / (effect);
+```
+
+Note: Due to how the encryption works, it's possible for two different SPARSNÄS with unique sender_id to confuse each other. When decrypting a packet from sender B with the A key, it's possible that the resulting packet gets a sender_id equal to A, which means A will display incorrectly decrypted data from the B device.
+
+Example usage -- Also for troubleshooting
+-------------
+```
+pi@raspberrypi:~ $ rtl_sdr -f 868000000 -s 1024000 -g 40 - | sparsnas_decode
+Found 1 device(s):
+ 0: Realtek, RTL2838UHIDIR, SN: 00000001
+
+Using device 0: Generic RTL2832U OEM
+Found Rafael Micro R820T tuner
+[R82XX] PLL not locked!
+Sampling at 1024000 S/s.
+Tuned to 868000000 Hz.
+Tuner gain set to 40.20 dB.
+Reading samples in async mode...
+{"Sequence":43096,"Watt": 7256.7,"kWh":21483.315,"battery":100,"FreqErr":0.83}
+```
diff --git a/help.png b/oldDoc/help.png
similarity index 100%
rename from help.png
rename to oldDoc/help.png
diff --git a/sparsnas.sh b/sparsnas.sh
new file mode 100755
index 0000000..88cc682
--- /dev/null
+++ b/sparsnas.sh
@@ -0,0 +1,77 @@
+#!/bin/zsh
+
+set -o pipefail
+
+#Associatve array
+typeset -A SENSORS
+SENSORS=(id_1 pulses_1 id_2 pulses_2)
+
+RTL_SDR=(/usr/bin/rtl_sdr -f 868000000 -s 1024000 -g 40 -)
+#RTL_SDR=(cat /sparsnas.raw)
+SPARSNAS_DECODE="/usr/bin/sparsnas_decode"
+
+FIND_FREQ="SPARSNAS_SENSOR_ID=_SENSE_ $SPARSNAS_DECODE /tmp/sparsnas.raw --find-frequencies > /tmp/_SENSE_.freq"
+DECODE="> >(SPARSNAS_SENSOR_ID=_SENSE_ SPARSNAS_PULSES_PER_KWH=_pulse_ SPARSNAS_FREQ_MIN=_min_ SPARSNAS_FREQ_MAX=_max_ /usr/bin/sparsnas_decode)"
+
+[ "${(k)SENSORS}" ] || exit 0
+
+#Build find freq-cmd..
+function findfreq () {
+ CMD=""
+ for i in ${(k)SENSORS}; do
+ CMD="$CMD ${CMD:+ &} `echo $FIND_FREQ | sed "s/_SENSE_/$i/g"`"
+ done
+
+ echo $CMD
+}
+
+#Build decode-cmd.
+function decode () {
+ CMD=""
+ for i in ${(k)SENSORS}; do
+ _c=$(echo $DECODE | \
+ sed -e "s/_SENSE_/$i/g" -e "s/_pulse_/$SENSORS[$i]/" \
+ -e "s/_min_/$(awk -F= '/MIN/ {print $2}' < /tmp/$i.freq)/" \
+ -e "s/_max_/$(awk -F= '/MAX/ {print $2}' < /tmp/$i.freq)/")
+ CMD="$CMD $_c"
+ done
+
+ COMMAND="$RTL_SDR $CMD"
+ echo $COMMAND
+}
+
+function frequencies () {
+ for sensor in ${(k)SENSORS}; do
+ grep -e SPARSNAS_FREQ_MAX -e SPARSNAS_FREQ_MIN /tmp/${sensor}.freq -q || return 1
+ done
+ return 0
+}
+
+
+if ( frequencies ); then
+ echo "Frequencies have already been detected"
+ CMD=`decode`
+ # echo $CMD
+ eval $CMD
+else
+ echo "Need to find frequencies"
+ busybox timeout 45 $RTL_SDR > /tmp/sparsnas.raw
+ #ls -l /tmp/sparsnas.raw
+ F=`findfreq`
+ # echo $F
+ eval $F
+ sleep 1
+ rm /tmp/sparsnas.raw
+ if ( frequencies ); then
+ CMD=`decode`
+ # echo $CMD
+ eval $CMD
+ else
+ if [ ${#SENSORS[@]} -gt 1 ]; then
+ echo "Could not find the sensors please verify that the sensor ids are: ${(k)SENSORS}"
+ else
+ echo "Could not find the sensor please verify that the sensor id is: ${(k)SENSORS}"
+ fi
+ exit -1
+ fi
+fi
diff --git a/sparsnas_decode.cpp b/sparsnas_decode.cpp
index c222831..2bd1b85 100644
--- a/sparsnas_decode.cpp
+++ b/sparsnas_decode.cpp
@@ -1,34 +1,41 @@
-#include
-#include
-#define _USE_MATH_DEFINES 1
-#include
+#include
+#include
+#include
#include
-#include
-#include
-#include
+#include
+#include
+
+// This is the number of pulse per kWh consumed of your elecricity meter.
+// In Sweden at least, the standard seems to be 1 pulse per Wh, i.e. 1000 pulses per kWh
+int PULSES_PER_KWH=1000;
-//////////////////////////////////////
-#define PULSES_PER_KWH 1000
// These are the last 6 digits from the serial number of the sender.
// The serial number is located under the battery.
// The full serial number looks like "400 666 111"
-#define SENSOR_ID 666111
+int SENSOR_ID;
// It seems like different RTL-SDR tune to slightly different frequencies
// Or I'm not really sure what's up, but the 0 and 1 frequencies differ
-// between different RTL-SDR and/or sparsnäs. You can have a look at the
+// between different RTL-SDR and/or sparsnäs. You can have a look at the
// signal in a wave file editor and you can measure the wavelengths of the
// sine waves and put in appropriate values here.
//#define FREQUENCIES {12500.0,50000.0}
-#define FREQUENCIES {67500.0,105000.0}
-//#define FREQUENCIES {20000.0,60000.0}
+float frequencies[2]; //={67500.0,105000.0};
-//////////////////////////////////////
+// MQTT connection
+struct mosquitto *mosq = NULL;
-FILE *outfile;
-int testing;
+// MQTT connection parameters
+char MQTT_HOSTNAME[64];
+uint32_t MQTT_PORT = 1883;
+char MQTT_USERNAME[64];
+char MQTT_PASSWORD[64];
+char MQTT_TOPIC[64];
+char MQTT_CRC_TOPIC[64];
+FILE *outfile;
+int testing=0;
// Implementation of Complex numbers, cause std::complex is stupid and doesn't inline properly.
@@ -140,6 +147,7 @@ class SignalDetector {
void add_fail(float freq) {
char mesg[1024];
+ int bad = false;
shift_ = 0;
found_sync_ = 0;
@@ -148,18 +156,6 @@ class SignalDetector {
uint16_t packet_crc = data_[18] << 8 | data_[19];
char *m = mesg;
- time_t mytime;
- mytime = time(NULL);
- struct tm * timeinfo;
- timeinfo = localtime(&mytime);
-
- m += sprintf(m, "[%.4d-%.2d-%.2d %.2d:%.2d:%.2d] ", timeinfo->tm_year + 1900,
- timeinfo->tm_mon + 1,
- timeinfo->tm_mday,
- timeinfo->tm_hour,
- timeinfo->tm_min,
- timeinfo->tm_sec);
-
uint8_t dec[32];
uint8_t enc_key[5];
@@ -174,30 +170,48 @@ class SignalDetector {
for(size_t i = 0; i < 13; i++)
dec[5 + i] = data_[5 + i] ^ enc_key[i % 5];
- uint32_t rcv_sensor_id = dec[5] << 24 | dec[6] << 16 | dec[7] << 8 | dec[8];
+ int rcv_sensor_id = dec[5] << 24 | dec[6] << 16 | dec[7] << 8 | dec[8];
- if (data_[0] != 0x11 || data_[1] != (SENSOR_ID & 0xFF) || data_[3] != 0x07 || data_[4] != 0x0E || rcv_sensor_id != SENSOR_ID) {
- m += sprintf(m, "Bad: ");
+ if (data_[0] != 0x11 || data_[1] != (SENSOR_ID & 0xFF) || data_[3] != 0x07 || rcv_sensor_id != SENSOR_ID) {
+ bad = true;
+ m += sprintf(m, "{\"Bad\":\"");
for (int i = 0; i < 18; i++)
m += sprintf(m, "%.2X ", data_[i]);
- } else {
+ m += sprintf(m, "\"");
+ } else if (crc == packet_crc) {
+ bad = false;
int seq = (dec[9] << 8 | dec[10]);
- int effect = (dec[11] << 8 | dec[12]);
+ unsigned int effect = (dec[11] << 8 | dec[12]);
int pulse = (dec[13] << 24 | dec[14] << 16 | dec[15] << 8 | dec[16]);
int battery = dec[17];
- float watt = (float)((3600000 / PULSES_PER_KWH) * 1024) / (effect);
- m += sprintf(m, "%5d: %7.1f W. %d.%.3d kWh. Batt %d%%. FreqErr: %.2f", seq, watt, pulse/1000, pulse%1000, battery, freq);
-
+ float watt = effect * 24;
+ int data4 = data_[4]^0x0f;
+// Note that data_[4] cycles between 0-3 when you first put in the batterys in t$
+ if(data4 == 1){
+ watt = (double)((3600000 / PULSES_PER_KWH) * 1024) / (effect);
+ } else if (data4 == 0 ) { // special mode for low power usage
+ watt = effect * 0.24 / PULSES_PER_KWH;
+ }
+ m += sprintf(m, "{\"Sequence\":%6d, \"Watt\":%8.2f, \"kWh\":%9.3f, \"battery\":%4d, \"FreqErr\":%5.2f, \"Effect\":%5d, \"Data4\":%2d",
+ seq, watt, pulse/(float)PULSES_PER_KWH, battery, freq, effect, data4);
if (testing && crc == packet_crc) {
error_sum += fabs(freq);
error_sum_count += 1;
}
+ } else {
+ m += sprintf(m, "{\"CRC\": \"ERR\"");
}
- m += sprintf(m, (crc == packet_crc) ? "\n" : "CRC ERR\n");
-
+ m += sprintf(m, ", \"Sensor\": %6d}\n", SENSOR_ID);
+ char* topic = (crc == packet_crc) ? MQTT_TOPIC : MQTT_CRC_TOPIC;
if (!testing) {
- fprintf(stderr, "%s", mesg);
+ if (mosq && !bad) {
+ int ret = mosquitto_publish (mosq, NULL, topic, strlen(mesg) - 1, mesg, 0, true);
+ if ( ret != MOSQ_ERR_SUCCESS) {
+ fprintf(stderr, "Can't publish to Mosquitto server. %s\n", mosquitto_strerror(ret));
+ }
+ } else
+ bad ? fprintf(stderr, "%s", mesg) : printf("%s", mesg);
if (outfile) {
fprintf(outfile, "%s", mesg);
fflush(outfile);
@@ -213,12 +227,12 @@ class SignalDetector {
uint32_t bits_;
};
-int run_for_frequencies(FILE *f, FILE *logfile, float F1, float F2) {
+void run_for_frequencies(FILE *f, FILE *logfile, float F1, float F2) {
uint8_t buf[16384];
SignalDetector sd;
- Complex hist1[27] = { 0 };
- Complex hist2[27] = { 0 };
+ Complex hist1[27] = { {0} };
+ Complex hist2[27] = { {0} };
Complex sum1 = {0, 0};
Complex sum2 = {0, 0};
@@ -229,7 +243,7 @@ int run_for_frequencies(FILE *f, FILE *logfile, float F1, float F2) {
bool last_signal = false;
int last_sigtime = 0;
-
+
const float PERFECT_PULSE_LEN = 26.6666666f * S / 1024000.0;
const int MIN_PULSE_LEN = 12 * S / 1024000.0;
const int MAX_PULSE_LEN = 42 * S / 1024000.0;
@@ -280,7 +294,7 @@ int run_for_frequencies(FILE *f, FILE *logfile, float F1, float F2) {
if (signal != last_signal) {
int pulse_len = (unsigned)j - last_sigtime;
-
+
if (pulse_len >= MIN_PULSE_LEN && (sd.has_some_sync() || pulse_len < MAX_PULSE_LEN)) {
if (signal)
avg_err = -avg_err;
@@ -308,68 +322,153 @@ int run_for_frequencies(FILE *f, FILE *logfile, float F1, float F2) {
}
-int main(int argc, char **argv)
-{
- FILE *f = stdin;
- if (argc >= 2) {
- f = fopen(argv[1], "rb");
- if (!f) {
- fprintf(stderr, "Failed load!\n");
- return 1;
- }
-// for loading wav files
-// fseek(f, 44, SEEK_SET);
- }
-
- testing = (argc >= 3 && strcmp(argv[2], "--find-frequencies") == 0);
+int run_calibration(FILE *f){
+ testing = 1;
+ float range_min = -100000, range_max = 100000, step = 5000;
+ float invalid_f1 = 1e100, best_f1;
- outfile = fopen("sparsnas.log", "a");
+ do {
+ float best_error = 1e100;
+ best_f1 = invalid_f1;
+
+ for(float f1 = range_min; f1 <= range_max; f1 += step) {
+ fseek(f, 0, SEEK_SET);
+
+ fprintf(stderr, "Trying %.0f hz...\n", f1);
+
+ error_sum = 0;
+ error_sum_count = 0;
+ run_for_frequencies(f, NULL, f1, f1 + 40000.0f);
+
+ if (error_sum_count != 0) {
+ float error = error_sum / error_sum_count;
+ if (error < best_error) {
+ fprintf(stderr, "Freq %.0f gives error %f\n", f1, error);
+ best_error = error;
+ best_f1 = f1;
+ }
+ }
+ }
- FILE *logfile = NULL;// fopen("logfile.pcm", "wb");
+ if (best_f1 == invalid_f1) {
+ fprintf(stderr, "Nothing found...\n");
+ return 1;
+ }
+ range_min = best_f1 - step * 0.5f;
+ range_max = best_f1 + step * 0.5f;
+ step /= 10.0f;
+ } while (step >= 10.0f);
- if (!testing) {
- float frequencies[] = FREQUENCIES;
- run_for_frequencies(f, logfile, frequencies[0], frequencies[1]);
- } else {
- float range_min = -100000, range_max = 100000, step = 5000;
- float invalid_f1 = 1e100, best_f1;
+ fprintf(stderr, "#define FREQUENCIES {%f, %f}\n", best_f1, best_f1 + 40000.0f);
+ printf("export SPARSNAS_FREQ_MIN=%f\nexport SPARSNAS_FREQ_MAX=%f\n" , best_f1, best_f1 + 40000.0f);
+ return 0;
+}
- do {
- float best_error = 1e100;
- best_f1 = invalid_f1;
- for(float f1 = range_min; f1 <= range_max; f1 += step) {
- fseek(f, 0, SEEK_SET);
+int get_env_int(const char * env_var_name,int * buf) {
- fprintf(stderr, "Trying %.0f hz...\n", f1);
+ if (const char *env_p = std::getenv(env_var_name))
+ if (sscanf(env_p,"%d",buf))
+ return 1;
+ return 0;
+}
- error_sum = 0;
- error_sum_count = 0;
- run_for_frequencies(f, NULL, f1, f1 + 40000.0f);
- if (error_sum_count != 0) {
- float error = error_sum / error_sum_count;
- if (error < best_error) {
- fprintf(stderr, "Freq %.0f gives error %f\n", f1, error);
- best_error = error;
- best_f1 = f1;
- }
+int main(int argc, char **argv)
+{
+ int tmp;
+ FILE *f = stdin;
+ if (argc >= 2) {
+ f = fopen(argv[1], "rb");
+ if (!f) {
+ fprintf(stderr, "Failed load!\n");
+ return 1;
}
- }
+ }
- if (best_f1 == invalid_f1) {
- fprintf(stderr, "Nothing found...\n");
- return 0;
- }
- range_min = best_f1 - step * 0.5f;
- range_max = best_f1 + step * 0.5f;
- step /= 10.0f;
- } while (step >= 10.0f);
+ //Get SENSOR_ID from environment
+ if (get_env_int("SPARSNAS_SENSOR_ID",&tmp))
+ SENSOR_ID = tmp;
+ else {
+ fprintf(stderr, "SPARSNAS_SENSOR_ID not defined or incorrect. Aborting!\n");
+ return 1;
+ }
- fprintf(stderr, "#define FREQUENCIES {%f, %f}\n", best_f1, best_f1 + 40000.0f);
- }
+ if (argc >= 3 && strcmp(argv[2], "--find-frequencies") == 0)
+ return run_calibration(f);
+
+
+ //Get the parameters from environment
+ if (get_env_int("SPARSNAS_PULSES_PER_KWH",&tmp))
+ PULSES_PER_KWH = tmp;
+
+ if (get_env_int("SPARSNAS_FREQ_MIN",&tmp))
+ frequencies[0] = tmp;
+ else {
+ fprintf(stderr, "SPARSNAS_FREQ_MIN not defined or incorrect. Aborting!\n");
+ return 1;
+ }
+
+ if (get_env_int("SPARSNAS_FREQ_MAX",&tmp))
+ frequencies[1] = tmp;
+ else {
+ fprintf(stderr, "SPARSNAS_FREQ_MAX not defined or incorrect. Aborting!\n");
+ return 1;
+ }
+
+ FILE *logfile;
+ if (const char *env_p = std::getenv("SPARSNAS_LOG"))
+ logfile=fopen(env_p,"a");
+ else
+ logfile = NULL;
+
+ memset(MQTT_HOSTNAME, '\0', sizeof(MQTT_HOSTNAME));
+ if (const char *env_p = std::getenv("MQTT_HOST"))
+ strncpy(MQTT_HOSTNAME, env_p, sizeof(MQTT_HOSTNAME)-1);
+ else
+ strncpy(MQTT_HOSTNAME, "localhost", sizeof(MQTT_HOSTNAME)-1);
+
+ if (get_env_int("MQTT_PORT",&tmp))
+ MQTT_PORT = tmp;
+
+ memset(MQTT_TOPIC, '\0', sizeof(MQTT_TOPIC));
+ if (const char *env_p = std::getenv("MQTT_TOPIC"))
+ strncpy(MQTT_TOPIC, env_p, sizeof(MQTT_TOPIC)-1);
+ else
+ sprintf(MQTT_TOPIC, "sparsnas/%d", SENSOR_ID);
+
+ sprintf(MQTT_CRC_TOPIC, "%s/crc", MQTT_TOPIC);
+
+ memset(MQTT_USERNAME, '\0', sizeof(MQTT_USERNAME));
+ if (const char *env_p = std::getenv("MQTT_USERNAME"))
+ strncpy(MQTT_USERNAME, env_p, sizeof(MQTT_USERNAME)-1);
+
+ memset(MQTT_PASSWORD, '\0', sizeof(MQTT_PASSWORD));
+ if (const char *env_p = std::getenv("MQTT_PASSWORD"))
+ strncpy(MQTT_PASSWORD, env_p, sizeof(MQTT_PASSWORD)-1);
+
+ // Initialize the Mosquitto library
+ mosquitto_lib_init();
+
+ // Create a new Mosquitto runtime instance with a random client ID,
+ mosq = mosquitto_new (NULL, true, NULL);
+ mosquitto_loop_start(mosq);
+
+ if (mosq) {
+ //Set username and password (will be ignored of MQTT_USERNAME=NULL)
+ mosquitto_username_pw_set (mosq, MQTT_USERNAME, MQTT_PASSWORD);
+ int ret = mosquitto_connect_async (mosq, MQTT_HOSTNAME, MQTT_PORT, 30);
+ if (ret != MOSQ_ERR_SUCCESS) {
+ fprintf (stderr, "Mosquitto connect issues, will write to stdout.\n");
+ mosq = NULL;
+ }
+ } else
+ fprintf (stderr, "Can't initialize Mosquitto library\n");
+
+ //Run the main program
+ run_for_frequencies(f, logfile, frequencies[0], frequencies[1]);
- return 0;
+ return 0;
}