1818from .models import V2AuthResponse , V2HomieSchema , V2StatusInfo
1919
2020
21+ def _build_url (host : str , port : int , path : str ) -> str :
22+ """Build an HTTP URL, omitting the port when it is the default (80)."""
23+ if port == 80 :
24+ return f"http://{ host } { path } "
25+ return f"http://{ host } :{ port } { path } "
26+
27+
2128def _str (val : object ) -> str :
2229 """Extract a string from a JSON-decoded value."""
2330 return str (val ) if val is not None else ""
@@ -37,6 +44,7 @@ async def register_v2(
3744 name : str ,
3845 passphrase : str | None = None ,
3946 timeout : float = 10.0 ,
47+ port : int = 80 ,
4048) -> V2AuthResponse :
4149 """Register with the SPAN Panel v2 API and obtain access + MQTT credentials.
4250
@@ -49,6 +57,7 @@ async def register_v2(
4957 name: Client display name base (e.g., "home-assistant"); a UUID suffix is appended
5058 passphrase: Panel passphrase (printed on label or set by owner). None for door bypass.
5159 timeout: Request timeout in seconds
60+ port: HTTP port of the panel bootstrap API
5261
5362 Returns:
5463 V2AuthResponse with access token and MQTT broker credentials
@@ -59,7 +68,7 @@ async def register_v2(
5968 SpanPanelTimeoutError: Request timed out
6069 SpanPanelAPIError: Unexpected response
6170 """
62- url = f"http:// { host } / api/v2/auth/register"
71+ url = _build_url ( host , port , "/ api/v2/auth/register")
6372 # The panel requires unique client names — append a random suffix.
6473 # The passphrase field must be "hopPassphrase" per the SPAN v2 API spec.
6574 suffix = uuid .uuid4 ().hex [:8 ]
@@ -99,12 +108,13 @@ async def register_v2(
99108 )
100109
101110
102- async def download_ca_cert (host : str , timeout : float = 10.0 ) -> str :
111+ async def download_ca_cert (host : str , timeout : float = 10.0 , port : int = 80 ) -> str :
103112 """Download the PEM CA certificate from the SPAN Panel.
104113
105114 Args:
106115 host: IP address or hostname of the SPAN Panel
107116 timeout: Request timeout in seconds
117+ port: HTTP port of the panel bootstrap API
108118
109119 Returns:
110120 PEM-encoded CA certificate as a string
@@ -114,7 +124,7 @@ async def download_ca_cert(host: str, timeout: float = 10.0) -> str:
114124 SpanPanelTimeoutError: Request timed out
115125 SpanPanelAPIError: Unexpected response or invalid PEM
116126 """
117- url = f"http:// { host } / api/v2/certificate/ca"
127+ url = _build_url ( host , port , "/ api/v2/certificate/ca")
118128
119129 try :
120130 async with httpx .AsyncClient (timeout = timeout , verify = False ) as client : # nosec B501
@@ -134,14 +144,15 @@ async def download_ca_cert(host: str, timeout: float = 10.0) -> str:
134144 return pem
135145
136146
137- async def get_homie_schema (host : str , timeout : float = 10.0 ) -> V2HomieSchema :
147+ async def get_homie_schema (host : str , timeout : float = 10.0 , port : int = 80 ) -> V2HomieSchema :
138148 """Fetch the Homie property schema from the SPAN Panel.
139149
140150 This endpoint is unauthenticated.
141151
142152 Args:
143153 host: IP address or hostname of the SPAN Panel
144154 timeout: Request timeout in seconds
155+ port: HTTP port of the panel bootstrap API
145156
146157 Returns:
147158 V2HomieSchema with firmware version, schema hash, and type definitions
@@ -151,7 +162,7 @@ async def get_homie_schema(host: str, timeout: float = 10.0) -> V2HomieSchema:
151162 SpanPanelTimeoutError: Request timed out
152163 SpanPanelAPIError: Unexpected response
153164 """
154- url = f"http:// { host } / api/v2/homie/schema"
165+ url = _build_url ( host , port , "/ api/v2/homie/schema")
155166
156167 try :
157168 async with httpx .AsyncClient (timeout = timeout , verify = False ) as client : # nosec B501
@@ -187,7 +198,7 @@ async def get_homie_schema(host: str, timeout: float = 10.0) -> V2HomieSchema:
187198 )
188199
189200
190- async def regenerate_passphrase (host : str , token : str , timeout : float = 10.0 ) -> str :
201+ async def regenerate_passphrase (host : str , token : str , timeout : float = 10.0 , port : int = 80 ) -> str :
191202 """Rotate the MQTT broker password on the SPAN Panel.
192203
193204 After this call, the previous broker password is invalidated.
@@ -198,6 +209,7 @@ async def regenerate_passphrase(host: str, token: str, timeout: float = 10.0) ->
198209 host: IP address or hostname of the SPAN Panel
199210 token: Valid JWT access token
200211 timeout: Request timeout in seconds
212+ port: HTTP port of the panel bootstrap API
201213
202214 Returns:
203215 New MQTT broker password
@@ -208,7 +220,7 @@ async def regenerate_passphrase(host: str, token: str, timeout: float = 10.0) ->
208220 SpanPanelTimeoutError: Request timed out
209221 SpanPanelAPIError: Unexpected response
210222 """
211- url = f"http:// { host } / api/v2/auth/passphrase"
223+ url = _build_url ( host , port , "/ api/v2/auth/passphrase")
212224 headers = {"Authorization" : f"Bearer { token } " }
213225
214226 try :
@@ -229,12 +241,13 @@ async def regenerate_passphrase(host: str, token: str, timeout: float = 10.0) ->
229241 return _str (data ["ebusBrokerPassword" ])
230242
231243
232- async def get_v2_status (host : str , timeout : float = 5.0 ) -> V2StatusInfo :
244+ async def get_v2_status (host : str , timeout : float = 5.0 , port : int = 80 ) -> V2StatusInfo :
233245 """Lightweight v2 status probe (unauthenticated).
234246
235247 Args:
236248 host: IP address or hostname of the SPAN Panel
237249 timeout: Request timeout in seconds
250+ port: HTTP port of the panel bootstrap API
238251
239252 Returns:
240253 V2StatusInfo with serial number and firmware version
@@ -244,7 +257,7 @@ async def get_v2_status(host: str, timeout: float = 5.0) -> V2StatusInfo:
244257 SpanPanelTimeoutError: Request timed out
245258 SpanPanelAPIError: Unexpected response or non-v2 panel
246259 """
247- url = f"http:// { host } / api/v2/status"
260+ url = _build_url ( host , port , "/ api/v2/status")
248261
249262 try :
250263 async with httpx .AsyncClient (timeout = timeout , verify = False ) as client : # nosec B501
0 commit comments