boneIO 0.1.0.dev6__tar.gz → 0.1.0.dev8__tar.gz
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-0.1.0.dev6 → boneio-0.1.0.dev8}/MANIFEST.in +1 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/PKG-INFO +2 -2
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/bonecli.py +18 -11
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/const.py +5 -1
- boneio-0.1.0.dev8/boneio/fonts/danube__.ttf +0 -0
- boneio-0.1.0.dev8/boneio/helper/__init__.py +14 -0
- boneio-0.1.0.dev8/boneio/helper/oled.py +15 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/helper/stats.py +48 -11
- boneio-0.1.0.dev8/boneio/input/__init__.py +5 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/input/gpio.py +11 -10
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/manager.py +64 -24
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/mqtt_client.py +4 -4
- boneio-0.1.0.dev8/boneio/oled.py +108 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/relay/basic.py +11 -2
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/relay/gpio.py +3 -2
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/relay/mcp.py +16 -3
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/schema.yaml +7 -1
- boneio-0.1.0.dev8/boneio/version.py +2 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/PKG-INFO +2 -2
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/SOURCES.txt +2 -0
- boneio-0.1.0.dev6/boneio/helper/__init__.py +0 -4
- boneio-0.1.0.dev6/boneio/input/__init__.py +0 -5
- boneio-0.1.0.dev6/boneio/oled.py +0 -120
- boneio-0.1.0.dev6/boneio/version.py +0 -2
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/README.md +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/__init__.py +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/config.example.yaml +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/config.yaml +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/helper/gpio.py +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/helper/yaml.py +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/relay/__init__.py +1 -1
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/dependency_links.txt +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/entry_points.txt +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/requires.txt +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/top_level.txt +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/requirements.txt +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/setup.cfg +0 -0
- {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/setup.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: boneio
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev8
|
|
4
4
|
Summary: Python Helper for BoneIO
|
|
5
5
|
Home-page: https://github.com/maciejk1984/boneIO
|
|
6
6
|
License: GNU General Public License v3.0
|
|
7
|
-
Download-URL: https://github.com/maciejk1984/boneIO/archive/0.1.0.
|
|
7
|
+
Download-URL: https://github.com/maciejk1984/boneIO/archive/0.1.0.dev8.zip
|
|
8
8
|
Platform: UNKNOWN
|
|
9
9
|
Classifier: Programming Language :: Python
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.7
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
import click
|
|
1
|
+
import asyncio
|
|
3
2
|
import logging
|
|
3
|
+
import os
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
import click
|
|
4
7
|
from colorlog import ColoredFormatter
|
|
5
|
-
|
|
6
|
-
from .manager import Manager
|
|
8
|
+
|
|
7
9
|
from .const import (
|
|
8
10
|
ENABLED,
|
|
9
11
|
GPIO_INPUT,
|
|
10
12
|
HA_DISCOVERY,
|
|
13
|
+
HOST,
|
|
11
14
|
MCP23017,
|
|
15
|
+
MQTT,
|
|
12
16
|
OLED,
|
|
13
17
|
OUTPUT,
|
|
14
18
|
PAHO,
|
|
15
|
-
|
|
16
|
-
HOST,
|
|
19
|
+
PASSWORD,
|
|
17
20
|
TOPIC_PREFIX,
|
|
18
21
|
USERNAME,
|
|
19
|
-
PASSWORD,
|
|
20
22
|
)
|
|
23
|
+
from .helper import CustomValidator, load_yaml_file
|
|
24
|
+
from .manager import Manager
|
|
25
|
+
from .mqtt_client import MQTTClient
|
|
21
26
|
from .version import __version__
|
|
22
|
-
import asyncio
|
|
23
|
-
import os
|
|
24
|
-
from functools import wraps
|
|
25
27
|
|
|
26
28
|
_LOGGER = logging.getLogger(__name__)
|
|
27
29
|
logging.basicConfig(level=logging.INFO)
|
|
@@ -102,6 +104,7 @@ _options = [
|
|
|
102
104
|
@coro
|
|
103
105
|
async def run(ctx, debug: int, config: str, mqttpassword: str = ""):
|
|
104
106
|
"""Run BoneIO."""
|
|
107
|
+
_LOGGER.info("BoneIO starting.")
|
|
105
108
|
if debug == 0:
|
|
106
109
|
logging.getLogger().setLevel(logging.INFO)
|
|
107
110
|
if debug > 0:
|
|
@@ -120,6 +123,7 @@ async def run(ctx, debug: int, config: str, mqttpassword: str = ""):
|
|
|
120
123
|
_LOGGER.info("Missing file.")
|
|
121
124
|
return
|
|
122
125
|
_config = v.normalized(config_yaml)
|
|
126
|
+
_LOGGER.info("Connecting to MQTT.")
|
|
123
127
|
client = MQTTClient(
|
|
124
128
|
host=_config[MQTT][HOST],
|
|
125
129
|
username=_config[MQTT].get(USERNAME),
|
|
@@ -136,7 +140,10 @@ async def run(ctx, debug: int, config: str, mqttpassword: str = ""):
|
|
|
136
140
|
mcp23017=_config[MCP23017],
|
|
137
141
|
oled=_config.get(OLED),
|
|
138
142
|
)
|
|
139
|
-
|
|
143
|
+
tasks = set()
|
|
144
|
+
tasks.update(manager.get_tasks())
|
|
145
|
+
tasks.add(client.start_client(manager))
|
|
146
|
+
await asyncio.gather(*tasks)
|
|
140
147
|
|
|
141
148
|
|
|
142
149
|
if __name__ == "__main__":
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# from typing import Literal
|
|
2
|
-
from typing_extensions import Literal
|
|
3
2
|
from datetime import timedelta
|
|
4
3
|
|
|
4
|
+
from typing_extensions import Literal
|
|
5
|
+
|
|
5
6
|
DEBOUNCE_DURATION = timedelta(seconds=0.2)
|
|
6
7
|
LONG_PRESS_DURATION = timedelta(seconds=0.7)
|
|
7
8
|
DELAY_DURATION = 0.1
|
|
@@ -21,6 +22,7 @@ HOST = "host"
|
|
|
21
22
|
USERNAME = "username"
|
|
22
23
|
PASSWORD = "password"
|
|
23
24
|
TOPIC_PREFIX = "topic_prefix"
|
|
25
|
+
HOMEASSISTANT = "homeassistant"
|
|
24
26
|
HA_DISCOVERY = "ha_discovery"
|
|
25
27
|
ENABLED = "enabled"
|
|
26
28
|
GPIO_INPUT = "gpio_input"
|
|
@@ -52,4 +54,6 @@ MEMORY = "memory"
|
|
|
52
54
|
SWAP = "swap"
|
|
53
55
|
WHITE = "white"
|
|
54
56
|
OLED = "oled"
|
|
57
|
+
FONTS = "fonts"
|
|
55
58
|
OledDataTypes = Literal[UPTIME, NETWORK, CPU, DISK, MEMORY, SWAP, OUTPUT]
|
|
59
|
+
INIT_SLEEP = "init_sleep"
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Helper dir for BoneIO."""
|
|
2
|
+
from .gpio import edge_detect, setup_input
|
|
3
|
+
from .oled import make_font
|
|
4
|
+
from .stats import HostData
|
|
5
|
+
from .yaml import CustomValidator, load_yaml_file
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CustomValidator",
|
|
9
|
+
"load_yaml_file",
|
|
10
|
+
"HostData",
|
|
11
|
+
"setup_input",
|
|
12
|
+
"edge_detect",
|
|
13
|
+
"make_font",
|
|
14
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PIL import ImageFont
|
|
4
|
+
|
|
5
|
+
from ..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)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import
|
|
3
|
-
from math import floor
|
|
4
|
-
from ..const import GIGABYTE, MEGABYTE, NETWORK, DISK, MEMORY, CPU, SWAP, UPTIME
|
|
2
|
+
import socket
|
|
5
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 ..const import CPU, DISK, GIGABYTE, HOST, MEGABYTE, MEMORY, NETWORK, SWAP, UPTIME
|
|
6
11
|
|
|
7
|
-
intervals = (
|
|
8
|
-
("d", 86400), # 60 * 60 * 24
|
|
9
|
-
("h", 3600), # 60 * 60
|
|
10
|
-
("m", 60),
|
|
11
|
-
("s", 1),
|
|
12
|
-
)
|
|
12
|
+
intervals = (("d", 86400), ("h", 3600), ("m", 60))
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def display_time(seconds):
|
|
@@ -34,7 +34,7 @@ async def get_network_info(host_data):
|
|
|
34
34
|
NETWORK,
|
|
35
35
|
{"ip": net[0].address, "mask": net[0].netmask, "mac": net[2].address},
|
|
36
36
|
)
|
|
37
|
-
await asyncio.sleep(
|
|
37
|
+
await asyncio.sleep(60)
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
async def get_cpu_info(host_data):
|
|
@@ -102,7 +102,7 @@ async def get_uptime(host_data):
|
|
|
102
102
|
while True:
|
|
103
103
|
uptime = display_time(time.clock_gettime(time.CLOCK_BOOTTIME))
|
|
104
104
|
host_data.write_uptime(uptime)
|
|
105
|
-
await asyncio.sleep(
|
|
105
|
+
await asyncio.sleep(30)
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
host_stats = {
|
|
@@ -113,3 +113,40 @@ host_stats = {
|
|
|
113
113
|
SWAP: get_swap_info,
|
|
114
114
|
UPTIME: get_uptime,
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class HostData:
|
|
119
|
+
"""Helper class to store host data."""
|
|
120
|
+
|
|
121
|
+
data = {UPTIME: {}, NETWORK: {}, CPU: {}, DISK: {}, MEMORY: {}, SWAP: {}}
|
|
122
|
+
|
|
123
|
+
def __init__(self, output: dict, callback: Callable) -> None:
|
|
124
|
+
"""Initialize HostData."""
|
|
125
|
+
self._hostname = socket.gethostname()
|
|
126
|
+
self.data[UPTIME] = {HOST: self._hostname, UPTIME: 0}
|
|
127
|
+
self._output = output
|
|
128
|
+
self._callback = callback
|
|
129
|
+
self._loop = asyncio.get_running_loop()
|
|
130
|
+
|
|
131
|
+
def write(self, type: str, data: dict) -> None:
|
|
132
|
+
"""Write data of chosen type."""
|
|
133
|
+
self.data[type] = data
|
|
134
|
+
self._loop.call_soon_threadsafe(partial(self._callback, type))
|
|
135
|
+
|
|
136
|
+
def write_uptime(self, uptime: str) -> None:
|
|
137
|
+
"""Write uptime."""
|
|
138
|
+
self.data[UPTIME][UPTIME] = uptime
|
|
139
|
+
self._loop.call_soon_threadsafe(partial(self._callback, UPTIME))
|
|
140
|
+
|
|
141
|
+
def get(self, type: str) -> dict:
|
|
142
|
+
"""Get saved stats."""
|
|
143
|
+
if type in self._output:
|
|
144
|
+
return self._get_output(type)
|
|
145
|
+
return self.data[type]
|
|
146
|
+
|
|
147
|
+
def _get_output(self, type: str) -> dict:
|
|
148
|
+
"""Get stats for output."""
|
|
149
|
+
out = {}
|
|
150
|
+
for output in self._output[type].values():
|
|
151
|
+
out[output.id] = output.state
|
|
152
|
+
return out
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
"""GPIOInputButton to receive signals."""
|
|
2
|
-
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
3
4
|
import subprocess
|
|
4
|
-
from functools import partial
|
|
5
|
-
from typing import Callable, Any
|
|
6
5
|
from datetime import datetime
|
|
7
|
-
import
|
|
8
|
-
import
|
|
6
|
+
from functools import partial
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
9
|
from ..const import (
|
|
10
10
|
CONFIG_PIN,
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
DEBOUNCE_DURATION,
|
|
12
|
+
DELAY_DURATION,
|
|
13
13
|
DOUBLE,
|
|
14
|
+
GPIO,
|
|
14
15
|
LONG,
|
|
15
|
-
ClickTypes,
|
|
16
|
-
DEBOUNCE_DURATION,
|
|
17
16
|
LONG_PRESS_DURATION,
|
|
18
|
-
DELAY_DURATION,
|
|
19
17
|
SECOND_DELAY_DURATION,
|
|
18
|
+
SINGLE,
|
|
19
|
+
ClickTypes,
|
|
20
20
|
)
|
|
21
|
+
from ..helper.gpio import edge_detect, read_input, setup_input
|
|
21
22
|
|
|
22
23
|
_LOGGER = logging.getLogger(__name__)
|
|
23
24
|
|
|
@@ -1,31 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, Callable, List, Optional, Set, Union
|
|
5
|
+
|
|
6
|
+
from adafruit_mcp230xx.mcp23017 import MCP23017
|
|
7
|
+
from board import SCL, SDA
|
|
8
|
+
from busio import I2C
|
|
9
|
+
|
|
4
10
|
from .const import (
|
|
5
11
|
ACTION,
|
|
6
12
|
ACTIONS,
|
|
7
13
|
ADDRESS,
|
|
8
14
|
GPIO,
|
|
9
15
|
HA_TYPE,
|
|
16
|
+
HOMEASSISTANT,
|
|
10
17
|
ID,
|
|
18
|
+
INIT_SLEEP,
|
|
11
19
|
KIND,
|
|
12
20
|
MCP,
|
|
13
21
|
MCP_ID,
|
|
22
|
+
OFF,
|
|
23
|
+
ON,
|
|
24
|
+
ONLINE,
|
|
14
25
|
OUTPUT,
|
|
15
26
|
PIN,
|
|
16
27
|
RELAY,
|
|
17
|
-
ON,
|
|
18
|
-
OFF,
|
|
19
|
-
ONLINE,
|
|
20
28
|
STATE,
|
|
21
29
|
ClickTypes,
|
|
22
30
|
)
|
|
23
|
-
from
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
from
|
|
27
|
-
from board import SCL, SDA
|
|
28
|
-
from adafruit_mcp230xx.mcp23017 import MCP23017
|
|
31
|
+
from .helper.stats import HostData, host_stats
|
|
32
|
+
from .input import GpioInputButton
|
|
33
|
+
from .oled import Oled
|
|
34
|
+
from .relay import GpioRelay, MCPRelay
|
|
29
35
|
|
|
30
36
|
_LOGGER = logging.getLogger(__name__)
|
|
31
37
|
|
|
@@ -61,12 +67,15 @@ class Manager:
|
|
|
61
67
|
relay_pins: List,
|
|
62
68
|
input_pins: List,
|
|
63
69
|
ha_discovery: bool = True,
|
|
64
|
-
ha_discovery_prefix: str =
|
|
70
|
+
ha_discovery_prefix: str = HOMEASSISTANT,
|
|
65
71
|
mcp23017: Optional[List] = None,
|
|
66
72
|
oled: bool = False,
|
|
73
|
+
web: bool = True,
|
|
67
74
|
) -> None:
|
|
68
75
|
"""Initialize the manager."""
|
|
76
|
+
_LOGGER.info("Initializing manager module.")
|
|
69
77
|
loop = asyncio.get_event_loop()
|
|
78
|
+
self._host_data = None
|
|
70
79
|
|
|
71
80
|
self.send_message = send_message
|
|
72
81
|
self._topic_prefix = topic_prefix
|
|
@@ -74,36 +83,54 @@ class Manager:
|
|
|
74
83
|
self._input_pins = input_pins
|
|
75
84
|
self._i2cbusio = I2C(SCL, SDA)
|
|
76
85
|
self._mcp = {}
|
|
86
|
+
self._grouped_outputs = {}
|
|
77
87
|
self._oled = None
|
|
88
|
+
self._tasks: List[asyncio.Task] = []
|
|
78
89
|
|
|
79
90
|
if mcp23017:
|
|
80
91
|
for mcp in mcp23017:
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
id = mcp[ID] or mcp[ADDRESS]
|
|
93
|
+
self._mcp[id] = MCP23017(i2c=self._i2cbusio, address=mcp[ADDRESS])
|
|
94
|
+
sleep_time = mcp.get(INIT_SLEEP, 0)
|
|
95
|
+
_LOGGER.debug(
|
|
96
|
+
f"Sleeping for {sleep_time}s while MCP {id} is initializing."
|
|
83
97
|
)
|
|
98
|
+
time.sleep(sleep_time)
|
|
99
|
+
self._grouped_outputs[id] = {}
|
|
84
100
|
|
|
85
101
|
def configure_relay(gpio: dict) -> Any:
|
|
86
102
|
"""Configure kind of relay. Most common MCP."""
|
|
87
103
|
if gpio[KIND] == MCP:
|
|
88
|
-
|
|
104
|
+
mcp_id = gpio.get(MCP_ID, "")
|
|
105
|
+
mcp = self._mcp.get(mcp_id)
|
|
89
106
|
if not mcp:
|
|
90
107
|
_LOGGER.error("No such MCP configured!")
|
|
91
108
|
return
|
|
92
|
-
|
|
109
|
+
relay_id = gpio[ID]
|
|
110
|
+
mcp_relay = MCPRelay(
|
|
93
111
|
pin=int(gpio[PIN]),
|
|
94
|
-
id=
|
|
112
|
+
id=relay_id,
|
|
95
113
|
send_message=self.send_message,
|
|
96
114
|
topic_prefix=topic_prefix,
|
|
97
115
|
mcp=mcp,
|
|
116
|
+
mcp_id=mcp_id,
|
|
98
117
|
ha_type=gpio[HA_TYPE],
|
|
118
|
+
callback=lambda: self._host_data_callback(mcp_id),
|
|
99
119
|
)
|
|
120
|
+
self._grouped_outputs[mcp_id][relay_id] = mcp_relay
|
|
121
|
+
return mcp_relay
|
|
100
122
|
elif gpio[KIND] == GPIO:
|
|
101
|
-
|
|
123
|
+
if not GPIO in self._grouped_outputs:
|
|
124
|
+
self._grouped_outputs[GPIO] = {}
|
|
125
|
+
gpio_relay = GpioRelay(
|
|
102
126
|
pin=gpio[PIN],
|
|
103
127
|
id=gpio[ID],
|
|
104
128
|
send_message=self.send_message,
|
|
105
129
|
topic_prefix=topic_prefix,
|
|
130
|
+
callback=lambda: self._host_data_callback(GPIO),
|
|
106
131
|
)
|
|
132
|
+
self._grouped_outputs[GPIO][gpio[ID]] = gpio_relay
|
|
133
|
+
return gpio_relay
|
|
107
134
|
|
|
108
135
|
self.output = {gpio[ID]: configure_relay(gpio) for gpio in relay_pins}
|
|
109
136
|
for out in self.output.values():
|
|
@@ -127,15 +154,28 @@ class Manager:
|
|
|
127
154
|
for gpio in self._input_pins
|
|
128
155
|
]
|
|
129
156
|
|
|
157
|
+
if oled or web:
|
|
158
|
+
self._host_data = HostData(
|
|
159
|
+
output=self._grouped_outputs, callback=self._host_data_callback
|
|
160
|
+
)
|
|
161
|
+
for f in host_stats.values():
|
|
162
|
+
self._tasks.append(asyncio.create_task(f(self._host_data)))
|
|
163
|
+
_LOGGER.debug("Gathering host data enabled.")
|
|
164
|
+
|
|
130
165
|
if oled:
|
|
131
|
-
self._oled = Oled(
|
|
166
|
+
self._oled = Oled(
|
|
167
|
+
host_data=self._host_data, output_groups=list(self._grouped_outputs)
|
|
168
|
+
)
|
|
132
169
|
|
|
133
170
|
self.send_message(topic=f"{topic_prefix}/{STATE}", payload=ONLINE)
|
|
171
|
+
_LOGGER.info("BoneIO manager is ready.")
|
|
172
|
+
|
|
173
|
+
def _host_data_callback(self, type):
|
|
174
|
+
if self._oled:
|
|
175
|
+
self._oled.handle_data_update(type)
|
|
134
176
|
|
|
135
|
-
def
|
|
136
|
-
|
|
137
|
-
return []
|
|
138
|
-
return self._oled.get_tasks()
|
|
177
|
+
def get_tasks(self) -> Set[asyncio.Task]:
|
|
178
|
+
return self._tasks
|
|
139
179
|
|
|
140
180
|
def press_callback(self, x: ClickTypes, inpin: str, actions: dict) -> None:
|
|
141
181
|
"""Press callback to use in input gpio.
|
|
@@ -6,10 +6,12 @@ import uuid
|
|
|
6
6
|
from contextlib import AsyncExitStack
|
|
7
7
|
from typing import Any, Callable, Optional, Set, Union
|
|
8
8
|
|
|
9
|
-
from asyncio_mqtt import Client as AsyncioClient, MqttError
|
|
10
9
|
import paho.mqtt.client as mqtt
|
|
10
|
+
from asyncio_mqtt import Client as AsyncioClient
|
|
11
|
+
from asyncio_mqtt import MqttError
|
|
11
12
|
from paho.mqtt.properties import Properties
|
|
12
13
|
from paho.mqtt.subscribeoptions import SubscribeOptions
|
|
14
|
+
|
|
13
15
|
from .const import PAHO
|
|
14
16
|
|
|
15
17
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -144,7 +146,7 @@ class MQTTClient:
|
|
|
144
146
|
self.create_client() # reset connect/reconnect futures
|
|
145
147
|
|
|
146
148
|
async def _subscribe_manager(self, manager: any) -> None:
|
|
147
|
-
"""Connect and subscribe to manager topics."""
|
|
149
|
+
"""Connect and subscribe to manager topics + host stats."""
|
|
148
150
|
async with AsyncExitStack() as stack:
|
|
149
151
|
tasks: Set[asyncio.Task] = set()
|
|
150
152
|
|
|
@@ -166,8 +168,6 @@ class MQTTClient:
|
|
|
166
168
|
)
|
|
167
169
|
tasks.add(messages_task)
|
|
168
170
|
|
|
169
|
-
tasks.update(manager.get_oled_tasks())
|
|
170
|
-
|
|
171
171
|
topic = f"{manager.relay_topic}"
|
|
172
172
|
await self.subscribe(topic)
|
|
173
173
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from itertools import cycle
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from luma.core.interface.serial import i2c
|
|
6
|
+
from luma.core.render import canvas
|
|
7
|
+
from luma.oled.device import sh1106
|
|
8
|
+
from PIL import ImageDraw
|
|
9
|
+
|
|
10
|
+
from .const import CPU, DISK, MEMORY, NETWORK, OLED_PIN, SWAP, UPTIME, WHITE
|
|
11
|
+
from .helper import HostData, edge_detect, make_font, setup_input
|
|
12
|
+
from .input import configure_pin
|
|
13
|
+
|
|
14
|
+
_LOGGER = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
fontBig = make_font("DejaVuSans.ttf", 12)
|
|
18
|
+
fontSmall = make_font("DejaVuSans.ttf", 10)
|
|
19
|
+
fontExtraSmall = make_font("DejaVuSans.ttf", 7)
|
|
20
|
+
danube = make_font("danube__.ttf", 15, local=True)
|
|
21
|
+
|
|
22
|
+
screen_order = [UPTIME, NETWORK, CPU, DISK, MEMORY, SWAP]
|
|
23
|
+
|
|
24
|
+
STANDARD_ROWS = [17, 32, 47]
|
|
25
|
+
UPTIME_ROWS = list(range(25, 40, 10))
|
|
26
|
+
OUTPUT_ROWS = list(range(14, 60, 6))
|
|
27
|
+
OUTPUT_COLS = range(0, 101, 50)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Oled:
|
|
31
|
+
"""Oled display class."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, host_data: HostData, output_groups: List[str]) -> None:
|
|
34
|
+
"""Initialize OLED screen."""
|
|
35
|
+
self._screen_order = cycle(screen_order + output_groups)
|
|
36
|
+
self._output_groups = output_groups
|
|
37
|
+
self._current_screen = next(self._screen_order)
|
|
38
|
+
self._host_data = host_data
|
|
39
|
+
configure_pin(OLED_PIN)
|
|
40
|
+
setup_input(OLED_PIN)
|
|
41
|
+
edge_detect(OLED_PIN, callback=self._handle_press, bounce=50)
|
|
42
|
+
serial = i2c(port=2, address=0x3C)
|
|
43
|
+
self._device = sh1106(serial)
|
|
44
|
+
_LOGGER.debug("Configuring OLED screen.")
|
|
45
|
+
|
|
46
|
+
def _draw_standard(self, data: dict, draw: ImageDraw) -> None:
|
|
47
|
+
"""Draw standard information about host screen."""
|
|
48
|
+
draw.text((1, 1), self._current_screen, font=fontBig, fill=WHITE)
|
|
49
|
+
i = 0
|
|
50
|
+
for k in data:
|
|
51
|
+
draw.text(
|
|
52
|
+
(3, STANDARD_ROWS[i]),
|
|
53
|
+
f"{k} {data[k]}",
|
|
54
|
+
font=fontSmall,
|
|
55
|
+
fill=WHITE,
|
|
56
|
+
)
|
|
57
|
+
i += 1
|
|
58
|
+
|
|
59
|
+
def _draw_uptime(self, data: dict, draw: ImageDraw) -> None:
|
|
60
|
+
"""Draw uptime screen with boneIO logo."""
|
|
61
|
+
draw.text((3, 5), "bone", font=danube, fill=WHITE)
|
|
62
|
+
draw.text((53, 5), "iO", font=danube, fill=WHITE)
|
|
63
|
+
i = 0
|
|
64
|
+
for k in data:
|
|
65
|
+
draw.text(
|
|
66
|
+
(3, UPTIME_ROWS[i]),
|
|
67
|
+
f"{k}: {data[k]}",
|
|
68
|
+
font=fontSmall,
|
|
69
|
+
fill=WHITE,
|
|
70
|
+
)
|
|
71
|
+
i += 1
|
|
72
|
+
|
|
73
|
+
def _draw_output(self, data: dict, draw: ImageDraw) -> None:
|
|
74
|
+
"Draw outputs of GPIO/MCP relays."
|
|
75
|
+
cols = cycle(OUTPUT_COLS)
|
|
76
|
+
draw.text((1, 1), f"Relay {self._current_screen}", font=fontSmall, fill=WHITE)
|
|
77
|
+
i = 0
|
|
78
|
+
j = next(cols)
|
|
79
|
+
for k in data:
|
|
80
|
+
if len(OUTPUT_ROWS) == i:
|
|
81
|
+
j = next(cols)
|
|
82
|
+
i = 0
|
|
83
|
+
draw.text(
|
|
84
|
+
(j, OUTPUT_ROWS[i]), f"{k} {data[k]}", font=fontExtraSmall, fill=WHITE
|
|
85
|
+
)
|
|
86
|
+
i += 1
|
|
87
|
+
|
|
88
|
+
def render_display(self) -> None:
|
|
89
|
+
"""Render display."""
|
|
90
|
+
data = self._host_data.get(self._current_screen)
|
|
91
|
+
if data:
|
|
92
|
+
with canvas(self._device) as draw:
|
|
93
|
+
if self._current_screen in self._output_groups:
|
|
94
|
+
self._draw_output(data, draw)
|
|
95
|
+
elif self._current_screen == UPTIME:
|
|
96
|
+
self._draw_uptime(data, draw)
|
|
97
|
+
else:
|
|
98
|
+
self._draw_standard(data, draw)
|
|
99
|
+
|
|
100
|
+
def handle_data_update(self, type: str):
|
|
101
|
+
"""Callback to handle new data present into screen."""
|
|
102
|
+
if type == self._current_screen:
|
|
103
|
+
self.render_display()
|
|
104
|
+
|
|
105
|
+
def _handle_press(self, pin: any) -> None:
|
|
106
|
+
"""Handle press of PIN for OLED display."""
|
|
107
|
+
self._current_screen = next(self._screen_order)
|
|
108
|
+
self.render_display()
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Basic Relay module."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from typing import Callable, Union
|
|
5
|
-
from ..const import STATE, RELAY, ON, OFF, SWITCH
|
|
6
4
|
import logging
|
|
5
|
+
from typing import Callable, Union
|
|
6
|
+
|
|
7
|
+
from ..const import OFF, ON, RELAY, STATE, SWITCH
|
|
7
8
|
|
|
8
9
|
_LOGGER = logging.getLogger(__name__)
|
|
9
10
|
|
|
@@ -14,6 +15,7 @@ class BasicRelay:
|
|
|
14
15
|
def __init__(
|
|
15
16
|
self,
|
|
16
17
|
send_message: Callable[[str, Union[str, dict]], None],
|
|
18
|
+
callback: Callable,
|
|
17
19
|
topic_prefix: str,
|
|
18
20
|
id: str = None,
|
|
19
21
|
ha_type=SWITCH,
|
|
@@ -25,8 +27,14 @@ class BasicRelay:
|
|
|
25
27
|
self._relay_topic = f"{topic_prefix}/{RELAY}/"
|
|
26
28
|
self._send_topic = f"{self._relay_topic}{self.id}"
|
|
27
29
|
self._state = False
|
|
30
|
+
self._callback = callback
|
|
28
31
|
self._loop = asyncio.get_running_loop()
|
|
29
32
|
|
|
33
|
+
@property
|
|
34
|
+
def is_mcp_type(self) -> bool:
|
|
35
|
+
"""Check if relay is mcp type."""
|
|
36
|
+
return False
|
|
37
|
+
|
|
30
38
|
@property
|
|
31
39
|
def id(self) -> bool:
|
|
32
40
|
"""Id of the relay."""
|
|
@@ -45,6 +53,7 @@ class BasicRelay:
|
|
|
45
53
|
topic=self._send_topic,
|
|
46
54
|
payload={STATE: state},
|
|
47
55
|
)
|
|
56
|
+
self._loop.call_soon_threadsafe(self._callback)
|
|
48
57
|
|
|
49
58
|
def toggle(self) -> None:
|
|
50
59
|
"""Toggle relay."""
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
Created just in case.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from ..helper.gpio import setup_output, read_input, write_output, HIGH, LOW
|
|
7
|
-
from .basic import BasicRelay
|
|
8
6
|
import logging
|
|
9
7
|
|
|
8
|
+
from ..helper.gpio import HIGH, LOW, read_input, setup_output, write_output
|
|
9
|
+
from .basic import BasicRelay
|
|
10
|
+
|
|
10
11
|
_LOGGER = logging.getLogger(__name__)
|
|
11
12
|
|
|
12
13
|
|
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
"""MCP23017 Relay module."""
|
|
2
2
|
|
|
3
|
-
from .basic import BasicRelay
|
|
4
|
-
from adafruit_mcp230xx.mcp23017 import MCP23017
|
|
5
3
|
import logging
|
|
6
4
|
|
|
5
|
+
from adafruit_mcp230xx.mcp23017 import MCP23017
|
|
6
|
+
|
|
7
|
+
from .basic import BasicRelay
|
|
8
|
+
|
|
7
9
|
_LOGGER = logging.getLogger(__name__)
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class MCPRelay(BasicRelay):
|
|
11
13
|
"""Represents MCP Relay output"""
|
|
12
14
|
|
|
13
|
-
def __init__(self, pin: int, mcp: MCP23017, **kwargs) -> None:
|
|
15
|
+
def __init__(self, pin: int, mcp: MCP23017, mcp_id: str, **kwargs) -> None:
|
|
14
16
|
"""Initialize MCP relay."""
|
|
15
17
|
super().__init__(**kwargs)
|
|
16
18
|
self._pin_id = pin
|
|
17
19
|
self._pin = mcp.get_pin(pin)
|
|
18
20
|
self._pin.switch_to_output(value=True)
|
|
21
|
+
self._mcp_id = mcp_id
|
|
19
22
|
self._pin.value = False
|
|
20
23
|
_LOGGER.debug("Setup MCP with pin %s", self._pin_id)
|
|
21
24
|
|
|
25
|
+
@property
|
|
26
|
+
def is_mcp_type(self) -> bool:
|
|
27
|
+
"""Check if relay is mcp type."""
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def mcp_id(self) -> str:
|
|
32
|
+
"""Retrieve parent MCP ID."""
|
|
33
|
+
return self._mcp_id
|
|
34
|
+
|
|
22
35
|
@property
|
|
23
36
|
def is_active(self) -> bool:
|
|
24
37
|
"""Is relay active."""
|
|
@@ -69,7 +69,13 @@ mcp23017:
|
|
|
69
69
|
type: string
|
|
70
70
|
required: True
|
|
71
71
|
meta:
|
|
72
|
-
label: GPIO of I2C SDA
|
|
72
|
+
label: GPIO of I2C SDA
|
|
73
|
+
init_sleep:
|
|
74
|
+
type: integer
|
|
75
|
+
required: True
|
|
76
|
+
default: 0
|
|
77
|
+
meta:
|
|
78
|
+
label: How long to sleep for MCP to initialize.
|
|
73
79
|
|
|
74
80
|
output:
|
|
75
81
|
type: list
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: boneio
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev8
|
|
4
4
|
Summary: Python Helper for BoneIO
|
|
5
5
|
Home-page: https://github.com/maciejk1984/boneIO
|
|
6
6
|
License: GNU General Public License v3.0
|
|
7
|
-
Download-URL: https://github.com/maciejk1984/boneIO/archive/0.1.0.
|
|
7
|
+
Download-URL: https://github.com/maciejk1984/boneIO/archive/0.1.0.dev8.zip
|
|
8
8
|
Platform: UNKNOWN
|
|
9
9
|
Classifier: Programming Language :: Python
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.7
|
|
@@ -18,8 +18,10 @@ boneio.egg-info/dependency_links.txt
|
|
|
18
18
|
boneio.egg-info/entry_points.txt
|
|
19
19
|
boneio.egg-info/requires.txt
|
|
20
20
|
boneio.egg-info/top_level.txt
|
|
21
|
+
boneio/fonts/danube__.ttf
|
|
21
22
|
boneio/helper/__init__.py
|
|
22
23
|
boneio/helper/gpio.py
|
|
24
|
+
boneio/helper/oled.py
|
|
23
25
|
boneio/helper/stats.py
|
|
24
26
|
boneio/helper/yaml.py
|
|
25
27
|
boneio/input/__init__.py
|
boneio-0.1.0.dev6/boneio/oled.py
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
from .helper.gpio import setup_input, edge_detect
|
|
2
|
-
from .helper.stats import host_stats
|
|
3
|
-
from .input.gpio import configure_pin
|
|
4
|
-
from .const import (
|
|
5
|
-
OLED_PIN,
|
|
6
|
-
NETWORK,
|
|
7
|
-
CPU,
|
|
8
|
-
DISK,
|
|
9
|
-
MEMORY,
|
|
10
|
-
SWAP,
|
|
11
|
-
NUM_OF_SCREENS,
|
|
12
|
-
UPTIME,
|
|
13
|
-
HOST,
|
|
14
|
-
OUTPUT,
|
|
15
|
-
WHITE,
|
|
16
|
-
OledDataTypes,
|
|
17
|
-
)
|
|
18
|
-
import asyncio
|
|
19
|
-
import os
|
|
20
|
-
import socket
|
|
21
|
-
import logging
|
|
22
|
-
|
|
23
|
-
from functools import partial
|
|
24
|
-
from luma.core.interface.serial import i2c
|
|
25
|
-
from luma.core.render import canvas
|
|
26
|
-
from luma.oled.device import sh1106
|
|
27
|
-
from PIL import ImageFont
|
|
28
|
-
|
|
29
|
-
_LOGGER = logging.getLogger(__name__)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def make_font(name, size):
|
|
33
|
-
font_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "fonts", name))
|
|
34
|
-
return ImageFont.truetype(font_path, size)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
fontBig = make_font("DejaVuSans.ttf", 12)
|
|
38
|
-
fontSmall = make_font("DejaVuSans.ttf", 10)
|
|
39
|
-
|
|
40
|
-
screen_order = [UPTIME, NETWORK, CPU, DISK, MEMORY, SWAP, OUTPUT]
|
|
41
|
-
lines = [17, 32, 47]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class HostData:
|
|
45
|
-
"""Helper class to store host data."""
|
|
46
|
-
|
|
47
|
-
data = {UPTIME: {}, NETWORK: {}, CPU: {}, DISK: {}, MEMORY: {}, SWAP: {}}
|
|
48
|
-
|
|
49
|
-
def __init__(self, output: dict) -> None:
|
|
50
|
-
"""Initialize HostData."""
|
|
51
|
-
self._hostname = socket.gethostname()
|
|
52
|
-
self.data[UPTIME] = {HOST: self._hostname, UPTIME: 0}
|
|
53
|
-
self._output = output
|
|
54
|
-
|
|
55
|
-
def write(self, type: str, data: dict) -> None:
|
|
56
|
-
"""Write data of chosen type."""
|
|
57
|
-
self.data[type] = data
|
|
58
|
-
|
|
59
|
-
def write_uptime(self, uptime: str) -> None:
|
|
60
|
-
"""Write uptime."""
|
|
61
|
-
self.data[UPTIME][UPTIME] = uptime
|
|
62
|
-
|
|
63
|
-
def get(self, type: OledDataTypes) -> dict:
|
|
64
|
-
"""Get saved stats."""
|
|
65
|
-
if type == OUTPUT:
|
|
66
|
-
return self._get_output()
|
|
67
|
-
return self.data[type]
|
|
68
|
-
|
|
69
|
-
def _get_output(self) -> dict:
|
|
70
|
-
"""Get stats for output."""
|
|
71
|
-
out = {}
|
|
72
|
-
for output in self._output.values():
|
|
73
|
-
out[output.id] = output.state
|
|
74
|
-
return out
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class Oled:
|
|
78
|
-
"""Oled display class."""
|
|
79
|
-
|
|
80
|
-
def __init__(self, output: dict) -> None:
|
|
81
|
-
"""Initialize OLED screen."""
|
|
82
|
-
self._screen_no = 0
|
|
83
|
-
self._loop = asyncio.get_running_loop()
|
|
84
|
-
self._host_data = HostData(output=output)
|
|
85
|
-
configure_pin(OLED_PIN)
|
|
86
|
-
setup_input(OLED_PIN)
|
|
87
|
-
edge_detect(OLED_PIN, callback=self._handle_press, bounce=50)
|
|
88
|
-
serial = i2c(port=2, address=0x3C)
|
|
89
|
-
self._device = sh1106(serial)
|
|
90
|
-
_LOGGER.debug("Configuring OLED screen.")
|
|
91
|
-
|
|
92
|
-
def get_tasks(self) -> list:
|
|
93
|
-
"""Periodically check for host statistics."""
|
|
94
|
-
return [
|
|
95
|
-
asyncio.create_task(func(self._host_data)) for func in host_stats.values()
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
def _handle_press(self, pin: any) -> None:
|
|
99
|
-
"""Handle press of PIN for OLED display."""
|
|
100
|
-
|
|
101
|
-
def press_callback(screen_no):
|
|
102
|
-
screen = screen_order[screen_no]
|
|
103
|
-
data = self._host_data.get(screen)
|
|
104
|
-
with canvas(self._device) as draw:
|
|
105
|
-
draw.text((1, 1), screen, font=fontBig, fill=WHITE)
|
|
106
|
-
i = 0
|
|
107
|
-
for k in data:
|
|
108
|
-
draw.text(
|
|
109
|
-
(3, lines[i]),
|
|
110
|
-
f"{k} {data[k]}",
|
|
111
|
-
font=fontSmall,
|
|
112
|
-
fill=WHITE,
|
|
113
|
-
)
|
|
114
|
-
i += 1
|
|
115
|
-
|
|
116
|
-
self._loop.call_soon_threadsafe(partial(press_callback, self._screen_no))
|
|
117
|
-
if self._screen_no < NUM_OF_SCREENS:
|
|
118
|
-
self._screen_no += 1
|
|
119
|
-
else:
|
|
120
|
-
self._screen_no = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|