dsm2sql: data acquisite uit een slimme meter (Raspberry PI in C en MariaDB)

Begin juli 2016 werd er in mijn huis een zogenaamde "slimme meter" geïnstalleerd. Voor mij hét moment om de data uit deze meter te gaan opslaan en deze te analyseren. Want zeg nou zelf, als je dit doet:

Analyseren kan best gebeuren door de data te visualiseren. Een voorbeeld daarvan zijn twee grafieken die het electriciteit- en gasverbruik in een week laten zien:
R plot - electriciteits verbuik R plot - gas verbruik

Een slimme meter is eigenlijk niet slim, met "slim" wordt slechts bedoeld dat hij op afstand uitgelezen kan worden door de netwerk beheerder. Vanaf het begin heeft men bij de specificatie van deze meters voorzien dat deze een voorziening moeten krijgen om ook lokaal uitgelezen te kunnen worden. Deze voorziening is de zogenoemde P1 poort die op elke slimme meter zit. De specificatie van poort P1 is open, dat betekent dat je de precieze werking van deze poort kunt lezen in een document. Vanaf het begin is de specificatie periodiek bijgesteld. Dit heeft geleid tot verschillende versies. Alle specificaties zijn te vinden op de website van Netbeheer Nederland. Mijn meter bleek te voldoen aan versie 4.2, beschreven in een document met de titel: "Dutch Smart Meter Requirements v4.2.2 Final P1 (14 maart 2014)". Recent, juli 2016, is versie 5 verschenen.

Bij het zoeken op Internet naar "slimme meter uitlezen" vond ik sites waar ik goede ideeën heb mogen lezen. De site van Ge Janssen wordt vaak geciteerd. De domoticx site refereert netjes aan de DSMR standaarden. En Tweakblogs laat enkele goed bruikbare grafieken zien.

Ik wil voortbouwen op bovenstaande sites, zal de slimme meter uitlezen met een Raspberry PI, maar zal de software realiseren in de programmeertaal C. Waarom C? Eigenlijk alleen maar omdat het leuk is en kan, maar wellicht ook omdat langs deze weg het uitlezen gerealiseerd kan worden door één programma op een Raspberry PI te installeren.

Ik stel de software, dsm2sql, onder de GPL beschikbaar.

Ik ga er verder van uit de je behoorlijk vertrouwd bent met werken onder Linux op een Raspberry PI of op een ander op Raspbian (= Debian) gebaseerd systeem. Ik zal bijvoorbeeld schrijven "installeer dit programma" en ga er dan vanuit dat je weet hoe dat met "aptitude" kan gebeuren. Ook ga ik er vanuit dat je met een MariaDB-server kunt omgaan.

Kabel, P1 - GPIO

Om de P1 poort van de "slimme meter" met de Raspberry PI te verbinden is een kabel nodig. De P1 poort wordt bepaald door een RJ12 connector, op de Raspberry PI gebruik ik de GPIO interface. Hierdoor is een 3-aderige kabel nodig. Als alternatief kun de P1 poort op een USB connector van de Raspberry PI aansluiten, de kabel die hiervoor nodig is is te koop. In beide gevallen dient het data signaal geïnverteerd te worden. Bij de USB-kabel kan dit door middel van een software instelling.

In aanvulling op de inhoud van de andere websites geef ik hieronder meteen het complete aansluitschema van de benodigde kabel tussen P1 en de GPIO van de Raspberry PI. De inverter, opgebouwd met een BC547 transistor en drie weerstanden (2× 10kΩ, 1× 1kΩ), is op een GPIO Header for Raspberry Pi HAT - 2x20 Short Female Header gesoldeerd. Deze header kun je op de GPIO connector van de Raspberry PI steken om de elektrische verbindingen te maken.

kabel rpi rpi_detail

Doordat pin 2, CTS, van de slimme meter met pin 2, "Power 5V" van de GPIO is verbonden, zal de slimme meter elke 10 seconden een telegram versturen. Om de verbinding te controleren, kun je bijvoorbeeld het programma "cu" gebruiken. Het GPIO UART device is /dev/ttyAMA0, gebruik je een USB kabel dan moet je een ander device kiezen, b.v. /dev/ttyUSBx! Let op: om van de RS232-poort te kunnen lezen moet de gebruiker (user) lid zijn van de groep (group) "dialout"

