88
88
color_xy_to_RGB ,
89
89
)
90
90
import homeassistant .util .dt as dt_util
91
+ import ulid_transform
91
92
import voluptuous as vol
92
93
93
94
from .const import (
182
183
}
183
184
184
185
# Keep a short domain version for the context instances (which can only be 36 chars)
185
- _DOMAIN_SHORT = "adapt_lgt "
186
+ _DOMAIN_SHORT = "al "
186
187
187
188
188
- def _int_to_bytes (i : int , signed : bool = False ) -> bytes :
189
- bits = i .bit_length ()
190
- if signed :
191
- # Make room for the sign bit.
192
- bits += 1
193
- return i .to_bytes ((bits + 7 ) // 8 , "little" , signed = signed )
189
+ def _int_to_base36 (num : int ) -> str :
190
+ """
191
+ Convert an integer to its base-36 representation using numbers and uppercase letters.
192
+
193
+ Base-36 encoding uses digits 0-9 and uppercase letters A-Z, providing a case-insensitive
194
+ alphanumeric representation. The function takes an integer `num` as input and returns
195
+ its base-36 representation as a string.
196
+
197
+ Parameters
198
+ ----------
199
+ num
200
+ The integer to convert to base-36.
201
+
202
+ Returns
203
+ -------
204
+ str
205
+ The base-36 representation of the input integer.
206
+
207
+ Examples
208
+ --------
209
+ >>> num = 123456
210
+ >>> base36_num = int_to_base36(num)
211
+ >>> print(base36_num)
212
+ '2N9'
213
+ """
214
+ ALPHANUMERIC_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
215
+
216
+ if num == 0 :
217
+ return ALPHANUMERIC_CHARS [0 ]
218
+
219
+ base36_str = ""
220
+ base = len (ALPHANUMERIC_CHARS )
221
+
222
+ while num :
223
+ num , remainder = divmod (num , base )
224
+ base36_str = ALPHANUMERIC_CHARS [remainder ] + base36_str
225
+
226
+ return base36_str
194
227
195
228
196
229
def _short_hash (string : str , length : int = 4 ) -> str :
197
230
"""Create a hash of 'string' with length 'length'."""
198
- str_hash_bytes = _int_to_bytes (hash (string ), signed = True )
199
- return base64 .b85encode (str_hash_bytes )[:length ]
231
+ return base64 .b32encode (string .encode ()).decode ("utf-8" ).zfill (length )[:length ]
232
+
233
+
234
+ def _remove_vowels (input_str : str , length : int = 4 ) -> str :
235
+ vowels = "aeiouAEIOU"
236
+ output_str = "" .join ([char for char in input_str if char not in vowels ])
237
+ return output_str .zfill (length )[:length ]
200
238
201
239
202
240
def create_context (
203
241
name : str , which : str , index : int , parent : Context | None = None
204
242
) -> Context :
205
243
"""Create a context that can identify this integration."""
206
244
# Use a hash for the name because otherwise the context might become
207
- # too long (max len == 36) to fit in the database.
208
- name_hash = _short_hash (name )
245
+ # too long (max len == 26) to fit in the database.
209
246
# Pack index with base85 to maximize the number of contexts we can create
210
- # before we exceed the 36-character limit and are forced to wrap.
211
- index_packed = base64 .b85encode (_int_to_bytes (index , signed = False ))
212
- context_id = f"{ _DOMAIN_SHORT } :{ name_hash } :{ which } :{ index_packed } " [:36 ]
247
+ # before we exceed the 26-character limit and are forced to wrap.
248
+ time_stamp = ulid_transform .ulid_now ()[:10 ] # time part of a ULID
249
+ name_hash = _short_hash (name )
250
+ which_short = _remove_vowels (which )
251
+ context_id_start = f"{ time_stamp } :{ _DOMAIN_SHORT } :{ name_hash } :{ which_short } :"
252
+ chars_left = 26 - len (context_id_start )
253
+ index_packed = _int_to_base36 (index ).zfill (chars_left )[- chars_left :]
254
+ context_id = context_id_start + index_packed
213
255
parent_id = parent .id if parent else None
214
256
return Context (id = context_id , parent_id = parent_id )
215
257
@@ -218,7 +260,7 @@ def is_our_context(context: Context | None) -> bool:
218
260
"""Check whether this integration created 'context'."""
219
261
if context is None :
220
262
return False
221
- return context .id . startswith ( _DOMAIN_SHORT )
263
+ return f": { _DOMAIN_SHORT } :" in context .id
222
264
223
265
224
266
def _split_service_data (service_data , adapt_brightness , adapt_color ):
0 commit comments