Skip to content

Commit 2155c61

Browse files
committed
Add early version
1 parent 6b0e2a2 commit 2155c61

15 files changed

+2989
-0
lines changed

.codeclimate.yml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
exclude_patterns:
2+
- "**/*.test.js"

.dockerignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

.eslintrc

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
extends: "airbnb-base",
3+
plugins: [
4+
"jest"
5+
],
6+
rules: {
7+
indent: [
8+
"error",
9+
4
10+
]
11+
},
12+
env: {
13+
jest/globals: true
14+
}
15+
}

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.vscode/

.travis.yml

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
sudo: required
2+
3+
arch:
4+
- amd64
5+
- arm64
6+
7+
env:
8+
global:
9+
- CC_TEST_REPORTER_ID=054a8c4f2007b9299933b67221504554462eed6cbb06c364c926a9869dfd4020
10+
11+
language: node_js
12+
node_js:
13+
- "12"
14+
15+
services:
16+
- docker
17+
18+
install:
19+
- npm ci
20+
21+
before_script:
22+
23+
# Init Code Climate
24+
- if [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then
25+
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter;
26+
chmod +x ./cc-test-reporter;
27+
./cc-test-reporter before-build;
28+
fi
29+
30+
script:
31+
32+
# Run Linter
33+
- npm run lint
34+
35+
# Run Unit tests
36+
# - npm test
37+
38+
after_script:
39+
40+
# Report to Code Climate
41+
- if [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then
42+
./cc-test-reporter after-build -t lcov --debug --exit-code $TRAVIS_TEST_RESULT;
43+
fi
44+
45+
# Build docker image
46+
- docker build -t $DOCKER_USERNAME/teleinfo-mqtt:$TRAVIS_BRANCH-$TRAVIS_CPU_ARCH -f Dockerfile .
47+
48+
# Tag or master branch? -> push
49+
- if [ ! -z "$TRAVIS_TAG" ] || [ "$TRAVIS_BRANCH" == "master" ] ; then
50+
docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD";
51+
docker push $DOCKER_USERNAME/teleinfo-mqtt:$TRAVIS_BRANCH-$TRAVIS_CPU_ARCH;
52+
fi
53+
54+
# Tag? -> push tag & latest
55+
- if [ ! -z "$TRAVIS_TAG" ] ; then
56+
docker tag $DOCKER_USERNAME/teleinfo-mqtt:$TRAVIS_BRANCH $DOCKER_USERNAME/teleinfo-mqtt:latest-$TRAVIS_CPU_ARCH;
57+
docker push $DOCKER_USERNAME/teleinfo-mqtt:latest-$TRAVIS_CPU_ARCH;
58+
fi

Dockerfile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM node:12-alpine
2+
3+
LABEL maintainer="fmartinou"
4+
5+
RUN apk update && apk add --no-cache make gcc g++ python linux-headers udev
6+
7+
WORKDIR /home/node/app
8+
9+
# Default Command
10+
CMD ["node", "index"]
11+
12+
# Copy package.json
13+
COPY package* ./
14+
15+
# Copy app
16+
COPY app/ ./
17+
18+
# Install dependendencies
19+
RUN npm ci --production

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) [2019] [Manfred Martin]
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# teleinfo-mqtt
22
Publish teleinfo to mqtt topics
3+
4+
WIP!!!

app/config/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
logLevel: process.env.LOG_LEVEL || 'info',
3+
serial: process.env.SERIAL || '/dev/ttyUSB0',
4+
mqttUrl: process.env.MQTT_URL || 'mqtt://localhost:1883',
5+
mqttTopic: process.env.MQTT_TOPIC || '/teleinfo',
6+
};

app/index.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const teleinfo = require('./teleinfo');
2+
const mqtt = require('./mqtt');
3+
const log = require('./log');
4+
5+
async function disconnect() {
6+
await teleinfo.disconnect();
7+
await mqtt.disconnect();
8+
}
9+
10+
async function run() {
11+
try {
12+
// Connect to MQTT
13+
14+
await mqtt.connect();
15+
16+
// Connect to serial port
17+
const teleinfoEventEmitter = await teleinfo.connect();
18+
19+
// Register to frame events and publish to mqtt
20+
teleinfoEventEmitter.on('frame', (frame) => {
21+
mqtt.publishFrame(frame);
22+
});
23+
24+
// Graceful exit
25+
process.on('exit', disconnect);
26+
process.on('SIGINT', disconnect);
27+
} catch (e) {
28+
log.error('Error');
29+
log.error(e);
30+
}
31+
}
32+
33+
run();