luc@nekum: ~$ cu -l /dev/ttyAMA0 -s 115200 --parity=none

In de shell zul je nu elke 10 seconden een zogenoemd telegram voorbij zien komen, hieronder een voorbeeld van een telegram uit mijn slimme meter, waarbij ik de inhoud van de regels met daarin 0-0:96.1.1 en 0-1:96.1.0 veranderd heb vanwege privacy redenen.

/KFM5KAIFA-METER
1-3:0.2.8(42)
0-0:1.0.0(160708202351S)
0-0:96.1.1(idvanmijnslimmeelektriciteitsmeter)
1-0:1.8.1(000008.218*kWh)
1-0:1.8.2(000005.165*kWh)
1-0:2.8.1(000002.047*kWh)
1-0:2.8.2(000063.437*kWh)
0-0:96.14.0(0002)
1-0:1.7.0(00.201*kW)
1-0:2.7.0(00.000*kW)
0-0:96.7.21(00001)
0-0:96.7.9(00001)
1-0:99.97.0(1)(0-0:96.7.19)(000101000011W)(2147483647*s)
1-0:32.32.0(00000)
1-0:52.32.0(00000)
1-0:72.32.0(00000)
1-0:32.36.0(00000)
1-0:52.36.0(00000)
1-0:72.36.0(00000)
0-0:96.13.1()
0-0:96.13.0()
1-0:31.7.0(000*A)
1-0:51.7.0(001*A)
1-0:71.7.0(000*A)
1-0:21.7.0(00.022*kW)
1-0:41.7.0(00.042*kW)
1-0:61.7.0(00.137*kW)
1-0:22.7.0(00.000*kW)
1-0:42.7.0(00.000*kW)
1-0:62.7.0(00.000*kW)
0-1:24.1.0(003)
0-1:96.1.0(idvanmijnslimmeagasmeter)
0-1:24.2.1(160708200000S)(00003.407*m3)
!6833

Telegram en MariaDB

De inhoud van het telegram is beschreven in de Dutch Smart Meter Requirements v4.2.2 Final P1 (14 maart 2014). We hoeven ons nu alleen af te vragen in welke waarden we geïnteresseerd zijn en waar we die gaan bewaren?

De laatste vraag wordt net als op veel andere sites beantwoord met MariaDB. Dit is een relationele database server die grote hoeveelheden data aan kan, maar die ook op een Raspberry PI kan draaien. In deze database server zullen we een database aanmaken en voorzien van een tabel waarin we de verbruiksgegevens van de slimme meter zullen opslaan.

Stel nu dat we het hele telegram (in ASCII formaat) elke 10 seconden zouden opslaan. Hoeveel groot zou onze database dan worden? Het telegram is 835 bytes lang, in een jaar zitten 365×24×60×6 perioden van 10 seconden. Per jaar bewaren we dan 365×24×60×6×835 = 2.633.25.000 = 2,6 GByte. Waarbij ik geen rekening gehouden heb met de grootte van bijvoorbeeld drijvende komma getallen in MariaDB. Deze hoeveelheid data is veel te groot en onnodig. Om de datahoeveelheid te beperken tot bijvoorbeeld een acceptabele 4,9 MByte/jaar slaan we slechts een deel van het telegram, 49 bytes, elke 5 minuten op. De grootte, formaten, details en de betekenis van de velden staan in onderstaande tabel, waarbij we de relaties tussen de inhoud van het telegram en de velden in de database transparant laten zien:

