Karl Zweimüller
/
eth_comfort_test
Programm for decoding radio-signals sent by a ETH-Window-Shutter-Contact, received with a RFM12B-module
eth_comfort.cpp@1:fc72e0bdb693, 2011-04-07 (annotated)
- Committer:
- charly
- Date:
- Thu Apr 07 19:54:09 2011 +0000
- Revision:
- 1:fc72e0bdb693
- Parent:
- 0:96794c9fc5a3
Reorganized and created classes for RFM12B and ETH-Comfort
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
charly | 0:96794c9fc5a3 | 1 | /** module for receiving data from eth comfort window sensor by ELV(R) with a RFM12 transceiver module by Hope |
charly | 0:96794c9fc5a3 | 2 | |
charly | 0:96794c9fc5a3 | 3 | Details see discussion on http://www.mikrocontroller.net/topic/172034 |
charly | 0:96794c9fc5a3 | 4 | Frequenz 868,3 |
charly | 0:96794c9fc5a3 | 5 | Modulation FSK |
charly | 0:96794c9fc5a3 | 6 | Hub +/- 15kHz |
charly | 0:96794c9fc5a3 | 7 | Datenrate 10kbit Manchester |
charly | 1:fc72e0bdb693 | 8 | |
charly | 0:96794c9fc5a3 | 9 | das Protokoll ist Machester codiert |
charly | 0:96794c9fc5a3 | 10 | 0x7e zz ll aa aa aa cmd crc crc |
charly | 0:96794c9fc5a3 | 11 | |
charly | 0:96794c9fc5a3 | 12 | zz ist ein fortlaufender Zaehler |
charly | 0:96794c9fc5a3 | 13 | ll die Blocklaenge 10,20,30 (20 = 9 Byte, 10= 10Byte ,30= ?) |
charly | 0:96794c9fc5a3 | 14 | aa 3 Byte Adresse |
charly | 0:96794c9fc5a3 | 15 | cmd der Befehl |
charly | 0:96794c9fc5a3 | 16 | crc 16 bit checksumme |
charly | 0:96794c9fc5a3 | 17 | |
charly | 0:96794c9fc5a3 | 18 | die Reihenfolge der Bits im Byte ist verdreht. |
charly | 0:96794c9fc5a3 | 19 | |
charly | 0:96794c9fc5a3 | 20 | */ |
charly | 1:fc72e0bdb693 | 21 | /*! |
charly | 1:fc72e0bdb693 | 22 | * \file eth_comfort.cpp |
charly | 1:fc72e0bdb693 | 23 | * \brief Read the messages from the ETH-Radio-Shutter |
charly | 1:fc72e0bdb693 | 24 | * \author Karl Zweimüller based on code from WED 6.9.2009 |
charly | 1:fc72e0bdb693 | 25 | */ |
charly | 0:96794c9fc5a3 | 26 | |
charly | 0:96794c9fc5a3 | 27 | |
charly | 0:96794c9fc5a3 | 28 | #include "eth_comfort.h" |
charly | 0:96794c9fc5a3 | 29 | |
charly | 0:96794c9fc5a3 | 30 | /* orig AVR-values |
charly | 0:96794c9fc5a3 | 31 | #define BIT_MAX 0x90 |
charly | 0:96794c9fc5a3 | 32 | #define BIT_MIN 0x10 |
charly | 0:96794c9fc5a3 | 33 | #define LEN_2T 0x44 |
charly | 0:96794c9fc5a3 | 34 | */ |
charly | 0:96794c9fc5a3 | 35 | |
charly | 0:96794c9fc5a3 | 36 | //working: 300-80-200 |
charly | 0:96794c9fc5a3 | 37 | // nominal values are: 208-104-208 for 9600 bits/sec |
charly | 0:96794c9fc5a3 | 38 | #define BIT_MAX 300 // maximum allowed time for one or two bits high or low |
charly | 0:96794c9fc5a3 | 39 | #define BIT_MIN 80 // minimum allowed time for one or two bits high or low |
charly | 0:96794c9fc5a3 | 40 | #define LEN_2T 200 // time-value to differentiate one or two bit length |
charly | 0:96794c9fc5a3 | 41 | |
charly | 0:96794c9fc5a3 | 42 | |
charly | 0:96794c9fc5a3 | 43 | // Timer for bit-length-measurement |
charly | 0:96794c9fc5a3 | 44 | Timer BitTime; |
charly | 0:96794c9fc5a3 | 45 | |
charly | 1:fc72e0bdb693 | 46 | |
charly | 1:fc72e0bdb693 | 47 | eth_comfort::eth_comfort(PinName mosi, PinName miso, PinName sclk, PinName nsel, PinName rxdata, PinName rxled) { |
charly | 1:fc72e0bdb693 | 48 | |
charly | 1:fc72e0bdb693 | 49 | rfm12b_spi = new rfm12b(mosi,miso,sclk,nsel,rxdata); |
charly | 1:fc72e0bdb693 | 50 | |
charly | 1:fc72e0bdb693 | 51 | // the ReceiveLED |
charly | 1:fc72e0bdb693 | 52 | rxLED = new DigitalOut(rxled); |
charly | 1:fc72e0bdb693 | 53 | |
charly | 1:fc72e0bdb693 | 54 | // init the eth_receiver |
charly | 1:fc72e0bdb693 | 55 | init(); |
charly | 1:fc72e0bdb693 | 56 | |
charly | 1:fc72e0bdb693 | 57 | // Interrupt on every bit-change |
charly | 1:fc72e0bdb693 | 58 | rfm12b_spi->attachISR(this, ð_comfort::ISR); |
charly | 1:fc72e0bdb693 | 59 | |
charly | 1:fc72e0bdb693 | 60 | // Init the RFM12B |
charly | 1:fc72e0bdb693 | 61 | rfm12b_spi->RFM_init(); |
charly | 1:fc72e0bdb693 | 62 | |
charly | 1:fc72e0bdb693 | 63 | }; |
charly | 0:96794c9fc5a3 | 64 | //------------------------------------------------------------------------- |
charly | 0:96794c9fc5a3 | 65 | // calcCRC16r() |
charly | 0:96794c9fc5a3 | 66 | //------------------------------------------------------------------------- |
charly | 0:96794c9fc5a3 | 67 | /** |
charly | 0:96794c9fc5a3 | 68 | * @brief calculate reverse CRC |
charly | 0:96794c9fc5a3 | 69 | * @param c 16bit value for crc |
charly | 0:96794c9fc5a3 | 70 | * @param crc old crc value |
charly | 0:96794c9fc5a3 | 71 | * @param mask crc polynom |
charly | 0:96794c9fc5a3 | 72 | * @return crc value |
charly | 0:96794c9fc5a3 | 73 | */ |
charly | 1:fc72e0bdb693 | 74 | uint16_t eth_comfort::calcCRC16r( uint16_t c,uint16_t crc, uint16_t mask) { // reverse crc!!!!!! |
charly | 1:fc72e0bdb693 | 75 | uint8_t i; |
charly | 1:fc72e0bdb693 | 76 | for (i=0;i<8;i++) { |
charly | 1:fc72e0bdb693 | 77 | if ((crc ^ c) & 1) { |
charly | 1:fc72e0bdb693 | 78 | crc=(crc>>1)^mask; |
charly | 1:fc72e0bdb693 | 79 | } else crc>>=1; |
charly | 1:fc72e0bdb693 | 80 | c>>=1; |
charly | 1:fc72e0bdb693 | 81 | }; |
charly | 1:fc72e0bdb693 | 82 | return(crc); |
charly | 0:96794c9fc5a3 | 83 | } |
charly | 0:96794c9fc5a3 | 84 | |
charly | 0:96794c9fc5a3 | 85 | // initialize eth_receiver |
charly | 1:fc72e0bdb693 | 86 | void eth_comfort::init() { |
charly | 1:fc72e0bdb693 | 87 | |
charly | 1:fc72e0bdb693 | 88 | rbyte=0; |
charly | 1:fc72e0bdb693 | 89 | bit_cnt=0; |
charly | 1:fc72e0bdb693 | 90 | buffer_cnt=0; |
charly | 1:fc72e0bdb693 | 91 | decode=0; |
charly | 1:fc72e0bdb693 | 92 | pack_ok=0; |
charly | 0:96794c9fc5a3 | 93 | |
charly | 1:fc72e0bdb693 | 94 | // start timer for bit-length-measurement |
charly | 1:fc72e0bdb693 | 95 | BitTime.start(); |
charly | 1:fc72e0bdb693 | 96 | message_received = false; |
charly | 1:fc72e0bdb693 | 97 | // init the buffer |
charly | 1:fc72e0bdb693 | 98 | last_message.cnt = 0; |
charly | 1:fc72e0bdb693 | 99 | last_message.len = 0; |
charly | 1:fc72e0bdb693 | 100 | last_message.adr = 0; |
charly | 1:fc72e0bdb693 | 101 | last_message.cmd = 0; |
charly | 1:fc72e0bdb693 | 102 | last_message.data = 0; |
charly | 1:fc72e0bdb693 | 103 | last_message.xdata = 0; |
charly | 1:fc72e0bdb693 | 104 | last_message.crc = 0; |
charly | 1:fc72e0bdb693 | 105 | |
charly | 1:fc72e0bdb693 | 106 | new_message = last_message; |
charly | 0:96794c9fc5a3 | 107 | } |
charly | 0:96794c9fc5a3 | 108 | |
charly | 1:fc72e0bdb693 | 109 | |
charly | 1:fc72e0bdb693 | 110 | // is a new message readable |
charly | 1:fc72e0bdb693 | 111 | bool eth_comfort::readable() { |
charly | 1:fc72e0bdb693 | 112 | return(message_received); |
charly | 1:fc72e0bdb693 | 113 | } |
charly | 1:fc72e0bdb693 | 114 | |
charly | 1:fc72e0bdb693 | 115 | // read a eth-messsage |
charly | 1:fc72e0bdb693 | 116 | eth_message eth_comfort::getMessage() { |
charly | 1:fc72e0bdb693 | 117 | if (readable()) { |
charly | 1:fc72e0bdb693 | 118 | last_message = new_message; |
charly | 1:fc72e0bdb693 | 119 | message_received = false; |
charly | 1:fc72e0bdb693 | 120 | return(new_message); |
charly | 1:fc72e0bdb693 | 121 | } else |
charly | 1:fc72e0bdb693 | 122 | // we should return nothing here! |
charly | 1:fc72e0bdb693 | 123 | return(last_message); |
charly | 1:fc72e0bdb693 | 124 | } |
charly | 0:96794c9fc5a3 | 125 | |
charly | 0:96794c9fc5a3 | 126 | /** ISR Interrupt routine for received data |
charly | 0:96794c9fc5a3 | 127 | * triggers on every pin change high/low and low/high |
charly | 0:96794c9fc5a3 | 128 | * does all the encoding of the signal including manchester decoding! |
charly | 0:96794c9fc5a3 | 129 | * as the eth doesn't send a good signal, which the rfm12 could decode for himself |
charly | 0:96794c9fc5a3 | 130 | * didn't test for myself - just got the code from someone else and ported to mbed! |
charly | 0:96794c9fc5a3 | 131 | */ |
charly | 1:fc72e0bdb693 | 132 | void eth_comfort::ISR() { // pin change on rxin ->interrupt |
charly | 1:fc72e0bdb693 | 133 | //led2=!led2; |
charly | 1:fc72e0bdb693 | 134 | b=BitTime.read_us(); // time since last change |
charly | 1:fc72e0bdb693 | 135 | BitTime.reset(); |
charly | 1:fc72e0bdb693 | 136 | |
charly | 1:fc72e0bdb693 | 137 | if ((b>BIT_MAX)||(b<BIT_MIN)) { // is bit time in range? |
charly | 1:fc72e0bdb693 | 138 | state=0; |
charly | 1:fc72e0bdb693 | 139 | } else { |
charly | 1:fc72e0bdb693 | 140 | |
charly | 1:fc72e0bdb693 | 141 | |
charly | 1:fc72e0bdb693 | 142 | if (state==0) { // wait for first bitchange 2T |
charly | 1:fc72e0bdb693 | 143 | if (b>LEN_2T)state=1; |
charly | 1:fc72e0bdb693 | 144 | //if((rxin)!=0)state=0; // level should be low |
charly | 1:fc72e0bdb693 | 145 | } else if (state<=10) { // wait for 5 fullbit without bitchange 10 shortbits |
charly | 1:fc72e0bdb693 | 146 | if (b<LEN_2T)state++; |
charly | 1:fc72e0bdb693 | 147 | else state=1; // bitchange found back to state 1 |
charly | 1:fc72e0bdb693 | 148 | } else if (state==11) { // now expecting bitchange (last bit in 7e is 0) |
charly | 1:fc72e0bdb693 | 149 | if (b<LEN_2T) { |
charly | 1:fc72e0bdb693 | 150 | state=0; // no bitchange -> back to search |
charly | 1:fc72e0bdb693 | 151 | }; |
charly | 1:fc72e0bdb693 | 152 | state=20; // now we found 7e sync finished |
charly | 1:fc72e0bdb693 | 153 | rbyte=0x7e; // set shift value to 0 |
charly | 1:fc72e0bdb693 | 154 | bit_cnt=8; // clear bitcounter |
charly | 1:fc72e0bdb693 | 155 | buffer_cnt=0; // clear buffercounter |
charly | 1:fc72e0bdb693 | 156 | bcnt=0; |
charly | 1:fc72e0bdb693 | 157 | lastbit=0; |
charly | 0:96794c9fc5a3 | 158 | |
charly | 1:fc72e0bdb693 | 159 | } else if (state==20) { |
charly | 1:fc72e0bdb693 | 160 | if (b>LEN_2T) { // check for bitchange |
charly | 1:fc72e0bdb693 | 161 | if (lastbit!=0) { |
charly | 1:fc72e0bdb693 | 162 | rbyte=(rbyte>>1); // last bit was 1 new is 0 |
charly | 1:fc72e0bdb693 | 163 | bcnt=0; |
charly | 1:fc72e0bdb693 | 164 | lastbit=0; |
charly | 1:fc72e0bdb693 | 165 | } else { |
charly | 1:fc72e0bdb693 | 166 | rbyte=(rbyte>>1)|0x80; // last bit was 0 new is 1 |
charly | 1:fc72e0bdb693 | 167 | bcnt++; |
charly | 1:fc72e0bdb693 | 168 | lastbit=1; |
charly | 1:fc72e0bdb693 | 169 | } |
charly | 1:fc72e0bdb693 | 170 | state=20; // fullbit compleate |
charly | 1:fc72e0bdb693 | 171 | bit_cnt++; // increase bit counter |
charly | 1:fc72e0bdb693 | 172 | } else { |
charly | 1:fc72e0bdb693 | 173 | state=21; // bit is halfbit, wait for next halfbit |
charly | 1:fc72e0bdb693 | 174 | }; |
charly | 1:fc72e0bdb693 | 175 | } else if (state==21) { // no bitchange |
charly | 1:fc72e0bdb693 | 176 | if (b<LEN_2T) { // next bit must be a halfbit |
charly | 1:fc72e0bdb693 | 177 | if (lastbit==0) { |
charly | 1:fc72e0bdb693 | 178 | rbyte=(rbyte>>1); // last bit was 0 new is 0 |
charly | 1:fc72e0bdb693 | 179 | lastbit=0; |
charly | 1:fc72e0bdb693 | 180 | bcnt=0; |
charly | 1:fc72e0bdb693 | 181 | } else { |
charly | 1:fc72e0bdb693 | 182 | rbyte=(rbyte>>1)|0x80; // last bit was 1 new is 1 |
charly | 1:fc72e0bdb693 | 183 | lastbit=1; |
charly | 1:fc72e0bdb693 | 184 | bcnt++; |
charly | 1:fc72e0bdb693 | 185 | } |
charly | 1:fc72e0bdb693 | 186 | state=20; |
charly | 1:fc72e0bdb693 | 187 | bit_cnt++; |
charly | 1:fc72e0bdb693 | 188 | } else { |
charly | 1:fc72e0bdb693 | 189 | state=0; // bit is no halfbit -> Manchester violation |
charly | 1:fc72e0bdb693 | 190 | // state=20; |
charly | 1:fc72e0bdb693 | 191 | }; |
charly | 1:fc72e0bdb693 | 192 | } else if (state==22) { // after 5 bit 1 skip one bit 0 |
charly | 1:fc72e0bdb693 | 193 | if (b>LEN_2T) { // check for bitchange (next bit 0) |
charly | 1:fc72e0bdb693 | 194 | lastbit=0; |
charly | 1:fc72e0bdb693 | 195 | state=20; |
charly | 1:fc72e0bdb693 | 196 | } else { |
charly | 1:fc72e0bdb693 | 197 | |
charly | 1:fc72e0bdb693 | 198 | lastbit=1; |
charly | 1:fc72e0bdb693 | 199 | //state=11; |
charly | 1:fc72e0bdb693 | 200 | state=21; |
charly | 1:fc72e0bdb693 | 201 | } |
charly | 1:fc72e0bdb693 | 202 | bcnt=0; |
charly | 0:96794c9fc5a3 | 203 | |
charly | 0:96794c9fc5a3 | 204 | |
charly | 1:fc72e0bdb693 | 205 | } |
charly | 1:fc72e0bdb693 | 206 | if (bcnt==5)state=22; |
charly | 1:fc72e0bdb693 | 207 | |
charly | 1:fc72e0bdb693 | 208 | if (bit_cnt>7) { // wait for 8 bits |
charly | 1:fc72e0bdb693 | 209 | buf[buffer_cnt]=rbyte; // save value into buffer |
charly | 1:fc72e0bdb693 | 210 | if (buffer_cnt<1020) { |
charly | 1:fc72e0bdb693 | 211 | buffer_cnt++; |
charly | 1:fc72e0bdb693 | 212 | }; |
charly | 1:fc72e0bdb693 | 213 | pack_ok=1; // set receiveflag |
charly | 1:fc72e0bdb693 | 214 | bit_cnt=0; // clear bitcounter |
charly | 1:fc72e0bdb693 | 215 | *rxLED = ! *rxLED; //show received byte |
charly | 1:fc72e0bdb693 | 216 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// |
charly | 1:fc72e0bdb693 | 217 | //here we received another byte |
charly | 0:96794c9fc5a3 | 218 | |
charly | 1:fc72e0bdb693 | 219 | // we have to check if we are ready with the message |
charly | 1:fc72e0bdb693 | 220 | if (buffer_cnt>8) { |
charly | 1:fc72e0bdb693 | 221 | if (buf[2]==0x10) blocklength=10; |
charly | 1:fc72e0bdb693 | 222 | else if (buf[2]==0x20) blocklength=9; |
charly | 1:fc72e0bdb693 | 223 | else blocklength=99; |
charly | 1:fc72e0bdb693 | 224 | j=0; |
charly | 1:fc72e0bdb693 | 225 | crc_ok = false; |
charly | 1:fc72e0bdb693 | 226 | for (i=0;i<=buffer_cnt;i++) { |
charly | 1:fc72e0bdb693 | 227 | //pc.printf("%02X ",buf[i]); |
charly | 1:fc72e0bdb693 | 228 | j++; |
charly | 1:fc72e0bdb693 | 229 | if (j==blocklength) { |
charly | 1:fc72e0bdb693 | 230 | //check crc |
charly | 1:fc72e0bdb693 | 231 | if (blocklength==9) { |
charly | 1:fc72e0bdb693 | 232 | crc=0xbdb7; |
charly | 1:fc72e0bdb693 | 233 | for (k=0;k<7;k++) { // crc over first 7 byte |
charly | 1:fc72e0bdb693 | 234 | crc=calcCRC16r(buf[k],crc,0x8408); |
charly | 1:fc72e0bdb693 | 235 | } |
charly | 1:fc72e0bdb693 | 236 | //swap the two crc-bytes |
charly | 1:fc72e0bdb693 | 237 | swapped = ((crc >> 8) & 0xff) | ((crc << 8) & 0xff00); |
charly | 1:fc72e0bdb693 | 238 | //pc.printf("CRC: %04X ",swapped); |
charly | 1:fc72e0bdb693 | 239 | if (((buf[7]<<8) | buf[8]) == swapped) crc_ok = true; |
charly | 1:fc72e0bdb693 | 240 | else crc_ok = false; |
charly | 1:fc72e0bdb693 | 241 | //pc.printf("%s", (crc_ok==true) ? "OK" : "Not OK"); |
charly | 1:fc72e0bdb693 | 242 | if (crc_ok) { |
charly | 1:fc72e0bdb693 | 243 | /* |
charly | 1:fc72e0bdb693 | 244 | pc.printf("\n\rCounter: %02X\n\r",buf[1]); |
charly | 1:fc72e0bdb693 | 245 | pc.printf( " Dev-ID: %02X %02X %02X\n\r",buf[3],buf[4],buf[5]); |
charly | 1:fc72e0bdb693 | 246 | //pc.printf( "Battery: %s\n\r", (buf[6]&0x80 != 0x00) ? "WEAK" : "GOOD"); |
charly | 1:fc72e0bdb693 | 247 | pc.printf( "Window : %s\n\r\n\r", (buf[6]&0x01 != 0x00) ? "OPEN" : "CLOSE"); |
charly | 1:fc72e0bdb693 | 248 | lcd.cls(); |
charly | 1:fc72e0bdb693 | 249 | lcd.printf("#:%02X ID: %02X%02X%02X\n",buf[1],buf[3],buf[4],buf[5]); |
charly | 1:fc72e0bdb693 | 250 | lcd.printf("Window : %s\n", (buf[6]&0x01 != 0x00) ? "OPEN" : "CLOSE"); |
charly | 1:fc72e0bdb693 | 251 | */ |
charly | 0:96794c9fc5a3 | 252 | |
charly | 1:fc72e0bdb693 | 253 | // insert buf into message |
charly | 1:fc72e0bdb693 | 254 | new_message.cnt = buf[1]; |
charly | 1:fc72e0bdb693 | 255 | new_message.len = buf[2]; |
charly | 1:fc72e0bdb693 | 256 | new_message.adr = buf[3]<<16 | buf[4]<<8 | buf[5]; |
charly | 1:fc72e0bdb693 | 257 | new_message.cmd = buf[6]; |
charly | 1:fc72e0bdb693 | 258 | new_message.data = buf[7]; |
charly | 1:fc72e0bdb693 | 259 | new_message.xdata = 0; |
charly | 1:fc72e0bdb693 | 260 | new_message.crc = swapped; |
charly | 1:fc72e0bdb693 | 261 | |
charly | 1:fc72e0bdb693 | 262 | //is the new message different from the old message? |
charly | 1:fc72e0bdb693 | 263 | if (new_message.cnt != last_message.cnt) { |
charly | 1:fc72e0bdb693 | 264 | last_message = new_message; |
charly | 1:fc72e0bdb693 | 265 | message_received = true; |
charly | 1:fc72e0bdb693 | 266 | } |
charly | 1:fc72e0bdb693 | 267 | |
charly | 1:fc72e0bdb693 | 268 | } //crc_ok |
charly | 1:fc72e0bdb693 | 269 | } //block_length = 9 |
charly | 1:fc72e0bdb693 | 270 | } //j==blocklength |
charly | 1:fc72e0bdb693 | 271 | } //for |
charly | 0:96794c9fc5a3 | 272 | |
charly | 0:96794c9fc5a3 | 273 | |
charly | 1:fc72e0bdb693 | 274 | //start receive from beginning |
charly | 1:fc72e0bdb693 | 275 | buffer_cnt=0; |
charly | 1:fc72e0bdb693 | 276 | bit_cnt=0; |
charly | 1:fc72e0bdb693 | 277 | startbit=0; |
charly | 1:fc72e0bdb693 | 278 | state=0; |
charly | 1:fc72e0bdb693 | 279 | //pc.printf("\n\r-----------------------------\n\r"); |
charly | 1:fc72e0bdb693 | 280 | //clear the buffer |
charly | 1:fc72e0bdb693 | 281 | for (i=0;i<1023;i++)buf[i]=0; |
charly | 1:fc72e0bdb693 | 282 | *rxLED = 0; //turn off receive led |
charly | 1:fc72e0bdb693 | 283 | } //buffer_cnt >8 |
charly | 1:fc72e0bdb693 | 284 | ////////////////////////////////////////////////////////////////////////////////////////////////////////// |
charly | 1:fc72e0bdb693 | 285 | // |
charly | 1:fc72e0bdb693 | 286 | } |
charly | 1:fc72e0bdb693 | 287 | } |
charly | 0:96794c9fc5a3 | 288 | |
charly | 0:96794c9fc5a3 | 289 | } |
charly | 0:96794c9fc5a3 | 290 | |
charly | 0:96794c9fc5a3 | 291 | |
charly | 0:96794c9fc5a3 | 292 | |
charly | 0:96794c9fc5a3 | 293 |