app/log/index.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const bunyan = require('bunyan');
2+
const config = require('../config');
3+
4+
const log = bunyan.createLogger({
5+
name: 'teleinfo-mqtt',
6+
level: config.logLevel,
7+
});
8+
9+
module.exports = log;

app/mqtt/index.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const mqtt = require('mqtt');
2+
const { mqttUrl, mqttTopic } = require('../config');
3+
const log = require('../log');
4+
5+
let client;
6+
7+
async function connect() {
8+
return new Promise((resolve, reject) => {
9+
log.info(`Connecting to MQTT broker [${mqttUrl}]`);
10+
client = mqtt.connect(mqttUrl);
11+
12+
client.on('connect', () => {
13+
log.info(`Connected to MQTT broker [${mqttUrl}]`);
14+
resolve();
15+
});
16+
17+
client.on('error', (e) => {
18+
log.info('MQTT connection error');
19+
reject(e);
20+
});
21+
});
22+
}
23+
24+
async function disconnect() {
25+
return new Promise((resolve, reject) => {
26+
log.info(`Disconnecting from MQTT broker [${mqttUrl}]`);
27+
client.end((e) => {
28+
if (e) {
29+
log.info(`Error on disconnecting from MQTT broker [${mqttUrl}]`);
30+
reject(e);
31+
} else {
32+
log.info(`Disconnected from MQTT broker [${mqttUrl}]`);
33+
resolve();
34+
}
35+
});
36+
});
37+
}
38+
39+
function publishFrame(frame) {
40+
log.debug(`Publish frame to topic [${mqttTopic}]`);
41+
client.publish(mqttTopic, JSON.stringify(frame));
42+
}
43+
44+
module.exports = {
45+
connect,
46+
disconnect,
47+
publishFrame,
48+
};

app/teleinfo/index.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const Readline = require('@serialport/parser-readline');
2+
const SerialPort = require('serialport');
3+
const events = require('events');
4+
const log = require('../log');
5+
const { serial } = require('../config');
6+
7+
let serialPort;
8+
9+
async function connect() {
10+
log.info(`Connecting teleinfo to port [${serial}]`);
11+
const teleInfoEventEmitter = new events.EventEmitter();
12+
let currentFrame = {};
13+
14+
serialPort = new SerialPort(serial, {
15+
baudRate: 1200,
16+
dataBits: 7,
17+
parity: 'even',
18+
stopBits: 1,
19+
});
20+
21+
const parser = serialPort.pipe(new Readline());
22+
23+
parser.on('data', (data) => {
24+
const dataStr = data.toString('utf-8');
25+
26+
// Split line `${label} ${value} ${checksum}`
27+
const lineItems = dataStr.split(' ');
28+
29+
if (lineItems.length < 2) {
30+
log.error(`Corrupted data received [${dataStr}]`);
31+
return;
32+
}
33+
34+
const label = lineItems[0];
35+
36+
// Frame end? -> Dispatch frame event
37+
if (label === 'MOTDETAT' && currentFrame.ADCO) {
38+
log.debug(`Dispatch frame ${JSON.stringify(currentFrame)}`);
39+
teleInfoEventEmitter.emit('frame', currentFrame);
40+
return;
41+
}
42+
43+
// Frame start? -> Reset frame accumulator object
44+
if (label === 'ADCO') {
45+
currentFrame = {};
46+
}
47+
48+
// Try to convert value to number
49+
const valueNumber = Number.parseInt(lineItems[1], 10);
50+
const value = !Number.isNaN(valueNumber) ? valueNumber : lineItems[1];
51+
52+
currentFrame[label] = value;
53+
});
54+
55+
parser.on('error', (e) => {
56+
log.error('Error on reading data');
57+
log.error(e);
58+
});
59+
60+
return teleInfoEventEmitter;
61+
}
62+
63+
async function disconnect() {
64+
log.info(`Disconnecting teleinfo from port [${serial}]`);
65+
return new Promise((resolve, reject) => {
66+
serialPort.close((e) => {
67+
if (e) {
68+
log.error(`Error on disconnecting teleinfo from port [${serial}]`);
69+
reject(e);
70+
} else {
71+
log.info(`Disconnected teleinfo from port [${serial}]`);
72+
resolve();
73+
}
74+
});
75+
});
76+
}
77+
78+
module.exports = {
79+
connect,
80+
disconnect,
81+
};

0 commit comments

Comments
 (0)