]> git.basschouten.com Git - openhab-addons.git/blob
417157fdaab1b5854bbe5df44bb3a7b9a626ff24
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.teleinfo.internal.reader.io;
14
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.InputStreamReader;
19 import java.nio.charset.StandardCharsets;
20 import java.time.LocalDate;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.UUID;
24 import java.util.concurrent.Callable;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.ExecutorService;
27 import java.util.concurrent.Executors;
28 import java.util.concurrent.Future;
29 import java.util.concurrent.TimeUnit;
30 import java.util.concurrent.TimeoutException;
31
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.openhab.binding.teleinfo.internal.dto.Frame;
35 import org.openhab.binding.teleinfo.internal.dto.cbemm.FrameCbemm;
36 import org.openhab.binding.teleinfo.internal.dto.cbemm.FrameCbemmBaseOption;
37 import org.openhab.binding.teleinfo.internal.dto.cbemm.FrameCbemmEjpOption;
38 import org.openhab.binding.teleinfo.internal.dto.cbemm.FrameCbemmHcOption;
39 import org.openhab.binding.teleinfo.internal.dto.cbemm.FrameCbemmTempoOption;
40 import org.openhab.binding.teleinfo.internal.dto.cbemm.evoicc.FrameCbemmEvolutionIcc;
41 import org.openhab.binding.teleinfo.internal.dto.cbemm.evoicc.FrameCbemmEvolutionIccBaseOption;
42 import org.openhab.binding.teleinfo.internal.dto.cbemm.evoicc.FrameCbemmEvolutionIccEjpOption;
43 import org.openhab.binding.teleinfo.internal.dto.cbemm.evoicc.FrameCbemmEvolutionIccHcOption;
44 import org.openhab.binding.teleinfo.internal.dto.cbemm.evoicc.FrameCbemmEvolutionIccTempoOption;
45 import org.openhab.binding.teleinfo.internal.dto.cbetm.FrameCbetmLong;
46 import org.openhab.binding.teleinfo.internal.dto.cbetm.FrameCbetmLongBaseOption;
47 import org.openhab.binding.teleinfo.internal.dto.cbetm.FrameCbetmLongEjpOption;
48 import org.openhab.binding.teleinfo.internal.dto.cbetm.FrameCbetmLongHcOption;
49 import org.openhab.binding.teleinfo.internal.dto.cbetm.FrameCbetmLongTempoOption;
50 import org.openhab.binding.teleinfo.internal.dto.cbetm.FrameCbetmShort;
51 import org.openhab.binding.teleinfo.internal.dto.common.FrameBaseOption;
52 import org.openhab.binding.teleinfo.internal.dto.common.FrameEjpOption;
53 import org.openhab.binding.teleinfo.internal.dto.common.FrameHcOption;
54 import org.openhab.binding.teleinfo.internal.dto.common.FrameTempoOption;
55 import org.openhab.binding.teleinfo.internal.dto.common.FrameTempoOption.CouleurDemain;
56 import org.openhab.binding.teleinfo.internal.dto.common.FrameTempoOption.ProgrammeCircuit1;
57 import org.openhab.binding.teleinfo.internal.dto.common.FrameTempoOption.ProgrammeCircuit2;
58 import org.openhab.binding.teleinfo.internal.dto.common.Hhphc;
59 import org.openhab.binding.teleinfo.internal.dto.common.Ptec;
60 import org.openhab.binding.teleinfo.internal.reader.io.serialport.ConversionException;
61 import org.openhab.binding.teleinfo.internal.reader.io.serialport.FrameUtil;
62 import org.openhab.binding.teleinfo.internal.reader.io.serialport.InvalidFrameException;
63 import org.openhab.binding.teleinfo.internal.reader.io.serialport.Label;
64 import org.openhab.binding.teleinfo.internal.reader.io.serialport.converter.Converter;
65 import org.openhab.binding.teleinfo.internal.reader.io.serialport.converter.CouleurDemainConverter;
66 import org.openhab.binding.teleinfo.internal.reader.io.serialport.converter.FloatConverter;
67 import org.openhab.binding.teleinfo.internal.reader.io.serialport.converter.HhphcConverter;
68 import org.openhab.binding.teleinfo.internal.reader.io.serialport.converter.IntegerConverter;
69 import org.openhab.binding.teleinfo.internal.reader.io.serialport.converter.PtecConverter;
70 import org.openhab.binding.teleinfo.internal.reader.io.serialport.converter.StringConverter;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
73
74 /**
75  * InputStream for Teleinfo {@link Frame} in serial port format.
76  */
77 /**
78  * The {@link TeleinfoInputStream} class is an {@link InputStream} to decode/read Teleinfo frames.
79  *
80  * @author Nicolas SIBERIL - Initial contribution
81  */
82 @NonNullByDefault
83 public class TeleinfoInputStream extends InputStream {
84
85     public static final long DEFAULT_TIMEOUT_NEXT_HEADER_FRAME_US = 33400;
86     public static final long DEFAULT_TIMEOUT_READING_FRAME_US = 33400;
87
88     private final Logger logger = LoggerFactory.getLogger(TeleinfoInputStream.class);
89     private static final Map<Class<?>, Converter> LABEL_VALUE_CONVERTERS;
90
91     private BufferedReader bufferedReader;
92     private @Nullable String groupLine;
93     private ExecutorService executorService;
94     private long waitNextHeaderFrameTimeoutInUs;
95     private long readingFrameTimeoutInUs;
96     private boolean autoRepairInvalidADPSgroupLine;
97     private boolean useOwnScheduler;
98
99     static {
100         LABEL_VALUE_CONVERTERS = new HashMap<>();
101         LABEL_VALUE_CONVERTERS.put(Integer.class, new IntegerConverter());
102         LABEL_VALUE_CONVERTERS.put(String.class, new StringConverter());
103         LABEL_VALUE_CONVERTERS.put(Float.class, new FloatConverter());
104         LABEL_VALUE_CONVERTERS.put(Ptec.class, new PtecConverter());
105         LABEL_VALUE_CONVERTERS.put(Hhphc.class, new HhphcConverter());
106         LABEL_VALUE_CONVERTERS.put(CouleurDemain.class, new CouleurDemainConverter());
107     }
108
109     public TeleinfoInputStream(final InputStream teleinfoInputStream) {
110         this(teleinfoInputStream, DEFAULT_TIMEOUT_NEXT_HEADER_FRAME_US, DEFAULT_TIMEOUT_READING_FRAME_US, false);
111     }
112
113     public TeleinfoInputStream(final InputStream teleinfoInputStream, boolean autoRepairInvalidADPSgroupLine) {
114         this(teleinfoInputStream, DEFAULT_TIMEOUT_NEXT_HEADER_FRAME_US, DEFAULT_TIMEOUT_READING_FRAME_US,
115                 autoRepairInvalidADPSgroupLine);
116     }
117
118     public TeleinfoInputStream(final @Nullable InputStream teleinfoInputStream, long waitNextHeaderFrameTimeoutInUs,
119             long readingFrameTimeoutInUs, boolean autoRepairInvalidADPSgroupLine) {
120         this(teleinfoInputStream, waitNextHeaderFrameTimeoutInUs, readingFrameTimeoutInUs,
121                 autoRepairInvalidADPSgroupLine, null);
122     }
123
124     public TeleinfoInputStream(final @Nullable InputStream teleinfoInputStream, long waitNextHeaderFrameTimeoutInUs,
125             long readingFrameTimeoutInUs, boolean autoRepairInvalidADPSgroupLine,
126             @Nullable ExecutorService executorService) {
127         if (teleinfoInputStream == null) {
128             throw new IllegalArgumentException("Teleinfo inputStream is null");
129         }
130
131         this.waitNextHeaderFrameTimeoutInUs = waitNextHeaderFrameTimeoutInUs;
132         this.readingFrameTimeoutInUs = readingFrameTimeoutInUs;
133         this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
134         if (executorService == null) {
135             this.executorService = Executors.newFixedThreadPool(2);
136             this.useOwnScheduler = true;
137         } else {
138             this.executorService = executorService;
139             this.useOwnScheduler = false;
140         }
141
142         this.bufferedReader = new BufferedReader(new InputStreamReader(teleinfoInputStream, StandardCharsets.US_ASCII));
143
144         groupLine = null;
145     }
146
147     @Override
148     public void close() throws IOException {
149         logger.debug("close() [start]");
150         bufferedReader.close();
151         if (useOwnScheduler) {
152             executorService.shutdownNow();
153         }
154         super.close();
155         logger.debug("close() [end]");
156     }
157
158     /**
159      * Returns the next frame.
160      *
161      * @return the next frame or null if end of stream
162      * @throws TimeoutException
163      * @throws IOException
164      * @throws InvalidFrameException
165      * @throws Exception
166      */
167     public synchronized @Nullable Frame readNextFrame() throws TimeoutException, InvalidFrameException, IOException {
168         // seek the next header frame
169         Future<@Nullable Void> seekNextHeaderFrameTask = executorService.submit(() -> {
170             while (!isHeaderFrame(groupLine)) {
171                 groupLine = bufferedReader.readLine();
172                 if (logger.isTraceEnabled()) {
173                     logger.trace("groupLine = {}", groupLine);
174                 }
175                 if (groupLine == null) { // end of stream
176                     logger.trace("end of stream reached !");
177                     return null;
178                 }
179             }
180
181             logger.trace("header frame found !");
182             return null;
183         });
184         try {
185             logger.debug("seeking the next header frame...");
186             logger.trace("waitNextHeaderFrameTimeoutInUs = {}", waitNextHeaderFrameTimeoutInUs);
187             seekNextHeaderFrameTask.get(waitNextHeaderFrameTimeoutInUs, TimeUnit.MICROSECONDS);
188
189             if (groupLine == null) { // end of stream
190                 return null;
191             }
192         } catch (InterruptedException e) {
193             logger.debug("Got interrupted exception", e);
194             Thread.currentThread().interrupt();
195         } catch (ExecutionException e) {
196             rethrowTaskExecutionException(e);
197         }
198
199         Future<Map<Label, Object>> nextFrameFuture = executorService.submit(new Callable<Map<Label, Object>>() {
200             @Override
201             public Map<Label, Object> call() throws Exception {
202                 // read label values
203                 Map<Label, Object> frameValues = new HashMap<>();
204                 while ((groupLine = bufferedReader.readLine()) != null && !isHeaderFrame(groupLine)) {
205                     logger.trace("groupLine = {}", groupLine);
206                     String groupLineRef = groupLine;
207                     if (groupLineRef != null) {
208                         String[] groupLineTokens = groupLineRef.split("\\s");
209                         if (groupLineTokens.length != 2 && groupLineTokens.length != 3) {
210                             final String error = String.format("The groupLine '%1$s' is incomplete", groupLineRef);
211                             throw new InvalidFrameException(error);
212                         }
213                         String labelStr = groupLineTokens[0];
214                         String valueString = groupLineTokens[1];
215
216                         // verify integrity (through checksum)
217                         char checksum = (groupLineTokens.length == 3 ? groupLineTokens[2].charAt(0) : ' ');
218                         char computedChecksum = FrameUtil.computeGroupLineChecksum(labelStr, valueString);
219                         if (computedChecksum != checksum) {
220                             logger.trace("computedChecksum = {}", computedChecksum);
221                             logger.trace("checksum = {}", checksum);
222                             final String error = String.format(
223                                     "The groupLine '%s' is corrupted (integrity not checked). Actual checksum: '%s' / Expected checksum: '%s'",
224                                     groupLineRef, checksum, computedChecksum);
225                             throw new InvalidFrameException(error);
226                         }
227
228                         Label label;
229                         try {
230                             label = Label.valueOf(labelStr);
231                         } catch (IllegalArgumentException e) {
232                             if (autoRepairInvalidADPSgroupLine && labelStr.startsWith(Label.ADPS.name())) {
233                                 // in this hardware issue, label variable is composed by label name and value. E.g:
234                                 // ADPS032
235                                 logger.warn("Try to auto repair malformed ADPS groupLine '{}'", labelStr);
236                                 label = Label.ADPS;
237                                 valueString = labelStr.substring(Label.ADPS.name().length());
238                             } else {
239                                 final String error = String.format("The label '%s' is unknown", labelStr);
240                                 throw new InvalidFrameException(error);
241                             }
242                         }
243
244                         Class<?> labelType = label.getType();
245                         Converter converter = LABEL_VALUE_CONVERTERS.get(labelType);
246                         try {
247                             Object value = converter.convert(valueString);
248                             if (value != null) {
249                                 frameValues.put(label, value);
250                             }
251                         } catch (ConversionException e) {
252                             final String error = String.format("An error occurred during '%s' value conversion",
253                                     valueString);
254                             throw new InvalidFrameException(error, e);
255                         }
256                     }
257                 }
258
259                 return frameValues;
260             }
261         });
262
263         try {
264             logger.debug("reading data frame...");
265             logger.trace("readingFrameTimeoutInUs = {}", readingFrameTimeoutInUs);
266             Map<Label, Object> frameValues = nextFrameFuture.get(readingFrameTimeoutInUs, TimeUnit.MICROSECONDS);
267
268             // build the frame from map values
269             final Frame frame = buildFrame(frameValues);
270             frame.setTimestamp(LocalDate.now());
271             frame.setId(UUID.randomUUID());
272
273             return frame;
274         } catch (InterruptedException e) {
275             logger.debug("Got interrupted exception", e);
276             Thread.currentThread().interrupt();
277             throw new IllegalStateException(e);
278         } catch (ExecutionException e) {
279             rethrowTaskExecutionException(e);
280         }
281         return null;
282     }
283
284     public long getWaitNextHeaderFrameTimeoutInUs() {
285         return waitNextHeaderFrameTimeoutInUs;
286     }
287
288     public void setWaitNextHeaderFrameTimeoutInUs(long waitNextHeaderFrameTimeoutInUs) {
289         this.waitNextHeaderFrameTimeoutInUs = waitNextHeaderFrameTimeoutInUs;
290     }
291
292     public long getReadingFrameTimeoutInUs() {
293         return readingFrameTimeoutInUs;
294     }
295
296     public void setReadingFrameTimeoutInUs(long readingFrameTimeoutInUs) {
297         this.readingFrameTimeoutInUs = readingFrameTimeoutInUs;
298     }
299
300     public boolean isAutoRepairInvalidADPSgroupLine() {
301         return autoRepairInvalidADPSgroupLine;
302     }
303
304     public void setAutoRepairInvalidADPSgroupLine(boolean autoRepairInvalidADPSgroupLine) {
305         this.autoRepairInvalidADPSgroupLine = autoRepairInvalidADPSgroupLine;
306     }
307
308     @Override
309     public int read() throws IOException {
310         throw new UnsupportedOperationException("The 'read()' is not supported");
311     }
312
313     private boolean isHeaderFrame(final @Nullable String line) {
314         // A new teleinfo trame begin with '3' and '2' bytes (END OF TEXT et START OF TEXT)
315         return (line != null && line.length() > 1 && line.codePointAt(0) == 3 && line.codePointAt(1) == 2);
316     }
317
318     private Frame buildFrame(final Map<Label, Object> frameValues) throws InvalidFrameException {
319         if (frameValues.containsKey(Label.IINST1)) {
320             if (frameValues.containsKey(Label.IMAX1)) {
321                 return buildFrameCbetmLong(frameValues);
322             } else {
323                 return buildFrameCbetmShort(frameValues);
324             }
325         } else if (frameValues.containsKey(Label.PAPP)) {
326             return buildFrameCbemmEvolutionIcc(frameValues);
327         } else {
328             return buildFrameCbemm(frameValues);
329         }
330     }
331
332     private FrameCbetmLong buildFrameCbetmLong(final Map<Label, Object> frameValues) throws InvalidFrameException {
333         logger.trace("buildFrameCbetmLong(Map<Label, Object>) [start]");
334         final FrameCbetmLong frameCbetm;
335         String optionTarif = getRequiredLabelValue(Label.OPTARIF, frameValues);
336         if ("BASE".equals(optionTarif)) {
337             frameCbetm = buildFrameCbetmLongBaseOption(frameValues);
338         } else if ("HC..".equals(optionTarif)) {
339             frameCbetm = buildFrameCbetmLongHcOption(frameValues);
340         } else if ("EJP.".equals(optionTarif)) {
341             frameCbetm = buildFrameCbetmLongEjpOption(frameValues);
342         } else if (optionTarif.startsWith("BBR") && optionTarif.length() == 4) {
343             ProgrammeCircuit1 prgCircuit1 = convertProgrammeCircuit1(optionTarif.charAt(3));
344             ProgrammeCircuit2 prgCircuit2 = convertProgrammeCircuit2(optionTarif.charAt(3));
345             frameCbetm = buildFrameCbetmLongTempoOption(frameValues, prgCircuit1, prgCircuit2);
346         } else {
347             final String error = String.format("The option Tarif '%s' is not supported", optionTarif);
348             throw new InvalidFrameException(error);
349         }
350         logger.trace("buildFrameCbetmLong(Map<Label, Object>) [end]");
351         return frameCbetm;
352     }
353
354     private void setCbetmCommonFrameFields(final FrameCbetmLong frame, final Map<Label, Object> frameValues)
355             throws InvalidFrameException {
356         logger.trace("setCbetmCommonFrameFields(Frame, Map<Label, Object>) [start]");
357         frame.setAdco(getRequiredLabelValue(Label.ADCO, frameValues));
358         frame.setIsousc(getRequiredLabelValue(Label.ISOUSC, frameValues));
359         frame.setIinst1(getRequiredLabelValue(Label.IINST1, frameValues));
360         frame.setIinst2(getRequiredLabelValue(Label.IINST2, frameValues));
361         frame.setIinst3(getRequiredLabelValue(Label.IINST3, frameValues));
362         frame.setImax1(getRequiredLabelValue(Label.IMAX1, frameValues));
363         frame.setImax2(getRequiredLabelValue(Label.IMAX2, frameValues));
364         frame.setImax3(getRequiredLabelValue(Label.IMAX3, frameValues));
365         frame.setPtec(getRequiredLabelValue(Label.PTEC, frameValues));
366         frame.setPmax(getRequiredLabelValue(Label.PMAX, frameValues));
367         frame.setPapp(getRequiredLabelValue(Label.PAPP, frameValues));
368         frame.setMotdetat(getRequiredLabelValue(Label.MOTDETAT, frameValues));
369         frame.setPpot(getRequiredLabelValue(Label.PPOT, frameValues));
370         logger.trace("setCbetmCommonFrameFields(Frame, Map<Label, Object>) [end]");
371     }
372
373     private FrameCbetmLongBaseOption buildFrameCbetmLongBaseOption(final Map<Label, Object> frameValues)
374             throws InvalidFrameException {
375         logger.trace("buildFrameCbetmBaseOption(Map<Label, Object>) [start]");
376         FrameCbetmLongBaseOption frame = new FrameCbetmLongBaseOption();
377         setCbetmCommonFrameFields(frame, frameValues);
378         setFrameBaseOptionFields(frame, frameValues);
379         logger.trace("buildFrameCbetmBaseOption(Map<Label, Object>) [end]");
380         return frame;
381     }
382
383     private FrameCbetmLongHcOption buildFrameCbetmLongHcOption(final Map<Label, Object> frameValues)
384             throws InvalidFrameException {
385         logger.trace("buildFrameCbetmHcOption(Map<Label, Object>) [start]");
386         FrameCbetmLongHcOption frame = new FrameCbetmLongHcOption();
387         setCbetmCommonFrameFields(frame, frameValues);
388         setFrameHcOptionFields(frame, frameValues);
389         logger.trace("buildFrameCbetmHcOption(Map<Label, Object>) [end]");
390         return frame;
391     }
392
393     private FrameCbetmLongEjpOption buildFrameCbetmLongEjpOption(final Map<Label, Object> frameValues)
394             throws InvalidFrameException {
395         logger.trace("buildFrameCbetmEjpOption(Map<Label, Object>) [start]");
396         FrameCbetmLongEjpOption frame = new FrameCbetmLongEjpOption();
397         setCbetmCommonFrameFields(frame, frameValues);
398         setFrameEjpOptionFields(frame, frameValues);
399         logger.trace("buildFrameCbetmEjpOption(Map<Label, Object>) [end]");
400         return frame;
401     }
402
403     private FrameCbetmLongTempoOption buildFrameCbetmLongTempoOption(final Map<Label, Object> frameValues,
404             ProgrammeCircuit1 prgCircuit1, ProgrammeCircuit2 prgCircuit2) throws InvalidFrameException {
405         logger.trace("buildFrameCbetmTempoOption(Map<Label, Object>) [start]");
406         FrameCbetmLongTempoOption frame = new FrameCbetmLongTempoOption();
407         setCbetmCommonFrameFields(frame, frameValues);
408         setFrameTempoOptionFields(frame, frameValues, prgCircuit1, prgCircuit2);
409         logger.trace("buildFrameCbetmTempoOption(Map<Label, Object>) [end]");
410         return frame;
411     }
412
413     private FrameCbetmShort buildFrameCbetmShort(final Map<Label, Object> frameValues) throws InvalidFrameException {
414         logger.trace("buildFrameCbetmShort(Map<Label, Object>) [start]");
415         FrameCbetmShort frame = new FrameCbetmShort();
416         frame.setAdco(getRequiredLabelValue(Label.ADCO, frameValues));
417         frame.setIinst1(getRequiredLabelValue(Label.IINST1, frameValues));
418         frame.setIinst2(getRequiredLabelValue(Label.IINST2, frameValues));
419         frame.setIinst3(getRequiredLabelValue(Label.IINST3, frameValues));
420         frame.setAdir1(getOptionalLabelValue(Label.ADIR1, frameValues));
421         frame.setAdir2(getOptionalLabelValue(Label.ADIR2, frameValues));
422         frame.setAdir3(getOptionalLabelValue(Label.ADIR3, frameValues));
423         logger.trace("buildFrameCbetmShort(Map<Label, Object>) [end]");
424         return frame;
425     }
426
427     private FrameCbemm buildFrameCbemm(final Map<Label, Object> frameValues) throws InvalidFrameException {
428         logger.trace("buildFrameCbemm(Map<Label, Object>) [start]");
429         final FrameCbemm frameCbemm;
430         String optionTarif = getRequiredLabelValue(Label.OPTARIF, frameValues);
431         if ("BASE".equals(optionTarif)) {
432             frameCbemm = buildFrameCbemmBaseOption(frameValues);
433         } else if ("HC..".equals(optionTarif)) {
434             frameCbemm = buildFrameCbemmHcOption(frameValues);
435         } else if ("EJP.".equals(optionTarif)) {
436             frameCbemm = buildFrameCbemmEjpOption(frameValues);
437         } else if (optionTarif.startsWith("BBR") && optionTarif.length() == 4) {
438             ProgrammeCircuit1 prgCircuit1 = convertProgrammeCircuit1(optionTarif.charAt(3));
439             ProgrammeCircuit2 prgCircuit2 = convertProgrammeCircuit2(optionTarif.charAt(3));
440             frameCbemm = buildFrameCbemmTempoOption(frameValues, prgCircuit1, prgCircuit2);
441         } else {
442             final String error = String.format("The option Tarif '%s' is not supported", optionTarif);
443             throw new InvalidFrameException(error);
444         }
445         logger.trace("buildFrameCbemm(Map<Label, Object>) [end]");
446         return frameCbemm;
447     }
448
449     private void setCbemmCommonFrameFields(final FrameCbemm frame, final Map<Label, Object> frameValues)
450             throws InvalidFrameException {
451         logger.trace("setCbemmCommonFrameFields(Frame, Map<Label, Object>) [start]");
452         frame.setAdco(getRequiredLabelValue(Label.ADCO, frameValues));
453         frame.setIsousc(getRequiredLabelValue(Label.ISOUSC, frameValues));
454         frame.setIinst(getRequiredLabelValue(Label.IINST, frameValues));
455         frame.setImax(getOptionalLabelValue(Label.IMAX, frameValues));
456         frame.setPtec(getRequiredLabelValue(Label.PTEC, frameValues));
457         frame.setAdps(getOptionalLabelValue(Label.ADPS, frameValues));
458         frame.setMotdetat(getRequiredLabelValue(Label.MOTDETAT, frameValues));
459         logger.trace("setCbemmCommonFrameFields(Frame, Map<Label, Object>) [end]");
460     }
461
462     private FrameCbemmBaseOption buildFrameCbemmBaseOption(final Map<Label, Object> frameValues)
463             throws InvalidFrameException {
464         logger.trace("buildFrameCbemmBaseOption(Map<Label, Object>) [start]");
465         FrameCbemmBaseOption frame = new FrameCbemmBaseOption();
466         setCbemmCommonFrameFields(frame, frameValues);
467         setFrameBaseOptionFields(frame, frameValues);
468         logger.trace("buildFrameCbemmBaseOption(Map<Label, Object>) [end]");
469         return frame;
470     }
471
472     private FrameCbemmHcOption buildFrameCbemmHcOption(final Map<Label, Object> frameValues)
473             throws InvalidFrameException {
474         logger.trace("buildFrameCbemmHcOption(Map<Label, Object>) [start]");
475         FrameCbemmHcOption frame = new FrameCbemmHcOption();
476         setCbemmCommonFrameFields(frame, frameValues);
477         setFrameHcOptionFields(frame, frameValues);
478         logger.trace("buildFrameCbemmHcOption(Map<Label, Object>) [end]");
479         return frame;
480     }
481
482     private FrameCbemmEjpOption buildFrameCbemmEjpOption(final Map<Label, Object> frameValues)
483             throws InvalidFrameException {
484         logger.trace("buildFrameCbemmEjpOption(Map<Label, Object>) [start]");
485         FrameCbemmEjpOption frame = new FrameCbemmEjpOption();
486         setCbemmCommonFrameFields(frame, frameValues);
487         setFrameEjpOptionFields(frame, frameValues);
488         logger.trace("buildFrameCbemmEjpOption(Map<Label, Object>) [end]");
489         return frame;
490     }
491
492     private FrameCbemmTempoOption buildFrameCbemmTempoOption(final Map<Label, Object> frameValues,
493             ProgrammeCircuit1 prgCircuit1, ProgrammeCircuit2 prgCircuit2) throws InvalidFrameException {
494         logger.trace("buildFrameCbemmTempoOption(Map<Label, Object>) [start]");
495         FrameCbemmTempoOption frame = new FrameCbemmTempoOption();
496         setCbemmCommonFrameFields(frame, frameValues);
497         setFrameTempoOptionFields(frame, frameValues, prgCircuit1, prgCircuit2);
498         logger.trace("buildFrameCbemmTempoOption(Map<Label, Object>) [end]");
499         return frame;
500     }
501
502     private FrameCbemmEvolutionIcc buildFrameCbemmEvolutionIcc(final Map<Label, Object> frameValues)
503             throws InvalidFrameException {
504         logger.trace("buildFrameCbemmEvolutionIcc(Map<Label, Object>) [start]");
505         final FrameCbemmEvolutionIcc frameCbemmEvoIcc;
506         String optionTarif = getRequiredLabelValue(Label.OPTARIF, frameValues);
507         if ("BASE".equals(optionTarif)) {
508             frameCbemmEvoIcc = buildFrameCbemmEvolutionIccBaseOption(frameValues);
509         } else if ("HC..".equals(optionTarif)) {
510             frameCbemmEvoIcc = buildFrameCbemmEvolutionIccHcOption(frameValues);
511         } else if ("EJP.".equals(optionTarif)) {
512             frameCbemmEvoIcc = buildFrameCbemmEvolutionIccEjpOption(frameValues);
513         } else if (optionTarif.startsWith("BBR") && optionTarif.length() == 4) {
514             ProgrammeCircuit1 prgCircuit1 = convertProgrammeCircuit1(optionTarif.charAt(3));
515             ProgrammeCircuit2 prgCircuit2 = convertProgrammeCircuit2(optionTarif.charAt(3));
516             frameCbemmEvoIcc = buildFrameCbemmEvolutionIccTempoOption(frameValues, prgCircuit1, prgCircuit2);
517         } else {
518             final String error = String.format("The option Tarif '%s' is not supported", optionTarif);
519             throw new InvalidFrameException(error);
520         }
521
522         logger.trace("buildFrameCbemmEvolutionIcc(Map<Label, Object>) [end]");
523         return frameCbemmEvoIcc;
524     }
525
526     private void setCbemmEvolutionIccCommonFrameFields(final FrameCbemmEvolutionIcc frame,
527             final Map<Label, Object> frameValues) throws InvalidFrameException {
528         logger.trace("setCbemmEvolutionIccCommonFrameFields(Frame, Map<Label, Object>) [start]");
529         setCbemmCommonFrameFields(frame, frameValues);
530         frame.setPapp(getRequiredLabelValue(Label.PAPP, frameValues));
531         logger.trace("setCbemmEvolutionIccCommonFrameFields(Frame, Map<Label, Object>) [end]");
532     }
533
534     private FrameCbemmEvolutionIccBaseOption buildFrameCbemmEvolutionIccBaseOption(final Map<Label, Object> frameValues)
535             throws InvalidFrameException {
536         logger.trace("buildFrameCbemmEvolutionIccBaseOption(Map<Label, Object>) [start]");
537         FrameCbemmEvolutionIccBaseOption frame = new FrameCbemmEvolutionIccBaseOption();
538         setCbemmEvolutionIccCommonFrameFields(frame, frameValues);
539         setFrameBaseOptionFields(frame, frameValues);
540         logger.trace("buildFrameCbemmEvolutionIccBaseOption(Map<Label, Object>) [end]");
541         return frame;
542     }
543
544     private FrameCbemmEvolutionIccHcOption buildFrameCbemmEvolutionIccHcOption(final Map<Label, Object> frameValues)
545             throws InvalidFrameException {
546         logger.trace("buildFrameCbemmEvolutionIccHcOption(Map<Label, Object>) [start]");
547         FrameCbemmEvolutionIccHcOption frame = new FrameCbemmEvolutionIccHcOption();
548         setCbemmEvolutionIccCommonFrameFields(frame, frameValues);
549         setFrameHcOptionFields(frame, frameValues);
550         logger.trace("buildFrameCbemmEvolutionIccHcOption(Map<Label, Object>) [end]");
551         return frame;
552     }
553
554     private FrameCbemmEvolutionIccTempoOption buildFrameCbemmEvolutionIccTempoOption(
555             final Map<Label, Object> frameValues, ProgrammeCircuit1 prgCircuit1, ProgrammeCircuit2 prgCircuit2)
556             throws InvalidFrameException {
557         logger.trace("buildFrameCbemmEvolutionIccTempoOption(Map<Label, Object>) [start]");
558         FrameCbemmEvolutionIccTempoOption frame = new FrameCbemmEvolutionIccTempoOption();
559         setCbemmEvolutionIccCommonFrameFields(frame, frameValues);
560         setFrameTempoOptionFields(frame, frameValues, prgCircuit1, prgCircuit2);
561         logger.trace("buildFrameCbemmEvolutionIccTempoOption(Map<Label, Object>) [end]");
562         return frame;
563     }
564
565     private FrameCbemmEvolutionIccEjpOption buildFrameCbemmEvolutionIccEjpOption(final Map<Label, Object> frameValues)
566             throws InvalidFrameException {
567         logger.trace("buildFrameCbemmEvolutionIccEjpOption(Map<Label, Object>) [start]");
568         FrameCbemmEvolutionIccEjpOption frame = new FrameCbemmEvolutionIccEjpOption();
569         setCbemmEvolutionIccCommonFrameFields(frame, frameValues);
570         setFrameEjpOptionFields(frame, frameValues);
571         logger.trace("buildFrameCbemmEvolutionIccEjpOption(Map<Label, Object>) [end]");
572         return frame;
573     }
574
575     private void setFrameBaseOptionFields(final FrameBaseOption frameBaseOption, final Map<Label, Object> frameValues)
576             throws InvalidFrameException {
577         logger.trace("setFrameBaseOptionFields(FrameBaseOption) [start]");
578         frameBaseOption.setBase(getRequiredLabelValue(Label.BASE, frameValues));
579         logger.trace("setFrameBaseOptionFields(FrameBaseOption) [end]");
580     }
581
582     private void setFrameHcOptionFields(final FrameHcOption frameHcOption, final Map<Label, Object> frameValues)
583             throws InvalidFrameException {
584         logger.trace("setFrameHcOptionFields(FrameHcOption) [start]");
585         frameHcOption.setHchc(getRequiredLabelValue(Label.HCHC, frameValues));
586         frameHcOption.setHchp(getRequiredLabelValue(Label.HCHP, frameValues));
587         frameHcOption.setHhphc(getRequiredLabelValue(Label.HHPHC, frameValues));
588         logger.trace("setFrameHcOptionFields(FrameHcOption) [end]");
589     }
590
591     private void setFrameEjpOptionFields(final FrameEjpOption frameEjpOption, final Map<Label, Object> frameValues)
592             throws InvalidFrameException {
593         logger.trace("setFrameEjpOptionFields(FrameEjpOption) [start]");
594         frameEjpOption.setEjphn(getRequiredLabelValue(Label.EJPHN, frameValues));
595         frameEjpOption.setEjphpm(getRequiredLabelValue(Label.EJPHPM, frameValues));
596         frameEjpOption.setPejp(getOptionalLabelValue(Label.PEJP, frameValues));
597         logger.trace("setFrameEjpOptionFields(FrameEjpOption) [end]");
598     }
599
600     private void setFrameTempoOptionFields(final FrameTempoOption frameTempoOption,
601             final Map<Label, Object> frameValues, ProgrammeCircuit1 prgCircuit1, ProgrammeCircuit2 prgCircuit2)
602             throws InvalidFrameException {
603         logger.trace("setFrameTempoOptionFields(FrameTempoOption) [start]");
604         frameTempoOption.setBbrhpjr(getRequiredLabelValue(Label.BBRHPJR, frameValues));
605         frameTempoOption.setBbrhcjr(getRequiredLabelValue(Label.BBRHCJR, frameValues));
606         frameTempoOption.setBbrhpjw(getRequiredLabelValue(Label.BBRHPJW, frameValues));
607         frameTempoOption.setBbrhcjw(getRequiredLabelValue(Label.BBRHCJW, frameValues));
608         frameTempoOption.setBbrhpjb(getRequiredLabelValue(Label.BBRHPJB, frameValues));
609         frameTempoOption.setBbrhcjb(getRequiredLabelValue(Label.BBRHCJB, frameValues));
610         frameTempoOption.setDemain(getOptionalLabelValue(Label.DEMAIN, frameValues));
611         frameTempoOption.setHhphc(getRequiredLabelValue(Label.HHPHC, frameValues));
612         frameTempoOption.setProgrammeCircuit1(prgCircuit1);
613         frameTempoOption.setProgrammeCircuit2(prgCircuit2);
614         logger.trace("setFrameTempoOptionFields(FrameTempoOption) [end]");
615     }
616
617     @SuppressWarnings("unchecked")
618     private <T> T getRequiredLabelValue(Label label, final Map<Label, Object> frameValues)
619             throws InvalidFrameException {
620         if (!frameValues.containsKey(label)) {
621             final String error = String.format("The required label '%1$s' is missing in frame", label);
622             throw new InvalidFrameException(error);
623         }
624
625         return (T) frameValues.get(label);
626     }
627
628     @SuppressWarnings("unchecked")
629     private <T> T getOptionalLabelValue(Label label, final Map<Label, Object> frameValues) {
630         return (T) frameValues.get(label);
631     }
632
633     private ProgrammeCircuit1 convertProgrammeCircuit1(char value) {
634         String prgCircuit1 = computeProgrammeCircuitBinaryValue(value).substring(3, 5);
635         switch (prgCircuit1) {
636             case "01":
637                 return ProgrammeCircuit1.A;
638             case "10":
639                 return ProgrammeCircuit1.B;
640             case "11":
641                 return ProgrammeCircuit1.C;
642             default:
643                 final String error = String.format("Programme circuit 1 '%s' is unknown", prgCircuit1);
644                 throw new IllegalStateException(error);
645         }
646     }
647
648     private ProgrammeCircuit2 convertProgrammeCircuit2(char value) {
649         String prgCircuit2 = computeProgrammeCircuitBinaryValue(value).substring(5, 8);
650         switch (prgCircuit2) {
651             case "000":
652                 return ProgrammeCircuit2.P0;
653             case "001":
654                 return ProgrammeCircuit2.P1;
655             case "010":
656                 return ProgrammeCircuit2.P2;
657             case "011":
658                 return ProgrammeCircuit2.P3;
659             case "100":
660                 return ProgrammeCircuit2.P4;
661             case "101":
662                 return ProgrammeCircuit2.P5;
663             case "110":
664                 return ProgrammeCircuit2.P6;
665             case "111":
666                 return ProgrammeCircuit2.P7;
667             default:
668                 final String error = String.format("Programme circuit 2 '%s' is unknown", prgCircuit2);
669                 throw new IllegalStateException(error);
670         }
671     }
672
673     private String computeProgrammeCircuitBinaryValue(char value) {
674         return String.format("%8s", Integer.toBinaryString(value)).replace(' ', '0');
675     }
676
677     private void rethrowTaskExecutionException(ExecutionException e)
678             throws InvalidFrameException, IOException, TimeoutException {
679         Throwable cause = e.getCause();
680         if (cause instanceof InvalidFrameException) {
681             throw (InvalidFrameException) cause;
682         } else if (cause instanceof IOException) {
683             throw (IOException) cause;
684         } else if (cause instanceof TimeoutException) {
685             throw (TimeoutException) cause;
686         } else {
687             throw new IllegalStateException(e);
688         }
689     }
690 }