Skip to content

Commit e41c762

Browse files
committed
Include ADCO in topic & New doc
1 parent b31b95e commit e41c762

27 files changed

+4956
-616
lines changed

.dockerignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
node_modules/
1+
**/node_modules/
2+
coverage/
3+
docs/

.travis.yml

+10-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ os: linux
33

44
env:
55
global:
6-
- NODE_VERSION=16
6+
- NODE_VERSION=14
77
- CC_TEST_REPORTER_ID=054a8c4f2007b9299933b67221504554462eed6cbb06c364c926a9869dfd4020
88
- DOCKER_CLI_EXPERIMENTAL=enabled
99
- IMAGE_VERSION=develop
@@ -17,33 +17,32 @@ before_install:
1717
- sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) edge"
1818
- sudo apt-get update
1919
- sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
20-
#- docker run --rm --privileged multiarch/qemu-user-static:register --reset
2120
- docker run --rm --privileged docker/binfmt:820fdd95a9972a5308930a2bdfb8573dd4447ad3
2221
- cat /proc/sys/fs/binfmt_misc/qemu-aarch64
2322

2423
# Install Nodejs
2524
- nvm install $NODE_VERSION
2625

2726
# Init Code Climate
28-
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
29-
- chmod +x ./cc-test-reporter
30-
- ./cc-test-reporter before-build
27+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./app/cc-test-reporter
28+
- (cd app && chmod +x ./cc-test-reporter)
29+
- (cd app && ./cc-test-reporter before-build)
3130

3231
install:
3332

34-
# Install npm dependencies
35-
- npm ci
33+
# Install dependencies
34+
- (cd app && npm ci)
3635

3736
# Run Linter
38-
- npm run lint
37+
- (cd app && npm run lint)
3938

40-
# Run Unit tests
41-
- npm test
39+
# Run UT
40+
- (cd app && npm test)
4241

4342
after_success:
4443

4544
# Report to Code Climate
46-
- ./cc-test-reporter after-build -t lcov --debug --exit-code $TRAVIS_TEST_RESULT;
45+
- (cd app && ./cc-test-reporter after-build -t lcov --debug --exit-code $TRAVIS_TEST_RESULT)
4746

4847
- docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD"
4948
- docker version

Dockerfile

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:16-alpine
1+
FROM node:14-alpine
22

33
LABEL maintainer="fmartinou"
44

@@ -15,9 +15,6 @@ ENTRYPOINT ["/usr/bin/entrypoint.sh"]
1515
# Default Command
1616
CMD ["node", "index"]
1717

18-
# Copy package.json
19-
COPY package* ./
20-
2118
# Copy app
2219
COPY app/ ./
2320

README.md

+14-96
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,23 @@
11
# teleinfo-mqtt
22

