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