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.
- boneio/__init__.py +11 -0
- boneio/bonecli.py +165 -0
- boneio/const.py +111 -0
- boneio/example_config/__init__.py +0 -0
- boneio/example_config/adc.yaml +14 -0
- boneio/example_config/config.yaml +25 -0
- boneio/example_config/input.yaml +200 -0
- boneio/example_config/output24x16A.yaml +139 -0
- boneio/example_config/output32x5A.yaml +160 -0
- boneio/fonts/danube__.ttf +0 -0
- boneio/helper/__init__.py +50 -0
- boneio/helper/exceptions.py +13 -0
- boneio/helper/gpio.py +81 -0
- boneio/helper/ha_discovery.py +128 -0
- boneio/helper/mqtt.py +30 -0
- boneio/helper/oled.py +15 -0
- boneio/helper/queue.py +32 -0
- boneio/helper/stats.py +185 -0
- boneio/helper/yaml.py +46 -0
- boneio/input/__init__.py +5 -0
- boneio/input/gpio.py +122 -0
- boneio/manager.py +398 -0
- boneio/modbus.py +107 -0
- boneio/mqtt_client.py +170 -0
- boneio/oled.py +118 -0
- boneio/relay/__init__.py +5 -0
- boneio/relay/basic.py +76 -0
- boneio/relay/gpio.py +44 -0
- boneio/relay/mcp.py +56 -0
- boneio/schema.yaml +310 -0
- boneio/sensor/__init__.py +12 -0
- boneio/sensor/adc.py +38 -0
- boneio/sensor/gpio.py +32 -0
- boneio/sensor/sdm630.py +193 -0
- boneio/sensor/temp/__init__.py +52 -0
- boneio/version.py +2 -0
- boneio-0.1.0.dist-info/LICENSE +674 -0
- boneio-0.1.0.dist-info/METADATA +51 -0
- boneio-0.1.0.dist-info/RECORD +42 -0
- boneio-0.1.0.dist-info/WHEEL +5 -0
- boneio-0.1.0.dist-info/entry_points.txt +3 -0
- boneio-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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
|
+
]
|
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
|