RemoteRF 0.0.1__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.
- remoterf-0.0.1/LICENSE +21 -0
- remoterf-0.0.1/PKG-INFO +16 -0
- remoterf-0.0.1/README.md +0 -0
- remoterf-0.0.1/setup.cfg +4 -0
- remoterf-0.0.1/setup.py +25 -0
- remoterf-0.0.1/src/remoteRF/__init__.py +0 -0
- remoterf-0.0.1/src/remoteRF/common/__init__.py +2 -0
- remoterf-0.0.1/src/remoteRF/common/grpc/__init__.py +1 -0
- remoterf-0.0.1/src/remoteRF/common/grpc/grpc_pb2.py +59 -0
- remoterf-0.0.1/src/remoteRF/common/grpc/grpc_pb2_grpc.py +102 -0
- remoterf-0.0.1/src/remoteRF/common/utils/__init__.py +4 -0
- remoterf-0.0.1/src/remoteRF/common/utils/ansi_codes.py +120 -0
- remoterf-0.0.1/src/remoteRF/common/utils/api_token.py +31 -0
- remoterf-0.0.1/src/remoteRF/common/utils/list_string.py +5 -0
- remoterf-0.0.1/src/remoteRF/common/utils/process_arg.py +80 -0
- remoterf-0.0.1/src/remoteRF/core/__init__.py +2 -0
- remoterf-0.0.1/src/remoteRF/core/acc_login.py +4 -0
- remoterf-0.0.1/src/remoteRF/core/app.py +270 -0
- remoterf-0.0.1/src/remoteRF/core/grpc_acc.py +52 -0
- remoterf-0.0.1/src/remoteRF/core/grpc_client.py +92 -0
- remoterf-0.0.1/src/remoteRF.egg-info/PKG-INFO +16 -0
- remoterf-0.0.1/src/remoteRF.egg-info/SOURCES.txt +24 -0
- remoterf-0.0.1/src/remoteRF.egg-info/dependency_links.txt +1 -0
- remoterf-0.0.1/src/remoteRF.egg-info/entry_points.txt +2 -0
- remoterf-0.0.1/src/remoteRF.egg-info/requires.txt +5 -0
- remoterf-0.0.1/src/remoteRF.egg-info/top_level.txt +1 -0
remoterf-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ethan Ge
|
|
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 THE
|
|
21
|
+
SOFTWARE.
|
remoterf-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: remoteRF
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A python API to remotely access signal centric hardware. Client-side only!
|
|
5
|
+
Author: Ethan Ge
|
|
6
|
+
Author-email: ethoGalaxy@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: grpcio
|
|
13
|
+
Requires-Dist: protobuf
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: prompt_toolkit
|
|
16
|
+
Requires-Dist: python-dotenv
|
remoterf-0.0.1/README.md
ADDED
|
File without changes
|
remoterf-0.0.1/setup.cfg
ADDED
remoterf-0.0.1/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="remoteRF",
|
|
5
|
+
version="0.0.1",
|
|
6
|
+
author="Ethan Ge",
|
|
7
|
+
author_email="ethoGalaxy@gmail.com",
|
|
8
|
+
description="A python API to remotely access signal centric hardware. Client-side only!",
|
|
9
|
+
packages=find_packages(where="src"), # Automatically finds subpackages like core, deviceA, deviceB
|
|
10
|
+
package_dir={"": "src"},
|
|
11
|
+
install_requires=[
|
|
12
|
+
"grpcio", "protobuf", "numpy", "prompt_toolkit", "python-dotenv"
|
|
13
|
+
],
|
|
14
|
+
classifiers=[
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
],
|
|
19
|
+
python_requires='>=3.6',
|
|
20
|
+
entry_points={
|
|
21
|
+
'console_scripts': [
|
|
22
|
+
'remoterf-login=remoteRF.core.acc_login:main',
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import grpc_pb2, grpc_pb2_grpc
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: grpc.proto
|
|
5
|
+
# Protobuf Python Version: 5.27.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
5,
|
|
15
|
+
27,
|
|
16
|
+
1,
|
|
17
|
+
'',
|
|
18
|
+
'grpc.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ngrpc.proto\x12\tremote_rf\"\xa2\x01\n\x11GenericRPCRequest\x12\x15\n\rfunction_name\x18\x01 \x01(\t\x12\x34\n\x04\x61rgs\x18\x02 \x03(\x0b\x32&.remote_rf.GenericRPCRequest.ArgsEntry\x1a@\n\tArgsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x96\x01\n\x12GenericRPCResponse\x12;\n\x07results\x18\x01 \x03(\x0b\x32*.remote_rf.GenericRPCResponse.ResultsEntry\x1a\x43\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x19\n\nArrayShape\x12\x0b\n\x03\x64im\x18\x01 \x03(\x05\"+\n\rComplexNumber\x12\x0c\n\x04real\x18\x01 \x01(\x02\x12\x0c\n\x04imag\x18\x02 \x01(\x02\"a\n\x11\x43omplexNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12&\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x18.remote_rf.ComplexNumber\"D\n\x0eRealNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12\x0c\n\x04\x64\x61ta\x18\x02 \x03(\x02\"\xd7\x01\n\x08\x41rgument\x12\x16\n\x0cstring_value\x18\x01 \x01(\tH\x00\x12\x15\n\x0bint64_value\x18\x02 \x01(\x03H\x00\x12\x15\n\x0b\x66loat_value\x18\x03 \x01(\x02H\x00\x12\x14\n\nbool_value\x18\x04 \x01(\x08H\x00\x12\x35\n\rcomplex_array\x18\x05 \x01(\x0b\x32\x1c.remote_rf.ComplexNumpyArrayH\x00\x12/\n\nreal_array\x18\x06 \x01(\x0b\x32\x19.remote_rf.RealNumpyArrayH\x00\x42\x07\n\x05value2Q\n\nGenericRPC\x12\x43\n\x04\x43\x61ll\x12\x1c.remote_rf.GenericRPCRequest\x1a\x1d.remote_rf.GenericRPCResponseB\x1e\n\x10\x63om.example.demoB\nDemoProtosb\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'grpc_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
_globals['DESCRIPTOR']._loaded_options = None
|
|
34
|
+
_globals['DESCRIPTOR']._serialized_options = b'\n\020com.example.demoB\nDemoProtos'
|
|
35
|
+
_globals['_GENERICRPCREQUEST_ARGSENTRY']._loaded_options = None
|
|
36
|
+
_globals['_GENERICRPCREQUEST_ARGSENTRY']._serialized_options = b'8\001'
|
|
37
|
+
_globals['_GENERICRPCRESPONSE_RESULTSENTRY']._loaded_options = None
|
|
38
|
+
_globals['_GENERICRPCRESPONSE_RESULTSENTRY']._serialized_options = b'8\001'
|
|
39
|
+
_globals['_GENERICRPCREQUEST']._serialized_start=26
|
|
40
|
+
_globals['_GENERICRPCREQUEST']._serialized_end=188
|
|
41
|
+
_globals['_GENERICRPCREQUEST_ARGSENTRY']._serialized_start=124
|
|
42
|
+
_globals['_GENERICRPCREQUEST_ARGSENTRY']._serialized_end=188
|
|
43
|
+
_globals['_GENERICRPCRESPONSE']._serialized_start=191
|
|
44
|
+
_globals['_GENERICRPCRESPONSE']._serialized_end=341
|
|
45
|
+
_globals['_GENERICRPCRESPONSE_RESULTSENTRY']._serialized_start=274
|
|
46
|
+
_globals['_GENERICRPCRESPONSE_RESULTSENTRY']._serialized_end=341
|
|
47
|
+
_globals['_ARRAYSHAPE']._serialized_start=343
|
|
48
|
+
_globals['_ARRAYSHAPE']._serialized_end=368
|
|
49
|
+
_globals['_COMPLEXNUMBER']._serialized_start=370
|
|
50
|
+
_globals['_COMPLEXNUMBER']._serialized_end=413
|
|
51
|
+
_globals['_COMPLEXNUMPYARRAY']._serialized_start=415
|
|
52
|
+
_globals['_COMPLEXNUMPYARRAY']._serialized_end=512
|
|
53
|
+
_globals['_REALNUMPYARRAY']._serialized_start=514
|
|
54
|
+
_globals['_REALNUMPYARRAY']._serialized_end=582
|
|
55
|
+
_globals['_ARGUMENT']._serialized_start=585
|
|
56
|
+
_globals['_ARGUMENT']._serialized_end=800
|
|
57
|
+
_globals['_GENERICRPC']._serialized_start=802
|
|
58
|
+
_globals['_GENERICRPC']._serialized_end=883
|
|
59
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
|
2
|
+
"""Client and server classes corresponding to protobuf-defined services."""
|
|
3
|
+
import grpc
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from . import grpc_pb2 as grpc__pb2
|
|
7
|
+
|
|
8
|
+
GRPC_GENERATED_VERSION = '1.64.1'
|
|
9
|
+
GRPC_VERSION = grpc.__version__
|
|
10
|
+
EXPECTED_ERROR_RELEASE = '1.65.0'
|
|
11
|
+
SCHEDULED_RELEASE_DATE = 'June 25, 2024'
|
|
12
|
+
_version_not_supported = False
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from grpc._utilities import first_version_is_lower
|
|
16
|
+
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
|
17
|
+
except ImportError:
|
|
18
|
+
_version_not_supported = True
|
|
19
|
+
|
|
20
|
+
if _version_not_supported:
|
|
21
|
+
warnings.warn(
|
|
22
|
+
f'The grpc package installed is at version {GRPC_VERSION},'
|
|
23
|
+
+ f' but the generated code in grpc_pb2_grpc.py depends on'
|
|
24
|
+
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
|
25
|
+
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
|
26
|
+
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
|
27
|
+
+ f' This warning will become an error in {EXPECTED_ERROR_RELEASE},'
|
|
28
|
+
+ f' scheduled for release on {SCHEDULED_RELEASE_DATE}.',
|
|
29
|
+
RuntimeWarning
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GenericRPCStub(object):
|
|
34
|
+
"""Missing associated documentation comment in .proto file."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, channel):
|
|
37
|
+
"""Constructor.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
channel: A grpc.Channel.
|
|
41
|
+
"""
|
|
42
|
+
self.Call = channel.unary_unary(
|
|
43
|
+
'/remote_rf.GenericRPC/Call',
|
|
44
|
+
request_serializer=grpc__pb2.GenericRPCRequest.SerializeToString,
|
|
45
|
+
response_deserializer=grpc__pb2.GenericRPCResponse.FromString,
|
|
46
|
+
_registered_method=True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class GenericRPCServicer(object):
|
|
50
|
+
"""Missing associated documentation comment in .proto file."""
|
|
51
|
+
|
|
52
|
+
def Call(self, request, context):
|
|
53
|
+
"""Missing associated documentation comment in .proto file."""
|
|
54
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
55
|
+
context.set_details('Method not implemented!')
|
|
56
|
+
raise NotImplementedError('Method not implemented!')
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def add_GenericRPCServicer_to_server(servicer, server):
|
|
60
|
+
rpc_method_handlers = {
|
|
61
|
+
'Call': grpc.unary_unary_rpc_method_handler(
|
|
62
|
+
servicer.Call,
|
|
63
|
+
request_deserializer=grpc__pb2.GenericRPCRequest.FromString,
|
|
64
|
+
response_serializer=grpc__pb2.GenericRPCResponse.SerializeToString,
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
generic_handler = grpc.method_handlers_generic_handler(
|
|
68
|
+
'remote_rf.GenericRPC', rpc_method_handlers)
|
|
69
|
+
server.add_generic_rpc_handlers((generic_handler,))
|
|
70
|
+
server.add_registered_method_handlers('remote_rf.GenericRPC', rpc_method_handlers)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# This class is part of an EXPERIMENTAL API.
|
|
74
|
+
class GenericRPC(object):
|
|
75
|
+
"""Missing associated documentation comment in .proto file."""
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def Call(request,
|
|
79
|
+
target,
|
|
80
|
+
options=(),
|
|
81
|
+
channel_credentials=None,
|
|
82
|
+
call_credentials=None,
|
|
83
|
+
insecure=False,
|
|
84
|
+
compression=None,
|
|
85
|
+
wait_for_ready=None,
|
|
86
|
+
timeout=None,
|
|
87
|
+
metadata=None):
|
|
88
|
+
return grpc.experimental.unary_unary(
|
|
89
|
+
request,
|
|
90
|
+
target,
|
|
91
|
+
'/remote_rf.GenericRPC/Call',
|
|
92
|
+
grpc__pb2.GenericRPCRequest.SerializeToString,
|
|
93
|
+
grpc__pb2.GenericRPCResponse.FromString,
|
|
94
|
+
options,
|
|
95
|
+
channel_credentials,
|
|
96
|
+
insecure,
|
|
97
|
+
call_credentials,
|
|
98
|
+
compression,
|
|
99
|
+
wait_for_ready,
|
|
100
|
+
timeout,
|
|
101
|
+
metadata,
|
|
102
|
+
_registered_method=True)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from prompt_toolkit.styles import Style
|
|
2
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
3
|
+
from prompt_toolkit import print_formatted_text
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
class Sty(Enum):
|
|
7
|
+
# Basic colors
|
|
8
|
+
RED = 'red'
|
|
9
|
+
GREEN = 'green'
|
|
10
|
+
BLUE = 'blue'
|
|
11
|
+
YELLOW = 'yellow'
|
|
12
|
+
MAGENTA = 'magenta'
|
|
13
|
+
CYAN = 'cyan'
|
|
14
|
+
GRAY = 'gray'
|
|
15
|
+
|
|
16
|
+
# Background colors
|
|
17
|
+
BG_RED = 'bg-red'
|
|
18
|
+
BG_GREEN = 'bg-green'
|
|
19
|
+
BG_BLUE = 'bg-blue'
|
|
20
|
+
|
|
21
|
+
# Bright versions
|
|
22
|
+
BRIGHT_RED = 'bright-red'
|
|
23
|
+
BRIGHT_GREEN = 'bright-green'
|
|
24
|
+
BRIGHT_BLUE = 'bright-blue'
|
|
25
|
+
|
|
26
|
+
# Formatting
|
|
27
|
+
BOLD = 'bold'
|
|
28
|
+
ITALIC = 'italic'
|
|
29
|
+
UNDERLINE = 'underline'
|
|
30
|
+
BLINK = 'blink'
|
|
31
|
+
REVERSE = 'reverse'
|
|
32
|
+
|
|
33
|
+
# Combinations
|
|
34
|
+
ERROR = 'error'
|
|
35
|
+
WARNING = 'warning'
|
|
36
|
+
INFO = 'info'
|
|
37
|
+
|
|
38
|
+
# Special
|
|
39
|
+
SELECTED = 'selected'
|
|
40
|
+
DEFAULT = 'default'
|
|
41
|
+
|
|
42
|
+
# Define the styles based on ANSI codes
|
|
43
|
+
style = Style.from_dict({
|
|
44
|
+
# Basic colors
|
|
45
|
+
'red': 'fg:#110000',
|
|
46
|
+
'green': 'fg:#003300',
|
|
47
|
+
'blue': 'fg:#0000ff',
|
|
48
|
+
'yellow': 'fg:#ffff00',
|
|
49
|
+
'magenta': 'fg:#ff00ff',
|
|
50
|
+
'cyan': 'fg:#00ffff',
|
|
51
|
+
'gray': 'fg:#808080',
|
|
52
|
+
|
|
53
|
+
# Bright versions
|
|
54
|
+
'bright-red': 'fg:#ff5555',
|
|
55
|
+
'bright-green': 'fg:#00ff00',
|
|
56
|
+
'bright-blue': 'fg:#5555ff',
|
|
57
|
+
|
|
58
|
+
# Formatting
|
|
59
|
+
'bold': 'bold',
|
|
60
|
+
'italic': 'italic',
|
|
61
|
+
'underline': 'underline',
|
|
62
|
+
'reverse': 'reverse',
|
|
63
|
+
|
|
64
|
+
# Combinations
|
|
65
|
+
'error': 'bg:#ff0000 fg:#ffffff bold',
|
|
66
|
+
'warning': 'bg:#ffff00 fg:#000000 bold',
|
|
67
|
+
'info': 'bg:#0000ff fg:#ffffff italic underline',
|
|
68
|
+
|
|
69
|
+
# Special
|
|
70
|
+
'selected': 'bg:#ffffff #000000 reverse',
|
|
71
|
+
'default':''
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
def printf(*args) -> str:
|
|
75
|
+
if len(args) % 2 != 0:
|
|
76
|
+
raise ValueError('Arguments must be in pairs of two.')
|
|
77
|
+
|
|
78
|
+
# Create formatted text using the defined style
|
|
79
|
+
formatted_text = []
|
|
80
|
+
|
|
81
|
+
for i in range(0, len(args), 2):
|
|
82
|
+
message = args[i]
|
|
83
|
+
styles = args[i+1]
|
|
84
|
+
|
|
85
|
+
if not isinstance(styles, tuple):
|
|
86
|
+
styles = (styles,)
|
|
87
|
+
|
|
88
|
+
resolved_styles = (s.value if isinstance(s, Enum) else s for s in styles)
|
|
89
|
+
|
|
90
|
+
style_class = ' '.join(resolved_styles)
|
|
91
|
+
|
|
92
|
+
formatted_text.append(('class:' + style_class, message))
|
|
93
|
+
|
|
94
|
+
# Create FormattedText object from pairs
|
|
95
|
+
text = FormattedText(formatted_text)
|
|
96
|
+
|
|
97
|
+
print_formatted_text(text, style=style)
|
|
98
|
+
return text
|
|
99
|
+
|
|
100
|
+
def stylize(*args):
|
|
101
|
+
"""
|
|
102
|
+
Create a styled prompt text based on pairs of (text, (Sty, ...), ...).
|
|
103
|
+
"""
|
|
104
|
+
if len(args) % 2 != 0:
|
|
105
|
+
raise ValueError("Arguments must be in pairs of (text, style_class).")
|
|
106
|
+
|
|
107
|
+
styled_parts = []
|
|
108
|
+
for i in range(0, len(args), 2):
|
|
109
|
+
text = args[i]
|
|
110
|
+
styles = args[i + 1]
|
|
111
|
+
|
|
112
|
+
if not isinstance(styles, tuple):
|
|
113
|
+
styles = (styles,)
|
|
114
|
+
|
|
115
|
+
resolved_styles = (s.value if isinstance(s, Enum) else s for s in styles)
|
|
116
|
+
|
|
117
|
+
style_class = ' '.join(resolved_styles)
|
|
118
|
+
styled_parts.append(('class:' + style_class, text))
|
|
119
|
+
|
|
120
|
+
return FormattedText(styled_parts)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import hashlib
|
|
3
|
+
import base64
|
|
4
|
+
import secrets
|
|
5
|
+
from dotenv import load_dotenv, find_dotenv
|
|
6
|
+
|
|
7
|
+
### API Token Management
|
|
8
|
+
# API Tokens are used to authenticate clients to the device
|
|
9
|
+
# API Tokens are stored locally on the device in its .env file
|
|
10
|
+
|
|
11
|
+
def generate_token(length=8) -> tuple[str, str, str]:
|
|
12
|
+
random_bytes = secrets.token_bytes(length) # Generate a random byte string
|
|
13
|
+
token = base64.urlsafe_b64encode(random_bytes).decode('utf-8').rstrip('=') # Encode the byte string in a URL-safe base64 format
|
|
14
|
+
salt = os.urandom(16).hex() # 16 bytes of random salt
|
|
15
|
+
hashed = hashlib.sha256(bytes.fromhex(salt) + token.encode()).hexdigest() # Hash to sha256 standard
|
|
16
|
+
return salt, hashed, token
|
|
17
|
+
|
|
18
|
+
def validate_token(salt, hash, token) -> bool:
|
|
19
|
+
new_hashed = hashlib.sha256(bytes.fromhex(salt) + token.encode()).hexdigest()
|
|
20
|
+
return new_hashed == hash
|
|
21
|
+
|
|
22
|
+
def hash_token(token: str) -> tuple[str, str]:
|
|
23
|
+
salt = os.urandom(16).hex()
|
|
24
|
+
hashed = hashlib.sha256(bytes.fromhex(salt) + token.encode()).hexdigest()
|
|
25
|
+
return salt, hashed
|
|
26
|
+
|
|
27
|
+
# Example Usage:
|
|
28
|
+
# if __name__ == '__main__':
|
|
29
|
+
# okay = generate_token()
|
|
30
|
+
# assert validate_token(okay[0], okay[1], okay[2] + "s"), "Token validation failed!"
|
|
31
|
+
# print("Token validation succeeded!")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from ..grpc import grpc_pb2, grpc_pb2_grpc
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def unmap_arg(arg):
|
|
5
|
+
if arg.HasField('int64_value'):
|
|
6
|
+
return arg.int64_value
|
|
7
|
+
elif arg.HasField('float_value'):
|
|
8
|
+
return arg.float_value
|
|
9
|
+
elif arg.HasField('string_value'):
|
|
10
|
+
return arg.string_value
|
|
11
|
+
elif arg.HasField('bool_value'):
|
|
12
|
+
return arg.bool_value
|
|
13
|
+
elif arg.HasField('real_array'):
|
|
14
|
+
shape = tuple(arg.real_array.shape.dim)
|
|
15
|
+
return np.array(arg.real_array.data, dtype=np.float64).reshape(shape)
|
|
16
|
+
elif arg.HasField('complex_array'):
|
|
17
|
+
shape = tuple(arg.complex_array.shape.dim)
|
|
18
|
+
data = [complex(c.real, c.imag) for c in arg.complex_array.data]
|
|
19
|
+
return np.array(data, dtype=np.complex64).reshape(shape)
|
|
20
|
+
else:
|
|
21
|
+
raise ValueError(f"Unknown argument type during unmapping: {arg}")
|
|
22
|
+
|
|
23
|
+
def map_arg(value):
|
|
24
|
+
arg = grpc_pb2.Argument()
|
|
25
|
+
|
|
26
|
+
if isinstance(value, int):
|
|
27
|
+
arg.int64_value = value
|
|
28
|
+
elif isinstance(value, float):
|
|
29
|
+
arg.float_value = value
|
|
30
|
+
elif isinstance(value, str):
|
|
31
|
+
arg.string_value = value
|
|
32
|
+
elif isinstance(value, bool):
|
|
33
|
+
arg.bool_value = value
|
|
34
|
+
elif isinstance(value, np.ndarray):
|
|
35
|
+
if np.iscomplexobj(value):
|
|
36
|
+
complex_array = arg.complex_array
|
|
37
|
+
complex_array.shape.dim.extend(value.shape)
|
|
38
|
+
for num in value.ravel():
|
|
39
|
+
complex_num = complex_array.data.add()
|
|
40
|
+
complex_num.real = num.real
|
|
41
|
+
complex_num.imag = num.imag
|
|
42
|
+
else:
|
|
43
|
+
float_array = arg.real_array
|
|
44
|
+
float_array.shape.dim.extend(value.shape)
|
|
45
|
+
float_array.data.extend(value.ravel())
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError(f"Unknown argument type during mapping: {value}")
|
|
48
|
+
return arg
|
|
49
|
+
|
|
50
|
+
def map_array_proto(np_array):
|
|
51
|
+
arg = grpc_pb2.Argument()
|
|
52
|
+
|
|
53
|
+
# Check if the array is complex
|
|
54
|
+
if np.iscomplexobj(np_array):
|
|
55
|
+
complex_array = grpc_pb2.ComplexArray()
|
|
56
|
+
for num in np_array.flat:
|
|
57
|
+
complex_number = complex_array.data.add()
|
|
58
|
+
complex_number.real = num.real
|
|
59
|
+
complex_number.imag = num.imag
|
|
60
|
+
arg.complex_array.CopyFrom(complex_array)
|
|
61
|
+
else:
|
|
62
|
+
# Handle as a regular float array
|
|
63
|
+
float_array = grpc_pb2.FloatArray()
|
|
64
|
+
float_array.data.extend(np_array.flat)
|
|
65
|
+
arg.float_array.CopyFrom(float_array)
|
|
66
|
+
|
|
67
|
+
return arg
|
|
68
|
+
|
|
69
|
+
def unmap_array_proto(arg):
|
|
70
|
+
# Check which type of array is available and convert appropriately
|
|
71
|
+
if arg.HasField('complex_array'):
|
|
72
|
+
# Convert ComplexArray to a numpy array of complex numbers
|
|
73
|
+
data = [complex(cn.real, cn.imag) for cn in arg.complex_array.data]
|
|
74
|
+
return np.array(data, dtype=np.complex64)
|
|
75
|
+
elif arg.HasField('float_array'):
|
|
76
|
+
# Convert FloatArray to a numpy array of floats
|
|
77
|
+
return np.array(arg.float_array.data, dtype=np.float32)
|
|
78
|
+
else:
|
|
79
|
+
raise ValueError("Argument does not contain a recognizable array.")
|
|
80
|
+
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from . import *
|
|
2
|
+
from ..common.utils import *
|
|
3
|
+
|
|
4
|
+
import getpass
|
|
5
|
+
import os
|
|
6
|
+
import datetime
|
|
7
|
+
import time
|
|
8
|
+
import ast
|
|
9
|
+
|
|
10
|
+
from prompt_toolkit import PromptSession
|
|
11
|
+
|
|
12
|
+
account = RemoteRFAccount()
|
|
13
|
+
session = PromptSession()
|
|
14
|
+
|
|
15
|
+
def welcome():
|
|
16
|
+
printf("Welcome to Remote RF Account System.", (Sty.BOLD, Sty.BLUE))
|
|
17
|
+
try:
|
|
18
|
+
inpu = session.prompt(stylize("Please ", Sty.DEFAULT, "login", Sty.GREEN, " or ", Sty.DEFAULT, "register", Sty.RED, " to continue. (", Sty.DEFAULT, 'l', Sty.GREEN, "/", Sty.DEFAULT, 'r', Sty.RED, "):", Sty.DEFAULT))
|
|
19
|
+
if inpu == 'r':
|
|
20
|
+
print("Registering new account ...")
|
|
21
|
+
account.username = input("Username: ")
|
|
22
|
+
double_check = True
|
|
23
|
+
while double_check:
|
|
24
|
+
password = getpass.getpass("Password: ")
|
|
25
|
+
password2 = getpass.getpass("Confirm Password: ")
|
|
26
|
+
if password == password2:
|
|
27
|
+
double_check = False
|
|
28
|
+
else:
|
|
29
|
+
print("Passwords do not match. Try again")
|
|
30
|
+
|
|
31
|
+
account.password = password
|
|
32
|
+
account.email = input("Email: ") # TODO: Email verification.
|
|
33
|
+
# check if login was valid
|
|
34
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
35
|
+
|
|
36
|
+
if not account.create_user():
|
|
37
|
+
welcome()
|
|
38
|
+
else:
|
|
39
|
+
account.username = input("Username: ")
|
|
40
|
+
account.password = getpass.getpass("Password: ")
|
|
41
|
+
# check if login was valid
|
|
42
|
+
if not account.login_user():
|
|
43
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
44
|
+
print("Invalid login. Try again.")
|
|
45
|
+
welcome()
|
|
46
|
+
except KeyboardInterrupt:
|
|
47
|
+
exit()
|
|
48
|
+
except EOFError:
|
|
49
|
+
exit()
|
|
50
|
+
|
|
51
|
+
def title():
|
|
52
|
+
printf(f"Remote RF Account System", Sty.BOLD)
|
|
53
|
+
# printf(f"Logged in as: ", Sty.DEFAULT, f'{account.username}', Sty.MAGENTA)
|
|
54
|
+
printf(f"Input ", Sty.DEFAULT, "'help' ", Sty.BRIGHT_GREEN, "for avaliable commands.", Sty.DEFAULT)
|
|
55
|
+
|
|
56
|
+
def commands():
|
|
57
|
+
printf("Commands:", Sty.BOLD)
|
|
58
|
+
printf("'clear' ", Sty.MAGENTA, "- Clear Terminal", Sty.DEFAULT)
|
|
59
|
+
printf("'getdev' ", Sty.MAGENTA, "- View Devices", Sty.DEFAULT)
|
|
60
|
+
printf("'help' ", Sty.MAGENTA, "- Show this help message", Sty.DEFAULT)
|
|
61
|
+
printf("'perms' ", Sty.MAGENTA, "- View Permissions", Sty.DEFAULT)
|
|
62
|
+
printf("'exit' ", Sty.MAGENTA, "- Exit", Sty.DEFAULT)
|
|
63
|
+
printf("'getres' ", Sty.MAGENTA, "- View All Reservations", Sty.DEFAULT)
|
|
64
|
+
printf("'myres' ", Sty.MAGENTA, "- View My Reservations", Sty.DEFAULT)
|
|
65
|
+
printf("'cancelres' ", Sty.MAGENTA, "- Cancel a Reservation", Sty.DEFAULT)
|
|
66
|
+
printf("'resdev' ", Sty.MAGENTA, "- Reserve a Device", Sty.DEFAULT)
|
|
67
|
+
|
|
68
|
+
def clear():
|
|
69
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
70
|
+
title()
|
|
71
|
+
|
|
72
|
+
def reservations():
|
|
73
|
+
data = account.get_reservations()
|
|
74
|
+
if 'ace' in data.results:
|
|
75
|
+
print(f"Error: {unmap_arg(data.results['ace'])}")
|
|
76
|
+
return
|
|
77
|
+
entries = []
|
|
78
|
+
|
|
79
|
+
for key, value in data.results.items():
|
|
80
|
+
parts = unmap_arg(value).split(',')
|
|
81
|
+
# Create a dictionary for each entry with named fields
|
|
82
|
+
entry = {
|
|
83
|
+
'username': parts[0],
|
|
84
|
+
'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
|
|
85
|
+
'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
|
|
86
|
+
'end_time': parts[3]
|
|
87
|
+
}
|
|
88
|
+
entries.append(entry)
|
|
89
|
+
|
|
90
|
+
if (entries == []):
|
|
91
|
+
printf("No reservations found.", Sty.BOLD)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
printf("Reservations:", Sty.BOLD)
|
|
95
|
+
|
|
96
|
+
# Sort the entries by device_id and then by start_time
|
|
97
|
+
sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time']))
|
|
98
|
+
|
|
99
|
+
# Format the sorted entries into strings
|
|
100
|
+
for entry in sorted_entries:
|
|
101
|
+
printf(f'Device ID: ', Sty.RED, f'{entry["device_id"]}', Sty.MAGENTA, f', Start Time: ', Sty.RED, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f', End Time: ', Sty.RED, f'{entry["end_time"]}', Sty.BLUE)
|
|
102
|
+
|
|
103
|
+
def my_reservations():
|
|
104
|
+
data = account.get_reservations()
|
|
105
|
+
if 'ace' in data.results:
|
|
106
|
+
print(f"Error: {unmap_arg(data.results['ace'])}")
|
|
107
|
+
return
|
|
108
|
+
entries = []
|
|
109
|
+
|
|
110
|
+
for key, value in data.results.items():
|
|
111
|
+
parts = unmap_arg(value).split(',')
|
|
112
|
+
# Create a dictionary for each entry with named fields
|
|
113
|
+
entry = {
|
|
114
|
+
'username': parts[0],
|
|
115
|
+
'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
|
|
116
|
+
'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
|
|
117
|
+
'end_time': parts[3]
|
|
118
|
+
}
|
|
119
|
+
entries.append(entry)
|
|
120
|
+
|
|
121
|
+
if (entries == []):
|
|
122
|
+
printf("No reservations found.", Sty.BOLD)
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
printf("Reservations under: ", Sty.BOLD, f'{account.username}', Sty.MAGENTA)
|
|
126
|
+
|
|
127
|
+
# Sort the entries by device_id and then by start_time
|
|
128
|
+
sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time']))
|
|
129
|
+
|
|
130
|
+
for entry in sorted_entries:
|
|
131
|
+
if account.username == entry['username']:
|
|
132
|
+
printf(f'Device ID: ', Sty.RED, f'{entry["device_id"]}', Sty.MAGENTA, f', Start Time: ', Sty.RED, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f', End Time: ', Sty.RED, f'{entry["end_time"]}', Sty.BLUE)
|
|
133
|
+
|
|
134
|
+
def cancel_my_reservation():
|
|
135
|
+
## print all of ur reservations and their ids
|
|
136
|
+
## ask for id to cancel
|
|
137
|
+
## remove said reservation
|
|
138
|
+
data = account.get_reservations()
|
|
139
|
+
if 'ace' in data.results:
|
|
140
|
+
print(f"Error: {unmap_arg(data.results['ace'])}")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
entries:list = []
|
|
144
|
+
|
|
145
|
+
for key, value in data.results.items():
|
|
146
|
+
parts = unmap_arg(value).split(',')
|
|
147
|
+
# Create a dictionary for each entry with named fields
|
|
148
|
+
entry = {
|
|
149
|
+
'id': -1,
|
|
150
|
+
'internal_id': key,
|
|
151
|
+
'username': parts[0],
|
|
152
|
+
'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
|
|
153
|
+
'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
|
|
154
|
+
'end_time': parts[3]
|
|
155
|
+
}
|
|
156
|
+
if account.username == entry['username']:
|
|
157
|
+
entries.append(entry)
|
|
158
|
+
|
|
159
|
+
printf("Current Reservation(s) under ", Sty.BOLD, f'{account.username}:', Sty.MAGENTA)
|
|
160
|
+
|
|
161
|
+
sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time'])) # sort by device_id and start_time
|
|
162
|
+
for i, entry in enumerate(sorted_entries): # label all reservations with unique id
|
|
163
|
+
entry['id'] = i
|
|
164
|
+
printf(f'Reservation ID: ', Sty.GRAY, f'{i}', Sty.MAGENTA, f' Device ID: ', Sty.GRAY, f'{entry["device_id"]}', Sty.BRIGHT_GREEN, f' Start Time: ', Sty.GRAY, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f' End Time: ', Sty.GRAY, f'{entry["end_time"]}', Sty.BLUE)
|
|
165
|
+
# print(f"Reservation ID {i}, Device ID: {entry['device_id']}, Start Time: {entry['start_time'].strftime('%Y-%m-%d %H:%M:%S')}, End Time: {entry['end_time']}")
|
|
166
|
+
|
|
167
|
+
if sorted_entries == []:
|
|
168
|
+
printf("No reservations found.", Sty.BOLD)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
inpu = session.prompt(stylize("Enter the ID of the reservation you would like to cancel ", Sty.BOLD, '(abort with any non number key input)', Sty.RED, ': ', Sty.BOLD))
|
|
172
|
+
|
|
173
|
+
if inpu.isdigit():
|
|
174
|
+
id = int(inpu)
|
|
175
|
+
if id >= len(sorted_entries):
|
|
176
|
+
print("Invalid ID.")
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
#grab the reservation
|
|
180
|
+
for entry in sorted_entries:
|
|
181
|
+
if entry['id'] == id:
|
|
182
|
+
db_id = entry['internal_id']
|
|
183
|
+
if session.prompt(stylize(f'Cancel reservation ID ', Sty.DEFAULT, f'{id}', Sty.MAGENTA, f' Device ID: ', Sty.DEFAULT, f'{entry["device_id"]}', Sty.BRIGHT_GREEN, f' Start Time: ', Sty.GRAY, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f' End Time: ', Sty.DEFAULT, f'{entry["end_time"]}', Sty.BLUE, f' ? (y/n):', Sty.DEFAULT)) == 'y':
|
|
184
|
+
response = account.cancel_reservation(db_id)
|
|
185
|
+
if 'ace' in response.results:
|
|
186
|
+
print(f"Error: {unmap_arg(response.results['ace'])}")
|
|
187
|
+
elif 'UC' in response.results:
|
|
188
|
+
printf(f"Reservation ID ", Sty.DEFAULT, f'{id}', Sty.BRIGHT_BLUE, ' successfully canceled.', Sty.DEFAULT)
|
|
189
|
+
else:
|
|
190
|
+
print("Aborting. User canceled action.")
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
print(f"Error: No reservation found with ID {id}.")
|
|
194
|
+
else:
|
|
195
|
+
print("Aborting. A non integer key was given.")
|
|
196
|
+
|
|
197
|
+
def devices():
|
|
198
|
+
data = account.get_devices()
|
|
199
|
+
if 'ace' in data.results:
|
|
200
|
+
print(f"Error: {unmap_arg(data.results['ace'])}")
|
|
201
|
+
return
|
|
202
|
+
printf("Devices:", Sty.BOLD)
|
|
203
|
+
|
|
204
|
+
for key in sorted(data.results, key=int):
|
|
205
|
+
printf(f"Device ID:", Sty.DEFAULT, f' {key}', Sty.MAGENTA, f" Device Name: ", Sty.DEFAULT, f"{unmap_arg(data.results[key])}", Sty.GRAY)
|
|
206
|
+
|
|
207
|
+
def get_datetime(question:str):
|
|
208
|
+
timestamp = session.prompt(stylize(f'{question}', Sty.DEFAULT, ' (YYYY-MM-DD HH:MM): ', Sty.GRAY))
|
|
209
|
+
return datetime.datetime.strptime(timestamp + ':00', '%Y-%m-%d %H:%M:%S')
|
|
210
|
+
|
|
211
|
+
def reserve():
|
|
212
|
+
try:
|
|
213
|
+
id = session.prompt(stylize("Enter the device ID you would like to reserve: ", Sty.DEFAULT))
|
|
214
|
+
token = account.reserve_device(int(id), get_datetime("Reserve Start Time"), get_datetime("Reserve End Time"))
|
|
215
|
+
if token != '':
|
|
216
|
+
printf(f"Reservation successful. Thy Token -> ", Sty.BOLD, f"{token}", Sty.BG_GREEN)
|
|
217
|
+
printf(f"Please keep this token safe, as it is not saved on server side, and cannot be regenerated/reretrieved. ", Sty.DEFAULT)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
printf(f"Error: {e}", Sty.BRIGHT_RED)
|
|
220
|
+
|
|
221
|
+
def perms():
|
|
222
|
+
data = account.get_perms()
|
|
223
|
+
if 'ace' in data.results:
|
|
224
|
+
print(f"Error: {unmap_arg(data.results['ace'])}")
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
results = ast.literal_eval(unmap_arg(data.results['UC']))[0]
|
|
228
|
+
printf(f'Permission Level: ', Sty.BOLD, f'{results[0]}', Sty.BLUE)
|
|
229
|
+
if results[0] == 'Normal User':
|
|
230
|
+
print(unmap_arg(data.results['details']))
|
|
231
|
+
elif results[0] == 'Power User':
|
|
232
|
+
printf(f'Max Reservations: ', Sty.DEFAULT, f'{results[3]}', Sty.MAGENTA)
|
|
233
|
+
printf(f'Max Reservation Duration (min): ', Sty.DEFAULT, f'{int(results[4]/60)}', Sty.MAGENTA)
|
|
234
|
+
printf(f'Device IDs allowed Access to: ', Sty.DEFAULT, f'{results[5]}', Sty.MAGENTA)
|
|
235
|
+
elif results[0] == 'Admin':
|
|
236
|
+
printf(f'No restrictions on reservation count or duration.', Sty.DEFAULT)
|
|
237
|
+
else:
|
|
238
|
+
printf(f"Error: Unknown permission level {results[0]}", Sty.BRIGHT_RED)
|
|
239
|
+
|
|
240
|
+
welcome()
|
|
241
|
+
clear()
|
|
242
|
+
|
|
243
|
+
while True:
|
|
244
|
+
try:
|
|
245
|
+
inpu = session.prompt(stylize(f'{account.username}@remote_rf: ', Sty.BLUE))
|
|
246
|
+
if inpu == "clear":
|
|
247
|
+
clear()
|
|
248
|
+
elif inpu == "getdev":
|
|
249
|
+
devices()
|
|
250
|
+
elif inpu == "help" or inpu == "h":
|
|
251
|
+
commands()
|
|
252
|
+
elif inpu == "perms":
|
|
253
|
+
perms()
|
|
254
|
+
elif inpu == "quit" or inpu == "exit":
|
|
255
|
+
break
|
|
256
|
+
elif inpu == "getres":
|
|
257
|
+
reservations()
|
|
258
|
+
elif inpu == "myres":
|
|
259
|
+
my_reservations()
|
|
260
|
+
elif inpu == "resdev":
|
|
261
|
+
reserve()
|
|
262
|
+
elif inpu == 'cancelres':
|
|
263
|
+
cancel_my_reservation()
|
|
264
|
+
else:
|
|
265
|
+
print(f"Unknown command: {inpu}")
|
|
266
|
+
except KeyboardInterrupt:
|
|
267
|
+
break
|
|
268
|
+
except EOFError:
|
|
269
|
+
break
|
|
270
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from .grpc_client import rpc_client
|
|
2
|
+
from ..common.utils import *
|
|
3
|
+
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
class RemoteRFAccount:
|
|
7
|
+
def __init__(self, username:str=None, password:str=None, email:str=None):
|
|
8
|
+
self.username = username
|
|
9
|
+
self.password = password
|
|
10
|
+
self.email = email
|
|
11
|
+
|
|
12
|
+
def create_user(self):
|
|
13
|
+
response = rpc_client(function_name="ACC:create_user", args={"un":map_arg(self.username), "pw":map_arg(self.password), "em":map_arg(self.email)})
|
|
14
|
+
if 'UC' in response.results:
|
|
15
|
+
print(f'User {unmap_arg(response.results["UC"])} successfully created.')
|
|
16
|
+
return True
|
|
17
|
+
elif 'UE' in response.results:
|
|
18
|
+
print(f'Error: {unmap_arg(response.results["UE"])}')
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
def login_user(self):
|
|
22
|
+
username = self.username
|
|
23
|
+
password = self.password
|
|
24
|
+
response = rpc_client(function_name="ACC:login", args={"un":map_arg(username), "pw":map_arg(password)})
|
|
25
|
+
if 'UC' in response.results:
|
|
26
|
+
print(f'User {unmap_arg(response.results["UC"])} successful login.')
|
|
27
|
+
return True
|
|
28
|
+
elif 'UE' in response.results:
|
|
29
|
+
print(f'Error: {unmap_arg(response.results["UE"])}')
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
def reserve_device(self, device_id:int, start_time:datetime, end_time:datetime):
|
|
33
|
+
response = rpc_client(function_name="ACC:reserve_device", args={"un":map_arg(self.username), "pw":map_arg(self.password), "dd":map_arg(device_id), "st":map_arg(int(start_time.timestamp())), "et":map_arg(int(end_time.timestamp()))})
|
|
34
|
+
|
|
35
|
+
if 'ace' in response.results:
|
|
36
|
+
raise Exception(f'{unmap_arg(response.results["ace"])}')
|
|
37
|
+
elif 'Token' in response.results:
|
|
38
|
+
return unmap_arg(response.results["Token"])
|
|
39
|
+
|
|
40
|
+
def get_reservations(self):
|
|
41
|
+
return rpc_client(function_name='ACC:get_res', args={"un":map_arg(self.username), "pw":map_arg(self.password)})
|
|
42
|
+
|
|
43
|
+
def get_devices(self):
|
|
44
|
+
return rpc_client(function_name='ACC:get_dev', args={"un":map_arg(self.username), "pw":map_arg(self.password)})
|
|
45
|
+
|
|
46
|
+
def cancel_reservation(self, res_id:int):
|
|
47
|
+
return rpc_client(function_name='ACC:cancel_res', args={"un":map_arg(self.username), "pw":map_arg(self.password), "res_id":map_arg(res_id)})
|
|
48
|
+
|
|
49
|
+
def get_perms(self):
|
|
50
|
+
return rpc_client(function_name='ACC:get_perms', args={"un":map_arg(self.username), "pw":map_arg(self.password)})
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import getpass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import grpc
|
|
6
|
+
from ..common.grpc import grpc_pb2
|
|
7
|
+
from ..common.grpc import grpc_pb2_grpc
|
|
8
|
+
from ..common.utils import *
|
|
9
|
+
|
|
10
|
+
# wlab credentials
|
|
11
|
+
server_ip = '164.67.195.207'
|
|
12
|
+
server_port = '61005'
|
|
13
|
+
|
|
14
|
+
# Server.crt
|
|
15
|
+
certs_path = Path(__file__).resolve().parent.parent/'core'/'certs'/'server.crt'
|
|
16
|
+
with certs_path.open('rb') as f:
|
|
17
|
+
trusted_certs = f.read()
|
|
18
|
+
|
|
19
|
+
credentials = grpc.ssl_channel_credentials(root_certificates=trusted_certs)
|
|
20
|
+
channel = grpc.secure_channel(f'{server_ip}:{server_port}', credentials)
|
|
21
|
+
stub = grpc_pb2_grpc.GenericRPCStub(channel)
|
|
22
|
+
|
|
23
|
+
tcp_calls = 0
|
|
24
|
+
|
|
25
|
+
def get_tcp_calls():
|
|
26
|
+
return tcp_calls
|
|
27
|
+
|
|
28
|
+
def rpc_client(*, function_name, args):
|
|
29
|
+
global tcp_calls
|
|
30
|
+
tcp_calls += 1
|
|
31
|
+
# print(tcp_calls)
|
|
32
|
+
# if not is_connected:
|
|
33
|
+
# response = rpc_client(function_name="UserLogin", args={"username": grpc_pb2.Argument(string_value=input("Username: ")), "password": grpc_pb2.Argument(string_value=getpass.getpass("Password: ")), "client_ip": grpc_pb2.Argument(string_value=local_ip)})
|
|
34
|
+
# if (response.results['status'].string_value == 'Success'):
|
|
35
|
+
# print("Login successful.")
|
|
36
|
+
# is_connected = True
|
|
37
|
+
|
|
38
|
+
# TODO: Handle user login
|
|
39
|
+
|
|
40
|
+
# print(f"Opening connection to {server_ip}:{server_port}")
|
|
41
|
+
|
|
42
|
+
# TODO: Handle Errors
|
|
43
|
+
|
|
44
|
+
# print(f"Calling function: {function_name}")
|
|
45
|
+
response = stub.Call(grpc_pb2.GenericRPCRequest(function_name=function_name, args=args))
|
|
46
|
+
|
|
47
|
+
if 'a' in response.results:
|
|
48
|
+
print(f"Error: {unmap_arg(response.results['a'])}")
|
|
49
|
+
exit()
|
|
50
|
+
|
|
51
|
+
if 'UE' in response.results:
|
|
52
|
+
print(f"UserError: {unmap_arg(response.results['UE'])}")
|
|
53
|
+
input("Hit enter to continue...")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
return response
|
|
57
|
+
|
|
58
|
+
#region Example Usage
|
|
59
|
+
|
|
60
|
+
# if __name__ == '__main__':
|
|
61
|
+
# args={
|
|
62
|
+
# "key1": grpc_pb2.Argument(string_value="Hello"),
|
|
63
|
+
# "key2": grpc_pb2.Argument(int32_value=123),
|
|
64
|
+
# "key3": grpc_pb2.Argument(float_value=4.56),
|
|
65
|
+
# "key4": grpc_pb2.Argument(bool_value=True),
|
|
66
|
+
# "client_ip": grpc_pb2.Argument(string_value=local_ip)
|
|
67
|
+
# }
|
|
68
|
+
|
|
69
|
+
# response = rpc_client(function_name="echo", args=args)
|
|
70
|
+
|
|
71
|
+
# if 'client_ip' in response.results:
|
|
72
|
+
# del response.results['client_ip']
|
|
73
|
+
|
|
74
|
+
# # Print results
|
|
75
|
+
# print("Received response:")
|
|
76
|
+
# for key, arg in response.results.items():
|
|
77
|
+
# # Decode the oneof fields
|
|
78
|
+
# if arg.HasField('string_value'):
|
|
79
|
+
# value = arg.string_value
|
|
80
|
+
# elif arg.HasField('int32_value'):
|
|
81
|
+
# value = arg.int32_value
|
|
82
|
+
# elif arg.HasField('float_value'):
|
|
83
|
+
# value = arg.float_value
|
|
84
|
+
# elif arg.HasField('bool_value'):
|
|
85
|
+
# value = arg.bool_value
|
|
86
|
+
# else:
|
|
87
|
+
# value = "Undefined"
|
|
88
|
+
|
|
89
|
+
# print(f"{key}: {value}")
|
|
90
|
+
|
|
91
|
+
#endregion
|
|
92
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: remoteRF
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A python API to remotely access signal centric hardware. Client-side only!
|
|
5
|
+
Author: Ethan Ge
|
|
6
|
+
Author-email: ethoGalaxy@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.6
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: grpcio
|
|
13
|
+
Requires-Dist: protobuf
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: prompt_toolkit
|
|
16
|
+
Requires-Dist: python-dotenv
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
src/remoteRF/__init__.py
|
|
5
|
+
src/remoteRF.egg-info/PKG-INFO
|
|
6
|
+
src/remoteRF.egg-info/SOURCES.txt
|
|
7
|
+
src/remoteRF.egg-info/dependency_links.txt
|
|
8
|
+
src/remoteRF.egg-info/entry_points.txt
|
|
9
|
+
src/remoteRF.egg-info/requires.txt
|
|
10
|
+
src/remoteRF.egg-info/top_level.txt
|
|
11
|
+
src/remoteRF/common/__init__.py
|
|
12
|
+
src/remoteRF/common/grpc/__init__.py
|
|
13
|
+
src/remoteRF/common/grpc/grpc_pb2.py
|
|
14
|
+
src/remoteRF/common/grpc/grpc_pb2_grpc.py
|
|
15
|
+
src/remoteRF/common/utils/__init__.py
|
|
16
|
+
src/remoteRF/common/utils/ansi_codes.py
|
|
17
|
+
src/remoteRF/common/utils/api_token.py
|
|
18
|
+
src/remoteRF/common/utils/list_string.py
|
|
19
|
+
src/remoteRF/common/utils/process_arg.py
|
|
20
|
+
src/remoteRF/core/__init__.py
|
|
21
|
+
src/remoteRF/core/acc_login.py
|
|
22
|
+
src/remoteRF/core/app.py
|
|
23
|
+
src/remoteRF/core/grpc_acc.py
|
|
24
|
+
src/remoteRF/core/grpc_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
remoteRF
|