UC2-REST 0.2.0.30__tar.gz → 0.2.0.31__tar.gz
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.
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/PKG-INFO +1 -1
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/PKG-INFO +1 -1
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/SOURCES.txt +1 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/UC2Client.py +4 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/__version__.py +1 -1
- uc2_rest-0.2.0.31/uc2rest/can.py +142 -0
- uc2_rest-0.2.0.31/uc2rest/canota.py +256 -0
- uc2_rest-0.2.0.31/uc2rest/home.py +305 -0
- uc2_rest-0.2.0.31/uc2rest/laser.py +143 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/motor.py +516 -79
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/mserial.py +11 -7
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/objective.py +33 -32
- uc2_rest-0.2.0.30/uc2rest/can.py +0 -33
- uc2_rest-0.2.0.30/uc2rest/home.py +0 -125
- uc2_rest-0.2.0.30/uc2rest/laser.py +0 -77
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/LICENSE +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/README.md +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/dependency_links.txt +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/not-zip-safe +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/requires.txt +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/UC2_REST.egg-info/top_level.txt +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/setup.cfg +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/setup.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/MockSerial.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/__init__.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/analog.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/camera.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/cmdrecorder.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/digitalout.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/galvo.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/gripper.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/lcddisplay.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/ledmatrix.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/logger.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/message.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/modules.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/pid.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/rotator.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/slm.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/state.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/temperature.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/utils.py +0 -0
- {uc2_rest-0.2.0.30 → uc2_rest-0.2.0.31}/uc2rest/wifi.py +0 -0
|
@@ -27,6 +27,7 @@ from .cmdrecorder import cmdRecorder
|
|
|
27
27
|
from .temperature import Temperature
|
|
28
28
|
from .message import Message
|
|
29
29
|
from .can import CAN
|
|
30
|
+
from .canota import CANOTA
|
|
30
31
|
try:
|
|
31
32
|
import requests
|
|
32
33
|
except:
|
|
@@ -109,6 +110,9 @@ class UC2Client(object):
|
|
|
109
110
|
# initialize CAN
|
|
110
111
|
self.can = CAN(self)
|
|
111
112
|
|
|
113
|
+
# initialize CAN OTA
|
|
114
|
+
self.canota = CANOTA(self)
|
|
115
|
+
|
|
112
116
|
# initialize gripper
|
|
113
117
|
self.gripper = Gripper(self)
|
|
114
118
|
|
|
@@ -6,7 +6,7 @@ __version__.py
|
|
|
6
6
|
|
|
7
7
|
__title__ = 'UC2-REST'
|
|
8
8
|
__description__ = 'This pacage will help you to drive the ESP32-driven microscopy control modules from UC2'
|
|
9
|
-
__version__ = "v0.2.0.
|
|
9
|
+
__version__ = "v0.2.0.31"
|
|
10
10
|
__author__ = 'Benedict Diederich'
|
|
11
11
|
__author_email__ = 'benedictdied@gmail.com'
|
|
12
12
|
__license__ = 'GPL v3'
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
class CAN(object):
|
|
2
|
+
def __init__(self, parent):
|
|
3
|
+
"""
|
|
4
|
+
CANController handles sending commands to a remote CAN device via the parent post_json interface.
|
|
5
|
+
|
|
6
|
+
:param parent: Parent object with post_json(path, payload, getReturn, timeout, nResponses)
|
|
7
|
+
"""
|
|
8
|
+
self._parent = parent
|
|
9
|
+
|
|
10
|
+
# Store latest scan results
|
|
11
|
+
self.scanResults = []
|
|
12
|
+
self.deviceCount = 0
|
|
13
|
+
|
|
14
|
+
# Register a callback function for the CAN status on the serial loop
|
|
15
|
+
if hasattr(self._parent, "serial"):
|
|
16
|
+
self._parent.serial.register_callback(self._callback_can_status, pattern="scan")
|
|
17
|
+
|
|
18
|
+
# Announce a function that is called when we receive a CAN scan update through the callback
|
|
19
|
+
self._callbackPerKey = {}
|
|
20
|
+
self.nCallbacks = 10
|
|
21
|
+
self._callbackPerKey = self.init_callback_functions(nCallbacks=self.nCallbacks)
|
|
22
|
+
print(self._callbackPerKey)
|
|
23
|
+
|
|
24
|
+
def init_callback_functions(self, nCallbacks=10):
|
|
25
|
+
"""Initialize the callback functions."""
|
|
26
|
+
_callbackPerKey = {}
|
|
27
|
+
self.nCallbacks = nCallbacks
|
|
28
|
+
for i in range(nCallbacks):
|
|
29
|
+
_callbackPerKey[i] = []
|
|
30
|
+
return _callbackPerKey
|
|
31
|
+
|
|
32
|
+
def _callback_can_status(self, data):
|
|
33
|
+
"""
|
|
34
|
+
Cast the json in the form:
|
|
35
|
+
{
|
|
36
|
+
"scan": [
|
|
37
|
+
{"canId": 20, "deviceType": 1, "status": 0, "deviceTypeStr": "laser", "statusStr": "idle"},
|
|
38
|
+
{"canId": 10, "deviceType": 0, "status": 0, "deviceTypeStr": "motor", "statusStr": "idle"}
|
|
39
|
+
],
|
|
40
|
+
"qid": 2,
|
|
41
|
+
"count": 1
|
|
42
|
+
}
|
|
43
|
+
into the scan results array.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
if "scan" in data:
|
|
47
|
+
self.scanResults = data["scan"]
|
|
48
|
+
self.deviceCount = data.get("count", len(self.scanResults))
|
|
49
|
+
|
|
50
|
+
# Call registered callback function with scan results
|
|
51
|
+
if callable(self._callbackPerKey[0]):
|
|
52
|
+
self._callbackPerKey[0](self.scanResults)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print("Error in _callback_can_status: ", e)
|
|
55
|
+
|
|
56
|
+
def register_callback(self, key, callbackfct):
|
|
57
|
+
"""Register a callback function for a specific key."""
|
|
58
|
+
self._callbackPerKey[key] = callbackfct
|
|
59
|
+
|
|
60
|
+
def reboot_remote(self, qid=1, can_address=0, isBlocking=False, timeout=2):
|
|
61
|
+
"""
|
|
62
|
+
Send a reboot signal to the remote CAN device.
|
|
63
|
+
|
|
64
|
+
:param qid: Query ID for the CAN command (default: 1)
|
|
65
|
+
:param isBlocking: If True, wait for response
|
|
66
|
+
:param timeout: Timeout for the command in seconds
|
|
67
|
+
:param can_address: Address of the CAN device to reboot (0 is master)
|
|
68
|
+
:return: Response from the device
|
|
69
|
+
"""
|
|
70
|
+
path = "/can_act"
|
|
71
|
+
payload = {
|
|
72
|
+
"task": path,
|
|
73
|
+
"restart": int(can_address)
|
|
74
|
+
}
|
|
75
|
+
nResponses = 1 if isBlocking else 0
|
|
76
|
+
# Send the payload to the parent, which handles the actual communication
|
|
77
|
+
return self._parent.post_json(
|
|
78
|
+
path,
|
|
79
|
+
payload,
|
|
80
|
+
getReturn=isBlocking,
|
|
81
|
+
timeout=timeout if isBlocking else 0,
|
|
82
|
+
nResponses=nResponses
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def scan(self, qid=1, timeout=5):
|
|
86
|
+
"""
|
|
87
|
+
Scan the CAN bus for connected devices.
|
|
88
|
+
|
|
89
|
+
:param qid: Query ID for the CAN command (default: 1)
|
|
90
|
+
:param timeout: Timeout for the scan in seconds (default: 5)
|
|
91
|
+
:return: Response containing scan results with device information
|
|
92
|
+
Example: {
|
|
93
|
+
"scan": [
|
|
94
|
+
{"canId": 10, "deviceType": 0, "deviceTypeStr": "motor", "status": 0, "statusStr": "idle"},
|
|
95
|
+
{"canId": 20, "deviceType": 1, "deviceTypeStr": "laser", "status": 0, "statusStr": "idle"}
|
|
96
|
+
],
|
|
97
|
+
"qid": 1,
|
|
98
|
+
"count": 2
|
|
99
|
+
}
|
|
100
|
+
"""
|
|
101
|
+
path = "/can_act"
|
|
102
|
+
payload = {
|
|
103
|
+
"task": path,
|
|
104
|
+
"scan": True,
|
|
105
|
+
"qid": qid
|
|
106
|
+
}
|
|
107
|
+
return self._parent.post_json(
|
|
108
|
+
path,
|
|
109
|
+
payload,
|
|
110
|
+
getReturn=True,
|
|
111
|
+
timeout=timeout,
|
|
112
|
+
nResponses=2
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def get_available_devices(self, timeout=2):
|
|
116
|
+
"""
|
|
117
|
+
Get list of available CAN devices.
|
|
118
|
+
|
|
119
|
+
:param timeout: Timeout for the command in seconds (default: 2)
|
|
120
|
+
:return: Response containing available CAN IDs and other bus information
|
|
121
|
+
Example: {
|
|
122
|
+
"input": {"task": "/can_get"},
|
|
123
|
+
"address": 1,
|
|
124
|
+
"addresspref": 1,
|
|
125
|
+
"addressgetcan": 1,
|
|
126
|
+
"nonworking": [0,0,0,...],
|
|
127
|
+
"available": [20,0,0,...],
|
|
128
|
+
"rx": 18,
|
|
129
|
+
"tx": 17
|
|
130
|
+
}
|
|
131
|
+
"""
|
|
132
|
+
path = "/can_get"
|
|
133
|
+
payload = {
|
|
134
|
+
"task": path
|
|
135
|
+
}
|
|
136
|
+
return self._parent.post_json(
|
|
137
|
+
path,
|
|
138
|
+
payload,
|
|
139
|
+
getReturn=True,
|
|
140
|
+
timeout=timeout,
|
|
141
|
+
nResponses=2
|
|
142
|
+
)
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
gTIMEOUT = 10 # seconds to wait for a response from the ESP32
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CANOTA(object):
|
|
9
|
+
"""
|
|
10
|
+
CAN OTA (Over-The-Air) update controller for UC2 satellite devices.
|
|
11
|
+
|
|
12
|
+
This class handles sending OTA commands to CAN slave devices and processing
|
|
13
|
+
the responses through callbacks. It manages the WiFi connection setup and
|
|
14
|
+
OTA server initialization on remote devices.
|
|
15
|
+
|
|
16
|
+
The OTA process involves:
|
|
17
|
+
1. Sending OTA command with WiFi credentials to a specific CAN device
|
|
18
|
+
2. Device connects to WiFi and starts ArduinoOTA server
|
|
19
|
+
3. Device sends back acknowledgment and IP address via serial
|
|
20
|
+
4. Device becomes available as UC2-CAN-<HEXID>.local for firmware upload
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, parent=None, nCallbacks=10):
|
|
24
|
+
"""
|
|
25
|
+
Initialize the CAN OTA controller.
|
|
26
|
+
|
|
27
|
+
:param parent: Parent UC2Client object with post_json and serial interfaces
|
|
28
|
+
:param nCallbacks: Number of callback slots to initialize (default: 10)
|
|
29
|
+
"""
|
|
30
|
+
self._parent = parent
|
|
31
|
+
self.nCallbacks = nCallbacks
|
|
32
|
+
|
|
33
|
+
# Initialize callback functions for different types of OTA events
|
|
34
|
+
self.init_callback_functions(self.nCallbacks)
|
|
35
|
+
|
|
36
|
+
# Register callback function for OTA status messages on the serial loop
|
|
37
|
+
if hasattr(self._parent, "serial"):
|
|
38
|
+
self._parent.serial.register_callback(self._callback_ota_status, pattern="ota")
|
|
39
|
+
|
|
40
|
+
def init_callback_functions(self, nCallbacks=10):
|
|
41
|
+
"""
|
|
42
|
+
Initialize the callback function dictionary.
|
|
43
|
+
|
|
44
|
+
:param nCallbacks: Number of callback slots to create
|
|
45
|
+
"""
|
|
46
|
+
self._callbackPerKey = {}
|
|
47
|
+
self.nCallbacks = nCallbacks
|
|
48
|
+
for i in range(nCallbacks):
|
|
49
|
+
self._callbackPerKey[i] = None
|
|
50
|
+
|
|
51
|
+
def _callback_ota_status(self, data):
|
|
52
|
+
"""
|
|
53
|
+
Process incoming OTA status messages from CAN devices.
|
|
54
|
+
|
|
55
|
+
Expected message format:
|
|
56
|
+
{
|
|
57
|
+
"ota": {
|
|
58
|
+
"canId": 20,
|
|
59
|
+
"status": 0,
|
|
60
|
+
"statusMsg": "Success",
|
|
61
|
+
"ip": "192.168.2.137",
|
|
62
|
+
"hostname": "UC2-CAN-14.local"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Status codes:
|
|
67
|
+
- 0: Success (WiFi connected, OTA server started)
|
|
68
|
+
- 1: WiFi connection failed
|
|
69
|
+
- 2: OTA start failed
|
|
70
|
+
|
|
71
|
+
:param data: JSON data containing OTA status information
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
ota_data = data["ota"]
|
|
75
|
+
can_id = ota_data.get("canId")
|
|
76
|
+
status = ota_data.get("status")
|
|
77
|
+
status_msg = ota_data.get("statusMsg", "")
|
|
78
|
+
ip_address = ota_data.get("ip", "")
|
|
79
|
+
hostname = ota_data.get("hostname", "")
|
|
80
|
+
|
|
81
|
+
# Create a structured response for callbacks
|
|
82
|
+
ota_response = {
|
|
83
|
+
"canId": can_id,
|
|
84
|
+
"status": status,
|
|
85
|
+
"statusMsg": status_msg,
|
|
86
|
+
"ip": ip_address,
|
|
87
|
+
"hostname": hostname,
|
|
88
|
+
"success": status == 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Trigger callbacks for different events
|
|
92
|
+
# Key 0: General OTA status updates
|
|
93
|
+
if callable(self._callbackPerKey[0]):
|
|
94
|
+
self._callbackPerKey[0](ota_response)
|
|
95
|
+
|
|
96
|
+
# Key for specific CAN ID (use can_id % nCallbacks to avoid overflow)
|
|
97
|
+
if can_id is not None:
|
|
98
|
+
callback_key = (can_id % (self.nCallbacks - 1)) + 1 # Reserve key 0 for general
|
|
99
|
+
if callable(self._callbackPerKey[callback_key]):
|
|
100
|
+
self._callbackPerKey[callback_key](ota_response)
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(f"Error in _callback_ota_status: {e}")
|
|
104
|
+
|
|
105
|
+
def register_callback(self, key, callback_function):
|
|
106
|
+
"""
|
|
107
|
+
Register a callback function for OTA events.
|
|
108
|
+
|
|
109
|
+
:param key: Callback key (0 for general events, 1-9 for specific CAN IDs)
|
|
110
|
+
:param callback_function: Function to call when OTA event occurs
|
|
111
|
+
Function should accept one parameter: ota_response dict
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
def my_ota_callback(ota_response):
|
|
115
|
+
if ota_response["success"]:
|
|
116
|
+
print(f"Device {ota_response['canId']} ready at {ota_response['ip']}")
|
|
117
|
+
else:
|
|
118
|
+
print(f"OTA failed for device {ota_response['canId']}: {ota_response['statusMsg']}")
|
|
119
|
+
|
|
120
|
+
ESP32.canota.register_callback(0, my_ota_callback)
|
|
121
|
+
"""
|
|
122
|
+
if 0 <= key < self.nCallbacks:
|
|
123
|
+
self._callbackPerKey[key] = callback_function
|
|
124
|
+
else:
|
|
125
|
+
raise ValueError(f"Callback key must be between 0 and {self.nCallbacks-1}")
|
|
126
|
+
|
|
127
|
+
def start_ota_update(self, can_id, ssid, password, timeout=300000, is_blocking=False,
|
|
128
|
+
response_timeout=gTIMEOUT):
|
|
129
|
+
"""
|
|
130
|
+
Send OTA command to a specific CAN slave device.
|
|
131
|
+
|
|
132
|
+
This tells the slave to connect to WiFi and start an OTA server.
|
|
133
|
+
The device will become available as UC2-CAN-<HEXID>.local for firmware upload.
|
|
134
|
+
|
|
135
|
+
:param can_id: CAN ID of the target device (e.g., 11 for Motor X, 20 for Laser)
|
|
136
|
+
:param ssid: WiFi network name
|
|
137
|
+
:param password: WiFi password
|
|
138
|
+
:param timeout: OTA timeout in milliseconds (default: 300000 = 5 minutes)
|
|
139
|
+
:param is_blocking: If True, wait for acknowledgment response
|
|
140
|
+
:param response_timeout: Timeout for response in seconds
|
|
141
|
+
:return: Response from the device (if blocking)
|
|
142
|
+
|
|
143
|
+
Examples:
|
|
144
|
+
# Motor X (CAN ID 11)
|
|
145
|
+
ESP32.canota.start_ota_update(11, "WiFi", "pass123")
|
|
146
|
+
|
|
147
|
+
# Laser (CAN ID 20) with custom timeout
|
|
148
|
+
ESP32.canota.start_ota_update(20, "WiFi", "pass123", timeout=600000)
|
|
149
|
+
"""
|
|
150
|
+
path = "/can_act"
|
|
151
|
+
payload = {
|
|
152
|
+
"task": path,
|
|
153
|
+
"ota": {
|
|
154
|
+
"canid": can_id,
|
|
155
|
+
"ssid": ssid,
|
|
156
|
+
"password": password,
|
|
157
|
+
"timeout": timeout
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# Send the payload to the parent, which handles the actual communication
|
|
162
|
+
nResponses = 1 if is_blocking else 0
|
|
163
|
+
return self._parent.post_json(
|
|
164
|
+
path,
|
|
165
|
+
payload,
|
|
166
|
+
getReturn=is_blocking,
|
|
167
|
+
timeout=response_timeout if is_blocking else 0,
|
|
168
|
+
nResponses=nResponses
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def start_motor_ota(self, motor_axis, ssid, password, timeout=300000, is_blocking=False):
|
|
172
|
+
"""
|
|
173
|
+
Convenience method for starting OTA on motor controllers.
|
|
174
|
+
|
|
175
|
+
:param motor_axis: Motor axis ("X", "Y", "Z") or CAN ID (11, 12, 13)
|
|
176
|
+
:param ssid: WiFi network name
|
|
177
|
+
:param password: WiFi password
|
|
178
|
+
:param timeout: OTA timeout in milliseconds
|
|
179
|
+
:param is_blocking: If True, wait for acknowledgment response
|
|
180
|
+
:return: Response from the device
|
|
181
|
+
|
|
182
|
+
Examples:
|
|
183
|
+
ESP32.canota.start_motor_ota("X", "WiFi", "pass123")
|
|
184
|
+
ESP32.canota.start_motor_ota(11, "WiFi", "pass123") # Same as above
|
|
185
|
+
"""
|
|
186
|
+
# Map motor axes to CAN IDs
|
|
187
|
+
motor_can_ids = {
|
|
188
|
+
"X": 11,
|
|
189
|
+
"Y": 12,
|
|
190
|
+
"Z": 13
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if isinstance(motor_axis, str):
|
|
194
|
+
motor_axis = motor_axis.upper()
|
|
195
|
+
if motor_axis not in motor_can_ids:
|
|
196
|
+
raise ValueError(f"Invalid motor axis '{motor_axis}'. Use 'X', 'Y', or 'Z'")
|
|
197
|
+
can_id = motor_can_ids[motor_axis]
|
|
198
|
+
else:
|
|
199
|
+
can_id = motor_axis
|
|
200
|
+
|
|
201
|
+
return self.start_ota_update(can_id, ssid, password, timeout, is_blocking)
|
|
202
|
+
|
|
203
|
+
def start_led_ota(self, ssid, password, timeout=300000, is_blocking=False):
|
|
204
|
+
"""
|
|
205
|
+
Convenience method for starting OTA on LED controller (CAN ID 30).
|
|
206
|
+
|
|
207
|
+
:param ssid: WiFi network name
|
|
208
|
+
:param password: WiFi password
|
|
209
|
+
:param timeout: OTA timeout in milliseconds
|
|
210
|
+
:param is_blocking: If True, wait for acknowledgment response
|
|
211
|
+
:return: Response from the device
|
|
212
|
+
"""
|
|
213
|
+
return self.start_ota_update(30, ssid, password, timeout, is_blocking)
|
|
214
|
+
|
|
215
|
+
def start_laser_ota(self, laser_id=0, ssid="", password="", timeout=300000, is_blocking=False):
|
|
216
|
+
"""
|
|
217
|
+
Convenience method for starting OTA on laser controller.
|
|
218
|
+
|
|
219
|
+
:param laser_id: Laser ID (0 for main laser, maps to CAN ID 20)
|
|
220
|
+
:param ssid: WiFi network name
|
|
221
|
+
:param password: WiFi password
|
|
222
|
+
:param timeout: OTA timeout in milliseconds
|
|
223
|
+
:param is_blocking: If True, wait for acknowledgment response
|
|
224
|
+
:return: Response from the device
|
|
225
|
+
"""
|
|
226
|
+
# Map laser ID to CAN ID (for now, laser 0 = CAN ID 20)
|
|
227
|
+
can_id = 20 + laser_id
|
|
228
|
+
return self.start_ota_update(can_id, ssid, password, timeout, is_blocking)
|
|
229
|
+
|
|
230
|
+
def get_ota_hostname(self, can_id):
|
|
231
|
+
"""
|
|
232
|
+
Generate the expected hostname for a CAN device in OTA mode.
|
|
233
|
+
|
|
234
|
+
:param can_id: CAN ID of the device
|
|
235
|
+
:return: Expected hostname (e.g., "UC2-CAN-14.local" for CAN ID 20)
|
|
236
|
+
"""
|
|
237
|
+
hex_id = format(can_id, 'X') # Convert to hexadecimal
|
|
238
|
+
return f"UC2-CAN-{hex_id}.local"
|
|
239
|
+
|
|
240
|
+
def get_platformio_upload_command(self, can_id, project_path="."):
|
|
241
|
+
"""
|
|
242
|
+
Generate the PlatformIO upload command for OTA firmware update.
|
|
243
|
+
|
|
244
|
+
:param can_id: CAN ID of the target device
|
|
245
|
+
:param project_path: Path to the PlatformIO project (default: current directory)
|
|
246
|
+
:return: PlatformIO upload command string
|
|
247
|
+
|
|
248
|
+
Example:
|
|
249
|
+
cmd = ESP32.canota.get_platformio_upload_command(20)
|
|
250
|
+
# Returns: "platformio run -t upload --upload-port UC2-CAN-14.local"
|
|
251
|
+
"""
|
|
252
|
+
hostname = self.get_ota_hostname(can_id)
|
|
253
|
+
if project_path != ".":
|
|
254
|
+
return f"platformio run -t upload --upload-port {hostname} -d {project_path}"
|
|
255
|
+
else:
|
|
256
|
+
return f"platformio run -t upload --upload-port {hostname}"
|