Dutch Smart Meter Requirements v4.2.2 Final P1 (14 maart 2014) MariaDB tabel "emeter"
OBIS Betekenis Formaat # Veld
0-0:1.0.0 datumtijd + zomer (S)/wintertijd(W), Format: YYMMDDhhmmssX datetime 8 e_dattijd
1-0:1.8.1 totale verbruik tarief 1 (kWh), Format: YYYYYY.YYY float(9,3) 4 e_ver_t1
1-0:1.8.2 totale verbruik tarief 2 (kWh), Format: YYYYYY.YYY float(9,3) 4 e_ver_t2
1-0:2.8.1 totale teruglevering tarief 1 (kWh); Format: YYYYYY.YYY float(9,3) 4 e_ter_t1
1-0:2.8.2 totale teruglevering tarief 2 (kWh); Format: YYYYYY.YYY float(9,3) 4 e_ter_t2
0:96.14.0 actueel tarief; 1= tarief 1, 2 = tarief 2 unsigned tinyint(1) 1 t
1-0:1.7.0 actueel verbruik (+P) (kW), Format: YY.YYY float(5,3) 4 e_ver_mom
1-0:2.7.0 actuele teruglevering (-P) (kW), Format: YY.YYY float(5,3) 4 e_ter_mom
0-1:24.2.1 datumtijd + zomer (S)/wintertijd(W) + gas verbruik (m3)
Format: YYMMDDhhmmssX + YYYYY.YYY
datetime
float(8,3)
8
4
g_dattijd
g_ver
  ID (auto_increment) unsigned int (11) 4 id
    TOTAAL Bytes 49

Om het telegram op te slaan is in MariaDB een database "energie" aangemaakt met daarin een tabel "emeter". Van deze laatste tabel staat hieronder de definitie. Naast de benodigde velden is een "id" toegevoegd, deze wordt gebruikt als de unieke aanduiding voor een rij in de tabel, de "primary key". Voor "id" is een "unsigned int", 32-bits veld, gekozen, waardoor deze tot 4.294.967.296 of te wel bijna 4,3 miljard kan lopen, genoeg voor 4.294.967.296 rijen / (12×24×365) rijen/jaar = 41000 jaar opslag.

mariadb> desc emeter;
+-----------+---------------------+------+-----+---------+----------------+
| Field     | Type                | Null | Key | Default | Extra          |
+-----------+---------------------+------+-----+---------+----------------+
| id        | int(11) unsigned    | NO   | PRI | NULL    | auto_increment |
| e_dattijd | datetime            | YES  |     | NULL    |                |
| e_ver_t1  | float(9,3)          | YES  |     | NULL    |                |
| e_ver_t2  | float(9,3)          | YES  |     | NULL    |                |
| e_ter_t1  | float(9,3)          | YES  |     | NULL    |                |
| e_ter_t2  | float(9,3)          | YES  |     | NULL    |                |
| t         | tinyint(1) unsigned | YES  |     | NULL    |                |
| e_ver_mom | float(5,3)          | YES  |     | NULL    |                |
| e_ter_mom | float(5,3)          | YES  |     | NULL    |                |
| g_dattijd | datetime            | YES  |     | NULL    |                |
| g_ver     | float(8,3)          | YES  |     | NULL    |                |
+-----------+---------------------+------+-----+---------+----------------+
11 rows in set (0.01 sec)

mariadb>

De naaam van de database kan willekeurig gekozen worden, de naam van de tabel en de namen van de velden zijn vast gekozen. Wil je die veranderen dan zul je de C-code moeten veranderen.

Beschrijving dsm2sql

Het programma dsm2sql interfaced tussen de P1-poort en een MariaDB server.

dsm2sqlstructuur

Het gedraagt zich precies zoals het MariaDB-client programma, zoals b.v. MariaDB-client op Raspberry PI. Het programma leest een telegram dat op poort P1 verschijnen, zet deze om in een MariaDB query en stuurt de query naar een MariaDB-server. Deze laatste wordt geidentificeerd door het vijf-tal: host, port, username, password en database naam, zoals bij de MariaDB-client. Gebruik je de MariaDB configuratie file $HOME/.my.cnf met daarin de toegangsgegevens van een MariaDB-server, dan geldt dit bestand ook voor dsm2sql. Dit impliceert dat de MariaDB server NIET PERSÉ op de Raspberry PI hoeft te draaien. Hij mag ook op je NAS of zelfs extern draaien. Je moet natuurlijk wel schrijfrechten hebben. Hieronder vind je een voorbeeld van een .my.cnf bestand dat drie van de vijf benodigde gegevens bevat:

luc@nekum:~$ cat ~/.my.cnf
[client]
password=mypassword
user=myusername
host=localhost
luc@nekum:~$

