Affichage des articles dont le libellé est BeagleBone Black. Afficher tous les articles
Affichage des articles dont le libellé est BeagleBone Black. Afficher tous les articles

dimanche 24 septembre 2017

Capteurs I2C

I2C

Le protocole I2C est un bus de communication très simple, accessible depuis les GPIO d'une carte type Raspberry Pi ou BeagleBone Black.

BME280

Le BME280 est un capteur BOSCH de faible coût avec interfaces I2C et SPI, qui fournit des valeurs de:
  • température
  • pression
  • humidité relative.

Spécifications

  • Current consumption 1.8 μA @ 1 Hz humidity and temperature
  • Supply voltage VDD main supply voltage range: 1.71 V to 3.6 V
  • VDDIO interface voltage range: 1.2 V to 3.6 V
  • 2.8 μA @ 1 Hz pressure and temperature
  • 3.6 μA @ 1 Hz humidity, pressure and temperature
  • 0.1 μA in sleep mode
  • Operating range -40...+85 °C, 0...100 % rel. humidity, 300...1100 hPa
  • BME280 digital pressure sensor RoHS compliant, halogen-free, MSL1
  • Key parameters for humidity sensor1
  • Response time 1 s
  • Accuracy tolerance ±3 % relative humidity
  • Hysteresis ±1% relative humidity
  • Key parameters for pressure sensor
  • RMS Noise 0.2 Pa, equiv. to 1.7 cm
  • Offset temperature coefficient ±1.5 Pa/K, equiv. to ±12.6 cm at 1 °C temperature change
  • Material: Plastic
  • Size: approx.20mm*12mm*3mm
  • Color: Red
Package includes:
  • 1 x Barometric Pressure Sensor Board
  • 2 x Pin Header
Adress 0x77

Installation sur Rpi 1 A rev 2 (Wheezy)

La plupart des documentations font référence au protocole i2c, mais l'implémentation que l'on rencontre sur les systèmes monocartes sous Linux (Raspberry Pi, BeagleBone Black, etc.) est plutôt une spécialisation de ce protocole nommée SMBus (System Management Bus). Les différences essentielles sont des limitations dans les tensions autorisées (3.3V maxi.), ou les fréquences de communication (10 à 100 kHz seulement). Le protocole SMBus est plus particulièrement utilisé pour communiquer entre un microprocesseur et les périphériques annexes sur la carte-même (ventilateur, capteur de température, etc.). Ceci explique que de nombreuses fonctions de l'API Linux pour le sous-système i2c soient préfixées par smbus. L'accès depuis l'espace utilisateur est facilité par le module i2c-dev qui rend les bus i2c visibles dans le répertoire /dev sous forme de fichiers spéciaux représentant des périphériques en mode caractère.





Le protocole i2c est supporté par le noyau Linux depuis sa version 2.4. De nombreux périphériques sont reconnus par le kernel.
Il faut charger dans le noyau le module qui gère le contrôleur i2c inclus dans le system-on-chip (de la famille Broadcom 2708) du Raspberry Pi. Ce module est nommé i2c_bcm2708. On peut le charger automatiquement avec l'aide du menu raspi-config.

i2cdetect -l
i2c-1 i2c        20804000.i2c                     I2C adapter
Le bus i2c 0 est vide mais le BME280 est connecté sur le bus i2c 1 à l'adresse 0x77

Librarie python Adafruit

Python offre une interface pour la communication en i2c qui s'appuie sur les fonctionnalités i2c-dev. Il existe une bibliothèque permettant un accès simple aux périphériques qui supporte le sous-ensemble SMBus d'i2c. Les fonctions de haut-niveau permettent une communication avec le périphérique en dissimulant les multiples émissions/réceptions d'octets sous-jacentes.

apt-get update
apt-get install build-essential python-pip python-dev python-smbus git
git clone https://github.com/adafruit/Adafruit_Python_GPIO.git
cd Adafruit_Python_GPIO
sudo python setup.py install


git clone https://github.com/adafruit/Adafruit_Python_BME280.git

python ./Adafruit_BME280_Example.py
Temp      = 17.494 deg C
Pressure  = 904.30 hPa
Humidity  = 71.20 %

Client python influxDB

Installation du client influxdb: 
https://github.com/influxdata/influxdb-python

Install, upgrade and uninstall InfluxDB-Python with these commands:

$ pip install influxdb
$ pip install --upgrade influxdb
$ pip uninstall influxdb

Successfully installed influxdb python-dateutil pytz requests six chardet idna urllib3 certifi
Cleaning up...

Installation d'influxDB

Paquet  officiel désormais disponible pour Raspberry Pi (sur forme Linux Binaries - ARM) 
wget https://dl.influxdata.com/influxdb/releases/influxdb-1.3.5_linux_armhf.tar.gz
tar xvfz influxdb-1.3.5_linux_armhf.tar.gz


/usr/lib/influxdb/scripts# cp init.sh /etc/init.d/influxd
update-rc.d influxd defaults

useradd -m influxdb
groupadd  influxdb
usermod -a -G influxdb influxdb

influx
Connected to http://localhost:8086 version 1.3.5
InfluxDB shell version: 1.3.5
> create database domotic;

service influxd start

Visualisation

N'importe quel serveur Grafana peut publier des tableaux de bords à partir des données du capteur BME280 enregistrées dans InfluxDB

 
 

Beagle Bone Black




debian@bbb-tic:~$ ls -l /sys/bus/i2c/devices/i2c-*
lrwxrwxrwx 1 root root 0 Jan  1  2000 /sys/bus/i2c/devices/i2c-0 -> ../../../devices/ocp.3/44e0b000.i2c/i2c-0
lrwxrwxrwx 1 root root 0 Jan  1  2000 /sys/bus/i2c/devices/i2c-2 -> ../../../devices/ocp.3/4819c000.i2c/i2c-2
debian@bbb-tic:~$ 

apt-get update
apt-get install i2c-tools

root@bbb-tic:/home/debian# i2cdetect -l

i2c-0 i2c        OMAP I2C adapter                 I2C adapter
i2c-2 i2c        OMAP I2C adapter                 I2C adapter


root@bbb-tic:/home/debian# i2cdetect -y -r 2
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --  

bug in device tree MUST: roollback to a previous kernel

To enable the I2c-1 on the BeagleBone Black Rev A, B and C:

    Rev A/B: Open the file /media/BEAGLEBONE/uEnv.txt in an editor (vim/nano)
    Rec C: Open the file /boot/uboot/uEnv.txt in an editor (vim/nano)
    Add the key "capemgr.enable_partno="
    Add the ports you want to enable, comma separated (BB-I2C0, BB-I2C1, etc)
    Reboot

I had the same problem (missing cape-manager ) in the newer kernel versions a few weeks ago.
Needed to switch back to kernel 3.8.13 to get the cape-manager working. I haven't had time to investigate more.

If you want to give it a try:

sudo apt-get update
sudo apt-get install linux-image-3.8.13-bone67
sudo reboot

/boot/uEnv.txt

#Docs: http://elinux.org/Beagleboard:U-boot_partitioning_layout_2.0

uname_r=3.14.33-ti-r50
#dtb=
cmdline=quiet init=/lib/systemd/systemd 

##Example
#cape_disable=capemgr.disable_partno=
cape_enable=capemgr.enable_partno=BB-I2C0,BB-I2C1,BB-I2C2 

