rpi2home-assistant 2.3.0__py2.py3-none-any.whl
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.
- _raspy2mqtt_version.py +1 -0
- raspy2mqtt/__init__.py +0 -0
- raspy2mqtt/circular_buffer.py +122 -0
- raspy2mqtt/config.py +674 -0
- raspy2mqtt/constants.py +89 -0
- raspy2mqtt/gpio_inputs_handler.py +151 -0
- raspy2mqtt/gpio_outputs_handler.py +288 -0
- raspy2mqtt/homeassistant_status_tracker.py +100 -0
- raspy2mqtt/main.py +276 -0
- raspy2mqtt/optoisolated_inputs_handler.py +255 -0
- raspy2mqtt/stats.py +60 -0
- rpi2home_assistant-2.3.0.dist-info/METADATA +201 -0
- rpi2home_assistant-2.3.0.dist-info/RECORD +16 -0
- rpi2home_assistant-2.3.0.dist-info/WHEEL +5 -0
- rpi2home_assistant-2.3.0.dist-info/entry_points.txt +2 -0
- rpi2home_assistant-2.3.0.dist-info/licenses/LICENSE +28 -0
_raspy2mqtt_version.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
version = "2.3.0"
|
raspy2mqtt/__init__.py
ADDED
File without changes
|
@@ -0,0 +1,122 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
#
|
4
|
+
# Author: fmontorsi
|
5
|
+
# Created: June 2024
|
6
|
+
# License: Apache license
|
7
|
+
#
|
8
|
+
|
9
|
+
|
10
|
+
# =======================================================================================================
|
11
|
+
# CircularBuffer
|
12
|
+
# =======================================================================================================
|
13
|
+
|
14
|
+
|
15
|
+
class CircularBuffer:
|
16
|
+
"""
|
17
|
+
This is a specialized implementation of a circular buffer designed to:
|
18
|
+
* hold boolean/digital samples
|
19
|
+
* hold non-uniformly-distributed samples: each sample has its companion timestamp and
|
20
|
+
there is no fixed sampling frequency assumption
|
21
|
+
* hold in the buffer only CHANGES in value: pushing twice the same value into the buffer
|
22
|
+
(with different timestamps) means the second sample is merged with the first one
|
23
|
+
* allow simple & efficient filtering for "stable" values discarding transient fluctuations
|
24
|
+
if their duration is below a fixed threshold
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self, size: int):
|
28
|
+
assert size > 0
|
29
|
+
self.size = size
|
30
|
+
# buffer is empty at the start
|
31
|
+
# each entry is actually a tuple (TIMESTAMP;VALUE)
|
32
|
+
self.buffer = [(None, None)] * size
|
33
|
+
self.index = 0 # next writable location in the buffer
|
34
|
+
self.last_timestamp = 0
|
35
|
+
|
36
|
+
def push_sample(self, timestamp: int, value: bool) -> None:
|
37
|
+
# timestamp handling
|
38
|
+
if timestamp > self.last_timestamp:
|
39
|
+
assert timestamp > 0
|
40
|
+
self.last_timestamp = timestamp
|
41
|
+
else:
|
42
|
+
# fix invalid timestamp (NTP adjustment?)
|
43
|
+
self.last_timestamp += 1
|
44
|
+
timestamp = self.last_timestamp
|
45
|
+
|
46
|
+
# check if this is a value transition
|
47
|
+
if self.index > 0:
|
48
|
+
last_index = (self.index - 1) % self.size
|
49
|
+
if self.buffer[last_index][1] == value:
|
50
|
+
# not a transition... just discard the new sample -- it brings no information actually
|
51
|
+
# (to be fair: it provides the information that the value is STILL the same... but this
|
52
|
+
# is assumed implicitly to be the case when no new samples are present)
|
53
|
+
return
|
54
|
+
|
55
|
+
self.buffer[self.index % self.size] = (timestamp, value)
|
56
|
+
self.index += 1
|
57
|
+
|
58
|
+
def get_all_samples(self) -> list:
|
59
|
+
if self.index == 0:
|
60
|
+
return None # buffer is empty
|
61
|
+
if self.index <= self.size:
|
62
|
+
return self.buffer[: self.index] # return only valid/populated items
|
63
|
+
idx_mod = self.index % self.size
|
64
|
+
return self.buffer[idx_mod:] + self.buffer[:idx_mod] # linearize the circular buffer
|
65
|
+
|
66
|
+
def get_last_sample(self) -> tuple:
|
67
|
+
return self.get_past_sample(1) # look 1 sample in the past, which means the last pushed sample
|
68
|
+
|
69
|
+
def get_past_sample(self, sample_offset_in_the_past: int) -> tuple:
|
70
|
+
if self.index == 0:
|
71
|
+
return None # buffer is empty
|
72
|
+
if self.index < sample_offset_in_the_past:
|
73
|
+
# the caller is requesting a sample too much in the past -- beyond the buffer memory
|
74
|
+
return None
|
75
|
+
if sample_offset_in_the_past < 1:
|
76
|
+
# sample_offset_in_the_past==0 (or negative) is not a sample in the past
|
77
|
+
return None
|
78
|
+
if sample_offset_in_the_past > self.size:
|
79
|
+
# the caller is trying to explore too much in the past -- beyond the buffer memory
|
80
|
+
return None
|
81
|
+
last_index = (self.index - sample_offset_in_the_past) % self.size
|
82
|
+
return self.buffer[last_index]
|
83
|
+
|
84
|
+
def get_stable_sample(self, now_ts: int, min_stability_sec: float) -> tuple:
|
85
|
+
if self.index == 0:
|
86
|
+
return None # buffer is empty
|
87
|
+
if now_ts < self.last_timestamp:
|
88
|
+
# fix invalid timestamp (NTP adjustment?)
|
89
|
+
now_ts = self.last_timestamp
|
90
|
+
# starting from the last sample search going backwards the first "stable" sample:
|
91
|
+
last_ts = now_ts
|
92
|
+
sample_offset_in_the_past = 1
|
93
|
+
while sample_offset_in_the_past <= self.size:
|
94
|
+
s = self.get_past_sample(sample_offset_in_the_past)
|
95
|
+
if s is None:
|
96
|
+
# trying to dig too much into the past... perhaps the circular buffer is not completely full yet...
|
97
|
+
return None
|
98
|
+
|
99
|
+
sample_age_sec = last_ts - s[0]
|
100
|
+
if sample_age_sec >= min_stability_sec:
|
101
|
+
# debug only:
|
102
|
+
# print(
|
103
|
+
# f"sample_offset_in_the_past={sample_offset_in_the_past} -> sample_age_sec={sample_age_sec} -> STABLE for threshold {min_stability_sec}"
|
104
|
+
# )
|
105
|
+
# found a stable sample!
|
106
|
+
return s
|
107
|
+
|
108
|
+
# debug only:
|
109
|
+
# print(
|
110
|
+
# f"sample_offset_in_the_past={sample_offset_in_the_past} -> sample_age_sec={sample_age_sec} -> UNSTABLE for threshold {min_stability_sec}"
|
111
|
+
# )
|
112
|
+
|
113
|
+
# keep going backward
|
114
|
+
sample_offset_in_the_past += 1
|
115
|
+
last_ts = s[0]
|
116
|
+
|
117
|
+
# the whole buffer has been inspected but all value transitions were shorter than 'min_stability_sec'
|
118
|
+
return None
|
119
|
+
|
120
|
+
def clear(self) -> None:
|
121
|
+
self.buffer = [(None, None)] * self.size
|
122
|
+
self.index = 0
|