boneIO 0.1.0.dev2__tar.gz → 0.1.0.dev4__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 (30) hide show
  1. {boneIO-0.1.0.dev2 → boneio-0.1.0.dev4}/PKG-INFO +3 -3
  2. boneio-0.1.0.dev4/boneio/__init__.py +11 -0
  3. boneio-0.1.0.dev4/boneio/bonecli.py +143 -0
  4. boneio-0.1.0.dev4/boneio/const.py +55 -0
  5. boneio-0.1.0.dev4/boneio/helper/__init__.py +4 -0
  6. boneio-0.1.0.dev4/boneio/helper/gpio.py +30 -0
  7. boneio-0.1.0.dev4/boneio/helper/stats.py +115 -0
  8. boneio-0.1.0.dev4/boneio/helper/yaml.py +46 -0
  9. boneio-0.1.0.dev4/boneio/input/__init__.py +5 -0
  10. boneio-0.1.0.dev4/boneio/input/gpio.py +147 -0
  11. boneio-0.1.0.dev4/boneio/manager.py +170 -0
  12. boneio-0.1.0.dev4/boneio/mqtt_client.py +183 -0
  13. boneio-0.1.0.dev4/boneio/oled.py +120 -0
  14. boneio-0.1.0.dev4/boneio/relay/__init__.py +5 -0
  15. boneio-0.1.0.dev4/boneio/relay/basic.py +68 -0
  16. boneio-0.1.0.dev4/boneio/relay/gpio.py +42 -0
  17. boneio-0.1.0.dev4/boneio/relay/mcp.py +45 -0
  18. boneio-0.1.0.dev4/boneio/version.py +2 -0
  19. {boneIO-0.1.0.dev2/boneIO.egg-info → boneio-0.1.0.dev4/boneio.egg-info}/PKG-INFO +3 -3
  20. boneio-0.1.0.dev4/boneio.egg-info/SOURCES.txt +25 -0
  21. boneio-0.1.0.dev4/boneio.egg-info/entry_points.txt +3 -0
  22. boneio-0.1.0.dev4/boneio.egg-info/top_level.txt +1 -0
  23. {boneIO-0.1.0.dev2 → boneio-0.1.0.dev4}/setup.py +3 -3
  24. boneIO-0.1.0.dev2/boneIO.egg-info/SOURCES.txt +0 -8
  25. boneIO-0.1.0.dev2/boneIO.egg-info/entry_points.txt +0 -3
  26. boneIO-0.1.0.dev2/boneIO.egg-info/top_level.txt +0 -1
  27. {boneIO-0.1.0.dev2 → boneio-0.1.0.dev4}/README.md +0 -0
  28. {boneIO-0.1.0.dev2/boneIO.egg-info → boneio-0.1.0.dev4/boneio.egg-info}/dependency_links.txt +0 -0
  29. {boneIO-0.1.0.dev2/boneIO.egg-info → boneio-0.1.0.dev4/boneio.egg-info}/requires.txt +0 -0
  30. {boneIO-0.1.0.dev2 → boneio-0.1.0.dev4}/setup.cfg +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
- Name: boneIO
3
- Version: 0.1.0.dev2
2
+ Name: boneio
3
+ Version: 0.1.0.dev4
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.0dev2.zip
7
+ Download-URL: https://github.com/maciejk1984/boneIO/archive/0.1.0dev4.zip
8
8
  Platform: UNKNOWN
9
9
  Classifier: Programming Language :: Python
10
10
  Classifier: Programming Language :: Python :: 3.7
