autoverse-cli 0.28.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.
- autoverse_cli-0.28.1.dist-info/METADATA +91 -0
- autoverse_cli-0.28.1.dist-info/RECORD +51 -0
- autoverse_cli-0.28.1.dist-info/WHEEL +5 -0
- autoverse_cli-0.28.1.dist-info/entry_points.txt +2 -0
- autoverse_cli-0.28.1.dist-info/licenses/LICENSE +33 -0
- autoverse_cli-0.28.1.dist-info/top_level.txt +1 -0
- avrs/__init__.py +0 -0
- avrs/app_version.py +24 -0
- avrs/argparse_help.py +30 -0
- avrs/avrs.py +183 -0
- avrs/can_tool.py +192 -0
- avrs/can_tool_util.py +190 -0
- avrs/cfg.py +78 -0
- avrs/launcher.py +256 -0
- avrs/launcher_util.py +203 -0
- avrs/race_cloud.py +506 -0
- avrs/race_cloud_bridge_can.py +100 -0
- avrs/race_cloud_cfg_util.py +310 -0
- avrs/race_cloud_fwd_api.py +49 -0
- avrs/race_cloud_util.py +384 -0
- avrs/requests/change_camera.py +24 -0
- avrs/requests/code_booz.py +69 -0
- avrs/requests/demo.py +19 -0
- avrs/requests/dump_sim_config.py +14 -0
- avrs/requests/environment.py +46 -0
- avrs/requests/fault_injection.py +186 -0
- avrs/requests/get_object_config.py +18 -0
- avrs/requests/get_web_viz_meta.py +11 -0
- avrs/requests/leaderboard.py +74 -0
- avrs/requests/list_sim_objects.py +26 -0
- avrs/requests/log_path.py +28 -0
- avrs/requests/misc.py +70 -0
- avrs/requests/move_to_landmark.py +16 -0
- avrs/requests/npc.py +289 -0
- avrs/requests/race_control.py +48 -0
- avrs/requests/request.py +61 -0
- avrs/requests/reset_to_track.py +21 -0
- avrs/requests/rest_request.py +45 -0
- avrs/requests/restart.py +12 -0
- avrs/requests/scenario_control.py +43 -0
- avrs/requests/spawn_object.py +44 -0
- avrs/requests/teleport.py +40 -0
- avrs/requests/toggle_hud.py +11 -0
- avrs/requests/vd.py +64 -0
- avrs/requests/vehicle_input.py +21 -0
- avrs/requests/vehicle_replay.py +454 -0
- avrs/shell_completion.py +121 -0
- avrs/simconfig.py +75 -0
- avrs/simconfig_util.py +170 -0
- avrs/tests.py +9 -0
- avrs/util.py +13 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import glob
|
|
3
|
+
import json
|
|
4
|
+
import base64
|
|
5
|
+
import logging
|
|
6
|
+
import datetime
|
|
7
|
+
|
|
8
|
+
from avrs.simconfig_util import *
|
|
9
|
+
|
|
10
|
+
# these corresond to what SimConfigFiles sets in its init
|
|
11
|
+
# and should autospawns configured
|
|
12
|
+
# todo: we could parse these from simconfig.json's environments
|
|
13
|
+
# inside SimConfigFiles instead of doing it manually
|
|
14
|
+
EXPECTED_ENV_NAMES = [
|
|
15
|
+
"yas",
|
|
16
|
+
"adrome",
|
|
17
|
+
"suzuka",
|
|
18
|
+
"yasnorth"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
def get_payload(cfg_object, payload_name):
|
|
22
|
+
for p in cfg_object['payloads']:
|
|
23
|
+
if p['typeName'].lower() == payload_name.lower():
|
|
24
|
+
return p
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
def clear_autospawns():
|
|
28
|
+
logger = logging.getLogger('avrs')
|
|
29
|
+
sim_path = os.environ.get('AVRS_INSTALL_PATH',
|
|
30
|
+
os.path.join(os.environ['HOME'], 'autoverse-linux'))
|
|
31
|
+
sim_saved = os.path.join(sim_path, 'Linux', 'Autoverse', 'Saved')
|
|
32
|
+
logger.info('clearing autospawns from sim saved at {}'.format(sim_saved))
|
|
33
|
+
|
|
34
|
+
cfg_files = SimConfigFiles(sim_saved)
|
|
35
|
+
|
|
36
|
+
cfg_ok, msg = cfg_files.validate()
|
|
37
|
+
if not cfg_ok:
|
|
38
|
+
logger.error(msg)
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# also remove all configs from Objects dir
|
|
42
|
+
files = glob.glob(os.path.join(sim_saved, 'Objects/*'))
|
|
43
|
+
for f in files:
|
|
44
|
+
# do not remove default !!
|
|
45
|
+
if 'Eav24_default' in f:
|
|
46
|
+
logger.info('not removing {} because it is the default'.format(f))
|
|
47
|
+
else:
|
|
48
|
+
logger.info('removing object config: {}'.format(f))
|
|
49
|
+
os.remove(f)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
for ee in EXPECTED_ENV_NAMES:
|
|
53
|
+
cfg_files.files[ee]['autoSpawnObjects'] = []
|
|
54
|
+
|
|
55
|
+
# keep the default!
|
|
56
|
+
cfg_files.files['main']['objectTemplatePaths'] = ['Objects/Eav24_default.json']
|
|
57
|
+
|
|
58
|
+
# we want splitscreen in cloud
|
|
59
|
+
cfg_files.files['main']['bEnableSplitscreen'] = True
|
|
60
|
+
|
|
61
|
+
cfg_files.save()
|
|
62
|
+
|
|
63
|
+
def register_received_vehicle(team_name, slot, cfg_data, bsu_vcan, kistler_vcan, badenia_vcan):
|
|
64
|
+
logger = logging.getLogger('avrs')
|
|
65
|
+
logger.info('registering received vehicle in slot {} for team {}'.format(slot, team_name))
|
|
66
|
+
|
|
67
|
+
cfg_string = base64.b64decode(cfg_data)
|
|
68
|
+
cfg_object = json.loads(cfg_string.decode('utf-8'))
|
|
69
|
+
|
|
70
|
+
# ensure replay for recording is enabled
|
|
71
|
+
logger.info('ensuring replay component is enabled')
|
|
72
|
+
replay = get_payload(cfg_object, 'WheeledVehicleReplayIpd')
|
|
73
|
+
replay['bEnabled'] = True
|
|
74
|
+
replay['body']['bEnableRecording'] = True
|
|
75
|
+
|
|
76
|
+
# ensure perception is disabled
|
|
77
|
+
eav24 = get_payload(cfg_object, 'Eav24Initializer')
|
|
78
|
+
if eav24 is None:
|
|
79
|
+
return (False, 'no eav24 payload found')
|
|
80
|
+
|
|
81
|
+
logger.info('disabling perception for received vehicle config')
|
|
82
|
+
eav24['body']['bLidarEnabled'] = False
|
|
83
|
+
eav24['body']['bCameraEnabled'] = False
|
|
84
|
+
eav24['body']['bRadarEnabled'] = False
|
|
85
|
+
eav24['body']['bPublishGroundTruth'] = False
|
|
86
|
+
eav24['body']['bPublishInputs'] = False
|
|
87
|
+
eav24['body']['bRenderHudInWorld'] = True
|
|
88
|
+
|
|
89
|
+
# do not disable HUD
|
|
90
|
+
#logger.info('disabling hud for received vehicle config')
|
|
91
|
+
eav24['body']['bHudEnabled'] = True
|
|
92
|
+
|
|
93
|
+
logger.info('setting primary vcan to: {}, secondary to: {}, and <unused> to: {}'.format(
|
|
94
|
+
bsu_vcan, kistler_vcan, badenia_vcan))
|
|
95
|
+
eav24['body']['primaryCanName'] = bsu_vcan
|
|
96
|
+
eav24['body']['secondaryCanName'] = kistler_vcan
|
|
97
|
+
|
|
98
|
+
eav24['body']['badeniaCanName'] = badenia_vcan
|
|
99
|
+
|
|
100
|
+
# limit can rates to conserve resources
|
|
101
|
+
|
|
102
|
+
if eav24["body"]["canReceiveRate"] > 10000:
|
|
103
|
+
logger.info("can rx was > 10000. clamping")
|
|
104
|
+
eav24["body"]["canReceiveRate"] = 10000
|
|
105
|
+
|
|
106
|
+
if eav24["body"]["canLowSendRate"] > 10:
|
|
107
|
+
logger.info("can tx low was > 10. clamping")
|
|
108
|
+
eav24["body"]["canLowSendRate"] = 10
|
|
109
|
+
|
|
110
|
+
if eav24["body"]["canMedSendRate"] > 100:
|
|
111
|
+
logger.info("can tx med was > 100. clamping")
|
|
112
|
+
eav24["body"]["canMedSendRate"] = 100
|
|
113
|
+
|
|
114
|
+
if eav24["body"]["canHighSendRate"] > 500:
|
|
115
|
+
logger.info("can tx high was > 500. clamping")
|
|
116
|
+
eav24["body"]["canHighSendRate"] = 500
|
|
117
|
+
|
|
118
|
+
# clamp vectornav rates
|
|
119
|
+
|
|
120
|
+
vn = get_payload(cfg_object, "VectornavIpd")
|
|
121
|
+
if vn.get("body", {}).get("sensorDesc", {}).get("namedDataStreams", {}).get("CommonGroup", {}).get("rateHz", 0) > 100:
|
|
122
|
+
logger.info("vn CommonGroup rate was > 100. clamping")
|
|
123
|
+
vn["body"]["sensorDesc"]["namedDataStreams"]["CommonGroup"]["rateHz"] = 100
|
|
124
|
+
|
|
125
|
+
if vn.get("body", {}).get("sensorDesc", {}).get("namedDataStreams", {}).get("GpsGroup", {}).get("rateHz", 0) > 10:
|
|
126
|
+
logger.info("vn GpsGroup rate was > 10. clamping")
|
|
127
|
+
vn["body"]["sensorDesc"]["namedDataStreams"]["GpsGroup"]["rateHz"] = 10
|
|
128
|
+
|
|
129
|
+
if vn.get("body", {}).get("sensorDesc", {}).get("namedDataStreams", {}).get("Gps2Group", {}).get("rateHz", 0) > 10:
|
|
130
|
+
logger.info("vn Gps2Group rate was > 10. clamping")
|
|
131
|
+
vn["body"]["sensorDesc"]["namedDataStreams"]["Gps2Group"]["rateHz"] = 10
|
|
132
|
+
|
|
133
|
+
if vn.get("body", {}).get("sensorDesc", {}).get("namedDataStreams", {}).get("ImuGroup", {}).get("rateHz", 0) > 150:
|
|
134
|
+
logger.info("vn ImuGroup rate was > 150. clamping")
|
|
135
|
+
vn["body"]["sensorDesc"]["namedDataStreams"]["ImuGroup"]["rateHz"] = 150
|
|
136
|
+
|
|
137
|
+
if vn.get("body", {}).get("sensorDesc", {}).get("namedDataStreams", {}).get("AttitudeGroup", {}).get("rateHz", 0) > 100:
|
|
138
|
+
logger.info("vn AttitudeGroup rate was > 100. clamping")
|
|
139
|
+
vn["body"]["sensorDesc"]["namedDataStreams"]["AttitudeGroup"]["rateHz"] = 100
|
|
140
|
+
|
|
141
|
+
if vn.get("body", {}).get("sensorDesc", {}).get("namedDataStreams", {}).get("Tii", {}).get("rateHz", 0) > 100:
|
|
142
|
+
logger.info("vn Tii rate was > 100. clamping")
|
|
143
|
+
vn["body"]["sensorDesc"]["namedDataStreams"]["Tii"]["rateHz"] = 100
|
|
144
|
+
|
|
145
|
+
if vn.get("body", {}).get("sensorDesc", {}).get("namedDataStreams", {}).get("NavSatFix", {}).get("rateHz", 0) > 10:
|
|
146
|
+
logger.info("vn NavSatFix rate was > 10. clamping")
|
|
147
|
+
vn["body"]["sensorDesc"]["namedDataStreams"]["NavSatFix"]["rateHz"] = 10
|
|
148
|
+
|
|
149
|
+
ros2 = get_payload(cfg_object, 'Ros2')
|
|
150
|
+
if ros2 is None:
|
|
151
|
+
logger.info('no ros2 payload found. adding with domain id {}'.format(slot))
|
|
152
|
+
# need to add a ros2 payload for domain id
|
|
153
|
+
cfg_object['payloads'].append({
|
|
154
|
+
'typeName': 'Ros2',
|
|
155
|
+
'body': {
|
|
156
|
+
'domainId': slot
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
else:
|
|
160
|
+
logger.info('found Ros2 payload OK'.format())
|
|
161
|
+
logger.info('setting ros2 domain id to {}'.format(slot))
|
|
162
|
+
ros2['body']['domainId'] = slot
|
|
163
|
+
|
|
164
|
+
# auto add display widget
|
|
165
|
+
wtc = get_payload(cfg_object, 'WorldTextComponent')
|
|
166
|
+
if wtc is None:
|
|
167
|
+
logger.info('no WorldTextComponent payload found. adding')
|
|
168
|
+
cfg_object['payloads'].append({
|
|
169
|
+
'typeName': 'WorldTextComponent',
|
|
170
|
+
'body': {
|
|
171
|
+
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
else:
|
|
175
|
+
logger.info('found WorldTextComponent payload. ensuring it is enabled')
|
|
176
|
+
wtc['bEnabled'] = True
|
|
177
|
+
|
|
178
|
+
# auto add / enable ground truth payload
|
|
179
|
+
gtc = get_payload(cfg_object, 'GroundTruthSensor')
|
|
180
|
+
if gtc is None:
|
|
181
|
+
logger.info('no GroundTruthSensor payload found. adding')
|
|
182
|
+
cfg_object['payloads'].append({
|
|
183
|
+
'typeName': 'GroundTruthSensor',
|
|
184
|
+
'body': {
|
|
185
|
+
'myGroundTruthDsd': {
|
|
186
|
+
'streamName': 'ground_truth',
|
|
187
|
+
'rateHz': 50.0
|
|
188
|
+
},
|
|
189
|
+
'opponentGroundTruthDsd': {
|
|
190
|
+
'streamName': 'v2v_ground_truth',
|
|
191
|
+
'rateHz': 50.0
|
|
192
|
+
},
|
|
193
|
+
'bUseOpponentRelativeRotation': False
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
else:
|
|
197
|
+
logger.info('found GroundTruthSensor payload. ensuring it is enabled')
|
|
198
|
+
gtc['bEnabled'] = True
|
|
199
|
+
|
|
200
|
+
# limit gt rates
|
|
201
|
+
|
|
202
|
+
gtc = get_payload(cfg_object, 'GroundTruthSensor')
|
|
203
|
+
if gtc["body"]["myGroundTruthDsd"]["rateHz"] > 100:
|
|
204
|
+
logger.info("myGroundTruthDsd rate was > 100. clamping")
|
|
205
|
+
gtc["body"]["myGroundTruthDsd"]["rateHz"] = 100
|
|
206
|
+
|
|
207
|
+
if gtc["body"]["opponentGroundTruthDsd"]["rateHz"] > 100:
|
|
208
|
+
logger.info("opponentGroundTruthDsd rate was > 100. clamping")
|
|
209
|
+
gtc["body"]["opponentGroundTruthDsd"]["rateHz"] = 100
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# do not allow default object name (collision)
|
|
213
|
+
if cfg_object['name'] == 'eav24':
|
|
214
|
+
logger.info('setting vehicle name from default to team name: {}'.format(team_name))
|
|
215
|
+
cfg_object['name'] = team_name
|
|
216
|
+
object_spec_name = 'eav24_{}'.format(team_name)
|
|
217
|
+
cfg_object['specName'] = object_spec_name
|
|
218
|
+
|
|
219
|
+
# also need to edit the yas_marina_env.json to have autospawn for this config
|
|
220
|
+
sim_path = os.environ.get('AVRS_INSTALL_PATH',
|
|
221
|
+
os.path.join(os.environ['HOME'], 'autoverse-linux'))
|
|
222
|
+
sim_saved = os.path.join(sim_path, 'Linux', 'Autoverse', 'Saved')
|
|
223
|
+
cfg_files = SimConfigFiles(sim_saved)
|
|
224
|
+
|
|
225
|
+
cfg_ok, msg = cfg_files.validate()
|
|
226
|
+
if not cfg_ok:
|
|
227
|
+
print(msg)
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
start_landmark = 'PitsSlot{}'.format(slot + 1)
|
|
231
|
+
|
|
232
|
+
for ee in EXPECTED_ENV_NAMES:
|
|
233
|
+
entry_exists = False
|
|
234
|
+
|
|
235
|
+
# we want logging on for all environments
|
|
236
|
+
cfg_files.files[ee]['bAutoRecordVehicles'] = True
|
|
237
|
+
|
|
238
|
+
for i in cfg_files.files[ee]['autoSpawnObjects']:
|
|
239
|
+
if 'objectSpec' in i and i['objectSpec'] == cfg_object['specName']:
|
|
240
|
+
entry_exists = True
|
|
241
|
+
logger.info('config is already in auto spawn list')
|
|
242
|
+
|
|
243
|
+
if not entry_exists:
|
|
244
|
+
logger.info('config is not in auto spawn list. adding')
|
|
245
|
+
cfg_files.files[ee]['autoSpawnObjects'].append({
|
|
246
|
+
'bShouldSpawn': True,
|
|
247
|
+
'objectType': 'Eav24',
|
|
248
|
+
'objectSpec': cfg_object['specName'],
|
|
249
|
+
'bSpawnAtLandmark': True,
|
|
250
|
+
'spawnLandmarkName': start_landmark
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
# also need to add the object template to main sim config
|
|
254
|
+
|
|
255
|
+
new_cfg_name = 'eav24_{}.json'.format(object_spec_name)
|
|
256
|
+
cfg_files.files['main']['objectTemplatePaths'].append(os.path.join('Objects', new_cfg_name))
|
|
257
|
+
logger.info('saving config file: {}'.format(new_cfg_name))
|
|
258
|
+
cfg_files.save()
|
|
259
|
+
|
|
260
|
+
target_path = os.path.join(sim_saved, 'Objects', new_cfg_name)
|
|
261
|
+
with open(target_path, 'w', encoding='utf-8') as f:
|
|
262
|
+
json.dump(cfg_object, f, ensure_ascii=False, indent=4)
|
|
263
|
+
backup_path = os.path.join(os.environ['HOME'], 'team_configs')
|
|
264
|
+
if not os.path.exists(backup_path):
|
|
265
|
+
os.mkdir(backup_path)
|
|
266
|
+
date_time = datetime.datetime.now(datetime.timezone.utc).strftime("%Y_%m_%d_%H_%M_%S")
|
|
267
|
+
backup_path = os.path.join(backup_path, '{}_{}'.format(date_time,new_cfg_name))
|
|
268
|
+
with open(backup_path, 'w', encoding='utf-8') as f:
|
|
269
|
+
json.dump(cfg_object, f, ensure_ascii=False, indent=4)
|
|
270
|
+
|
|
271
|
+
def prepare_vehicle_cfg(cfg_path):
|
|
272
|
+
logger = logging.getLogger('avrs')
|
|
273
|
+
#print('preparing config for transmission: {}'.format(cfg_path))
|
|
274
|
+
|
|
275
|
+
if not os.path.isfile(cfg_path):
|
|
276
|
+
return (False, '{} is not a valid file'.format(cfg_path), None, None, None)
|
|
277
|
+
|
|
278
|
+
cfg_object = {}
|
|
279
|
+
with open(cfg_path, 'r', encoding='utf-8') as f:
|
|
280
|
+
cfg_object = json.load(f)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# obtain desired CAN names to start cannelloni
|
|
284
|
+
eav24 = get_payload(cfg_object, 'Eav24Initializer')
|
|
285
|
+
if eav24 is None:
|
|
286
|
+
return (False, 'no eav24 payload found', None, None, None)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
bsu_vcan = eav24['body'].get('primaryCanName', '')
|
|
291
|
+
if bsu_vcan == '':
|
|
292
|
+
logger.info('primaryCanName key not found, trying old bsuCanName')
|
|
293
|
+
bsu_vcan = eav24['body'].get('bsuCanName', '')
|
|
294
|
+
kistler_vcan = eav24['body'].get('secondaryCanName', '')
|
|
295
|
+
if kistler_vcan == '':
|
|
296
|
+
logger.info('secondaryCanName key not found, trying old kistlerCanName')
|
|
297
|
+
kistler_vcan = eav24['body'].get('kistlerCanName', '')
|
|
298
|
+
badenia_vcan = 'unused'
|
|
299
|
+
|
|
300
|
+
if bsu_vcan == '':
|
|
301
|
+
logger.error('could not find either primaryCanName or bsuCanName')
|
|
302
|
+
if kistler_vcan == '':
|
|
303
|
+
logger.error('could not find either secondaryCanName or kistlerCanName')
|
|
304
|
+
|
|
305
|
+
logger.info('detected vcan names from sent config: bsu {}, kistler {}, badenia {}'.format(
|
|
306
|
+
bsu_vcan, kistler_vcan, badenia_vcan))
|
|
307
|
+
|
|
308
|
+
cfg_data = base64.b64encode(json.dumps(cfg_object).encode('utf-8')).decode('utf-8')
|
|
309
|
+
return (True, cfg_data, bsu_vcan, kistler_vcan, badenia_vcan)
|
|
310
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
import http
|
|
6
|
+
from http import HTTPStatus
|
|
7
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
8
|
+
|
|
9
|
+
# https://gist.github.com/dfrankow/f91aefd683ece8e696c26e183d696c29
|
|
10
|
+
|
|
11
|
+
class ApiForwardHandler(BaseHTTPRequestHandler):
|
|
12
|
+
def __init__(self, target_port):
|
|
13
|
+
self.target_port = target_port
|
|
14
|
+
|
|
15
|
+
# allows to be passed to the HTTPServer ctor
|
|
16
|
+
def __call__(self, *args, **kwargs):
|
|
17
|
+
"""Handle a request."""
|
|
18
|
+
super().__init__(*args, **kwargs)
|
|
19
|
+
|
|
20
|
+
def do_POST(self):
|
|
21
|
+
|
|
22
|
+
length = int(self.headers.get('content-length'))
|
|
23
|
+
rfile_str = self.rfile.read(length).decode('utf8')
|
|
24
|
+
sim_response = self.get_fwd_response(rfile_str)
|
|
25
|
+
try:
|
|
26
|
+
print("{} : {}".format(self.client_address, rfile_str))
|
|
27
|
+
except:
|
|
28
|
+
pass
|
|
29
|
+
self.send_response(HTTPStatus.OK)
|
|
30
|
+
self.send_header("Content-Type", "application/json")
|
|
31
|
+
self.end_headers()
|
|
32
|
+
self.wfile.write(sim_response.encode('utf-8'))
|
|
33
|
+
self.end_headers()
|
|
34
|
+
|
|
35
|
+
def get_fwd_response(self, body):
|
|
36
|
+
connection = http.client.HTTPConnection('localhost', self.target_port, timeout=3)
|
|
37
|
+
headers = {
|
|
38
|
+
'Content-type': 'application/json',
|
|
39
|
+
}
|
|
40
|
+
#body = json.dumps(body).encode('utf-8') # already a string here
|
|
41
|
+
connection.request('POST', '/post', body, headers)
|
|
42
|
+
response = connection.getresponse()
|
|
43
|
+
response_string = ''
|
|
44
|
+
if response.status != 200:
|
|
45
|
+
pass
|
|
46
|
+
else:
|
|
47
|
+
response_string = response.read().decode('utf-8')
|
|
48
|
+
return response_string
|
|
49
|
+
|