boneIO 0.1.0.dev7__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.dev7 → boneio-0.1.0.dev8}/MANIFEST.in +1 -0
  2. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/PKG-INFO +2 -2
  3. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/bonecli.py +15 -12
  4. {boneio-0.1.0.dev7 → 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.dev7 → boneio-0.1.0.dev8}/boneio/helper/stats.py +21 -31
  9. boneio-0.1.0.dev8/boneio/input/__init__.py +5 -0
  10. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/input/gpio.py +11 -10
  11. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/manager.py +53 -26
  12. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/mqtt_client.py +3 -1
  13. boneio-0.1.0.dev8/boneio/oled.py +108 -0
  14. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/relay/basic.py +11 -2
  15. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/relay/gpio.py +3 -2
  16. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/relay/mcp.py +16 -3
  17. {boneio-0.1.0.dev7 → 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.dev7 → boneio-0.1.0.dev8}/boneio.egg-info/PKG-INFO +2 -2
  20. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio.egg-info/SOURCES.txt +2 -0
  21. boneio-0.1.0.dev7/boneio/helper/__init__.py +0 -4
  22. boneio-0.1.0.dev7/boneio/input/__init__.py +0 -5
  23. boneio-0.1.0.dev7/boneio/oled.py +0 -82
  24. boneio-0.1.0.dev7/boneio/version.py +0 -2
  25. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/README.md +0 -0
  26. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/__init__.py +0 -0
  27. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/config.example.yaml +0 -0
  28. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/config.yaml +0 -0
  29. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/helper/gpio.py +0 -0
  30. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/helper/yaml.py +0 -0
  31. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio/relay/__init__.py +1 -1
  32. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio.egg-info/dependency_links.txt +0 -0
  33. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio.egg-info/entry_points.txt +0 -0
  34. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio.egg-info/requires.txt +0 -0
  35. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/boneio.egg-info/top_level.txt +0 -0
  36. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/requirements.txt +0 -0
  37. {boneio-0.1.0.dev7 → boneio-0.1.0.dev8}/setup.cfg +0 -0
  38. {boneio-0.1.0.dev7 → 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.dev7
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.dev7.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,28 +1,29 @@
1
- from .helper import CustomValidator, load_yaml_file
2
- import click
1
+ import asyncio
3
2
  import logging
4
- from contextlib import AsyncExitStack
3
+ import os
4
+ from functools import wraps
5
+
6
+ import click
5
7
  from colorlog import ColoredFormatter
6
- from .mqtt_client import MQTTClient
7
- from .manager import Manager
8
+
8
9
  from .const import (
9
10
  ENABLED,
10
11
  GPIO_INPUT,
11
12
  HA_DISCOVERY,
13
+ HOST,
12
14
  MCP23017,
15
+ MQTT,
13
16
  OLED,
14
17
  OUTPUT,
15
18
  PAHO,
16
- MQTT,
17
- HOST,
19
+ PASSWORD,
18
20
  TOPIC_PREFIX,
19
21
  USERNAME,
20
- PASSWORD,
21
22
  )
23
+ from .helper import CustomValidator, load_yaml_file
24
+ from .manager import Manager
25
+ from .mqtt_client import MQTTClient
22
26
  from .version import __version__
23
- import asyncio
24
- import os
25
- from functools import wraps
26
27
 
27
28
  _LOGGER = logging.getLogger(__name__)
28
29
  logging.basicConfig(level=logging.INFO)
@@ -103,6 +104,7 @@ _options = [
103
104
  @coro
104
105
  async def run(ctx, debug: int, config: str, mqttpassword: str = ""):
105
106
  """Run BoneIO."""
107
+ _LOGGER.info("BoneIO starting.")
106
108
  if debug == 0:
107
109
  logging.getLogger().setLevel(logging.INFO)
108
110
  if debug > 0:
@@ -121,6 +123,7 @@ async def run(ctx, debug: int, config: str, mqttpassword: str = ""):
121
123
  _LOGGER.info("Missing file.")
122
124
  return
123
125
  _config = v.normalized(config_yaml)
126
+ _LOGGER.info("Connecting to MQTT.")
124
127
  client = MQTTClient(
125
128
  host=_config[MQTT][HOST],
126
129
  username=_config[MQTT].get(USERNAME),
@@ -138,8 +141,8 @@ async def run(ctx, debug: int, config: str, mqttpassword: str = ""):
138
141
  oled=_config.get(OLED),
139
142
  )
140
143
  tasks = set()
141
- tasks.add(client.start_client(manager))
142
144
  tasks.update(manager.get_tasks())
145
+ tasks.add(client.start_client(manager))
143
146
  await asyncio.gather(*tasks)
144
147
 
145
148
 
@@ -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,27 +1,15 @@
1
1
  import asyncio
2
- import psutil
3
- from math import floor
4
- from ..const import (
5
- GIGABYTE,
6
- MEGABYTE,
7
- NETWORK,
8
- DISK,
9
- MEMORY,
10
- CPU,
11
- SWAP,
12
- UPTIME,
13
- HOST,
14
- OUTPUT,
15
- OledDataTypes,
16
- )
2
+ import socket
17
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
18
11
 
19
- intervals = (
20
- ("d", 86400), # 60 * 60 * 24
21
- ("h", 3600), # 60 * 60
22
- ("m", 60),
23
- ("s", 1),
24
- )
12
+ intervals = (("d", 86400), ("h", 3600), ("m", 60))
25
13
 
26
14
 
27
15
  def display_time(seconds):
@@ -46,7 +34,7 @@ async def get_network_info(host_data):
46
34
  NETWORK,
47
35
  {"ip": net[0].address, "mask": net[0].netmask, "mac": net[2].address},
48
36
  )
49
- await asyncio.sleep(30)
37
+ await asyncio.sleep(60)
50
38
 
51
39
 
52
40
  async def get_cpu_info(host_data):
@@ -114,7 +102,7 @@ async def get_uptime(host_data):
114
102
  while True:
115
103
  uptime = display_time(time.clock_gettime(time.CLOCK_BOOTTIME))
116
104
  host_data.write_uptime(uptime)
117
- await asyncio.sleep(10)
105
+ await asyncio.sleep(30)
118
106
 
119
107
 
120
108
  host_stats = {
@@ -126,37 +114,39 @@ host_stats = {
126
114
  UPTIME: get_uptime,
127
115
  }
128
116
 
129
- import socket
130
-
131
117
 
132
118
  class HostData:
133
119
  """Helper class to store host data."""
134
120
 
135
121
  data = {UPTIME: {}, NETWORK: {}, CPU: {}, DISK: {}, MEMORY: {}, SWAP: {}}
136
122
 
137
- def __init__(self, output: dict) -> None:
123
+ def __init__(self, output: dict, callback: Callable) -> None:
138
124
  """Initialize HostData."""
139
125
  self._hostname = socket.gethostname()
140
126
  self.data[UPTIME] = {HOST: self._hostname, UPTIME: 0}
141
127
  self._output = output
128
+ self._callback = callback
129
+ self._loop = asyncio.get_running_loop()
142
130
 
143
131
  def write(self, type: str, data: dict) -> None:
144
132
  """Write data of chosen type."""
145
133
  self.data[type] = data
134
+ self._loop.call_soon_threadsafe(partial(self._callback, type))
146
135
 
147
136
  def write_uptime(self, uptime: str) -> None:
148
137
  """Write uptime."""
149
138
  self.data[UPTIME][UPTIME] = uptime
139
+ self._loop.call_soon_threadsafe(partial(self._callback, UPTIME))
150
140
 
151
- def get(self, type: OledDataTypes) -> dict:
141
+ def get(self, type: str) -> dict:
152
142
  """Get saved stats."""
153
- if type == OUTPUT:
154
- return self._get_output()
143
+ if type in self._output:
144
+ return self._get_output(type)
155
145
  return self.data[type]
156
146
 
157
- def _get_output(self) -> dict:
147
+ def _get_output(self, type: str) -> dict:
158
148
  """Get stats for output."""
159
149
  out = {}
160
- for output in self._output.values():
150
+ for output in self._output[type].values():
161
151
  out[output.id] = output.state
162
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,37 +1,37 @@
1
- from .input import GpioInputButton
2
- from .relay import GpioRelay, MCPRelay
3
- from .oled import Oled
4
- from .helper.stats import host_stats, HostData
5
- from typing import Set
6
1
  import asyncio
7
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
8
9
 
9
10
  from .const import (
10
11
  ACTION,
11
- OUTPUT,
12
12
  ACTIONS,
13
13
  ADDRESS,
14
14
  GPIO,
15
15
  HA_TYPE,
16
+ HOMEASSISTANT,
16
17
  ID,
18
+ INIT_SLEEP,
17
19
  KIND,
18
20
  MCP,
19
21
  MCP_ID,
22
+ OFF,
23
+ ON,
24
+ ONLINE,
20
25
  OUTPUT,
21
26
  PIN,
22
27
  RELAY,
23
- ON,
24
- OFF,
25
- ONLINE,
26
28
  STATE,
27
29
  ClickTypes,
28
30
  )
29
- from typing import Callable, Optional, Union, List, Any
30
- import logging
31
- import asyncio
32
- from busio import I2C
33
- from board import SCL, SDA
34
- 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
35
35
 
36
36
  _LOGGER = logging.getLogger(__name__)
37
37
 
@@ -67,12 +67,13 @@ class Manager:
67
67
  relay_pins: List,
68
68
  input_pins: List,
69
69
  ha_discovery: bool = True,
70
- ha_discovery_prefix: str = "homeassistant",
70
+ ha_discovery_prefix: str = HOMEASSISTANT,
71
71
  mcp23017: Optional[List] = None,
72
72
  oled: bool = False,
73
- web: bool = False,
73
+ web: bool = True,
74
74
  ) -> None:
75
75
  """Initialize the manager."""
76
+ _LOGGER.info("Initializing manager module.")
76
77
  loop = asyncio.get_event_loop()
77
78
  self._host_data = None
78
79
 
@@ -82,37 +83,54 @@ class Manager:
82
83
  self._input_pins = input_pins
83
84
  self._i2cbusio = I2C(SCL, SDA)
84
85
  self._mcp = {}
86
+ self._grouped_outputs = {}
85
87
  self._oled = None
86
88
  self._tasks: List[asyncio.Task] = []
87
89
 
88
90
  if mcp23017:
89
91
  for mcp in mcp23017:
90
- self._mcp[mcp[ID] or mcp[ADDRESS]] = MCP23017(
91
- 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."
92
97
  )
98
+ time.sleep(sleep_time)
99
+ self._grouped_outputs[id] = {}
93
100
 
94
101
  def configure_relay(gpio: dict) -> Any:
95
102
  """Configure kind of relay. Most common MCP."""
96
103
  if gpio[KIND] == MCP:
97
- mcp = self._mcp.get(gpio.get(MCP_ID, ""))
104
+ mcp_id = gpio.get(MCP_ID, "")
105
+ mcp = self._mcp.get(mcp_id)
98
106
  if not mcp:
99
107
  _LOGGER.error("No such MCP configured!")
100
108
  return
101
- return MCPRelay(
109
+ relay_id = gpio[ID]
110
+ mcp_relay = MCPRelay(
102
111
  pin=int(gpio[PIN]),
103
- id=gpio[ID],
112
+ id=relay_id,
104
113
  send_message=self.send_message,
105
114
  topic_prefix=topic_prefix,
106
115
  mcp=mcp,
116
+ mcp_id=mcp_id,
107
117
  ha_type=gpio[HA_TYPE],
118
+ callback=lambda: self._host_data_callback(mcp_id),
108
119
  )
120
+ self._grouped_outputs[mcp_id][relay_id] = mcp_relay
121
+ return mcp_relay
109
122
  elif gpio[KIND] == GPIO:
110
- return GpioRelay(
123
+ if not GPIO in self._grouped_outputs:
124
+ self._grouped_outputs[GPIO] = {}
125
+ gpio_relay = GpioRelay(
111
126
  pin=gpio[PIN],
112
127
  id=gpio[ID],
113
128
  send_message=self.send_message,
114
129
  topic_prefix=topic_prefix,
130
+ callback=lambda: self._host_data_callback(GPIO),
115
131
  )
132
+ self._grouped_outputs[GPIO][gpio[ID]] = gpio_relay
133
+ return gpio_relay
116
134
 
117
135
  self.output = {gpio[ID]: configure_relay(gpio) for gpio in relay_pins}
118
136
  for out in self.output.values():
@@ -137,15 +155,24 @@ class Manager:
137
155
  ]
138
156
 
139
157
  if oled or web:
140
- self._host_data = HostData(output=self.output)
158
+ self._host_data = HostData(
159
+ output=self._grouped_outputs, callback=self._host_data_callback
160
+ )
141
161
  for f in host_stats.values():
142
162
  self._tasks.append(asyncio.create_task(f(self._host_data)))
163
+ _LOGGER.debug("Gathering host data enabled.")
143
164
 
144
165
  if oled:
145
- self._oled = Oled(self._host_data)
146
- self._tasks.append(self._oled.get_task())
166
+ self._oled = Oled(
167
+ host_data=self._host_data, output_groups=list(self._grouped_outputs)
168
+ )
147
169
 
148
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)
149
176
 
150
177
  def get_tasks(self) -> Set[asyncio.Task]:
151
178
  return self._tasks
@@ -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__)
@@ -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.dev7
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.dev7.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,82 +0,0 @@
1
- from .helper.gpio import setup_input, edge_detect
2
- from .helper.stats import HostData
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
- OUTPUT,
14
- WHITE,
15
- )
16
- import asyncio
17
- import os
18
- import logging
19
-
20
- from luma.core.interface.serial import i2c
21
- from luma.core.render import canvas
22
- from luma.oled.device import sh1106
23
- from PIL import ImageFont
24
-
25
- _LOGGER = logging.getLogger(__name__)
26
-
27
-
28
- def make_font(name, size):
29
- font_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "fonts", name))
30
- return ImageFont.truetype(font_path, size)
31
-
32
-
33
- fontBig = make_font("DejaVuSans.ttf", 12)
34
- fontSmall = make_font("DejaVuSans.ttf", 10)
35
-
36
- screen_order = [UPTIME, NETWORK, CPU, DISK, MEMORY, SWAP, OUTPUT]
37
- lines = [17, 32, 47]
38
-
39
-
40
- class Oled:
41
- """Oled display class."""
42
-
43
- def __init__(self, host_data: HostData) -> None:
44
- """Initialize OLED screen."""
45
- self._screen_no = 0
46
- self._loop = asyncio.get_running_loop()
47
- self._host_data = host_data
48
- configure_pin(OLED_PIN)
49
- setup_input(OLED_PIN)
50
- edge_detect(OLED_PIN, callback=self._handle_press, bounce=50)
51
- serial = i2c(port=2, address=0x3C)
52
- self._device = sh1106(serial)
53
- _LOGGER.debug("Configuring OLED screen.")
54
-
55
- def get_task(self) -> list:
56
- """Periodically check for host statistics."""
57
- return asyncio.create_task(self.render_display())
58
-
59
- async def render_display(self):
60
- while True:
61
- screen = screen_order[self._screen_no]
62
- data = self._host_data.get(screen)
63
- if data:
64
- with canvas(self._device) as draw:
65
- draw.text((1, 1), screen, font=fontBig, fill=WHITE)
66
- i = 0
67
- for k in data:
68
- draw.text(
69
- (3, lines[i]),
70
- f"{k} {data[k]}",
71
- font=fontSmall,
72
- fill=WHITE,
73
- )
74
- i += 1
75
- await asyncio.sleep(0.5)
76
-
77
- def _handle_press(self, pin: any) -> None:
78
- """Handle press of PIN for OLED display."""
79
- if self._screen_no < NUM_OF_SCREENS:
80
- self._screen_no += 1
81
- else:
82
- self._screen_no = 0
@@ -1,2 +0,0 @@
1
- # flake8: noqa
2
- __version__ = "0.1.0.dev7"
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