@@ -0,0 +1,11 @@
1
+ """Python library to control BoneIO."""
2
+
3
+ from .version import __version__ as version
4
+
5
+ name = "boneio"
6
+
7
+
8
+ __all__ = [
9
+ "boneio",
10
+ "version",
11
+ ]
@@ -0,0 +1,143 @@
1
+ from .helper import CustomValidator, load_yaml_file
2
+ import click
3
+ import logging
4
+ from colorlog import ColoredFormatter
5
+ from .mqtt_client import MQTTClient
6
+ from .manager import Manager
7
+ from .const import (
8
+ ENABLED,
9
+ GPIO_INPUT,
10
+ HA_DISCOVERY,
11
+ MCP23017,
12
+ OLED,
13
+ OUTPUT,
14
+ PAHO,
15
+ MQTT,
16
+ HOST,
17
+ TOPIC_PREFIX,
18
+ USERNAME,
19
+ PASSWORD,
20
+ )
21
+ from .version import __version__
22
+ import asyncio
23
+ import os
24
+ from functools import wraps
25
+
26
+ _LOGGER = logging.getLogger(__name__)
27
+ logging.basicConfig(level=logging.INFO)
28
+ fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
29
+ datefmt = "%Y-%m-%d %H:%M:%S"
30
+ colorfmt = f"%(log_color)s{fmt}%(reset)s"
31
+ logging.getLogger().handlers[0].setFormatter(
32
+ ColoredFormatter(
33
+ colorfmt,
34
+ datefmt=datefmt,
35
+ reset=True,
36
+ log_colors={
37
+ "DEBUG": "cyan",
38
+ "INFO": "green",
39
+ "WARNING": "yellow",
40
+ "ERROR": "red",
41
+ "CRITICAL": "red",
42
+ },
43
+ )
44
+ )
45
+
46
+ MAINPATH = os.path.join(os.path.dirname(__file__))
47
+
48
+
49
+ def add_options(options):
50
+ def _add_options(func):
51
+ for option in reversed(options):
52
+ func = option(func)
53
+ return func
54
+
55
+ return _add_options
56
+
57
+
58
+ def coro(f):
59
+ @wraps(f)
60
+ def wrapper(*args, **kwargs):
61
+ return asyncio.run(f(*args, **kwargs))
62
+
63
+ return wrapper
64
+
65
+
66
+ @click.group(no_args_is_help=True)
67
+ @click.pass_context
68
+ @click.version_option(__version__)
69
+ @coro
70
+ async def cli(ctx):
71
+ """A tool to run commands."""
72
+ pass
73
+
74
+
75
+ _options = [
76
+ click.option(
77
+ "-d",
78
+ "--debug",
79
+ default=False,
80
+ count=True,
81
+ help="Set Debug mode. Single debug is debug of this lib. Second d is debug of aioxmpp as well.",
82
+ ),
83
+ click.option(
84
+ "--config",
85
+ "-c",
86
+ type=str,
87
+ default="./config.yaml",
88
+ help="Config yaml file. Default to ./config.yaml",
89
+ ),
90
+ click.option(
91
+ "--mqttpassword",
92
+ envvar="MQTTPASS",
93
+ type=str,
94
+ help="Mqtt password. To use as ENV named MQTTPASS",
95
+ ),
96
+ ]
97
+
98
+
99
+ @cli.command()
100
+ @add_options(_options)
101
+ @click.pass_context
102
+ @coro
103
+ async def run(ctx, debug: int, config: str, mqttpassword: str = ""):
104
+ """Run BoneIO."""
105
+ if debug == 0:
106
+ logging.getLogger().setLevel(logging.INFO)
107
+ if debug > 0:
108
+ logging.getLogger().setLevel(logging.DEBUG)
109
+
110
+ _LOGGER.info("Debug mode active")
111
+ _LOGGER.debug(f"Lib version is {__version__}")
112
+ if debug > 1:
113
+ logging.getLogger(PAHO).setLevel(logging.DEBUG)
114
+ else:
115
+ logging.getLogger(PAHO).setLevel(logging.WARN)
116
+ schema = load_yaml_file(os.path.join(MAINPATH, "schema.yaml"))
117
+ v = CustomValidator(schema, purge_unknown=True)
118
+ config_yaml = load_yaml_file(config)
119
+ if not config_yaml:
120
+ _LOGGER.info("Missing file.")
121
+ return
122
+ _config = v.normalized(config_yaml)
123
+ client = MQTTClient(
124
+ host=_config[MQTT][HOST],
125
+ username=_config[MQTT].get(USERNAME),
126
+ password=_config[MQTT].get(PASSWORD, mqttpassword),
127
+ )
128
+
129
+ manager = Manager(
130
+ send_message=client.send_message,
131
+ topic_prefix=_config[MQTT][TOPIC_PREFIX],
132
+ relay_pins=_config[OUTPUT],
133
+ input_pins=_config[GPIO_INPUT],
134
+ ha_discovery=_config[MQTT][HA_DISCOVERY][ENABLED],
135
+ ha_discovery_prefix=_config[MQTT][HA_DISCOVERY][TOPIC_PREFIX],
136
+ mcp23017=_config[MCP23017],
137
+ oled=_config.get(OLED),
138
+ )
139
+ await client.start_client(manager)
140
+
141
+
142
+ if __name__ == "__main__":
143
+ asyncio.get_event_loop().run_until_complete(cli())
@@ -0,0 +1,55 @@
1
+ # from typing import Literal
2
+ from typing_extensions import Literal
3
+ from datetime import timedelta
4
+
5
+ DEBOUNCE_DURATION = timedelta(seconds=0.2)
6
+ LONG_PRESS_DURATION = timedelta(seconds=0.7)
7
+ DELAY_DURATION = 0.1
8
+ SECOND_DELAY_DURATION = 0.3
9
+ RELAY = "relay"
10
+ SINGLE = "single"
11
+ DOUBLE = "double"
12
+ LONG = "long"
13
+ ONLINE = "online"
14
+ ON = "ON"
15
+ OFF = "OFF"
16
+ STATE = "state"
17
+ PAHO = "paho.mqtt.client"
18
+ ClickTypes = Literal[SINGLE, DOUBLE, LONG]
19
+ MQTT = "mqtt"
20
+ HOST = "host"
21
+ USERNAME = "username"
22
+ PASSWORD = "password"
23
+ TOPIC_PREFIX = "topic_prefix"
24
+ HA_DISCOVERY = "ha_discovery"
25
+ ENABLED = "enabled"
26
+ GPIO_INPUT = "gpio_input"
27
+ OUTPUT = "output"
28
+ PIN = "pin"
29
+ ID = "id"
30
+ KIND = "kind"
31
+ GPIO = "gpio"
32
+ ACTIONS = "actions"
33
+ ACTION = "action"
34
+ OUTPUT = "output"
35
+ ADDRESS = "address"
36
+ MCP23017 = "mcp23017"
37
+ SWITCH = "switch"
38
+ CONFIG_PIN = "/usr/bin/config-pin"
39
+ MCP = "mcp"
40
+ MCP_ID = "mcp_id"
41
+ HA_TYPE = "ha_type"
42
+ GIGABYTE = 1073741824
43
+ MEGABYTE = 1048576
44
+ OLED_PIN = "P9_41"
45
+ NUM_OF_SCREENS = 6
46
+ WIDTH = 128
47
+ UPTIME = "uptime"
48
+ NETWORK = "network"
49
+ CPU = "cpu"
50
+ DISK = "disk"
51
+ MEMORY = "memory"
52
+ SWAP = "swap"
53
+ WHITE = "white"
54
+ OLED = "oled"
55
+ OledDataTypes = Literal[UPTIME, NETWORK, CPU, DISK, MEMORY, SWAP, OUTPUT]
@@ -0,0 +1,4 @@
1
+ """Helper dir for BoneIO."""
2
+ from .yaml import CustomValidator, load_yaml_file
3
+
4
+ __all__ = ["CustomValidator", "load_yaml_file"]
@@ -0,0 +1,30 @@
1
+ from Adafruit_BBIO import GPIO
2
+ from Adafruit_BBIO.GPIO import HIGH, LOW
3
+
4
+
5
+ def setup_output(pin: str):
6
+ """Set up a GPIO as output."""
7
+
8
+ GPIO.setup(pin, GPIO.OUT, pull_up_down=GPIO.PUD_DOWN)
9
+
10
+
11
+ def setup_input(pin: str, pull_mode: str = "UP"):
12
+ """Set up a GPIO as input."""
13
+ GPIO.setup(pin, GPIO.IN, GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP)
14
+
15
+
16
+ def write_output(pin: str, value: str):
17
+ """Write a value to a GPIO."""
18
+
19
+ GPIO.output(pin, value)
20
+
21
+
22
+ def read_input(pin: str, on_state=LOW):
23
+ """Read a value from a GPIO."""
24
+ return GPIO.input(pin) is on_state
25
+
26
+
27
+ def edge_detect(pin, callback, bounce):
28
+ """Add detection for RISING and FALLING events."""
29
+
30
+ GPIO.add_event_detect(pin, GPIO.FALLING, callback=callback, bouncetime=bounce)
@@ -0,0 +1,115 @@
1
+ import asyncio
2
+ import psutil
3
+ from math import floor
4
+ from ..const import GIGABYTE, MEGABYTE, NETWORK, DISK, MEMORY, CPU, SWAP, UPTIME
5
+ import time
6
+
7
+ intervals = (
8
+ ("d", 86400), # 60 * 60 * 24
9
+ ("h", 3600), # 60 * 60
10
+ ("m", 60),
11
+ ("s", 1),
12
+ )
13
+
14
+
15
+ def display_time(seconds):
16
+ """Strf time."""
17
+ result = []
18
+
19
+ for name, count in intervals:
20
+ value = seconds // count
21
+ if value:
22
+ seconds -= value * count
23
+ if value == 1:
24
+ name = name.rstrip("s")
25
+ result.append(f"{int(value)}{name}")
26
+ return "".join(result)
27
+
28
+
29
+ async def get_network_info(host_data):
30
+ """Fetch network info."""
31
+ while True:
32
+ net = psutil.net_if_addrs()["eth0"]
33
+ host_data.write(
34
+ NETWORK,
35
+ {"ip": net[0].address, "mask": net[0].netmask, "mac": net[2].address},
36
+ )
37
+ await asyncio.sleep(30)
38
+
39
+
40
+ async def get_cpu_info(host_data):
41
+ """Fetch CPU info."""
42
+ while True:
43
+ cpu = psutil.cpu_times_percent()
44
+ host_data.write(
45
+ CPU,
46
+ {
47
+ "total": f"{int(100 - cpu.idle)}%",
48
+ "user": f"{cpu.user}%",
49
+ "system": f"{cpu.system}%",
50
+ },
51
+ )
52
+ await asyncio.sleep(5)
53
+
54
+
55
+ async def get_disk_info(host_data):
56
+ """Fetch disk info."""
57
+ while True:
58
+ disk = psutil.disk_usage("/")
59
+ host_data.write(
60
+ DISK,
61
+ {
62
+ "total": f"{floor(disk.total / GIGABYTE)}GB",
63
+ "used": f"{floor(disk.used / GIGABYTE)}GB",
64
+ "free": f"{floor(disk.free / GIGABYTE)}GB",
65
+ },
66
+ )
67
+ await asyncio.sleep(60)
68
+
69
+
70
+ async def get_memory_info(host_data):
71
+ """Fetch memory info."""
72
+ while True:
73
+ vm = psutil.virtual_memory()
74
+ host_data.write(
75
+ MEMORY,
76
+ {
77
+ "total": f"{floor(vm.total / MEGABYTE)}%",
78
+ "used": f"{floor(vm.used / MEGABYTE)}%",
79
+ "free": f"{floor(vm.available / MEGABYTE)}%",
80
+ },
81
+ )
82
+ await asyncio.sleep(10)
83
+
84
+
85
+ async def get_swap_info(host_data):
86
+ """Fetch swap info."""
87
+ while True:
88
+ swap = psutil.swap_memory()
89
+ host_data.write(
90
+ SWAP,
91
+ {
92
+ "total": f"{floor(swap.total / MEGABYTE)}%",
93
+ "used": f"{floor(swap.used / MEGABYTE)}%",
94
+ "free": f"{floor(swap.free / MEGABYTE)}%",
95
+ },
96
+ )
97
+ await asyncio.sleep(10)
98
+
99
+
100
+ async def get_uptime(host_data):
101
+ """Fetch uptime info."""
102
+ while True:
103
+ uptime = display_time(time.clock_gettime(time.CLOCK_BOOTTIME))
104
+ host_data.write_uptime(uptime)
105
+ await asyncio.sleep(10)
106
+
107
+
108
+ host_stats = {
109
+ NETWORK: get_network_info,
110
+ CPU: get_cpu_info,
111
+ DISK: get_disk_info,
112
+ MEMORY: get_memory_info,
113
+ SWAP: get_swap_info,
114
+ UPTIME: get_uptime,
115
+ }
@@ -0,0 +1,46 @@
1
+ from typing import Any
2
+ import os
3
+ from cerberus import Validator
4
+ from ..const import ID, OLED, OUTPUT
5
+ from yaml import load, YAMLError, SafeLoader
6
+
7
+
8
+ class Loader(SafeLoader):
9
+ """Loader which support for include in yaml files."""
10
+
11
+ def __init__(self, stream):
12
+
13
+ self._root = os.path.split(stream.name)[0]
14
+
15
+ super(Loader, self).__init__(stream)
16
+
17
+ def include(self, node):
18
+
19
+ filename = os.path.join(self._root, self.construct_scalar(node))
20
+
21
+ with open(filename, "r") as f:
22
+ return load(f, Loader)
23
+
24
+
25
+ Loader.add_constructor("!include", Loader.include)
26
+
27
+
28
+ def load_yaml_file(filename: str) -> Any:
29
+ with open(filename, "r") as stream:
30
+ try:
31
+ return load(stream, Loader=Loader)
32
+ except YAMLError as exception:
33
+ raise exception
34
+
35
+
36
+ class CustomValidator(Validator):
37
+ """Custom validator of cerberus"""
38
+
39
+ def _check_with_output_id_uniqueness(self, field, value):
40
+ """Check if outputs ids are unique."""
41
+ all_ids = [x[ID] for x in self.document[OUTPUT]]
42
+ if len(all_ids) != len(set(all_ids)):
43
+ self._error(field, "Output IDs are not unique.")
44
+
45
+ def _normalize_coerce_to_bool(self, value):
46
+ return True
@@ -0,0 +1,5 @@
1
+ """Input classes."""
2
+
3
+ from .gpio import GpioInputButton
4
+
5
+ __all__ = ["GpioInputButton"]
@@ -0,0 +1,147 @@
1
+ """GPIOInputButton to receive signals."""
2
+ from ..helper.gpio import setup_input, read_input, edge_detect
3
+ import subprocess
4
+ from functools import partial
5
+ from typing import Callable, Any
6
+ from datetime import datetime
7
+ import logging
8
+ import asyncio
9
+ from ..const import (
10
+ CONFIG_PIN,
11
+ GPIO,
12
+ SINGLE,
13
+ DOUBLE,
14
+ LONG,
15
+ ClickTypes,
16
+ DEBOUNCE_DURATION,
17
+ LONG_PRESS_DURATION,
18
+ DELAY_DURATION,
19
+ SECOND_DELAY_DURATION,
20
+ )
21
+
22
+ _LOGGER = logging.getLogger(__name__)
23
+
24
+
25
+ def configure_pin(pin: str) -> None:
26
+ pin = f"{pin[0:3]}0{pin[3]}" if len(pin) == 4 else pin
27
+ _LOGGER.debug(f"Configuring pin for GPIO {pin}")
28
+ subprocess.run(
29
+ [CONFIG_PIN, pin, GPIO],
30
+ stdout=subprocess.DEVNULL,
31
+ stderr=subprocess.STDOUT,
32
+ )
33
+
34
+
35
+ class GpioInputButton:
36
+ """Represent Gpio input switch."""
37
+
38
+ def __init__(
39
+ self, pin: str, press_callback: Callable[[ClickTypes, str], None], rest_pin: Any
40
+ ) -> None:
41
+ """Setup GPIO Input Button"""
42
+ self._pin = pin
43
+ configure_pin(pin)
44
+ self._loop = asyncio.get_running_loop()
45
+ self._press_callback = press_callback
46
+ setup_input(self._pin)
47
+ edge_detect(self._pin, callback=self._handle_press, bounce=25)
48
+ self._first_press_timestamp = None
49
+ self._is_long_press = False
50
+ self._second_press_timestamp = None
51
+ self._second_check = False
52
+ _LOGGER.debug("Configured listening for input pin %s", self._pin)
53
+
54
+ def _handle_press(self, pin) -> None:
55
+ """Handle the button press callback"""
56
+ # Ignore if we are in a long press
57
+ if self._is_long_press:
58
+ return
59
+ now = datetime.now()
60
+
61
+ # Debounce button
62
+ if (
63
+ self._first_press_timestamp is not None
64
+ and now - self._first_press_timestamp < DEBOUNCE_DURATION
65
+ ):
66
+ return
67
+
68
+ # Second click debounce. Just in case.
69
+ if (
70
+ self._second_press_timestamp is not None
71
+ and now - self._second_press_timestamp < DEBOUNCE_DURATION
72
+ ):
73
+ return
74
+
75
+ if not self._first_press_timestamp:
76
+ self._first_press_timestamp = now
77
+ elif not self._second_press_timestamp:
78
+ self._second_press_timestamp = now
79
+
80
+ self._loop.call_soon_threadsafe(
81
+ self._loop.call_later,
82
+ DELAY_DURATION,
83
+ self.check_press_length,
84
+ )
85
+
86
+ @property
87
+ def is_pressed(self):
88
+ """Is button pressed."""
89
+ return read_input(self._pin)
90
+
91
+ def check_press_length(self) -> None:
92
+ """Check if it's a single, double or long press"""
93
+ # Check if button is still pressed
94
+ if self.is_pressed:
95
+ # Schedule a new check
96
+ self._loop.call_soon_threadsafe(
97
+ self._loop.call_later,
98
+ DELAY_DURATION,
99
+ self.check_press_length,
100
+ )
101
+
102
+ # Handle edge case due to multiple clicks
103
+ if self._first_press_timestamp is None:
104
+ return
105
+
106
+ # Check if we reached a long press
107
+ diff = datetime.now() - self._first_press_timestamp
108
+ if not self._is_long_press and diff > LONG_PRESS_DURATION:
109
+ self._is_long_press = True
110
+ _LOGGER.debug("Long button press, call callback")
111
+ self._loop.call_soon_threadsafe(
112
+ partial(self._press_callback, LONG, self._pin)
113
+ )
114
+ return
115
+
116
+ # Handle short press
117
+ if not self._is_long_press:
118
+ if not self._second_press_timestamp and not self._second_check:
119
+ # let's try to check if second click will atempt
120
+ self._second_check = True
121
+ self._loop.call_soon_threadsafe(
122
+ self._loop.call_later,
123
+ SECOND_DELAY_DURATION,
124
+ self.check_press_length,
125
+ )
126
+ return
127
+ if self._second_check:
128
+ if self._second_press_timestamp:
129
+ _LOGGER.debug(
130
+ "Double click event, roznica %s",
131
+ self._second_press_timestamp - self._first_press_timestamp,
132
+ )
133
+ self._loop.call_soon_threadsafe(
134
+ partial(self._press_callback, DOUBLE, self._pin)
135
+ )
136
+
137
+ elif self._first_press_timestamp:
138
+ _LOGGER.debug("One click event, call callback")
139
+ self._loop.call_soon_threadsafe(
140
+ partial(self._press_callback, SINGLE, self._pin)
141
+ )
142
+
143
+ # Clean state on button released
144
+ self._first_press_timestamp = None
145
+ self._second_press_timestamp = None
146
+ self._second_check = False
147
+ self._is_long_press = False
@@ -0,0 +1,170 @@
1
+ from .input import GpioInputButton
2
+ from .relay import GpioRelay, MCPRelay
3
+ from .oled import Oled
4
+ from .const import (
5
+ ACTION,
6
+ ACTIONS,
7
+ ADDRESS,
8
+ GPIO,
9
+ HA_TYPE,
10
+ ID,
11
+ KIND,
12
+ MCP,
13
+ MCP_ID,
14
+ OUTPUT,
15
+ PIN,
16
+ RELAY,
17
+ ON,
18
+ OFF,
19
+ ONLINE,
20
+ STATE,
21
+ ClickTypes,
22
+ )
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
29
+
30
+ _LOGGER = logging.getLogger(__name__)
31
+
32
+
33
+ def ha_availibilty_message(topic, relay_id):
34
+ """Create availability topic for HA."""
35
+ return {
36
+ "availability": [{"topic": f"{topic}/{STATE}"}],
37
+ "command_topic": f"{topic}/relay/{relay_id}/set",
38
+ "device": {
39
+ "identifiers": [topic],
40
+ "manufacturer": "BoneIO",
41
+ "model": "BoneIO Relay Board",
42
+ "name": f"BoneIO {topic}",
43
+ "sw_version": "0.0.1",
44
+ },
45
+ "name": f"Relay {relay_id}",
46
+ "payload_off": OFF,
47
+ "payload_on": ON,
48
+ "state_topic": f"{topic}/{RELAY}/{relay_id}",
49
+ "unique_id": f"{topic}{RELAY}{relay_id}",
50
+ "value_template": "{{ value_json.state }}",
51
+ }
52
+
53
+
54
+ class Manager:
55
+ """Manager to communicate MQTT with GPIO inputs and outputs."""
56
+
57
+ def __init__(
58
+ self,
59
+ send_message: Callable[[str, Union[str, dict]], None],
60
+ topic_prefix: str,
61
+ relay_pins: List,
62
+ input_pins: List,
63
+ ha_discovery: bool = True,
64
+ ha_discovery_prefix: str = "homeassistant",
65
+ mcp23017: Optional[List] = None,
66
+ oled: bool = False,
67
+ ) -> None:
68
+ """Initialize the manager."""
69
+ loop = asyncio.get_event_loop()
70
+
71
+ self.send_message = send_message
72
+ self._topic_prefix = topic_prefix
73
+ self.relay_topic = f"{topic_prefix}/{RELAY}/+/set"
74
+ self._input_pins = input_pins
75
+ self._i2cbusio = I2C(SCL, SDA)
76
+ self._mcp = {}
77
+ self._oled = None
78
+
79
+ if mcp23017:
80
+ for mcp in mcp23017:
81
+ self._mcp[mcp[ID] or mcp[ADDRESS]] = MCP23017(
82
+ i2c=self._i2cbusio, address=mcp[ADDRESS]
83
+ )
84
+
85
+ def configure_relay(gpio: dict) -> Any:
86
+ """Configure kind of relay. Most common MCP."""
87
+ if gpio[KIND] == MCP:
88
+ mcp = self._mcp.get(gpio.get(MCP_ID, ""))
89
+ if not mcp:
90
+ _LOGGER.error("No such MCP configured!")
91
+ return
92
+ return MCPRelay(
93
+ pin=int(gpio[PIN]),
94
+ id=gpio[ID],
95
+ send_message=self.send_message,
96
+ topic_prefix=topic_prefix,
97
+ mcp=mcp,
98
+ ha_type=gpio[HA_TYPE],
99
+ )
100
+ elif gpio[KIND] == GPIO:
101
+ return GpioRelay(
102
+ pin=gpio[PIN],
103
+ id=gpio[ID],
104
+ send_message=self.send_message,
105
+ topic_prefix=topic_prefix,
106
+ )
107
+
108
+ self.output = {gpio[ID]: configure_relay(gpio) for gpio in relay_pins}
109
+ for out in self.output.values():
110
+ if ha_discovery:
111
+ _LOGGER.debug("Sending HA discovery.")
112
+ self.send_ha_autodiscovery(
113
+ relay=out.id, ha_type=out.ha_type, prefix=ha_discovery_prefix
114
+ )
115
+ loop.call_soon_threadsafe(
116
+ loop.call_later,
117
+ 0.5,
118
+ out.send_state,
119
+ )
120
+
121
+ self.buttons = [
122
+ GpioInputButton(
123
+ pin=gpio[PIN],
124
+ press_callback=lambda x, i: self.press_callback(x, i, gpio[ACTIONS]),
125
+ rest_pin=gpio,
126
+ )
127
+ for gpio in self._input_pins
128
+ ]
129
+
130
+ if oled:
131
+ self._oled = Oled(self.output)
132
+
133
+ self.send_message(topic=f"{topic_prefix}/{STATE}", payload=ONLINE)
134
+
135
+ def get_oled_tasks(self) -> any:
136
+ if not self._oled:
137
+ return []
138
+ return self._oled.get_tasks()
139
+
140
+ def press_callback(self, x: ClickTypes, inpin: str, actions: dict) -> None:
141
+ """Press callback to use in input gpio.
142
+ If relay input map is provided also toggle action on relay."""
143
+ self.send_message(topic=f"{self._topic_prefix}/input/{inpin}", payload=x)
144
+ action = actions.get(x)
145
+ if action:
146
+ if action[ACTION] == OUTPUT:
147
+ """For now only output type is supported"""
148
+ output_gpio = self.output.get(action[PIN])
149
+ if output_gpio:
150
+ output_gpio.toggle()
151
+
152
+ def send_ha_autodiscovery(
153
+ self, relay: str, prefix: str, ha_type: str = "switch"
154
+ ) -> None:
155
+ """Send HA autodiscovery information for each relay."""
156
+ msg = ha_availibilty_message(self._topic_prefix, relay_id=relay)
157
+ topic = f"{prefix}/{ha_type}/{self._topic_prefix}/{ha_type}/config"
158
+ self.send_message(topic=topic, payload=msg)
159
+
160
+ def receive_message(self, topic: str, message: str) -> None:
161
+ """Callback for receiving action from Mqtt."""
162
+ extracted_relay = topic.replace(f"{self._topic_prefix}/{RELAY}/", "").replace(
163
+ "/set", ""
164
+ )
165
+ target_device = self.output.get(extracted_relay)
166
+ if target_device:
167
+ if message == ON:
168
+ target_device.turn_on()
169
+ elif message == OFF:
170
+ target_device.turn_off()
@@ -0,0 +1,183 @@
1
+ """Provide an MQTT client for providing BoneIO MQTT broker."""
2
+ import asyncio
3
+ import json
4
+ import logging
5
+ import uuid
6
+ from contextlib import AsyncExitStack
7
+ from typing import Any, Callable, Optional, Set, Union
8
+
9
+ from asyncio_mqtt import Client as AsyncioClient, MqttError
10
+ import paho.mqtt.client as mqtt
11
+ from paho.mqtt.properties import Properties
12
+ from paho.mqtt.subscribeoptions import SubscribeOptions
13
+ from .const import PAHO
14
+
15
+ _LOGGER = logging.getLogger(__name__)
16
+
17
+
18
+ class MQTTClient:
19
+ """Represent an MQTT client."""
20
+
21
+ def __init__(
22
+ self,
23
+ host: str,
24
+ port: int = 1883,
25
+ **client_options: Any,
26
+ ) -> None:
27
+ """Set up client."""
28
+ self.host = host
29
+ self.port = port
30
+ if "client_id" not in client_options:
31
+ client_options["client_id"] = mqtt.base62(uuid.uuid4().int, padding=22)
32
+ if "logger" not in client_options:
33
+ client_options["logger"] = logging.getLogger(PAHO)
34
+ client_options["clean_session"] = True
35
+ self.client_options = client_options
36
+ self.asyncio_client: AsyncioClient = None
37
+ self.create_client()
38
+ self.reconnect_interval = 1
39
+ self.publish_queue: asyncio.Queue = asyncio.Queue()
40
+
41
+ def create_client(self) -> None:
42
+ """Create the asyncio client."""
43
+ self.asyncio_client = AsyncioClient(
44
+ self.host,
45
+ self.port,
46
+ **self.client_options,
47
+ )
48
+
49
+ async def connect(self, *, timeout: float = 10.0) -> None:
50
+ """Connect to the broker.
51
+
52
+ Can raise asyncio_mqtt.MqttError.
53
+ """
54
+ await self.asyncio_client.connect(timeout=timeout)
55
+
56
+ async def disconnect(self, *, timeout: float = 10.0) -> None:
57
+ """Disconnect from the broker.
58
+
59
+ Can raise asyncio_mqtt.MqttError.
60
+ """
61
+ await self.asyncio_client.disconnect(timeout=timeout)
62
+
63
+ async def publish( # pylint:disable=too-many-arguments
64
+ self,
65
+ topic: str,
66
+ payload: Optional[str] = None,
67
+ qos: int = 0,
68
+ retain: bool = False,
69
+ properties: Optional[Properties] = None,
70
+ timeout: float = 10,
71
+ ) -> None:
72
+ """Publish to topic.
73
+
74
+ Can raise asyncio_mqtt.MqttError.
75
+ """
76
+ params: dict = {"qos": qos, "retain": retain, "timeout": timeout}
77
+ if payload:
78
+ params["payload"] = payload
79
+ if properties:
80
+ params["properties"] = properties
81
+
82
+ _LOGGER.debug("Sending message topic: %s, payload: %s", topic, payload)
83
+ await self.asyncio_client.publish(topic, **params)
84
+
85
+ async def subscribe( # pylint:disable=too-many-arguments
86
+ self,
87
+ topic: str,
88
+ qos: int = 0,
89
+ options: Optional[SubscribeOptions] = None,
90
+ properties: Optional[Properties] = None,
91
+ timeout: float = 10.0,
92
+ ) -> None:
93
+ """Subscribe to topic.
94
+
95
+ Can raise asyncio_mqtt.MqttError.
96
+ """
97
+ params: dict = {"qos": qos, "timeout": timeout}
98
+ if options:
99
+ params["options"] = options
100
+ if properties:
101
+ params["properties"] = properties
102
+
103
+ await self.asyncio_client.subscribe(topic, **params)
104
+
105
+ async def unsubscribe(
106
+ self, topic: str, properties: Optional[Properties] = None, timeout: float = 10.0
107
+ ) -> None:
108
+ """Unsubscribe from topic.
109
+
110
+ Can raise asyncio_mqtt.MqttError.
111
+ """
112
+ params: dict = {"timeout": timeout}
113
+ if properties:
114
+ params["properties"] = properties
115
+
116
+ await self.asyncio_client.unsubscribe(topic, **params)
117
+
118
+ def send_message(self, topic: str, payload: Union[str, dict]) -> None:
119
+ """Send a message from the manager options."""
120
+ to_publish = (topic, json.dumps(payload) if type(payload) == dict else payload)
121
+ self.publish_queue.put_nowait(to_publish)
122
+
123
+ async def _handle_publish(self) -> None:
124
+ """Publish messages as they are put on the queue."""
125
+ while True:
126
+ to_publish: tuple = await self.publish_queue.get()
127
+ await self.publish(*to_publish)
128
+ self.publish_queue.task_done()
129
+
130
+ async def start_client(self, manager: any) -> None:
131
+ """Start the client with the manager."""
132
+ # Reconnect automatically until the client is stopped.
133
+ while True:
134
+ try:
135
+ await self._subscribe_manager(manager)
136
+ except MqttError as err:
137
+ self.reconnect_interval = min(self.reconnect_interval * 2, 900)
138
+ _LOGGER.error(
139
+ "MQTT error: %s. Reconnecting in %s seconds",
140
+ err,
141
+ self.reconnect_interval,
142
+ )
143
+ await asyncio.sleep(self.reconnect_interval)
144
+ self.create_client() # reset connect/reconnect futures
145
+
146
+ async def _subscribe_manager(self, manager: any) -> None:
147
+ """Connect and subscribe to manager topics."""
148
+ async with AsyncExitStack() as stack:
149
+ tasks: Set[asyncio.Task] = set()
150
+
151
+ # Connect to the MQTT broker.
152
+ await stack.enter_async_context(self.asyncio_client)
153
+ # Reset the reconnect interval after successful connection.
154
+ self.reconnect_interval = 1
155
+
156
+ publish_task = asyncio.create_task(self._handle_publish())
157
+ tasks.add(publish_task)
158
+
159
+ # Messages that doesn't match a filter will get logged and handled here.
160
+ messages = await stack.enter_async_context(
161
+ self.asyncio_client.unfiltered_messages()
162
+ )
163
+
164
+ messages_task = asyncio.create_task(
165
+ handle_messages(messages, manager.receive_message)
166
+ )
167
+ tasks.add(messages_task)
168
+
169
+ tasks.update(manager.get_oled_tasks())
170
+
171
+ topic = f"{manager.relay_topic}"
172
+ await self.subscribe(topic)
173
+
174
+ # Wait for everything to complete (or fail due to, e.g., network errors).
175
+ await asyncio.gather(*tasks)
176
+
177
+
178
+ async def handle_messages(messages: Any, callback: Callable[[str, str], None]) -> None:
179
+ """Handle messages with callback."""
180
+ async for message in messages:
181
+ payload = message.payload.decode()
182
+ _LOGGER.debug("Received message topic: %s, payload: %s", message.topic, payload)
183
+ callback(message.topic, payload)
@@ -0,0 +1,120 @@
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
@@ -0,0 +1,5 @@
1
+ """Relay module."""
2
+ from .mcp import MCPRelay
3
+ from .gpio import GpioRelay
4
+
5
+ __all__ = ["MCPRelay", "GpioRelay"]
@@ -0,0 +1,68 @@
1
+ """Basic Relay module."""
2
+
3
+ import asyncio
4
+ from typing import Callable, Union
5
+ from ..const import STATE, RELAY, ON, OFF, SWITCH
6
+ import logging
7
+
8
+ _LOGGER = logging.getLogger(__name__)
9
+
10
+
11
+ class BasicRelay:
12
+ """Basic relay class."""
13
+
14
+ def __init__(
15
+ self,
16
+ send_message: Callable[[str, Union[str, dict]], None],
17
+ topic_prefix: str,
18
+ id: str = None,
19
+ ha_type=SWITCH,
20
+ ) -> None:
21
+ """Initialize Basic relay."""
22
+ self._id = id
23
+ self._ha_type = ha_type
24
+ self._send_message = send_message
25
+ self._relay_topic = f"{topic_prefix}/{RELAY}/"
26
+ self._send_topic = f"{self._relay_topic}{self.id}"
27
+ self._state = False
28
+ self._loop = asyncio.get_running_loop()
29
+
30
+ @property
31
+ def id(self) -> bool:
32
+ """Id of the relay."""
33
+ return self._id or self._pin
34
+
35
+ @property
36
+ def state(self) -> bool:
37
+ """Is relay active."""
38
+ return self._state
39
+
40
+ def send_state(self) -> None:
41
+ """Send state to Mqtt on action."""
42
+ state = ON if self.is_active else OFF
43
+ self._state = state
44
+ self._send_message(
45
+ topic=self._send_topic,
46
+ payload={STATE: state},
47
+ )
48
+
49
+ def toggle(self) -> None:
50
+ """Toggle relay."""
51
+ _LOGGER.debug("Toggle relay.")
52
+ if self.is_active:
53
+ self.turn_off()
54
+ else:
55
+ self.turn_on()
56
+
57
+ @property
58
+ def is_active(self) -> bool:
59
+ """Is active check."""
60
+ raise NotImplementedError
61
+
62
+ def turn_on(self) -> None:
63
+ """Call turn on action."""
64
+ raise NotImplementedError
65
+
66
+ def turn_off(self) -> None:
67
+ """Call turn off action."""
68
+ raise NotImplementedError
@@ -0,0 +1,42 @@
1
+ """GPIO Relay module.
2
+ !!Not used in BoneIO.
3
+ Created just in case.
4
+ """
5
+
6
+ from ..helper.gpio import setup_output, read_input, write_output, HIGH, LOW
7
+ from .basic import BasicRelay
8
+ import logging
9
+
10
+ _LOGGER = logging.getLogger(__name__)
11
+
12
+
13
+ class GpioRelay(BasicRelay):
14
+ """Represents GPIO Relay output"""
15
+
16
+ def __init__(self, pin: str, **kwargs) -> None:
17
+ """Initialize Gpio relay."""
18
+ super().__init(**kwargs)
19
+ self._pin = pin
20
+ setup_output(self._pin)
21
+ write_output(self.pin, LOW)
22
+ _LOGGER.debug("Setup relay with pin %s", self._pin)
23
+
24
+ @property
25
+ def is_active(self) -> bool:
26
+ """Is relay active."""
27
+ return read_input(self.pin, on_state=HIGH)
28
+
29
+ @property
30
+ def pin(self) -> str:
31
+ """PIN of the relay"""
32
+ return self._pin
33
+
34
+ def turn_on(self) -> None:
35
+ """Call turn on action."""
36
+ write_output(self.pin, HIGH)
37
+ self._loop.call_soon_threadsafe(self.send_state)
38
+
39
+ def turn_off(self) -> None:
40
+ """Call turn off action."""
41
+ write_output(self.pin, LOW)
42
+ self._loop.call_soon_threadsafe(self.send_state)
@@ -0,0 +1,45 @@
1
+ """MCP23017 Relay module."""
2
+
3
+ from .basic import BasicRelay
4
+ from adafruit_mcp230xx.mcp23017 import MCP23017
5
+ import logging
6
+
7
+ _LOGGER = logging.getLogger(__name__)
8
+
9
+
10
+ class MCPRelay(BasicRelay):
11
+ """Represents MCP Relay output"""
12
+
13
+ def __init__(self, pin: int, mcp: MCP23017, **kwargs) -> None:
14
+ """Initialize MCP relay."""
15
+ super().__init__(**kwargs)
16
+ self._pin_id = pin
17
+ self._pin = mcp.get_pin(pin)
18
+ self._pin.switch_to_output(value=True)
19
+ self._pin.value = False
20
+ _LOGGER.debug("Setup MCP with pin %s", self._pin_id)
21
+
22
+ @property
23
+ def is_active(self) -> bool:
24
+ """Is relay active."""
25
+ return self.pin.value
26
+
27
+ @property
28
+ def pin(self) -> str:
29
+ """PIN of the relay"""
30
+ return self._pin
31
+
32
+ @property
33
+ def ha_type(self) -> str:
34
+ """HA type."""
35
+ return self._ha_type
36
+
37
+ def turn_on(self) -> None:
38
+ """Call turn on action."""
39
+ self.pin.value = True
40
+ self._loop.call_soon_threadsafe(self.send_state)
41
+
42
+ def turn_off(self) -> None:
43
+ """Call turn off action."""
44
+ self.pin.value = False
45
+ self._loop.call_soon_threadsafe(self.send_state)
@@ -0,0 +1,2 @@
1
+ # flake8: noqa
2
+ __version__ = "0.1.0dev4"
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
- Name: boneIO
3
- Version: 0.1.0.dev2
2
+ Name: boneio
3
+ Version: 0.1.0.dev4
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.0dev2.zip
7
+ Download-URL: https://github.com/maciejk1984/boneIO/archive/0.1.0dev4.zip
8
8
  Platform: UNKNOWN