##enable BBB: eMMC Flasher:
#cmdline=init=/opt/scripts/tools/eMMC/init-eMMC-flasher-v3.sh

uuid=5b9e0cf5-52b7-4bf3-8e38-c9c805d89105



i2cdetect -l
i2c-0 i2c        OMAP I2C adapter                 I2C adapter
i2c-1 i2c        OMAP I2C adapter                 I2C adapter
i2c-2 i2c        OMAP I2C adapter                 I2C adapter

root@bbb-tic:/home/debian# i2cdetect -y -r 0 
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- UU -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- UU -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: UU -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: UU -- -- -- -- -- -- --     

i2cdetect -y -r 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- UU UU UU UU -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- 77
              
root@bbb-tic:/home/debian# i2cdetect -y -r 2
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

Attention:
BME280 connecté sur pins P9 19,20 soient i2C2
Inversion de l'énumération dans le kernel i2C2 est de dev 1

BBB avec lib python adafruit

Install de la lib python portable GPIO d'adafruit: https://xdevs.com/guide/thp_rpi/

apt-get update
apt-get install build-essential python-pip python-dev python-smbus git
git clone https://github.com/adafruit/Adafruit_Python_GPIO.git
cd Adafruit_Python_GPIO
sudo python setup.py install

git clone https://github.com/adafruit/Adafruit_Python_BME280.git

Install du client influxdb: 
https://github.com/influxdata/influxdb-python

Install, upgrade and uninstall InfluxDB-Python with these commands:
$ pip install influxdb
$ pip install --upgrade influxdb
$ pip uninstall influxdb

pip install influxdb

Successfully installed influxdb python-dateutil pytz requests six chardet idna urllib3 certifi
Cleaning up...

Tests

:~/bme280/Adafruit_Python_BME280$ python ./Adafruit_BME280_Example.py 
Temp      = 16.531 deg C
Pressure  = 914.29 hPa
Humidity  = 56.21 %

Code source

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import argparse
import json
import time
from collections import OrderedDict
from datetime import datetime
from Adafruit_BME280 import *
from influxdb import InfluxDBClient

def parse_args():
    parser = argparse.ArgumentParser(
        description='example code to play with InfluxDB')
    parser.add_argument('--host', type=str, required=False, default='localhost',
                        help='hostname of InfluxDB http API')
    parser.add_argument('--port', type=int, required=False, default=8086,
                        help='port of InfluxDB http API')
    return parser.parse_args()

def main(host='192.168.1.xxx', port=8086):

    user = 'xxxx'
    password = 'xxxx'
    dbname = 'xxxxx'
    query = 'select * from bureau;'

    measurement = OrderedDict()
    measurement['measurement']='Garage'
    tags = OrderedDict()
    tags['host']='bbb-tic'
    tags['sensor']='bme280'
    measurement['tags'] = tags

    fields = OrderedDict()

    #### get the values
    sensor = BME280(t_mode=BME280_OSAMPLE_8, p_mode=BME280_OSAMPLE_8, h_mode=BME280_OSAMPLE_8)

    while True:
        temp = sensor.read_temperature()
        pressure = sensor.read_pressure()
        humidity = sensor.read_humidity()
        #print 'Temp      = {0:0.3f} deg C'.format(temp)
        #print 'Pressure  = {0:0.2f} hPa'.format(pressure/100)
        #print 'Humidity  = {0:0.2f} %'.format(humidity)

        fields["Temp"] = temp
        fields["Pressure"] = pressure/100
        fields["Humidity"] = humidity
        measurement["fields"] = fields
        #### get the time
        ## UNIX timestamp
        ## measurement['time'] = int(time.time())
        current_time = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
        measurement['time'] = current_time
        json_body = json.dumps(measurement)
#        print("Result: {0}".format(json_body))

        client = InfluxDBClient(host, port, user, password, dbname)
#        client.write_points([measurement])

#        print("Queyring data: " + query)
#        result = client.query(query)
#        print("Result: {0}".format(result))
        break
        time.sleep(60)

if __name__ == '__main__':
    args = parse_args()
    main(host=args.host, port=args.port)
debian@bbb-tic:~/bme280/Adafruit_Python_BME280$ 

samedi 15 août 2015

Domoticz sur BeagleBone

Introduction


Cet article résume la procèdure de compilation et d'installation de Domoticz sur une carte BeagleBone Black (rev B) configurée avec une distribution Linux Debian 8 (Jessie).

Prérequis


  • Un serveur Linux/Debian
  • Les outils de compilation C++
  • La librairie open-zwave pour utiliser ce protocole
  • Un client Subversion

Client git

Le client git s'installe depuis le repository de paquets Debian avec la commande suivante:
sudo apt-get install git

Outils de compilation C++

Les librairies nécessaires à la compilation s'installent avec les deux commandes suivantes:
sudo apt-get install build-essential -y
sudo apt-get install cmake libboost-dev libboost-thread-dev libboost-system-dev libsqlite3-dev curl libcurl4-openssl-dev libusb-dev zlib1g-dev libssl-dev

Compilation d'openZWave


Installer la dépendance libudev:
sudo apt-get install libudev-dev

Depuis le répertoire racine des compilations qui est par défaut /home/debian:
Récupérer les sources depuis github avec l'aide de la commande suivante:
git clone https://github.com/OpenZWave/open-zwave.git

Lancer la compilation:
/home/debian$ make

La sortie standard de la commande make devrait ressembler aux lignes suivantes (noter la version 1.3.437):
make -C /home/debian/open-zwave/cpp/build/ -
make[1]: Entering directory '/home/debian/open-zwave/cpp/build'
Building OpenZWave Version 1.3-437-g09a092
...
Linking Static Library
Linking Shared Library
make[1]: Leaving directory '/home/debian/open-zwave/cpp/build'
make -C /home/
debian/open-zwave/cpp/examples/MinOZW/ -
make[1]: Entering directory '/home/debian/open-zwave/cpp/examples/MinOZW'
Building Main.o
Linking /home/
debian/open-zwave/.lib/MinOZW
g++ -o /home/
debian/open-zwave/.lib/MinOZW /home/debian/open-zwave/.lib/Main.o /home/debian/open-zwave/libopenzwave.so -pthread
Creating Temporary Shell Launch Script
make[1]: Leaving directory '/home/
debian/open-zwave/cpp/examples/MinOZW'

Création d'un lien vers libOpenZWave


La librairie OpenZwave compilée précedemment correspond à la version 1.3-437. Je recommande la création d'un lien symbolique vers cette librairie depuis le répertoire racine utilisé pour les compilations (/home/debian):

ls -al
drwxr-xr-x 23 debian debiann  4096 Aug 9 15:23 domoticz
drwxr-xr-x 11 debian debian  4096 Aug 9 16:07 open-zwave

mv open-zwave ozw-1.3.434
ln -s ozw-1.3.434 ozw 

ls -al
drwxr-xr-x 23 debiandebian  4096 Aug 9 15:23 domoticz
lrwxrwxrwx 1 debiandebian  11 Aug 9 16:13 ozw -> ozw-1.3.434
drwxr-xr-x 11 debiandebian  4096 Aug 9 16:07 ozw-1.3.434

Compilation de domoticz



Etat des lieux

La commande free -mh affiche la quantité de RAM utilisée:
                              total       used       free     shared    buffers    cached
Mem:                     490M     102M      387M  8,3M       15M        51M
-/+ buffers/cache:  35M       455M
Swap:                     0B         0B            0B

