boneIO 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,160 @@
1
+ - id: light 1
2
+ kind: mcp
3
+ mcp_id: mcp1
4
+ pin: 0
5
+ ha_type: light
6
+ - id: light 2
7
+ kind: mcp
8
+ mcp_id: mcp1
9
+ pin: 1
10
+ ha_type: light
11
+ - id: light 3
12
+ kind: mcp
13
+ mcp_id: mcp1
14
+ pin: 2
15
+ ha_type: light
16
+ - id: light 4
17
+ kind: mcp
18
+ mcp_id: mcp1
19
+ pin: 3
20
+ ha_type: light
21
+ - id: light 5
22
+ kind: mcp
23
+ mcp_id: mcp1
24
+ pin: 4
25
+ ha_type: light
26
+ - id: light 6
27
+ kind: mcp
28
+ mcp_id: mcp1
29
+ pin: 5
30
+ ha_type: light
31
+ - id: light 7
32
+ kind: mcp
33
+ mcp_id: mcp1
34
+ pin: 6
35
+ ha_type: light
36
+ - id: light 8
37
+ kind: mcp
38
+ mcp_id: mcp1
39
+ pin: 7
40
+ ha_type: light
41
+ - id: light 9
42
+ kind: mcp
43
+ mcp_id: mcp1
44
+ pin: 8
45
+ ha_type: light
46
+ - id: light 10
47
+ kind: mcp
48
+ mcp_id: mcp1
49
+ pin: 9
50
+ ha_type: light
51
+ - id: light 11
52
+ kind: mcp
53
+ mcp_id: mcp1
54
+ pin: 10
55
+ ha_type: light
56
+ - id: light 12
57
+ kind: mcp
58
+ mcp_id: mcp1
59
+ pin: 11
60
+ ha_type: light
61
+ - id: light 13
62
+ kind: mcp
63
+ mcp_id: mcp1
64
+ pin: 12
65
+ ha_type: light
66
+ - id: light 14
67
+ kind: mcp
68
+ mcp_id: mcp1
69
+ pin: 13
70
+ ha_type: light
71
+ - id: light 15
72
+ kind: mcp
73
+ mcp_id: mcp1
74
+ pin: 14
75
+ ha_type: light
76
+ - id: light 16
77
+ kind: mcp
78
+ mcp_id: mcp1
79
+ pin: 15
80
+ ha_type: light
81
+ - id: switch 1
82
+ kind: mcp
83
+ mcp_id: mcp2
84
+ pin: 0
85
+ ha_type: switch
86
+ - id: switch 2
87
+ kind: mcp
88
+ mcp_id: mcp2
89
+ pin: 1
90
+ ha_type: switch
91
+ - id: switch 3
92
+ kind: mcp
93
+ mcp_id: mcp2
94
+ pin: 2
95
+ ha_type: switch
96
+ - id: switch 4
97
+ kind: mcp
98
+ mcp_id: mcp2
99
+ pin: 3
100
+ ha_type: switch
101
+ - id: switch 5
102
+ kind: mcp
103
+ mcp_id: mcp2
104
+ pin: 4
105
+ ha_type: switch
106
+ - id: switch 6
107
+ kind: mcp
108
+ mcp_id: mcp2
109
+ pin: 5
110
+ ha_type: switch
111
+ - id: switch 7
112
+ kind: mcp
113
+ mcp_id: mcp2
114
+ pin: 6
115
+ ha_type: switch
116
+ - id: switch 8
117
+ kind: mcp
118
+ mcp_id: mcp2
119
+ pin: 7
120
+ ha_type: switch
121
+ - id: switch 9
122
+ kind: mcp
123
+ mcp_id: mcp2
124
+ pin: 8
125
+ ha_type: switch
126
+ - id: switch 10
127
+ kind: mcp
128
+ mcp_id: mcp2
129
+ pin: 9
130
+ ha_type: switch
131
+ - id: switch 11
132
+ kind: mcp
133
+ mcp_id: mcp2
134
+ pin: 10
135
+ ha_type: switch
136
+ - id: switch 12
137
+ kind: mcp
138
+ mcp_id: mcp2
139
+ pin: 11
140
+ ha_type: switch
141
+ - id: switch 13
142
+ kind: mcp
143
+ mcp_id: mcp2
144
+ pin: 12
145
+ ha_type: switch
146
+ - id: switch 14
147
+ kind: mcp
148
+ mcp_id: mcp2
149
+ pin: 13
150
+ ha_type: switch
151
+ - id: switch 15
152
+ kind: mcp
153
+ mcp_id: mcp2
154
+ pin: 14
155
+ ha_type: switch
156
+ - id: switch 16
157
+ kind: mcp
158
+ mcp_id: mcp2
159
+ pin: 15
160
+ ha_type: switch
Binary file
@@ -0,0 +1,50 @@
1
+ """Helper dir for BoneIO."""
2
+ from boneio.helper.gpio import (
3
+ edge_detect,
4
+ setup_input,
5
+ setup_output,
6
+ read_input,
7
+ write_output,
8
+ GpioBaseClass,
9
+ configure_pin,
10
+ )
11
+ from boneio.helper.oled import make_font
12
+ from boneio.helper.mqtt import BasicMqtt
13
+ from boneio.helper.stats import HostData, host_stats
14
+ from boneio.helper.yaml import CustomValidator, load_yaml_file
15
+ from boneio.helper.ha_discovery import (
16
+ ha_relay_availabilty_message,
17
+ ha_sensor_temp_availabilty_message,
18
+ ha_binary_sensor_availabilty_message,
19
+ ha_input_availabilty_message,
20
+ ha_sensor_availabilty_message,
21
+ ha_adc_sensor_availabilty_message,
22
+ )
23
+ from boneio.helper.exceptions import GPIOInputException, I2CError
24
+ from boneio.helper.queue import UniqueQueue
25
+
26
+
27
+ __all__ = [
28
+ "CustomValidator",
29
+ "load_yaml_file",
30
+ "HostData",
31
+ "host_stats",
32
+ "setup_input",
33
+ "setup_output",
34
+ "edge_detect",
35
+ "read_input",
36
+ "write_output",
37
+ "make_font",
38
+ "ha_relay_availabilty_message",
39
+ "ha_sensor_availabilty_message",
40
+ "ha_adc_sensor_availabilty_message",
41
+ "ha_sensor_temp_availabilty_message",
42
+ "ha_binary_sensor_availabilty_message",
43
+ "ha_input_availabilty_message",
44
+ "GPIOInputException",
45
+ "I2CError",
46
+ "GpioBaseClass",
47
+ "configure_pin",
48
+ "BasicMqtt",
49
+ "UniqueQueue",
50
+ ]
@@ -0,0 +1,13 @@
1
+ """BoneIO Errors"""
2
+
3
+
4
+ class BoneIOException(Exception):
5
+ """BoneIO standard exception."""
6
+
7
+
8
+ class GPIOInputException(BoneIOException):
9
+ """GPIOInput Exception."""
10
+
11
+
12
+ class I2CError(BoneIOException):
13
+ """I2C Exception."""
boneio/helper/gpio.py ADDED
@@ -0,0 +1,81 @@
1
+ import asyncio
2
+ import logging
3
+ from Adafruit_BBIO import GPIO
4
+
5
+ from boneio.helper.exceptions import GPIOInputException
6
+ from boneio.const import LOW, FALLING, Gpio_Edges, Gpio_States
7
+ from typing import Callable
8
+ import subprocess
9
+
10
+ from boneio.const import (
11
+ CONFIG_PIN,
12
+ GPIO as GPIO_STR,
13
+ ClickTypes,
14
+ )
15
+
16
+ _LOGGER = logging.getLogger(__name__)
17
+
18
+
19
+ def configure_pin(pin: str, mode: str = GPIO_STR) -> None:
20
+ pin = f"{pin[0:3]}0{pin[3]}" if len(pin) == 4 else pin
21
+ _LOGGER.debug(f"Configuring pin {pin} for mode {mode}.")
22
+ subprocess.run(
23
+ [CONFIG_PIN, pin, mode],
24
+ stdout=subprocess.DEVNULL,
25
+ stderr=subprocess.STDOUT,
26
+ timeout=1,
27
+ )
28
+
29
+
30
+ def setup_output(pin: str):
31
+ """Set up a GPIO as output."""
32
+
33
+ GPIO.setup(pin, GPIO.OUT, pull_up_down=GPIO.PUD_DOWN)
34
+
35
+
36
+ def setup_input(pin: str, pull_mode: str = "UP"):
37
+ """Set up a GPIO as input."""
38
+ try:
39
+ GPIO.setup(pin, GPIO.IN, GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP)
40
+ except (ValueError, SystemError) as err:
41
+ raise GPIOInputException(err)
42
+
43
+
44
+ def write_output(pin: str, value: str):
45
+ """Write a value to a GPIO."""
46
+
47
+ GPIO.output(pin, value)
48
+
49
+
50
+ def read_input(pin: str, on_state: Gpio_States = LOW):
51
+ """Read a value from a GPIO."""
52
+ return GPIO.input(pin) is on_state
53
+
54
+
55
+ def edge_detect(
56
+ pin: str, callback: Callable, bounce: int = 0, edge: Gpio_Edges = FALLING
57
+ ):
58
+ """Add detection for RISING and FALLING events."""
59
+ try:
60
+ GPIO.add_event_detect(gpio=pin, edge=edge, callback=callback, bouncetime=bounce)
61
+ except RuntimeError as err:
62
+ raise GPIOInputException(err)
63
+
64
+
65
+ class GpioBaseClass:
66
+ """Base class for initialize GPIO"""
67
+
68
+ def __init__(
69
+ self, pin: str, press_callback: Callable[[ClickTypes, str], None], **kwargs
70
+ ) -> None:
71
+ """Setup GPIO Input Button"""
72
+ self._pin = pin
73
+ configure_pin(pin)
74
+ self._loop = asyncio.get_running_loop()
75
+ self._press_callback = press_callback
76
+ setup_input(self._pin)
77
+
78
+ @property
79
+ def is_pressed(self):
80
+ """Is button pressed."""
81
+ return read_input(self._pin)
@@ -0,0 +1,128 @@
1
+ from boneio.version import __version__
2
+
3
+
4
+ from boneio.const import INPUT, OFF, ON, RELAY, STATE, SENSOR, INPUT_SENSOR
5
+
6
+
7
+ def ha_relay_availabilty_message(id: str, name: str, topic: str = "boneio"):
8
+ """Create availability topic for HA."""
9
+ return {
10
+ "availability": [{"topic": f"{topic}/{STATE}"}],
11
+ "command_topic": f"{topic}/relay/{id}/set",
12
+ "device": {
13
+ "identifiers": [topic],
14
+ "manufacturer": "BoneIO",
15
+ "model": "BoneIO Relay Board",
16
+ "name": f"BoneIO {topic}",
17
+ "sw_version": __version__,
18
+ },
19
+ "name": name,
20
+ "payload_off": OFF,
21
+ "payload_on": ON,
22
+ "state_topic": f"{topic}/{RELAY}/{id}",
23
+ "unique_id": f"{topic}{RELAY}{id}",
24
+ "value_template": "{{ value_json.state }}",
25
+ }
26
+
27
+
28
+ def ha_availabilty_message(
29
+ id: str, name: str, topic: str = "boneio", sensor_type: str = INPUT
30
+ ):
31
+ """Create availability topic for HA."""
32
+ return {
33
+ "availability": [{"topic": f"{topic}/{STATE}"}],
34
+ "device": {
35
+ "identifiers": [topic],
36
+ "manufacturer": "BoneIO",
37
+ "model": "BoneIO Relay Board",
38
+ "name": f"BoneIO {topic}",
39
+ "sw_version": __version__,
40
+ },
41
+ "icon": "mdi:gesture-double-tap",
42
+ "name": name,
43
+ "state_topic": f"{topic}/{sensor_type}/{id}",
44
+ "unique_id": f"{topic}{sensor_type}{id}",
45
+ }
46
+
47
+
48
+ def ha_input_availabilty_message(**kwargs):
49
+ return ha_availabilty_message(sensor_type=INPUT, **kwargs)
50
+
51
+
52
+ def ha_adc_sensor_availabilty_message(**kwargs):
53
+ msg = ha_availabilty_message(sensor_type=SENSOR, **kwargs)
54
+ msg["unit_of_measurement"] = "V"
55
+ msg["device_class"] = "voltage"
56
+ msg["state_class"] = "measurement"
57
+ return msg
58
+
59
+
60
+ def ha_sensor_availabilty_message(unit_of_measurement: str = None, **kwargs):
61
+ msg = ha_availabilty_message(sensor_type=SENSOR, **kwargs)
62
+ if not unit_of_measurement:
63
+ return msg
64
+
65
+
66
+ def ha_binary_sensor_availabilty_message(id: str, name: str, topic: str = "boneio"):
67
+ """Create availability topic for HA."""
68
+ return {
69
+ "availability": [{"topic": f"{topic}/{STATE}"}],
70
+ "device": {
71
+ "identifiers": [topic],
72
+ "manufacturer": "BoneIO",
73
+ "model": "BoneIO Relay Board",
74
+ "name": f"BoneIO {topic}",
75
+ "sw_version": __version__,
76
+ },
77
+ "payload_on": "pressed",
78
+ "payload_off": "released",
79
+ "name": name,
80
+ "state_topic": f"{topic}/{INPUT_SENSOR}/{id}",
81
+ "unique_id": f"{topic}{INPUT_SENSOR}{id}",
82
+ }
83
+
84
+
85
+ def ha_sensor_temp_availabilty_message(id: str, name: str, topic: str = "boneio"):
86
+ """Create availability topic for HA."""
87
+ return {
88
+ "availability": [{"topic": f"{topic}/{STATE}"}],
89
+ "device": {
90
+ "identifiers": [topic],
91
+ "manufacturer": "BoneIO",
92
+ "model": "BoneIO Relay Board",
93
+ "name": f"BoneIO {topic}",
94
+ "sw_version": __version__,
95
+ },
96
+ "name": name,
97
+ "state_topic": f"{topic}/{SENSOR}/{id}",
98
+ "unique_id": f"{topic}{SENSOR}{id}",
99
+ "device_class": "temperature",
100
+ "state_class": "measurement",
101
+ "unit_of_measurement": "°C",
102
+ "value_template": "{{ value_json.state }}",
103
+ }
104
+
105
+
106
+ def sdm630_availabilty_message(
107
+ id: str,
108
+ sensor_id: str,
109
+ name: str,
110
+ topic: str = "sdm630",
111
+ sensor_type: str = SENSOR,
112
+ **kwargs,
113
+ ):
114
+ """Create SDM630 availability topic for HA."""
115
+ return {
116
+ "availability": [{"topic": f"{topic}/{id}{STATE}"}],
117
+ "device": {
118
+ "identifiers": [topic, id],
119
+ "manufacturer": "BoneIO",
120
+ "model": "SDM630",
121
+ "name": f"BoneIO {name.upper()}",
122
+ "sw_version": __version__,
123
+ },
124
+ "name": sensor_id,
125
+ "state_topic": f"{topic}/{sensor_type}/{id}/{sensor_id}",
126
+ "unique_id": f"{topic}{sensor_id.replace('_', '').lower()}",
127
+ **kwargs,
128
+ }
boneio/helper/mqtt.py ADDED
@@ -0,0 +1,30 @@
1
+ """Class to help initialize classes which uses mqtt send."""
2
+ from typing import Callable, Union
3
+
4
+
5
+ class BasicMqtt:
6
+ """Basic MQTT class."""
7
+
8
+ def __init__(
9
+ self,
10
+ id: str,
11
+ topic_prefix: str,
12
+ name: str,
13
+ send_message: Callable[[str, Union[str, dict]], None],
14
+ topic_type: str,
15
+ ):
16
+ """Initialize module."""
17
+ self._id = id.replace(" ", "")
18
+ self._name = name
19
+ self._send_message = send_message
20
+ self._send_topic = f"{topic_prefix}/{topic_type}/{self.id}"
21
+
22
+ @property
23
+ def id(self) -> bool:
24
+ """Id of the module."""
25
+ return self._id
26
+
27
+ @property
28
+ def name(self):
29
+ """Return name of the sensor."""
30
+ return self._name
boneio/helper/oled.py ADDED
@@ -0,0 +1,15 @@
1
+ import os
2
+
3
+ from PIL import ImageFont
4
+
5
+ from boneio.const import FONTS
6
+
7
+
8
+ def make_font(name: str, size: int, local: bool = False):
9
+ """Prepare ImageFont for Oled screen."""
10
+ font_path = (
11
+ name
12
+ if not local
13
+ else os.path.join(os.path.dirname(__file__), "..", FONTS, name)
14
+ )
15
+ return ImageFont.truetype(font_path, size)
boneio/helper/queue.py ADDED
@@ -0,0 +1,32 @@
1
+ """
2
+ Special unique implementation of asyncio.Queue for boneIO.
3
+ If MQTT is down then regular queue can append multiple ON/OFF for same topic.
4
+ After re-connection it would send all messages. It's not necessary, last payload of same topic is enough.
5
+ """
6
+ import asyncio
7
+
8
+
9
+ class UniqueQueue(asyncio.Queue):
10
+ """Unique implementation of asyncio.Queue."""
11
+
12
+ def _init(self, maxsize):
13
+ """Create unique dict of tuple[0]."""
14
+ super()._init(maxsize=maxsize)
15
+ self._unique_set = {}
16
+
17
+ def _put(self, item):
18
+ """If item does not exists add it.
19
+ If exists remove old one and put new one in the beginning of the queue."""
20
+ if item[0] not in self._unique_set:
21
+ super()._put(item)
22
+ self._unique_set[item[0]] = item
23
+ else:
24
+ self._queue.remove(self._unique_set[item[0]])
25
+ super()._put(item)
26
+ self._unique_set[item[0]] = item
27
+
28
+ def _get(self):
29
+ """Get item and remove it."""
30
+ item = super()._get()
31
+ del self._unique_set[item[0]]
32
+ return item
boneio/helper/stats.py ADDED
@@ -0,0 +1,185 @@
1
+ import asyncio
2
+ import socket
3
+ import time
4
+ from functools import partial
5
+ from math import floor
6
+ from typing import Callable
7
+
8
+ import psutil
9
+
10
+ from boneio.const import (
11
+ CPU,
12
+ DISK,
13
+ GIGABYTE,
14
+ HOST,
15
+ MEGABYTE,
16
+ MEMORY,
17
+ NETWORK,
18
+ SWAP,
19
+ UPTIME,
20
+ IP,
21
+ MASK,
22
+ MAC,
23
+ NONE,
24
+ )
25
+ from boneio.sensor import LM75Sensor, MCP9808Sensor
26
+
27
+
28
+ intervals = (("d", 86400), ("h", 3600), ("m", 60))
29
+
30
+
31
+ def display_time(seconds):
32
+ """Strf time."""
33
+ result = []
34
+
35
+ for name, count in intervals:
36
+ value = seconds // count
37
+ if value:
38
+ seconds -= value * count
39
+ if value == 1:
40
+ name = name.rstrip("s")
41
+ result.append(f"{int(value)}{name}")
42
+ return "".join(result)
43
+
44
+
45
+ async def get_network_info(host_data):
46
+ """Fetch network info."""
47
+
48
+ def retrieve_from_psutil():
49
+ addrs = psutil.net_if_addrs()["eth0"]
50
+ out = {IP: NONE, MASK: NONE, MAC: NONE}
51
+ for addr in addrs:
52
+ if addr.family == socket.AF_INET:
53
+ out["ip"] = addr.address
54
+ out["mask"] = addr.netmask
55
+ elif addr.family == psutil.AF_LINK:
56
+ out["mac"] = addr.address
57
+ return out
58
+
59
+ while True:
60
+ data = retrieve_from_psutil()
61
+ host_data.write(NETWORK, data)
62
+ await asyncio.sleep(60)
63
+
64
+
65
+ async def get_cpu_info(host_data):
66
+ """Fetch CPU info."""
67
+ while True:
68
+ cpu = psutil.cpu_times_percent()
69
+ host_data.write(
70
+ CPU,
71
+ {
72
+ "total": f"{int(100 - cpu.idle)}%",
73
+ "user": f"{cpu.user}%",
74
+ "system": f"{cpu.system}%",
75
+ },
76
+ )
77
+ await asyncio.sleep(5)
78
+
79
+
80
+ async def get_disk_info(host_data):
81
+ """Fetch disk info."""
82
+ while True:
83
+ disk = psutil.disk_usage("/")
84
+ host_data.write(
85
+ DISK,
86
+ {
87
+ "total": f"{floor(disk.total / GIGABYTE)}GB",
88
+ "used": f"{floor(disk.used / GIGABYTE)}GB",
89
+ "free": f"{floor(disk.free / GIGABYTE)}GB",
90
+ },
91
+ )
92
+ await asyncio.sleep(60)
93
+
94
+
95
+ async def get_memory_info(host_data):
96
+ """Fetch memory info."""
97
+ while True:
98
+ vm = psutil.virtual_memory()
99
+ host_data.write(
100
+ MEMORY,
101
+ {
102
+ "total": f"{floor(vm.total / MEGABYTE)}MB",
103
+ "used": f"{floor(vm.used / MEGABYTE)}MB",
104
+ "free": f"{floor(vm.available / MEGABYTE)}MB",
105
+ },
106
+ )
107
+ await asyncio.sleep(10)
108
+
109
+
110
+ async def get_swap_info(host_data):
111
+ """Fetch swap info."""
112
+ while True:
113
+ swap = psutil.swap_memory()
114
+ host_data.write(
115
+ SWAP,
116
+ {
117
+ "total": f"{floor(swap.total / MEGABYTE)}MB",
118
+ "used": f"{floor(swap.used / MEGABYTE)}MB",
119
+ "free": f"{floor(swap.free / MEGABYTE)}MB",
120
+ },
121
+ )
122
+ await asyncio.sleep(10)
123
+
124
+
125
+ async def get_uptime(host_data):
126
+ """Fetch uptime info."""
127
+ while True:
128
+ uptime = display_time(time.clock_gettime(time.CLOCK_BOOTTIME))
129
+ host_data.write_uptime(uptime)
130
+ await asyncio.sleep(30)
131
+
132
+
133
+ host_stats = {
134
+ NETWORK: get_network_info,
135
+ CPU: get_cpu_info,
136
+ DISK: get_disk_info,
137
+ MEMORY: get_memory_info,
138
+ SWAP: get_swap_info,
139
+ UPTIME: get_uptime,
140
+ }
141
+
142
+
143
+ class HostData:
144
+ """Helper class to store host data."""
145
+
146
+ data = {UPTIME: {}, NETWORK: {}, CPU: {}, DISK: {}, MEMORY: {}, SWAP: {}}
147
+
148
+ def __init__(
149
+ self,
150
+ output: dict,
151
+ callback: Callable,
152
+ temp_sensor: Callable[[LM75Sensor, MCP9808Sensor], None],
153
+ ) -> None:
154
+ """Initialize HostData."""
155
+ self._hostname = socket.gethostname()
156
+ self.data[UPTIME] = {HOST: self._hostname, UPTIME: 0}
157
+ self._temp_sensor = temp_sensor
158
+ self._output = output
159
+ self._callback = callback
160
+ self._loop = asyncio.get_running_loop()
161
+
162
+ def write(self, type: str, data: dict) -> None:
163
+ """Write data of chosen type."""
164
+ self.data[type] = data
165
+ self._loop.call_soon_threadsafe(partial(self._callback, type))
166
+
167
+ def write_uptime(self, uptime: str) -> None:
168
+ """Write uptime."""
169
+ self.data[UPTIME][UPTIME] = uptime
170
+ if self._temp_sensor:
171
+ self.data[UPTIME][self._temp_sensor.name] = self._temp_sensor.state
172
+ self._loop.call_soon_threadsafe(partial(self._callback, UPTIME))
173
+
174
+ def get(self, type: str) -> dict:
175
+ """Get saved stats."""
176
+ if type in self._output:
177
+ return self._get_output(type)
178
+ return self.data[type]
179
+
180
+ def _get_output(self, type: str) -> dict:
181
+ """Get stats for output."""
182
+ out = {}
183
+ for output in self._output[type].values():
184
+ out[output.id] = output.state
185
+ return out