boneIO 0.1.0.dev3__tar.gz → 0.1.0.dev5__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 (34) hide show
  1. boneio-0.1.0.dev5/MANIFEST.in +4 -0
  2. {boneio-0.1.0.dev3 → boneio-0.1.0.dev5}/PKG-INFO +11 -3
  3. boneio-0.1.0.dev5/boneio/__init__.py +11 -0
  4. boneio-0.1.0.dev5/boneio/bonecli.py +143 -0
  5. boneio-0.1.0.dev5/boneio/config.example.yaml +38 -0
  6. boneio-0.1.0.dev5/boneio/config.yaml +20 -0
  7. boneio-0.1.0.dev5/boneio/const.py +55 -0
  8. boneio-0.1.0.dev5/boneio/helper/__init__.py +4 -0
  9. boneio-0.1.0.dev5/boneio/helper/gpio.py +30 -0
  10. boneio-0.1.0.dev5/boneio/helper/stats.py +115 -0
  11. boneio-0.1.0.dev5/boneio/helper/yaml.py +46 -0
  12. boneio-0.1.0.dev5/boneio/input/__init__.py +5 -0
  13. boneio-0.1.0.dev5/boneio/input/gpio.py +147 -0
  14. boneio-0.1.0.dev5/boneio/manager.py +170 -0
  15. boneio-0.1.0.dev5/boneio/mqtt_client.py +183 -0
  16. boneio-0.1.0.dev5/boneio/oled.py +120 -0
  17. boneio-0.1.0.dev5/boneio/relay/__init__.py +5 -0
  18. boneio-0.1.0.dev5/boneio/relay/basic.py +68 -0
  19. boneio-0.1.0.dev5/boneio/relay/gpio.py +42 -0
  20. boneio-0.1.0.dev5/boneio/relay/mcp.py +45 -0
  21. boneio-0.1.0.dev5/boneio/schema.yaml +167 -0
  22. boneio-0.1.0.dev5/boneio/version.py +2 -0
  23. {boneio-0.1.0.dev3 → boneio-0.1.0.dev5}/boneio.egg-info/PKG-INFO +11 -3
  24. boneio-0.1.0.dev5/boneio.egg-info/SOURCES.txt +30 -0
  25. boneio-0.1.0.dev5/boneio.egg-info/top_level.txt +1 -0
  26. boneio-0.1.0.dev5/requirements.txt +45 -0
  27. {boneio-0.1.0.dev3 → boneio-0.1.0.dev5}/setup.py +5 -1
  28. boneio-0.1.0.dev3/boneio.egg-info/SOURCES.txt +0 -8
  29. boneio-0.1.0.dev3/boneio.egg-info/top_level.txt +0 -1
  30. {boneio-0.1.0.dev3 → boneio-0.1.0.dev5}/README.md +0 -0
  31. {boneio-0.1.0.dev3 → boneio-0.1.0.dev5}/boneio.egg-info/dependency_links.txt +0 -0
  32. {boneio-0.1.0.dev3 → boneio-0.1.0.dev5}/boneio.egg-info/entry_points.txt +0 -0
  33. {boneio-0.1.0.dev3 → boneio-0.1.0.dev5}/boneio.egg-info/requires.txt +0 -0
  34. {boneio-0.1.0.dev3 → boneio-0.1.0.dev5}/setup.cfg +0 -0
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include requirements.txt
3
+ include LICENSE
4
+ recursive-include boneio *.yaml
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: boneio
3
- Version: 0.1.0.dev3
3
+ Version: 0.1.0.dev5
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.0dev3.zip
7
+ Download-URL: https://github.com/maciejk1984/boneIO/archive/0.1.0.dev5.zip
8
8
  Platform: UNKNOWN
9
9
  Classifier: Programming Language :: Python
10
10
  Classifier: Programming Language :: Python :: 3.7
@@ -12,5 +12,13 @@ Classifier: Programming Language :: Python :: 3.8
12
12
  Classifier: Programming Language :: Python :: 3.9
13
13
  Description-Content-Type: text/markdown
14
14
 
15
- Python Helper for BoneIO
15
+ Example usage:
16
+ boneio run -dd -c config.yaml
17
+
18
+ Install needed:
19
+
20
+ ```
21
+ sudo apt-get install libopenjp2-7-dev libatlas-base-dev
22
+ ```
23
+
16
24
 
@@ -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,38 @@
1
+ mqtt:
2
+ host: mqtt.hass
3
+ username: mqtt
4
+ password: pass
5
+ topic_prefix: boneiohome
6
+ ha_discovery:
7
+ enabled: yes
8
+
9
+ i2c:
10
+ id: myb
11
+ sda: P9_18
12
+ scl: P9_17
13
+
14
+ oled:
15
+
16
+ mcp23017:
17
+ - id: mcp1
18
+ address: 0x20
19
+
20
+ gpio_input:
21
+ - id: "one"
22
+ pin: P8_12
23
+ - id: "two"
24
+ pin: P8_7
25
+ actions:
26
+ single:
27
+ action: output
28
+ pin: myoutput
29
+
30
+ output: !include output.yaml
31
+
32
+
33
+ ##OUTPUT.YAML
34
+ - id: myoutput
35
+ kind: mcp
36
+ mcp_id: mcp1
37
+ pin: 0
38
+ ha_type: light
@@ -0,0 +1,20 @@
1
+ mqtt:
2
+ host: mqtt.hass
3
+ username: mqtt
4
+ password: pass
5
+ # ha_discovery:
6
+ # enabled: True
7
+
8
+ # i2c:
9
+ # - id: myb
10
+ # sda: "P9_18"
11
+ # scl: "P9_17"
12
+
13
+ gpio_input:
14
+ - id: "one"
15
+ pin: "P8_3"
16
+
17
+ output:
18
+ - id: myinput
19
+ kind: gpio
20
+ pin: P9_17
@@ -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