La commande df -h affiche la liste des systémes de fichiers montés ainsi que leurs points de montage et taux d'occupation.
Sys. de fichiers   Taille  Utilisé   Dispo   Utilisé   Monté sur
udev                    10M    0          10M      0% /dev
tmpfs                   99M    4,3M    94M      5% /run
/dev/mmcblk0p1 1,8G    1,1G     573M    66% /
tmpfs                   246M  0          246M    0% /dev/shm
tmpfs                   5,0M   4,0K     5,0M     1% /run/lock
tmpfs                   246M  0          246M     0% /sys/fs/cgroup


Création d'un espace de swap

La commande free montre que ce système ne possède pas d'espace de swap.
L'utilisation de librairies templates comme boost requiert beaucoup de mémoire pendant l'étape de compilation de domoticz, en particulier lors de la fabrication du serveur Web. Afin de garantir le succès de cette étape, je recommande d'ajouter un RAM disk pour permettre au système de swapper s'il ne dispose pas de la quantité de memoire physique nécessaire.

Le file système dispose de 573Mo libre, on peut donc créer un espace de swap temporaire de 256Mo.

La commande suivante permet de créer un fichier d'une capacité de 256Mo pour le futur espace de swap (1024*256 = 262144 blocks).

sudo dd if=/dev/zero of=/swapfile bs=1024 count=262144

262144+0 enregistrements lus
262144+0 enregistrements écrits
268435456 octets (268 MB) copiés, 34,4415 s, 7,8 MB/s


Ensuite, il convient de sécuriser les accès à cet espace mémoire:

sudo chown root:root /swapfile
sudo chmod 0600 /swapfile

Enfin, on définit puis on active cet espace de swap avec l'aide des commandes suivantes:
sudo mkswap /swapfile

Setting up swapspace version 1, size = 262140 KiB
no label, UUID=d1868600-d866-4982-a4f5-e7ee0abd580f


sudo swapon /swapfile
                              total       used       free       shared    buffers     cached
Mem:                     490M     330M      160M    4,3M       7,7M        287M
-/+ buffers/cache:  34M       455M
Swap:                    255M      0B           255M

Compilation

Depuis le répertoire racine des compilations (/home/debian).
Récupérer les sources depuis le repository git hub avec l'aide de la commande suivante:
git clone https://github.com/domoticz/domoticz.git domoticz

Aller dans le réperoire domoticz et modifier le chemin de recherche de la librarie openZWave dans le fichier CMakeLists.txt:

cd domoticz; vi  CMakeLists.txt:

# try to find open-zwave, if found, include supportfind_library(OpenZWave NAMES libopenzwave.a HINTS "../ozw" "../ozw/cpp/build")

Lancer la fabrication du makefile avec la commande suivante:
cmake -DCMAKE_BUILD_TYPE=Release .

La sortie standard de la commande cmake devrait ressembler aux lignes suivantes (noter la révision 2737 ainsi que la détection de libopenzwave):

-- Compiling Revision #2737
-- Boost version: 1.49.0
-- Found the following Boost libraries:
--   thread
--   date_time
--   system
-- Linking against boost static libraries
-- Found ZLIB: /usr/lib/arm-linux-gnueabihf/libz.so (found version "1.2.7")
-- ZLIB libraries found at: /usr/lib/arm-linux-gnueabihf/libz.so
-- ZLIB includes found at: /usr/include
-- Found CURL: /usr/lib/arm-linux-gnueabihf/libcurl.so (found version "7.26.0")
-- Curl libraries found at: /usr/lib/arm-linux-gnueabihf/libcurl.so
-- Curl includes found at: /usr/include
-- Found LIBUSB: /usr/lib/arm-linux-gnueabihf/libusb.so 
-- LIBUSB found at: /usr/lib/arm-linux-gnueabihf/libusb.so
-- Crypto library found at: /usr/lib/arm-linux-gnueabihf/libcrypto.so
-- /home/debian/ozw/libopenzwave.a
 

 Lancer la compilation de domoticz avec l'aide de la commande:
make
... 
Linking C static library liblua.a
[ 19%] Built target lua
Scanning dependencies of target mqtt
[ 19%] Building C object MQTT/CMakeFiles/mqtt.dir/mosquitto.c.o
[ 20%] Building C object MQTT/CMakeFiles/mqtt.dir/logging_mosq.c.o
[ 20%] Building C object MQTT/CMakeFiles/mqtt.dir/memory_mosq.c.o
[ 21%] Building C object MQTT/CMakeFiles/mqtt.dir/messages_mosq.c.o
[ 21%] Building C object MQTT/CMakeFiles/mqtt.dir/net_mosq.c.o
[ 22%] Building C object MQTT/CMakeFiles/mqtt.dir/read_handle.c.o
[ 22%] Building C object MQTT/CMakeFiles/mqtt.dir/read_handle_client.c.o
[ 23%] Building C object MQTT/CMakeFiles/mqtt.dir/read_handle_shared.c.o
[ 24%] Building C object MQTT/CMakeFiles/mqtt.dir/send_client_mosq.c.o
[ 24%] Building C object MQTT/CMakeFiles/mqtt.dir/send_mosq.c.o
[ 25%] Building C object MQTT/CMakeFiles/mqtt.dir/socks_mosq.c.o
[ 25%] Building C object MQTT/CMakeFiles/mqtt.dir/srv_mosq.c.o
[ 26%] Building C object MQTT/CMakeFiles/mqtt.dir/thread_mosq.c.o
[ 26%] Building C object MQTT/CMakeFiles/mqtt.dir/time_mosq.c.o
[ 27%] Building C object MQTT/CMakeFiles/mqtt.dir/tls_mosq.c.o
[ 27%] Building C object MQTT/CMakeFiles/mqtt.dir/util_mosq.c.o
[ 28%] Building C object MQTT/CMakeFiles/mqtt.dir/will_mosq.c.o
Linking C static library libmqtt.a
[ 28%] Built target mqtt
Scanning dependencies of target sqlite
...

Remarque

L'espace de swap est recommandé puisque lors de la phase de compilation du serveur web:
[ 36%] Building CXX object CMakeFiles/domoticz.dir/main/WebServer.cpp.o

Le système réalise quelques opérations de swap (14Mo):
             total         used       free      shared   buffers    cached
Mem:                     490M     456M   33M       280K       636K       
-/+ buffers/cache:  431M     59M
Swap:                     255M     14M     241M

Suppression du swap

La commande suivante desactive l'espace de swap:
sudo swapoff /swapfile

La commande suivante supprime le fichier de swap correspond:
sudo rm /swapfile

Création d'un lien vers domoticz


La version de domoticz compilée précedemment correspond à la révision  2737. Je recommande la création d'un lien symbolique vers cette librairie depuis le répertoire racine utilisé pour les compilations ((/home/debian):

ls -alh
drwxr-xr-x 25 pi   debian   4.0K Aug  9 19:35 domoticz
lrwxrwxrwx  1 debian   debian     11 Aug  9 16:13 ozw -> ozw-1.3.434
drwxr-xr-x 11 debian   debian   4.0K Aug  9 16:07 ozw-1.3.434
mv domoticz domoticz-2737
ln -s domoticz-2737 domoticz

ls -al
lrwxrwxrwx  1 debian   debian       13 Aug  9 19:39 domoticz -> domoticz-2737
drwxr-xr-x 25 debian   debian     4096 Aug  9 19:35 domoticz-2737
lrwxrwxrwx  1 debian   debian       11 Aug  9 16:13 ozw -> ozw-1.3.434
drwxr-xr-x 11 debian   debian     4096 Aug  9 16:07 ozw-1.3.434

