Nyx Node
Loading...
Searching...
No Matches
bind.py
1# -*- coding: utf-8 -*-
2########################################################################################################################
3# NyxNode
4# Author: Jérôme ODIER <jerome.odier@lpsc.in2p3.fr>
5# SPDX-License-Identifier: GPL-3.0+
6########################################################################################################################
7
8import os
9import sys
10import enum
11import ctypes
12import typing
13import pathlib
14
15########################################################################################################################
16# ERRORS #
17########################################################################################################################
18
19class NyxError(RuntimeError):
20 """!
21 @brief Base error raised by the Python Nyx binding.
22 """
23
25 """!
26 @brief Raised when the C shared library cannot be loaded or used.
27 """
28
29########################################################################################################################
30# ALIASES #
31########################################################################################################################
32
33c_void_p = ctypes.c_void_p
34c_char_p = ctypes.c_char_p
35c_bool = ctypes.c_bool
36c_uint = ctypes.c_uint
37c_int = ctypes.c_int
38c_ulong = ctypes.c_ulong
39c_long = ctypes.c_long
40
41c_uint8 = ctypes.c_uint8
42c_int8 = ctypes.c_int8
43c_uint32 = ctypes.c_uint32
44c_int32 = ctypes.c_int32
45c_uint64 = ctypes.c_uint64
46c_int64 = ctypes.c_int64
47c_size_t = ctypes.c_size_t
48
49c_float = ctypes.c_float
50c_double = ctypes.c_double
51
52########################################################################################################################
53# TYPES #
54########################################################################################################################
55
56class NyxObjectType(enum.IntEnum):
57 """!
58 @brief JSON object types.
59 """
60
61 NULL = 0x65656500
62 BOOLEAN = 0x65656501
63 NUMBER = 0x65656502
64 STRING = 0x65656503
65 DICT = 0x65656504
66 LIST = 0x65656505
67
68########################################################################################################################
69
70# noinspection PyPep8Naming
71class nyx_object_t(ctypes.Structure):
72 """!
73 @private
74 """
75
76 _fields_ = [
77 ('type', c_int32),
78 ('flags', c_uint64),
79 ('ref', c_int32),
80 ('node', c_void_p),
81 ('parent', c_void_p),
82 ('callback', c_void_p),
83 ('ctx', c_void_p),
84 ]
85
86########################################################################################################################
87
88nyx_object_p = ctypes.POINTER(nyx_object_t)
89
90########################################################################################################################
91
92# noinspection PyPep8Naming
93class nyx_dict_t(ctypes.Structure):
94 """!
95 @private
96 """
97
98 _fields_ = [
99 ('base', nyx_object_t),
100 ('head', c_void_p),
101 ('tail', c_void_p),
102 ]
103
104########################################################################################################################
105
106nyx_dict_p = ctypes.POINTER(nyx_dict_t)
107
108########################################################################################################################
109
110# noinspection PyPep8Naming
111class nyx_dict_iter_t(ctypes.Structure):
112 """!
113 @private
114 """
115
116 _fields_ = [
117 ('idx', c_size_t),
118 ('head', c_void_p),
119 ]
120
121########################################################################################################################
122
123nyx_dict_iter_p = ctypes.POINTER(nyx_dict_iter_t)
124
125########################################################################################################################
126
127# noinspection PyPep8Naming
128class nyx_list_t(ctypes.Structure):
129 """!
130 @private
131 """
132
133 _fields_ = [
134 ('base', nyx_object_t),
135 ('head', c_void_p),
136 ('tail', c_void_p),
137 ]
138
139########################################################################################################################
140
141nyx_list_p = ctypes.POINTER(nyx_list_t)
142
143########################################################################################################################
144
145# noinspection PyPep8Naming
146class nyx_list_iter_t(ctypes.Structure):
147 """!
148 @private
149 """
150
151 _fields_ = [
152 ('idx', c_size_t),
153 ('head', c_void_p),
154 ]
155
156########################################################################################################################
157
158nyx_list_iter_p = ctypes.POINTER(nyx_list_iter_t)
159
160########################################################################################################################
161
162nyx_callback_int_t = ctypes.CFUNCTYPE(
163 c_bool,
164 nyx_dict_p,
165 nyx_dict_p,
166 c_int,
167 c_int,
168)
169
170nyx_callback_uint_t = ctypes.CFUNCTYPE(
171 c_bool,
172 nyx_dict_p,
173 nyx_dict_p,
174 c_uint,
175 c_uint,
176)
177
178nyx_callback_long_t = ctypes.CFUNCTYPE(
179 c_bool,
180 nyx_dict_p,
181 nyx_dict_p,
182 c_long,
183 c_long,
184)
185
186nyx_callback_ulong_t = ctypes.CFUNCTYPE(
187 c_bool,
188 nyx_dict_p,
189 nyx_dict_p,
190 c_ulong,
191 c_ulong,
192)
193
194nyx_callback_double_t = ctypes.CFUNCTYPE(
195 c_bool,
196 nyx_dict_p,
197 nyx_dict_p,
198 c_double,
199 c_double,
200)
201
202nyx_callback_str_t = ctypes.CFUNCTYPE(
203 c_bool,
204 nyx_dict_p,
205 nyx_dict_p,
206 c_char_p,
207 c_char_p,
208)
209
210nyx_callback_buffer_t = ctypes.CFUNCTYPE(
211 c_bool,
212 nyx_dict_p,
213 nyx_dict_p,
214 c_size_t,
215 c_void_p,
216)
217
218nyx_callback_vector_t = ctypes.CFUNCTYPE(
219 None,
220 nyx_dict_p,
221 c_bool,
222)
223
224########################################################################################################################
225
226# noinspection PyPep8Naming
227class nyx_opts_t(ctypes.Structure):
228 """!
229 @private
230 """
231
232 _fields_ = [
233 ('group', c_char_p),
234 ('label', c_char_p),
235 ('hints', c_char_p),
236 ('message', c_char_p),
237 ('timeout', c_double),
238 ]
239
240########################################################################################################################
241
242class nyx_node_t(ctypes.Structure):
243 """!
244 @private
245 """
246
247 pass
248
249########################################################################################################################
250
251nyx_node_p = ctypes.POINTER(nyx_node_t)
252
253########################################################################################################################
254
255nyx_timer_callback_t = ctypes.CFUNCTYPE(
256 None,
257 c_void_p
258)
259
260########################################################################################################################
261
262nyx_mqtt_handler_t = ctypes.CFUNCTYPE(
263 None,
264 nyx_node_p,
265 c_int,
266 c_size_t,
267 c_void_p,
268 c_size_t,
269 c_void_p,
270)
271
272########################################################################################################################
273# HELPERS #
274########################################################################################################################
275
276# noinspection PyOverloads
277@typing.overload
278def as_bytes(value: str | bytes, *, allow_none: bool = True) -> bytes:
279 ...
280
281# noinspection PyOverloads
282@typing.overload
283def as_bytes(value: None, *, allow_none: typing.Literal[True] = True) -> None:
284 ...
285
286# noinspection PyOverloads
287@typing.overload
288def as_bytes(value: None, *, allow_none: typing.Literal[False]) -> typing.NoReturn:
289 ...
290
291# noinspection PyOverloads
292@typing.overload
293def as_bytes(value: str | bytes | None, *, allow_none: typing.Literal[False]) -> bytes:
294 ...
295
296# noinspection PyOverloads
297@typing.overload
298def as_bytes(value: str | bytes | None, *, allow_none: typing.Literal[True] = True) -> bytes | None:
299 ...
300
301########################################################################################################################
302
303def as_bytes(value: str | bytes | None, *, allow_none: bool) -> bytes | None:
304
305 ####################################################################################################################
306
307 if value is None:
308
309 if not allow_none:
310
311 raise TypeError('None is not allowed here')
312
313 return None
314
315 ####################################################################################################################
316
317 if isinstance(value, str):
318
319 return value.encode('utf-8')
320
321 if isinstance(value, bytes):
322
323 return value#.encode('utf-8')
324
325 ####################################################################################################################
326
327 raise TypeError(f'expected str or bytes, got {type(value).__name__}')
328
329########################################################################################################################
330
331def as_opts(opts: dict[str, typing.Any] | None) -> nyx_opts_t | None:
332
333 if opts is None:
334
335 return None
336
337 return nyx_opts_t(
338 as_bytes(opts.get('group'), allow_none = True),
339 as_bytes(opts.get('label'), allow_none = True),
340 as_bytes(opts.get('hints'), allow_none = True),
341 as_bytes(opts.get('message'), allow_none = True),
342 float(opts.get('timeout', 0.0)),
343 )
344
345########################################################################################################################
346
347def check_ptr(ptr: int | c_void_p | None, what: str = 'C object') -> c_void_p:
348
349 ####################################################################################################################
350
351 if isinstance(ptr, c_void_p):
352
353 if ptr.value:
354
355 return ptr
356
357 elif ptr:
358
359 # noinspection PyTypeChecker
360 return c_void_p(ptr)
361
362 ####################################################################################################################
363
364 raise TypeError(f'{what} is NULL')
365
366########################################################################################################################
367
368def take_bytes(ptr: int | c_void_p | None, size: int) -> bytes:
369
370 ptr = check_ptr(ptr, 'C buffer')
371
372 try:
373 return ctypes.string_at(ptr, size)
374 finally:
375 lib.nyx_memory_free(ptr)
376
377########################################################################################################################
378
379def take_string(ptr: int | c_void_p | None, size: int | None = None) -> str:
380
381 ptr = check_ptr(ptr, 'C string')
382
383 try:
384 if size is None:
385 return ctypes.string_at(ptr).decode('utf-8')
386 else:
387 return ctypes.string_at(ptr, size).decode('utf-8')
388 finally:
389 lib.nyx_memory_free(ptr)
390
391########################################################################################################################
392# LOAD LIBRARY #
393########################################################################################################################
394
395def _load_library() -> ctypes.CDLL:
396
397 ####################################################################################################################
398
399 if os.name == 'nt':
400 names = ('libnyx-node.dll', 'nyx-node.dll')
401 directories = ()
402 elif sys.platform == 'darwin':
403 names = ('libnyx-node.dylib', 'nyx-node.dylib')
404 directories = (
405 pathlib.Path('/usr/local/lib'),
406 pathlib.Path('/usr/lib'),
407 pathlib.Path('/lib'),
408 )
409 else:
410 names = ('libnyx-node.so', 'nyx-node.so')
411 directories = (
412 pathlib.Path('/usr/local/lib'),
413 pathlib.Path('/usr/lib'),
414 pathlib.Path('/lib'),
415 )
416
417 ####################################################################################################################
418
419 candidates = (
420 *(str(directory / name) for directory in directories for name in names),
421 *names,
422 )
423
424 ####################################################################################################################
425
426 errors: list[str] = []
427
428 for candidate in dict.fromkeys(filter(None, candidates)):
429
430 if not os.path.dirname(candidate) or pathlib.Path(candidate).exists():
431
432 try:
433
434 return ctypes.CDLL(candidate)
435
436 except OSError as exc:
437
438 errors.append(f'{candidate}: {exc}')
439
440 ####################################################################################################################
441
442 message = 'Unable to load libnyx-node. Install it in a system library directory.'
443
444 details = '\n'.join(errors)
445
446 if details:
447
448 message += f'\nTried:\n{details}'
449
450 ####################################################################################################################
451
452 raise NyxLibraryError(message)
453
454########################################################################################################################
455
456lib = _load_library()
457
458########################################################################################################################
459# BINDINGS #
460########################################################################################################################
461
462def _bind(name: str, restype, argtypes: typing.Sequence[object]) -> None:
463
464 ####################################################################################################################
465
466 argtypes = list(argtypes)
467
468 ####################################################################################################################
469
470 try:
471
472 func = getattr(lib, name)
473
474 func.argtypes = argtypes
475 func.restype = restype
476
477 except AttributeError as e:
478
479 raise NyxLibraryError(f'Missing C symbol: {name}') from e
480
481########################################################################################################################
482
483## LOG ##
484
485_bind('nyx_set_log_level', None, [c_int])
486
487########################################################################################################################
488
489## MEMORY ##
490
491_bind('nyx_memory_initialize', None, [])
492_bind('nyx_memory_finalize', c_bool, [])
493
494_bind('nyx_memory_free', c_size_t, [c_void_p])
495
496_bind('nyx_string_ndup', c_void_p, [c_void_p, c_size_t])
497_bind('nyx_buffer_ndup', c_void_p, [c_void_p, c_size_t])
498
499########################################################################################################################
500
501## UTILS ##
502
503_bind('nyx_hash', ctypes.c_uint32, [c_size_t, c_void_p, c_uint32])
504
505_bind('nyx_generate_mac_addr', None, [c_uint8 * 6, c_uint8, c_uint8, c_char_p])
506
507_bind('nyx_base64_encode', c_void_p, [ctypes.POINTER(c_size_t), c_size_t, c_void_p])
508_bind('nyx_base64_decode', c_void_p, [ctypes.POINTER(c_size_t), c_size_t, c_void_p])
509
510########################################################################################################################
511
512## OBJECT ##
513
514_bind('nyx_object_parse', c_void_p, [c_char_p])
515_bind('nyx_object_ref', c_void_p, [c_void_p])
516_bind('nyx_object_unref', c_void_p, [c_void_p])
517_bind('nyx_object_get_type', c_int32, [c_void_p])
518_bind('nyx_object_notify', c_bool, [c_void_p])
519_bind('nyx_object_to_string', c_void_p, [c_void_p])
520_bind('nyx_object_to_cstring', c_void_p, [c_void_p])
521
522########################################################################################################################
523
524## XMLDOC ##
525
526_bind('nyx_xmldoc_parse', c_void_p, [c_char_p])
527_bind('nyx_xmldoc_free', None, [c_void_p])
528_bind('nyx_xmldoc_to_string', c_void_p, [c_void_p])
529
530########################################################################################################################
531
532## JSON ##
533
534_bind('nyx_null_new', c_void_p, [])
535
536_bind('nyx_boolean_new', c_void_p, [])
537_bind('nyx_boolean_get', c_bool, [c_void_p])
538_bind('nyx_boolean_set', c_bool, [c_void_p, c_bool])
539
540_bind('nyx_string_new', c_void_p, [])
541_bind('nyx_string_get', c_char_p, [c_void_p])
542_bind('nyx_string_set', c_bool, [c_void_p, c_char_p, c_bool])
543
544_bind('nyx_number_new', c_void_p, [])
545_bind('nyx_number_get', c_double, [c_void_p])
546_bind('nyx_number_set', c_bool, [c_void_p, c_double])
547
548_bind('nyx_dict_new', c_void_p, [])
549_bind('nyx_dict_clear', None, [c_void_p])
550_bind('nyx_dict_del', None, [c_void_p, c_char_p])
551_bind('nyx_dict_iterate', c_bool, [nyx_dict_iter_p, ctypes.POINTER(c_char_p), ctypes.POINTER(c_void_p)])
552_bind('nyx_dict_get', c_void_p, [c_void_p, c_char_p])
553_bind('nyx_dict_set', c_bool, [c_void_p, c_char_p, c_void_p])
554_bind('nyx_dict_size', c_size_t, [c_void_p])
555
556_bind('nyx_list_new', c_void_p, [])
557_bind('nyx_list_clear', None, [c_void_p])
558_bind('nyx_list_del', None, [c_void_p, c_size_t])
559_bind('nyx_list_iterate', c_bool, [nyx_list_iter_p, ctypes.POINTER(c_size_t), ctypes.POINTER(c_void_p)])
560_bind('nyx_list_get', c_void_p, [c_void_p, c_size_t])
561_bind('nyx_list_set', c_bool, [c_void_p, c_size_t, c_void_p])
562_bind('nyx_list_size', c_size_t, [c_void_p])
563
564########################################################################################################################
565
566## NYX MESSAGE ##
567
568_bind('nyx_message_new', c_void_p, [c_char_p, c_char_p])
569
570## NYX DEL PROPERTY ##
571
572_bind('nyx_del_property_new', c_void_p, [c_char_p, c_char_p, c_char_p])
573
574## NYX NUMBER ##
575
576_bind('nyx_number_prop_new_int', c_void_p, [c_char_p, c_char_p, c_char_p, c_int32, c_int32, c_int32, c_int32])
577_bind('nyx_number_prop_new_uint', c_void_p, [c_char_p, c_char_p, c_char_p, c_uint32, c_uint32, c_uint32, c_uint32])
578_bind('nyx_number_prop_new_long', c_void_p, [c_char_p, c_char_p, c_char_p, c_int64, c_int64, c_int64, c_int64])
579_bind('nyx_number_prop_new_ulong', c_void_p, [c_char_p, c_char_p, c_char_p, c_uint64, c_uint64, c_uint64, c_uint64])
580_bind('nyx_number_prop_new_double', c_void_p, [c_char_p, c_char_p, c_char_p, c_double, c_double, c_double, c_double])
581
582_bind('nyx_number_prop_set_int', c_bool, [c_void_p, c_int32])
583_bind('nyx_number_prop_set_uint', c_bool, [c_void_p, c_uint32])
584_bind('nyx_number_prop_set_long', c_bool, [c_void_p, c_int64])
585_bind('nyx_number_prop_set_ulong', c_bool, [c_void_p, c_uint64])
586_bind('nyx_number_prop_set_double', c_bool, [c_void_p, c_double])
587
588_bind('nyx_number_prop_get_int', c_int32, [c_void_p])
589_bind('nyx_number_prop_get_uint', c_uint32, [c_void_p])
590_bind('nyx_number_prop_get_long', c_int64, [c_void_p])
591_bind('nyx_number_prop_get_ulong', c_uint64, [c_void_p])
592_bind('nyx_number_prop_get_double', c_double, [c_void_p])
593
594_bind('nyx_number_vector_new', c_void_p, [c_char_p, c_char_p, c_int, c_int, ctypes.POINTER(nyx_dict_p), ctypes.POINTER(nyx_opts_t)])
595
596## NYX TEXT ##
597
598_bind("nyx_text_prop_new", c_void_p, [c_char_p, c_char_p, c_char_p, c_bool])
599_bind("nyx_text_vector_new", c_void_p, [c_char_p, c_char_p, c_int, c_int, ctypes.POINTER(nyx_dict_p), ctypes.POINTER(nyx_opts_t)])
600
601## NYX LIGHT ##
602
603_bind("nyx_light_prop_new", c_void_p, [c_char_p, c_char_p, c_int])
604_bind("nyx_light_vector_new", c_void_p, [c_char_p, c_char_p, c_int, ctypes.POINTER(nyx_dict_p), ctypes.POINTER(nyx_opts_t)])
605
606## NYX TEXT ##
607
608_bind("nyx_switch_prop_new", c_void_p, [c_char_p, c_char_p, c_int])
609_bind("nyx_switch_vector_new", c_void_p, [c_char_p, c_char_p, c_int, c_int, c_int, ctypes.POINTER(nyx_dict_p), ctypes.POINTER(nyx_opts_t)])
610
611## NYX BLOB ##
612
613_bind("nyx_blob_prop_new", c_void_p, [c_char_p, c_char_p, c_char_p, c_size_t, c_void_p, c_bool])
614_bind("nyx_blob_vector_new", c_void_p, [c_char_p, c_char_p, c_int, c_int, ctypes.POINTER(nyx_dict_p), ctypes.POINTER(nyx_opts_t)])
615
616## NYX STREAM ##
617
618_bind("nyx_stream_prop_new", c_void_p, [c_char_p, c_char_p])
619_bind("nyx_stream_vector_new", c_void_p, [c_char_p, c_char_p, c_int, ctypes.POINTER(nyx_dict_p), ctypes.POINTER(nyx_opts_t)])
620
621_bind('nyx_stream_pub', c_bool, [c_void_p, c_size_t, ctypes.POINTER(c_size_t), ctypes.POINTER(c_void_p)])
622
623########################################################################################################################
624
625## NYX NODE ##
626
627_bind('nyx_node_initialize', nyx_node_p, [c_char_p, ctypes.POINTER(nyx_dict_p), c_char_p, c_char_p, c_char_p, c_char_p, c_char_p, nyx_mqtt_handler_t, c_uint32, c_bool])
628_bind('nyx_node_finalize', None, [nyx_node_p, c_bool])
629
630_bind('nyx_node_add_timer', None, [nyx_node_p, c_uint32, nyx_timer_callback_t, c_void_p])
631
632_bind('nyx_node_poll', None, [nyx_node_p, c_uint32])
633
634_bind('nyx_node_enable', None, [nyx_node_p, c_char_p, c_char_p, c_char_p])
635_bind('nyx_node_disable', None, [nyx_node_p, c_char_p, c_char_p, c_char_p])
636
637_bind('nyx_node_send_message', None, [nyx_node_p, c_char_p, c_char_p])
638_bind('nyx_node_send_del_property', None, [nyx_node_p, c_char_p, c_char_p, c_char_p])
639
640_bind('nyx_mqtt_sub', None, [c_void_p, c_char_p, c_int])
641_bind('nyx_mqtt_pub', None, [c_void_p, c_char_p, c_size_t, c_void_p, c_int])
642
643_bind('nyx_nss_pub', None, [c_void_p, c_char_p, c_char_p, c_size_t, ctypes.POINTER(c_uint32), ctypes.POINTER(c_size_t), ctypes.POINTER(c_void_p)])
644
645########################################################################################################################
ERRORS #.
Definition bind.py:19
Raised when the C shared library cannot be loaded or used.
Definition bind.py:24
Struct describing the options for INDI / Nyx vectors.
Definition nyx_node.h:2049