pykoplenti 1.0.0__py3-none-any.whl → 1.2.1__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.

Potentially problematic release.


This version of pykoplenti might be problematic. Click here for more details.

kostal/plenticore/cli.py DELETED
@@ -1,352 +0,0 @@
1
- import asyncio
2
- import os
3
- from pprint import pprint
4
- import re
5
- import tempfile
6
- import traceback
7
- from ast import literal_eval
8
- from collections import defaultdict
9
- from inspect import iscoroutinefunction
10
- from typing import Callable
11
-
12
- import click
13
- from aiohttp import ClientSession, ClientTimeout
14
- from prompt_toolkit import PromptSession, print_formatted_text
15
-
16
- from kostal import PlenticoreApiClient
17
-
18
-
19
- class SessionCache:
20
- """Persistent the session in a temporary file."""
21
- def __init__(self, host):
22
- self.host = host
23
-
24
- def read_session_id(self) -> str:
25
- file = os.path.join(tempfile.gettempdir(),
26
- f'plenticore-session-{self.host}')
27
- if os.path.isfile(file):
28
- with open(file, 'rt') as f:
29
- return f.readline(256)
30
- else:
31
- return None
32
-
33
- def write_session_id(self, id: str):
34
- file = os.path.join(tempfile.gettempdir(),
35
- f'plenticore-session-{self.host}')
36
- f = os.open(file, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode=0o600)
37
- try:
38
- os.write(f, id.encode('ascii'))
39
- finally:
40
- os.close(f)
41
-
42
-
43
- class PlenticoreShell:
44
- """Provides a shell-like access to the plenticore client."""
45
- def __init__(self, client: PlenticoreApiClient):
46
- super().__init__()
47
- self.client = client
48
- self._session_cache = SessionCache(self.client.host)
49
-
50
- async def prepare_client(self, passwd):
51
- # first try to reuse existing session
52
- session_id = self._session_cache.read_session_id()
53
- if session_id is not None:
54
- self.client.session_id = session_id
55
- print_formatted_text('Trying to reuse existing session... ',
56
- end=None)
57
- me = await self.client.get_me()
58
- if me.is_authenticated:
59
- print_formatted_text('Success')
60
- return
61
-
62
- print_formatted_text('Failed')
63
-
64
- if passwd is not None:
65
- print_formatted_text('Logging in... ', end=None)
66
- await self.client.login(passwd)
67
- self._session_cache.write_session_id(self.client.session_id)
68
- print_formatted_text('Success')
69
-
70
- def print_exception(self):
71
- """Prints an excpetion from executing a method."""
72
- print_formatted_text(traceback.format_exc())
73
-
74
- async def run(self, passwd):
75
- session = PromptSession()
76
- print_formatted_text(flush=True) # Initialize output
77
-
78
- # Test commands:
79
- # get_settings
80
- # get_setting_values 'devices:local' 'Battery:MinSoc'
81
- # get_setting_values 'devices:local' ['Battery:MinSoc','Battery:MinHomeComsumption']
82
- # get_setting_values 'scb:time'
83
- # set_setting_values 'devices:local' {'Battery:MinSoc':'15'}
84
-
85
- await self.prepare_client(passwd)
86
-
87
- while True:
88
- try:
89
- text = await session.prompt_async('(plenticore)> ')
90
-
91
- if text.strip().lower() == 'exit':
92
- raise EOFError()
93
-
94
- if text.strip() == '':
95
- continue
96
- else:
97
- # TODO split does not know about lists or dicts or strings with spaces
98
- method_name, *arg_values = text.strip().split()
99
-
100
- if method_name == 'help':
101
- if len(arg_values) == 0:
102
- print_formatted_text("Try: help <command>")
103
- else:
104
- method = getattr(self.client, arg_values[0])
105
- print_formatted_text(method.__doc__)
106
- continue
107
-
108
- try:
109
- method = getattr(self.client, method_name)
110
- except AttributeError:
111
- print_formatted_text(f'Unknown method: {method_name}')
112
- continue
113
-
114
- try:
115
- args = list([literal_eval(x) for x in arg_values])
116
- except:
117
- print_formatted_text('Error parsing arguments')
118
- self.print_exception()
119
- continue
120
-
121
- try:
122
- if iscoroutinefunction(method):
123
- result = await method(*args)
124
- else:
125
- result = method(*args)
126
- except:
127
- print_formatted_text('Error executing method')
128
- self.print_exception()
129
- continue
130
-
131
- pprint(result)
132
-
133
- except KeyboardInterrupt:
134
- continue
135
- except EOFError:
136
- break
137
-
138
-
139
- async def repl_main(host, port, passwd):
140
- async with ClientSession(timeout=ClientTimeout(total=10)) as session:
141
- client = PlenticoreApiClient(session, host=host, port=port)
142
-
143
- shell = PlenticoreShell(client)
144
- await shell.run(passwd)
145
-
146
-
147
- async def command_main(host: str, port: int, passwd: str,
148
- fn: Callable[[PlenticoreApiClient], None]):
149
- async with ClientSession(timeout=ClientTimeout(total=10)) as session:
150
- client = PlenticoreApiClient(session, host=host, port=port)
151
- session_cache = SessionCache(host)
152
-
153
- # Try to reuse an existing session
154
- client.session_id = session_cache.read_session_id()
155
- me = await client.get_me()
156
- if not me.is_authenticated:
157
- # create a new session
158
- await client.login(passwd)
159
- session_cache.write_session_id(client.session_id)
160
-
161
- await fn(client)
162
-
163
-
164
- class GlobalArgs:
165
- """Global arguments over all sub commands."""
166
- def __init__(self):
167
- self.host = None
168
- self.port = None
169
- self.password = None
170
- self.password_file = None
171
-
172
-
173
- pass_global_args = click.make_pass_decorator(GlobalArgs, ensure=True)
174
-
175
-
176
- @click.group()
177
- @click.option('--host', help='hostname or ip of plenticore inverter')
178
- @click.option('--port', default=80, help='port of plenticore (default 80)')
179
- @click.option('--password', default=None, help='the password')
180
- @click.option(
181
- '--password-file',
182
- default='secrets',
183
- help='password file (default "secrets" in the current working directory)')
184
- @pass_global_args
185
- def cli(global_args, host, port, password, password_file):
186
- """Handling of global arguments with click"""
187
- if password is not None:
188
- global_args.passwd = password
189
- elif os.path.isfile(password_file):
190
- with open(password_file, 'rt') as f:
191
- global_args.passwd = f.readline()
192
- else:
193
- global_args.passwd = None
194
-
195
- global_args.host = host
196
- global_args.port = port
197
-
198
-
199
- @cli.command()
200
- @pass_global_args
201
- def repl(global_args):
202
- """Provides a simple REPL for executing API requests to plenticore inverters."""
203
- asyncio.run(
204
- repl_main(global_args.host, global_args.port, global_args.passwd))
205
-
206
-
207
- @cli.command()
208
- @pass_global_args
209
- def all_processdata(global_args):
210
- """Returns a list of all available process data."""
211
- async def fn(client: PlenticoreApiClient):
212
- data = await client.get_process_data()
213
- for k, v in data.items():
214
- for x in v:
215
- print(f'{k}/{x}')
216
-
217
- asyncio.run(
218
- command_main(global_args.host, global_args.port, global_args.passwd,
219
- fn))
220
-
221
-
222
- @cli.command()
223
- @click.argument('ids', required=True, nargs=-1)
224
- @pass_global_args
225
- def read_processdata(global_args, ids):
226
- """Returns the values of the given process data.
227
-
228
- IDS is the identifier (<module_id>/<processdata_id>) of one or more processdata
229
- to read.
230
-
231
- \b
232
- Examples:
233
- read-processdata devices:local/Inverter:State
234
- """
235
- async def fn(client: PlenticoreApiClient):
236
- if len(ids) == 1 and '/' not in ids[0]:
237
- # all process data ids of a moudle
238
- values = await client.get_process_data_values(ids[0])
239
- else:
240
- query = defaultdict(list)
241
- for id in ids:
242
- m = re.match(r'(?P<module_id>.+)/(?P<processdata_id>.+)', id)
243
- if not m:
244
- raise Exception(f'Invalid format of {id}')
245
-
246
- module_id = m.group('module_id')
247
- setting_id = m.group('processdata_id')
248
-
249
- query[module_id].append(setting_id)
250
-
251
- values = await client.get_process_data_values(query)
252
-
253
- for k, v in values.items():
254
- for x in v:
255
- print(f'{k}/{x.id}={x.value}')
256
-
257
- asyncio.run(
258
- command_main(global_args.host, global_args.port, global_args.passwd,
259
- fn))
260
-
261
-
262
- @cli.command()
263
- @click.option('--rw', is_flag=True, default=False, help='display only writable settings')
264
- @pass_global_args
265
- def all_settings(global_args, rw):
266
- """Returns the ids of all settings."""
267
- async def fn(client: PlenticoreApiClient):
268
- settings = await client.get_settings()
269
- for k, v in settings.items():
270
- for x in v:
271
- if not rw or x.access == 'readwrite':
272
- print(f'{k}/{x.id}')
273
-
274
- asyncio.run(
275
- command_main(global_args.host, global_args.port, global_args.passwd,
276
- fn))
277
-
278
-
279
- @cli.command()
280
- @click.argument('ids', required=True, nargs=-1)
281
- @pass_global_args
282
- def read_settings(global_args, ids):
283
- """Read the value of the given settings.
284
-
285
- IDS is the identifier (<module_id>/<setting_id>) of one or more settings to read
286
-
287
- \b
288
- Examples:
289
- read-settings devices:local/Battery:MinSoc
290
- read-settings devices:local/Battery:MinSoc devices:local/Battery:MinHomeComsumption
291
- """
292
- async def fn(client: PlenticoreApiClient):
293
- query = defaultdict(list)
294
- for id in ids:
295
- m = re.match(r'(?P<module_id>.+)/(?P<setting_id>.+)', id)
296
- if not m:
297
- raise Exception(f'Invalid format of {id}')
298
-
299
- module_id = m.group('module_id')
300
- setting_id = m.group('setting_id')
301
-
302
- query[module_id].append(setting_id)
303
-
304
- values = await client.get_setting_values(query)
305
-
306
- for k, x in values.items():
307
- for i, v in x.items():
308
- print(f'{k}/{i}={v}')
309
-
310
- asyncio.run(
311
- command_main(global_args.host, global_args.port, global_args.passwd,
312
- fn))
313
-
314
-
315
- @cli.command()
316
- @click.argument('id_values', required=True, nargs=-1)
317
- @pass_global_args
318
- def write_settings(global_args, id_values):
319
- """Write the values of the given settings.
320
-
321
- ID_VALUES is the identifier plus the the value to write
322
-
323
- \b
324
- Examples:
325
- write-settings devices:local/Battery:MinSoc=15
326
- """
327
- async def fn(client: PlenticoreApiClient):
328
- query = defaultdict(dict)
329
- for id_value in id_values:
330
- m = re.match(r'(?P<module_id>.+)/(?P<setting_id>.+)=(?P<value>.+)',
331
- id_value)
332
- if not m:
333
- raise Exception(f'Invalid format of {id_value}')
334
-
335
- module_id = m.group('module_id')
336
- setting_id = m.group('setting_id')
337
- value = m.group('value')
338
-
339
- query[module_id][setting_id] = value
340
-
341
- for module_id, setting_values in query.items():
342
- await client.set_setting_values(module_id, setting_values)
343
-
344
- asyncio.run(
345
- command_main(global_args.host, global_args.port, global_args.passwd,
346
- fn))
347
-
348
-
349
- # entry point for pycharm; should not be used for commandline usage
350
- if __name__ == '__main__':
351
- import sys
352
- cli(sys.argv[1:])
@@ -1,11 +0,0 @@
1
- kostal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- kostal/plenticore/__init__.py,sha256=SIjZp--uGq0VIQbrGeei4ZeLQTVaNewoFMbgYMnOZTQ,24186
3
- kostal/plenticore/cli.py,sha256=woo0izeFltTv5TZ9YdPMMUV2jBnV5YN_antSTbjK4iI,11544
4
- pykoplenti/__init__.py,sha256=HjjB5yAnA2GnX9xAI2dD4b9MswoA3vYda5QpJ6AXTpU,27205
5
- pykoplenti/cli.py,sha256=lKAWqUJqu9-R_2wdcZKAk3WzrBio_7rkgj9r5C-un-U,12674
6
- pykoplenti-1.0.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
7
- pykoplenti-1.0.0.dist-info/METADATA,sha256=OgnpHqubJ6bVqcxtSS7NCrRMpYZTVsGXqrTl8VJH_aY,4633
8
- pykoplenti-1.0.0.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
9
- pykoplenti-1.0.0.dist-info/entry_points.txt,sha256=wEHiNCeOALOIPnjw9l0OSvplSDWqYyMqO7QcOU8bRAo,57
10
- pykoplenti-1.0.0.dist-info/top_level.txt,sha256=Bi915FGIFYzCujwn5Kwhu3B-sxElgc7gX3gNaYjl4j8,11
11
- pykoplenti-1.0.0.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- pykoplenti = pykoplenti.cli:cli [cli]
3
-
File without changes