Démarrage


Pour démarrer domoticz sous la forme d'un service recopier le script  domoticz.sh sous le répertoire /etc/init.d:
sudo cp domoticz.sh /etc/init.d 
sudo chmod +x /etc/init.d/domoticz.sh 
sudo update-rc.d domoticz.sh defaults 

Editer le script afin de mettre à jour le chemin d'accès au programme domoticz: 
sudo vi /etc/init.d/domoticz.sh 
DAEMON=/home/debian/domoticz/domoticz

Le script permet aussi de changer le port utilisé par l'interface Web:
OPTIONS=-www 8080

domoticz peut maintenant être supervisé avec les commandes suivantes:
sudo service domoticz.sh start
sudo service domoticz.sh stop
sudo service domoticz.sh status

Gestion des services


La commande update-rc.d permet d'ajouter, modifier ou supprimer un service.

Ajout

La commande update-rc.d [-n] domoticz.sh defaults ajoute le service domoticz (l’option -n permet de tester la commande sans la jouer).

Suppression

La commande update-rc.d domoticz.sh remove desactive le service domoticz. Cependant, si on souhaite réactiver le service ultérieurement, on pourra exécuter de nouveau la commande suivante: update-rc.d domoticz.sh defaults.

samedi 8 août 2015

TIC sur BeagleBone

Télé Information Client (TIC)


La TéléInformation Client (TIC) est une spécification d'interface mise en place par EDF dans ses compteurs bleus électroniques (CBE). La BeagleBone Black (BBB) constitue une plate forme de choix pour exploiter ces informations de TIC mises à disposition par le CBE. Cet article propose de collecter à moindre frais, les informations de consommation électrique d'une habitation . Les données pourront servir à représenter graphiquement des habitudes de consommation.

On entend beaucoup parler d'économie d'énergie du fait de considérations écologiques, mais il existe assez peu d'information sur la manière d'évaluer simplement sa consommation énergétique. Une évidence devrait pourtant s'imposer: pour faire des économies, il faut avant toute chose savoir mesurer et identifier l'origine de cette consommation.

Compteur Bleu Electronique (CBE)


Les compteurs bleus électroniques sont des objets communicants qui offrent
deux bus accessibles par l'exploitant ou l'usager.

L'exploitant possède un accès relatif aux données de comptage par un bus de télérelève au standard EURIDIS: une transaction par le bus de télérelève se réalise en 3 ou 4 secondes à l'initiative d'un équipement de relève à distance. Dans la pratique, l'équipement de l'exploitant possède un accès partagé à N compteurs qu'il doit interroger en séquence. Par conséquent, la fréquence des transactions sera limitée à  la fois par leur durée et le nombre N de compteurs à relever.
L'usager dispose d'une sortie téléinfo spécifique d'EDF qui offre ses données en continu. Cette téléinformation est réalisée par une liaison série numérique modulée qui diffuse en permanence des données contenues dans les mémoires du compteur. La liaison TIC pourra être utilisée par l'usager afin de connecter des systèmes tels qu'un afficheur déporté, un terminal ou un gestionnaire d’énergie.

La TéléInformation Client (TIC) rend le compteur CBE communicant vers le consommateur pour lui permettre de réaliser et d'utiliser ses propres services:
  • Le suivi de sa consommation instantanée en temps réel (environ 1Hz) depuis le web sur PC ou terminaux mobiles.
  • La sauvegarde sous la forme d'historique de ses données de consommation.
  • Une visualisation graphique de sa consommation.
Les informations de TIC sont transmises cycliquement en série sur la ligne et chaque donnée est précédée d’une étiquette permettant de l’identifier. L’ensemble des informations transmis dépend de la programmation du compteur. Ce point est important pour la réalisation des algorithmes de lecture qui doivent interpréter des trames selon le type d'abonnement. Les groupes d’informations inutiles, compte tenu du mode de fonctionnement programmé, ne sont pas émis.
Les grandeurs fournies par la sortie téléinfo sont classées en deux catégories:
  1. Information sur le client: tarif, puissance souscrite ...
  2. La consommation du client: puissance appelée, Index, Intensité instantanée ...
Les grandeurs sont mises à jour environ chaque seconde, ce qui permet de suivre leur évolution au cours du temps avec une résolution de l'ordre de 1Hz.

Accessoires


  • Carte BeagleBone Black configurée avec une distribution Linux Debian.
  • Démodulateur des trames TIC et circuit FTDI.
Les informations de TIC sont émises cycliquement sous forme de messages composés d’une étiquette d’identification suivie généralement d’une valeur. Le signal est modulé à 50 KHz: la présence de modulation correspondant à un 0 logique, l’absence à un 1 logique. Il est donc nécessaire de démoduler ce signal pour obtenir une suite de caractères ASCII (émise à 1200 bits/s, 7 bits/caractères, parité paire, 1 bit de stop).Chaque information débute par le caractère LF (0A hexa), suivie de l’étiquette et de sa valeur associée, puis se termine par le caractère CR (0D).
Il est possible de réaliser son propre démodulateur à partir de schémas disponibles sur Internet. Cependant, le matériel utlisé dans cet article est un TéléinfoStick v2 prêt à l'emploi. En plus du démodulateur de signal TIC,  le TéléinfoStick comprend un adaptateur USB/TLL qui permet de le raccorder très facilement à la BeagleBone.

Montage





Une partie du TéléinfoStick v2 est raccordé aux bornes I1I2 du compteur bleu électronique.


L'autre partie du TéléinfoStick v2 correspond au connecteur USB/TTL. Elle sera connectée sur un port USB d'une BeagleBone Black ou d'une carte de même type.

Serveur LAMP


Les données sont sauvegardées dans une base MySQL et leur visualisation est assurée par un module PHP inclus dans un serveur Apache.


Il s'agit d'une architecture de type LAMP installée sur des cartes BeagleBone Black.

Archivage des données


Un abonnement EDF de type Tempo possède trois couleurs (Bleu/Blanc/Rouge) avec un traif Heures Creuses/Pleines pour chacune d'elles, soit 6 tarifs au total. Le changement de couleur du jour se fait à 6h du matin et la période d'heures creuses est de 22h a 6h.

Modèle de données d'un abonnement tempo

