altoslib: Set time in state for KML output correctly
[fw/altos] / altoslib / AltosEeprom.java
1 /*
2  * Copyright © 2017 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  */
14
15 package org.altusmetrum.altoslib_12;
16
17 import java.util.*;
18 import java.io.*;
19
20 public class AltosEeprom {
21
22         private AltosJson       config;
23         ArrayList<Byte>         data;
24         private AltosConfigData config_data;
25
26         /*
27          * Public accessor APIs
28          */
29         public int data8(int offset) {
30                 return ((int) data.get(offset)) & 0xff;
31         }
32
33         public int data16(int offset) {
34                 return data8(offset) | (data8(offset+1) << 8);
35         }
36
37         public int data24(int offset) {
38                 return (data8(offset) |
39                         (data8(offset+1) << 8) |
40                         (data8(offset+2) << 16));
41         }
42
43         public int data32(int offset) {
44                 return (data8(offset) |
45                         (data8(offset+1) << 8) |
46                         (data8(offset+2) << 16) |
47                         (data8(offset+3) << 24));
48         }
49
50         public int size() {
51                 return data.size();
52         }
53
54         public AltosConfigData config_data() {
55                 if (config_data == null) {
56                         config_data = (AltosConfigData) config.make(AltosConfigData.class);
57                         if (config_data == null)
58                                 config_data = new AltosConfigData();
59
60                         if (config_data.log_format == AltosLib.AO_LOG_FORMAT_UNKNOWN) {
61                                 config_data.log_format = AltosLib.AO_LOG_FORMAT_FULL;
62                                 if (config_data.product != null) {
63                                         if (config_data.product.startsWith("TeleMetrum"))
64                                                 config_data.log_format = AltosLib.AO_LOG_FORMAT_FULL;
65                                         else if (config_data.product.startsWith("TeleMini"))
66                                                 config_data.log_format = AltosLib.AO_LOG_FORMAT_TINY;
67                                 }
68                         }
69                 }
70                 return config_data;
71         }
72
73         private void write_config(Writer w) throws IOException {
74                 config.write(w, 0, true);
75                 w.append('\n');
76         }
77
78         /*
79          * Private I/O APIs
80          */
81         private void write_data(Writer w) throws IOException {
82                 PrintWriter pw = new PrintWriter(w);
83
84                 for (int i = 0; i < data.size(); i++) {
85                         if (i > 0) {
86                                 if ((i & 0x1f) == 0)
87                                         pw.printf("\n");
88                                 else
89                                         pw.printf(" ");
90                         }
91                         pw.printf("%02x", data.get(i));
92                 }
93                 w.append('\n');
94         }
95
96         private boolean read_config(InputStream stream) throws IOException {
97                 config = AltosJson.fromInputStream(stream);
98                 if (config == null)
99                         return false;
100                 return true;
101         }
102
103         private String read_line(InputStream stream) throws IOException {
104                 StringBuffer    buffer = null;
105                 int             c;
106
107                 for (;;) {
108                         c = stream.read();
109                         if (c == -1 && buffer == null)
110                                 return null;
111                         if (buffer == null)
112                                 buffer = new StringBuffer();
113                         if (c == -1 || c == '\n')
114                                 return buffer.toString();
115                         buffer.append((char) c);
116                 }
117         }
118
119         private boolean read_data(InputStream stream) throws IOException {
120                 String                  s;
121
122                 data = new ArrayList<Byte>();
123                 while ((s = read_line(stream)) != null) {
124
125                         String[] tokens = s.split("\\s+");
126
127                         for (int i = 0; i < tokens.length; i++) {
128                                 if (tokens[i].length() > 0) {
129                                         try {
130                                                 data.add((byte) AltosLib.fromhex(tokens[i]));
131                                         } catch (NumberFormatException e) {
132                                                 throw new IOException(e.toString());
133                                         }
134                                 }
135                         }
136                 }
137                 return true;
138         }
139
140         private boolean read_old_config(InputStream stream) throws IOException {
141                 AltosConfigData cfg = new AltosConfigData();
142                 for (;;) {
143                         boolean done = false;
144
145                         /* The data starts with an upper case F character followed by a space */
146                         stream.mark(2);
147                         int     first = stream.read();
148                         if (first == 'F') {
149                                 int second =  stream.read();
150                                 if (second == ' ')
151                                         done = true;
152                         }
153                         stream.reset();
154                         if (done)
155                                 break;
156
157                         String line = read_line(stream);
158                         if (line == null)
159                                 return false;
160                         cfg.parse_line(line);
161                 }
162                 config = new AltosJson(cfg);
163                 return true;
164         }
165
166         private boolean read_old_data(InputStream stream) throws IOException {
167                 String line;
168
169                 data = new ArrayList<Byte>();
170                 while ((line = read_line(stream)) != null) {
171                         String[] tokens = line.split("\\s+");
172
173                         /* Make sure there's at least a type and time */
174                         if (tokens.length < 2)
175                                 break;
176
177                         /* packet type */
178                         if (tokens[0].length() != 1)
179                                 break;
180                         int start = data.size();
181
182                         if (config_data().log_format != AltosLib.AO_LOG_FORMAT_TINY) {
183                                 byte cmd = (byte) tokens[0].codePointAt(0);
184                                 data.add(cmd);
185
186                                 int time = AltosLib.fromhex(tokens[1]);
187
188                                 data.add((byte) 0);
189                                 data.add((byte) (time & 0xff));
190                                 data.add((byte) (time >> 8));
191                         }
192                         if (tokens.length == 4) {
193                                 /* Handle ancient log files */
194                                 if (config_data().log_format == AltosLib.AO_LOG_FORMAT_TINY) {
195                                         /*
196                                          * Ancient TeleMini log files stored "extra" data to pretend
197                                          * that it was a TeleMetrum device. Throw that away and
198                                          * just save the actual log data.
199                                          */
200                                         int a = AltosLib.fromhex(tokens[2]);
201                                         int b = AltosLib.fromhex(tokens[3]);
202                                         if (a != 0)
203                                                 b = 0x8000 | a;
204                                         data.add((byte) (b & 0xff));
205                                         data.add((byte) ((b >> 8)));
206                                 } else {
207                                         for (int i = 2; i < tokens.length; i++) {
208                                                 int v = AltosLib.fromhex(tokens[i]);
209                                                 data.add((byte) (v & 0xff));
210                                                 data.add((byte) ((v >> 8)));
211                                         }
212                                         /* Re-compute the checksum byte */
213                                         data.set(start + 1, (byte) (256 - AltosConvert.checksum(data, start, data.size() - start)));
214                                 }
215                         } else {
216                                 for (int i = 2; i < tokens.length; i++)
217                                         data.add((byte) AltosLib.fromhex(tokens[i]));
218                                 /* Re-compute the checksum byte */
219                                 data.set(start + 1, (byte) (256 - AltosConvert.checksum(data, start, data.size() - start)));
220                         }
221                 }
222                 return true;
223         }
224
225         private void read(InputStream stream) throws IOException {
226                 BufferedInputStream     bis = new BufferedInputStream(stream);
227
228                 bis.mark(1);
229                 int c = bis.read();
230                 bis.reset();
231
232                 if (c == '{') {
233                         if (!read_config(bis))
234                                 throw new IOException("failed to read config");
235                         if (!read_data(bis))
236                                 throw new IOException("failed to read data");
237                 } else {
238                         if (!read_old_config(bis))
239                                 throw new IOException("failed to read old config");
240                         if (!read_old_data(bis))
241                                 throw new IOException("failed to read old data");
242                 }
243         }
244
245         /*
246          * Public APIs for I/O
247          */
248         public void write(Writer w) throws IOException {
249                 write_config(w);
250                 write_data(w);
251         }
252
253         public String toString() {
254                 try {
255                         Writer  w = new StringWriter();
256
257                         write(w);
258                         return w.toString();
259                 } catch (Exception e) {
260                         return null;
261                 }
262         }
263
264         public void print() throws IOException {
265                 System.out.printf("%s", toString());
266         }
267
268         /*
269          * Constructors
270          */
271         public AltosEeprom(InputStream stream) throws IOException {
272                 read(stream);
273         }
274
275         public AltosEeprom(String s) throws IOException {
276                 read(new AltosStringInputStream(s));
277         }
278
279         public AltosEeprom(AltosJson config, ArrayList<Byte> data) {
280                 this.config = config;
281                 this.data = data;
282         }
283
284         public AltosEeprom(AltosConfigData config_data, ArrayList<Byte> data) {
285                 this.config = new AltosJson(config_data);
286                 this.data = data;
287         }
288
289         public AltosEeprom() {
290         }
291 }