9
9
  Classifier: Programming Language :: Python
10
10
  Classifier: Programming Language :: Python :: 3.7
@@ -0,0 +1,25 @@
1
+ README.md
2
+ setup.py
3
+ boneio/__init__.py
4
+ boneio/bonecli.py
5
+ boneio/const.py
6
+ boneio/manager.py
7
+ boneio/mqtt_client.py
8
+ boneio/oled.py
9
+ boneio/version.py
10
+ boneio.egg-info/PKG-INFO
11
+ boneio.egg-info/SOURCES.txt
12
+ boneio.egg-info/dependency_links.txt
13
+ boneio.egg-info/entry_points.txt
14
+ boneio.egg-info/requires.txt
15
+ boneio.egg-info/top_level.txt
16
+ boneio/helper/__init__.py
17
+ boneio/helper/gpio.py
18
+ boneio/helper/stats.py
19
+ boneio/helper/yaml.py
20
+ boneio/input/__init__.py
21
+ boneio/input/gpio.py
22
+ boneio/relay/__init__.py
23
+ boneio/relay/basic.py
24
+ boneio/relay/gpio.py
25
+ boneio/relay/mcp.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ boneio = boneio.bonecli:cli
3
+
@@ -0,0 +1 @@
1
+ boneio
@@ -2,11 +2,11 @@
2
2
  # -*- coding:utf-8 -*-