Ik heb ervoor gekozen de MariaDB-server ook op de Raspberry PI te draaien, heb je toegang tot een MariaDB-server elders, b.v. op een NAS of "in de cloud" en wil je die gebruiken geef, dan de toegangsgegevens hiervan in de .my.cnf in. Dat is alles. Let op dat we er voor willen zorgen dat dsm2sql elke 5 minuten een query afgeeft, je MariaDB-server moet daarom continue toegankelijk zijn.

In vergelijking met de MariaDB-client heeft dsm2sql 2 extra opties: de rs232 poort, -c, en de debug, -d, vlag. Voor de GPIO poort op de Raspberry PI is de rs232 poort ttyAMA0. Als de debug-vlag is gezet zal dsm2sql precies één telegram ontvangen en hierna de resulterende query als uitvoer geven. Deze query wordt in dit geval niet aan de MariaDB server door gegeven, maar slechts naar stdout gestuurd.

luc@nekum:~ $ ./dsm2sql/dsm2sql -c ttyAMA0 -d energie
[2016-08-14 10:31:50] INSERT INTO emeter (e_dattijd, e_ver_t1,e_ver_t2,e_ter_t1,e_ter_t2,t, e_ver_mom,e_ter_mom,g_dattijd,g_ver) VALUES ("2016-08-14 10:31:47",000098.091,000029.806,000152.162,000396.454,0001,00.995,00.000,"2016-08-14 10:00:00",00024.439);
luc@nekum:~ $

Om dsm2sql elke 5 minuten te draaien gebruik ik "cron". Door het aanmaken van onderstaande regel, na ingeven van het commando "crontab -e" wordt "dsm2sql" elke 5 minuten aangeoepen, tevens worden meldingen bewaard in een log-bestand.

# m h dom mon dow command
*/5 * * * * /home/luc/dsm2sql/dsm2sql -c ttyAMA0 energie >> /home/luc/dsm2sql.log 2>&1

Door met de MariaDB-client de MariaDB-server te benaderen kun je de "emeter" tabel bekijken en controleren of dsm2sql zijn werk doet.

Het programma dsm2sql bestaat uit 3 delen: rs232.c, db.c en dm.c.

  1. rs232.c beheert de seriële poorten, het opent en sluit deze en leest er van. Ik heb hiervoor gebruik gemaakt van het werk van Teunis van Beelen, dat onder GPL ter beschikking is gesteld. Let op: om van de RS232-poort te lezen moet de gebruiker van dsm2sql lid zijn van de groep "dialout"
  2. dm.c leest via rs232.c de telegrammen in en zet deze om in een query. De in het telegram aanwezige CRC wordt gecontroleerd. Het vinden van het juiste algoritme heeft enig zoek werk gekost, het DSMR document is hier vaag over. Navraag bij netbeheernederland.nl leerde dat het het CRC-16 algoritme op de site van Lammert Bies is. Telegrammen met een ongeldige CRC worden als zodanig gemeld. De data in het telegram wordt in de bewerkingen niet gewijzigd. Het programma dm.c is zo ontworpen dat de ASCII waarden die in het telegram zitten, één op één terecht komen in de query. Pas de MariaDB-server zal de betreffende ASCII waarde omzetten in een "float". Dit principe houdt de data schoon, en gooit niks weg, op het laatste moment kun je zo beslissen of je de data wil veranderen of niet.
  3. db.c bevat main(). Het leest de MariaDB opties in en zal uiteindelijk de query naar de MariaDB-server uitvoeren. Het is mogelijk dat dsm2sql zijn werk begint terwijl er een telegram uitgezonden wordt. Er zal dan een "half telegram" ontvangen worden. De CRC controle zal het bericht ongeldig doen verklaren, maar het is goed om zo snel mogelijk een nieuw telegram te ontvangen. Daarom vind je in de main() functie twee aanroepen van "DM_rcv_telegram()" achter elkaar.

Installatie op een Rasperry PI

T.o.v. Raspbian Jessie Lite Minimal image based on Debian Jessie
Version: May 2016
Release date: 2016-05-27
login met pi en raspberrypi

root@nekum:~# aptitude update
root@nekum:~# aptitude dist-upgrade
root@nekum:~# /etc/init.d/ssh start

