@leonardojc/capacitor-ioboard 1.2.8 → 2.0.0

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.
@@ -1,262 +1,926 @@
1
- package com.leonardojc.capacitor.ioboard;
2
-
3
- import com.getcapacitor.JSArray;
4
- import com.getcapacitor.JSObject;
5
- import com.getcapacitor.Plugin;
6
- import com.getcapacitor.PluginCall;
7
- import com.getcapacitor.PluginMethod;
8
- import com.getcapacitor.annotation.CapacitorPlugin;
9
- import android.util.Log;
10
- import java.util.Arrays;
11
- import org.json.JSONObject;
12
-
13
- @CapacitorPlugin(name = "CapacitorIoboard")
14
- public class CapacitorIoboardPlugin extends Plugin {
15
-
16
- private static final String TAG = "CapacitorIoboard";
17
-
18
- @Override
19
- public void load() {
20
- super.load();
21
- Log.d(TAG, "IOBoard Protocol Plugin loaded - working at protocol level");
22
- }
23
-
24
- // Protocol-level methods - these return frames to be sent and parse responses
25
-
26
- @PluginMethod
27
- public void buildCommand(PluginCall call) {
28
- String command = call.getString("command");
29
- Integer address = call.getInt("address");
30
-
31
- if (command == null || address == null) {
32
- call.reject("Command and address are required");
33
- return;
34
- }
35
-
36
- try {
37
- byte[] frame = null;
38
-
39
- switch (command.toUpperCase()) {
40
- case "GET_STATUS":
41
- frame = IOBoardProtocolUtils.createStatusQuery((byte) address.intValue());
42
- break;
43
-
44
- case "UNLOCK_PALLET":
45
- Integer palletNumber = call.getInt("palletNumber");
46
- String ledColor = call.getString("ledColor", "green");
47
- if (palletNumber == null) {
48
- call.reject("palletNumber is required for UNLOCK_PALLET command");
49
- return;
50
- }
51
-
52
- // Create data payload for single pallet unlock
53
- byte[] singlePalletData = new byte[2];
54
- singlePalletData[0] = (byte) palletNumber.intValue(); // Pallet number
55
- singlePalletData[1] = (byte) (ledColor.equals("red") ? 1 : 0); // LED color: 0=green, 1=red
56
-
57
- frame = IOBoardProtocolUtils.createFrame((byte) address.intValue(), IOBoardProtocolUtils.FRAME_TYPE_SINGLE_PALLET, singlePalletData);
58
- break;
59
-
60
- case "CONTROL_MULTIPLE":
61
- Integer doorLockMask = call.getInt("doorLockMask", 0);
62
- String ledColorMultiple = call.getString("ledColor", "green");
63
-
64
- // Create data payload for multiple pallet control
65
- byte[] multiplePalletData = new byte[2];
66
- multiplePalletData[0] = (byte) doorLockMask.intValue(); // Door lock mask
67
- multiplePalletData[1] = (byte) (ledColorMultiple.equals("red") ? 1 : 0); // LED color
68
-
69
- frame = IOBoardProtocolUtils.createFrame((byte) address.intValue(), IOBoardProtocolUtils.FRAME_TYPE_FULL_PALLET, multiplePalletData);
70
- break;
71
-
72
- case "OTA_NOTIFICATION":
73
- Integer majorVersion = call.getInt("majorVersion");
74
- Integer minorVersion = call.getInt("minorVersion");
75
- Integer patchVersion = call.getInt("patchVersion");
76
- Integer firmwareSize = call.getInt("firmwareSize");
77
-
78
- if (majorVersion == null || minorVersion == null || patchVersion == null || firmwareSize == null) {
79
- call.reject("All version parameters and firmware size are required for OTA_NOTIFICATION");
80
- return;
81
- }
82
-
83
- // Create OTA notification data payload (example structure)
84
- byte[] otaData = new byte[7];
85
- otaData[0] = (byte) majorVersion.intValue();
86
- otaData[1] = (byte) minorVersion.intValue();
87
- otaData[2] = (byte) patchVersion.intValue();
88
- // Firmware size as 4 bytes (little endian)
89
- otaData[3] = (byte) (firmwareSize & 0xFF);
90
- otaData[4] = (byte) ((firmwareSize >> 8) & 0xFF);
91
- otaData[5] = (byte) ((firmwareSize >> 16) & 0xFF);
92
- otaData[6] = (byte) ((firmwareSize >> 24) & 0xFF);
93
-
94
- frame = IOBoardProtocolUtils.createFrame((byte) address.intValue(), IOBoardProtocolUtils.FRAME_TYPE_OTA_REQUEST, otaData);
95
- break;
96
-
97
- default:
98
- call.reject("Unknown command: " + command);
99
- return;
100
- }
101
-
102
- JSObject result = new JSObject();
103
- result.put("frame", IOBoardProtocolUtils.frameToHex(frame));
104
- result.put("command", command);
105
- result.put("address", address);
106
-
107
- call.resolve(result);
108
-
109
- } catch (Exception e) {
110
- Log.e(TAG, "Error building command", e);
111
- call.reject("Failed to build command: " + e.getMessage());
112
- }
113
- }
114
-
115
- @PluginMethod
116
- public void parseResponse(PluginCall call) {
117
- String hexData = call.getString("data");
118
-
119
- if (hexData == null || hexData.trim().isEmpty()) {
120
- call.reject("Data is required");
121
- return;
122
- }
123
-
124
- try {
125
- Log.d(TAG, "Parsing response: " + hexData);
126
-
127
- IOBoardProtocolUtils.ParsedResponse parsedResponse = IOBoardProtocolUtils.parseResponse(hexData);
128
-
129
- JSObject result = new JSObject();
130
- result.put("valid", parsedResponse.success);
131
- result.put("rawData", hexData);
132
- result.put("message", parsedResponse.message);
133
-
134
- if (parsedResponse.success) {
135
- result.put("address", parsedResponse.address);
136
- result.put("frameType", parsedResponse.frameType);
137
-
138
- if (parsedResponse.data != null && parsedResponse.data.length > 0) {
139
- result.put("payload", IOBoardProtocolUtils.frameToHex(parsedResponse.data));
140
-
141
- // If it's a status response, parse the data
142
- if (parsedResponse.frameType == IOBoardProtocolUtils.FRAME_TYPE_STATUS_QUERY && parsedResponse.data.length >= 4) {
143
- result.put("doorLockStatus", parsedResponse.data[0]);
144
-
145
- // Software version (assuming bytes 1-3 are major, minor, patch)
146
- if (parsedResponse.data.length >= 4) {
147
- JSObject version = new JSObject();
148
- version.put("major", parsedResponse.data[1]);
149
- version.put("minor", parsedResponse.data[2]);
150
- version.put("patch", parsedResponse.data[3]);
151
- result.put("softwareVersion", version);
152
- }
153
- }
154
- }
155
- }
156
-
157
- call.resolve(result);
158
-
159
- } catch (Exception e) {
160
- Log.e(TAG, "Error parsing response", e);
161
- call.reject("Failed to parse response: " + e.getMessage());
162
- }
163
- }
164
-
165
- @PluginMethod
166
- public void validateFrame(PluginCall call) {
167
- String hexData = call.getString("data");
168
-
169
- if (hexData == null || hexData.trim().isEmpty()) {
170
- call.reject("Data is required");
171
- return;
172
- }
173
-
174
- try {
175
- // Use parseResponse to validate the frame
176
- IOBoardProtocolUtils.ParsedResponse parsed = IOBoardProtocolUtils.parseResponse(hexData);
177
-
178
- JSObject result = new JSObject();
179
- result.put("valid", parsed.success);
180
- result.put("data", hexData);
181
- result.put("message", parsed.message);
182
-
183
- if (parsed.success) {
184
- byte[] frameData = IOBoardProtocolUtils.hexStringToBytes(hexData);
185
- result.put("length", frameData.length);
186
- }
187
-
188
- call.resolve(result);
189
-
190
- } catch (Exception e) {
191
- Log.e(TAG, "Error validating frame", e);
192
- call.reject("Failed to validate frame: " + e.getMessage());
193
- }
194
- }
195
-
196
- @PluginMethod
197
- public void hexToBytes(PluginCall call) {
198
- String hexData = call.getString("data");
199
-
200
- if (hexData == null || hexData.trim().isEmpty()) {
201
- call.reject("Data is required");
202
- return;
203
- }
204
-
205
- try {
206
- byte[] bytes = IOBoardProtocolUtils.hexStringToBytes(hexData);
207
-
208
- // Convert byte array to JSArray of integers
209
- JSArray bytesArray = new JSArray();
210
- for (byte b : bytes) {
211
- bytesArray.put((int) b & 0xFF); // Convert to unsigned int
212
- }
213
-
214
- JSObject result = new JSObject();
215
- result.put("bytes", bytesArray);
216
- result.put("length", bytes.length);
217
- result.put("hex", hexData);
218
-
219
- call.resolve(result);
220
-
221
- } catch (Exception e) {
222
- Log.e(TAG, "Error converting hex to bytes", e);
223
- call.reject("Failed to convert hex to bytes: " + e.getMessage());
224
- }
225
- }
226
-
227
- @PluginMethod
228
- public void bytesToHex(PluginCall call) {
229
- JSArray bytesArray = call.getArray("bytes");
230
-
231
- if (bytesArray == null) {
232
- call.reject("Bytes array is required");
233
- return;
234
- }
235
-
236
- try {
237
- byte[] bytes = new byte[bytesArray.length()];
238
- for (int i = 0; i < bytesArray.length(); i++) {
239
- bytes[i] = (byte) bytesArray.getInt(i);
240
- }
241
-
242
- String hexString = IOBoardProtocolUtils.frameToHex(bytes);
243
-
244
- // Convert byte array back to JSArray for consistency
245
- JSArray resultBytesArray = new JSArray();
246
- for (byte b : bytes) {
247
- resultBytesArray.put((int) b & 0xFF);
248
- }
249
-
250
- JSObject result = new JSObject();
251
- result.put("hex", hexString);
252
- result.put("length", bytes.length);
253
- result.put("bytes", resultBytesArray);
254
-
255
- call.resolve(result);
256
-
257
- } catch (Exception e) {
258
- Log.e(TAG, "Error converting bytes to hex", e);
259
- call.reject("Failed to convert bytes to hex: " + e.getMessage());
260
- }
261
- }
262
- }
1
+ package com.leonardojc.capacitor.ioboard;
2
+
3
+ import android.util.Log;
4
+ import com.getcapacitor.JSArray;
5
+ import com.getcapacitor.JSObject;
6
+ import com.getcapacitor.Plugin;
7
+ import com.getcapacitor.PluginCall;
8
+ import com.getcapacitor.PluginMethod;
9
+ import com.getcapacitor.annotation.CapacitorPlugin;
10
+ import java.util.List;
11
+ import java.util.ArrayList;
12
+
13
+ @CapacitorPlugin(name = "CapacitorIoboard")
14
+ public class CapacitorIoboardPlugin extends Plugin implements SerialConnectionManager.SerialDataListener {
15
+
16
+ private static final String TAG = "CapacitorIoboard";
17
+ private SerialConnectionManager serialManager;
18
+ private IOBoardManager ioBoardManager;
19
+ private boolean isConnected = false;
20
+ private String currentDeviceInfo = "";
21
+ private String lastError = "";
22
+
23
+ // Helper method to convert bytes to hex string for debugging
24
+ private String bytesToHex(byte[] bytes) {
25
+ StringBuilder hexString = new StringBuilder();
26
+ for (byte b : bytes) {
27
+ String hex = Integer.toHexString(0xFF & b);
28
+ if (hex.length() == 1) {
29
+ hexString.append('0');
30
+ }
31
+ hexString.append(hex.toUpperCase()).append(" ");
32
+ }
33
+ return hexString.toString().trim();
34
+ }
35
+
36
+ // Helper method to convert string to detailed debug info
37
+ private String getDetailedCommandInfo(String command) {
38
+ StringBuilder info = new StringBuilder();
39
+ info.append("String: '").append(command).append("'");
40
+ info.append(", Length: ").append(command.length());
41
+ info.append(", Chars: [");
42
+ for (int i = 0; i < command.length(); i++) {
43
+ char c = command.charAt(i);
44
+ if (i > 0) info.append(", ");
45
+ if (c == '\n') {
46
+ info.append("\\n(10)");
47
+ } else if (c == '\r') {
48
+ info.append("\\r(13)");
49
+ } else if (c >= 32 && c <= 126) {
50
+ info.append("'").append(c).append("'(").append((int)c).append(")");
51
+ } else {
52
+ info.append("(").append((int)c).append(")");
53
+ }
54
+ }
55
+ info.append("]");
56
+ return info.toString();
57
+ }
58
+
59
+ @Override
60
+ public void load() {
61
+ Log.d(TAG, "Plugin loaded");
62
+ serialManager = new SerialConnectionManager();
63
+ serialManager.setDataListener(this);
64
+ ioBoardManager = new IOBoardManager();
65
+ }
66
+
67
+ @PluginMethod
68
+ public void connect(PluginCall call) {
69
+ String portName = call.getString("portName");
70
+ Integer baudRate = call.getInt("baudRate", 115200);
71
+
72
+ if (portName == null) {
73
+ call.reject("Port name is required");
74
+ return;
75
+ }
76
+
77
+ try {
78
+ boolean connected = serialManager.openPort(portName, baudRate, 8, 1, "none");
79
+ if (connected) {
80
+ isConnected = true;
81
+ currentDeviceInfo = "Connected to " + portName + " at " + baudRate + " baud";
82
+
83
+ JSObject result = new JSObject();
84
+ result.put("success", true);
85
+ result.put("deviceInfo", currentDeviceInfo);
86
+ call.resolve(result);
87
+
88
+ notifyListeners("connectionStatusChanged", result);
89
+ } else {
90
+ lastError = "Failed to connect to " + portName;
91
+ call.reject(lastError);
92
+ }
93
+ } catch (Exception e) {
94
+ lastError = "Connection error: " + e.getMessage();
95
+ Log.e(TAG, lastError, e);
96
+ call.reject(lastError);
97
+ }
98
+ }
99
+
100
+ @PluginMethod
101
+ public void disconnect(PluginCall call) {
102
+ try {
103
+ serialManager.closePort();
104
+ isConnected = false;
105
+ currentDeviceInfo = "";
106
+
107
+ JSObject result = new JSObject();
108
+ result.put("success", true);
109
+ call.resolve(result);
110
+
111
+ notifyListeners("connectionStatusChanged", result);
112
+ } catch (Exception e) {
113
+ lastError = "Disconnect error: " + e.getMessage();
114
+ Log.e(TAG, lastError, e);
115
+ call.reject(lastError);
116
+ }
117
+ }
118
+
119
+ @PluginMethod
120
+ public void getStatus(PluginCall call) {
121
+ Log.d(TAG, "📊 getStatus called");
122
+
123
+ if (serialManager == null) {
124
+ call.reject("Serial connection not initialized");
125
+ return;
126
+ }
127
+
128
+ if (!serialManager.isConnected()) {
129
+ call.reject("Not connected to serial port");
130
+ return;
131
+ }
132
+
133
+ try {
134
+ int address = call.getInt("address", 1);
135
+ byte[] statusFrame = createStatusQueryFrame(address);
136
+ boolean success = serialManager.writeData(statusFrame);
137
+
138
+ if (success) {
139
+ JSObject result = new JSObject();
140
+ result.put("success", true);
141
+ result.put("message", "Status query sent successfully");
142
+ result.put("address", address);
143
+ call.resolve(result);
144
+ } else {
145
+ call.reject("Failed to send status query");
146
+ }
147
+
148
+ } catch (Exception e) {
149
+ Log.e(TAG, "Error in getStatus: " + e.getMessage(), e);
150
+ call.reject("Error getting status: " + e.getMessage());
151
+ }
152
+ }
153
+
154
+ @PluginMethod
155
+ public void getConnectionInfo(PluginCall call) {
156
+ Log.d(TAG, "🔌 getConnectionInfo called");
157
+
158
+ try {
159
+ List<String> ports = serialManager.listAvailablePorts();
160
+ boolean isConnected = serialManager.isConnected();
161
+ String currentPort = serialManager.getCurrentPortPath();
162
+
163
+ JSObject result = new JSObject();
164
+ result.put("connected", isConnected);
165
+ result.put("currentPort", currentPort != null ? currentPort : "");
166
+ result.put("availablePorts", new JSArray(ports));
167
+
168
+ call.resolve(result);
169
+ } catch (Exception e) {
170
+ Log.e(TAG, "Error getting connection info: " + e.getMessage(), e);
171
+ call.reject("Error getting connection info: " + e.getMessage());
172
+ }
173
+ }
174
+
175
+ @PluginMethod
176
+ public void controlMultiplePallets(PluginCall call) {
177
+ Log.d(TAG, "🎯 controlMultiplePallets called");
178
+
179
+ if (serialManager == null) {
180
+ call.reject("Serial connection not initialized");
181
+ return;
182
+ }
183
+
184
+ if (!serialManager.isConnected()) {
185
+ call.reject("Not connected to serial port");
186
+ return;
187
+ }
188
+
189
+ try {
190
+ // TEMPORAL: Generar exactamente el frame de la documentación
191
+ // Address: 01; Unlock: 01; Extended output: 80;
192
+ // All 8 LEDs: flashing red, brightness 255, flashing 16 times, interval 1 second
193
+ Log.d(TAG, "🧪 TESTING: Generating exact documentation frame");
194
+
195
+ int address = 1;
196
+ String color = "RED";
197
+ int intensity = 255; // FF en hex
198
+ int blinkTimes = 16; // 10 en hex
199
+ int blinkSpeed = 1; // 01 en hex
200
+
201
+ // Usar todos los pallets (1-8) como en el ejemplo
202
+ List<Integer> allPallets = new ArrayList<>();
203
+ for (int i = 1; i <= 8; i++) {
204
+ allPallets.add(i);
205
+ }
206
+
207
+ byte[] frame = createMultiplePalletsFrame(address, color, intensity, blinkTimes, blinkSpeed, allPallets);
208
+ boolean success = serialManager.writeData(frame);
209
+
210
+ if (success) {
211
+ JSObject result = new JSObject();
212
+ result.put("success", true);
213
+ result.put("message", String.format("TEST: Documentation frame sent - %d pallets with %s LEDs (intensity=%d, blink=%d)",
214
+ allPallets.size(), color, intensity, blinkTimes));
215
+ result.put("address", address);
216
+ result.put("palletsCount", allPallets.size());
217
+ call.resolve(result);
218
+ } else {
219
+ call.reject("Failed to send multiple pallets command");
220
+ }
221
+
222
+ } catch (Exception e) {
223
+ Log.e(TAG, "Error in controlMultiplePallets: " + e.getMessage(), e);
224
+ call.reject("Error controlling multiple pallets: " + e.getMessage());
225
+ }
226
+ }
227
+
228
+ @PluginMethod
229
+ public void listPorts(PluginCall call) {
230
+ try {
231
+ List<String> ports = serialManager.listAvailablePorts();
232
+ Log.d(TAG, "Found " + ports.size() + " ports: " + ports.toString());
233
+
234
+ JSArray portsArray = new JSArray();
235
+ for (String port : ports) {
236
+ portsArray.put(port);
237
+ }
238
+
239
+ JSObject result = new JSObject();
240
+ result.put("success", true);
241
+ result.put("ports", portsArray);
242
+ call.resolve(result);
243
+ } catch (Exception e) {
244
+ Log.e(TAG, "Error listing ports", e);
245
+ JSObject errorResult = new JSObject();
246
+ errorResult.put("success", false);
247
+ errorResult.put("error", "Error listing ports: " + e.getMessage());
248
+ call.resolve(errorResult);
249
+ }
250
+ }
251
+
252
+ @PluginMethod
253
+ public void sendRawCommand(PluginCall call) {
254
+ if (!isConnected) {
255
+ JSObject errorResult = new JSObject();
256
+ errorResult.put("success", false);
257
+ errorResult.put("error", "Not connected to device");
258
+ call.resolve(errorResult);
259
+ return;
260
+ }
261
+
262
+ String command = call.getString("command");
263
+ if (command == null) {
264
+ JSObject errorResult = new JSObject();
265
+ errorResult.put("success", false);
266
+ errorResult.put("error", "Command is required");
267
+ call.resolve(errorResult);
268
+ return;
269
+ }
270
+
271
+ try {
272
+ // Detailed logging before sending
273
+ Log.d(TAG, "=== SENDING COMMAND ===");
274
+ Log.d(TAG, "Command details: " + getDetailedCommandInfo(command));
275
+
276
+ byte[] commandBytes = command.getBytes();
277
+ Log.d(TAG, "Bytes to send (length=" + commandBytes.length + "): " + bytesToHex(commandBytes));
278
+ Log.d(TAG, "Raw bytes: " + java.util.Arrays.toString(commandBytes));
279
+
280
+ boolean sent = serialManager.writeData(commandBytes);
281
+
282
+ if (sent) {
283
+ Log.i(TAG, "✅ Command successfully written to serial port");
284
+ Log.i(TAG, "HEX sent: " + bytesToHex(commandBytes));
285
+ } else {
286
+ Log.e(TAG, "❌ Failed to write command to serial port");
287
+ }
288
+
289
+ JSObject result = new JSObject();
290
+ result.put("success", sent);
291
+ result.put("command", command);
292
+ result.put("hexSent", bytesToHex(commandBytes));
293
+ result.put("bytesLength", commandBytes.length);
294
+ if (!sent) {
295
+ result.put("error", "Failed to send command");
296
+ }
297
+ call.resolve(result);
298
+ } catch (Exception e) {
299
+ String error = "Error sending raw command: " + e.getMessage();
300
+ Log.e(TAG, error, e);
301
+ JSObject errorResult = new JSObject();
302
+ errorResult.put("success", false);
303
+ errorResult.put("error", error);
304
+ call.resolve(errorResult);
305
+ }
306
+ }
307
+
308
+ // SerialDataListener implementation
309
+ @Override
310
+ public void onDataReceived(byte[] data) {
311
+ String dataString = new String(data);
312
+ Log.d(TAG, "Data received: " + dataString);
313
+
314
+ JSObject result = new JSObject();
315
+ result.put("rawData", dataString);
316
+ result.put("timestamp", System.currentTimeMillis());
317
+
318
+ notifyListeners("dataReceived", result);
319
+ }
320
+
321
+ @Override
322
+ public void onConnectionStateChanged(boolean connected) {
323
+ Log.d(TAG, "Connection state changed: " + connected);
324
+ isConnected = connected;
325
+
326
+ if (!connected) {
327
+ currentDeviceInfo = "";
328
+ lastError = "Connection lost";
329
+ }
330
+
331
+ JSObject result = new JSObject();
332
+ result.put("isConnected", connected);
333
+ result.put("timestamp", System.currentTimeMillis());
334
+ notifyListeners("connectionStatusChanged", result);
335
+ }
336
+
337
+ @Override
338
+ public void onError(String error) {
339
+ Log.e(TAG, "Serial error: " + error);
340
+ lastError = error;
341
+
342
+ JSObject result = new JSObject();
343
+ result.put("error", error);
344
+ result.put("timestamp", System.currentTimeMillis());
345
+ notifyListeners("serialError", result);
346
+ }
347
+
348
+ @PluginMethod
349
+ public void unlockPallet(PluginCall call) {
350
+ Log.d(TAG, "🔓 unlockPallet called");
351
+
352
+ int address = call.getInt("address", 1);
353
+ int pallet = call.getInt("pallet", 1);
354
+ String color = call.getString("color", "FF0000");
355
+
356
+ Log.d(TAG, String.format("Parameters - address: %d, pallet: %d, color: %s", address, pallet, color));
357
+
358
+ if (serialManager == null) {
359
+ call.reject("Serial connection not initialized");
360
+ return;
361
+ }
362
+
363
+ if (!serialManager.isConnected()) {
364
+ call.reject("Not connected to serial port");
365
+ return;
366
+ }
367
+
368
+ try {
369
+ byte[] frame = createUnlockPalletFrame(address, pallet, color);
370
+ boolean success = serialManager.writeData(frame);
371
+
372
+ if (success) {
373
+ JSObject result = new JSObject();
374
+ result.put("success", true);
375
+ result.put("message", "Unlock command sent successfully");
376
+ call.resolve(result);
377
+ } else {
378
+ call.reject("Failed to send unlock command");
379
+ }
380
+
381
+ } catch (Exception e) {
382
+ Log.e(TAG, "Error in unlockPallet: " + e.getMessage(), e);
383
+ call.reject("Error sending unlock command: " + e.getMessage());
384
+ }
385
+ }
386
+
387
+ // Parser de respuestas del IOBoard
388
+ public static class IOBoardResponse {
389
+ public boolean isValid = false;
390
+ public int address = 0;
391
+ public int type = 0;
392
+ public byte[] data = new byte[0];
393
+ public String firmware = "";
394
+ public int doorStatus = 0;
395
+ public String rawHex = "";
396
+
397
+ public String toString() {
398
+ return String.format("IOBoardResponse{valid=%s, addr=%d, type=%d, firmware='%s', doorStatus=0x%02X, rawHex='%s'}",
399
+ isValid, address, type, firmware, doorStatus, rawHex);
400
+ }
401
+ }
402
+
403
+ public static IOBoardResponse parseIOBoardResponse(byte[] response) {
404
+ IOBoardResponse result = new IOBoardResponse();
405
+
406
+ // Convertir a hex para logging
407
+ StringBuilder hexBuilder = new StringBuilder();
408
+ for (byte b : response) {
409
+ hexBuilder.append(String.format("%02X ", b & 0xFF));
410
+ }
411
+ result.rawHex = hexBuilder.toString().trim();
412
+
413
+ Log.d(TAG, String.format("📥 Parsing IOBoard response: %s (%d bytes)", result.rawHex, response.length));
414
+
415
+ // Verificar longitud mínima (SOI + LEN + ADDR + TYPE + CRC = 6 bytes mínimo)
416
+ if (response.length < 6) {
417
+ Log.w(TAG, "❌ Response too short: " + response.length + " bytes");
418
+ return result;
419
+ }
420
+
421
+ // Verificar SOI (Start of Information = 0x0A para respuestas)
422
+ if ((response[0] & 0xFF) != 0x0A) {
423
+ Log.w(TAG, String.format("❌ Invalid SOI: expected 0x0A, got 0x%02X", response[0] & 0xFF));
424
+ return result;
425
+ }
426
+
427
+ // Obtener longitud del payload (incluye LEN + ADDRESS + TYPE + DATA + CRC + EOI)
428
+ int payloadLength = response[1] & 0xFF;
429
+ Log.d(TAG, String.format("📏 Payload length: %d bytes (includes LEN byte itself)", payloadLength));
430
+
431
+ // Calcular el tamaño del frame válido según documentación: SOI + payload
432
+ // El payloadLength ya incluye todo desde LEN hasta EOI
433
+ int expectedFrameLength = 1 + payloadLength; // SOI(1) + payload_completo(que incluye EOI)
434
+
435
+ // Detectar y eliminar byte FC extra si está presente
436
+ byte[] cleanResponse = response;
437
+ if (response.length > expectedFrameLength) {
438
+ // Verificar si el último byte es FC (0xFC)
439
+ int lastByte = response[response.length - 1] & 0xFF;
440
+ if (lastByte == 0xFC) {
441
+ Log.d(TAG, String.format("🔧 Detected FC byte at end, removing: got %d bytes, expected %d",
442
+ response.length, expectedFrameLength));
443
+ cleanResponse = new byte[response.length - 1];
444
+ System.arraycopy(response, 0, cleanResponse, 0, cleanResponse.length);
445
+ } else {
446
+ Log.d(TAG, String.format("⚠️ Frame longer than expected but last byte is not FC: 0x%02X", lastByte));
447
+ }
448
+ }
449
+
450
+ // Calcular el tamaño del frame válido: SOI + payload_completo
451
+ // Según documentación MTC3P08L: [SOI][LEN][ADDR][TYPE][DATA][CRC][EOI]
452
+ // El payloadLength ya incluye desde LEN hasta EOI
453
+ int validFrameLength = 1 + payloadLength; // SOI(1) + payload_completo(LEN_value que incluye EOI)
454
+
455
+ // Usar el frame limpio (sin FC)
456
+ byte[] validFrame;
457
+ if (cleanResponse.length >= validFrameLength) {
458
+ validFrame = new byte[validFrameLength];
459
+ System.arraycopy(cleanResponse, 0, validFrame, 0, validFrameLength);
460
+ } else {
461
+ validFrame = response;
462
+ }
463
+
464
+ // Verificar que tengamos suficientes bytes para el frame válido
465
+ if (validFrame.length < validFrameLength) {
466
+ Log.w(TAG, String.format("❌ Incomplete frame: expected %d bytes, got %d", validFrameLength, validFrame.length));
467
+ return result;
468
+ }
469
+
470
+ // Verificar EOI al final del frame (según documentación debe ser 0x0A)
471
+ int eoiPos = validFrameLength - 1;
472
+ if ((validFrame[eoiPos] & 0xFF) != 0x0A) {
473
+ Log.w(TAG, String.format("❌ Invalid EOI: expected 0x0A at position %d, got 0x%02X",
474
+ eoiPos, validFrame[eoiPos] & 0xFF));
475
+ }
476
+
477
+ // Extraer campos del payload (saltando SOI y LEN)
478
+ result.address = validFrame[2] & 0xFF;
479
+ result.type = validFrame[3] & 0xFF;
480
+
481
+ // Calcular longitud de datos basado en el tipo de frame
482
+ // TYPE 00 (Status): usa checksum de 8 bits (1 byte)
483
+ // TYPE 01: usa checksum de 8 bits (1 byte)
484
+ // TYPE 02: usa CRC16-Modbus (2 bytes)
485
+ int checksumLength = (result.type == 0x02) ? 2 : 1; // TYPE 02 usa CRC16, otros usan checksum de 8 bits
486
+ int dataLength = payloadLength - (3 + checksumLength); // payload - LEN(1) - ADDR(1) - TYPE(1) - CHECKSUM/CRC
487
+ if (dataLength > 0) {
488
+ result.data = new byte[dataLength];
489
+ System.arraycopy(validFrame, 4, result.data, 0, dataLength);
490
+ }
491
+
492
+ Log.d(TAG, String.format("🔍 Parsed - Address: %d, Type: %d, Data length: %d",
493
+ result.address, result.type, dataLength));
494
+
495
+ // Verificar CRC (antes del EOI final)
496
+ // Frame estructura: [SOI][LEN][ADDR][TYPE][DATA][CRC_LOW][CRC_HIGH][EOI]
497
+ // CRC está justo antes del EOI
498
+ int crcPos = validFrameLength - 3; // EOI(-1) + CRC_HIGH(-1) + CRC_LOW = -3
499
+ if (crcPos + 1 < validFrame.length) {
500
+ // Probar ambos órdenes de bytes para el CRC
501
+ int receivedCRC_BE = ((validFrame[crcPos + 1] & 0xFF) << 8) | (validFrame[crcPos] & 0xFF); // Big-endian
502
+ int receivedCRC_LE = ((validFrame[crcPos] & 0xFF) << 8) | (validFrame[crcPos + 1] & 0xFF); // Little-endian
503
+
504
+ // Probar diferentes variantes del CRC para encontrar la correcta
505
+
506
+ // Variante 1: Desde LEN hasta antes del CRC (actual)
507
+ byte[] frameForCRC1 = new byte[payloadLength - 2]; // payload_length - CRC(2)
508
+ System.arraycopy(validFrame, 1, frameForCRC1, 0, frameForCRC1.length);
509
+ int calculatedCRC1 = calculateCRC16Modbus(frameForCRC1);
510
+
511
+ // Variante 2: Sin incluir LEN (solo desde ADDRESS)
512
+ byte[] frameForCRC2 = new byte[payloadLength - 3]; // payload_length - LEN(1) - CRC(2)
513
+ System.arraycopy(validFrame, 2, frameForCRC2, 0, frameForCRC2.length);
514
+ int calculatedCRC2 = calculateCRC16Modbus(frameForCRC2);
515
+
516
+ // Variante 3: Todo el frame menos CRC y EOI (incluyendo SOI)
517
+ byte[] frameForCRC3 = new byte[validFrame.length - 3]; // todo menos CRC(2) y EOI(1)
518
+ System.arraycopy(validFrame, 0, frameForCRC3, 0, frameForCRC3.length);
519
+ int calculatedCRC3 = calculateCRC16Modbus(frameForCRC3);
520
+
521
+ // Debug: mostrar todas las variantes
522
+ StringBuilder crc1Hex = new StringBuilder();
523
+ for (byte b : frameForCRC1) { crc1Hex.append(String.format("%02X ", b & 0xFF)); }
524
+
525
+ StringBuilder crc2Hex = new StringBuilder();
526
+ for (byte b : frameForCRC2) { crc2Hex.append(String.format("%02X ", b & 0xFF)); }
527
+
528
+ StringBuilder crc3Hex = new StringBuilder();
529
+ for (byte b : frameForCRC3) { crc3Hex.append(String.format("%02X ", b & 0xFF)); }
530
+
531
+ Log.d(TAG, String.format("🔍 CRC V1 (LEN+): %s = 0x%04X", crc1Hex.toString().trim(), calculatedCRC1));
532
+ Log.d(TAG, String.format("🔍 CRC V2 (ADDR+): %s = 0x%04X", crc2Hex.toString().trim(), calculatedCRC2));
533
+ Log.d(TAG, String.format("🔍 CRC V3 (SOI+): %s = 0x%04X", crc3Hex.toString().trim(), calculatedCRC3));
534
+ Log.d(TAG, String.format("🔍 CRC Debug - CRC bytes: 0x%02X 0x%02X at positions %d,%d",
535
+ validFrame[crcPos] & 0xFF, validFrame[crcPos + 1] & 0xFF, crcPos, crcPos + 1));
536
+
537
+ // También probar interpretando el CRC como byte único
538
+ int receivedCRC_Single = validFrame[crcPos] & 0xFF; // Solo el primer byte del "CRC"
539
+
540
+ // Verificar todas las variantes contra todos los formatos posibles
541
+ boolean crcValid = false;
542
+ String validVariant = "";
543
+
544
+ // Probar V1 (LEN+)
545
+ if (receivedCRC_BE == calculatedCRC1) {
546
+ Log.d(TAG, "✅ CRC verification passed (V1-LEN+, Big-Endian)!");
547
+ crcValid = true; validVariant = "V1-BE";
548
+ } else if (receivedCRC_LE == calculatedCRC1) {
549
+ Log.d(TAG, "✅ CRC verification passed (V1-LEN+, Little-Endian)!");
550
+ crcValid = true; validVariant = "V1-LE";
551
+ }
552
+
553
+ // Probar V2 (ADDR+)
554
+ if (!crcValid && receivedCRC_BE == calculatedCRC2) {
555
+ Log.d(TAG, "✅ CRC verification passed (V2-ADDR+, Big-Endian)!");
556
+ crcValid = true; validVariant = "V2-BE";
557
+ } else if (!crcValid && receivedCRC_LE == calculatedCRC2) {
558
+ Log.d(TAG, "✅ CRC verification passed (V2-ADDR+, Little-Endian)!");
559
+ crcValid = true; validVariant = "V2-LE";
560
+ }
561
+
562
+ // Probar V3 (SOI+)
563
+ if (!crcValid && receivedCRC_BE == calculatedCRC3) {
564
+ Log.d(TAG, "✅ CRC verification passed (V3-SOI+, Big-Endian)!");
565
+ crcValid = true; validVariant = "V3-BE";
566
+ } else if (!crcValid && receivedCRC_LE == calculatedCRC3) {
567
+ Log.d(TAG, "✅ CRC verification passed (V3-SOI+, Little-Endian)!");
568
+ crcValid = true; validVariant = "V3-LE";
569
+ }
570
+
571
+ // Probar como checksum de 8 bits
572
+ if (!crcValid) {
573
+ int checksum1 = calculateSimpleChecksum(frameForCRC1);
574
+ int checksum2 = calculateSimpleChecksum(frameForCRC2);
575
+ int checksum3 = calculateSimpleChecksum(frameForCRC3);
576
+
577
+ Log.d(TAG, String.format("🔍 Simple checksums - V1: 0x%02X, V2: 0x%02X, V3: 0x%02X vs received: 0x%02X",
578
+ checksum1 & 0xFF, checksum2 & 0xFF, checksum3 & 0xFF, receivedCRC_Single));
579
+
580
+ if (receivedCRC_Single == (checksum1 & 0xFF)) {
581
+ Log.d(TAG, "✅ Checksum verification passed (V1-8bit)!");
582
+ crcValid = true; validVariant = "V1-8bit";
583
+ } else if (receivedCRC_Single == (checksum2 & 0xFF)) {
584
+ Log.d(TAG, "✅ Checksum verification passed (V2-8bit)!");
585
+ crcValid = true; validVariant = "V2-8bit";
586
+ } else if (receivedCRC_Single == (checksum3 & 0xFF)) {
587
+ Log.d(TAG, "✅ Checksum verification passed (V3-8bit)!");
588
+ crcValid = true; validVariant = "V3-8bit";
589
+ }
590
+ }
591
+
592
+ if (!crcValid) {
593
+ Log.w(TAG, "❌ CRC/Checksum mismatch in all variants and formats!");
594
+ Log.i(TAG, "ℹ️ IOBoard may use proprietary checksum algorithm - data parsing will continue");
595
+ } else {
596
+ Log.i(TAG, String.format("🎯 IOBoard CRC/Checksum format identified: %s", validVariant));
597
+ }
598
+ } else {
599
+ Log.w(TAG, "⚠️ No CRC found in response");
600
+ }
601
+
602
+ // Parse específico según el tipo de respuesta
603
+ if (result.type == 0x00) { // Status Response
604
+ parseStatusResponse(result);
605
+ }
606
+
607
+ result.isValid = true;
608
+ Log.d(TAG, String.format("✅ Valid IOBoard response parsed: %s", result.toString()));
609
+
610
+ return result;
611
+ }
612
+
613
+ private static void parseStatusResponse(IOBoardResponse response) {
614
+ if (response.data.length >= 6) {
615
+ // Byte 0: Door status
616
+ response.doorStatus = response.data[0] & 0xFF;
617
+
618
+ // Bytes 1-3: Firmware version (ejemplo: 01 00 63 = v1.0.99)
619
+ int major = response.data[1] & 0xFF;
620
+ int minor = response.data[2] & 0xFF;
621
+ int patch = response.data[3] & 0xFF;
622
+ response.firmware = String.format("v%d.%d.%d", major, minor, patch);
623
+
624
+ Log.d(TAG, String.format("📊 Status - Door: 0x%02X, Firmware: %s",
625
+ response.doorStatus, response.firmware));
626
+
627
+ // Bytes 4-5: Información adicional del sistema
628
+ if (response.data.length >= 6) {
629
+ int systemInfo1 = response.data[4] & 0xFF;
630
+ int systemInfo2 = response.data[5] & 0xFF;
631
+ Log.d(TAG, String.format("🔧 System Info: 0x%02X 0x%02X", systemInfo1, systemInfo2));
632
+ }
633
+ }
634
+ }
635
+
636
+ // Create MTC3P08L protocol frame for unlock pallet command
637
+ private byte[] createUnlockPalletFrame(int address, int palletNumber, String ledColor) {
638
+ // MTC3P08L Protocol Frame Structure (según documentación):
639
+ // [SOI][LEN][ADDR][TYPE][DATA][CRC][EOI]
640
+ // TYPE 01 = Single pallet parameter setting
641
+ // DATA = [NUM][LOCK][LED] = [1][1][6] = 8 bytes
642
+
643
+ byte soi = 0x0D; // Start of frame (según documentación)
644
+ byte eoi = 0x0A; // End of frame (según documentación)
645
+ byte dataLen = 8; // NUM(1) + LOCK(1) + LED(6) = 8 bytes
646
+ byte len = (byte) (dataLen + 7); // N+7 según documentación = 8+7 = 15 (0x0F)
647
+ byte addr = (byte) address;
648
+ byte type = 0x01; // Single pallet parameter setting
649
+
650
+ // DATA section:
651
+ byte palletNum = (byte) palletNumber; // NUM: 00-07 para pallets 1-8
652
+ byte doorLock = 0x01; // LOCK: 1=unlock, 0=no action
653
+
654
+ // LED section (6 bytes): [RGB_R][RGB_G][RGB_B][Intensity][BlinkTimes][BlinkSpeed]
655
+ byte red = 0, green = 0, blue = 0;
656
+ switch (ledColor.toLowerCase()) {
657
+ case "red":
658
+ red = (byte) 255;
659
+ break;
660
+ case "green":
661
+ green = (byte) 255;
662
+ break;
663
+ case "blue":
664
+ blue = (byte) 255;
665
+ break;
666
+ case "white":
667
+ red = green = blue = (byte) 255;
668
+ break;
669
+ case "yellow":
670
+ red = green = (byte) 255;
671
+ break;
672
+ case "cyan":
673
+ green = blue = (byte) 255;
674
+ break;
675
+ case "magenta":
676
+ red = blue = (byte) 255;
677
+ break;
678
+ default:
679
+ green = (byte) 255; // Default to green
680
+ break;
681
+ }
682
+
683
+ byte intensity = (byte) 255; // Max intensity
684
+ byte blinkTimes = 16; // 16 veces como en el ejemplo
685
+ byte blinkSpeed = 1; // 1 segundo
686
+
687
+ // Calculate CRC16-Modbus (x16+x15+x2+1) según documentación
688
+ byte[] dataForCrc = new byte[] {
689
+ soi, len, addr, type, palletNum, doorLock, red, green, blue, intensity, blinkTimes, blinkSpeed
690
+ };
691
+ int crc16 = calculateCRC16Modbus(dataForCrc);
692
+
693
+ // CRC16 en formato little-endian (low byte first, high byte second)
694
+ byte crcLow = (byte) (crc16 & 0xFF);
695
+ byte crcHigh = (byte) ((crc16 >> 8) & 0xFF);
696
+
697
+ return new byte[] {
698
+ soi, len, addr, type, // Header
699
+ palletNum, doorLock, // NUM, LOCK
700
+ red, green, blue, intensity, blinkTimes, blinkSpeed, // LED (6 bytes)
701
+ crcLow, crcHigh, // CRC (2 bytes)
702
+ eoi // End of frame
703
+ };
704
+ }
705
+
706
+ // Create Full Pallet Control frame (TYPE 02) - controls all 8 pallets
707
+ private byte[] createMultiplePalletsFrame(int address, String color, int intensity,
708
+ int blinkTimes, int blinkSpeed, List<Integer> pallets) {
709
+ // MTC3P08L Full Pallet Control Frame Structure (TYPE 02):
710
+ // [SOI][LEN][ADDR][TYPE][LOCK][EXT][LED1(6bytes)][LED2(6bytes)]...[LED8(6bytes)][CRC][EOI]
711
+ // Total: 1+1+1+1+1+1+(8*6)+2+1 = 57 bytes (0x39)
712
+
713
+ Log.d(TAG, "🎨 Creating Full Pallet Control frame: address=" + address +
714
+ ", color=" + color + ", intensity=" + intensity +
715
+ ", blinkTimes=" + blinkTimes + ", blinkSpeed=" + blinkSpeed +
716
+ ", pallets=" + pallets.toString());
717
+
718
+ byte soi = 0x0D; // Start of frame
719
+ byte eoi = 0x0A; // End of frame
720
+ byte len = 0x39; // Fixed length: 57 bytes for full pallet control
721
+ byte addr = (byte) address;
722
+ byte type = 0x02; // Full Pallet Control command
723
+
724
+ // Door lock control - following the documentation example:
725
+ // Example shows LOCK=0x01 for "Unlock: 01" (seems to be action code, not bit mask)
726
+ // For now, use 0x01 to match the documentation example exactly
727
+ byte lockControl = 0x01; // Unlock action (as per documentation example)
728
+
729
+ byte extControl = (byte) 0x80; // Extended output control (fixed as per example)
730
+
731
+ // Convert color to RGB
732
+ byte red = 0x00, green = 0x00, blue = 0x00;
733
+ switch (color.toUpperCase()) {
734
+ case "RED":
735
+ red = (byte) 0xFF;
736
+ break;
737
+ case "GREEN":
738
+ green = (byte) 0xFF;
739
+ break;
740
+ case "BLUE":
741
+ blue = (byte) 0xFF;
742
+ break;
743
+ case "YELLOW":
744
+ red = (byte) 0xFF;
745
+ green = (byte) 0xFF;
746
+ break;
747
+ case "PURPLE":
748
+ red = (byte) 0xFF;
749
+ blue = (byte) 0xFF;
750
+ break;
751
+ case "CYAN":
752
+ green = (byte) 0xFF;
753
+ blue = (byte) 0xFF;
754
+ break;
755
+ case "WHITE":
756
+ red = (byte) 0xFF;
757
+ green = (byte) 0xFF;
758
+ blue = (byte) 0xFF;
759
+ break;
760
+ default:
761
+ Log.w(TAG, "⚠️ Unknown color: " + color + ", using RED as default");
762
+ red = (byte) 0xFF;
763
+ break;
764
+ }
765
+
766
+ // Build data array for CRC calculation
767
+ // CRC includes SOI + all data up to (but not including) the CRC field itself
768
+ List<Byte> dataForCrc = new ArrayList<>();
769
+ dataForCrc.add(soi); // Include SOI in CRC calculation
770
+ dataForCrc.add(len);
771
+ dataForCrc.add(addr);
772
+ dataForCrc.add(type);
773
+ dataForCrc.add(lockControl);
774
+ dataForCrc.add(extControl);
775
+
776
+ // Add 8 LED groups (6 bytes each) - one for each pallet
777
+ for (int i = 1; i <= 8; i++) {
778
+ if (pallets.contains(i)) {
779
+ // This pallet should have the specified color and effects
780
+ dataForCrc.add(red);
781
+ dataForCrc.add(green);
782
+ dataForCrc.add(blue);
783
+ dataForCrc.add((byte) intensity);
784
+ dataForCrc.add((byte) blinkTimes);
785
+ dataForCrc.add((byte) blinkSpeed);
786
+ } else {
787
+ // This pallet should be off (all zeros)
788
+ dataForCrc.add((byte) 0x00); // R
789
+ dataForCrc.add((byte) 0x00); // G
790
+ dataForCrc.add((byte) 0x00); // B
791
+ dataForCrc.add((byte) 0x00); // Intensity (off)
792
+ dataForCrc.add((byte) 0x00); // BlinkTimes
793
+ dataForCrc.add((byte) 0x01); // BlinkSpeed (minimum value)
794
+ }
795
+ }
796
+
797
+ // Convert to byte array for CRC calculation
798
+ byte[] crcData = new byte[dataForCrc.size()];
799
+ for (int i = 0; i < dataForCrc.size(); i++) {
800
+ crcData[i] = dataForCrc.get(i);
801
+ }
802
+
803
+ // Calculate CRC16-Modbus (TYPE 02 uses CRC16, not simple checksum like TYPE 01)
804
+ int crc16 = calculateCRC16Modbus(crcData);
805
+
806
+ // CRC16 en formato little-endian (low byte first) - documentation shows 0C F1
807
+ byte crcLow = (byte) (crc16 & 0xFF);
808
+ byte crcHigh = (byte) ((crc16 >> 8) & 0xFF);
809
+
810
+ // Build final frame - DON'T add SOI again since it's already in dataForCrc
811
+ List<Byte> frame = new ArrayList<>();
812
+ frame.addAll(dataForCrc); // This already includes SOI + all data
813
+ frame.add(crcLow); // CRC low byte first (little-endian)
814
+ frame.add(crcHigh); // CRC high byte second
815
+ frame.add(eoi);
816
+
817
+ // Convert to byte array
818
+ byte[] frameArray = new byte[frame.size()];
819
+ for (int i = 0; i < frame.size(); i++) {
820
+ frameArray[i] = frame.get(i);
821
+ }
822
+
823
+ // Log the frame for debugging
824
+ StringBuilder hexFrame = new StringBuilder();
825
+ for (byte b : frameArray) {
826
+ hexFrame.append(String.format("%02X ", b & 0xFF));
827
+ }
828
+ Log.d(TAG, "🎯 Full Pallet Control frame created: " + hexFrame.toString().trim());
829
+ Log.d(TAG, "📊 Frame details - LockControl: 0x" + String.format("%02X", lockControl & 0xFF) +
830
+ ", ExtControl: 0x" + String.format("%02X", extControl & 0xFF) +
831
+ ", Color: " + color + " (" + String.format("%02X%02X%02X", red & 0xFF, green & 0xFF, blue & 0xFF) + ")" +
832
+ ", CRC16: 0x" + String.format("%02X%02X", crcLow & 0xFF, crcHigh & 0xFF) + " (little-endian)");
833
+
834
+ return frameArray;
835
+ }
836
+
837
+ // Implementación CRC16-Modbus (x16+x15+x2+1) según documentación MTC3P08L
838
+ private static int calculateCRC16Modbus(byte[] data) {
839
+ int crc = 0xFFFF; // Initial value
840
+
841
+ for (byte b : data) {
842
+ crc ^= (b & 0xFF); // XOR with byte
843
+
844
+ for (int i = 0; i < 8; i++) {
845
+ if ((crc & 0x0001) != 0) {
846
+ crc >>= 1;
847
+ crc ^= 0xA001; // Polynomial for Modbus (reversed)
848
+ } else {
849
+ crc >>= 1;
850
+ }
851
+ }
852
+ }
853
+
854
+ return crc;
855
+ }
856
+
857
+ // Create Status Query frame (Type 00) - matches documentation exactly
858
+ private byte[] createStatusQueryFrame(int address) {
859
+ // MTC3P08L Status Query Frame Structure (from documentation):
860
+ // 0D 07 01 00 B2 D9 0A
861
+ // [SOI][LEN][ADDR][TYPE][CRC][EOI]
862
+ // TYPE 00 = Status query (no data needed) - uses CRC16-Modbus like TYPE 02
863
+
864
+ byte soi = 0x0D; // Start of frame
865
+ byte eoi = 0x0A; // End of frame
866
+ byte len = 0x07; // Length: 7 (as per documentation)
867
+ byte addr = (byte) address;
868
+ byte type = 0x00; // Status query
869
+
870
+ // Calculate CRC16-Modbus for TYPE 00 (same as TYPE 02)
871
+ // CRC includes: SOI + LEN + ADDR + TYPE (no data for status query)
872
+ byte[] dataForCrc = new byte[] { soi, len, addr, type };
873
+ int crc16 = calculateCRC16Modbus(dataForCrc);
874
+
875
+ // CRC16 en formato little-endian (low byte first, high byte second)
876
+ byte crcLow = (byte) (crc16 & 0xFF);
877
+ byte crcHigh = (byte) ((crc16 >> 8) & 0xFF);
878
+
879
+ return new byte[] {
880
+ soi, len, addr, type, // Header (no data for status query)
881
+ crcLow, crcHigh, // CRC16-Modbus (2 bytes)
882
+ eoi // End of frame
883
+ };
884
+ }
885
+
886
+ // Test function to verify CRC calculation against documentation example
887
+ private void testCRCAgainstDocumentation() {
888
+ // Documentation example: 0D 0F 01 01 00 01 FF 00 00 FF 10 01 1D 6F 0A
889
+ // Expected CRC: 0x6F1D (little-endian: 1D 6F)
890
+ byte[] testData = new byte[] {
891
+ 0x0D, 0x0F, 0x01, 0x01, 0x00, 0x01, (byte)0xFF, 0x00, 0x00, (byte)0xFF, 0x10, 0x01
892
+ };
893
+
894
+ int calculatedCRC = calculateCRC16Modbus(testData);
895
+ int expectedCRC = 0x6F1D;
896
+
897
+ Log.d(TAG, "🧪 CRC TEST:");
898
+ Log.d(TAG, "Expected CRC: 0x" + Integer.toHexString(expectedCRC).toUpperCase() + " (bytes: 1D 6F)");
899
+ Log.d(TAG, "Calculated CRC: 0x" + Integer.toHexString(calculatedCRC).toUpperCase() +
900
+ " (bytes: " + String.format("%02X %02X", calculatedCRC & 0xFF, (calculatedCRC >> 8) & 0xFF) + ")");
901
+
902
+ if (calculatedCRC == expectedCRC) {
903
+ Log.i(TAG, "✅ CRC16-Modbus calculation is CORRECT!");
904
+ } else {
905
+ Log.w(TAG, "❌ CRC16-Modbus calculation mismatch - needs adjustment");
906
+ }
907
+ }
908
+
909
+ // Método para calcular checksum simple (suma de bytes)
910
+ private static int calculateSimpleChecksum(byte[] data) {
911
+ int sum = 0;
912
+ for (byte b : data) {
913
+ sum += (b & 0xFF);
914
+ }
915
+ return sum & 0xFF; // Retorna solo el byte menos significativo
916
+ }
917
+
918
+ @Override
919
+ protected void handleOnDestroy() {
920
+ Log.d(TAG, "Plugin destroyed");
921
+ if (serialManager != null) {
922
+ serialManager.closePort();
923
+ }
924
+ super.handleOnDestroy();
925
+ }
926
+ }