3
3
  from setuptools import setup, find_packages
4
4
 
5
- with open("bone/version.py") as f:
5
+ with open("boneio/version.py") as f:
6
6
  exec(f.read())
7
7
 
8
8
  setup(
9
- name="boneIO",
9
+ name="boneio",
10
10
  version=__version__, # type: ignore # noqa: F821,
11
11
  description="Python Helper for BoneIO",
12
12
  long_description="Python Helper for BoneIO",
@@ -43,5 +43,5 @@ setup(
43
43
  "pyaml==21.10.1",
44
44
  "typing-extensions==3.10.0.2",
45
45
  ],
46
- entry_points={"console_scripts": ["boneio=bone.bonecli:cli"]},
46
+ entry_points={"console_scripts": ["boneio=boneio.bonecli:cli"]},
47
47
  )
@@ -1,8 +0,0 @@
1
- README.md
2
- setup.py
3
- boneIO.egg-info/PKG-INFO
4
- boneIO.egg-info/SOURCES.txt
5
- boneIO.egg-info/dependency_links.txt
6
- boneIO.egg-info/entry_points.txt
7
- boneIO.egg-info/requires.txt
8
- boneIO.egg-info/top_level.txt
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- boneio = bone.bonecli:cli
3
-
File without changes
File without changes