Migrate to Python 3
[collecty.git] / src / _collectymodule.c
1 /*
2  * collecty
3  * Copyright (C) 2015 IPFire Team (www.ipfire.org)
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <Python.h>
20
21 #include <atasmart.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <linux/hdreg.h>
25 #include <sensors/error.h>
26 #include <sensors/sensors.h>
27 #include <stdbool.h>
28 #include <string.h>
29 #include <sys/ioctl.h>
30
31 #define MODEL_SIZE  40
32 #define SERIAL_SIZE 20
33
34 typedef struct {
35         PyObject_HEAD
36         char* path;
37         struct hd_driveid identity;
38         SkDisk* disk;
39 } BlockDevice;
40
41 static void BlockDevice_dealloc(BlockDevice* self) {
42         if (self->disk)
43                 sk_disk_free(self->disk);
44
45         if (self->path)
46                 free(self->path);
47
48         Py_TYPE(self)->tp_free((PyObject*)self);
49 }
50
51 static int BlockDevice_get_identity(BlockDevice* device) {
52         int fd;
53
54         if ((fd = open(device->path, O_RDONLY | O_NONBLOCK)) < 0) {
55                 return 1;
56         }
57
58         int r = ioctl(fd, HDIO_GET_IDENTITY, &device->identity);
59         close(fd);
60
61         if (r)
62                 return 1;
63
64         return 0;
65 }
66
67 static int BlockDevice_smart_is_available(BlockDevice* device) {
68         SkBool available = FALSE;
69
70         int r = sk_disk_smart_is_available(device->disk, &available);
71         if (r)
72                 return -1;
73
74         if (available)
75                 return 0;
76
77         return 1;
78 }
79
80 static int BlockDevice_check_sleep_mode(BlockDevice* device) {
81         SkBool awake = FALSE;
82
83         int r = sk_disk_check_sleep_mode(device->disk, &awake);
84         if (r)
85                 return -1;
86
87         if (awake)
88                 return 0;
89
90         return 1;
91 }
92
93 static PyObject * BlockDevice_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
94         BlockDevice* self = (BlockDevice*)type->tp_alloc(type, 0);
95
96         if (self) {
97                 self->path = NULL;
98
99                 // libatasmart
100                 self->disk = NULL;
101         }
102
103         return (PyObject *)self;
104 }
105
106 static int BlockDevice_init(BlockDevice* self, PyObject* args, PyObject* kwds) {
107         const char* path = NULL;
108
109         if (!PyArg_ParseTuple(args, "s", &path))
110                 return -1;
111
112         self->path = strdup(path);
113
114         int r = BlockDevice_get_identity(self);
115         if (r) {
116                 PyErr_Format(PyExc_OSError, "Could not open block device: %s", path);
117                 return -1;
118         }
119
120         r = sk_disk_open(path, &self->disk);
121         if (r == 0) {
122                 if (BlockDevice_smart_is_available(self) == 0) {
123                         if (BlockDevice_check_sleep_mode(self) == 0) {
124                                 r = sk_disk_smart_read_data(self->disk);
125                                 if (r) {
126                                         PyErr_Format(PyExc_OSError, "Could not open block device %s: %s", path,
127                                                 strerror(errno));
128                                         return -1;
129                                 }
130                         }
131                 }
132         } else {
133                 PyErr_Format(PyExc_OSError, "Could not open block device %s: %s", path,
134                         strerror(errno));
135                 return -1;
136         }
137
138         //sk_disk_identify_is_available
139
140         return 0;
141 }
142
143 static PyObject* BlockDevice_get_path(PyObject* self) {
144         BlockDevice* device = (BlockDevice*)self;
145
146         return PyUnicode_FromString(device->path);
147 }
148
149 static void clean_string(char *s) {
150         for (char* e = s; *e; e++) {
151                 if (*e < ' ' || *e >= 127)
152                         *e = ' ';
153         }
154 }
155
156 static void drop_spaces(char *s) {
157         char *d = s;
158         bool prev_space = false;
159
160         s += strspn(s, " ");
161
162         for (; *s; s++) {
163                 if (prev_space) {
164                         if (*s != ' ') {
165                                 prev_space = false;
166                                 *(d++) = ' ';
167                                 *(d++) = *s;
168                         }
169                 } else {
170                         if (*s == ' ')
171                                 prev_space = true;
172                         else
173                                 *(d++) = *s;
174                 }
175         }
176
177         *d = 0;
178 }
179
180 static void copy_string(char* d, const char* s, size_t n) {
181         // Copy the source buffer to the destination buffer up to n
182         memcpy(d, s, n);
183
184         // Terminate the destination buffer with NULL
185         d[n] = '\0';
186
187         // Clean up the string from non-printable characters
188         clean_string(d);
189         drop_spaces(d);
190 }
191
192 static PyObject* BlockDevice_get_model(PyObject* self) {
193         BlockDevice* device = (BlockDevice*)self;
194
195         char model[MODEL_SIZE + 1];
196         copy_string(model, device->identity.model, sizeof(model));
197
198         return PyUnicode_FromString(model);
199 }
200
201 static PyObject* BlockDevice_get_serial(PyObject* self) {
202         BlockDevice* device = (BlockDevice*)self;
203
204         char serial[SERIAL_SIZE + 1];
205         copy_string(serial, device->identity.serial_no, sizeof(serial));
206
207         return PyUnicode_FromString(serial);
208 }
209
210 static PyObject* BlockDevice_is_smart_supported(PyObject* self) {
211         BlockDevice* device = (BlockDevice*)self;
212
213         if (BlockDevice_smart_is_available(device) == 0)
214                 Py_RETURN_TRUE;
215
216         Py_RETURN_FALSE;
217 }
218
219 static PyObject* BlockDevice_is_awake(PyObject* self) {
220         BlockDevice* device = (BlockDevice*)self;
221
222         if (BlockDevice_check_sleep_mode(device) == 0)
223                 Py_RETURN_TRUE;
224
225         Py_RETURN_FALSE;
226 }
227
228 static PyObject* BlockDevice_get_bad_sectors(PyObject* self) {
229         BlockDevice* device = (BlockDevice*)self;
230
231         if (BlockDevice_smart_is_available(device)) {
232                 PyErr_Format(PyExc_OSError, "Device does not support SMART");
233                 return NULL;
234         }
235
236         uint64_t bad_sectors;
237         int r = sk_disk_smart_get_bad(device->disk, &bad_sectors);
238         if (r)
239                 return NULL;
240
241         return PyLong_FromUnsignedLongLong((unsigned long long)bad_sectors);
242 }
243
244 static PyObject* BlockDevice_get_temperature(PyObject* self) {
245         BlockDevice* device = (BlockDevice*)self;
246
247         if (BlockDevice_smart_is_available(device)) {
248                 PyErr_Format(PyExc_OSError, "Device does not support SMART");
249                 return NULL;
250         }
251
252         uint64_t mkelvin;
253         int r = sk_disk_smart_get_temperature(device->disk, &mkelvin);
254         if (r)
255                 return NULL;
256
257         // Convert the temperature to Kelvin
258         return PyFloat_FromDouble((double)mkelvin / 1000.0);
259 }
260
261 static PyGetSetDef BlockDevice_getsetters[] = {
262         {"path", (getter)BlockDevice_get_path, NULL, NULL, NULL},
263         {"model", (getter)BlockDevice_get_model, NULL, NULL, NULL},
264         {"serial", (getter)BlockDevice_get_serial, NULL, NULL, NULL},
265 };
266
267 static PyMethodDef BlockDevice_methods[] = {
268         {"get_bad_sectors", (PyCFunction)BlockDevice_get_bad_sectors, METH_NOARGS, NULL},
269         {"get_temperature", (PyCFunction)BlockDevice_get_temperature, METH_NOARGS, NULL},
270         {"is_smart_supported", (PyCFunction)BlockDevice_is_smart_supported, METH_NOARGS, NULL},
271         {"is_awake", (PyCFunction)BlockDevice_is_awake, METH_NOARGS, NULL},
272         {NULL}
273 };
274
275 static PyTypeObject BlockDeviceType = {
276         PyVarObject_HEAD_INIT(NULL, 0)
277         "_collecty.BlockDevice",            /*tp_name*/
278         sizeof(BlockDevice),                /*tp_basicsize*/
279         0,                                  /*tp_itemsize*/
280         (destructor)BlockDevice_dealloc,    /*tp_dealloc*/
281         0,                                  /*tp_print*/
282         0,                                  /*tp_getattr*/
283         0,                                  /*tp_setattr*/
284         0,                                  /*tp_compare*/
285         0,                                  /*tp_repr*/
286         0,                                  /*tp_as_number*/
287         0,                                  /*tp_as_sequence*/
288         0,                                  /*tp_as_mapping*/
289         0,                                  /*tp_hash */
290         0,                                  /*tp_call*/
291         0,                                  /*tp_str*/
292         0,                                  /*tp_getattro*/
293         0,                                  /*tp_setattro*/
294         0,                                  /*tp_as_buffer*/
295         Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
296         "BlockDevice objects",              /* tp_doc */
297         0,                                          /* tp_traverse */
298         0,                                          /* tp_clear */
299         0,                                          /* tp_richcompare */
300         0,                                          /* tp_weaklistoffset */
301         0,                                          /* tp_iter */
302         0,                                          /* tp_iternext */
303         BlockDevice_methods,                /* tp_methods */
304         0,                                  /* tp_members */
305         BlockDevice_getsetters,             /* tp_getset */
306         0,                                  /* tp_base */
307         0,                                  /* tp_dict */
308         0,                                  /* tp_descr_get */
309         0,                                  /* tp_descr_set */
310         0,                                  /* tp_dictoffset */
311         (initproc)BlockDevice_init,         /* tp_init */
312         0,                                  /* tp_alloc */
313         BlockDevice_new,                    /* tp_new */
314 };
315
316 typedef struct {
317         PyObject_HEAD
318         const sensors_chip_name* chip;
319         const sensors_feature* feature;
320 } SensorObject;
321
322 static void Sensor_dealloc(SensorObject* self) {
323         Py_TYPE(self)->tp_free((PyObject*)self);
324 }
325
326 static PyObject* Sensor_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
327         SensorObject* self = (SensorObject*)type->tp_alloc(type, 0);
328
329         return (PyObject *)self;
330 }
331
332 static int Sensor_init(SensorObject* self, PyObject* args, PyObject* kwds) {
333         return 0;
334 }
335
336 static PyObject* Sensor_get_label(SensorObject* self) {
337         char* label = sensors_get_label(self->chip, self->feature);
338
339         if (label) {
340                 PyObject* string = PyUnicode_FromString(label);
341                 free(label);
342
343                 return string;
344         }
345
346         Py_RETURN_NONE;
347 }
348
349 static PyObject* Sensor_get_name(SensorObject* self) {
350         char chip_name[512];
351
352         int r = sensors_snprintf_chip_name(chip_name, sizeof(chip_name), self->chip);
353         if (r < 0) {
354                 PyErr_Format(PyExc_RuntimeError, "Could not print chip name");
355                 return NULL;
356         }
357
358         return PyUnicode_FromString(chip_name);
359 }
360
361 static PyObject* Sensor_get_type(SensorObject* self) {
362         const char* type = NULL;
363
364         switch (self->feature->type) {
365                 case SENSORS_FEATURE_IN:
366                         type = "voltage";
367                         break;
368
369                 case SENSORS_FEATURE_FAN:
370                         type = "fan";
371                         break;
372
373                 case SENSORS_FEATURE_TEMP:
374                         type = "temperature";
375                         break;
376
377                 case SENSORS_FEATURE_POWER:
378                         type = "power";
379                         break;
380
381                 default:
382                         break;
383         }
384
385         if (type)
386                 return PyUnicode_FromString(type);
387
388         Py_RETURN_NONE;
389 }
390
391 static PyObject* Sensor_get_bus(SensorObject* self) {
392         const char* type = NULL;
393
394         switch (self->chip->bus.type) {
395                 case SENSORS_BUS_TYPE_I2C:
396                         type = "i2c";
397                         break;
398
399                 case SENSORS_BUS_TYPE_ISA:
400                         type = "isa";
401                         break;
402
403                 case SENSORS_BUS_TYPE_PCI:
404                         type = "pci";
405                         break;
406
407                 case SENSORS_BUS_TYPE_SPI:
408                         type = "spi";
409                         break;
410
411                 case SENSORS_BUS_TYPE_VIRTUAL:
412                         type = "virtual";
413                         break;
414
415                 case SENSORS_BUS_TYPE_ACPI:
416                         type = "acpi";
417                         break;
418
419                 case SENSORS_BUS_TYPE_HID:
420                         type = "hid";
421                         break;
422
423                 default:
424                         break;
425         }
426
427         if (type)
428                 return PyUnicode_FromString(type);
429
430         Py_RETURN_NONE;
431 }
432
433 static const sensors_subfeature* Sensor_get_subfeature(SensorObject* sensor, sensors_subfeature_type type) {
434         const sensors_subfeature* subfeature;
435         int subfeature_num = 0;
436
437         while ((subfeature = sensors_get_all_subfeatures(sensor->chip, sensor->feature, &subfeature_num))) {
438                 if (subfeature->type == type)
439                         break;
440         }
441
442         return subfeature;
443 }
444
445 static PyObject* Sensor_return_value(SensorObject* sensor, sensors_subfeature_type subfeature_type) {
446         double value;
447
448         const sensors_subfeature* subfeature = Sensor_get_subfeature(sensor, subfeature_type);
449         if (!subfeature) {
450                 PyErr_Format(PyExc_AttributeError, "Could not find sensor of requested type");
451                 return NULL;
452         }
453
454         // Fetch value from the sensor
455         int r = sensors_get_value(sensor->chip, subfeature->number, &value);
456         if (r < 0) {
457                 PyErr_Format(PyExc_ValueError, "Error retrieving value from sensor: %s",
458                         sensors_strerror(errno));
459                 return NULL;
460         }
461
462         // Convert all temperature values from Celcius to Kelvon
463         if (sensor->feature->type == SENSORS_FEATURE_TEMP)
464                 value += 273.15;
465
466         return PyFloat_FromDouble(value);
467 }
468
469 static PyObject* Sensor_no_value() {
470         PyErr_Format(PyExc_ValueError, "Value not supported for this sensor type");
471         return NULL;
472 }
473
474 static PyObject* Sensor_get_value(SensorObject* self) {
475         sensors_subfeature_type subfeature_type;
476
477         switch (self->feature->type) {
478                 case SENSORS_FEATURE_IN:
479                         subfeature_type = SENSORS_SUBFEATURE_IN_INPUT;
480                         break;
481
482                 case SENSORS_FEATURE_FAN:
483                         subfeature_type = SENSORS_SUBFEATURE_FAN_INPUT;
484                         break;
485
486                 case SENSORS_FEATURE_TEMP:
487                         subfeature_type = SENSORS_SUBFEATURE_TEMP_INPUT;
488                         break;
489
490                 case SENSORS_FEATURE_POWER:
491                         subfeature_type = SENSORS_SUBFEATURE_POWER_INPUT;
492                         break;
493
494                 default:
495                         return Sensor_no_value();
496         }
497
498         return Sensor_return_value(self, subfeature_type);
499 }
500
501 static PyObject* Sensor_get_critical(SensorObject* self) {
502         sensors_subfeature_type subfeature_type;
503
504         switch (self->feature->type) {
505                 case SENSORS_FEATURE_IN:
506                         subfeature_type = SENSORS_SUBFEATURE_IN_CRIT;
507                         break;
508
509                 case SENSORS_FEATURE_TEMP:
510                         subfeature_type = SENSORS_SUBFEATURE_TEMP_CRIT;
511                         break;
512
513                 case SENSORS_FEATURE_POWER:
514                         subfeature_type = SENSORS_SUBFEATURE_POWER_CRIT;
515                         break;
516
517                 default:
518                         return Sensor_no_value();
519         }
520
521         return Sensor_return_value(self, subfeature_type);
522 }
523
524 static PyObject* Sensor_get_maximum(SensorObject* self) {
525         sensors_subfeature_type subfeature_type;
526
527         switch (self->feature->type) {
528                 case SENSORS_FEATURE_IN:
529                         subfeature_type = SENSORS_SUBFEATURE_IN_MAX;
530                         break;
531
532                 case SENSORS_FEATURE_FAN:
533                         subfeature_type = SENSORS_SUBFEATURE_FAN_MAX;
534                         break;
535
536                 case SENSORS_FEATURE_TEMP:
537                         subfeature_type = SENSORS_SUBFEATURE_TEMP_MAX;
538                         break;
539
540                 case SENSORS_FEATURE_POWER:
541                         subfeature_type = SENSORS_SUBFEATURE_POWER_MAX;
542                         break;
543
544                 default:
545                         return Sensor_no_value();
546         }
547
548         return Sensor_return_value(self, subfeature_type);
549 }
550
551 static PyObject* Sensor_get_minimum(SensorObject* self) {
552         sensors_subfeature_type subfeature_type;
553
554         switch (self->feature->type) {
555                 case SENSORS_FEATURE_IN:
556                         subfeature_type = SENSORS_SUBFEATURE_IN_MIN;
557                         break;
558
559                 case SENSORS_FEATURE_FAN:
560                         subfeature_type = SENSORS_SUBFEATURE_FAN_MIN;
561                         break;
562
563                 case SENSORS_FEATURE_TEMP:
564                         subfeature_type = SENSORS_SUBFEATURE_TEMP_MIN;
565                         break;
566
567                 default:
568                         return Sensor_no_value();
569         }
570
571         return Sensor_return_value(self, subfeature_type);
572 }
573
574 static PyObject* Sensor_get_high(SensorObject* self) {
575         sensors_subfeature_type subfeature_type;
576
577         switch (self->feature->type) {
578                 case SENSORS_FEATURE_TEMP:
579                         subfeature_type = SENSORS_SUBFEATURE_TEMP_MAX;
580                         break;
581
582                 default:
583                         return Sensor_no_value();
584         }
585
586         return Sensor_return_value(self, subfeature_type);
587 }
588
589 static PyGetSetDef Sensor_getsetters[] = {
590         {"bus", (getter)Sensor_get_bus, NULL, NULL, NULL},
591         {"critical", (getter)Sensor_get_critical, NULL, NULL, NULL},
592         {"high", (getter)Sensor_get_high, NULL, NULL, NULL},
593         {"label", (getter)Sensor_get_label, NULL, NULL, NULL},
594         {"maximum", (getter)Sensor_get_maximum, NULL, NULL, NULL},
595         {"minumum", (getter)Sensor_get_minimum, NULL, NULL, NULL},
596         {"name", (getter)Sensor_get_name, NULL, NULL, NULL},
597         {"type", (getter)Sensor_get_type, NULL, NULL, NULL},
598         {"value", (getter)Sensor_get_value, NULL, NULL, NULL},
599         {NULL},
600 };
601
602 static PyTypeObject SensorType = {
603         PyObject_HEAD_INIT(NULL)
604         "_collecty.Sensor",                 /*tp_name*/
605         sizeof(SensorObject),               /*tp_basicsize*/
606         0,                                  /*tp_itemsize*/
607         (destructor)Sensor_dealloc,         /*tp_dealloc*/
608         0,                                  /*tp_print*/
609         0,                                  /*tp_getattr*/
610         0,                                  /*tp_setattr*/
611         0,                                  /*tp_compare*/
612         0,                                  /*tp_repr*/
613         0,                                  /*tp_as_number*/
614         0,                                  /*tp_as_sequence*/
615         0,                                  /*tp_as_mapping*/
616         0,                                  /*tp_hash */
617         0,                                  /*tp_call*/
618         0,                                  /*tp_str*/
619         0,                                  /*tp_getattro*/
620         0,                                  /*tp_setattro*/
621         0,                                  /*tp_as_buffer*/
622         Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
623         "Sensor objects",                   /* tp_doc */
624         0,                                  /* tp_traverse */
625         0,                                  /* tp_clear */
626         0,                                  /* tp_richcompare */
627         0,                                  /* tp_weaklistoffset */
628         0,                                  /* tp_iter */
629         0,                                  /* tp_iternext */
630         0,                                  /* tp_methods */
631         0,                                  /* tp_members */
632         Sensor_getsetters,                  /* tp_getset */
633         0,                                  /* tp_base */
634         0,                                  /* tp_dict */
635         0,                                  /* tp_descr_get */
636         0,                                  /* tp_descr_set */
637         0,                                  /* tp_dictoffset */
638         (initproc)Sensor_init,              /* tp_init */
639         0,                                  /* tp_alloc */
640         Sensor_new,                         /* tp_new */
641 };
642
643 static SensorObject* make_sensor_object(const sensors_chip_name* chip, const sensors_feature* feature) {
644         SensorObject* sensor = PyObject_New(SensorObject, &SensorType);
645         if (!sensor)
646                 return NULL;
647
648         if (!PyObject_Init((PyObject*)sensor, &SensorType)) {
649                 Py_DECREF(sensor);
650                 return NULL;
651         }
652
653         sensor->chip = chip;
654         sensor->feature = feature;
655
656         return sensor;
657 }
658
659 static PyObject* _collecty_sensors_init() {
660         // Clean up everything first in case sensors_init was called earlier
661         sensors_cleanup();
662
663         int r = sensors_init(NULL);
664         if (r) {
665                 PyErr_Format(PyExc_OSError, "Could not initialise sensors: %s",
666                         sensors_strerror(errno));
667                 return NULL;
668         }
669
670         Py_RETURN_NONE;
671 }
672
673 static PyObject* _collecty_sensors_cleanup() {
674         sensors_cleanup();
675         Py_RETURN_NONE;
676 }
677
678 static PyObject* _collecty_get_detected_sensors(PyObject* o, PyObject* args) {
679         const char* name = NULL;
680         sensors_chip_name chip_name;
681
682         if (!PyArg_ParseTuple(args, "|z", &name))
683                 return NULL;
684
685         if (name) {
686                 int r = sensors_parse_chip_name(name, &chip_name);
687                 if (r < 0) {
688                         PyErr_Format(PyExc_ValueError, "Could not parse chip name: %s", name);
689                         return NULL;
690                 }
691         }
692
693         PyObject* list = PyList_New(0);
694
695         const sensors_chip_name* chip;
696         int chip_num = 0;
697
698         while ((chip = sensors_get_detected_chips((name) ? &chip_name : NULL, &chip_num))) {
699                 const sensors_feature* feature;
700                 int feature_num = 0;
701
702                 while ((feature = sensors_get_features(chip, &feature_num))) {
703                         // Skip sensors we do not want to support
704                         switch (feature->type) {
705                                 case SENSORS_FEATURE_IN:
706                                 case SENSORS_FEATURE_FAN:
707                                 case SENSORS_FEATURE_TEMP:
708                                 case SENSORS_FEATURE_POWER:
709                                         break;
710
711                                 default:
712                                         continue;
713                         }
714
715                         SensorObject* sensor = make_sensor_object(chip, feature);
716                         PyList_Append(list, (PyObject*)sensor);
717                 }
718         }
719
720         return list;
721 }
722
723 static PyMethodDef collecty_module_methods[] = {
724         {"get_detected_sensors", (PyCFunction)_collecty_get_detected_sensors, METH_VARARGS, NULL},
725         {"sensors_cleanup", (PyCFunction)_collecty_sensors_cleanup, METH_NOARGS, NULL},
726         {"sensors_init", (PyCFunction)_collecty_sensors_init, METH_NOARGS, NULL},
727         {NULL},
728 };
729
730 static struct PyModuleDef collecty_module = {
731         PyModuleDef_HEAD_INIT,
732         "_collecty",                        /* m_name */
733         "_collecty module",                 /* m_doc */
734         -1,                                 /* m_size */
735         collecty_module_methods,            /* m_methods */
736         NULL,                               /* m_reload */
737         NULL,                               /* m_traverse */
738         NULL,                               /* m_clear */
739         NULL,                               /* m_free */
740 };
741
742 PyMODINIT_FUNC PyInit__collecty(void) {
743         if (PyType_Ready(&BlockDeviceType) < 0)
744                 return NULL;
745
746         if (PyType_Ready(&SensorType) < 0)
747                 return NULL;
748
749         PyObject* m = PyModule_Create(&collecty_module);
750
751         Py_INCREF(&BlockDeviceType);
752         PyModule_AddObject(m, "BlockDevice", (PyObject*)&BlockDeviceType);
753
754         Py_INCREF(&SensorType);
755         PyModule_AddObject(m, "Sensor", (PyObject*)&SensorType);
756
757         return m;
758 }