flcpy 0.2.4__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.
- flcpy-0.2.4/LICENSE +7 -0
- flcpy-0.2.4/PKG-INFO +32 -0
- flcpy-0.2.4/README.md +19 -0
- flcpy-0.2.4/pyproject.toml +14 -0
- flcpy-0.2.4/setup.cfg +4 -0
- flcpy-0.2.4/src/flcpy/__init__.py +1 -0
- flcpy-0.2.4/src/flcpy/flcpy.py +342 -0
- flcpy-0.2.4/src/flcpy.egg-info/PKG-INFO +32 -0
- flcpy-0.2.4/src/flcpy.egg-info/SOURCES.txt +9 -0
- flcpy-0.2.4/src/flcpy.egg-info/dependency_links.txt +1 -0
- flcpy-0.2.4/src/flcpy.egg-info/top_level.txt +1 -0
flcpy-0.2.4/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2026 Jiwei Wang
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
flcpy-0.2.4/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flcpy
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Python control interface for Flare Live / Flare Real
|
|
5
|
+
Author-email: Jiwei Wang <zmh78879@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# Flare Python Controller (flcpy)
|
|
15
|
+
|
|
16
|
+
Python networking interface for Flare Live / Flare Real.
|
|
17
|
+
|
|
18
|
+
## Install & Upgrade
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
pip install --upgrade flcpy
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Use
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
import flcpy
|
|
28
|
+
|
|
29
|
+
c = flcpy.Controller()
|
|
30
|
+
obj = c.get_object(...)
|
|
31
|
+
...
|
|
32
|
+
```
|
flcpy-0.2.4/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Flare Python Controller (flcpy)
|
|
2
|
+
|
|
3
|
+
Python networking interface for Flare Live / Flare Real.
|
|
4
|
+
|
|
5
|
+
## Install & Upgrade
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install --upgrade flcpy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
import flcpy
|
|
15
|
+
|
|
16
|
+
c = flcpy.Controller()
|
|
17
|
+
obj = c.get_object(...)
|
|
18
|
+
...
|
|
19
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "flcpy"
|
|
3
|
+
version = "0.2.4"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Jiwei Wang", email="zmh78879@gmail.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "Python control interface for Flare Live / Flare Real"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
]
|
flcpy-0.2.4/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .flcpy import Controller
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import struct
|
|
3
|
+
import sys
|
|
4
|
+
from typing import NamedTuple
|
|
5
|
+
|
|
6
|
+
class Output(NamedTuple):
|
|
7
|
+
outputs: dict
|
|
8
|
+
|
|
9
|
+
def get_output_of(self, obj: int) -> tuple:
|
|
10
|
+
if obj in self.outputs:
|
|
11
|
+
return self.outputs[obj]
|
|
12
|
+
|
|
13
|
+
return None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SimulationResult(NamedTuple):
|
|
17
|
+
failed: int
|
|
18
|
+
outputs: dict
|
|
19
|
+
|
|
20
|
+
def is_failed(self) -> bool:
|
|
21
|
+
return self.failed != 0
|
|
22
|
+
|
|
23
|
+
def get_output_of(self, obj: int) -> tuple:
|
|
24
|
+
if obj in self.outputs:
|
|
25
|
+
return self.outputs[obj]
|
|
26
|
+
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
buffer_size = 4096
|
|
30
|
+
uint_max = 4294967295
|
|
31
|
+
|
|
32
|
+
def bool_to_bytes(val: bool) -> bytes:
|
|
33
|
+
if val:
|
|
34
|
+
return b'\x00'
|
|
35
|
+
return b'\x01'
|
|
36
|
+
|
|
37
|
+
def sint_to_bytes(val : int) -> bytes:
|
|
38
|
+
return int.to_bytes(val, 4, byteorder=sys.byteorder, signed=True)
|
|
39
|
+
|
|
40
|
+
def uint_to_bytes(val : int) -> bytes:
|
|
41
|
+
return int.to_bytes(val, 4, byteorder=sys.byteorder, signed=False)
|
|
42
|
+
|
|
43
|
+
def bytes_to_uint(data : bytes, offset : int) -> int:
|
|
44
|
+
return int.from_bytes(data[offset:offset+4], byteorder=sys.byteorder, signed=False)
|
|
45
|
+
|
|
46
|
+
def double_to_bytes(val: float) -> bytes:
|
|
47
|
+
return struct.pack('d', val)
|
|
48
|
+
|
|
49
|
+
def bytes_to_double(data: bytes, offset: int) -> float:
|
|
50
|
+
return struct.unpack('d', data[offset:offset+8])[0]
|
|
51
|
+
|
|
52
|
+
def string_to_bytes(string: str) -> bytes:
|
|
53
|
+
return string.encode("utf-8")
|
|
54
|
+
|
|
55
|
+
class MessagingClient:
|
|
56
|
+
def __init__(self, host : str, port : int):
|
|
57
|
+
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
58
|
+
self.socket.connect((host, port))
|
|
59
|
+
|
|
60
|
+
def request(self, data : bytes) -> bytes:
|
|
61
|
+
self.socket.send(data)
|
|
62
|
+
return self.socket.recv(buffer_size)
|
|
63
|
+
|
|
64
|
+
def close(self):
|
|
65
|
+
self.socket.close()
|
|
66
|
+
|
|
67
|
+
class Controller:
|
|
68
|
+
def __init__(self, host : str = 'localhost', port : int = 25556, recover_world = True):
|
|
69
|
+
self.messaging = MessagingClient(host, port)
|
|
70
|
+
if recover_world:
|
|
71
|
+
self.clear()
|
|
72
|
+
self.recover_world()
|
|
73
|
+
|
|
74
|
+
# Parameters: none
|
|
75
|
+
# Returns: time step
|
|
76
|
+
def get_time_step(self) -> float:
|
|
77
|
+
reply = self.messaging.request(uint_to_bytes(0))
|
|
78
|
+
return bytes_to_double(reply, 0)
|
|
79
|
+
|
|
80
|
+
# Parameters: object path, relative to object
|
|
81
|
+
# Returns: None or object id
|
|
82
|
+
def get_object_by_path(self, path : str, relative_to_object : int=None) -> int:
|
|
83
|
+
request = uint_to_bytes(1)
|
|
84
|
+
if relative_to_object is None:
|
|
85
|
+
request += uint_to_bytes(uint_max)
|
|
86
|
+
else:
|
|
87
|
+
request += uint_to_bytes(relative_to_object)
|
|
88
|
+
array = string_to_bytes(path)
|
|
89
|
+
request += uint_to_bytes(len(array))
|
|
90
|
+
request += array
|
|
91
|
+
reply = self.messaging.request(request)
|
|
92
|
+
obj = bytes_to_uint(reply, 0)
|
|
93
|
+
if obj == uint_max:
|
|
94
|
+
return None
|
|
95
|
+
else:
|
|
96
|
+
return obj
|
|
97
|
+
|
|
98
|
+
# See 'get_object_by_path'
|
|
99
|
+
def get_object(self, path: str, relative_to_object: int = None) -> int:
|
|
100
|
+
return self.get_object_by_path(path, relative_to_object)
|
|
101
|
+
|
|
102
|
+
# Parameters: list of object to retrieve output
|
|
103
|
+
# Returns: Output
|
|
104
|
+
def get_output(self, output_objs: list) -> Output:
|
|
105
|
+
request = uint_to_bytes(2)
|
|
106
|
+
request += uint_to_bytes(len(output_objs))
|
|
107
|
+
for obj in output_objs:
|
|
108
|
+
request += uint_to_bytes(obj)
|
|
109
|
+
|
|
110
|
+
reply = self.messaging.request(request)
|
|
111
|
+
count = bytes_to_uint(reply, 0)
|
|
112
|
+
outputs = {}
|
|
113
|
+
start = 4
|
|
114
|
+
for i in range(count):
|
|
115
|
+
obj = bytes_to_uint(reply, start)
|
|
116
|
+
start += 4
|
|
117
|
+
dof = bytes_to_uint(reply, start)
|
|
118
|
+
start += 4
|
|
119
|
+
output = []
|
|
120
|
+
for j in range(dof):
|
|
121
|
+
output.append(bytes_to_double(reply, start))
|
|
122
|
+
start += 8
|
|
123
|
+
outputs[obj] = tuple(output)
|
|
124
|
+
|
|
125
|
+
return Output(outputs)
|
|
126
|
+
|
|
127
|
+
# Parameters: none
|
|
128
|
+
# Returns: (success, current state), where state = 0 for not running, 1 for running.
|
|
129
|
+
def start(self) -> tuple:
|
|
130
|
+
request = uint_to_bytes(3)
|
|
131
|
+
request += b'\x00'
|
|
132
|
+
reply = self.messaging.request(request)
|
|
133
|
+
success = reply[0:1] == b'\x00'
|
|
134
|
+
state = int.from_bytes(reply[1:2], byteorder=sys.byteorder, signed=False)
|
|
135
|
+
return success, state
|
|
136
|
+
|
|
137
|
+
# Parameters: none
|
|
138
|
+
# Returns: (success, current state), where state = 0 for not running, 1 for running.
|
|
139
|
+
def reset(self) -> tuple:
|
|
140
|
+
request = uint_to_bytes(3)
|
|
141
|
+
request += b'\x01'
|
|
142
|
+
reply = self.messaging.request(request)
|
|
143
|
+
success = reply[0:1] == b'\x00'
|
|
144
|
+
state = int.from_bytes(reply[1:2], byteorder=sys.byteorder, signed=False)
|
|
145
|
+
return success, state
|
|
146
|
+
|
|
147
|
+
# Parameters: none
|
|
148
|
+
# Returns: (success, current state), where state = 0 for not running, 1 for running.
|
|
149
|
+
def clear(self) -> tuple:
|
|
150
|
+
request = uint_to_bytes(3)
|
|
151
|
+
request += b'\x02'
|
|
152
|
+
reply = self.messaging.request(request)
|
|
153
|
+
success = reply[0:1] == b'\x00'
|
|
154
|
+
state = int.from_bytes(reply[1:2], byteorder=sys.byteorder, signed=False)
|
|
155
|
+
return success, state
|
|
156
|
+
|
|
157
|
+
def stop(self) -> tuple:
|
|
158
|
+
return self.clear()
|
|
159
|
+
|
|
160
|
+
# Parameters: steps to simulate, inputs, output_objs
|
|
161
|
+
# (inputs: obj (int) -> input (tuple of float))
|
|
162
|
+
# (output_objs: list of obj (int))
|
|
163
|
+
# Returns: (status code, sensor outputs), where status code = 0 for success, 1 for inpur error, 2 for simulation error.
|
|
164
|
+
# (sensor outputs: index (int) -> output (tuple of float))
|
|
165
|
+
def simulate(self, steps : int, inputs : dict, output_objs : list = []) -> SimulationResult:
|
|
166
|
+
request = uint_to_bytes(4)
|
|
167
|
+
request += uint_to_bytes(steps)
|
|
168
|
+
request += uint_to_bytes(len(inputs))
|
|
169
|
+
for obj in inputs:
|
|
170
|
+
request += uint_to_bytes(obj)
|
|
171
|
+
input = inputs[obj]
|
|
172
|
+
if not hasattr(input, '__len__'):
|
|
173
|
+
input = (input,)
|
|
174
|
+
request += uint_to_bytes(len(input))
|
|
175
|
+
for val in input:
|
|
176
|
+
request += struct.pack('d', val)
|
|
177
|
+
request += uint_to_bytes(len(output_objs))
|
|
178
|
+
for obj in output_objs:
|
|
179
|
+
request += uint_to_bytes(obj)
|
|
180
|
+
|
|
181
|
+
reply = self.messaging.request(request)
|
|
182
|
+
failed = int.from_bytes(reply[0:1], byteorder=sys.byteorder, signed=False)
|
|
183
|
+
if failed != 0:
|
|
184
|
+
return SimulationResult(failed, dict())
|
|
185
|
+
|
|
186
|
+
output_count = bytes_to_uint(reply, 1)
|
|
187
|
+
start = 5
|
|
188
|
+
outputs = {}
|
|
189
|
+
for i in range(output_count):
|
|
190
|
+
obj = bytes_to_uint(reply, start)
|
|
191
|
+
start += 4
|
|
192
|
+
dof = bytes_to_uint(reply, start)
|
|
193
|
+
start += 4
|
|
194
|
+
output = []
|
|
195
|
+
for j in range(dof):
|
|
196
|
+
output.append(bytes_to_double(reply, start))
|
|
197
|
+
start += 8
|
|
198
|
+
outputs[obj] = tuple(output)
|
|
199
|
+
|
|
200
|
+
return SimulationResult(0, outputs)
|
|
201
|
+
|
|
202
|
+
# Parameters: Simulate until time, inputs, output_objs
|
|
203
|
+
# (inputs: obj (int) -> input (tuple of float))
|
|
204
|
+
# (output_objs: list of obj (int))
|
|
205
|
+
# Returns: (status code, sensor outputs), where status code = 0 for success, 1 for inpur error, 2 for simulation error.
|
|
206
|
+
# (sensor outputs: index (int) -> output (tuple of float))
|
|
207
|
+
def simulate_until(self, until_time : float, inputs : dict, output_objs : list = []) -> SimulationResult:
|
|
208
|
+
request = uint_to_bytes(11)
|
|
209
|
+
request += double_to_bytes(until_time)
|
|
210
|
+
request += uint_to_bytes(len(inputs))
|
|
211
|
+
for obj in inputs:
|
|
212
|
+
request += uint_to_bytes(obj)
|
|
213
|
+
input = inputs[obj]
|
|
214
|
+
if not hasattr(input, '__len__'):
|
|
215
|
+
input = (input,)
|
|
216
|
+
request += uint_to_bytes(len(input))
|
|
217
|
+
for val in input:
|
|
218
|
+
request += struct.pack('d', val)
|
|
219
|
+
request += uint_to_bytes(len(output_objs))
|
|
220
|
+
for obj in output_objs:
|
|
221
|
+
request += uint_to_bytes(obj)
|
|
222
|
+
|
|
223
|
+
reply = self.messaging.request(request)
|
|
224
|
+
failed = int.from_bytes(reply[0:1], byteorder=sys.byteorder, signed=False)
|
|
225
|
+
if failed != 0:
|
|
226
|
+
return SimulationResult(failed, dict())
|
|
227
|
+
|
|
228
|
+
output_count = bytes_to_uint(reply, 1)
|
|
229
|
+
start = 5
|
|
230
|
+
outputs = {}
|
|
231
|
+
for i in range(output_count):
|
|
232
|
+
obj = bytes_to_uint(reply, start)
|
|
233
|
+
start += 4
|
|
234
|
+
dof = bytes_to_uint(reply, start)
|
|
235
|
+
start += 4
|
|
236
|
+
output = []
|
|
237
|
+
for j in range(dof):
|
|
238
|
+
output.append(bytes_to_double(reply, start))
|
|
239
|
+
start += 8
|
|
240
|
+
outputs[obj] = tuple(output)
|
|
241
|
+
|
|
242
|
+
return SimulationResult(0, outputs)
|
|
243
|
+
|
|
244
|
+
# Parameters: sequential number
|
|
245
|
+
# Returns: success or not
|
|
246
|
+
def export(self, seq_num : int) -> bool:
|
|
247
|
+
request = uint_to_bytes(5)
|
|
248
|
+
request += uint_to_bytes(seq_num)
|
|
249
|
+
reply = self.messaging.request(request)
|
|
250
|
+
return reply[0:1] == b'\x00'
|
|
251
|
+
|
|
252
|
+
# parameters: object to clone, position, rotation, name
|
|
253
|
+
# (position: a tuple of 3 float, in x, y, z)
|
|
254
|
+
# (rotation: a tuple of 4 float, in quaternion w, x, y, z)
|
|
255
|
+
# Returns: None if failed, otherwise the cloned object
|
|
256
|
+
def clone_object(self, source_obj : int, position: tuple, rotation: tuple, name: str) -> int:
|
|
257
|
+
request = uint_to_bytes(6)
|
|
258
|
+
request += uint_to_bytes(source_obj)
|
|
259
|
+
for i in range(3):
|
|
260
|
+
request += double_to_bytes(position[i])
|
|
261
|
+
for i in range(4):
|
|
262
|
+
request += double_to_bytes(rotation[i])
|
|
263
|
+
array = string_to_bytes(name)
|
|
264
|
+
request += uint_to_bytes(len(array))
|
|
265
|
+
request += array
|
|
266
|
+
|
|
267
|
+
reply = self.messaging.request(request)
|
|
268
|
+
failed = int.from_bytes(reply[0:1], byteorder=sys.byteorder, signed=False)
|
|
269
|
+
if failed != 0:
|
|
270
|
+
return None
|
|
271
|
+
return bytes_to_uint(reply, 1)
|
|
272
|
+
|
|
273
|
+
def set_object_property(self, obj: int, property_path: str, value: bytes) -> bool:
|
|
274
|
+
request = uint_to_bytes(7)
|
|
275
|
+
request += uint_to_bytes(obj)
|
|
276
|
+
path_arr = string_to_bytes(property_path)
|
|
277
|
+
request += uint_to_bytes(len(path_arr))
|
|
278
|
+
request += path_arr
|
|
279
|
+
request += uint_to_bytes(len(value))
|
|
280
|
+
request += value
|
|
281
|
+
|
|
282
|
+
reply = self.messaging.request(request)
|
|
283
|
+
return reply[0:1] == b'\x00'
|
|
284
|
+
|
|
285
|
+
def set_object_property_float(self, obj: int, property_path: str, value: float) -> bool:
|
|
286
|
+
return self.set_object_property(obj, property_path, double_to_bytes(value))
|
|
287
|
+
|
|
288
|
+
def set_object_property_int(self, obj: int, property_path: str, value: int) -> bool:
|
|
289
|
+
return self.set_object_property(obj, property_path, sint_to_bytes(value))
|
|
290
|
+
|
|
291
|
+
def set_object_property_uint(self, obj: int, property_path: str, value: int) -> bool:
|
|
292
|
+
return self.set_object_property(obj, property_path, uint_to_bytes(value))
|
|
293
|
+
|
|
294
|
+
def set_object_property_bool(self, obj: int, property_path: str, value: bool) -> bool:
|
|
295
|
+
return self.set_object_property(obj, property_path, bool_to_bytes(value))
|
|
296
|
+
|
|
297
|
+
def set_object_property_string(self, obj: int, property_path: str, value: str) -> bool:
|
|
298
|
+
return self.set_object_property(obj, property_path, string_to_bytes(value))
|
|
299
|
+
|
|
300
|
+
def set_object_property_reference(self, obj: int, property_path: str, value: int) -> bool:
|
|
301
|
+
return self.set_object_property(obj, property_path, uint_to_bytes(value))
|
|
302
|
+
|
|
303
|
+
# Recover the world to initial state. Not available when simulation is started.
|
|
304
|
+
# Returns: success or not
|
|
305
|
+
def recover_world(self) -> bool:
|
|
306
|
+
request = uint_to_bytes(8)
|
|
307
|
+
reply = self.messaging.request(request)
|
|
308
|
+
return reply[0:1] == b'\x00'
|
|
309
|
+
|
|
310
|
+
# Parameters: preset path
|
|
311
|
+
# Returns: None or preset's object id
|
|
312
|
+
def get_preset_by_path(self, path : str) -> int:
|
|
313
|
+
request = uint_to_bytes(9)
|
|
314
|
+
array = string_to_bytes(path)
|
|
315
|
+
request += uint_to_bytes(len(array))
|
|
316
|
+
request += array
|
|
317
|
+
reply = self.messaging.request(request)
|
|
318
|
+
obj = bytes_to_uint(reply, 0)
|
|
319
|
+
if obj == uint_max:
|
|
320
|
+
return None
|
|
321
|
+
else:
|
|
322
|
+
return obj
|
|
323
|
+
|
|
324
|
+
# Parameters: system name
|
|
325
|
+
# Returns: None or system's object id
|
|
326
|
+
def get_system_by_name(self, name : str) -> int:
|
|
327
|
+
request = uint_to_bytes(10)
|
|
328
|
+
array = string_to_bytes(name)
|
|
329
|
+
request += uint_to_bytes(len(array))
|
|
330
|
+
request += array
|
|
331
|
+
reply = self.messaging.request(request)
|
|
332
|
+
obj = bytes_to_uint(reply, 0)
|
|
333
|
+
if obj == uint_max:
|
|
334
|
+
return None
|
|
335
|
+
else:
|
|
336
|
+
return obj
|
|
337
|
+
|
|
338
|
+
def close(self, recover_world = False):
|
|
339
|
+
if recover_world:
|
|
340
|
+
self.clear()
|
|
341
|
+
self.recover_world()
|
|
342
|
+
self.messaging.close()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flcpy
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Python control interface for Flare Live / Flare Real
|
|
5
|
+
Author-email: Jiwei Wang <zmh78879@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# Flare Python Controller (flcpy)
|
|
15
|
+
|
|
16
|
+
Python networking interface for Flare Live / Flare Real.
|
|
17
|
+
|
|
18
|
+
## Install & Upgrade
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
pip install --upgrade flcpy
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Use
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
import flcpy
|
|
28
|
+
|
|
29
|
+
c = flcpy.Controller()
|
|
30
|
+
obj = c.get_object(...)
|
|
31
|
+
...
|
|
32
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flcpy
|