3+
![Docker pulls](https://img.shields.io/docker/pulls/fmartinou/teleinfo-mqtt)
4+
![License](https://img.shields.io/github/license/fmartinou/teleinfo-mqtt)
5+
![Travis](https://img.shields.io/travis/fmartinou/teleinfo-mqtt/master)
6+
![Maintainability](https://img.shields.io/codeclimate/maintainability/fmartinou/teleinfo-mqtt)
7+
![Coverage](https://img.shields.io/codeclimate/coverage/fmartinou/teleinfo-mqtt)
38

4-
![Travis](https://img.shields.io/travis/fmartinou/teleinfo-mqtt/master)
5-
![Maintainability](https://img.shields.io/codeclimate/maintainability/fmartinou/teleinfo-mqtt)
6-
![Docker pulls](https://img.shields.io/docker/pulls/fmartinou/teleinfo-mqtt)
9+
![](docs/teleinfo-mqtt-logo.png)
710

8-
<a href="https://www.buymeacoffee.com/61rUNMm" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
9-
===========================================
11+
**Teleinfo-Mqtt** allows you to read Teleinfo frames from a Serial port and publish them to an Mqtt broker.
1012

11-
Read teleinfo from serial port and publish with mqtt.
13+
## Ready to go?
14+
### Check out the [documentation](https://fmartinou.github.io/teleinfo-mqtt/) to get started!
1215

13-
## Usage
14-
Run the official Docker image (i386, amd64, armv6, armv7, arm64).
16+
## Contact & Support
1517

16-
### Run
17-
```
18-
docker run -d --name teleinfo-mqtt \
19-
--device=/dev/ttyUSB0:/dev/ttyUSB0 \ # add serial port device from host
20-
- e MQTT_URL=mqtt://my_mqtt_broker:1883 \ # set mqtt broker url
21-
fmartinou/teleinfo-mqtt
22-
```
18+
- Create a [GitHub issue](https://github.com/fmartinou/teleinfo-mqtt/issues) for bug reports, feature requests, or questions
19+
- Add a ⭐️ [star on GitHub](https://github.com/fmartinou/teleinfo-mqtt) to support the project!
2320

24-
### Configure
25-
Configuration uses environment variables.
21+
## License
2622

27-
| Env var | Description | Default value |
28-
|----------------------|------------------------------------------------------------------------|------------------------|
29-
|EMIT_INTERVAL | Interval in seconds between 2 MQTT emissions (0 = All frames are sent) | 10 |
30-
|HASS_DISCOVERY_PREFIX | Topic prefix for Home-Assistant Discovery | homeassistant |
31-
|HASS_IDENTIFIER | Identifier for Home-Assistant Discovery | |
32-
|LOG_LEVEL | Log level (INFO, DEBUG, ERROR) | INFO |
33-
|LOG_FORMAT | Log format (text, json) | text |
34-
|MQTT_URL | MQTT Broker connection URL | mqtt://localhost:1883 |
35-
|MQTT_USER | MQTT user (optional) | |
36-
|MQTT_PASSWORD | MQTT password (optional) | |
37-
|SERIAL | Serial Port location | /dev/ttyUSB0 |
38-
39-
### MQTT topics
40-
The frames are published to the topic `teleinfo`.
41-
42-
If you define an identifier, the frames are published to the topic `teleinfo/$identifier`.
43-
44-
### MQTT messages
45-
MQTT JSON messages may vary upon your Electricity meter.
46-
Values are sanitized and converted to Numbers if possible (you can still access the original `raw` value if necessary).
47-
48-
Example
49-
```json
50-
{
51-
"ADCO": {
52-
"raw": "12345678901",
53-
"value": 12345678901
54-
},
55-
"OPTARIF": {
56-
"raw": "HC..",
57-
"value": "HC"
58-
},
59-
"ISOUSC": {
60-
"raw": "45",
61-
"value": 45
62-
},
63-
"HCHC": {
64-
"raw": "9058688",
65-
"value": 9058688
66-
},
67-
"HCHP": {
68-
"raw": "9752846",
69-
"value": 9752846
70-
},
71-
"PTEC": {
72-
"raw": "HP..",
73-
"value": "HP"
74-
},
75-
"IINST": {
76-
"raw": "22",
77-
"value": 22
78-
},
79-
"IMAX": {
80-
"raw": "90",
81-
"value": 90
82-
},
83-
"PAPP": {
84-
"raw": "4110",
85-
"value": 4110
86-
},
87-
"PAPP": {
88-
"raw": "A",
89-
"value": "A"
90-
}
91-
}
92-
```
93-
94-
### Home Assistant MQTT discovery
95-
Home Assistant can automatically discover the teleinfo sensor. \
96-
[See here](https://www.home-assistant.io/docs/mqtt/discovery/).
97-
98-
#### MQTT Integration view
99-
![Integration](docs/images/integration.png)
100-
101-
#### MQTT Entities view
102-
![Integration](docs/images/entity.png)
103-
104-
### Changelog
105-
[See detailed changelog here](CHANGELOG.md).
23+
This project is licensed under the [MIT license](https://github.com/fmartinou/teleinfo-mqtt/blob/master/LICENSE).

.eslintrc app/.eslintrc

File renamed without changes.

app/config/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module.exports = {
22
emitInterval: process.env.EMIT_INTERVAL || 10,
33
hassDiscoveryPrefix: process.env.HASS_DISCOVERY_PREFIX || 'homeassistant',
4-
hassIdentifier: process.env.HASS_IDENTIFIER,
54
logLevel: process.env.LOG_LEVEL || 'info',
5+
mqttBaseTopic: process.env.MQTT_BASE_TOPIC || 'teleinfo',
66
mqttUrl: process.env.MQTT_URL || 'mqtt://localhost:1883',
77
mqttUser: process.env.MQTT_USER,
88
mqttPassword: process.env.MQTT_PASSWORD,

app/mqtt/index.js

+67-30
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,66 @@ const {
55
mqttUrl,
66
mqttUser,
77
mqttPassword,
8-
hassIdentifier,
8+
mqttBaseTopic,
99
hassDiscoveryPrefix,
1010
} = require('../config');
1111

12-
// Topic for teleinfo frames
13-
let frameTopic = 'teleinfo';
14-
if (hassIdentifier) {
15-
frameTopic += `/${hassIdentifier}`;
16-
}
17-
18-
// Topic for discovery (home-assistant)
19-
const discoveryTopic = `${hassDiscoveryPrefix}/sensor/teleinfo/${hassIdentifier || 'default'}/config`;
12+
const hassDeviceId = 'teleinfo-mqtt';
13+
const hassDeviceName = 'Teleinfo-mqtt';
14+
const hassManufacturer = 'Fmartinou';
15+
const hassEntityIcon = 'mdi:speedometer';
16+
const hassEntityValueTemplate = '{{ value_json.PAPP.value }}';
2017

21-
// Unique id for home-assistant
22-
let uniqueId = 'teleinfo';
23-
if (hassIdentifier) {
24-
uniqueId += `_${hassIdentifier}`;
25-
}
18+
/**
19+
* True when hass discovery configuration has been published.
20+
* @type {boolean}
21+
*/
22+
let discoveryConfigurationPublished = false;
2623

24+
/**
25+
* MQT Client.
26+
*/
2727
let client;
2828

29+
/**
30+
* Get frame topic.
31+
* @param adco
32+
* @returns {string}
33+
*/
34+
function getFrameTopic(adco) {
35+
return `${mqttBaseTopic}/${adco}`;
36+
}
37+
2938
/**
3039
* Publish Configuration for home-assistant discovery.
40+
* @param adco
3141
*/
32-
function publishConfigurationForDiscovery() {
42+
async function publishConfigurationForDiscovery(adco) {
43+
const discoveryTopic = `${hassDiscoveryPrefix}/sensor/${mqttBaseTopic}/${adco}/config`;
44+
3345
log.debug(`Publish configuration for discovery to topic [${discoveryTopic}]`);
34-
client.publish(discoveryTopic, JSON.stringify({
35-
unique_id: uniqueId,
36-
name: uniqueId,
37-
icon: 'mdi:speedometer',
38-
state_topic: frameTopic,
39-
json_attributes_topic: frameTopic,
40-
value_template: '{{ value_json.PAPP.value }}',
41-
unit_of_measurement: 'VA',
42-
}), {
43-
retain: true,
44-
});
46+
try {
47+
await client.publish(discoveryTopic, JSON.stringify({
48+
unique_id: `teleinfo_${adco}`,
49+
name: `Teleinfo ${adco}`,
50+
icon: hassEntityIcon,
51+
state_topic: getFrameTopic(adco),
52+
json_attributes_topic: getFrameTopic(adco),
53+
value_template: hassEntityValueTemplate,
54+
unit_of_measurement: 'VA',
55+
device: {
56+
identifiers: [hassDeviceId],
57+
manufacturer: hassManufacturer,
58+
model: hassDeviceId,
59+
name: hassDeviceName,
60+
},
61+
}), {
62+
retain: true,
63+
});
64+
discoveryConfigurationPublished = true;
65+
} catch (e) {
66+
log.warn(`Unable to publish discovery configuration to ${discoveryTopic} (${e.message})`);
67+
}
4568
}
4669

4770
/**
@@ -65,7 +88,6 @@ async function connect() {
6588
}
6689
try {
6790
if (client) {
68-
publishConfigurationForDiscovery();
6991
client.on('connect', () => {
7092
// Workaround to avoid reconnect issue (see https://github.com/mqttjs/MQTT.js/issues/1213)
7193
// eslint-disable-next-line no-underscore-dangle
@@ -102,9 +124,24 @@ async function disconnect() {
102124
* Publish teleinfo frame to MQTT broker.
103125
* @param {*} frame
104126
*/
105-
function publishFrame(frame) {
106-
log.debug(`Publish frame to topic [${frameTopic}]`);
107-
client.publish(frameTopic, JSON.stringify(frame));
127+
async function publishFrame(frame) {
128+
const adco = frame.ADCO ? frame.ADCO.raw : undefined;
129+
if (!adco) {
130+
log.warn('Cannot publish a frame without ADCO property');
131+
log.debug(frame);
132+
} else {
133+
if (!discoveryConfigurationPublished) {
134+
await publishConfigurationForDiscovery(adco);
135+
}
136+
const frameTopic = getFrameTopic(adco);
137+
log.debug(`Publish frame to topic [${frameTopic}]`);
138+
log.debug(frame);
139+
try {
140+
await client.publish(frameTopic, JSON.stringify(frame));
141+
} catch (e) {
142+
log.warn(`Unable to publish frame to ${frameTopic} (${e.message})`);
143+
}
144+
}
108145
}
109146

110147
module.exports = {

app/mqtt/index.test.js

+16-8
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,31 @@ test('publishFrame should be called as expected', () => {
2929
moduleToTest.__set__('client', {
3030
publish: jest.fn(() => {}),
3131
});
32-
moduleToTest.publishFrame({ a: 'b' });
33-
expect(moduleToTest.__get__('client').publish).toHaveBeenCalledWith('teleinfo', JSON.stringify({ a: 'b' }));
32+
moduleToTest.__set__('discoveryConfigurationPublished', true);
33+
const frame = { ADCO: { raw: '123456789' } };
34+
moduleToTest.publishFrame(frame);
35+
expect(moduleToTest.__get__('client').publish).toHaveBeenCalledWith('teleinfo/123456789', JSON.stringify(frame));
3436
});
3537

3638
test('publishConfigurationForDiscovery should be called as expected', () => {
3739
const moduleToTest = rewire('./index');
3840
moduleToTest.__set__('client', {
3941
publish: jest.fn(() => {}),
4042
});
41-
moduleToTest.publishConfigurationForDiscovery();
42-
expect(moduleToTest.__get__('client').publish).toHaveBeenCalledWith('homeassistant/sensor/teleinfo/default/config', JSON.stringify({
43-
unique_id: 'teleinfo',
44-
name: 'teleinfo',
43+
moduleToTest.publishConfigurationForDiscovery('123456789');
44+
expect(moduleToTest.__get__('client').publish).toHaveBeenCalledWith('homeassistant/sensor/teleinfo/123456789/config', JSON.stringify({
45+
unique_id: 'teleinfo_123456789',
46+
name: 'Teleinfo 123456789',
4547
icon: 'mdi:speedometer',
46-
state_topic: 'teleinfo',
47-
json_attributes_topic: 'teleinfo',
48+
state_topic: 'teleinfo/123456789',
49+
json_attributes_topic: 'teleinfo/123456789',
4850
value_template: '{{ value_json.PAPP.value }}',
4951
unit_of_measurement: 'VA',
52+
device: {
53+
identifiers: ['teleinfo-mqtt'],
54+
manufacturer: 'fmartinou',
55+
model: 'teleinfo-mqtt',
56+
name: 'Teleinfo-mqtt',
57+
},
5058
}), { retain: true });
5159
});

0 commit comments

Comments
 (0)