pygryfsmart 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Gryf Smart
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ include LICENSE
2
+ include README.md
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.2
2
+ Name: pygryfsmart
3
+ Version: 0.1.0
4
+ Summary: Library for the GryfSmart system
5
+ Home-page: https://github.com/karlowiczpl/pygryfsmart
6
+ Author: @karlowiczpl
7
+ Author-email: k.karlowski@outlook.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ License-File: LICENSE
13
+ Requires-Dist: pyserial
14
+ Requires-Dist: pyserial-asyncio
15
+
16
+ #Gryf smart system lib
17
+
@@ -0,0 +1,2 @@
1
+ #Gryf smart system lib
2
+
@@ -0,0 +1 @@
1
+ """GRYF SMART LIB"""
@@ -0,0 +1,38 @@
1
+ from enum import IntEnum
2
+
3
+ BAUDRATE = 115200
4
+
5
+ class OUTPUT_STATES(IntEnum):
6
+ ON = 1
7
+ OFF = 2
8
+ TOGGLE = 3
9
+
10
+ class SCHUTTER_STATES(IntEnum):
11
+ CLOSE = 1
12
+ OPEN = 2
13
+ STOP = 3
14
+ STEP_MODE = 4
15
+
16
+ class KEY_MODE(IntEnum):
17
+ NO = 0
18
+ NC = 1
19
+
20
+ COMMAND_FUNCTION_IN = "I"
21
+ COMMAND_FUNCTION_OUT = "O"
22
+ COMMAND_FUNCTION_PWM = "LED"
23
+ COMMAND_FUNCTION_COVER = "R"
24
+ COMMAND_FUNCTION_FIND = "AT+FIND"
25
+ COMMAND_FUNCTION_PONG = "PONG"
26
+ COMMAND_FUNCTION_PRESS_SHORT = "PS"
27
+ COMMAND_FUNCTION_PRESS_LONG = "PL"
28
+ COMMAND_FUNCTION_TEMP = "T"
29
+
30
+ COMMAND_FUNCTION_GET_IN_STATE = "AT+StanIN"
31
+ COMMAND_FUNCTION_GET_OUT_STATE = "AT+StanOUT"
32
+ COMMAND_FUNCTION_SET_OUT = "AT+SetOut"
33
+ COMMAND_FUNCTION_SET_COVER = "AT+SetRol"
34
+ COMMAND_FUNCTION_SET_PWM = "AT+SetLED"
35
+ COMMAND_FUNCTION_PING = "PING"
36
+ COMMAND_FUNCTION_SET_PRESS_TIME = "AT+Key"
37
+ COMMADN_FUNCTION_SEARCH_MODULE = "AT+Search"
38
+ COMMAND_FUNCTION_RESET = "AT+RST"
@@ -0,0 +1,164 @@
1
+ from pygryfsmart.feedback import Feedback
2
+ from pygryfsmart.rs232 import RS232Handler
3
+ from pygryfsmart.const import (
4
+ BAUDRATE,
5
+ KEY_MODE,
6
+ OUTPUT_STATES,
7
+ SCHUTTER_STATES,
8
+
9
+ COMMAND_FUNCTION_IN,
10
+ COMMAND_FUNCTION_OUT,
11
+
12
+ COMMAND_FUNCTION_GET_IN_STATE,
13
+ COMMAND_FUNCTION_GET_OUT_STATE,
14
+ COMMAND_FUNCTION_SET_OUT,
15
+ COMMAND_FUNCTION_SET_COVER,
16
+ COMMAND_FUNCTION_SET_PWM,
17
+ COMMAND_FUNCTION_PING,
18
+ COMMAND_FUNCTION_SET_PRESS_TIME,
19
+ COMMADN_FUNCTION_SEARCH_MODULE,
20
+ )
21
+
22
+ import asyncio
23
+ import logging
24
+
25
+
26
+ _LOGGER = logging.getLogger(__name__)
27
+
28
+ class Device(RS232Handler):
29
+ def __init__(self, port , callback = None):
30
+ super().__init__(port, BAUDRATE)
31
+ self.feedback = Feedback(callback=callback)
32
+
33
+ self._update_task = None
34
+ self._connection_task = None
35
+ self._last_ping = 0
36
+
37
+ def set_callback(self , callback):
38
+ self.feedback.callback = callback
39
+
40
+ async def stop_connection(self):
41
+ if self._connection_task:
42
+ self._connection_task.cancel()
43
+ try:
44
+ await self._connection_task
45
+ except asyncio.CancelledError:
46
+ _LOGGER.debug("Connection task was cancelled.")
47
+ if self._update_task:
48
+ self._update_task.cancel()
49
+ try:
50
+ await self._update_task
51
+ except asyncio.CancelledError:
52
+ _LOGGER.debug("Update task was cancelled.")
53
+ await self.close_connection()
54
+ _LOGGER.debug("Connection closed.")
55
+
56
+ async def __connection_task(self):
57
+ try:
58
+ while True:
59
+ line = await super().read_data()
60
+ _LOGGER.debug(f"Received data: {line}")
61
+ await self.feedback.input_data(line)
62
+ except asyncio.CancelledError:
63
+ _LOGGER.info("Connection task cancelled.")
64
+ await self.close_connection()
65
+ raise
66
+ except Exception as e:
67
+ _LOGGER.error(f"Error in connection task: {e}")
68
+ await self.close_connection()
69
+ raise
70
+
71
+ async def start_connection(self):
72
+ await super().open_connection()
73
+ self._connection_task = asyncio.create_task(self.__connection_task())
74
+ _LOGGER.info("Connection task started.")
75
+
76
+ async def send_data(self, data):
77
+ await super().send_data(data)
78
+
79
+ async def set_out(self, id: int, pin: int , state: OUTPUT_STATES | int):
80
+ states = ["0"] * 8 if pin > 6 else ["0"] * 6
81
+ states[pin - 1] = str(state)
82
+
83
+ command = f"{COMMAND_FUNCTION_SET_OUT}={id}," + ",".join(states) + "\n\r"
84
+ await self.send_data(command)
85
+
86
+ async def set_key_time(self , ps_time: int , pl_time: int , id: int , pin: int , type: KEY_MODE | int):
87
+ command = f"{COMMAND_FUNCTION_SET_PRESS_TIME}={id},{pin},{ps_time},{pl_time},{type}\n\r"
88
+ await self.send_data(command)
89
+
90
+ async def search_module(self , id: int):
91
+ if id != 0:
92
+ command = f"{COMMADN_FUNCTION_SEARCH_MODULE}=0,{id}\n\r"
93
+ await self.send_data(command)
94
+
95
+ async def search_modules(self , last_module: int):
96
+ for i in range(last_module):
97
+ command = f"{COMMADN_FUNCTION_SEARCH_MODULE}=0,{i + 1}\n\r"
98
+ await self.send_data(command)
99
+
100
+ async def ping(self , module_id: int):
101
+ command = f"{COMMAND_FUNCTION_PING}={module_id}\n\r"
102
+ await self.send_data(command)
103
+ await asyncio.sleep(0.05)
104
+ if self._last_ping == module_id:
105
+ self._last_ping = 0
106
+ return True
107
+ self._last_ping = 0
108
+ return False
109
+
110
+ async def set_pwm(self , id: int , pin: int , level: int):
111
+ command = f"{COMMAND_FUNCTION_SET_PWM}={id},{pin},{level}\n\r"
112
+ await self.send_data(command)
113
+
114
+ async def set_cover(self , id: int , pin: int , time: int , operation: SCHUTTER_STATES | int):
115
+ if operation in {SCHUTTER_STATES.CLOSE , SCHUTTER_STATES.OPEN , SCHUTTER_STATES.STOP , SCHUTTER_STATES.STEP_MODE} and pin in {1 , 2 , 3 , 4}:
116
+ states = ["0"] * 4
117
+ states[pin - 1] = str(operation)
118
+ control_sum = id + time + int(states[0]) + int(states[1]) + int(states[2]) + int(states[3])
119
+
120
+ command = f"{COMMAND_FUNCTION_SET_COVER}={id},{time},{states[0]},{states[1]},{states[2]},{states[3]},{control_sum}\n\r"
121
+ await self.send_data(command)
122
+ else:
123
+ raise ValueError(f"Argument out of scope: id: {id} , pin: {pin} , time: {time}, operation: {operation}")
124
+
125
+ async def reset(self , module_id: int , update_states: bool):
126
+ if module_id == 0:
127
+ command = "AT+RST=0\n\r"
128
+ await self.send_data(command)
129
+ if update_states == True:
130
+ module_count = len(self.feedback.data[COMMAND_FUNCTION_OUT])
131
+ await asyncio.sleep(2)
132
+ states = self.feedback.data[COMMAND_FUNCTION_OUT]
133
+ for i in range(module_count):
134
+ tabble = list(self.feedback.data[COMMAND_FUNCTION_OUT][i+1].values())
135
+ states = ",".join(map(str, tabble))
136
+ command = f"AT+SetOut={i+1},{states}\n\r"
137
+ await self.send_data(command)
138
+
139
+ def start_update_interval(self, time: int):
140
+ if not self._update_task:
141
+ self._update_task = asyncio.create_task(self.__states_update_interval(time))
142
+ _LOGGER.info("Update interval task started.")
143
+
144
+ async def __states_update_interval(self, time: int):
145
+ try:
146
+ while True:
147
+ module_count = max(len(self.feedback.data[COMMAND_FUNCTION_IN]), len(self.feedback.data[COMMAND_FUNCTION_OUT]))
148
+
149
+ for i in range(module_count):
150
+ try:
151
+ command = f"{COMMAND_FUNCTION_GET_IN_STATE}={i + 1}\n\r"
152
+ await self.send_data(command)
153
+ await asyncio.sleep(0.1)
154
+
155
+ command = f"{COMMAND_FUNCTION_GET_OUT_STATE}={i + 1}\n\r"
156
+ await self.send_data(command)
157
+ except Exception as e:
158
+ _LOGGER.error(f"Error updating module {i + 1}: {e}")
159
+
160
+ await asyncio.sleep(time)
161
+ except asyncio.CancelledError:
162
+ _LOGGER.info("Update interval task cancelled.")
163
+ except Exception as e:
164
+ _LOGGER.error(f"Error in update interval: {e}")
@@ -0,0 +1,130 @@
1
+ import logging
2
+ from datetime import datetime
3
+
4
+ from pygryfsmart.const import (
5
+ COMMAND_FUNCTION_IN,
6
+ COMMAND_FUNCTION_OUT,
7
+ COMMAND_FUNCTION_PWM,
8
+ COMMAND_FUNCTION_COVER,
9
+ COMMAND_FUNCTION_FIND,
10
+ COMMAND_FUNCTION_PONG,
11
+ COMMAND_FUNCTION_PRESS_SHORT,
12
+ COMMAND_FUNCTION_PRESS_LONG,
13
+ COMMAND_FUNCTION_TEMP,
14
+ )
15
+
16
+ _LOGGER = logging.getLogger(__name__)
17
+
18
+ class Feedback:
19
+ def __init__(self , callback=None) -> None:
20
+ self.callback = callback
21
+ self._data = {
22
+ COMMAND_FUNCTION_IN: {},
23
+ COMMAND_FUNCTION_OUT: {},
24
+ COMMAND_FUNCTION_PWM: {},
25
+ COMMAND_FUNCTION_COVER: {},
26
+ COMMAND_FUNCTION_FIND: {},
27
+ COMMAND_FUNCTION_PONG: {},
28
+ COMMAND_FUNCTION_TEMP: {},
29
+ }
30
+
31
+ @property
32
+ def data(self):
33
+ return self._data
34
+
35
+ async def __parse_metod_1(self , parsed_states , line: str , function: str):
36
+ if len(parsed_states) not in {7 , 9}:
37
+ raise ValueError(f"Invalid number of arguments: {line}")
38
+
39
+ for i in range(1, len(parsed_states)):
40
+ if parsed_states[i] not in {"0" , "1"}:
41
+ raise ValueError(f"Wrong parameter value: {line}")
42
+
43
+ pin = int(parsed_states[0])
44
+ if pin not in self._data[function]:
45
+ self._data[function][pin] = {}
46
+ self._data[function][pin][i] = int(parsed_states[i])
47
+
48
+ async def __parse_metod_2(self , parsed_states , line: str , function: str , prefix: int):
49
+ if parsed_states[0] not in {"1" , "2" , "3" , "4" , "5" , "6" , "7" , "8"}:
50
+ raise ValueError(f"Argument out of scope: {line}")
51
+
52
+ pin = int(parsed_states[1])
53
+ id = int(parsed_states[0])
54
+ if id not in self._data[function]:
55
+ self._data[function][id] = {}
56
+ self._data[function][id][pin] = prefix
57
+
58
+ async def __parse_metod_3(self , parsed_states , line: str , function: str):
59
+ if parsed_states[0] not in {"1" , "2" , "3" , "4" , "5" , "6" , "7" , "8"}:
60
+ raise ValueError(f"Argument out of scope: {line}")
61
+
62
+ pin = int(parsed_states[1])
63
+ id = int(parsed_states[0])
64
+ if id not in self._data[function]:
65
+ self._data[function][id] = {}
66
+ self._data[function][id][pin] = parsed_states[2]
67
+
68
+ async def __parse_cover(self , parsed_states , line: str , function: str):
69
+ if len(parsed_states) != 5:
70
+ raise ValueError(f"Invalid number of arguments: {line}")
71
+
72
+ for i in range(1, len(parsed_states)):
73
+ if parsed_states[i] not in {"0" , "1"}:
74
+ raise ValueError(f"Wrong parameter value: {line}")
75
+
76
+ pin = int(parsed_states[0])
77
+ if pin not in self._data[function]:
78
+ self._data[function][pin] = {}
79
+ self._data[function][pin][i] = int(parsed_states[i])
80
+
81
+ async def __parse_temp(self , parsed_states , line: str):
82
+ if parsed_states[0] not in {"1" , "2" , "3" , "4" , "5" , "6" , "7" , "8"}:
83
+ raise ValueError(f"Argument out of scope: {line}")
84
+
85
+ pin = int(parsed_states[1])
86
+ id = int(parsed_states[0])
87
+ if id not in self._data[COMMAND_FUNCTION_TEMP]:
88
+ self._data[COMMAND_FUNCTION_TEMP][id] = {}
89
+ self._data[COMMAND_FUNCTION_TEMP][id][pin] = float(f"{parsed_states[2]}.{parsed_states[3]}")
90
+
91
+ async def __parse_find(self , parsed_states):
92
+ id = int(parsed_states[0])
93
+ self._data[COMMAND_FUNCTION_FIND][id] = float(f"{parsed_states[1]}.{parsed_states[2]}")
94
+
95
+ async def __parse_pong(self , parsed_states):
96
+ now = datetime.now()
97
+
98
+ id = int(parsed_states[0])
99
+ self._data[COMMAND_FUNCTION_PONG][id] = now.strftime("%H:%M")
100
+
101
+
102
+ async def input_data(self , line):
103
+ if line == "??????????":
104
+ return
105
+ try:
106
+ parts = line.split('=')
107
+ parsed_states = parts[1].split(',')
108
+ last_state = parsed_states[-1].split(';')
109
+ parsed_states[-1] = last_state[0]
110
+ _LOGGER.debug(f"{parsed_states}")
111
+
112
+ COMMAND_MAPPER = {
113
+ COMMAND_FUNCTION_IN: lambda states , line : self.__parse_metod_1(states , line , COMMAND_FUNCTION_IN),
114
+ COMMAND_FUNCTION_OUT: lambda states , line : self.__parse_metod_1(states , line , COMMAND_FUNCTION_OUT),
115
+ COMMAND_FUNCTION_PRESS_SHORT: lambda states , line : self.__parse_metod_2(states , line , COMMAND_FUNCTION_IN , 2),
116
+ COMMAND_FUNCTION_PRESS_LONG: lambda states , line : self.__parse_metod_2(states , line , COMMAND_FUNCTION_IN , 3),
117
+ COMMAND_FUNCTION_TEMP: lambda states , line : self.__parse_temp(states , line),
118
+ COMMAND_FUNCTION_PWM: lambda states , line : self.__parse_metod_3(states , line , COMMAND_FUNCTION_PWM),
119
+ COMMAND_FUNCTION_COVER: lambda states , line : self.__parse_cover(states , line , COMMAND_FUNCTION_COVER),
120
+ COMMAND_FUNCTION_FIND: lambda states , line: self.__parse_find(states),
121
+ COMMAND_FUNCTION_PONG: lambda states , line: self.__parse_pong(states),
122
+ }
123
+
124
+ if str(parts[0]).upper() in COMMAND_MAPPER:
125
+ await COMMAND_MAPPER[str(parts[0]).upper()](parsed_states , line)
126
+
127
+ if self.callback:
128
+ await self.callback(self._data)
129
+ except Exception as e:
130
+ _LOGGER.error(f"ERROR parsing data: {e}")
@@ -0,0 +1,55 @@
1
+ import logging
2
+ from serial_asyncio import open_serial_connection
3
+
4
+ _LOGGER = logging.getLogger(__name__)
5
+
6
+ class RS232Handler:
7
+ def __init__(self, port, baudrate):
8
+ self.port = port
9
+ self.baudrate = baudrate
10
+ self.reader = None
11
+ self.writer = None
12
+
13
+ async def open_connection(self):
14
+ try:
15
+ self.reader, self.writer = await open_serial_connection(url=self.port, baudrate=self.baudrate)
16
+ _LOGGER.info(f"Connection opened on port {self.port} with baudrate {self.baudrate}")
17
+ except Exception as e:
18
+ _LOGGER.error(f"Failed to open connection on port {self.port}: {e}")
19
+ self.reader, self.writer = None, None
20
+ raise
21
+
22
+ async def close_connection(self):
23
+ if self.writer:
24
+ try:
25
+ self.writer.close()
26
+ await self.writer.wait_closed()
27
+ _LOGGER.info("Connection closed successfully.")
28
+ except Exception as e:
29
+ _LOGGER.error(f"Error while closing connection: {e}")
30
+ else:
31
+ _LOGGER.warning("Connection was already closed or not initialized.")
32
+
33
+ async def send_data(self, data):
34
+ if self.writer:
35
+ try:
36
+ self.writer.write(data.encode())
37
+ await self.writer.drain()
38
+ _LOGGER.debug(f"Sent data: {data}")
39
+ except Exception as e:
40
+ _LOGGER.error(f"Error while sending data: {e}")
41
+ else:
42
+ _LOGGER.warning("Cannot send data: Writer is not initialized.")
43
+
44
+ async def read_data(self):
45
+ if self.reader:
46
+ try:
47
+ data = await self.reader.readuntil(b"\n")
48
+ _LOGGER.debug(f"Read data: {data.decode().strip()}")
49
+ return data.decode().strip()
50
+ except Exception as e:
51
+ _LOGGER.error(f"Error while reading data: {e}")
52
+ return None
53
+ else:
54
+ _LOGGER.warning("Cannot read data: Reader is not initialized.")
55
+ return None
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.2
2
+ Name: pygryfsmart
3
+ Version: 0.1.0
4
+ Summary: Library for the GryfSmart system
5
+ Home-page: https://github.com/karlowiczpl/pygryfsmart
6
+ Author: @karlowiczpl
7
+ Author-email: k.karlowski@outlook.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ License-File: LICENSE
13
+ Requires-Dist: pyserial
14
+ Requires-Dist: pyserial-asyncio
15
+
16
+ #Gryf smart system lib
17
+
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.cfg
6
+ pygryfsmart/__init__.py
7
+ pygryfsmart/const.py
8
+ pygryfsmart/device.py
9
+ pygryfsmart/feedback.py
10
+ pygryfsmart/rs232.py
11
+ pygryfsmart.egg-info/PKG-INFO
12
+ pygryfsmart.egg-info/SOURCES.txt
13
+ pygryfsmart.egg-info/dependency_links.txt
14
+ pygryfsmart.egg-info/requires.txt
15
+ pygryfsmart.egg-info/top_level.txt
16
+ tests/__init__.py
17
+ tests/test_feedback.py
18
+ tests/test_rs232.py
@@ -0,0 +1,2 @@
1
+ pyserial
2
+ pyserial-asyncio
@@ -0,0 +1,2 @@
1
+ pygryfsmart
2
+ tests
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,24 @@
1
+ [metadata]
2
+ name = pygryfsmart
3
+ version = 0.1.0
4
+ author = @karlowiczpl
5
+ author_email = k.karlowski@outlook.com
6
+ description = Library for the GryfSmart system
7
+ long_description = file: README.md
8
+ url = https://github.com/karlowiczpl/pygryfsmart
9
+ license = MIT
10
+ classifiers =
11
+ Programming Language :: Python :: 3
12
+ Operating System :: OS Independent
13
+
14
+ [options]
15
+ packages = find:
16
+ python_requires = >=3.8
17
+ install_requires =
18
+ pyserial
19
+ pyserial-asyncio
20
+
21
+ [egg_info]
22
+ tag_build =
23
+ tag_date = 0
24
+
File without changes
@@ -0,0 +1,111 @@
1
+ import pytest
2
+ from unittest.mock import AsyncMock
3
+ from pygryfsmart.feedback import Feedback
4
+ from pygryfsmart.const import (
5
+ COMMAND_FUNCTION_IN,
6
+ COMMAND_FUNCTION_OUT,
7
+ COMMAND_FUNCTION_PWM,
8
+ COMMAND_FUNCTION_COVER,
9
+ COMMAND_FUNCTION_FIND,
10
+ COMMAND_FUNCTION_PONG,
11
+ COMMAND_FUNCTION_PRESS_SHORT,
12
+ COMMAND_FUNCTION_PRESS_LONG,
13
+ COMMAND_FUNCTION_TEMP,
14
+ )
15
+
16
+
17
+ @pytest.fixture
18
+ def feedback():
19
+ return Feedback()
20
+
21
+
22
+ @pytest.mark.asyncio
23
+ async def test_parse_metod_1(feedback):
24
+ line = "IN=1,1,0,1,0,1,0,1;"
25
+ parsed_states = ["1", "1", "0", "1", "0", "1", "0", "1" , "1"]
26
+ await feedback._Feedback__parse_metod_1(parsed_states, line, COMMAND_FUNCTION_IN)
27
+
28
+ assert feedback.data[COMMAND_FUNCTION_IN][1] == {1: 1, 2: 0, 3: 1, 4: 0, 5: 1, 6: 0, 7: 1, 8: 1}
29
+
30
+
31
+ @pytest.mark.asyncio
32
+ async def test_parse_metod_2(feedback):
33
+ line = "PS=2,3;"
34
+ parsed_states = ["2", "3"]
35
+ await feedback._Feedback__parse_metod_2(parsed_states, line, COMMAND_FUNCTION_PRESS_SHORT, 2)
36
+
37
+ assert feedback.data[COMMAND_FUNCTION_PRESS_SHORT][2][3] == 2
38
+
39
+
40
+ @pytest.mark.asyncio
41
+ async def test_parse_metod_3(feedback):
42
+ line = "PWM=3,2,255;"
43
+ parsed_states = ["3", "2", "255"]
44
+ await feedback._Feedback__parse_metod_3(parsed_states, line, COMMAND_FUNCTION_PWM)
45
+
46
+ assert feedback.data[COMMAND_FUNCTION_PWM][3][2] == "255"
47
+
48
+
49
+ @pytest.mark.asyncio
50
+ async def test_parse_cover(feedback):
51
+ line = "COVER=4,1,0,1,0;"
52
+ parsed_states = ["4", "1", "0", "1", "0"]
53
+ await feedback._Feedback__parse_cover(parsed_states, line, COMMAND_FUNCTION_COVER)
54
+
55
+ assert feedback.data[COMMAND_FUNCTION_COVER][4] == {1: 1, 2: 0, 3: 1, 4: 0}
56
+
57
+
58
+ @pytest.mark.asyncio
59
+ async def test_parse_temp(feedback):
60
+ line = "TEMP=5,2,25,5;"
61
+ parsed_states = ["5", "2", "25", "5"]
62
+ await feedback._Feedback__parse_temp(parsed_states, line)
63
+
64
+ assert feedback.data[COMMAND_FUNCTION_TEMP][5] == {2: 25.5}
65
+
66
+
67
+ @pytest.mark.asyncio
68
+ async def test_parse_find(feedback):
69
+ parsed_states = ["6", "123", "45"]
70
+ await feedback._Feedback__parse_find(parsed_states)
71
+
72
+ assert feedback.data[COMMAND_FUNCTION_FIND][6] == 123.45
73
+
74
+ @pytest.mark.asyncio
75
+ async def test_input_data_valid(feedback):
76
+ callback = AsyncMock()
77
+ feedback.callback = callback
78
+
79
+ line = "IN=1,1,0,1,0,1,0,1;"
80
+ await feedback.input_data(line)
81
+
82
+ assert feedback.data == {
83
+ COMMAND_FUNCTION_IN: {},
84
+ COMMAND_FUNCTION_OUT: {},
85
+ COMMAND_FUNCTION_PWM: {},
86
+ COMMAND_FUNCTION_COVER: {},
87
+ COMMAND_FUNCTION_FIND: {},
88
+ COMMAND_FUNCTION_PONG: {},
89
+ COMMAND_FUNCTION_TEMP: {},
90
+ COMMAND_FUNCTION_PRESS_SHORT: {},
91
+ COMMAND_FUNCTION_PRESS_LONG: {},
92
+ }
93
+
94
+ @pytest.mark.asyncio
95
+ async def test_input_data_invalid_command(feedback):
96
+ line = "INVALID=1,1,0,1;"
97
+ await feedback.input_data(line)
98
+
99
+ # Brak zmian w danych, ponieważ komenda jest nieznana
100
+ assert feedback.data == {
101
+ COMMAND_FUNCTION_IN: {},
102
+ COMMAND_FUNCTION_OUT: {},
103
+ COMMAND_FUNCTION_PWM: {},
104
+ COMMAND_FUNCTION_COVER: {},
105
+ COMMAND_FUNCTION_FIND: {},
106
+ COMMAND_FUNCTION_PONG: {},
107
+ COMMAND_FUNCTION_TEMP: {},
108
+ COMMAND_FUNCTION_PRESS_SHORT: {},
109
+ COMMAND_FUNCTION_PRESS_LONG: {},
110
+ }
111
+
@@ -0,0 +1,47 @@
1
+ import pytest
2
+ from unittest.mock import AsyncMock, patch
3
+ from pygryfsmart.rs232 import RS232Handler
4
+
5
+ @pytest.fixture
6
+ def rs232_handler():
7
+ return RS232Handler(port="/dev/ttyUSB0", baudrate=115200)
8
+ pytest.mark.asyncio
9
+ @patch("serial_asyncio.open_serial_connection")
10
+ async def test_open_connection(mock_open_serial):
11
+ mock_reader = AsyncMock()
12
+ mock_writer = AsyncMock()
13
+ mock_open_serial.return_value = (mock_reader, mock_writer)
14
+
15
+ rs232_handler = RS232Handler(port="/dev/ttyUSB0", baudrate=115200)
16
+
17
+ await rs232_handler.open_connection()
18
+
19
+ mock_open_serial.assert_called_once_with(url="/dev/ttyUSB0", baudrate=115200)
20
+ mock_writer = AsyncMock()
21
+ rs232_handler.writer = mock_writer
22
+
23
+ await rs232_handler.close_connection()
24
+
25
+ mock_writer.close.assert_called_once()
26
+ await mock_writer.wait_closed()
27
+
28
+ @pytest.mark.asyncio
29
+ async def test_send_data(rs232_handler):
30
+ mock_writer = AsyncMock()
31
+ rs232_handler.writer = mock_writer
32
+
33
+ await rs232_handler.send_data("Test data")
34
+
35
+ mock_writer.write.assert_called_once_with(b"Test data")
36
+ await mock_writer.drain()
37
+
38
+ @pytest.mark.asyncio
39
+ async def test_read_data(rs232_handler):
40
+ mock_reader = AsyncMock()
41
+ mock_reader.readuntil = AsyncMock(return_value=b"Test data\n")
42
+ rs232_handler.reader = mock_reader
43
+
44
+ data = await rs232_handler.read_data()
45
+
46
+ assert data == "Test data"
47
+ mock_reader.readuntil.assert_called_once_with(b"\n")