bear-tools 0.2.0__tar.gz → 0.2.2__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.
- {bear_tools-0.2.0 → bear_tools-0.2.2}/PKG-INFO +1 -1
- {bear_tools-0.2.0 → bear_tools-0.2.2}/pyproject.toml +1 -1
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/__init__.py +0 -5
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/client.py +2 -2
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/serial_manager.py +10 -8
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/server.py +1 -1
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/enhanced_enum.py +2 -2
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/macos_utils.py +42 -2
- {bear_tools-0.2.0 → bear_tools-0.2.2}/LICENSE +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/README.md +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/__init__.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/mods/__init__.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/mods/base.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/cereal/tokenizer.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/dict_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/enhanced_int_enum.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/example_protocol.yaml +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/fsm/README.md +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/fsm/__init__.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/fsm/fsm.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/linting_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/README.md +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/__init__.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/callback_config.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/color.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/log_level.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/lumberjack/logger.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/markdown_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/misc_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/network_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/os_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/publisher/__init__.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/publisher/listener.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/publisher/publisher.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/py.typed +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/spreadsheet_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/string_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/thread_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/time_utils.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/transport_protocol.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/type_defs.py +0 -0
- {bear_tools-0.2.0 → bear_tools-0.2.2}/src/bear_tools/yaml_utils.py +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
1
|
SERVER_ADDRESS: str = 'localhost'
|
|
4
2
|
SERVER_PORT_BASE: int = 14441
|
|
5
3
|
|
|
@@ -8,6 +6,3 @@ SERVER_COMMAND_CODE_SHUTDOWN: str = '__shutdown'
|
|
|
8
6
|
SERVER_COMMAND_GET_CONFIG: str = '__config'
|
|
9
7
|
SERVER_COMMAND_ANNOTATE: str = '__annotate:'
|
|
10
8
|
END_OF_MESSAGE: bytes = b'\x00\x00\x00' # All network messages terminated with this detectable blob
|
|
11
|
-
|
|
12
|
-
package_dir: Path = Path(__file__).parent.absolute()
|
|
13
|
-
config_path: Path = Path(package_dir / 'config.yaml')
|
|
@@ -330,7 +330,7 @@ class CerealClient(threading.Thread):
|
|
|
330
330
|
are visually distinct.
|
|
331
331
|
|
|
332
332
|
:param text: Message (command) to send
|
|
333
|
-
:param expect_output: If True, return
|
|
333
|
+
:param expect_output: If True, return device output up to {timeout_sec} after sending the message
|
|
334
334
|
:param timeout_sec: How long to wait for expected output to start+stop before returning whatever we have
|
|
335
335
|
:return: If {expect_output}, a list[str] containing output observed a result of sending {text}; otherwise, []
|
|
336
336
|
"""
|
|
@@ -345,7 +345,7 @@ class CerealClient(threading.Thread):
|
|
|
345
345
|
Used internally by :meth:`send` and :meth:`annotate`.
|
|
346
346
|
|
|
347
347
|
:param text: Message (command) to send
|
|
348
|
-
:param expect_output: If True, return
|
|
348
|
+
:param expect_output: If True, return device output up to {timeout_sec} after sending the message
|
|
349
349
|
:param timeout_sec: How long to wait for expected output to start+stop before returning whatever we have
|
|
350
350
|
:return: If {expect_output}, a list[str] containing output observed a result of sending {text}; otherwise, []
|
|
351
351
|
"""
|
|
@@ -337,6 +337,7 @@ class SerialManager(threading.Thread):
|
|
|
337
337
|
self.__talking_stick = threading.Lock()
|
|
338
338
|
self.__stop_event = threading.Event()
|
|
339
339
|
self.__processing_input = False # input to this system from the serial device
|
|
340
|
+
self.__line_buffer: str = '' # Holds trailing partial line across read() calls
|
|
340
341
|
|
|
341
342
|
self.device: serial.Serial | None = None
|
|
342
343
|
self.listeners: list[SerialListener] = []
|
|
@@ -447,22 +448,23 @@ class SerialManager(threading.Thread):
|
|
|
447
448
|
"""
|
|
448
449
|
Generator Method
|
|
449
450
|
|
|
450
|
-
Split raw bytes from the serial device into lines, prepending timestamps to each
|
|
451
|
+
Split raw bytes from the serial device into lines, prepending timestamps to each.
|
|
452
|
+
Incomplete trailing fragments are buffered in ``__line_buffer`` and prepended to the
|
|
453
|
+
next chunk so that log lines are never split across two timestamps.
|
|
451
454
|
|
|
452
455
|
:param raw: Raw bytes read from the serial device
|
|
453
456
|
"""
|
|
454
457
|
|
|
455
|
-
text = raw.decode(encoding='utf-8', errors='ignore')
|
|
458
|
+
text = self.__line_buffer + raw.decode(encoding='utf-8', errors='ignore')
|
|
459
|
+
self.__line_buffer = ''
|
|
456
460
|
lines = text.split('\n')
|
|
457
461
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
yield f'{timestamp}{line}\n'
|
|
462
|
+
# Last element is either '' (if text ended with \n) or an incomplete fragment
|
|
463
|
+
self.__line_buffer = lines.pop()
|
|
461
464
|
|
|
462
|
-
|
|
463
|
-
if lines[-1]:
|
|
465
|
+
for line in lines:
|
|
464
466
|
timestamp = f'[{get_timestamp()}] ' if self.add_timestamps else ''
|
|
465
|
-
yield f'{timestamp}{
|
|
467
|
+
yield f'{timestamp}{line}\n'
|
|
466
468
|
|
|
467
469
|
|
|
468
470
|
def run(self) -> None:
|
|
@@ -80,7 +80,7 @@ class CerealServer(socketserver.ThreadingTCPServer):
|
|
|
80
80
|
"""
|
|
81
81
|
Initializer
|
|
82
82
|
|
|
83
|
-
:param name: Nickname for the server (e.g. '
|
|
83
|
+
:param name: Nickname for the server (e.g. 'DeviceA', 'DeviceB')
|
|
84
84
|
:param address: The ipaddress or host name on which the server should listen
|
|
85
85
|
:param port: The port number that the server communicates through
|
|
86
86
|
:param device_path: The path to a serial device
|
|
@@ -38,7 +38,7 @@ class EnhancedEnum(Enum):
|
|
|
38
38
|
"""Return the member whose value equals `value`, or None if not found."""
|
|
39
39
|
try:
|
|
40
40
|
return cls(value)
|
|
41
|
-
except ValueError:
|
|
41
|
+
except (ValueError, TypeError):
|
|
42
42
|
return None
|
|
43
43
|
|
|
44
44
|
@classmethod
|
|
@@ -46,7 +46,7 @@ class EnhancedEnum(Enum):
|
|
|
46
46
|
"""Return the member name for `value`, or None if not found."""
|
|
47
47
|
try:
|
|
48
48
|
return cls(value).name
|
|
49
|
-
except ValueError:
|
|
49
|
+
except (ValueError, TypeError):
|
|
50
50
|
return None
|
|
51
51
|
|
|
52
52
|
|
|
@@ -70,6 +70,46 @@ def get_current_ssid() -> str | None:
|
|
|
70
70
|
return get_current_ssid_macos15()
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
def get_ssid_for_interface(interface: str) -> str | None:
|
|
74
|
+
"""
|
|
75
|
+
Get the SSID currently joined on a specific Wi-Fi interface
|
|
76
|
+
|
|
77
|
+
:param interface: The wireless interface to query (e.g. 'en0', 'en1')
|
|
78
|
+
:return: The SSID joined on `interface` if any; None otherwise
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
macos_version: str = platform.mac_ver()[0]
|
|
82
|
+
if macos_version.startswith('14'):
|
|
83
|
+
return get_current_ssid_macos14_and_older(interface=interface)
|
|
84
|
+
return get_current_ssid_macos15(interface=interface)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_local_ip_for_ssid(ssid: str, max_interface_index: int = 9) -> str | None:
|
|
88
|
+
"""
|
|
89
|
+
Find this Mac's local IPv4 address on the NIC currently joined to a given SSID
|
|
90
|
+
|
|
91
|
+
Iterates `en0` through `en<max_interface_index>` looking for an interface that reports the
|
|
92
|
+
requested SSID via `networksetup -getairportnetwork` (or its macOS 15+ equivalent), then
|
|
93
|
+
returns the IPv4 address bound to that interface via `ipconfig getifaddr`.
|
|
94
|
+
|
|
95
|
+
:param ssid: The SSID to match against
|
|
96
|
+
:param max_interface_index: Highest `en<N>` index to probe (default 9)
|
|
97
|
+
:return: The IPv4 address as a string if found; None otherwise
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
for index in range(max_interface_index + 1):
|
|
101
|
+
interface = f'en{index}'
|
|
102
|
+
if get_ssid_for_interface(interface) != ssid:
|
|
103
|
+
continue
|
|
104
|
+
try:
|
|
105
|
+
output: str = subprocess.check_output(['ipconfig', 'getifaddr', interface]).decode().strip()
|
|
106
|
+
except subprocess.CalledProcessError:
|
|
107
|
+
continue
|
|
108
|
+
if output:
|
|
109
|
+
return output
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
73
113
|
if __name__ == '__main__':
|
|
74
|
-
|
|
75
|
-
print(f'ssid: "{
|
|
114
|
+
_ssid: str | None = get_current_ssid()
|
|
115
|
+
print(f'ssid: "{_ssid}"')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|