+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default |
+----------+-------------+------+-----+---------+-------+
| DATE     | datetime    | YES  |     | NULL    |
| ADCO     | varchar(12) | YES  | MUL | NULL    |
| OPTARIF  | varchar(4)  | YES  |     | NULL    |
| ISOUSC   | varchar(2)  | YES  |     | NULL    |
| BASE     | varchar(9)  | YES  |     | NULL    |
| HCHC     | varchar(9)  | YES  |     | NULL    |
| HCHP     | varchar(49) | YES  |     | NULL    |
| EJPHN    | varchar(9)  | YES  |     | NULL    | 
| EJPHPM   | varchar(9)  | YES  |     | NULL    |
| BBRHCJB  | varchar(9)  | YES  |     | NULL    |
| BBRHPJB  | varchar(9)  | YES  |     | NULL    | 
| BBRHCJW  | varchar(9)  | YES  |     | NULL    |
| BBRHPJW  | varchar(9)  | YES  |     | NULL    | 
| BBRHCJR  | varchar(9)  | YES  |     | NULL    | 
| BBRHPJR  | varchar(9)  | YES  |     | NULL    |
| PEJP     | varchar(2)  | YES  |     | NULL    |
| PTEC     | varchar(4)  | YES  |     | NULL    |
| DEMAIN   | varchar(4)  | YES  |     | NULL    | 
| IINST    | varchar(3)  | YES  |     | NULL    |
| ADPS     | varchar(4)  | YES  |     | NULL    |
| IMAX     | varchar(4)  | YES  |     | NULL    |
| IINST1   | varchar(3)  | YES  |     | NULL    |
| IINST2   | varchar(3)  | YES  |     | NULL    |
| IINST3   | varchar(3)  | YES  |     | NULL    | 
| IMAX1    | varchar(3)  | YES  |     | NULL    | 
| IMAX2    | varchar(3)  | YES  |     | NULL    |
| IMAX3    | varchar(3)  | YES  |     | NULL    | 
| PMAX     | varchar(5)  | YES  |     | NULL    | 
| PAPP     | varchar(5)  | YES  |     | NULL    | 
| HHPHC    | varchar(5)  | YES  |     | NULL    |
| MOTDETAT | varchar(6)  | YES  |     | NULL    | 
| PPOT     | varchar(2)  | YES  |     | NULL    |
+----------+-------------+------+-----+---------+-------+

Description

  • DATE : date de la mesure au format MySQL (ex. 2015-02-18 08:51:27 pour 18 Février 2015) 
  • BBRHCJB : index compteur Heure Creuse Jour Bleu 
  • BBRHPJB : index compteur Heure Pleine Jour Bleu 
  • BBRHCJW : index compteur Heure Creuse Jour Blanc (w pour white en anglais) 
  • BBRHPJW : index compteur Heure Pleine Jour Blanc 
  • BBRHCJR : index compteur Heure Creuse Jour Rouge 
  • BBRHPJR : index compteur Heure Pleine Jour Rouge 
  • PTEC : tarification/couleur en cours qui contient:
    • HPJB pour HeurePleineJourBleu 
    • ou HCJB pour HeureCreuseJourBleu 
    • ou HCJW pour HeureCreuseJourBlanc 
    • ou HPJW pour HeurePleineJourBlanc 
    • ou HCJR pour HeureCreuseJourRouge 
    • ou HPJR pour HeurePleineJourRouge

Programme C


Le programme C suivant lit les données fournies par le TeleinfoStick sur un port USB/série d'une carte BeagleBone. Les données sont ensuite enregistrées dans une base MySQL. Le programme vérifie les checksums des trames de téléinformation et peut réaliser plusieurs essais successifs lors d'erreurs de lecture.

Code soure 

#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
#include <mysql/mysql.h>

// Check windows
#if _WIN32 || _WIN64
#if _WIN64
#define ENV_64BIT
#else
#define ENV_32BIT
#endif
#endif

// Check GCC
#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENV_64BIT
#else
#define ENV_32BIT
#endif
#endif

//#define DEBUG

const char *VERSION         = "1.0.2 (2014-08-19)";
const char *SERIAL_DEVICE   = "/dev/ttyUSB0";
const char *CSV_FILE_PATH   = "/home/pi/teleinfo/log/edf_tli.csv";
const char *OPTARIF         = "OPTARIF";

// MySQL settings
const char * MYSQL_HOST     = "192.168.1.xxx";
const char * MYSQL_DB       = "teleinfo";
const char * MYSQL_TABLE    = "xxx";
const char * MYSQL_LOGIN    = "yyy";
const char * MYSQL_PWD      = "zzz";
const char *MYSQL_FIELDS[] =
{"ADCO","OPTARIF","ISOUSC","BASE","HCHC","HCHP","EJPHN","EJPHPM",
 "BBRHCJB","BBRHPJB","BBRHCJW","BBRHPJW","BBRHCJR","BBRHPJR",
  "PEJP","PTEC","DEMAIN","IINST","ADPS","IMAX",
  "IINST1","IINST2","IINST3","IMAX1","IMAX2","IMAX3",
 "PMAX","PAPP","HHPHC","MODETAT","PPOT",
 NULL
};

const uint32_t BUFFER_SIZE  = 512;
const uint32_t ITEM_COUNT   = 64;
const uint32_t ITEM_LEN     = 16;
const uint32_t MAX_RETRY    = 3;

enum eStatus
{
    success = 0,
    failed,
    /*! serial line is still open */
    opened,
    /*! serial line has been closed*/
    closed,
    /*! serial line interrupt occurred */
    interrupt,
    /*! check sum failed */
    csFailed
};
typedef struct execStatus {
    execStatus(): status(failed), idx(0), idxError(0), errorCount(0){}
    eStatus status;
    uint32_t idx;
    uint32_t idxError;
    uint32_t errorCount;
} execStatus_t;

typedef struct dateTime
{
    char d[12];
    char h[10];
    char ts[11];
} dateTime_t;
typedef struct item
{
    item(): id(NULL),
            cs((unsigned char)'0') {}

    enum {UNKNOWN= 0, ALPHA, NUM} type;
    /*! item identifier */
    char *id;
    /*! id's checksum */
    unsigned char cs;
    union {
        unsigned long long int nVal[ITEM_COUNT];
        char sVal[ITEM_COUNT][ITEM_LEN];
    } values;
} item_t;
typedef struct edf_teleinfo
{
    edf_teleinfo(): device(NULL),
                    fd(-1),
                    status(failed) {}
    /*!  device is the device's name */
    const char *device;
    /*! fd is the file descriptor of this serial line */
    int32_t fd;
    /*! is the old termios settings */
    struct termios oldOpt;
    /*! is the current termios settings */
    struct termios curOpt;
    /*! status is the current state of this serial line */
    eStatus status;
} edf_teleinfo_t;

// global types
item_t          *gp_ids = NULL;
edf_teleinfo_t  g_tli;
FILE            *gp_csv = NULL;

// function declarations
/*!
 * @brief INThandler() is designed to handle signal SIGINT.
 * When the user press Ctrl-C, INThandler() is called with its only argument
 * indicating the signal number.
 *
 * @param sig should contain SIGINT.
 */
void INThandler(int sig);
/*!
 * \brief checksumCmp compares two checksum for equality
 * \param c is a checksum to valuate
 * \param cs is a checksum value the check against
 *
 * \return success if the two checksum are the same.
 */
eStatus checksumCmp(unsigned char c, unsigned char cs);
void getDateTime(dateTime_t *dt);
eStatus setOPTARIF(const char *s, item_t *ids);
eStatus setValue(const char *s, item_t *ids);
/*!
 * @brief Open the serial line.
 *
 * @param t contains serial line info
 * @param device is the device's name
 * @param term is the current termios settings
 *
 */
void openSerial(edf_teleinfo_t *t, const char *device);
/*!
  * findFrame is looking for the start of a frame
  *
  * @param t contains serial line info
  *
  * @return true if start of frame is found
  */
void printSerialOptions(edf_teleinfo_t *t);
eStatus findFrame(edf_teleinfo_t *t);
/*!
  * readFrame fills out a string buffer with the current frame content
  *
  * @param t contains serial line info
  * @param buf is the frame content
  *
  * @return success if start of frame is found
  */
