aboutsummaryrefslogtreecommitdiff
path: root/pyhon/command_loader.py
blob: fd615ec70749c02077ecc6f5bc5e68a81138bc0c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import asyncio
from contextlib import suppress
from copy import copy
from typing import Dict, Any, Optional, TYPE_CHECKING, List

from pyhon.commands import HonCommand
from pyhon.exceptions import NoAuthenticationException
from pyhon.parameter.fixed import HonParameterFixed
from pyhon.parameter.program import HonParameterProgram

if TYPE_CHECKING:
    from pyhon import HonAPI
    from pyhon.appliance import HonAppliance


class HonCommandLoader:
    """Loads and parses hOn command data"""

    def __init__(self, api: "HonAPI", appliance: "HonAppliance") -> None:
        self._api: "HonAPI" = api
        self._appliance: "HonAppliance" = appliance
        self._api_commands: Dict[str, Any] = {}
        self._favourites: List[Dict[str, Any]] = []
        self._command_history: List[Dict[str, Any]] = []
        self._commands: Dict[str, HonCommand] = {}
        self._appliance_data: Dict[str, Any] = {}
        self._additional_data: Dict[str, Any] = {}

    @property
    def api(self) -> "HonAPI":
        """api connection object"""
        if self._api is None:
            raise NoAuthenticationException("Missing hOn login")
        return self._api

    @property
    def appliance(self) -> "HonAppliance":
        """appliance object"""
        return self._appliance

    @property
    def commands(self) -> Dict[str, HonCommand]:
        """Get list of hon commands"""
        return self._commands

    @property
    def appliance_data(self) -> Dict[str, Any]:
        """Get command appliance data"""
        return self._appliance_data

    @property
    def additional_data(self) -> Dict[str, Any]:
        """Get command additional data"""
        return self._additional_data

    async def load_commands(self) -> None:
        """Trigger loading of command data"""
        await self._load_data()
        self._appliance_data = self._api_commands.pop("applianceModel", {})
        self._get_commands()
        self._add_favourites()
        self._recover_last_command_states()

    async def _load_commands(self) -> None:
        self._api_commands = await self._api.load_commands(self._appliance)

    async def _load_favourites(self) -> None:
        self._favourites = await self._api.load_favourites(self._appliance)

    async def _load_command_history(self) -> None:
        self._command_history = await self._api.load_command_history(self._appliance)

    async def _load_data(self) -> None:
        """Callback parallel all relevant data"""
        await asyncio.gather(
            *[
                self._load_commands(),
                self._load_favourites(),
                self._load_command_history(),
            ]
        )

    @staticmethod
    def _is_command(data: Dict[str, Any]) -> bool:
        """Check if dict can be parsed as command"""
        return (
            data.get("description") is not None and data.get("protocolType") is not None
        )

    @staticmethod
    def _clean_name(category: str) -> str:
        """Clean up category name"""
        if "PROGRAM" in category:
            return category.split(".")[-1].lower()
        return category

    def _get_commands(self) -> None:
        """Generates HonCommand dict from api data"""
        commands = []
        for name, data in self._api_commands.items():
            if command := self._parse_command(data, name):
                commands.append(command)
        self._commands = {c.name: c for c in commands}

    def _parse_command(
        self,
        data: Dict[str, Any] | str,
        command_name: str,
        categories: Optional[Dict[str, "HonCommand"]] = None,
        category_name: str = "",
    ) -> Optional[HonCommand]:
        """Try to create HonCommand object"""
        if not isinstance(data, dict):
            self._additional_data[command_name] = data
            return None
        if self._is_command(data):
            return HonCommand(
                command_name,
                data,
                self._appliance,
                category_name=category_name,
                categories=categories,
            )
        if category := self._parse_categories(data, command_name):
            return category
        return None

    def _parse_categories(
        self, data: Dict[str, Any], command_name: str
    ) -> Optional[HonCommand]:
        """Parse categories and create reference to other"""
        categories: Dict[str, HonCommand] = {}
        for category, value in data.items():
            if command := self._parse_command(
                value, command_name, category_name=category, categories=categories
            ):
                categories[self._clean_name(category)] = command
        if categories:
            # setParameters should be at first place
            if "setParameters" in categories:
                return categories["setParameters"]
            return list(categories.values())[0]
        return None

    def _get_last_command_index(self, name: str) -> Optional[int]:
        """Get index of last command execution"""
        return next(
            (
                index
                for (index, d) in enumerate(self._command_history)
                if d.get("command", {}).get("commandName") == name
            ),
            None,
        )

    def _set_last_category(
        self, command: HonCommand, name: str, parameters: Dict[str, Any]
    ) -> HonCommand:
        """Set category to last state"""
        if command.categories:
            if program := parameters.pop("program", None):
                command.category = self._clean_name(program)
            elif category := parameters.pop("category", None):
                command.category = category
            else:
                return command
            return self.commands[name]
        return command

    def _recover_last_command_states(self) -> None:
        """Set commands to last state"""
        for name, command in self.commands.items():
            if (last_index := self._get_last_command_index(name)) is None:
                continue
            last_command = self._command_history[last_index]
            parameters = last_command.get("command", {}).get("parameters", {})
            command = self._set_last_category(command, name, parameters)
            for key, data in command.settings.items():
                if parameters.get(key) is None:
                    continue
                with suppress(ValueError):
                    data.value = parameters.get(key)

    def _add_favourites(self) -> None:
        """Patch program categories with favourites"""
        for favourite in self._favourites:
            name, command_name, base = self._get_favourite_info(favourite)
            if not base:
                continue
            base_command: HonCommand = copy(base)
            self._update_base_command_with_data(base_command, favourite)
            self._update_base_command_with_favourite(base_command)
            self._update_program_categories(command_name, name, base_command)

    def _get_favourite_info(
        self, favourite: Dict[str, Any]
    ) -> tuple[str, str, HonCommand | None]:
        name: str = favourite.get("favouriteName", {})
        command = favourite.get("command", {})
        command_name: str = command.get("commandName", "")
        program_name = self._clean_name(command.get("programName", ""))
        base_command = self.commands[command_name].categories.get(program_name)
        return name, command_name, base_command

    def _update_base_command_with_data(
        self, base_command: HonCommand, command: Dict[str, Any]
    ) -> None:
        for data in command.values():
            if isinstance(data, str):
                continue
            for key, value in data.items():
                if not (parameter := base_command.parameters.get(key)):
                    continue
                with suppress(ValueError):
                    parameter.value = value

    def _update_base_command_with_favourite(self, base_command: HonCommand) -> None:
        extra_param = HonParameterFixed("favourite", {"fixedValue": "1"}, "custom")
        base_command.parameters.update(favourite=extra_param)

    def _update_program_categories(
        self, command_name: str, name: str, base_command: HonCommand
    ) -> None:
        program = base_command.parameters["program"]
        if isinstance(program, HonParameterProgram):
            program.set_value(name)
        self.commands[command_name].categories[name] = base_command