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.
Files changed (38) hide show
  1. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/MANIFEST.in +1 -0
  2. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/PKG-INFO +2 -2
  3. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/bonecli.py +18 -11
  4. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/const.py +5 -1
  5. boneio-0.1.0.dev8/boneio/fonts/danube__.ttf +0 -0
  6. boneio-0.1.0.dev8/boneio/helper/__init__.py +14 -0
  7. boneio-0.1.0.dev8/boneio/helper/oled.py +15 -0
  8. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/helper/stats.py +48 -11
  9. boneio-0.1.0.dev8/boneio/input/__init__.py +5 -0
  10. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/input/gpio.py +11 -10
  11. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/manager.py +64 -24
  12. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/mqtt_client.py +4 -4
  13. boneio-0.1.0.dev8/boneio/oled.py +108 -0
  14. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/relay/basic.py +11 -2
  15. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/relay/gpio.py +3 -2
  16. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/relay/mcp.py +16 -3
  17. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/schema.yaml +7 -1
  18. boneio-0.1.0.dev8/boneio/version.py +2 -0
  19. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/PKG-INFO +2 -2
  20. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/SOURCES.txt +2 -0
  21. boneio-0.1.0.dev6/boneio/helper/__init__.py +0 -4
  22. boneio-0.1.0.dev6/boneio/input/__init__.py +0 -5
  23. boneio-0.1.0.dev6/boneio/oled.py +0 -120
  24. boneio-0.1.0.dev6/boneio/version.py +0 -2
  25. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/README.md +0 -0
  26. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/__init__.py +0 -0
  27. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/config.example.yaml +0 -0
  28. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/config.yaml +0 -0
  29. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/helper/gpio.py +0 -0
  30. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/helper/yaml.py +0 -0
  31. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio/relay/__init__.py +1 -1
  32. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/dependency_links.txt +0 -0
  33. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/entry_points.txt +0 -0
  34. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/requires.txt +0 -0
  35. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/boneio.egg-info/top_level.txt +0 -0
  36. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/requirements.txt +0 -0
  37. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/setup.cfg +0 -0
  38. {boneio-0.1.0.dev6 → boneio-0.1.0.dev8}/setup.py +0 -0
@@ -2,3 +2,4 @@ include README.md
2
2
  include requirements.txt
3
3
  include LICENSE
4
4
  recursive-include boneio *.yaml
5
+ recursive-include boneio *.ttf
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: boneio
3
- Version: 0.1.0.dev6
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.dev6.zip
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
- from .helper import CustomValidator, load_yaml_file
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
- from .mqtt_client import MQTTClient
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
- MQTT,
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
- await client.start_client(manager)
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"
@@ -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 psutil
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(30)
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(10)
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
@@ -0,0 +1,5 @@
1
+ """Input classes."""
2
+
3
+ from .gpio import GpioInputButton, configure_pin
4
+
5
+ __all__ = ["GpioInputButton", "configure_pin"]
@@ -1,23 +1,24 @@
1
1
  """GPIOInputButton to receive signals."""
2
- from ..helper.gpio import setup_input, read_input, edge_detect
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 logging
8
- import asyncio
6
+ from functools import partial
7
+ from typing import Any, Callable
8
+
9
9
  from ..const import (
10
10
  CONFIG_PIN,
11
- GPIO,
12
- SINGLE,
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
- from .input import GpioInputButton
2
- from .relay import GpioRelay, MCPRelay
3
- from .oled import Oled
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 typing import Callable, Optional, Union, List, Any
24
- import logging
25
- import asyncio
26
- from busio import I2C
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 = "homeassistant",
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
- self._mcp[mcp[ID] or mcp[ADDRESS]] = MCP23017(
82
- i2c=self._i2cbusio, address=mcp[ADDRESS]
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
- mcp = self._mcp.get(gpio.get(MCP_ID, ""))
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
- return MCPRelay(
109
+ relay_id = gpio[ID]
110
+ mcp_relay = MCPRelay(
93
111
  pin=int(gpio[PIN]),
94
- id=gpio[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
- return GpioRelay(
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(self.output)
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 get_oled_tasks(self) -> any:
136
- if not self._oled:
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
@@ -0,0 +1,2 @@
1
+ # flake8: noqa
2
+ __version__ = "0.1.0.dev8"
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: boneio
3
- Version: 0.1.0.dev6
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.dev6.zip
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
@@ -1,4 +0,0 @@
1
- """Helper dir for BoneIO."""
2
- from .yaml import CustomValidator, load_yaml_file
3
-
4
- __all__ = ["CustomValidator", "load_yaml_file"]
@@ -1,5 +0,0 @@
1
- """Input classes."""
2
-
3
- from .gpio import GpioInputButton
4
-
5
- __all__ = ["GpioInputButton"]
@@ -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
@@ -1,2 +0,0 @@
1
- # flake8: noqa
2
- __version__ = "0.1.0.dev6"
File without changes
@@ -1,5 +1,5 @@
1
1
  """Relay module."""
2
- from .mcp import MCPRelay
3
2
  from .gpio import GpioRelay
3
+ from .mcp import MCPRelay
4
4
 
5
5
  __all__ = ["MCPRelay", "GpioRelay"]
File without changes
File without changes