eStatus readFrame(edf_teleinfo_t *t, char *frameBuf, int size, int *count);
item_t *createIds(char *frameBuf, int size, int *count);
eStatus addValues(char *frameBuf, int size, item_t *ids, int count);
FILE *createCsvFile(item_t *ids, int count);
void appendCsvFile(FILE *csv, dateTime_t *dt, item_t *ids, int count);
item_t *findId(const char *s, item_t *ids, int count);
eStatus appendMySql(dateTime_t *dt, item_t *ids, int count);

// function definitions
/*!
 * Retore the old settings for the serial line
 */
void INThandler(int sig)
{
    (void)sig;
    syslog(LOG_DEBUG, "=> INThandler");
    if (NULL != g_tli.device)
    {
        free((void *)(g_tli.device));
    }
    if (opened == g_tli.status)
    {
        tcflush(g_tli.fd, TCIFLUSH);
        tcsetattr(g_tli.fd, TCSANOW, &g_tli.oldOpt);
        close(g_tli.fd);
    }
    if (NULL != gp_csv)
    {
        fclose(gp_csv);
    }
    if (NULL != gp_ids)
    {
        free((void *)gp_ids->id);
    }
    syslog(LOG_DEBUG, "<= INThandler");
    closelog() ;
    exit(0);
}
eStatus checksumCmp(unsigned char c,unsigned char cs)
{
    eStatus status;
    c = (c & 0x3F) + 0x20;
    if (c == cs)
    {
        status = success;
    }
    else {
        syslog(LOG_ERR, "Wrong checksum: c= %#x, cs= %#x", c, cs);
        status = csFailed;
    }
    return status;
}
void getDateTime(dateTime_t *dt)
{
    time_t t;
    struct tm *ts;
    time(&t);
//    ts = gmtime(&t) ;
    ts = localtime(&t) ;
    strftime(dt->d, sizeof dt->d,"%Y-%m-%d", ts);
    strftime(dt->h, sizeof dt->h,"%H:%M:%S", ts);
    strftime(dt->ts, sizeof dt->ts,"%s", ts);
#ifdef DEBUG
    printf("getDateTime: ts= [%s], dt= %s %s\n", dt->ts, dt->d, dt->h);
#endif
}
eStatus setValue(const char *s, item_t *ids)
{
    char *endptr;
    eStatus status= failed;
    ids->type = item::UNKNOWN;

    if (NULL != s && NULL != ids)
    {
        errno = 0;
        *(ids->values.nVal) = strtoull(s, &endptr, 10);

        if ((errno == ERANGE &&
                *(ids->values.nVal) == ULLONG_MAX) ||
                (errno != 0 && *(ids->values.nVal) == 0))
        {
            syslog(LOG_ERR, "strtoull: out of range");
        }
        else {
            if (endptr == s)
            {
                char *p= *(ids->values.sVal);

                for (uint32_t i=0; i<ITEM_LEN; i++, p++, s++)
                {
                    *p = *s;
                    if ('\0' == *p ) break;
                }
                ids->type = item::ALPHA;
            }
            else {
                ids->type = item::NUM;
            }
            status = success;
        }
    }
    return status;
}
/*!
 *  9600 bps, 8 bits, pas de parité, 1 stop
 *
 */
void openSerial(edf_teleinfo_t *t, const char *device)
{
    syslog(LOG_DEBUG, "=> openSerial");

    t->fd = open (device, O_RDWR|O_NOCTTY);
    if (0 > t->fd)
    {
        syslog(LOG_ERR, "cannot open device %s: %s",
               device,
               strerror(errno));
    }
    else if (!isatty(t->fd)) {
        syslog(LOG_ERR, "device [%s] is not a tty", device);
        close(t->fd);
    }
    else {
        if (NULL == (t->device = strdup(device)))
        {
            syslog(LOG_ERR, "memory alloc failed");
            close(t->fd);
        }
        else {
            tcgetattr(t->fd, &t->oldOpt);
            tcgetattr(t->fd, &t->curOpt);
            t->curOpt.c_iflag = IGNBRK|IGNPAR;
            t->curOpt.c_oflag = 0;
            t->curOpt.c_cflag = B9600|CS8|CREAD|CLOCAL;
            t->curOpt.c_lflag = 0;
            t->curOpt.c_cc[VTIME] = 80;
            t->curOpt.c_cc[VMIN]  = 1;
            tcflush(t->fd, TCIFLUSH);
            tcsetattr(t->fd, TCSANOW, &t->curOpt);
            syslog(LOG_DEBUG,"Teleinfo serial line %s opened", t->device);
            t->status= opened;
        }
    }
    syslog(LOG_DEBUG, "<= openSerial");
}
void printSerialOptions(edf_teleinfo_t *t)
{
    syslog(LOG_DEBUG, "=> serialOptions");
    syslog(LOG_DEBUG, "old termios settings");
    syslog(LOG_DEBUG,"c_iflag= %u", t->oldOpt.c_iflag);
    syslog(LOG_DEBUG,"c_oflag= %u", t->oldOpt.c_oflag);
    syslog(LOG_DEBUG,"c_lflag= %u", t->oldOpt.c_lflag);
    for(unsigned int i=0; i<NCCS; i++)
    {
        syslog(LOG_DEBUG,"c_cc[%u]= %02X", i, t->oldOpt.c_cc[i]);
    }

    syslog(LOG_DEBUG, "current termios settings");
    syslog(LOG_DEBUG,"c_iflag= %u", t->curOpt.c_iflag);
    syslog(LOG_DEBUG,"c_oflag= %u", t->curOpt.c_oflag);
    syslog(LOG_DEBUG,"c_lflag= %u", t->curOpt.c_lflag);
    for(unsigned int i=0; i<NCCS; i++)
    {
        syslog(LOG_DEBUG,"c_cc[%u]= %02X", i, t->curOpt.c_cc[i]);
    }
    syslog(LOG_DEBUG, "<= serialOptions");
}
eStatus findFrame(edf_teleinfo_t *t)
{
    syslog(LOG_DEBUG, "=> findFrame");
    eStatus status = failed;
    char c, ch[1];

    int count = read(t->fd, ch, 1);
    if (0 > count)
    {
        syslog(LOG_ERR, "Error while reading EDF teleinfo: %s",
               strerror(errno));
        syslog(LOG_DEBUG, "<= findFrame");
        return status;
    }
    do {
        if (0x04 == (c = ch[0]))
        {
            syslog(LOG_ERR, "Line interrup occured");
            return status;
        }
        count = read(t->fd, ch, 1);
        if (0 > count)
        {
            syslog(LOG_ERR, "Error while reading EDF teleinfo: %s",
                   strerror(errno));
            syslog(LOG_DEBUG, "<= findFrame");
            return status;
        }
#ifdef DEBUG
        printf("findFrame: reading char: [0x%x]\n", (unsigned char)ch[0]);
#endif
     }
    while(!(ch[0] == 0x02 && c == 0x03));
    status = success;

    syslog(LOG_DEBUG, "<= findFrame");
    return status;
}
eStatus readFrame(edf_teleinfo_t *t, char *frameBuf, int size, int *count)
{
    syslog(LOG_DEBUG, "=> readFrame");
    eStatus status  = failed;
    char *p         = frameBuf;
    *count          = 0;
    char ch[1];

    int res = read(t->fd, ch, 1);
    do {
        if (0 > res)
        {
            syslog(LOG_ERR, "Error while reading EDF teleinfo: %s",
                   strerror(errno));
            syslog(LOG_DEBUG, "<= readFrame");
            return status;
        }
        else {
            if (0x04 == ch[0])
            {
                syslog(LOG_ERR, "Line interrup occured");
                return status;
            }
            *count += res;
            if (*count < size)
            {
                memcpy((void *)p, ch, 1);
                p+= res;
            }
            else {
                syslog(LOG_ERR, "buffer overflow while reading frame content");
                return status;
            }
        }
        res = read(t->fd, ch, 1);
     } while(ch[0] != 0x03);

    res = read(t->fd, ch, 1);
    if (0 > res)
    {
        syslog(LOG_ERR, "Error while reading EDF teleinfo: %s",
               strerror(errno));
        syslog(LOG_DEBUG, "<= readFrame");
        return status;
    }
    if (0x04 == ch[0])
    {
        syslog(LOG_ERR, "Line interrup occured");
        return status;
    }
    if (ch[0] != 0x02)
    {
        syslog(LOG_ERR, "invalid char [0x%x]", (unsigned char)ch[0]);
        return status;
    }
    status = success;
    syslog(LOG_DEBUG, "<= readFrame");
    return status;
}

