tasmota-webserial-esptool 9.1.7 → 9.1.9
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/dist/esp_loader.d.ts +1 -0
- package/dist/esp_loader.js +60 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -10
- package/dist/web/index.js +1 -1
- package/js/modules/esptool.js +1 -1
- package/js/script.js +8 -1
- package/js/webusb-serial.js +9 -19
- package/package.json +1 -1
- package/src/esp_loader.ts +70 -26
- package/src/index.ts +40 -13
package/dist/esp_loader.d.ts
CHANGED
package/dist/esp_loader.js
CHANGED
|
@@ -25,17 +25,18 @@ export class ESPLoader extends EventTarget {
|
|
|
25
25
|
this.__commandLock = Promise.resolve([0, []]);
|
|
26
26
|
this.__isReconfiguring = false;
|
|
27
27
|
this.__abandonCurrentOperation = false;
|
|
28
|
-
// Adaptive speed adjustment for flash read operations
|
|
29
|
-
// Using fixed conservative values that work reliably
|
|
28
|
+
// Adaptive speed adjustment for flash read operations
|
|
30
29
|
this.__adaptiveBlockMultiplier = 1;
|
|
31
30
|
this.__adaptiveMaxInFlightMultiplier = 1;
|
|
32
31
|
this.__consecutiveSuccessfulChunks = 0;
|
|
33
32
|
this.__lastAdaptiveAdjustment = 0;
|
|
34
33
|
this.__isCDCDevice = false;
|
|
35
34
|
this.state_DTR = false;
|
|
35
|
+
this.state_RTS = false;
|
|
36
36
|
this.__writeChain = Promise.resolve();
|
|
37
37
|
}
|
|
38
38
|
// Chip properties with parent delegation
|
|
39
|
+
// chipFamily accessed before initialization as designed
|
|
39
40
|
get chipFamily() {
|
|
40
41
|
return this._parent ? this._parent.chipFamily : this.__chipFamily;
|
|
41
42
|
}
|
|
@@ -81,7 +82,13 @@ export class ESPLoader extends EventTarget {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
get _inputBuffer() {
|
|
84
|
-
|
|
85
|
+
if (this._parent) {
|
|
86
|
+
return this._parent._inputBuffer;
|
|
87
|
+
}
|
|
88
|
+
if (this.__inputBuffer === undefined) {
|
|
89
|
+
throw new Error("_inputBuffer accessed before initialization");
|
|
90
|
+
}
|
|
91
|
+
return this.__inputBuffer;
|
|
85
92
|
}
|
|
86
93
|
get _inputBufferReadIndex() {
|
|
87
94
|
return this._parent
|
|
@@ -553,6 +560,7 @@ export class ESPLoader extends EventTarget {
|
|
|
553
560
|
// WebUSB (Android) - DTR/RTS Signal Handling & Reset Strategies
|
|
554
561
|
// ============================================================================
|
|
555
562
|
async setRTSWebUSB(state) {
|
|
563
|
+
this.state_RTS = state;
|
|
556
564
|
// Always specify both signals to avoid flipping the other line
|
|
557
565
|
// The WebUSB setSignals() now preserves unspecified signals, but being explicit is safer
|
|
558
566
|
await this.port.setSignals({
|
|
@@ -565,11 +573,12 @@ export class ESPLoader extends EventTarget {
|
|
|
565
573
|
// Always specify both signals to avoid flipping the other line
|
|
566
574
|
await this.port.setSignals({
|
|
567
575
|
dataTerminalReady: state,
|
|
568
|
-
requestToSend:
|
|
576
|
+
requestToSend: this.state_RTS, // Explicitly preserve current RTS state
|
|
569
577
|
});
|
|
570
578
|
}
|
|
571
579
|
async setDTRandRTSWebUSB(dtr, rts) {
|
|
572
580
|
this.state_DTR = dtr;
|
|
581
|
+
this.state_RTS = rts;
|
|
573
582
|
await this.port.setSignals({
|
|
574
583
|
dataTerminalReady: dtr,
|
|
575
584
|
requestToSend: rts,
|
|
@@ -753,28 +762,28 @@ export class ESPLoader extends EventTarget {
|
|
|
753
762
|
// Strategy 1: USB-JTAG/Serial (works in CDC mode on Desktop)
|
|
754
763
|
resetStrategies.push({
|
|
755
764
|
name: "USB-JTAG/Serial (WebUSB) - ESP32-S2",
|
|
756
|
-
fn: async
|
|
765
|
+
fn: async () => {
|
|
757
766
|
return await self.hardResetUSBJTAGSerialWebUSB();
|
|
758
767
|
},
|
|
759
768
|
});
|
|
760
769
|
// Strategy 2: USB-JTAG/Serial Inverted DTR (works in JTAG mode)
|
|
761
770
|
resetStrategies.push({
|
|
762
771
|
name: "USB-JTAG/Serial Inverted DTR (WebUSB) - ESP32-S2",
|
|
763
|
-
fn: async
|
|
772
|
+
fn: async () => {
|
|
764
773
|
return await self.hardResetUSBJTAGSerialInvertedDTRWebUSB();
|
|
765
774
|
},
|
|
766
775
|
});
|
|
767
776
|
// Strategy 3: UnixTight (CDC fallback)
|
|
768
777
|
resetStrategies.push({
|
|
769
778
|
name: "UnixTight (WebUSB) - ESP32-S2 CDC",
|
|
770
|
-
fn: async
|
|
779
|
+
fn: async () => {
|
|
771
780
|
return await self.hardResetUnixTightWebUSB();
|
|
772
781
|
},
|
|
773
782
|
});
|
|
774
783
|
// Strategy 4: Classic reset (CDC fallback)
|
|
775
784
|
resetStrategies.push({
|
|
776
785
|
name: "Classic (WebUSB) - ESP32-S2 CDC",
|
|
777
|
-
fn: async
|
|
786
|
+
fn: async () => {
|
|
778
787
|
return await self.hardResetClassicWebUSB();
|
|
779
788
|
},
|
|
780
789
|
});
|
|
@@ -783,19 +792,19 @@ export class ESPLoader extends EventTarget {
|
|
|
783
792
|
// Other USB-JTAG chips: Try Inverted DTR first - works best for ESP32-H2 and other JTAG chips
|
|
784
793
|
resetStrategies.push({
|
|
785
794
|
name: "USB-JTAG/Serial Inverted DTR (WebUSB)",
|
|
786
|
-
fn: async
|
|
795
|
+
fn: async () => {
|
|
787
796
|
return await self.hardResetUSBJTAGSerialInvertedDTRWebUSB();
|
|
788
797
|
},
|
|
789
798
|
});
|
|
790
799
|
resetStrategies.push({
|
|
791
800
|
name: "USB-JTAG/Serial (WebUSB)",
|
|
792
|
-
fn: async
|
|
801
|
+
fn: async () => {
|
|
793
802
|
return await self.hardResetUSBJTAGSerialWebUSB();
|
|
794
803
|
},
|
|
795
804
|
});
|
|
796
805
|
resetStrategies.push({
|
|
797
806
|
name: "Inverted DTR Classic (WebUSB)",
|
|
798
|
-
fn: async
|
|
807
|
+
fn: async () => {
|
|
799
808
|
return await self.hardResetInvertedDTRWebUSB();
|
|
800
809
|
},
|
|
801
810
|
});
|
|
@@ -807,31 +816,31 @@ export class ESPLoader extends EventTarget {
|
|
|
807
816
|
// CH340/CH343: UnixTight works best (like CP2102)
|
|
808
817
|
resetStrategies.push({
|
|
809
818
|
name: "UnixTight (WebUSB) - CH34x",
|
|
810
|
-
fn: async
|
|
819
|
+
fn: async () => {
|
|
811
820
|
return await self.hardResetUnixTightWebUSB();
|
|
812
821
|
},
|
|
813
822
|
});
|
|
814
823
|
resetStrategies.push({
|
|
815
824
|
name: "Classic (WebUSB) - CH34x",
|
|
816
|
-
fn: async
|
|
825
|
+
fn: async () => {
|
|
817
826
|
return await self.hardResetClassicWebUSB();
|
|
818
827
|
},
|
|
819
828
|
});
|
|
820
829
|
resetStrategies.push({
|
|
821
830
|
name: "Inverted Both (WebUSB) - CH34x",
|
|
822
|
-
fn: async
|
|
831
|
+
fn: async () => {
|
|
823
832
|
return await self.hardResetInvertedWebUSB();
|
|
824
833
|
},
|
|
825
834
|
});
|
|
826
835
|
resetStrategies.push({
|
|
827
836
|
name: "Inverted RTS (WebUSB) - CH34x",
|
|
828
|
-
fn: async
|
|
837
|
+
fn: async () => {
|
|
829
838
|
return await self.hardResetInvertedRTSWebUSB();
|
|
830
839
|
},
|
|
831
840
|
});
|
|
832
841
|
resetStrategies.push({
|
|
833
842
|
name: "Inverted DTR (WebUSB) - CH34x",
|
|
834
|
-
fn: async
|
|
843
|
+
fn: async () => {
|
|
835
844
|
return await self.hardResetInvertedDTRWebUSB();
|
|
836
845
|
},
|
|
837
846
|
});
|
|
@@ -841,31 +850,31 @@ export class ESPLoader extends EventTarget {
|
|
|
841
850
|
// Try it first, then fallback to other strategies
|
|
842
851
|
resetStrategies.push({
|
|
843
852
|
name: "UnixTight (WebUSB) - CP2102",
|
|
844
|
-
fn: async
|
|
853
|
+
fn: async () => {
|
|
845
854
|
return await self.hardResetUnixTightWebUSB();
|
|
846
855
|
},
|
|
847
856
|
});
|
|
848
857
|
resetStrategies.push({
|
|
849
858
|
name: "Classic (WebUSB) - CP2102",
|
|
850
|
-
fn: async
|
|
859
|
+
fn: async () => {
|
|
851
860
|
return await self.hardResetClassicWebUSB();
|
|
852
861
|
},
|
|
853
862
|
});
|
|
854
863
|
resetStrategies.push({
|
|
855
864
|
name: "Inverted Both (WebUSB) - CP2102",
|
|
856
|
-
fn: async
|
|
865
|
+
fn: async () => {
|
|
857
866
|
return await self.hardResetInvertedWebUSB();
|
|
858
867
|
},
|
|
859
868
|
});
|
|
860
869
|
resetStrategies.push({
|
|
861
870
|
name: "Inverted RTS (WebUSB) - CP2102",
|
|
862
|
-
fn: async
|
|
871
|
+
fn: async () => {
|
|
863
872
|
return await self.hardResetInvertedRTSWebUSB();
|
|
864
873
|
},
|
|
865
874
|
});
|
|
866
875
|
resetStrategies.push({
|
|
867
876
|
name: "Inverted DTR (WebUSB) - CP2102",
|
|
868
|
-
fn: async
|
|
877
|
+
fn: async () => {
|
|
869
878
|
return await self.hardResetInvertedDTRWebUSB();
|
|
870
879
|
},
|
|
871
880
|
});
|
|
@@ -874,7 +883,7 @@ export class ESPLoader extends EventTarget {
|
|
|
874
883
|
// For other USB-Serial chips, try UnixTight first, then multiple strategies
|
|
875
884
|
resetStrategies.push({
|
|
876
885
|
name: "UnixTight (WebUSB)",
|
|
877
|
-
fn: async
|
|
886
|
+
fn: async () => {
|
|
878
887
|
return await self.hardResetUnixTightWebUSB();
|
|
879
888
|
},
|
|
880
889
|
});
|
|
@@ -948,7 +957,6 @@ export class ESPLoader extends EventTarget {
|
|
|
948
957
|
}
|
|
949
958
|
}
|
|
950
959
|
else {
|
|
951
|
-
// Web Serial (Desktop) strategies
|
|
952
960
|
// Strategy: USB-JTAG/Serial reset
|
|
953
961
|
if (isUSBJTAGSerial || isEspressifUSB) {
|
|
954
962
|
resetStrategies.push({
|
|
@@ -1043,9 +1051,9 @@ export class ESPLoader extends EventTarget {
|
|
|
1043
1051
|
// just reset (no bootloader mode)
|
|
1044
1052
|
if (this.isWebUSB()) {
|
|
1045
1053
|
// WebUSB: Use longer delays for better compatibility
|
|
1046
|
-
await this.
|
|
1054
|
+
await this.setRTSWebUSB(true); // EN->LOW
|
|
1047
1055
|
await this.sleep(200);
|
|
1048
|
-
await this.
|
|
1056
|
+
await this.setRTSWebUSB(false);
|
|
1049
1057
|
await this.sleep(200);
|
|
1050
1058
|
this.logger.log("Hard reset (WebUSB).");
|
|
1051
1059
|
}
|
|
@@ -1265,6 +1273,11 @@ export class ESPLoader extends EventTarget {
|
|
|
1265
1273
|
// Process all available bytes without going back to outer loop
|
|
1266
1274
|
// This is critical for handling high-speed burst transfers
|
|
1267
1275
|
while (this._inputBufferAvailable > 0) {
|
|
1276
|
+
// Periodic timeout check to prevent hang on slow data
|
|
1277
|
+
if (Date.now() - startTime > timeout) {
|
|
1278
|
+
const waitingFor = partialPacket === null ? "header" : "content";
|
|
1279
|
+
throw new SlipReadError("Timed out waiting for packet " + waitingFor);
|
|
1280
|
+
}
|
|
1268
1281
|
const b = this._readByte();
|
|
1269
1282
|
if (partialPacket === null) {
|
|
1270
1283
|
// waiting for packet header
|
|
@@ -2163,7 +2176,15 @@ export class ESPLoader extends EventTarget {
|
|
|
2163
2176
|
resolve(undefined);
|
|
2164
2177
|
return;
|
|
2165
2178
|
}
|
|
2166
|
-
|
|
2179
|
+
// Set a timeout to prevent hanging (important for node-usb)
|
|
2180
|
+
const timeout = setTimeout(() => {
|
|
2181
|
+
this.logger.debug("Disconnect timeout - forcing resolution");
|
|
2182
|
+
resolve(undefined);
|
|
2183
|
+
}, 1000);
|
|
2184
|
+
this.addEventListener("disconnect", () => {
|
|
2185
|
+
clearTimeout(timeout);
|
|
2186
|
+
resolve(undefined);
|
|
2187
|
+
}, { once: true });
|
|
2167
2188
|
// Only cancel if reader is still active
|
|
2168
2189
|
try {
|
|
2169
2190
|
this._reader.cancel();
|
|
@@ -2171,10 +2192,19 @@ export class ESPLoader extends EventTarget {
|
|
|
2171
2192
|
catch (err) {
|
|
2172
2193
|
this.logger.debug(`Reader cancel error: ${err}`);
|
|
2173
2194
|
// Reader already released, resolve immediately
|
|
2195
|
+
clearTimeout(timeout);
|
|
2174
2196
|
resolve(undefined);
|
|
2175
2197
|
}
|
|
2176
2198
|
});
|
|
2177
2199
|
this.connected = false;
|
|
2200
|
+
// Close the port (important for node-usb adapter)
|
|
2201
|
+
try {
|
|
2202
|
+
await this.port.close();
|
|
2203
|
+
this.logger.debug("Port closed successfully");
|
|
2204
|
+
}
|
|
2205
|
+
catch (err) {
|
|
2206
|
+
this.logger.debug(`Port close error: ${err}`);
|
|
2207
|
+
}
|
|
2178
2208
|
}
|
|
2179
2209
|
/**
|
|
2180
2210
|
* @name reconnectAndResume
|
|
@@ -2693,6 +2723,10 @@ class EspStubLoader extends ESPLoader {
|
|
|
2693
2723
|
if (size > maxValue) {
|
|
2694
2724
|
throw new Error(`Size ${size} exceeds maximum value ${maxValue}`);
|
|
2695
2725
|
}
|
|
2726
|
+
// Check for wrap-around
|
|
2727
|
+
if (offset + size > maxValue) {
|
|
2728
|
+
throw new Error(`Region end (offset + size = ${offset + size}) exceeds maximum addressable range ${maxValue}`);
|
|
2729
|
+
}
|
|
2696
2730
|
const timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
|
|
2697
2731
|
const buffer = pack("<II", offset, size);
|
|
2698
2732
|
await this.checkCommand(ESP_ERASE_REGION, buffer, 0, timeout);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import { Logger } from "./const";
|
|
|
2
2
|
import { ESPLoader } from "./esp_loader";
|
|
3
3
|
export type { Logger } from "./const";
|
|
4
4
|
export { ESPLoader } from "./esp_loader";
|
|
5
|
-
export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31, } from "./const";
|
|
5
|
+
export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31, ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_END, ESP_MEM_DATA, ESP_SYNC, ESP_WRITE_REG, ESP_READ_REG, ESP_ERASE_FLASH, ESP_ERASE_REGION, ESP_READ_FLASH, ESP_SPI_SET_PARAMS, ESP_SPI_ATTACH, ESP_CHANGE_BAUDRATE, ESP_SPI_FLASH_MD5, ESP_GET_SECURITY_INFO, ESP_CHECKSUM_MAGIC, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, ROM_INVALID_RECV_MSG, USB_RAM_BLOCK, ESP_RAM_BLOCK, DEFAULT_TIMEOUT, CHIP_ERASE_TIMEOUT, MAX_TIMEOUT, SYNC_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, MEM_END_ROM_TIMEOUT, FLASH_READ_TIMEOUT, } from "./const";
|
|
6
6
|
export declare const connect: (logger: Logger) => Promise<ESPLoader>;
|
|
7
7
|
export declare const connectWithPort: (port: SerialPort, logger: Logger) => Promise<ESPLoader>;
|
package/dist/index.js
CHANGED
|
@@ -2,30 +2,34 @@
|
|
|
2
2
|
import { ESP_ROM_BAUD } from "./const";
|
|
3
3
|
import { ESPLoader } from "./esp_loader";
|
|
4
4
|
export { ESPLoader } from "./esp_loader";
|
|
5
|
-
export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31,
|
|
5
|
+
export { CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2, CHIP_FAMILY_ESP32S3, CHIP_FAMILY_ESP8266, CHIP_FAMILY_ESP32C2, CHIP_FAMILY_ESP32C3, CHIP_FAMILY_ESP32C5, CHIP_FAMILY_ESP32C6, CHIP_FAMILY_ESP32C61, CHIP_FAMILY_ESP32H2, CHIP_FAMILY_ESP32H4, CHIP_FAMILY_ESP32H21, CHIP_FAMILY_ESP32P4, CHIP_FAMILY_ESP32S31,
|
|
6
|
+
// Command constants
|
|
7
|
+
ESP_FLASH_BEGIN, ESP_FLASH_DATA, ESP_FLASH_END, ESP_MEM_BEGIN, ESP_MEM_END, ESP_MEM_DATA, ESP_SYNC, ESP_WRITE_REG, ESP_READ_REG, ESP_ERASE_FLASH, ESP_ERASE_REGION, ESP_READ_FLASH, ESP_SPI_SET_PARAMS, ESP_SPI_ATTACH, ESP_CHANGE_BAUDRATE, ESP_SPI_FLASH_MD5, ESP_GET_SECURITY_INFO, ESP_CHECKSUM_MAGIC, ESP_FLASH_DEFL_BEGIN, ESP_FLASH_DEFL_DATA, ESP_FLASH_DEFL_END, ROM_INVALID_RECV_MSG,
|
|
8
|
+
// Block size constants
|
|
9
|
+
USB_RAM_BLOCK, ESP_RAM_BLOCK,
|
|
10
|
+
// Timeout constants
|
|
11
|
+
DEFAULT_TIMEOUT, CHIP_ERASE_TIMEOUT, MAX_TIMEOUT, SYNC_TIMEOUT, ERASE_REGION_TIMEOUT_PER_MB, MEM_END_ROM_TIMEOUT, FLASH_READ_TIMEOUT, } from "./const";
|
|
6
12
|
export const connect = async (logger) => {
|
|
13
|
+
// - Request a port and open a connection.
|
|
14
|
+
// Try to use requestSerialPort if available (supports WebUSB for Android)
|
|
7
15
|
let port;
|
|
8
|
-
// Check if a custom requestSerialPort function is available (e.g., from WebUSB wrapper)
|
|
9
16
|
const customRequestPort = globalThis.requestSerialPort;
|
|
10
17
|
if (typeof customRequestPort === "function") {
|
|
11
|
-
// Use custom port request function (handles Android/WebUSB automatically)
|
|
12
|
-
logger.log("Using custom port request function");
|
|
13
18
|
port = await customRequestPort();
|
|
14
19
|
}
|
|
15
20
|
else {
|
|
16
|
-
//
|
|
21
|
+
// Check if Web Serial API is available
|
|
17
22
|
if (!navigator.serial) {
|
|
18
23
|
throw new Error("Web Serial API is not supported in this browser. " +
|
|
19
|
-
"Please use Chrome
|
|
24
|
+
"Please use Chrome, Edge, or Opera on desktop, or Chrome on Android. " +
|
|
20
25
|
"Note: The page must be served over HTTPS or localhost.");
|
|
21
26
|
}
|
|
22
27
|
port = await navigator.serial.requestPort();
|
|
23
28
|
}
|
|
24
|
-
// Only open if not already open (
|
|
29
|
+
// Only open if not already open (requestSerialPort may return an opened port)
|
|
25
30
|
if (!port.readable || !port.writable) {
|
|
26
31
|
await port.open({ baudRate: ESP_ROM_BAUD });
|
|
27
32
|
}
|
|
28
|
-
logger.log("Connected successfully.");
|
|
29
33
|
return new ESPLoader(port, logger);
|
|
30
34
|
};
|
|
31
35
|
export const connectWithPort = async (port, logger) => {
|
|
@@ -33,10 +37,9 @@ export const connectWithPort = async (port, logger) => {
|
|
|
33
37
|
if (!port) {
|
|
34
38
|
throw new Error("Port is required");
|
|
35
39
|
}
|
|
36
|
-
//
|
|
40
|
+
// Check if port is already open, if not open it
|
|
37
41
|
if (!port.readable || !port.writable) {
|
|
38
42
|
await port.open({ baudRate: ESP_ROM_BAUD });
|
|
39
43
|
}
|
|
40
|
-
logger.log("Connected successfully.");
|
|
41
44
|
return new ESPLoader(port, logger);
|
|
42
45
|
};
|