@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
- * Calculate CRC-16 CCITT checksum
139
+ * Constructor - Initialize the manager
131
140
  */
132
- private int calculateCRC16(int[] data) {
133
- int crc = 0xFFFF;
134
-
135
- for (int dataByte : data) {
136
- crc ^= (dataByte << 8);
137
-
138
- for (int bit = 0; bit < 8; bit++) {
139
- if ((crc & 0x8000) != 0) {
140
- crc = (crc << 1) ^ 0x1021;
141
- } else {
142
- crc = crc << 1;
143
- }
144
- crc &= 0xFFFF;
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
- * Build a command frame for IOBOARD
184
+ * Close serial port
153
185
  */
154
- private int[] buildFrame(int address, int frameType, int[] data) {
155
- // Calculate frame size: SOI + Address + FrameType + Data + CRC(2) + EOI
156
- int frameSize = 1 + 1 + 1 + data.length + 2 + 1;
157
- int[] frame = new int[frameSize];
158
- int index = 0;
159
-
160
- // SOI
161
- frame[index++] = SOI;
162
-
163
- // Address
164
- frame[index++] = address;
165
-
166
- // Frame Type
167
- frame[index++] = frameType;
168
-
169
- // Data
170
- for (int dataByte : data) {
171
- frame[index++] = dataByte;
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
- // Calculate CRC for address + frameType + data
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
- * Convert frame to byte array for serial transmission
252
+ * Stop reader thread
194
253
  */
195
- private byte[] frameToBytes(int[] frame) {
196
- byte[] bytes = new byte[frame.length];
197
- for (int i = 0; i < frame.length; i++) {
198
- bytes[i] = (byte) (frame[i] & 0xFF);
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
- * Convert frame to hex string for debugging
266
+ * Send command via serial port and wait for response
205
267
  */
206
- private String frameToHex(int[] frame) {
207
- StringBuilder sb = new StringBuilder();
208
- for (int i = 0; i < frame.length; i++) {
209
- if (i > 0) sb.append(" ");
210
- sb.append(String.format("%02X", frame[i]));
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
- // TODO: Initialize connection with serial port plugin
225
- // For now, just set the connected flag
226
- isConnected = true;
227
-
228
- return new IOBoardResponse(true, "Connected to " + config.portPath + " successfully");
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
- // TODO: Close connection with serial port plugin
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
- int[] frame = buildFrame(address, FRAME_TYPE_STATUS_QUERY, new int[0]);
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
- Log.d(TAG, "Sending status query frame: " + frameToHex(frame));
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
- // TODO: Send frame via serial port plugin and receive response
271
- // For now, return a mock response
272
- StatusData mockData = new StatusData(0b11010001, new SoftwareVersion(1, 2, 3));
273
- return new StatusResponse(true, "Status retrieved successfully", mockData);
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 data array for single pallet control
296
- int[] data = new int[8];
297
- data[0] = palletNumber;
298
- data[1] = 1; // doorLock = true (unlock)
299
- data[2] = ledControl.red;
300
- data[3] = ledControl.green;
301
- data[4] = ledControl.blue;
302
- data[5] = ledControl.intensity;
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
- int[] frame = buildFrame(address, FRAME_TYPE_SINGLE_PALLET, data);
418
+ // Send frame and wait for response
419
+ String response = sendCommandAndWaitResponse(frame, timeout);
307
420
 
308
- Log.d(TAG, "Sending unlock pallet frame: " + frameToHex(frame));
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 data array for full pallet control
334
- int[] data = new int[2 + (8 * 6)];
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
- LEDControl led = ((doorLockMask & (1 << i)) != 0) ? ledControl : new LEDControl(0, 0, 0, 0, 0, 0);
344
- data[index++] = led.red;
345
- data[index++] = led.green;
346
- data[index++] = led.blue;
347
- data[index++] = led.intensity;
348
- data[index++] = led.blinkTimes;
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
- int[] frame = buildFrame(address, FRAME_TYPE_FULL_PALLET, data);
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[] devices = new DeviceInfo[deviceCount];
499
+ List<DeviceInfo> deviceList = new ArrayList<>();
378
500
 
379
- // TODO: Actually scan devices by sending status queries
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
- boolean responding = (i % 3 == 0); // Mock: every 3rd device responds
384
- StatusData status = responding ? new StatusData(0x00, new SoftwareVersion(1, 0, 0)) : null;
385
- devices[i] = new DeviceInfo(address, responding, status);
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
- int[] frame = buildFrame(address, FRAME_TYPE_STATUS_QUERY, new int[0]);
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
- Log.d(TAG, "Sending status query frame: " + frameToHex(frame));
611
+ // Send frame and wait for response
612
+ String response = sendCommandAndWaitResponse(frame, 5000);
453
613
 
454
- // TODO: Send frame via serial port plugin and receive response
455
- // For now, return a mock response
456
- StatusData mockData = new StatusData(0x00, new SoftwareVersion(1, 0, 0));
457
- return new StatusResponse(true, "Status queried successfully", mockData);
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 data array
477
- int[] data = new int[8];
478
- data[0] = palletNumber;
479
- data[1] = doorLock ? 1 : 0;
480
- data[2] = ledControl.red;
481
- data[3] = ledControl.green;
482
- data[4] = ledControl.blue;
483
- data[5] = ledControl.intensity;
484
- data[6] = ledControl.blinkTimes;
485
- data[7] = ledControl.blinkSpeed;
486
-
487
- int[] frame = buildFrame(address, FRAME_TYPE_SINGLE_PALLET, data);
488
-
489
- Log.d(TAG, "Sending single pallet control frame: " + frameToHex(frame));
490
-
491
- // TODO: Send frame via serial port plugin and receive response
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
- // Build data array: doorLockControl + extendedControl + 8 LED controls (6 bytes each)
512
- int[] data = new int[2 + (8 * 6)];
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
- data[index++] = led.red;
522
- data[index++] = led.green;
523
- data[index++] = led.blue;
524
- data[index++] = led.intensity;
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
- int[] frame = buildFrame(address, FRAME_TYPE_FULL_PALLET, data);
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
- Log.d(TAG, "Sending full pallet control frame: " + frameToHex(frame));
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
- // Build OTA notification data: version(3) + firmware size(4)
554
- int[] data = new int[7];
555
- data[0] = majorVersion;
556
- data[1] = minorVersion;
557
- data[2] = patchVersion;
558
- data[3] = firmwareSize & 0xFF;
559
- data[4] = (firmwareSize >> 8) & 0xFF;
560
- data[5] = (firmwareSize >> 16) & 0xFF;
561
- data[6] = (firmwareSize >> 24) & 0xFF;
562
-
563
- int[] frame = buildFrame(address, FRAME_TYPE_OTA_REQUEST, data);
564
-
565
- Log.d(TAG, "Sending OTA notification frame: " + frameToHex(frame));
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
- // Build OTA data: packet number(4) + data
591
- int[] data = new int[4 + firmwareData.length];
592
- data[0] = packetNumber & 0xFF;
593
- data[1] = (packetNumber >> 8) & 0xFF;
594
- data[2] = (packetNumber >> 16) & 0xFF;
595
- data[3] = (packetNumber >> 24) & 0xFF;
596
-
597
- for (int i = 0; i < firmwareData.length; i++) {
598
- data[4 + i] = firmwareData[i] & 0xFF;
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
- int[] frame = buildFrame(address, FRAME_TYPE_OTA_DATA, data);
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
- // TODO: Send frame via serial port plugin and receive response
606
- return new IOBoardResponse(true, "OTA data sent successfully");
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
- return new IOBoardResponse(true, "Serial connection opened successfully");
809
+ // Parse response
810
+ IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(response);
626
811
 
627
- } catch (Exception e) {
628
- Log.e(TAG, "Error opening connection", e);
629
- return new IOBoardResponse(false, "Error opening connection: " + e.getMessage());
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, "Serial connection closed successfully");
816
+ return new IOBoardResponse(true, "OTA data sent successfully");
644
817
 
645
818
  } catch (Exception e) {
646
- Log.e(TAG, "Error closing connection", e);
647
- return new IOBoardResponse(false, "Error closing connection: " + e.getMessage());
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.0",
4
- "description": "A Capacitor plugin for controlling custom IOBOARD devices via RS485 serial communication with simplified native API",
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",