/*!
 * read the EDF teleinformation sent over the serial line
 */
item_t *createIds(char *frameBuf, int size, int *count)
{
    syslog(LOG_DEBUG, "=> createIds");

    item_t *ids;
    int len = 0;
    char *t = frameBuf;
    *count  = 0;

#ifdef DEBUG
    printf("createIds: reading buffer content\n");
    for(int i= 0; i<size; i++)
    {
       printf("createIds: char[%i]= %#x\n", i, *(frameBuf+i));
    }
#endif

    for (int i=0; i<size; i++, t++)
    {
        if (0x0a == *t)
        {
            ++len;
        }
    }

    if (NULL == (ids = (item_t *)malloc(sizeof(item_t)*len)))
    {
        syslog(LOG_ERR, "cannot allocate [%i] items in memory", len);
    }
    else {
        char *r;
        item_t *p = ids;
        t= frameBuf;
        for (int i=0; i<size; i++, t++)
        {
            if (0x0a == *t)
            {
                r= t;
            }
            else if (0x0d == *t)
            {
                char *s= ++r;
                unsigned char cs = (unsigned char)*r;
                while (0x20 != *s)
                {
                    ++s;
                    cs += (unsigned char)*s;
                }
                cs += (unsigned char)*s;
                if (NULL == (p->id = (char *)malloc(sizeof(char)*(s-r))))
                {
#ifdef ENV_64BIT
                    syslog(LOG_ERR, "cannot allocate [%ld] items in memory", (s-r));
#else
                    syslog(LOG_ERR, "cannot allocate [%d] items in memory", (s-r));
#endif
                }
                else {
                    p->cs = cs;
                    memcpy((void *)p->id, r, s-r);
                    *(p->id+(s-r))= '\0';
#ifdef DEBUG
                    printf("createIds: id[%i]= [%s], cs= [%#x]\n", *count, p->id, p->cs);
#endif
                    ++*count;
                    ++p;
                }
            }
        }
    }
    *count = len == *count ? *count : -1;
    syslog(LOG_DEBUG, "<= createIds");
    return ids;
}
eStatus addValues(char *frameBuf, int size, item_t *ids, int count)
{
    syslog(LOG_DEBUG, "=> addValues(buf[%i], count= %i)", size, count);
    eStatus status  = success;
    char *r         = NULL;
    char *s         = NULL;
    char *t         = frameBuf;
    int len         = 0;

    for (int i=0; i<size; i++, t++)
    {
        if (0x0a == *t)
        {
            r= s= NULL;
        }
        else if (0x20 == *t)
        {
            if (NULL == r)
            {
                r = t;
            }
            else {
                s = t;
            }
        }
        else if (0x0d == *t) {
            unsigned char cs = (unsigned char)*(++r);
            char *p = r;
            do {
                 ++p;
                cs += (unsigned char)*p;
            } while (p<s);
            *s++= '\0';
            if (success != (status = checksumCmp(ids->cs+cs, (unsigned char)*s)))
            {              
                break;
            }
            setValue(r, ids);
#ifdef DEBUG
            if (item_t::ALPHA == ids->type)
            {
                printf("addValues: alpha id[%i]= [%s], cs= [%#x], value= [%s]\n",
                       len, ids->id, ids->cs, *ids->values.sVal);
            }
            else if (item_t::NUM == ids->type) {
                printf("addValues: num id[%i]= [%s], cs= [%#x], value= [%llu]\n",
                       len, ids->id, ids->cs, *ids->values.nVal);
            }
#endif
            if (item_t::UNKNOWN == ids->type) {
                 syslog(LOG_ERR, "unknown value type : id[%i]= [%s]", len, ids->id);
            }
            ++ids;
            ++len;
        }
    }
    status = status == success ? count == len ? success : failed : status;
    syslog(LOG_DEBUG, "<= addValues(%i)", len);
    return status;
}
FILE *createCsvFile(item_t *ids, int count)
{
    struct stat sb;
    item_t *p= ids;

    if (-1 == stat(CSV_FILE_PATH, &sb))
    {
        if (NULL == (gp_csv = fopen(CSV_FILE_PATH, "a")))
        {
            syslog(LOG_ERR, "cannot open [%s]", CSV_FILE_PATH);
        }
        else {
            fprintf(gp_csv, "##;##\r\ntimestamp;date time");
            for (int i= 0; i<count; i++, p++)
            {
                fprintf(gp_csv, ";%s", p->id);
            }
            fprintf(gp_csv, "\r\n");
        }
    }
    else {
        if (NULL == (gp_csv = fopen(CSV_FILE_PATH, "a")))
        {
            syslog(LOG_ERR, "cannot open [%s]", CSV_FILE_PATH);
        }
    }
    return gp_csv;
}
void appendCsvFile(FILE *csv, dateTime_t *dt, item_t *ids, int count)
{
    item_t *p= ids;
    fprintf(csv, "%s; %s %s", dt->ts, dt->d, dt->h);
    for (int i= 0; i<count; i++, p++)
    {
        if (item_t::ALPHA == p->type)
        {
            fprintf(csv, ";%s", *p->values.sVal);
        }
        else if (item_t::NUM == p->type) {
            fprintf(csv, ";%llu", *p->values.nVal);
        }
    }
    fprintf(csv, "\r\n");
}

