@leonardojc/capacitor-ioboard 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,27 +2,36 @@ package com.leonardojc.capacitor.ioboard;
|
|
|
2
2
|
|
|
3
3
|
import android.util.Log;
|
|
4
4
|
import android.util.Base64;
|
|
5
|
+
import android.os.Handler;
|
|
6
|
+
import android.os.Looper;
|
|
5
7
|
import java.util.Arrays;
|
|
8
|
+
import java.util.ArrayList;
|
|
9
|
+
import java.util.List;
|
|
6
10
|
import java.util.concurrent.TimeUnit;
|
|
11
|
+
import java.util.concurrent.CountDownLatch;
|
|
12
|
+
import java.io.File;
|
|
13
|
+
import java.io.FileInputStream;
|
|
14
|
+
import java.io.FileOutputStream;
|
|
15
|
+
import java.io.IOException;
|
|
7
16
|
|
|
8
17
|
/**
|
|
9
18
|
* Manager class for IOBOARD communication protocol
|
|
10
19
|
* This class integrates with the @leonardojc/capacitor-serial-port plugin
|
|
20
|
+
* Uses IOBoardProtocolUtils for MTC3P08L protocol implementation
|
|
11
21
|
*/
|
|
12
22
|
public class IOBoardManager {
|
|
13
23
|
|
|
14
24
|
private static final String TAG = "IOBoardManager";
|
|
15
|
-
private static final int SOI = 0x0D; // Start of frame
|
|
16
|
-
private static final int EOI = 0x0A; // End of frame
|
|
17
|
-
|
|
18
|
-
// Frame types
|
|
19
|
-
private static final int FRAME_TYPE_STATUS_QUERY = 0x00;
|
|
20
|
-
private static final int FRAME_TYPE_SINGLE_PALLET = 0x01;
|
|
21
|
-
private static final int FRAME_TYPE_FULL_PALLET = 0x02;
|
|
22
|
-
private static final int FRAME_TYPE_OTA_REQUEST = 0xF1;
|
|
23
|
-
private static final int FRAME_TYPE_OTA_DATA = 0xF2;
|
|
24
25
|
|
|
26
|
+
// Serial communication variables
|
|
27
|
+
private FileInputStream serialInputStream;
|
|
28
|
+
private FileOutputStream serialOutputStream;
|
|
29
|
+
private String currentPortPath;
|
|
25
30
|
private boolean isConnected = false;
|
|
31
|
+
private Thread readerThread;
|
|
32
|
+
private Handler mainHandler;
|
|
33
|
+
private String responseBuffer = "";
|
|
34
|
+
private CountDownLatch responseLatch;
|
|
26
35
|
|
|
27
36
|
// Response classes
|
|
28
37
|
public static class IOBoardResponse {
|
|
@@ -127,89 +136,177 @@ public class IOBoardManager {
|
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
/**
|
|
130
|
-
*
|
|
139
|
+
* Constructor - Initialize the manager
|
|
131
140
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
public IOBoardManager() {
|
|
142
|
+
mainHandler = new Handler(Looper.getMainLooper());
|
|
143
|
+
Log.d(TAG, "IOBoardManager initialized");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Open serial port for communication
|
|
148
|
+
*/
|
|
149
|
+
public boolean openSerialPort(String portPath, int baudRate) {
|
|
150
|
+
if (isConnected) {
|
|
151
|
+
Log.w(TAG, "Port already connected. Close current connection first.");
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
// Verificar que el puerto existe
|
|
157
|
+
File portFile = new File(portPath);
|
|
158
|
+
if (!portFile.exists()) {
|
|
159
|
+
Log.e(TAG, "Port " + portPath + " does not exist");
|
|
160
|
+
return false;
|
|
145
161
|
}
|
|
162
|
+
|
|
163
|
+
// Intentar abrir streams
|
|
164
|
+
serialInputStream = new FileInputStream(portFile);
|
|
165
|
+
serialOutputStream = new FileOutputStream(portFile);
|
|
166
|
+
|
|
167
|
+
currentPortPath = portPath;
|
|
168
|
+
isConnected = true;
|
|
169
|
+
|
|
170
|
+
// Iniciar hilo de lectura
|
|
171
|
+
startReaderThread();
|
|
172
|
+
|
|
173
|
+
Log.d(TAG, "Port opened successfully: " + portPath + " at " + baudRate + " baud");
|
|
174
|
+
return true;
|
|
175
|
+
|
|
176
|
+
} catch (IOException e) {
|
|
177
|
+
Log.e(TAG, "Error opening port: " + e.getMessage());
|
|
178
|
+
isConnected = false;
|
|
179
|
+
return false;
|
|
146
180
|
}
|
|
147
|
-
|
|
148
|
-
return crc;
|
|
149
181
|
}
|
|
150
182
|
|
|
151
183
|
/**
|
|
152
|
-
*
|
|
184
|
+
* Close serial port
|
|
153
185
|
*/
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
186
|
+
public void closeSerialPort() {
|
|
187
|
+
if (!isConnected) return;
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
// Detener hilo de lectura
|
|
191
|
+
stopReaderThread();
|
|
192
|
+
|
|
193
|
+
// Cerrar streams
|
|
194
|
+
if (serialInputStream != null) {
|
|
195
|
+
serialInputStream.close();
|
|
196
|
+
serialInputStream = null;
|
|
197
|
+
}
|
|
198
|
+
if (serialOutputStream != null) {
|
|
199
|
+
serialOutputStream.close();
|
|
200
|
+
serialOutputStream = null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
isConnected = false;
|
|
204
|
+
currentPortPath = null;
|
|
205
|
+
Log.d(TAG, "Port closed successfully");
|
|
206
|
+
|
|
207
|
+
} catch (IOException e) {
|
|
208
|
+
Log.e(TAG, "Error closing port: " + e.getMessage());
|
|
172
209
|
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Start reader thread for incoming data
|
|
214
|
+
*/
|
|
215
|
+
private void startReaderThread() {
|
|
216
|
+
if (readerThread != null && readerThread.isAlive()) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
readerThread = new Thread(() -> {
|
|
221
|
+
byte[] buffer = new byte[1024];
|
|
222
|
+
|
|
223
|
+
while (isConnected && !Thread.currentThread().isInterrupted()) {
|
|
224
|
+
try {
|
|
225
|
+
if (serialInputStream.available() > 0) {
|
|
226
|
+
int bytesRead = serialInputStream.read(buffer);
|
|
227
|
+
if (bytesRead > 0) {
|
|
228
|
+
String receivedData = new String(buffer, 0, bytesRead, "ISO-8859-1");
|
|
229
|
+
synchronized (this) {
|
|
230
|
+
responseBuffer += receivedData;
|
|
231
|
+
if (responseLatch != null) {
|
|
232
|
+
responseLatch.countDown();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
Log.d(TAG, "Data received (length): " + bytesRead);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
Thread.sleep(10); // Small delay to prevent excessive CPU usage
|
|
239
|
+
} catch (IOException | InterruptedException e) {
|
|
240
|
+
if (isConnected) {
|
|
241
|
+
Log.e(TAG, "Error in reader thread: " + e.getMessage());
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
173
247
|
|
|
174
|
-
|
|
175
|
-
int[] crcData = new int[1 + 1 + data.length];
|
|
176
|
-
crcData[0] = address;
|
|
177
|
-
crcData[1] = frameType;
|
|
178
|
-
System.arraycopy(data, 0, crcData, 2, data.length);
|
|
179
|
-
|
|
180
|
-
int crc = calculateCRC16(crcData);
|
|
181
|
-
|
|
182
|
-
// CRC (high byte first)
|
|
183
|
-
frame[index++] = (crc >> 8) & 0xFF;
|
|
184
|
-
frame[index++] = crc & 0xFF;
|
|
185
|
-
|
|
186
|
-
// EOI
|
|
187
|
-
frame[index] = EOI;
|
|
188
|
-
|
|
189
|
-
return frame;
|
|
248
|
+
readerThread.start();
|
|
190
249
|
}
|
|
191
250
|
|
|
192
251
|
/**
|
|
193
|
-
*
|
|
252
|
+
* Stop reader thread
|
|
194
253
|
*/
|
|
195
|
-
private
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
254
|
+
private void stopReaderThread() {
|
|
255
|
+
if (readerThread != null && readerThread.isAlive()) {
|
|
256
|
+
readerThread.interrupt();
|
|
257
|
+
try {
|
|
258
|
+
readerThread.join(1000); // Wait up to 1 second
|
|
259
|
+
} catch (InterruptedException e) {
|
|
260
|
+
Thread.currentThread().interrupt();
|
|
261
|
+
}
|
|
199
262
|
}
|
|
200
|
-
return bytes;
|
|
201
263
|
}
|
|
202
264
|
|
|
203
265
|
/**
|
|
204
|
-
*
|
|
266
|
+
* Send command via serial port and wait for response
|
|
205
267
|
*/
|
|
206
|
-
private String
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
268
|
+
private String sendCommandAndWaitResponse(byte[] command, int timeoutMs) throws Exception {
|
|
269
|
+
if (!isConnected || serialOutputStream == null) {
|
|
270
|
+
throw new Exception("Serial port not connected");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
String hexCommand = IOBoardProtocolUtils.frameToHex(command);
|
|
274
|
+
Log.d(TAG, "Sending command (HEX): " + hexCommand);
|
|
275
|
+
|
|
276
|
+
synchronized (this) {
|
|
277
|
+
// Clear response buffer
|
|
278
|
+
responseBuffer = "";
|
|
279
|
+
responseLatch = new CountDownLatch(1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
// Convert command to Latin-1 string and write to serial port
|
|
284
|
+
String commandString = IOBoardProtocolUtils.frameToLatin1String(command);
|
|
285
|
+
byte[] bytes = commandString.getBytes("ISO-8859-1");
|
|
286
|
+
serialOutputStream.write(bytes);
|
|
287
|
+
serialOutputStream.flush();
|
|
288
|
+
|
|
289
|
+
Log.d(TAG, "Command sent, waiting for response...");
|
|
290
|
+
|
|
291
|
+
// Wait for response with timeout
|
|
292
|
+
boolean responseReceived = responseLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
|
|
293
|
+
|
|
294
|
+
if (responseReceived) {
|
|
295
|
+
synchronized (this) {
|
|
296
|
+
String response = responseBuffer;
|
|
297
|
+
Log.d(TAG, "Response received (length): " + response.length());
|
|
298
|
+
return response;
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
throw new Exception("Timeout waiting for response");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
} catch (IOException e) {
|
|
305
|
+
throw new Exception("Error sending command: " + e.getMessage());
|
|
306
|
+
} catch (InterruptedException e) {
|
|
307
|
+
Thread.currentThread().interrupt();
|
|
308
|
+
throw new Exception("Command interrupted");
|
|
211
309
|
}
|
|
212
|
-
return sb.toString();
|
|
213
310
|
}
|
|
214
311
|
|
|
215
312
|
// New simplified API methods
|
|
@@ -221,11 +318,12 @@ public class IOBoardManager {
|
|
|
221
318
|
Log.d(TAG, "Connecting to serial port: " + config.portPath + " at " + config.baudRate + " baud");
|
|
222
319
|
|
|
223
320
|
try {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
321
|
+
boolean success = openSerialPort(config.portPath, config.baudRate);
|
|
322
|
+
if (success) {
|
|
323
|
+
return new IOBoardResponse(true, "Connected to " + config.portPath + " successfully");
|
|
324
|
+
} else {
|
|
325
|
+
return new IOBoardResponse(false, "Failed to open serial port: " + config.portPath);
|
|
326
|
+
}
|
|
229
327
|
|
|
230
328
|
} catch (Exception e) {
|
|
231
329
|
Log.e(TAG, "Error connecting", e);
|
|
@@ -240,9 +338,7 @@ public class IOBoardManager {
|
|
|
240
338
|
Log.d(TAG, "Disconnecting from serial port");
|
|
241
339
|
|
|
242
340
|
try {
|
|
243
|
-
|
|
244
|
-
isConnected = false;
|
|
245
|
-
|
|
341
|
+
closeSerialPort();
|
|
246
342
|
return new IOBoardResponse(true, "Disconnected successfully");
|
|
247
343
|
|
|
248
344
|
} catch (Exception e) {
|
|
@@ -262,15 +358,33 @@ public class IOBoardManager {
|
|
|
262
358
|
}
|
|
263
359
|
|
|
264
360
|
try {
|
|
265
|
-
// Build status query frame
|
|
266
|
-
|
|
361
|
+
// Build status query frame using IOBoardProtocolUtils
|
|
362
|
+
byte[] frame = IOBoardProtocolUtils.createStatusQuery((byte) address);
|
|
363
|
+
|
|
364
|
+
Log.d(TAG, "Sending status query frame: " + IOBoardProtocolUtils.frameToHex(frame));
|
|
267
365
|
|
|
268
|
-
|
|
366
|
+
// Send frame and wait for response
|
|
367
|
+
String response = sendCommandAndWaitResponse(frame, timeout);
|
|
368
|
+
|
|
369
|
+
// Parse response
|
|
370
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
371
|
+
|
|
372
|
+
if (!parsedResponse.success) {
|
|
373
|
+
return new StatusResponse(false, "Failed to parse response: " + parsedResponse.message, null);
|
|
374
|
+
}
|
|
269
375
|
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
376
|
+
// Extract status data from response
|
|
377
|
+
if (parsedResponse.data.length >= 4) {
|
|
378
|
+
int doorLockStatus = parsedResponse.data[0] & 0xFF;
|
|
379
|
+
int majorVersion = parsedResponse.data[1] & 0xFF;
|
|
380
|
+
int minorVersion = parsedResponse.data[2] & 0xFF;
|
|
381
|
+
int patchVersion = parsedResponse.data[3] & 0xFF;
|
|
382
|
+
|
|
383
|
+
StatusData statusData = new StatusData(doorLockStatus, new SoftwareVersion(majorVersion, minorVersion, patchVersion));
|
|
384
|
+
return new StatusResponse(true, "Status retrieved successfully", statusData);
|
|
385
|
+
} else {
|
|
386
|
+
return new StatusResponse(false, "Invalid response data length", null);
|
|
387
|
+
}
|
|
274
388
|
|
|
275
389
|
} catch (Exception e) {
|
|
276
390
|
Log.e(TAG, "Error getting status", e);
|
|
@@ -292,22 +406,25 @@ public class IOBoardManager {
|
|
|
292
406
|
// Convert LED color to RGB values
|
|
293
407
|
LEDControl ledControl = getLEDControlFromColor(ledColor);
|
|
294
408
|
|
|
295
|
-
// Build
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
data[6] = ledControl.blinkTimes;
|
|
304
|
-
data[7] = ledControl.blinkSpeed;
|
|
409
|
+
// Build single pallet control frame using IOBoardProtocolUtils
|
|
410
|
+
byte[] frame = IOBoardProtocolUtils.createSinglePalletControl(
|
|
411
|
+
(byte) address, palletNumber, true,
|
|
412
|
+
ledControl.red, ledControl.green, ledControl.blue,
|
|
413
|
+
ledControl.intensity, ledControl.blinkTimes, ledControl.blinkSpeed
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
Log.d(TAG, "Sending unlock pallet frame: " + IOBoardProtocolUtils.frameToHex(frame));
|
|
305
417
|
|
|
306
|
-
|
|
418
|
+
// Send frame and wait for response
|
|
419
|
+
String response = sendCommandAndWaitResponse(frame, timeout);
|
|
307
420
|
|
|
308
|
-
|
|
421
|
+
// Parse response
|
|
422
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
423
|
+
|
|
424
|
+
if (!parsedResponse.success) {
|
|
425
|
+
return new IOBoardResponse(false, "Failed to parse response: " + parsedResponse.message);
|
|
426
|
+
}
|
|
309
427
|
|
|
310
|
-
// TODO: Send frame via serial port plugin and receive response
|
|
311
428
|
return new IOBoardResponse(true, "Pallet " + palletNumber + " unlocked successfully");
|
|
312
429
|
|
|
313
430
|
} catch (Exception e) {
|
|
@@ -330,30 +447,35 @@ public class IOBoardManager {
|
|
|
330
447
|
// Convert LED color to RGB values
|
|
331
448
|
LEDControl ledControl = getLEDControlFromColor(ledColor);
|
|
332
449
|
|
|
333
|
-
// Build
|
|
334
|
-
int[]
|
|
335
|
-
int index = 0;
|
|
336
|
-
|
|
337
|
-
data[index++] = doorLockMask;
|
|
338
|
-
data[index++] = 0; // extendedControl
|
|
339
|
-
|
|
340
|
-
// Add LED controls for 8 pallets
|
|
450
|
+
// Build LED controls array for 8 pallets
|
|
451
|
+
int[][] ledControls = new int[8][6];
|
|
341
452
|
for (int i = 0; i < 8; i++) {
|
|
342
453
|
// If pallet is affected by mask, use specified LED color, otherwise turn off
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
data[index++] = led.blinkSpeed;
|
|
454
|
+
if ((doorLockMask & (1 << i)) != 0) {
|
|
455
|
+
ledControls[i] = new int[]{ledControl.red, ledControl.green, ledControl.blue,
|
|
456
|
+
ledControl.intensity, ledControl.blinkTimes, ledControl.blinkSpeed};
|
|
457
|
+
} else {
|
|
458
|
+
ledControls[i] = new int[]{0, 0, 0, 0, 0, 1}; // Off
|
|
459
|
+
}
|
|
350
460
|
}
|
|
351
461
|
|
|
352
|
-
|
|
462
|
+
// Build full pallet control frame using IOBoardProtocolUtils
|
|
463
|
+
byte[] frame = IOBoardProtocolUtils.createFullPalletControl(
|
|
464
|
+
(byte) address, doorLockMask, 0, ledControls
|
|
465
|
+
);
|
|
353
466
|
|
|
354
|
-
Log.d(TAG, "Sending multiple pallets control frame: " + frameToHex(frame));
|
|
467
|
+
Log.d(TAG, "Sending multiple pallets control frame: " + IOBoardProtocolUtils.frameToHex(frame));
|
|
468
|
+
|
|
469
|
+
// Send frame and wait for response
|
|
470
|
+
String response = sendCommandAndWaitResponse(frame, timeout);
|
|
471
|
+
|
|
472
|
+
// Parse response
|
|
473
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
474
|
+
|
|
475
|
+
if (!parsedResponse.success) {
|
|
476
|
+
return new IOBoardResponse(false, "Failed to parse response: " + parsedResponse.message);
|
|
477
|
+
}
|
|
355
478
|
|
|
356
|
-
// TODO: Send frame via serial port plugin and receive response
|
|
357
479
|
return new IOBoardResponse(true, "Multiple pallets controlled successfully");
|
|
358
480
|
|
|
359
481
|
} catch (Exception e) {
|
|
@@ -374,17 +496,52 @@ public class IOBoardManager {
|
|
|
374
496
|
|
|
375
497
|
try {
|
|
376
498
|
int deviceCount = endAddress - startAddress + 1;
|
|
377
|
-
DeviceInfo
|
|
499
|
+
List<DeviceInfo> deviceList = new ArrayList<>();
|
|
378
500
|
|
|
379
|
-
//
|
|
380
|
-
// For now, create mock responses
|
|
501
|
+
// Scan each address by sending status queries
|
|
381
502
|
for (int i = 0; i < deviceCount; i++) {
|
|
382
503
|
int address = startAddress + i;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
504
|
+
|
|
505
|
+
try {
|
|
506
|
+
// Build status query frame using IOBoardProtocolUtils
|
|
507
|
+
byte[] frame = IOBoardProtocolUtils.createStatusQuery((byte) address);
|
|
508
|
+
|
|
509
|
+
Log.d(TAG, "Scanning address " + address + ": " + IOBoardProtocolUtils.frameToHex(frame));
|
|
510
|
+
|
|
511
|
+
// Send frame and wait for response (with shorter timeout for scanning)
|
|
512
|
+
String response = sendCommandAndWaitResponse(frame, Math.min(timeout, 1000));
|
|
513
|
+
|
|
514
|
+
// Parse response
|
|
515
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
516
|
+
|
|
517
|
+
if (parsedResponse.success) {
|
|
518
|
+
// Device responded - extract status data
|
|
519
|
+
StatusData status = null;
|
|
520
|
+
if (parsedResponse.data.length >= 4) {
|
|
521
|
+
int doorLockStatus = parsedResponse.data[0] & 0xFF;
|
|
522
|
+
int majorVersion = parsedResponse.data[1] & 0xFF;
|
|
523
|
+
int minorVersion = parsedResponse.data[2] & 0xFF;
|
|
524
|
+
int patchVersion = parsedResponse.data[3] & 0xFF;
|
|
525
|
+
|
|
526
|
+
status = new StatusData(doorLockStatus, new SoftwareVersion(majorVersion, minorVersion, patchVersion));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
deviceList.add(new DeviceInfo(address, true, status));
|
|
530
|
+
Log.d(TAG, "Device found at address " + address);
|
|
531
|
+
} else {
|
|
532
|
+
// No response or invalid response
|
|
533
|
+
deviceList.add(new DeviceInfo(address, false, null));
|
|
534
|
+
Log.d(TAG, "No response from address " + address);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
} catch (Exception e) {
|
|
538
|
+
// Device not responding
|
|
539
|
+
deviceList.add(new DeviceInfo(address, false, null));
|
|
540
|
+
Log.d(TAG, "Error scanning address " + address + ": " + e.getMessage());
|
|
541
|
+
}
|
|
386
542
|
}
|
|
387
543
|
|
|
544
|
+
DeviceInfo[] devices = deviceList.toArray(new DeviceInfo[0]);
|
|
388
545
|
return new ScanResponse(true, "Scanned addresses " + startAddress + "-" + endAddress, devices);
|
|
389
546
|
|
|
390
547
|
} catch (Exception e) {
|
|
@@ -446,15 +603,33 @@ public class IOBoardManager {
|
|
|
446
603
|
}
|
|
447
604
|
|
|
448
605
|
try {
|
|
449
|
-
// Build status query frame
|
|
450
|
-
|
|
606
|
+
// Build status query frame using IOBoardProtocolUtils
|
|
607
|
+
byte[] frame = IOBoardProtocolUtils.createStatusQuery((byte) address);
|
|
608
|
+
|
|
609
|
+
Log.d(TAG, "Sending status query frame: " + IOBoardProtocolUtils.frameToHex(frame));
|
|
451
610
|
|
|
452
|
-
|
|
611
|
+
// Send frame and wait for response
|
|
612
|
+
String response = sendCommandAndWaitResponse(frame, 5000);
|
|
453
613
|
|
|
454
|
-
//
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
614
|
+
// Parse response
|
|
615
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
616
|
+
|
|
617
|
+
if (!parsedResponse.success) {
|
|
618
|
+
return new StatusResponse(false, "Failed to parse response: " + parsedResponse.message, null);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Extract status data from response
|
|
622
|
+
if (parsedResponse.data.length >= 4) {
|
|
623
|
+
int doorLockStatus = parsedResponse.data[0] & 0xFF;
|
|
624
|
+
int majorVersion = parsedResponse.data[1] & 0xFF;
|
|
625
|
+
int minorVersion = parsedResponse.data[2] & 0xFF;
|
|
626
|
+
int patchVersion = parsedResponse.data[3] & 0xFF;
|
|
627
|
+
|
|
628
|
+
StatusData statusData = new StatusData(doorLockStatus, new SoftwareVersion(majorVersion, minorVersion, patchVersion));
|
|
629
|
+
return new StatusResponse(true, "Status queried successfully", statusData);
|
|
630
|
+
} else {
|
|
631
|
+
return new StatusResponse(false, "Invalid response data length", null);
|
|
632
|
+
}
|
|
458
633
|
|
|
459
634
|
} catch (Exception e) {
|
|
460
635
|
Log.e(TAG, "Error querying status", e);
|
|
@@ -473,22 +648,25 @@ public class IOBoardManager {
|
|
|
473
648
|
}
|
|
474
649
|
|
|
475
650
|
try {
|
|
476
|
-
// Build
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
651
|
+
// Build single pallet control frame using IOBoardProtocolUtils
|
|
652
|
+
byte[] frame = IOBoardProtocolUtils.createSinglePalletControl(
|
|
653
|
+
(byte) address, palletNumber, doorLock,
|
|
654
|
+
ledControl.red, ledControl.green, ledControl.blue,
|
|
655
|
+
ledControl.intensity, ledControl.blinkTimes, ledControl.blinkSpeed
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
Log.d(TAG, "Sending single pallet control frame: " + IOBoardProtocolUtils.frameToHex(frame));
|
|
659
|
+
|
|
660
|
+
// Send frame and wait for response
|
|
661
|
+
String response = sendCommandAndWaitResponse(frame, 5000);
|
|
662
|
+
|
|
663
|
+
// Parse response
|
|
664
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
665
|
+
|
|
666
|
+
if (!parsedResponse.success) {
|
|
667
|
+
return new IOBoardResponse(false, "Failed to parse response: " + parsedResponse.message);
|
|
668
|
+
}
|
|
669
|
+
|
|
492
670
|
return new IOBoardResponse(true, "Single pallet controlled successfully");
|
|
493
671
|
|
|
494
672
|
} catch (Exception e) {
|
|
@@ -508,29 +686,33 @@ public class IOBoardManager {
|
|
|
508
686
|
}
|
|
509
687
|
|
|
510
688
|
try {
|
|
511
|
-
//
|
|
512
|
-
int[]
|
|
513
|
-
int index = 0;
|
|
514
|
-
|
|
515
|
-
data[index++] = doorLockControl;
|
|
516
|
-
data[index++] = extendedControl;
|
|
517
|
-
|
|
518
|
-
// Add LED controls for 8 pallets
|
|
689
|
+
// Convert LEDControl array to int[][] for IOBoardProtocolUtils
|
|
690
|
+
int[][] ledControlsArray = new int[8][6];
|
|
519
691
|
for (int i = 0; i < 8; i++) {
|
|
520
692
|
LEDControl led = (i < ledControls.length) ? ledControls[i] : new LEDControl(0, 0, 0, 0, 0, 0);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
data[index++] = led.blinkTimes;
|
|
526
|
-
data[index++] = led.blinkSpeed;
|
|
693
|
+
ledControlsArray[i] = new int[]{
|
|
694
|
+
led.red, led.green, led.blue,
|
|
695
|
+
led.intensity, led.blinkTimes, led.blinkSpeed
|
|
696
|
+
};
|
|
527
697
|
}
|
|
528
698
|
|
|
529
|
-
|
|
699
|
+
// Build full pallet control frame using IOBoardProtocolUtils
|
|
700
|
+
byte[] frame = IOBoardProtocolUtils.createFullPalletControl(
|
|
701
|
+
(byte) address, doorLockControl, extendedControl, ledControlsArray
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
Log.d(TAG, "Sending full pallet control frame: " + IOBoardProtocolUtils.frameToHex(frame));
|
|
705
|
+
|
|
706
|
+
// Send frame and wait for response
|
|
707
|
+
String response = sendCommandAndWaitResponse(frame, 5000);
|
|
530
708
|
|
|
531
|
-
|
|
709
|
+
// Parse response
|
|
710
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
711
|
+
|
|
712
|
+
if (!parsedResponse.success) {
|
|
713
|
+
return new IOBoardResponse(false, "Failed to parse response: " + parsedResponse.message);
|
|
714
|
+
}
|
|
532
715
|
|
|
533
|
-
// TODO: Send frame via serial port plugin and receive response
|
|
534
716
|
return new IOBoardResponse(true, "Full pallet controlled successfully");
|
|
535
717
|
|
|
536
718
|
} catch (Exception e) {
|
|
@@ -550,21 +732,32 @@ public class IOBoardManager {
|
|
|
550
732
|
}
|
|
551
733
|
|
|
552
734
|
try {
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
data[
|
|
557
|
-
data[
|
|
558
|
-
data[
|
|
559
|
-
data[
|
|
560
|
-
data[
|
|
561
|
-
data[
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
735
|
+
// TODO: Add OTA notification method to IOBoardProtocolUtils
|
|
736
|
+
// For now, use manual frame building based on protocol specs
|
|
737
|
+
byte[] data = new byte[8]; // '?' + version(3) + firmware size(4)
|
|
738
|
+
data[0] = 0x3F; // '?' Notification header
|
|
739
|
+
data[1] = (byte) majorVersion;
|
|
740
|
+
data[2] = (byte) minorVersion;
|
|
741
|
+
data[3] = (byte) patchVersion;
|
|
742
|
+
data[4] = (byte) (firmwareSize & 0xFF);
|
|
743
|
+
data[5] = (byte) ((firmwareSize >> 8) & 0xFF);
|
|
744
|
+
data[6] = (byte) ((firmwareSize >> 16) & 0xFF);
|
|
745
|
+
data[7] = (byte) ((firmwareSize >> 24) & 0xFF);
|
|
746
|
+
|
|
747
|
+
byte[] frame = IOBoardProtocolUtils.createFrame((byte) address, IOBoardProtocolUtils.FRAME_TYPE_OTA_REQUEST, data);
|
|
748
|
+
|
|
749
|
+
Log.d(TAG, "Sending OTA notification frame: " + IOBoardProtocolUtils.frameToHex(frame));
|
|
750
|
+
|
|
751
|
+
// Send frame and wait for response
|
|
752
|
+
String response = sendCommandAndWaitResponse(frame, 5000);
|
|
753
|
+
|
|
754
|
+
// Parse response
|
|
755
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
756
|
+
|
|
757
|
+
if (!parsedResponse.success) {
|
|
758
|
+
return new IOBoardResponse(false, "Failed to parse response: " + parsedResponse.message);
|
|
759
|
+
}
|
|
566
760
|
|
|
567
|
-
// TODO: Send frame via serial port plugin and receive response
|
|
568
761
|
return new IOBoardResponse(true, "OTA notification sent successfully");
|
|
569
762
|
|
|
570
763
|
} catch (Exception e) {
|
|
@@ -587,64 +780,44 @@ public class IOBoardManager {
|
|
|
587
780
|
// Decode base64 data
|
|
588
781
|
byte[] firmwareData = Base64.decode(dataBase64, Base64.DEFAULT);
|
|
589
782
|
|
|
590
|
-
//
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
data[
|
|
594
|
-
data[
|
|
595
|
-
data[
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
783
|
+
// TODO: Add OTA data method to IOBoardProtocolUtils
|
|
784
|
+
// For now, use manual frame building based on protocol specs
|
|
785
|
+
byte[] data = new byte[4 + Math.min(firmwareData.length, 128)]; // Packet number(4) + Data content (max 128 bytes)
|
|
786
|
+
data[0] = (byte) (packetNumber & 0xFF);
|
|
787
|
+
data[1] = (byte) ((packetNumber >> 8) & 0xFF);
|
|
788
|
+
data[2] = (byte) ((packetNumber >> 16) & 0xFF);
|
|
789
|
+
data[3] = (byte) ((packetNumber >> 24) & 0xFF);
|
|
790
|
+
|
|
791
|
+
// Copy firmware data (max 128 bytes as per protocol)
|
|
792
|
+
int dataLength = Math.min(firmwareData.length, 128);
|
|
793
|
+
System.arraycopy(firmwareData, 0, data, 4, dataLength);
|
|
794
|
+
|
|
795
|
+
// Pad with zeros if needed
|
|
796
|
+
if (dataLength < 128) {
|
|
797
|
+
for (int i = 4 + dataLength; i < 4 + 128 && i < data.length; i++) {
|
|
798
|
+
data[i] = 0;
|
|
799
|
+
}
|
|
599
800
|
}
|
|
600
801
|
|
|
601
|
-
|
|
802
|
+
byte[] frame = IOBoardProtocolUtils.createFrame((byte) address, IOBoardProtocolUtils.FRAME_TYPE_OTA_DATA, data);
|
|
602
803
|
|
|
603
|
-
Log.d(TAG, "Sending OTA data frame: " + frameToHex(frame));
|
|
804
|
+
Log.d(TAG, "Sending OTA data frame: " + IOBoardProtocolUtils.frameToHex(frame));
|
|
604
805
|
|
|
605
|
-
//
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
} catch (Exception e) {
|
|
609
|
-
Log.e(TAG, "Error sending OTA data", e);
|
|
610
|
-
return new IOBoardResponse(false, "Error sending OTA data: " + e.getMessage());
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Open serial connection
|
|
616
|
-
*/
|
|
617
|
-
public IOBoardResponse openConnection(SerialConfig config) {
|
|
618
|
-
Log.d(TAG, "Opening serial connection - BaudRate: " + config.baudRate);
|
|
619
|
-
|
|
620
|
-
try {
|
|
621
|
-
// TODO: Initialize connection with serial port plugin
|
|
622
|
-
// For now, just set the connected flag
|
|
623
|
-
isConnected = true;
|
|
806
|
+
// Send frame and wait for response
|
|
807
|
+
String response = sendCommandAndWaitResponse(frame, 5000);
|
|
624
808
|
|
|
625
|
-
|
|
809
|
+
// Parse response
|
|
810
|
+
IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
|
|
626
811
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Close serial connection
|
|
635
|
-
*/
|
|
636
|
-
public IOBoardResponse closeConnection() {
|
|
637
|
-
Log.d(TAG, "Closing serial connection");
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
// TODO: Close connection with serial port plugin
|
|
641
|
-
isConnected = false;
|
|
812
|
+
if (!parsedResponse.success) {
|
|
813
|
+
return new IOBoardResponse(false, "Failed to parse response: " + parsedResponse.message);
|
|
814
|
+
}
|
|
642
815
|
|
|
643
|
-
return new IOBoardResponse(true, "
|
|
816
|
+
return new IOBoardResponse(true, "OTA data sent successfully");
|
|
644
817
|
|
|
645
818
|
} catch (Exception e) {
|
|
646
|
-
Log.e(TAG, "Error
|
|
647
|
-
return new IOBoardResponse(false, "Error
|
|
819
|
+
Log.e(TAG, "Error sending OTA data", e);
|
|
820
|
+
return new IOBoardResponse(false, "Error sending OTA data: " + e.getMessage());
|
|
648
821
|
}
|
|
649
822
|
}
|
|
650
823
|
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
package com.leonardojc.capacitor.ioboard;
|
|
2
|
+
|
|
3
|
+
import java.util.ArrayList;
|
|
4
|
+
import java.util.List;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* IOBOARD Protocol Helper Functions
|
|
8
|
+
* Protocolo actualizado según manual MTC3P08L
|
|
9
|
+
* Frame format: SOI | LEN | ADDR | TYPE | DATA | CRC | EOI
|
|
10
|
+
*/
|
|
11
|
+
public class IOBoardProtocolUtils {
|
|
12
|
+
|
|
13
|
+
// Frame constants
|
|
14
|
+
public static final byte SOI = 0x0D; // Start of frame
|
|
15
|
+
public static final byte EOI = 0x0A; // End of frame
|
|
16
|
+
|
|
17
|
+
// Frame types según manual actualizado
|
|
18
|
+
public static final byte FRAME_TYPE_STATUS_QUERY = 0x00; // Status query
|
|
19
|
+
public static final byte FRAME_TYPE_SINGLE_PALLET = 0x01; // Parameter settings (single pallet)
|
|
20
|
+
public static final byte FRAME_TYPE_FULL_PALLET = 0x02; // Parameter settings (full pallet)
|
|
21
|
+
public static final byte FRAME_TYPE_OTA_REQUEST = (byte) 0xF1; // OTA request
|
|
22
|
+
public static final byte FRAME_TYPE_OTA_DATA = (byte) 0xF2; // OTA data
|
|
23
|
+
public static final byte FRAME_TYPE_OTA_RESPONSE = (byte) 0xF3; // OTA response
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* CRC-16/Modbus (x16+x15+x2+1) según manual
|
|
27
|
+
*/
|
|
28
|
+
public static int calculateCRC16(byte[] data) {
|
|
29
|
+
int crc = 0xFFFF;
|
|
30
|
+
for (byte b : data) {
|
|
31
|
+
crc ^= (b & 0xFF);
|
|
32
|
+
for (int j = 0; j < 8; j++) {
|
|
33
|
+
if ((crc & 0x0001) != 0) {
|
|
34
|
+
crc = (crc >> 1) ^ 0xA001;
|
|
35
|
+
} else {
|
|
36
|
+
crc = crc >> 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return crc & 0xFFFF;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Formato de frame según manual: SOI | LEN | ADDR | TYPE | DATA | CRC | EOI
|
|
45
|
+
*/
|
|
46
|
+
public static byte[] createFrame(byte address, byte frameType, byte[] data) {
|
|
47
|
+
if (data == null) data = new byte[0];
|
|
48
|
+
|
|
49
|
+
int frameLength = 7 + data.length; // SOI(1) + LEN(1) + ADDR(1) + TYPE(1) + DATA(n) + CRC(2) + EOI(1)
|
|
50
|
+
byte[] frame = new byte[frameLength];
|
|
51
|
+
int index = 0;
|
|
52
|
+
|
|
53
|
+
// SOI (Frame Header)
|
|
54
|
+
frame[index++] = SOI;
|
|
55
|
+
|
|
56
|
+
// LEN (Data Length) - longitud completa del frame
|
|
57
|
+
frame[index++] = (byte) frameLength;
|
|
58
|
+
|
|
59
|
+
// ADDR (Target Address)
|
|
60
|
+
frame[index++] = address;
|
|
61
|
+
|
|
62
|
+
// TYPE (Frame Type)
|
|
63
|
+
frame[index++] = frameType;
|
|
64
|
+
|
|
65
|
+
// DATA (Data Content)
|
|
66
|
+
for (byte b : data) {
|
|
67
|
+
frame[index++] = b;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Calculate CRC para todos los datos desde SOI hasta antes del CRC
|
|
71
|
+
byte[] crcData = new byte[index];
|
|
72
|
+
System.arraycopy(frame, 0, crcData, 0, index);
|
|
73
|
+
int crc = calculateCRC16(crcData);
|
|
74
|
+
|
|
75
|
+
// CRC (Check Field) - Low byte first, High byte second según manual
|
|
76
|
+
frame[index++] = (byte) (crc & 0xFF); // Low byte
|
|
77
|
+
frame[index++] = (byte) ((crc >> 8) & 0xFF); // High byte
|
|
78
|
+
|
|
79
|
+
// EOI (Frame End)
|
|
80
|
+
frame[index] = EOI;
|
|
81
|
+
|
|
82
|
+
return frame;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Convert frame to hexadecimal string for debugging
|
|
87
|
+
*/
|
|
88
|
+
public static String frameToHex(byte[] frame) {
|
|
89
|
+
StringBuilder sb = new StringBuilder();
|
|
90
|
+
for (int i = 0; i < frame.length; i++) {
|
|
91
|
+
if (i > 0) sb.append(" ");
|
|
92
|
+
sb.append(String.format("%02X", frame[i] & 0xFF));
|
|
93
|
+
}
|
|
94
|
+
return sb.toString();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Convert frame to Latin-1 string preserving exact byte values
|
|
99
|
+
*/
|
|
100
|
+
public static String frameToLatin1String(byte[] frame) {
|
|
101
|
+
StringBuilder result = new StringBuilder();
|
|
102
|
+
for (byte b : frame) {
|
|
103
|
+
result.append((char) (b & 0xFF));
|
|
104
|
+
}
|
|
105
|
+
return result.toString();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create status query frame
|
|
110
|
+
*/
|
|
111
|
+
public static byte[] createStatusQuery(byte address) {
|
|
112
|
+
return createFrame(address, FRAME_TYPE_STATUS_QUERY, null);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Single Pallet Control según manual: NUM | LOCK | LED (8 bytes)
|
|
117
|
+
*/
|
|
118
|
+
public static byte[] createSinglePalletControl(byte address, int palletNumber, boolean doorLock,
|
|
119
|
+
int red, int green, int blue, int intensity,
|
|
120
|
+
int blinkTimes, int blinkSpeed) {
|
|
121
|
+
byte[] data = new byte[8];
|
|
122
|
+
data[0] = (byte) palletNumber; // Pallet number (0-7)
|
|
123
|
+
data[1] = (byte) (doorLock ? 1 : 0); // Door lock control (1=unlock, 0=no action)
|
|
124
|
+
data[2] = (byte) (red & 0xFF); // RGB Red (0-255)
|
|
125
|
+
data[3] = (byte) (green & 0xFF); // RGB Green (0-255)
|
|
126
|
+
data[4] = (byte) (blue & 0xFF); // RGB Blue (0-255)
|
|
127
|
+
data[5] = (byte) (intensity & 0xFF); // Intensity (0-100 percentage, >100 = max)
|
|
128
|
+
data[6] = (byte) (blinkTimes & 0xFF); // Blink times (0=no flash, 255=forever, 1-254=times)
|
|
129
|
+
data[7] = (byte) (blinkSpeed & 0xFF); // Blink speed (1-30 seconds interval)
|
|
130
|
+
|
|
131
|
+
return createFrame(address, FRAME_TYPE_SINGLE_PALLET, data);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Full Pallet Control según manual: LOCK | EXT | LED (8 x 6 bytes = 50 bytes total)
|
|
136
|
+
*/
|
|
137
|
+
public static byte[] createFullPalletControl(byte address, int doorLockControl, int extendedControl,
|
|
138
|
+
int[][] ledControls) {
|
|
139
|
+
byte[] data = new byte[50]; // 2 + (8 * 6)
|
|
140
|
+
int index = 0;
|
|
141
|
+
|
|
142
|
+
data[index++] = (byte) (doorLockControl & 0xFF); // Door lock control (8 bits)
|
|
143
|
+
data[index++] = (byte) (extendedControl & 0xFF); // Extended control (8 bits)
|
|
144
|
+
|
|
145
|
+
// Add LED controls (8 LEDs * 6 bytes each)
|
|
146
|
+
for (int i = 0; i < 8; i++) {
|
|
147
|
+
int[] led = (ledControls != null && i < ledControls.length && ledControls[i] != null)
|
|
148
|
+
? ledControls[i]
|
|
149
|
+
: new int[]{0, 0, 0, 0, 0, 1}; // Default: off
|
|
150
|
+
|
|
151
|
+
data[index++] = (byte) (led[0] & 0xFF); // RGB Red
|
|
152
|
+
data[index++] = (byte) (led[1] & 0xFF); // RGB Green
|
|
153
|
+
data[index++] = (byte) (led[2] & 0xFF); // RGB Blue
|
|
154
|
+
data[index++] = (byte) (led[3] & 0xFF); // Intensity
|
|
155
|
+
data[index++] = (byte) (led[4] & 0xFF); // Blink times
|
|
156
|
+
data[index++] = (byte) (led[5] & 0xFF); // Blink speed
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return createFrame(address, FRAME_TYPE_FULL_PALLET, data);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Response parsing result
|
|
164
|
+
*/
|
|
165
|
+
public static class ParsedResponse {
|
|
166
|
+
public boolean success;
|
|
167
|
+
public String message;
|
|
168
|
+
public byte address;
|
|
169
|
+
public byte frameType;
|
|
170
|
+
public byte[] data;
|
|
171
|
+
|
|
172
|
+
public ParsedResponse(boolean success, String message) {
|
|
173
|
+
this.success = success;
|
|
174
|
+
this.message = message;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public ParsedResponse(boolean success, String message, byte address, byte frameType, byte[] data) {
|
|
178
|
+
this.success = success;
|
|
179
|
+
this.message = message;
|
|
180
|
+
this.address = address;
|
|
181
|
+
this.frameType = frameType;
|
|
182
|
+
this.data = data;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Parse response frame según formato: SOI | LEN | ADDR | TYPE | DATA | CRC | EOI
|
|
188
|
+
*/
|
|
189
|
+
public static ParsedResponse parseResponse(String responseString) {
|
|
190
|
+
if (responseString == null || responseString.length() < 7) {
|
|
191
|
+
return new ParsedResponse(false, "Response too short");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
byte[] allBytes = responseString.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
|
|
195
|
+
|
|
196
|
+
byte soi = allBytes[0];
|
|
197
|
+
if (soi != SOI) {
|
|
198
|
+
return new ParsedResponse(false, "Invalid SOI");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
int len = allBytes[1] & 0xFF;
|
|
202
|
+
if (len > allBytes.length) {
|
|
203
|
+
return new ParsedResponse(false, "Invalid frame length");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Extraer solo los bytes que corresponden al frame
|
|
207
|
+
byte[] bytes = new byte[len];
|
|
208
|
+
System.arraycopy(allBytes, 0, bytes, 0, len);
|
|
209
|
+
|
|
210
|
+
byte address = bytes[2];
|
|
211
|
+
byte frameType = bytes[3];
|
|
212
|
+
byte eoi = bytes[len - 1];
|
|
213
|
+
|
|
214
|
+
if (eoi != EOI) {
|
|
215
|
+
return new ParsedResponse(false, "Invalid EOI");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Extract data (excluding SOI, LEN, ADDR, TYPE, CRC, CRC, EOI)
|
|
219
|
+
int dataLength = len - 7;
|
|
220
|
+
byte[] data = new byte[dataLength];
|
|
221
|
+
if (dataLength > 0) {
|
|
222
|
+
System.arraycopy(bytes, 4, data, 0, dataLength);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Extract CRC
|
|
226
|
+
int receivedCrcLow = bytes[4 + dataLength] & 0xFF;
|
|
227
|
+
int receivedCrcHigh = bytes[4 + dataLength + 1] & 0xFF;
|
|
228
|
+
int receivedCrc = receivedCrcLow | (receivedCrcHigh << 8);
|
|
229
|
+
|
|
230
|
+
// Verify CRC
|
|
231
|
+
byte[] crcData = new byte[4 + dataLength];
|
|
232
|
+
System.arraycopy(bytes, 0, crcData, 0, 4 + dataLength);
|
|
233
|
+
int calculatedCrc = calculateCRC16(crcData);
|
|
234
|
+
|
|
235
|
+
if (receivedCrc != calculatedCrc) {
|
|
236
|
+
return new ParsedResponse(false, "CRC verification failed");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return new ParsedResponse(true, "Response parsed successfully", address, frameType, data);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Create test frames based on manual examples
|
|
244
|
+
*/
|
|
245
|
+
public static byte[] createTestStatusQuery() {
|
|
246
|
+
return createStatusQuery((byte) 1); // Address 01
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public static byte[] createTestSinglePallet() {
|
|
250
|
+
return createSinglePalletControl((byte) 1, 0, true, 255, 0, 0, 255, 16, 1);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Validate that our commands match manual examples
|
|
255
|
+
*/
|
|
256
|
+
public static String validateExamples() {
|
|
257
|
+
StringBuilder results = new StringBuilder();
|
|
258
|
+
|
|
259
|
+
// Test Status Query
|
|
260
|
+
byte[] statusFrame = createTestStatusQuery();
|
|
261
|
+
String statusHex = frameToHex(statusFrame);
|
|
262
|
+
String expectedStatus = "0D 07 01 00 B2 D9 0A";
|
|
263
|
+
results.append("Status Query - Expected: ").append(expectedStatus)
|
|
264
|
+
.append(", Actual: ").append(statusHex)
|
|
265
|
+
.append(", Match: ").append(statusHex.equals(expectedStatus))
|
|
266
|
+
.append("\n");
|
|
267
|
+
|
|
268
|
+
// Test Single Pallet
|
|
269
|
+
byte[] singleFrame = createTestSinglePallet();
|
|
270
|
+
String singleHex = frameToHex(singleFrame);
|
|
271
|
+
String expectedSingle = "0D 0F 01 01 00 01 FF 00 00 FF 10 01 1D 6F 0A";
|
|
272
|
+
results.append("Single Pallet - Expected: ").append(expectedSingle)
|
|
273
|
+
.append(", Actual: ").append(singleHex)
|
|
274
|
+
.append(", Match: ").append(singleHex.equals(expectedSingle));
|
|
275
|
+
|
|
276
|
+
return results.toString();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Convert frame bytes to Latin-1 encoded string for transmission
|
|
281
|
+
*/
|
|
282
|
+
public static String frameToLatin1String(byte[] frame) {
|
|
283
|
+
try {
|
|
284
|
+
return new String(frame, "ISO-8859-1");
|
|
285
|
+
} catch (Exception e) {
|
|
286
|
+
// Fallback to default encoding
|
|
287
|
+
return new String(frame, java.nio.charset.StandardCharsets.ISO_8859_1);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -81,6 +81,7 @@ export interface UnlockPalletOptions extends DeviceOptions {
|
|
|
81
81
|
}
|
|
82
82
|
export interface MultiplePalletOptions extends DeviceOptions {
|
|
83
83
|
doorLockMask: number;
|
|
84
|
+
ledColor?: 'red' | 'green' | 'blue' | 'yellow' | 'white' | 'off';
|
|
84
85
|
ledControls?: LEDControlOptions[];
|
|
85
86
|
}
|
|
86
87
|
export interface ScanOptions {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leonardojc/capacitor-ioboard",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "A Capacitor plugin for controlling custom IOBOARD devices via RS485 serial communication with
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "A Capacitor plugin for controlling custom IOBOARD devices via RS485 serial communication with full native protocol implementation",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/esm/index.d.ts",
|