Log files in RAM

# http://raspberrypi.stackexchange.com/questions/169/how-can-i-extend-the-life-of-my-sd-card

root@nekum:~# vi /etc/default/tmpfs
root@nekum:~# vi /etc/fstab
root@nekum:~# vi /etc/init.d/prepare-dirs
root@nekum:~# update-rc.d prepare-dirs defaults 01 99
root@nekum:~# reboot

Controle (mount)

root@nekum:~# mount | fgrep tmpfs
devtmpfs on /dev type devtmpfs (rw,relatime,size=469544k,nr_inodes=117386,mode=755)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
tmpfs on /var/spool/cups type tmpfs (rw,noatime,size=102400k,mode=755)
tmpfs on /var/cache/apt/archives type tmpfs (rw,nosuid,nodev,noexec,relatime,size=102400k,mode=755)
tmpfs on /var/log type tmpfs (rw,noatime,size=20480k,mode=755)
tmpfs on /var/spool/cups/tmp type tmpfs (rw,noatime,mode=755)
root@nekum:~#

Installatie MariaDB

root@nekum:~# aptitude install mariadb-client mariadb-server libmariadbclient-dev

geef het root-wachtwoord op zodra dit gevraagd wordt.

Optimaliseren van MariaDB

Een eerste stap om MariaDB te optimaliseren voor een kleine computer zoals de Raspberry PI is het vervangen van de default configuratie my.cnf door een die beter geschikt is voor een computer met minder resources. sudo mv /etc/mariadb/my.cnf /etc/mariadb/my.cnf.bak sudo cp /usr/share/doc/mariadb-server-5.5/examples/my-small.cnf /etc/mariadb/my.cnf Vervolgens passen we de grootte van de query cache aan. MariaDB kan de queries in een cache houden totdat de data is veranderd, waarmee sneller resultaten worden opgeleverd. Deze cache grootte verklein ik tot 8Mbyte. Open /etc/mariadb/my.cnf, zoek de sectie [MariaDB] en voeg de regel "query_cache_size = 8M" hier aan toe.

root@nekum:~# vi /etc/mariadb/my.cnf

en voeg deze regel toe in de sectie [MariaDBd]: "query_cache_size = 8M". Herstart MariaDB zodat deze instellingen worden geëffectueerd.

root@nekum:~# service MariaDB restart

control (4) MariaDB

luc@nekum:/var/log $ fgrep MariaDB syslog
Aug 10 12:29:04 nekum systemd[1]: Starting LSB: Start and stop the MariaDB database server daemon...
Aug 10 12:29:10 nekum mariadb[610]: Starting MySQL database server: mariadbd . . ..
Aug 10 12:29:37 nekum mariadb[610]: Checking for tables which need an upgrade, are corrupt or were
Aug 10 12:29:37 nekum mariadb[610]: not closed cleanly..
Aug 10 12:29:37 nekum systemd[1]: Started LSB: Start and stop the mariadb database server daemon.
Aug 10 12:29:38 nekum /etc/mariadb/debian-start[963]: Upgrading mariadb tables if necessary.
Aug 10 12:29:39 nekum /etc/mariadb/debian-start[970]: /usr/bin/mariadb_upgrade: the '--basedir' option is always ignored
Aug 10 12:29:39 nekum /etc/mariadb/debian-start[970]: Looking for 'mariadb' as: /usr/bin/mariadb
Aug 10 12:29:39 nekum /etc/mariadb/debian-start[970]: Looking for 'mariadbcheck' as: /usr/bin/mariadbcheck
Aug 10 12:29:39 nekum /etc/mariadb/debian-start[970]: This installation of mariadb is already upgraded to 5.5.50, use --force if you still need to run mariadb_upgrade
Aug 10 12:29:39 nekum /etc/mariadb/debian-start[982]: Checking for insecure root accounts.
Aug 10 12:29:39 nekum /etc/mariadb/debian-start[988]: Triggering myisam-recover for all MyISAM tables
luc@nekum:/var/log $

Maak de database aan

luc@nekum:~ $ mariadb -u root -p test < createdb.sql
Enter password:

met als inhoud:

luc@nekum:~ $ cat createdb.sql
CREATE DATABASE IF NOT EXISTS `meter`;
DROP TABLE IF EXISTS `emeter`;
CREATE TABLE `emeter` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `e_dattijd` datetime DEFAULT NULL,
 `e_ver_t1` float(9,3) DEFAULT NULL,
 `e_ver_t2` float(9,3) DEFAULT NULL,
 `e_ter_t1` float(9,3) DEFAULT NULL,
 `e_ter_t2` float(9,3) DEFAULT NULL,
 `t` tinyint(1) unsigned DEFAULT NULL,
 `e_ver_mom` float(5,3) DEFAULT NULL,
 `e_ter_mom` float(5,3) DEFAULT NULL,
 `g_dattijd` datetime DEFAULT NULL,
 `g_ver` float(8,3) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4330 DEFAULT CHARSET=latin1;
luc@nekum:~ $

Maak nu het bestand /home/luc/.my.cnf aan:

luc@nekum:~ $ cat ~/.my.cnf
[client]
password  = jouw_wachtwoord
port      = 3306
user      = jouw_gebruiker
socket    = /var/run/mariadbd/mariadbd.sock

Toegang tot de seriïle poort:

Raspbian gaat ervan uit dat mensen via de seriïle poort toegang willen hebben tot het systeem, de z.g. console. Voor onze toepassing is dit niet wenselijk en moeten we deze toegangsmogelijkheid uit zetten. (1) service stoppen en (2) voorkomen dat die nog een keer start

luc@nekum:~ $ sudo systemctl stop serial-getty@ttyAMA0.service
luc@nekum:~ $ sudo systemctl disable serial-getty@ttyAMA0.service

Voorkomen dat de kernel een seriel poort als console gebruikt, door "console=ttyAMA0,115200" weg te halen uit cmdline.txt

luc@nekum:~/ $ sudo cat /boot/cmdline.txt dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsc k.repair=yes rootwait

Testen!

We testen het programma, met de debug vlag zien we het telegram en de query:

luc@nekum:~/dsm2sql $ ./dsm2sql -c ttyAMA0 -d energie
/KFM5KAIFA-METER

1-3:0.2.8(42)
0-0:1.0.0(161106115629W)
0-0:96.1.1(idvanmijnslimmeelektriciteitsmeter)
1-0:1.8.1(000350.759*kWh)
1-0:1.8.2(000159.184*kWh)
1-0:2.8.1(000358.177*kWh)
1-0:2.8.2(001041.184*kWh)
0-0:96.14.0(0001)
1-0:1.7.0(00.000*kW)
1-0:2.7.0(00.998*kW)
0-0:96.7.21(00001)
0-0:96.7.9(00001)
1-0:99.97.0(1)(0-0:96.7.19)(000101000011W)(2147483647*s)
1-0:32.32.0(00000)
1-0:52.32.0(00000)
1-0:72.32.0(00000)
1-0:32.36.0(00000)
1-0:52.36.0(00000)
1-0:72.36.0(00000)
0-0:96.13.1()
0-0:96.13.0()
1-0:31.7.0(000*A)
1-0:51.7.0(005*A)
1-0:71.7.0(002*A)
1-0:21.7.0(00.018*kW)
1-0:41.7.0(00.000*kW)
1-0:61.7.0(00.220*kW)
1-0:22.7.0(00.000*kW)
1-0:42.7.0(01.258*kW)
1-0:62.7.0(00.000*kW)
0-1:24.1.0(003)
0-1:96.1.0(idvanmijnslimmeelektriciteitsmeter)
0-1:24.2.1(161106110000W)(00185.352*m3)
!7AC8
[2016-11-06 10:56:32] INSERT INTO emeter (e_dattijd, e_ver_t1,e_ver_t2,e_ter_t1,e_ter_t2,t, e_ver_mom,e_ter_mom,g_dattijd,g_ver) VALUES ("2016-11-06 11:56:29",000350.759,000159.184,000358.177,001041.184,0001,00.000,00.998,"2016-11-06 11:00:00",00185.352); luc@nekum:~/dsm2sql $

$Id: index.html,v 1.7 2017/09/01 07:28:55 luc Exp $Valid XHTML 1.0! Valid CSS 1.0!