puda-drivers 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl

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.
@@ -3,12 +3,11 @@ Generic Serial Controller for communicating with devices over serial ports.
3
3
  """
4
4
 
5
5
  import serial
6
- import sys
7
6
  import time
8
7
  import serial.tools.list_ports
9
8
  import logging
10
9
  from typing import Optional, List, Tuple
11
- from abc import ABC, abstractmethod
10
+ from abc import ABC
12
11
 
13
12
  logger = logging.getLogger(__name__)
14
13
 
@@ -58,23 +57,6 @@ class SerialController(ABC):
58
57
 
59
58
  self.connect()
60
59
 
61
- @staticmethod
62
- def list_ports(filter_desc: Optional[str] = None) -> List[Tuple[str, str, str]]:
63
- """
64
- Lists available serial ports on the system.
65
-
66
- .. deprecated:: 0.0.1
67
- Use the module-level function :func:`list_serial_ports` instead.
68
- This method is kept for backward compatibility.
69
-
70
- Args:
71
- filter_desc: Optional string to filter ports by description (case-insensitive).
72
-
73
- Returns:
74
- List of tuples, where each tuple contains (port_name, description, hwid).
75
- """
76
- return list_serial_ports(filter_desc)
77
-
78
60
  def connect(self) -> None:
79
61
  """
80
62
  Establishes the serial connection to the port.
@@ -89,7 +71,9 @@ class SerialController(ABC):
89
71
 
90
72
  try:
91
73
  self._logger.info(
92
- f"Attempting connection to {self.port_name} at {self.baudrate} baud."
74
+ "Attempting connection to %s at %s baud.",
75
+ self.port_name,
76
+ self.baudrate,
93
77
  )
94
78
  self._serial = serial.Serial(
95
79
  port=self.port_name,
@@ -97,10 +81,10 @@ class SerialController(ABC):
97
81
  timeout=self.timeout,
98
82
  )
99
83
  self._serial.flush()
100
- self._logger.info(f"Successfully connected to {self.port_name}.")
84
+ self._logger.info("Successfully connected to %s.", self.port_name)
101
85
  except serial.SerialException as e:
102
86
  self._serial = None
103
- self._logger.error(f"Error connecting to port {self.port_name}: {e}")
87
+ self._logger.error("Error connecting to port %s: %s", self.port_name, e)
104
88
  raise serial.SerialException(
105
89
  f"Error connecting to port {self.port_name}: {e}"
106
90
  )
@@ -113,7 +97,7 @@ class SerialController(ABC):
113
97
  port_name = self._serial.port
114
98
  self._serial.close()
115
99
  self._serial = None
116
- self._logger.info(f"Serial connection to {port_name} closed.")
100
+ self._logger.info("Serial connection to %s closed.", port_name)
117
101
  else:
118
102
  self._logger.warning(
119
103
  "Serial port already disconnected or was never connected."
@@ -131,29 +115,31 @@ class SerialController(ABC):
131
115
  """
132
116
  if not self.is_connected or not self._serial:
133
117
  self._logger.error(
134
- f"Attempt to send command '{command}' failed: Device not connected."
118
+ "Attempt to send command '%s' failed: Device not connected.",
119
+ command,
135
120
  )
136
121
  # Retain raising an error for being disconnected, as that's a connection state issue
137
122
  raise serial.SerialException("Device not connected. Call connect() first.")
138
123
 
139
- command_bytes = bytes(command, "utf-8")
140
- self._logger.info(f"-> Sending: {repr(command)}")
124
+ self._logger.info("-> Sending: %r", command)
141
125
 
142
126
  # Send the command
143
127
  try:
144
128
  self._serial.reset_input_buffer() # clear input buffer
145
129
  self._serial.reset_output_buffer() # clear output buffer
146
- self._serial.write(command_bytes)
130
+ self._serial.write(bytes(command, "utf-8"))
147
131
  self._serial.flush()
148
132
 
149
133
  except serial.SerialTimeoutException as e:
150
134
  # Log the timeout error and return None as requested (no re-raise)
151
- self._logger.error(f"Timeout on command '{command}'. Error: {e}")
135
+ self._logger.error("Timeout on command '%s'. Error: %s", command, e)
152
136
  return None
153
137
 
154
138
  except serial.SerialException as e:
155
139
  self._logger.error(
156
- f"Serial error writing or reading command '{command}'. Error: {e}"
140
+ "Serial error writing or reading command '%s'. Error: %s",
141
+ command,
142
+ e,
157
143
  )
158
144
  return None
159
145
 
@@ -173,22 +159,51 @@ class SerialController(ABC):
173
159
  # Read all available bytes
174
160
  response += self._serial.read(self._serial.in_waiting)
175
161
 
176
- # 5. Check if the 'ok' response is in the accumulated data
177
- if b"ok" in response:
178
- self._logger.debug(f"<- Received response: {response!r}")
179
- return response.decode("utf-8", errors="ignore").strip()
180
- if b"err" in response:
181
- self._logger.error(f"<- Received error: {response!r}")
182
- return response.decode("utf-8", errors="ignore").strip()
162
+ # Check for expected response markers for early return
163
+ if b"ok" in response or b"err" in response:
164
+ break
183
165
  else:
184
166
  time.sleep(0.1)
185
167
 
168
+ # Timeout reached - check what we got
186
169
  if not response:
187
- self._logger.warning("<- Received no data (empty response).")
188
- self._logger.error(f"No response within {self.timeout} seconds.")
189
- sys.exit(124) # Exit code for timeout
190
-
191
- self._logger.info(
192
- f"<- Received: {response.decode('utf-8', errors='ignore').strip()!r}"
193
- )
194
- return response.decode("utf-8", errors="ignore").strip()
170
+ self._logger.error("No response within %s seconds.", self.timeout)
171
+ raise serial.SerialTimeoutException(
172
+ f"No response received within {self.timeout} seconds."
173
+ )
174
+
175
+ # Decode once and check the decoded string
176
+ decoded_response = response.decode("utf-8", errors="ignore").strip()
177
+
178
+ if "ok" in decoded_response.lower():
179
+ self._logger.debug("<- Received response: %r", decoded_response)
180
+ elif "err" in decoded_response.lower():
181
+ self._logger.error("<- Received error: %r", decoded_response)
182
+ else:
183
+ self._logger.warning(
184
+ "<- Received unexpected response (no 'ok' or 'err'): %r", decoded_response
185
+ )
186
+
187
+ return decoded_response
188
+
189
+ def execute(self, command: str) -> str:
190
+ """
191
+ Send a command and read the response atomically.
192
+
193
+ This method combines sending a command and reading its response into a
194
+ single atomic operation, ensuring the response corresponds to the command
195
+ that was just sent. This is the preferred method for commands that
196
+ require a response.
197
+
198
+ Args:
199
+ command: Command string to send (should include protocol terminator if needed)
200
+
201
+ Returns:
202
+ Response string from the device
203
+
204
+ Raises:
205
+ serial.SerialException: If device is not connected or communication fails
206
+ serial.SerialTimeoutException: If no response is received within timeout
207
+ """
208
+ self._send_command(command)
209
+ return self._read_response()