item_t *findId(const char *s, item_t *ids, int count)
{
    item_t *p = NULL;
    for (int i= 0; i<count; i++, ids++)
    {
        if (0 == strcmp(s, ids->id))
        {
            p = ids;
            break;
        }
    }
    return p;
}
eStatus appendMySql(dateTime_t *dt, item_t *ids, int count)
{
    char query[255], data[512];
    char *p = data;
    const char **s = MYSQL_FIELDS;
    MYSQL mysql;
    item_t *it;

    if(!mysql_init(&mysql))
    {
        syslog(LOG_ERR, "Erreur: Initialisation MySQL impossible !") ;
        return failed;
    }
    if(!mysql_real_connect(&mysql, MYSQL_HOST, MYSQL_LOGIN,    MYSQL_PWD, MYSQL_DB, 0, NULL, 0))
    {
        syslog(LOG_ERR, "Erreur connection %d: %s \n", mysql_errno(&mysql), mysql_error(&mysql));
        return failed;
    }

    p = (char *)memset(data, '\0', sizeof(data));
    strcpy(p, "'");
    strcat(p, (const char *)dt->d);
    strcat(p, " ");
    strcat(p, (const char *)dt->h);
    strcat(p, "','");
    if (NULL != (it= findId(*s, ids, count)))
    {
        if (item_t::ALPHA == it->type)
        {
// bmdOnline work around
            if (0 == strcmp("OPTARIF", it->id))
            {
                strcat(p, "BBR");
            }
            else {
                strcat(p, (const char *)*it->values.sVal);
            }
        }
        else if (item_t::NUM == it->type) {
            sprintf(p+strlen(p), "%llu",*it->values.nVal);
        }
        strcat(p, "'");
    }
    else{
        strcat(p, " '");
    }
    ++p; ++s;
    while (NULL != *s)
    {
        if (NULL != (it= findId(*s, ids, count)))
        {
            strcat(p, ",'");
            if (item_t::ALPHA == it->type)
            {
// bmdOnline work around
                if (0 == strcmp("OPTARIF", it->id))
                {
                    strcat(p, "BBR");
                }
                else {
                    strcat(p, (const char *)*it->values.sVal);
                }
            }
            else if (item_t::NUM == it->type) {
                sprintf(p+strlen(p), "%llu",*it->values.nVal);
            }
            strcat(p, "'");
        }
        else{
            strcat(p, ",NULL");
        }
        ++p; ++s;
    }
    sprintf(query, "INSERT INTO %s VALUES (%s)", MYSQL_TABLE, data);

    if(mysql_query(&mysql, query))
    {
        syslog(LOG_ERR, "Erreur INSERT %d: \%s \n", mysql_errno(&mysql), mysql_error(&mysql));
        mysql_close(&mysql);
        return failed ;
    }
#ifdef DEBUG
    else printf("MySql request ok\n");
#endif
    mysql_close(&mysql);
    return success;
}

int main()
{
    openlog("edf_tli", LOG_PID, LOG_USER);
    syslog(LOG_DEBUG, "=> main");
    syslog(LOG_INFO, "Version %s", VERSION);
    signal(SIGINT, INThandler);

    char buf[BUFFER_SIZE];
    int count, len;
#ifdef DEBUG
    item_t *p;
#endif
    dateTime_t dt;
    execStatus_t execStat;

    openSerial(&g_tli, SERIAL_DEVICE);
    if (opened != g_tli.status)
    {
        syslog(LOG_ERR, "fails to open serial port [%s]", SERIAL_DEVICE);
        goto END;
    }
    if (success != findFrame(&g_tli))
    {
        syslog(LOG_ERR, "No frame found");
        goto END;
    }
    if (success != readFrame(&g_tli, buf, BUFFER_SIZE, &len))
    {
        syslog(LOG_ERR, "Cannot read buffer frame content");
        goto END;
    }
    syslog(LOG_DEBUG, "[%i] char read", len);
    gp_ids = createIds(buf, len, &count);
    if (0 >= count)
    {
        syslog(LOG_ERR, "No item found");
        goto END;
    }
    if (NULL == (gp_csv = createCsvFile(gp_ids, count)))
    {
        syslog(LOG_ERR, "cannot create cvs file");
        goto END;
    }  
    while (0 == 0) {
        if (0 == execStat.idx%0x400)
        {
            printf("------------  LOOP : %i ------------------\n", execStat.idx);
        }
        if (success != readFrame(&g_tli, buf, BUFFER_SIZE, &len))
        {
            syslog(LOG_ERR, "Cannot read buffer frame content");
            goto END;
        }
        syslog(LOG_DEBUG, "[%i] char read", len);
        if (success != (execStat.status = addValues(buf, len, gp_ids, count)))
        {
            if (csFailed == execStat.status)
            {
                if (0 == execStat.idx)
                {
                    ++execStat.errorCount;
                }
                else if (execStat.idxError == execStat.idx-1)
                {
                    ++execStat.idxError;
                    if (++execStat.errorCount > MAX_RETRY)
                    {
                        printf("[%i] successive checksum arrors\n", MAX_RETRY);
                        syslog(LOG_ERR, "[%i] successive checksum arrors", MAX_RETRY);
                        goto END;
                    }
                }
                else {
                    execStat.errorCount = 0;
                }
            }
            else{
                printf("cannot read values\n");
                syslog(LOG_ERR, "cannot read values");
                goto END;
            }
        }
        if (success == execStat.status)
        {
#ifdef DEBUG
            p= gp_ids;
            for (int i= 0; i<count; i++)
            {
                if (item_t::ALPHA == p->type)
                {
                    printf("main :alpha id[%i, %s]= %s\n",
                           i, p->id, *p->values.sVal);
                }
                else if (item_t::NUM == p->type) {
                    printf("main :num id[%i, %s]= %llu\n",
                           i, p->id, *p->values.nVal);
                }
                else {
                     printf("main :unknown value type id[%i]= %s\n", i, p->id);
                }
                ++p;
            }
    #endif
            // Archive freq: one time series per min
//            if (0 == execStat.idx%0x01E)
//            {
                getDateTime(&dt);
//                appendCsvFile(gp_csv, &dt, gp_ids, count);
                appendMySql(&dt, gp_ids, count);
//            }
        }
        execStat.idx = ++execStat.idx < UINT32_MAX ? execStat.idx : 0;
    }

END:
    INThandler(0);
}

Paramétres du code source

  • SERIAL_DEVICE="/dev/ttyUSB0": spécifie le port série qui reçoit les trames de téléinformation. 
  • CSV_FILE_PATH="/home/pi/teleinfo/log/edf_tli.csv": définit l'emplacement d'un fichier pour sauvegarder les données de téléinformation dans le format CSV (Comma Separated Value). 
  • MYSQL_HOST="192.168.1.xxx": adresse de la machine qui héberhe le serveur MySQL. 
  • MYSQL_DB="teleinfo": nom de la base contenant les données MySQL de téléinformation. 
  • MYSQL_TABLE="xxx": nom de la table mySQL contenant les données de téléinformation 
  • const char * MYSQL_LOGIN="yyy": identifiant pour accéder à la base de données MySQL. 
  • const char * MYSQL_PWD      = "zzz": mot de passe permattant l'accès à la base de données MySQL.
  • const char *MYSQL_FIELDS[] ={"ADCO","OPTARIF","ISOUSC","BASE","HCHC","HCHP","EJPHN","EJPHPM", "BBRHCJB","BBRHPJB","BBRHCJW","BBRHPJW","BBRHCJR","BBRHPJR",  "PEJP","PTEC","DEMAIN","IINST","ADPS","IMAX",  "IINST1","IINST2","IINST3","IMAX1","IMAX2","IMAX3", "PMAX","PAPP","HHPHC","MODETAT","PPOT", NULL}: difinition de tous les  champs  qui seront recherchés dans les trames de téléinformation.

Procèdure de compilation

Ne pas oublier d'inclure les dépendances à la librairie cliente MySQL comme décrit dans le contenu du fichier Makefilr ci dessous.

 $ cat Makefile

CXXFLAGS    = -g -Wall ${INCLUDES}
OBJS        = main.o
SRC        = main.cpp
PROG        = teleInfo
INCLUDES    = -I /usr/local/include
LIBS        = -L/usr/lib/mysql -lmysqlclient

all:        $(PROG)

${PROG}:    $(OBJS)

${OBJS}:;    ${CXX}  $(INCLUDES) -c  ${SRC}
        ${CXX} -o ${PROG} ${OBJS} ${LIBS}

clean:;        $(RM) -f $(PROG) core *.o