difonlib 0.2.2__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.
- difonlib/__init__.py +0 -0
- difonlib/bt_utils.py +553 -0
- difonlib/input_devs.py +209 -0
- difonlib/ng_lib.py +151 -0
- difonlib/py.typed +0 -0
- difonlib/remctrl.py +157 -0
- difonlib/tuya_devs.py +196 -0
- difonlib/utils.py +200 -0
- difonlib-0.2.2.dist-info/METADATA +17 -0
- difonlib-0.2.2.dist-info/RECORD +12 -0
- difonlib-0.2.2.dist-info/WHEEL +5 -0
- difonlib-0.2.2.dist-info/top_level.txt +1 -0
difonlib/tuya_devs.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
# 24.07.25
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional, Dict, cast, List
|
|
6
|
+
import xmltodict # pip install xmltodict
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import tinytuya
|
|
11
|
+
from tinytuya import Contrib
|
|
12
|
+
|
|
13
|
+
# from typing import Dict, Optional, cast, Any, Callable, Type, LiteralString
|
|
14
|
+
# import ctypes
|
|
15
|
+
|
|
16
|
+
### export PYTHONPATH=$HOME/tips/devel/python/pymylib
|
|
17
|
+
from difonlib.utils import print_dicts
|
|
18
|
+
|
|
19
|
+
cur_dir = os.path.dirname(os.path.realpath(__file__))
|
|
20
|
+
|
|
21
|
+
# {'@name': 's_home_data23007434', '#text': '{"deviceRespBeen":[{"activeTime":1752406424,"devId":"bf36796bdace7a62fav1ys","displayOrder":0,"dpMaxTime":1752406939811,"dpName":{},"dps":{"1":true,"17":0,"18":0,"19":0,"20":2328,"21":1,"22":578,"23":29500,"24":15873,"25":2620,"26":0,"38":"memory","39":false,"40":"relay","41":false,"42":"","43":"","44":"","9":0},"errorCode":0,"iconUrl":"https://images.tuyaeu.com/smart/program_category_icon/cz.png","isShare":false,"key":"bf36796bdace7a62fav1ys","lat":"32.094","localKey":"lXeI5[a5-~F}MQAi","lon":"34.8863","moduleMap":{"mcu":{"cadv":"","isOnline":true,"verSw":"1.1.8"},"wifi":{"bv":"40.00","cadv":"1.0.3","isOnline":true,"pv":"2.2","verSw":"1.1.8"}},"name":"Outlet-20A-2","productId":"qm0iq4nqnrlzh4qc","resptime":0,"runtimeEnv":"prod","timezoneId":"Asia/Jerusalem","uuid":"6b789696c51d698a","virtual":false},
|
|
22
|
+
|
|
23
|
+
tuya_xml_data_file = Path(
|
|
24
|
+
os.path.join(
|
|
25
|
+
cur_dir,
|
|
26
|
+
"./docker-android/shared_prefs/preferences_global_keyeu1609443890901OWMrJ.xml",
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TuyaDevs:
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
xml_file: Path = tuya_xml_data_file,
|
|
35
|
+
file_scan_result: str = "snapshot.json",
|
|
36
|
+
):
|
|
37
|
+
self.fscan_result = file_scan_result
|
|
38
|
+
self.xml_file = xml_file
|
|
39
|
+
self.devs_cfg = self.load_cfg()
|
|
40
|
+
|
|
41
|
+
def load_cfg(self) -> List[Dict[str, Any]]:
|
|
42
|
+
"""Return list of tuya devices as list of dictionaries"""
|
|
43
|
+
with open(self.xml_file) as fxml:
|
|
44
|
+
data_dict = xmltodict.parse(fxml.read())
|
|
45
|
+
txt = data_dict["map"]["string"][3]["#text"]
|
|
46
|
+
devices = json.loads(txt)["deviceRespBeen"]
|
|
47
|
+
return cast(List[Dict[str, Any]], devices)
|
|
48
|
+
|
|
49
|
+
def get_localkey(self, id: str) -> Optional[str]:
|
|
50
|
+
"""Get localKey by device ID (key)"""
|
|
51
|
+
for dev in self.devs_cfg:
|
|
52
|
+
# print_dicts(dev)
|
|
53
|
+
if not dev["localKey"]:
|
|
54
|
+
continue
|
|
55
|
+
if dev["key"] == id:
|
|
56
|
+
return cast(str, dev["localKey"])
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def get_dev(self, id: str) -> Optional[Dict[str, Any]]:
|
|
60
|
+
"""Return device config by ID, or None if not found."""
|
|
61
|
+
for dev in self.devs_cfg:
|
|
62
|
+
if dev["key"] == id:
|
|
63
|
+
return dev
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
# def _scan(self, force_update=False) -> Optional[Dict[str, str]]:
|
|
67
|
+
def _scan(self, force_update: bool = False) -> dict:
|
|
68
|
+
"""Get last list of connected devices
|
|
69
|
+
If force_update=True - Get connected devives for now
|
|
70
|
+
"""
|
|
71
|
+
if not os.path.isfile(self.fscan_result) or force_update:
|
|
72
|
+
tinytuya.scan()
|
|
73
|
+
with open(self.fscan_result) as f:
|
|
74
|
+
scan_data: dict = json.load(f)
|
|
75
|
+
return cast(dict, scan_data["devices"])
|
|
76
|
+
|
|
77
|
+
def connected_devs(self, force_update: bool = False) -> list:
|
|
78
|
+
"""
|
|
79
|
+
If force_update=True - get connected devives for now
|
|
80
|
+
if not then last scan of connected devices from self.fscan_result
|
|
81
|
+
"""
|
|
82
|
+
con_devs = []
|
|
83
|
+
connected_devices = self._scan(force_update)
|
|
84
|
+
for con_dev in connected_devices:
|
|
85
|
+
dev_id = con_dev["id"]
|
|
86
|
+
dev_ip = con_dev["ip"]
|
|
87
|
+
dev_descript = self.get_dev(dev_id)
|
|
88
|
+
dev_name = None
|
|
89
|
+
dev_lk = None
|
|
90
|
+
if dev_descript:
|
|
91
|
+
dev_name = dev_descript["name"]
|
|
92
|
+
dev_lk = dev_descript["localKey"]
|
|
93
|
+
con_devs += [{"id": dev_id, "ip": dev_ip, "name": dev_name, "localkey": dev_lk}]
|
|
94
|
+
return con_devs
|
|
95
|
+
|
|
96
|
+
def all_devs(self) -> list:
|
|
97
|
+
"""
|
|
98
|
+
If force_update=True - get connected devives for now
|
|
99
|
+
if not then last scan of connected devices from self.fscan_result
|
|
100
|
+
"""
|
|
101
|
+
all_devs = []
|
|
102
|
+
for con_dev in self.devs_cfg:
|
|
103
|
+
dev_id = con_dev["devId"]
|
|
104
|
+
dev_descript = self.get_dev(dev_id)
|
|
105
|
+
dev_name = None
|
|
106
|
+
dev_lk = None
|
|
107
|
+
if dev_descript:
|
|
108
|
+
dev_name = dev_descript["name"]
|
|
109
|
+
dev_lk = dev_descript["localKey"]
|
|
110
|
+
all_devs += [{"id": dev_id, "name": dev_name, "localkey": dev_lk}]
|
|
111
|
+
return all_devs
|
|
112
|
+
|
|
113
|
+
def ir_connect_to_dev(
|
|
114
|
+
self, dev_id: str, local_key: str
|
|
115
|
+
) -> Optional[Contrib.IRRemoteControlDevice]:
|
|
116
|
+
try:
|
|
117
|
+
ir_dev = Contrib.IRRemoteControlDevice(
|
|
118
|
+
dev_id=dev_id,
|
|
119
|
+
# address = '192.168.0.89', #- no must
|
|
120
|
+
local_key=local_key,
|
|
121
|
+
persist=True,
|
|
122
|
+
connection_timeout=5,
|
|
123
|
+
)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f" =!= ir_connect_to_dev(): {e}")
|
|
126
|
+
return None
|
|
127
|
+
return ir_dev
|
|
128
|
+
|
|
129
|
+
# learn a new IR button key
|
|
130
|
+
def ir_receive_button(
|
|
131
|
+
self, ir_dev: Optional[Contrib.IRRemoteControlDevice], timeout: int = 20
|
|
132
|
+
) -> Optional[str]:
|
|
133
|
+
if not ir_dev:
|
|
134
|
+
print(f" =!= IR device is not connected! ir_dev:{ir_dev}")
|
|
135
|
+
return None
|
|
136
|
+
print("Press button on your remote control")
|
|
137
|
+
button = ir_dev.receive_button(timeout=timeout)
|
|
138
|
+
if isinstance(button, str):
|
|
139
|
+
return button
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
dbg = print
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
|
|
146
|
+
# devs_list = tuya_xml_cfg_get_data(tuya_xml_data_file)
|
|
147
|
+
|
|
148
|
+
# from pymylib import print_dicts
|
|
149
|
+
|
|
150
|
+
# for i, dev in enumerate(devs_list, start=1):
|
|
151
|
+
# # print(f" {i}) {dev}")
|
|
152
|
+
# print(f"----------------------- {i} --------------------------")
|
|
153
|
+
# print_dicts(dev)
|
|
154
|
+
# print(f"-----------------------------------------------------")
|
|
155
|
+
|
|
156
|
+
# to = TuyaDevsData(xml_file=tuya_xml_data_file)
|
|
157
|
+
td = TuyaDevs(xml_file=tuya_xml_data_file)
|
|
158
|
+
|
|
159
|
+
for i, _dev in enumerate(td.devs_cfg, start=1):
|
|
160
|
+
# print(f" {i}) {dev}")
|
|
161
|
+
print(f"----------------------- {i} --------------------------")
|
|
162
|
+
print_dicts(_dev)
|
|
163
|
+
print("-----------------------------------------------------")
|
|
164
|
+
|
|
165
|
+
dev_id = "bf04409288bdad3dd5dx35"
|
|
166
|
+
|
|
167
|
+
dev = td.get_dev(dev_id)
|
|
168
|
+
dbg(f"dev: {dev}") # //Dima
|
|
169
|
+
|
|
170
|
+
localkey = td.get_localkey(dev_id)
|
|
171
|
+
dbg(f"dev_id: {dev_id}; localkey: {localkey}") # //Dima
|
|
172
|
+
|
|
173
|
+
dev_id = "bf54140ad95255549f5d2h"
|
|
174
|
+
localkey = td.get_localkey(dev_id)
|
|
175
|
+
dbg(f"dev_id: {dev_id}; localkey: {localkey}") # //Dima
|
|
176
|
+
|
|
177
|
+
all_devs = td.all_devs()
|
|
178
|
+
for i, dev in enumerate(all_devs, start=1):
|
|
179
|
+
# print(f" {i}) {dev}")
|
|
180
|
+
print(f"----------------------- {i} --------------------------")
|
|
181
|
+
print_dicts(dev)
|
|
182
|
+
print("-----------------------------------------------------")
|
|
183
|
+
|
|
184
|
+
con_devs = td.connected_devs()
|
|
185
|
+
for i, dev in enumerate(con_devs, start=1):
|
|
186
|
+
# print(f" {i}) {dev}")
|
|
187
|
+
print(f"----------------------- {i} --------------------------")
|
|
188
|
+
print_dicts(dev)
|
|
189
|
+
print("-----------------------------------------------------")
|
|
190
|
+
|
|
191
|
+
# con_devs = td.tuya_devs(force_update=True)
|
|
192
|
+
# for i, dev in enumerate(con_devs, start=1):
|
|
193
|
+
# # print(f" {i}) {dev}")
|
|
194
|
+
# print(f"----------------------- {i} --------------------------")
|
|
195
|
+
# print_dicts(dev)
|
|
196
|
+
# print(f"-----------------------------------------------------")
|
difonlib/utils.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
import inspect
|
|
4
|
+
import struct
|
|
5
|
+
import os
|
|
6
|
+
import yaml
|
|
7
|
+
import shutil
|
|
8
|
+
import glob
|
|
9
|
+
from typing import Any, Callable, Optional, cast, List
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Text print style
|
|
13
|
+
RESET = 0
|
|
14
|
+
BOLD = 1
|
|
15
|
+
DARKEN = 2
|
|
16
|
+
ITALIC = 3
|
|
17
|
+
UNDERLINE = 4
|
|
18
|
+
BLINK_SLOW = 5
|
|
19
|
+
BLINK_FAST = 6
|
|
20
|
+
REVERSE = 7
|
|
21
|
+
HIDE = 8
|
|
22
|
+
CROSS_OUT = 9
|
|
23
|
+
|
|
24
|
+
# alias color_table2='bash -c "for (( i=1; i<256; i++ )); do tput setaf \$i; echo -n [\$i]; done; tput sgr0; echo"'
|
|
25
|
+
|
|
26
|
+
COLOR_OFF = "\x1b[0m"
|
|
27
|
+
|
|
28
|
+
# ---
|
|
29
|
+
RED = 160
|
|
30
|
+
GOLD = 222
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# MSG_COLOR = f"\x1b[{BOLD};38;5;{RED}m"
|
|
34
|
+
MSG_COLOR = f"\x1b[{RESET};38;5;{45}m"
|
|
35
|
+
# CRITICAL 50
|
|
36
|
+
# ERROR 40
|
|
37
|
+
# WARNING 30
|
|
38
|
+
# INFO 20
|
|
39
|
+
# DEBUG 10
|
|
40
|
+
# NOTSET 0
|
|
41
|
+
|
|
42
|
+
logging.basicConfig(
|
|
43
|
+
format=f"{MSG_COLOR}[%(filename)s:%(lineno)d]: %(message)s{COLOR_OFF}",
|
|
44
|
+
level=logging.DEBUG,
|
|
45
|
+
)
|
|
46
|
+
logdbg = logging.debug
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class UtilsError(Exception):
|
|
50
|
+
def __init__(self, message: str) -> None:
|
|
51
|
+
super().__init__(message)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def __LINE__() -> int:
|
|
55
|
+
return inspect.stack()[1][2]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_mac_address(mac: str) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Validates a MAC address string.
|
|
61
|
+
Acceptable formats: XX:XX:XX:XX:XX:XX (case-insensitive)
|
|
62
|
+
"""
|
|
63
|
+
mac_regex = re.compile(r"^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$")
|
|
64
|
+
return bool(mac_regex.match(mac))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def is_mac_address2(mac: str) -> bool:
|
|
68
|
+
"""
|
|
69
|
+
Validates a MAC address string.
|
|
70
|
+
Acceptable formats: XXXXXXXXXXXX (case-insensitive)
|
|
71
|
+
"""
|
|
72
|
+
mac_regex = re.compile(r"^([0-9A-Fa-f]{2}){6}$")
|
|
73
|
+
return bool(mac_regex.match(mac))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def mac_format(mac: str) -> str:
|
|
77
|
+
"""112233445566 -> 11:22:33:44:55:66"""
|
|
78
|
+
_mac = re.findall("[0-9A-Fa-f]{2}", mac)
|
|
79
|
+
if len(mac) != 12 or len(_mac) != 6:
|
|
80
|
+
raise UtilsError(f"Invalid MAC address: {mac}")
|
|
81
|
+
return ":".join(_mac)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def to_signed(number: int, bits: int = 32) -> int:
|
|
85
|
+
mask: int = (2**bits) - 1
|
|
86
|
+
if number & (1 << (bits - 1)):
|
|
87
|
+
return number | ~mask
|
|
88
|
+
else:
|
|
89
|
+
return number & mask
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def swap32(i: int) -> int:
|
|
93
|
+
return cast(int, struct.unpack("<I", struct.pack(">I", i))[0])
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def swap16(i: int) -> int:
|
|
97
|
+
return int(struct.unpack("<H", struct.pack(">H", i))[0])
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def fs_remove_dir_content(dir_path: str) -> None:
|
|
101
|
+
shutil.rmtree(dir_path)
|
|
102
|
+
os.mkdir(dir_path)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def file_get_latest(path: str, fpatern: str) -> Optional[str]:
|
|
106
|
+
files = glob.glob(os.path.join(path, fpatern)) # * means all if need specific format then *.csv
|
|
107
|
+
if not files:
|
|
108
|
+
return None
|
|
109
|
+
latest_file = max(files, key=os.path.getmtime) # modify time
|
|
110
|
+
return latest_file
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def print_lists(lst: list, shift: str = " ") -> None:
|
|
114
|
+
for v in lst:
|
|
115
|
+
if isinstance(v, list):
|
|
116
|
+
print_lists(v, shift=shift + " ")
|
|
117
|
+
else:
|
|
118
|
+
print(shift, v)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# import configparser
|
|
122
|
+
def print_dicts(dic: dict, shift: str = " ") -> None:
|
|
123
|
+
# Get max length of key line
|
|
124
|
+
klen = 0
|
|
125
|
+
for k in dic.keys():
|
|
126
|
+
kl = len(str(k))
|
|
127
|
+
if kl > klen:
|
|
128
|
+
klen = kl
|
|
129
|
+
for k, v in dic.items():
|
|
130
|
+
# print(f"**v: {type(v)}")
|
|
131
|
+
# if isinstance(v, (dict, configparser.SectionProxy)):
|
|
132
|
+
if isinstance(v, (dict)):
|
|
133
|
+
print(shift, k, ":")
|
|
134
|
+
print_dicts(v, shift=shift + " ")
|
|
135
|
+
else:
|
|
136
|
+
print(shift, f"{k:{klen}} : {v}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def print_dicts_list(dicts_list: List[dict]) -> None:
|
|
140
|
+
for d in dicts_list:
|
|
141
|
+
print_dicts(d)
|
|
142
|
+
print("------------------------")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def print_ctype_fields(
|
|
146
|
+
ctype_struct: Any,
|
|
147
|
+
indent: str = " ",
|
|
148
|
+
show_hex: bool = False,
|
|
149
|
+
logger: Callable[[str], None] = print,
|
|
150
|
+
) -> None:
|
|
151
|
+
alen = 0
|
|
152
|
+
for field_name, field_type in ctype_struct._fields_:
|
|
153
|
+
a = len(str(field_name))
|
|
154
|
+
if a > alen:
|
|
155
|
+
alen = a
|
|
156
|
+
for field_name, field_type in ctype_struct._fields_:
|
|
157
|
+
try:
|
|
158
|
+
if show_hex:
|
|
159
|
+
logger(f"{indent}{field_name:{alen}}: 0x{getattr(ctype_struct, field_name):X}")
|
|
160
|
+
else:
|
|
161
|
+
logger(f"{indent}{field_name:{alen}}: {getattr(ctype_struct, field_name):d}")
|
|
162
|
+
except Exception:
|
|
163
|
+
logger(f"{indent}{field_name}:")
|
|
164
|
+
print_ctype_fields(
|
|
165
|
+
getattr(ctype_struct, field_name),
|
|
166
|
+
indent=(indent + " "),
|
|
167
|
+
show_hex=show_hex,
|
|
168
|
+
logger=logger,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class YamlConfig:
|
|
173
|
+
def __init__(self, cfg_path: str):
|
|
174
|
+
self.config_path = Path(cfg_path)
|
|
175
|
+
self.config: dict = {}
|
|
176
|
+
self.load()
|
|
177
|
+
|
|
178
|
+
def clear(self) -> None:
|
|
179
|
+
self.config = {}
|
|
180
|
+
self.save()
|
|
181
|
+
|
|
182
|
+
def load(self) -> None:
|
|
183
|
+
if not self.config_path.exists():
|
|
184
|
+
self.save()
|
|
185
|
+
with self.config_path.open() as f:
|
|
186
|
+
self.config = yaml.safe_load(f) or {}
|
|
187
|
+
|
|
188
|
+
def save(self) -> None:
|
|
189
|
+
with self.config_path.open("w") as f:
|
|
190
|
+
yaml.safe_dump(
|
|
191
|
+
self.config,
|
|
192
|
+
f,
|
|
193
|
+
sort_keys=False,
|
|
194
|
+
allow_unicode=True,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
logdbg("Hello 0123456789 ABCDEF")
|
|
200
|
+
cfg = YamlConfig("./___CONFIG.yaml")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: difonlib
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: python libraries
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: evdev>=1.9.2
|
|
8
|
+
Requires-Dist: nicegui>=3.2.0
|
|
9
|
+
Requires-Dist: pexpect>=4.9.0
|
|
10
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
11
|
+
Requires-Dist: tinytuya>=1.17.4
|
|
12
|
+
Requires-Dist: types-pexpect>=4.9.0.20250916
|
|
13
|
+
Requires-Dist: types-PyYAML>=6.0.12.20250915
|
|
14
|
+
Requires-Dist: types-xmltodict>=1.0.1.20250920
|
|
15
|
+
Requires-Dist: xmltodict>=1.0.2
|
|
16
|
+
|
|
17
|
+
# Python libraries
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
difonlib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
difonlib/bt_utils.py,sha256=Cv5wOhkT4s5-NCzSCtCjwh4qjKo_Blrt2ED0jHcsDEs,16949
|
|
3
|
+
difonlib/input_devs.py,sha256=zi3hvHAMV-NZSWkbw9EGQSGz-MXULkvH3koLDNU0xo8,6424
|
|
4
|
+
difonlib/ng_lib.py,sha256=mEaJ86fwU1mzoEIJnh5qbsi_uITtD0YwwqvWakcDbik,6077
|
|
5
|
+
difonlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
difonlib/remctrl.py,sha256=KfhePrdFR9eSUlUahVAGQTh4tL2qnws0BgU4epLUA0o,5228
|
|
7
|
+
difonlib/tuya_devs.py,sha256=qTehh2OjJy57JI2TgfcDaRHWCb8hjZw2fDw6og3dtlU,7530
|
|
8
|
+
difonlib/utils.py,sha256=gTaXeuTJfFAX4WW9d13DDCdM05V1SYHfNM2w67gu0fk,4992
|
|
9
|
+
difonlib-0.2.2.dist-info/METADATA,sha256=yjMWhu07P-fnmjxlBPj18-ersFGi6TWwXo7yFhbmMqw,480
|
|
10
|
+
difonlib-0.2.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
11
|
+
difonlib-0.2.2.dist-info/top_level.txt,sha256=FDlpwQactDYUPor1ivSHo93-p1QQo-cYHNnXxCs_ECE,9
|
|
12
|
+
difonlib-0.2.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
difonlib
|