View Javadoc
1   package net.sf.mbus4j.encoder;
2   
3   /*
4    * #%L
5    * mbus4j-core
6    * %%
7    * Copyright (C) 2009 - 2014 MBus4J
8    * %%
9    * mbus4j - Drivers for the M-Bus protocol - http://mbus4j.sourceforge.net/
10   * Copyright (C) 2009-2014, mbus4j.sf.net, and individual contributors as indicated
11   * by the @authors tag. See the copyright.txt in the distribution for a
12   * full listing of individual contributors.
13   * 
14   * This is free software; you can redistribute it and/or modify it
15   * under the terms of the GNU General Public License as
16   * published by the Free Software Foundation; either version 3 of
17   * the License, or (at your option) any later version.
18   * 
19   * This software is distributed in the hope that it will be useful,
20   * but WITHOUT ANY WARRANTY; without even the implied warranty of
21   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22   * Lesser General Public License for more details.
23   * 
24   * You should have received a copy of the GNU Lesser General Public
25   * License along with this software; if not, write to the Free
26   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
27   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
28   * #L%
29   */
30  import java.util.Arrays;
31  import java.util.Calendar;
32  import java.util.logging.Logger;
33  import net.sf.mbus4j.MBusUtils;
34  
35  import net.sf.mbus4j.dataframes.ApplicationReset;
36  import net.sf.mbus4j.dataframes.ControlFrame;
37  import net.sf.mbus4j.dataframes.Frame;
38  import net.sf.mbus4j.dataframes.GeneralApplicationError;
39  import net.sf.mbus4j.dataframes.LongFrame;
40  import net.sf.mbus4j.dataframes.PrimaryAddress;
41  import net.sf.mbus4j.dataframes.RequestClassXData;
42  import net.sf.mbus4j.dataframes.SelectionOfSlaves;
43  import net.sf.mbus4j.dataframes.SendUserData;
44  import net.sf.mbus4j.dataframes.SetBaudrate;
45  import net.sf.mbus4j.dataframes.ShortFrame;
46  import net.sf.mbus4j.dataframes.SingleCharFrame;
47  import net.sf.mbus4j.dataframes.SynchronizeAction;
48  import net.sf.mbus4j.dataframes.UserDataResponse;
49  import net.sf.mbus4j.dataframes.UserDataResponse.StatusCode;
50  import net.sf.mbus4j.dataframes.datablocks.BcdValue;
51  import net.sf.mbus4j.dataframes.datablocks.ByteDataBlock;
52  import net.sf.mbus4j.dataframes.datablocks.DataBlock;
53  import net.sf.mbus4j.dataframes.datablocks.DateAndTimeDataBlock;
54  import net.sf.mbus4j.dataframes.datablocks.DateDataBlock;
55  import net.sf.mbus4j.dataframes.datablocks.EnhancedIdentificationDataBlock;
56  import net.sf.mbus4j.dataframes.datablocks.IntegerDataBlock;
57  import net.sf.mbus4j.dataframes.datablocks.LongDataBlock;
58  import net.sf.mbus4j.dataframes.datablocks.RawDataBlock;
59  import net.sf.mbus4j.dataframes.datablocks.RealDataBlock;
60  import net.sf.mbus4j.dataframes.datablocks.ShortDataBlock;
61  import net.sf.mbus4j.dataframes.datablocks.StringDataBlock;
62  import net.sf.mbus4j.dataframes.datablocks.vif.VifAscii;
63  import net.sf.mbus4j.dataframes.datablocks.vif.VifManufacturerSpecific;
64  import net.sf.mbus4j.dataframes.datablocks.vif.VifFB;
65  import net.sf.mbus4j.dataframes.datablocks.vif.VifeFC;
66  import net.sf.mbus4j.dataframes.datablocks.vif.VifFD;
67  import net.sf.mbus4j.dataframes.datablocks.vif.VifPrimary;
68  import net.sf.mbus4j.dataframes.datablocks.vif.VifeError;
69  import net.sf.mbus4j.dataframes.datablocks.vif.VifeManufacturerSpecific;
70  import net.sf.mbus4j.dataframes.datablocks.vif.VifePrimary;
71  import net.sf.mbus4j.decoder.Decoder;
72  import net.sf.mbus4j.log.LogUtils;
73  
74  /**
75   *
76   * @author arnep@users.sourceforge.net
77   * @version $Id: Encoder.java 155 2016-05-04 08:26:07Z arnep $
78   */
79  public class Encoder {
80  
81      private byte[] data;
82      private int currentPos;
83      private static Logger log = LogUtils.getEncoderLogger();
84  
85      public byte[] encode(Frame frame) {
86          if (frame instanceof SingleCharFrame) {
87              return encodeFrame((SingleCharFrame) frame);
88          } else if (frame instanceof ShortFrame) {
89              return encodeFrame((ShortFrame) frame);
90          } else if (frame instanceof ControlFrame) {
91              return encodeFrame((ControlFrame) frame);
92          } else if (frame instanceof LongFrame) {
93              return encodeFrame((LongFrame) frame);
94          } else {
95              return null;
96          }
97      }
98  
99      public byte[] encodeFrame(ControlFrame frame) {
100         initFrame(frame);
101         pushCField(frame);
102         pushAField(frame);
103         pushCIField(frame);
104         writeChecksumAndStop(4, 7);
105         return data;
106     }
107 
108     public byte[] encodeFrame(LongFrame frame) {
109         initFrame(frame);
110         pushCField(frame);
111         pushAField(frame);
112         pushCIField(frame);
113         if (frame instanceof ApplicationReset) {
114             pushApplicationResetData((ApplicationReset) frame);
115         } else if (frame instanceof GeneralApplicationError) {
116             pushGeneralApplicationError((GeneralApplicationError) frame);
117         } else if (frame instanceof UserDataResponse) {
118             pushVariableDataStructure((UserDataResponse) frame);
119         } else if (frame instanceof SelectionOfSlaves) {
120             pushSelectionOfSlavesDataHeader((SelectionOfSlaves) frame);
121             pushVariableDataBlocks(frame);
122         } else if (frame instanceof SendUserData) {
123             pushVariableDataBlocks(frame);
124         } else {
125             throw new RuntimeException("encode long frame" + frame.getClass().getName());
126         }
127         writeLenght((byte) (currentPos - 4));
128         writeChecksumAndStop(4, currentPos);
129         return data;
130     }
131 
132     public byte[] encodeFrame(ShortFrame frame) {
133         initFrame(frame);
134         pushCField(frame);
135         pushAField(frame);
136         writeChecksumAndStop(1, 3);
137         return data;
138     }
139 
140     public byte[] encodeFrame(SingleCharFrame frame) {
141         initFrame(frame);
142         return data;
143     }
144 
145     private void initFrame(Frame frame) {
146         if (frame instanceof SingleCharFrame) {
147             data = new byte[]{(byte) 0xe5};
148             currentPos = -1;
149         } else if (frame instanceof ShortFrame) {
150             data = new byte[5];
151             data[0] = 0x10;
152             currentPos = 1;
153         } else if (frame instanceof ControlFrame) {
154             data = new byte[9];
155             data[0] = 0x68;
156             data[1] = 0x03;
157             data[2] = 0x03;
158             data[3] = 0x68;
159             currentPos = 4;
160         } else if (frame instanceof LongFrame) {
161             data = new byte[0xFF];
162             data[0] = 0x68;
163             data[3] = 0x68;
164             currentPos = 4;
165         } else {
166             throw new RuntimeException("Unknown frame");
167         }
168     }
169 
170     private boolean needDIFE(DataBlock db, int index) {
171         return ((db.getStorageNumber() >> (1 + index * 4)) > 0x00)
172                 || (((db.getTariff() >> (index * 2)) << 0x04) > 0x00)
173                 || ((db.getSubUnit() >> index) > 0x00);
174     }
175 
176     private boolean needVIFE(DataBlock db, int index) {
177         if (db.getVifes().length == 0) {
178             return db.getAction() == null ? false : index == 0;
179         } else {
180             return db.getVifes().length > index;
181         }
182     }
183 
184     private void pushAField(PrimaryAddress address) {
185         data[currentPos++] = address.getAddress();
186     }
187 
188     private void pushApplicationResetData(ApplicationReset applicationReset) {
189         data[currentPos++] = (byte) (applicationReset.getTelegramType().id | applicationReset.getSubTelegram());
190     }
191 
192     /* convert to byte nibble (LCD recomendation) */
193     public int fromLcdDigit(char lcdDigit) {
194         switch (lcdDigit) {
195             case '0':
196                 return 0x00;
197             case '1':
198                 return 0x01;
199             case '2':
200                 return 0x02;
201             case '3':
202                 return 0x03;
203             case '4':
204                 return 0x04;
205             case '5':
206                 return 0x05;
207             case '6':
208                 return 0x06;
209             case '7':
210                 return 0x07;
211             case '8':
212                 return 0x08;
213             case '9':
214                 return 0x09;
215             case 'a':
216             case 'A':
217                 return 0x0A;
218             case 'b':
219             case 'B':
220                 return 0x0B;
221             case 'c':
222             case 'C':
223                 return 0x0C;
224             case ' ':
225             case 'd':
226             case 'D':
227                 return 0x0D;
228             case 'e':
229             case 'E':
230                 return 0x0E;
231             case '-':
232             case 'f':
233             case 'F':
234                 return 0x0F;
235             default:
236                 throw new RuntimeException("Should never ever happend!");
237         }
238 
239     }
240 
241     private void pushBcdError(String value) {
242         for (int i = value.length() - 1; i >= 0; i -= 2) {
243             int v = fromLcdDigit(value.charAt(i)) | fromLcdDigit(value.charAt(i - 1)) << 4;
244             data[currentPos++] = (byte) v;
245         }
246     }
247 
248     private void pushBcd(long value, int bcdDigits) {
249         final boolean isNegative = value < 0;
250         if (isNegative) {
251             value = -value;
252         }
253         for (int i = bcdDigits / 2; i > 0; i--) {
254             data[currentPos] = (byte) (value % 10);
255             value /= 10;
256             if ((i == 1) && (isNegative)) {
257                 data[currentPos++] |= (byte) (0xF0);
258             } else {
259                 data[currentPos++] |= (byte) ((value % 10) << 4);
260             }
261             value /= 10;
262         }
263     }
264 
265     private void pushBytes(byte[] value) {
266         if (value != null) {
267             for (byte b : value) {
268                 pushInteger(b, 1);
269             }
270         }
271     }
272 
273     private void pushCField(Frame frame) {
274         switch (frame.getControlCode()) {
275             case SND_NKE:
276                 data[currentPos] = 0x40;
277                 break;
278             case SND_UD:
279                 if (frame instanceof SynchronizeAction) {
280                     data[currentPos] = (byte) (0x53 + (((SynchronizeAction) frame).isFcb() ? 0x20 : 0));
281                 } else if (frame instanceof ApplicationReset) {
282                     data[currentPos] = (byte) (0x53 + (((ApplicationReset) frame).isFcb() ? 0x20 : 0));
283                 } else if (frame instanceof SetBaudrate) {
284                     data[currentPos] = (byte) (0x53 + (((SetBaudrate) frame).isFcb() ? 0x20 : 0));
285                 } else if (frame instanceof SendUserData) {
286                     data[currentPos] = (byte) (0x53 + (((SendUserData) frame).isFcb() ? 0x20 : 0));
287                 } else if (frame instanceof SelectionOfSlaves) {
288                     data[currentPos] = (byte) (0x53 + (((SelectionOfSlaves) frame).isFcb() ? 0x20 : 0));
289                 }
290 
291                 break;
292             case REQ_UD2:
293                 RequestClassXData req = (RequestClassXData) frame;
294                 data[currentPos] = (byte) (0x4B | (req.isFcb() ? 0x20 : 0) | (req.isFcv() ? 0x10 : 0));
295                 break;
296             case REQ_UD1:
297                 req = (RequestClassXData) frame;
298                 data[currentPos] = (byte) (0x4A | (req.isFcb() ? 0x20 : 0) | (req.isFcv() ? 0x10 : 0));
299                 break;
300             case RSP_UD:
301                 if (frame instanceof UserDataResponse) {
302                     final UserDataResponse resp = (UserDataResponse) frame;
303                     data[currentPos] = (byte) (0x08 | (resp.isAcd() ? 0x20 : 0) | (resp.isDfc() ? 0x10 : 0));
304                 } else if (frame instanceof GeneralApplicationError) {
305                     final GeneralApplicationError gae = (GeneralApplicationError) frame;
306                     data[currentPos] = (byte) (0x08 | (gae.isAcd() ? 0x20 : 0) | (gae.isDfc() ? 0x10 : 0));
307                 }
308                 break;
309             default:
310 
311         }
312         currentPos++;
313     }
314 
315     private void pushCIField(ControlFrame frame) {
316         if (frame instanceof SetBaudrate) {
317             final SetBaudrate sb = (SetBaudrate) frame;
318             switch (sb.getBaudrate()) {
319                 case 300:
320                     data[currentPos] = (byte) 0xB8;
321                     ;
322                     break;
323                 case 600:
324                     data[currentPos] = (byte) 0xB9;
325                     break;
326                 case 1200:
327                     data[currentPos] = (byte) 0xBA;
328                     break;
329                 case 2400:
330                     data[currentPos] = (byte) 0xBB;
331                     break;
332                 case 4800:
333                     data[currentPos] = (byte) 0xBC;
334                     break;
335                 case 9600:
336                     data[currentPos] = (byte) 0xBD;
337                     break;
338                 case 19200:
339                     data[currentPos] = (byte) 0xBE;
340                     break;
341                 case 38400:
342                     data[currentPos] = (byte) 0xBF;
343                     break;
344 
345             }
346         } else if (frame instanceof SynchronizeAction) {
347             data[currentPos] = 0x54;
348         }
349         currentPos++;
350     }
351 
352     private void pushCIField(LongFrame frame) {
353         if (frame instanceof ApplicationReset) {
354             data[currentPos] = 0x50;
355         } else if (frame instanceof SendUserData) {
356             data[currentPos] = 0x51;
357         } else if (frame instanceof SelectionOfSlaves) {
358             data[currentPos] = 0x52;
359         } else if (frame instanceof GeneralApplicationError) {
360             data[currentPos] = 0x70;
361         } else if (frame instanceof UserDataResponse) {
362             data[currentPos] = 0x72;
363         }
364         currentPos++;
365     }
366 
367     private void pushData(DataBlock db) {
368         switch (db.getDataFieldCode()) {
369             case NO_DATA:
370                 break;
371             case _8_BIT_INTEGER:
372                 pushInteger(((ByteDataBlock) db).getValue(), 1);
373                 break;
374             case _16_BIT_INTEGER:
375                 if (db instanceof DateDataBlock) {
376                     pushDate((DateDataBlock) db);
377                 } else {
378                     pushInteger(((ShortDataBlock) db).getValue(), 2);
379                 }
380                 break;
381             case _24_BIT_INTEGER:
382                 pushInteger(((IntegerDataBlock) db).getValue(), 3);
383                 break;
384             case _32_BIT_INTEGER:
385                 if (db instanceof DateAndTimeDataBlock) {
386                     pushTimeStamp((DateAndTimeDataBlock) db);
387                 } else {
388                     pushInteger(((IntegerDataBlock) db).getValue(), 4);
389                 }
390                 break;
391             case _32_BIT_REAL:
392                 pushInteger(Float.floatToIntBits(((RealDataBlock) db).getValue()), 4);
393                 break;
394             case _48_BIT_INTEGER:
395                 pushInteger(((LongDataBlock) db).getValue(), 6);
396                 break;
397             case _64_BIT_INTEGER:
398                 if (db instanceof EnhancedIdentificationDataBlock) {
399                     pushEnhancedIdentificationDataBlockLong((EnhancedIdentificationDataBlock) db, currentPos);
400                 } else {
401                     pushInteger(((LongDataBlock) db).getValue(), 8);
402                 }
403                 break;
404             case SELECTION_FOR_READOUT:
405                 break;
406             case _2_DIGIT_BCD:
407                 if (((ByteDataBlock) db).isBcdError()) {
408                     pushBcdError(((BcdValue) db).getBcdError());
409                 } else {
410                     pushBcd(((ByteDataBlock) db).getValue(), 2);
411                 }
412                 break;
413             case _4_DIGIT_BCD:
414                 if (((ShortDataBlock) db).isBcdError()) {
415                     pushBcdError(((BcdValue) db).getBcdError());
416                 } else {
417                     pushBcd(((ShortDataBlock) db).getValue(), 4);
418                 }
419                 break;
420             case _6_DIGIT_BCD:
421                 if (((IntegerDataBlock) db).isBcdError()) {
422                     pushBcdError(((BcdValue) db).getBcdError());
423                 } else {
424                     pushBcd(((IntegerDataBlock) db).getValue(), 6);
425                 }
426                 break;
427             case _8_DIGIT_BCD:
428                 if (db instanceof EnhancedIdentificationDataBlock) {
429                     pushEnhancedIdentificationDataBlockShort((EnhancedIdentificationDataBlock) db);
430                 } else {
431                     if (((IntegerDataBlock) db).isBcdError()) {
432                         pushBcdError(((BcdValue) db).getBcdError());
433                     } else {
434                         pushBcd(((IntegerDataBlock) db).getValue(), 8);
435                     }
436                 }
437                 break;
438             case VARIABLE_LENGTH:
439                 if (db instanceof StringDataBlock) {
440                     pushString(((StringDataBlock) db).getValue());
441                 } else {
442                     //TODO BCD
443                     throw new RuntimeException("pushData variable length " + db.getClass().getName());
444                 }
445                 break;
446             case _12_DIGIT_BCD:
447                 if (((LongDataBlock) db).isBcdError()) {
448                     pushBcdError(((BcdValue) db).getBcdError());
449                 } else {
450                     pushBcd(((LongDataBlock) db).getValue(), 12);
451                 }
452                 break;
453             case SPECIAL_FUNCTION_GLOBAL_READOUT_REQUEST:
454                 break;
455             case SPECIAL_FUNCTION_IDLE_FILLER:
456                 break;
457             case SPECIAL_FUNCTION_MAN_SPEC_DATA_LAST_PACKET:
458             case SPECIAL_FUNCTION_MAN_SPEC_DATA_PACKETS_FOLLOWS:
459                 pushBytes(((RawDataBlock) db).getValue());
460                 break;
461             default:
462                 throw new RuntimeException("push Data " + db.getDataFieldCode());
463         }
464     }
465 
466     private void pushDataInformationBlock(DataBlock db) {
467         pushDIF(db);
468         for (int i = 0; i < 10; i++) {
469             if (!needDIFE(db, i)) {
470                 break;
471             }
472             pushDIFE(db, i);
473         }
474     }
475 
476     private void pushDataRecordHeader(DataBlock db) {
477         pushDataInformationBlock(db);
478         pushValueInformationBlock(db);
479     }
480 
481     private void pushDate(DateDataBlock dateDataBlock) {
482         Calendar cal = Calendar.getInstance();
483         cal.setTime(dateDataBlock.getValue());
484         int val = cal.get(Calendar.DAY_OF_MONTH);
485         val |= (((cal.get(Calendar.YEAR) - 2000) & 0x07) << 5);
486         val |= (((cal.get(Calendar.YEAR) - 2000) & 0x78) << 9);
487         val |= ((cal.get(Calendar.MONTH) + 1) << 8);
488         pushInteger(val, 2);
489     }
490 
491     private void pushDIF(DataBlock db) {
492         data[currentPos] = db.getDataFieldCode().code;
493         switch (db.getDataFieldCode()) {
494             case SPECIAL_FUNCTION_MAN_SPEC_DATA_LAST_PACKET:
495                 data[currentPos++] = 0x0F;
496                 break;
497             case SPECIAL_FUNCTION_MAN_SPEC_DATA_PACKETS_FOLLOWS:
498                 data[currentPos++] = 0x1F;
499                 break;
500             case SPECIAL_FUNCTION_IDLE_FILLER:
501                 data[currentPos++] = 0x2F;
502                 break;
503             case SPECIAL_FUNCTION_GLOBAL_READOUT_REQUEST:
504                 data[currentPos++] = 0x7F;
505                 return;
506             default:
507                 data[currentPos] |= needDIFE(db, 0) ? Decoder.EXTENTION_BIT : 0x00;
508                 if (db.getFunctionField() != null) {
509                     data[currentPos] |= db.getFunctionField().code;
510                 }
511                 data[currentPos++] |= (db.getStorageNumber() & 0x01) << 6;
512         }
513     }
514 
515     private void pushDIFE(DataBlock db, int index) {
516         data[currentPos] = needDIFE(db, index + 1) ? Decoder.EXTENTION_BIT : 0x00;
517         data[currentPos] |= (db.getStorageNumber() >> (1 + index * 4)) & 0x0F;
518         data[currentPos] |= ((db.getTariff() >> (index * 2)) << 0x04) & 0x30;
519         data[currentPos++] |= ((db.getSubUnit() >> index) << 0x06) & 0x40;
520     }
521 
522     private void pushEnhancedIdentificationDataBlockLong(EnhancedIdentificationDataBlock db, int currentPos) {
523         pushBcd(db.getId(), 8);
524         pushManufacturer(db.getMan());
525         pushInteger(db.getVersion(), 1);
526         pushInteger(db.getMedium().getId(), 1);
527     }
528 
529     private void pushEnhancedIdentificationDataBlockShort(EnhancedIdentificationDataBlock db) {
530         pushBcd(db.getId(), 8);
531     }
532 
533     private void pushFixedDataHeader(UserDataResponse frame) {
534         pushBcd(frame.getIdentNumber(), 8);
535         pushManufacturer(frame.getManufacturer());
536         data[currentPos++] = frame.getVersion();
537         data[currentPos++] = (byte) frame.getMedium().getId();
538         data[currentPos++] = (byte) frame.getAccessNumber();
539         data[currentPos++] = StatusCode.toId(frame.getStatus());
540         pushInteger(frame.getSignature(), 2);
541     }
542 
543     private void pushGeneralApplicationError(GeneralApplicationError generalApplicationError) {
544         data[currentPos++] = generalApplicationError.getError().id;
545     }
546 
547     private void pushInteger(long value, int bytes) {
548         for (int i = bytes - 1; i >= 0; i--) {
549             data[currentPos++] = (byte) (value & 0xFF);
550             value >>= 8;
551         }
552     }
553 
554     private void pushManufacturer(String man) {
555         pushInteger(MBusUtils.man2Short(man), 2);
556     }
557 
558     private void pushObjectAction(DataBlock db) {
559         pushInteger(db.getAction().id, 1);
560     }
561 
562     private void pushSelectionOfSlavesDataHeader(SelectionOfSlaves frame) {
563         pushInteger(frame.getBcdMaskedId(), 4);
564         pushInteger(frame.getMaskedMan(), 2);
565         data[currentPos++] = (byte) frame.getMaskedVersion();
566         data[currentPos++] = (byte) frame.getMaskedMedium();
567     }
568 
569     private void pushString(String value) {
570         pushInteger(value.length(), 1);
571         for (int i = value.length() - 1; i >= 0; i--) {
572             pushInteger((byte) value.charAt(i), 1);
573         }
574     }
575 
576     private void pushTimeStamp(DateAndTimeDataBlock dateAndTimeDataBlock) {
577         Calendar cal = Calendar.getInstance();
578         cal.setTime(dateAndTimeDataBlock.getValue());
579         int val = dateAndTimeDataBlock.isValid() ? 0x00 : 0x80;
580         val |= dateAndTimeDataBlock.isSummerTime() ? 0x8000 : 0x00;
581         val |= dateAndTimeDataBlock.isRes1() ? 0x40 : 0x00;
582         val |= dateAndTimeDataBlock.isRes2() ? 0x4000 : 0x00;
583         val |= dateAndTimeDataBlock.isRes3() ? 0x2000 : 0x00;
584         val |= cal.get(Calendar.MINUTE);
585         val |= cal.get(Calendar.HOUR_OF_DAY) << 8;
586         val |= cal.get(Calendar.DAY_OF_MONTH) << 16;
587         val |= (cal.get(Calendar.MONTH) + 1) << 24;
588         val |= (((cal.get(Calendar.YEAR) - 2000) & 0x07) << 21) + (((cal.get(Calendar.YEAR) - 2000) & 0x78) << 25);
589         pushInteger(val, 4);
590     }
591 
592     private void pushValueInformationBlock(DataBlock db) {
593         pushVIF(db);
594         if (db.getAction() != null) {
595             pushObjectAction(db);
596         } else {
597             for (int i = 0; i < 10; i++) {
598                 if (!needVIFE(db, i)) {
599                     break;
600                 }
601                 pushVIFE(db, i);
602             }
603         }
604     }
605 
606     private void pushVariableDataBlock(DataBlock db) {
607         pushDataRecordHeader(db);
608         pushData(db);
609     }
610 
611     private void pushVariableDataBlocks(LongFrame frame) {
612         for (DataBlock db : frame) {
613             pushVariableDataBlock(db);
614         }
615     }
616 
617     private void pushVariableDataStructure(UserDataResponse frame) {
618         pushFixedDataHeader(frame);
619         pushVariableDataBlocks(frame);
620     }
621 
622     private void pushVIF(DataBlock db) {
623         if (db.getVif() == null) {
624         } else if (db.getVif() instanceof VifPrimary) {
625             data[currentPos] = needVIFE(db, 0) ? Decoder.EXTENTION_BIT : 0x00;
626             data[currentPos++] |= ((VifPrimary) db.getVif()).getTableIndex();
627         } else if (db.getVif() instanceof VifFB) {
628             data[currentPos++] = (byte) 0xFB;
629             data[currentPos] = needVIFE(db, 0) ? Decoder.EXTENTION_BIT : 0x00;
630             data[currentPos++] |= ((VifFB) db.getVif()).getTableIndex();
631         } else if (db.getVif() instanceof VifFD) {
632             data[currentPos++] = (byte) 0xFD;
633             data[currentPos] = needVIFE(db, 0) ? Decoder.EXTENTION_BIT : 0x00;
634             data[currentPos++] |= ((VifFD) db.getVif()).getTableIndex();
635         } else if (db.getVif() instanceof VifAscii) {
636             data[currentPos++] = (byte) (needVIFE(db, 0) ? 0xFC : 0x7C);
637             pushString(((VifAscii) db.getVif()).getValue());
638         } else if (db.getVif() instanceof VifManufacturerSpecific) {
639             data[currentPos] = needVIFE(db, 0) ? Decoder.EXTENTION_BIT : 0x00;
640             data[currentPos++] |= 0x7F;
641         } else {
642             throw new RuntimeException("Unknown vif " + db.getVif());
643         }
644     }
645 
646     private void pushVIFE(DataBlock db, int index) {
647         data[currentPos] = needVIFE(db, index + 1) ? Decoder.EXTENTION_BIT : 0x00;
648         if (db.getVifes()[index] instanceof VifePrimary) {
649             data[currentPos++] |= ((VifePrimary) db.getVifes()[index]).getTableIndex();
650         } else if (db.getVifes()[index] instanceof VifeError) {
651             data[currentPos++] |= ((VifeError) db.getVifes()[index]).getTableIndex();
652         } else if (db.getVifes()[index] instanceof VifeManufacturerSpecific) {
653             data[currentPos++] |= ((VifeManufacturerSpecific) db.getVifes()[index]).getVifeValue();
654 //        } else if (db.getVifes().get(index) instanceof VifeObjectAction) {
655             //TODO
656         }
657     }
658 
659     private void writeChecksumAndStop(int start, int chIndex) {
660         data[chIndex] = 0;
661         for (int i = start; i < chIndex; i++) {
662             data[chIndex] += data[i];
663         }
664         data[chIndex + 1] = 0x16;
665         data = Arrays.copyOf(data, chIndex + 2);
666     }
667 
668     private void writeLenght(byte i) {
669         data[1] = i;
670         data[2] = i;
671     }
672 }