@@ -1,11 +1,14 | |||||
1 | from django.shortcuts import render, redirect, get_object_or_404, HttpResponse |
|
1 | ||
2 | from datetime import datetime |
|
2 | from datetime import datetime | |
3 |
|
3 | |||
4 | from django.db import models |
|
4 | try: | |
5 | from polymorphic.models import PolymorphicModel |
|
5 | from polymorphic.models import PolymorphicModel | |
|
6 | except: | |||
|
7 | from polymorphic import PolymorphicModel | |||
6 |
|
8 | |||
|
9 | from django.db import models | |||
7 | from django.core.urlresolvers import reverse |
|
10 | from django.core.urlresolvers import reverse | |
8 |
|
11 | from django.shortcuts import get_object_or_404 | ||
9 |
|
12 | |||
10 | CONF_STATES = ( |
|
13 | CONF_STATES = ( | |
11 | (0, 'Disconnected'), |
|
14 | (0, 'Disconnected'), | |
@@ -26,11 +29,12 CONF_TYPES = ( | |||||
26 | (1, 'Historical'), |
|
29 | (1, 'Historical'), | |
27 | ) |
|
30 | ) | |
28 |
|
31 | |||
29 | DEV_STATES = ( |
|
32 | DEV_STATES = ( | |
30 | (0, 'No connected'), |
|
33 | (0, 'No connected'), | |
31 | (1, 'Connected'), |
|
34 | (1, 'Connected'), | |
32 | (2, 'Configured'), |
|
35 | (2, 'Configured'), | |
33 | (3, 'Running'), |
|
36 | (3, 'Running'), | |
|
37 | (4, 'Unknown'), | |||
34 | ) |
|
38 | ) | |
35 |
|
39 | |||
36 | DEV_TYPES = ( |
|
40 | DEV_TYPES = ( | |
@@ -123,13 +127,16 class Device(models.Model): | |||||
123 | color = "success" |
|
127 | color = "success" | |
124 |
|
128 | |||
125 | return color |
|
129 | return color | |
126 |
|
130 | |||
127 | @property |
|
131 | def url(self, path=None): | |
128 | def url(self): |
|
132 | ||
|
133 | if path: | |||
|
134 | return 'http://{}:{}/{}/'.format(self.ip_address, self.port_address, path) | |||
|
135 | else: | |||
|
136 | return 'http://{}:{}/'.format(self.ip_address, self.port_address) | |||
129 |
|
137 | |||
130 | return 'http://{}:{}/'.format(self.ip_address, self.port_address) |
|
|||
131 |
|
||||
132 | def get_absolute_url(self): |
|
138 | def get_absolute_url(self): | |
|
139 | ||||
133 | return reverse('url_device', args=[str(self.id)]) |
|
140 | return reverse('url_device', args=[str(self.id)]) | |
134 |
|
141 | |||
135 |
|
142 | |||
@@ -570,27 +577,32 class Configuration(PolymorphicModel): | |||||
570 |
|
577 | |||
571 | def status_device(self): |
|
578 | def status_device(self): | |
572 |
|
579 | |||
573 | raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper()) |
|
580 | self.message = 'Function not implemented' | |
|
581 | return False | |||
574 |
|
582 | |||
575 |
|
583 | |||
576 | def stop_device(self): |
|
584 | def stop_device(self): | |
577 |
|
585 | |||
578 | raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper()) |
|
586 | self.message = 'Function not implemented' | |
|
587 | return False | |||
579 |
|
588 | |||
580 |
|
589 | |||
581 | def start_device(self): |
|
590 | def start_device(self): | |
582 |
|
591 | |||
583 | raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper()) |
|
592 | self.message = 'Function not implemented' | |
|
593 | return False | |||
584 |
|
594 | |||
585 |
|
595 | |||
586 | def write_device(self, parms): |
|
596 | def write_device(self, parms): | |
587 |
|
597 | |||
588 | raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper()) |
|
598 | self.message = 'Function not implemented' | |
|
599 | return False | |||
589 |
|
600 | |||
590 |
|
601 | |||
591 | def read_device(self): |
|
602 | def read_device(self): | |
592 |
|
603 | |||
593 | raise NotImplementedError("This method should be implemented in %s Configuration model" %str(self.device.device_type.name).upper()) |
|
604 | self.message = 'Function not implemented' | |
|
605 | return False | |||
594 |
|
606 | |||
595 |
|
607 | |||
596 | def get_absolute_url(self): |
|
608 | def get_absolute_url(self): |
@@ -1141,7 +1141,7 def dev_conf_start(request, id_conf): | |||||
1141 | else: |
|
1141 | else: | |
1142 | messages.error(request, conf.message) |
|
1142 | messages.error(request, conf.message) | |
1143 |
|
1143 | |||
1144 | conf.status_device() |
|
1144 | #conf.status_device() | |
1145 |
|
1145 | |||
1146 | return redirect(conf.get_absolute_url()) |
|
1146 | return redirect(conf.get_absolute_url()) | |
1147 |
|
1147 | |||
@@ -1155,7 +1155,7 def dev_conf_stop(request, id_conf): | |||||
1155 | else: |
|
1155 | else: | |
1156 | messages.error(request, conf.message) |
|
1156 | messages.error(request, conf.message) | |
1157 |
|
1157 | |||
1158 | conf.status_device() |
|
1158 | #conf.status_device() | |
1159 |
|
1159 | |||
1160 | return redirect(conf.get_absolute_url()) |
|
1160 | return redirect(conf.get_absolute_url()) | |
1161 |
|
1161 | |||
@@ -1176,17 +1176,9 def dev_conf_write(request, id_conf): | |||||
1176 |
|
1176 | |||
1177 | conf = get_object_or_404(Configuration, pk=id_conf) |
|
1177 | conf = get_object_or_404(Configuration, pk=id_conf) | |
1178 |
|
1178 | |||
1179 |
|
|
1179 | if conf.write_device(): | |
1180 | conf.status_device() |
|
1180 | messages.success(request, conf.message) | |
1181 |
|
1181 | conf.clone(type=1, template=False) | ||
1182 | if answer: |
|
|||
1183 | messages.success(request, conf.message) |
|
|||
1184 |
|
||||
1185 | #Creating a historical configuration |
|
|||
1186 | conf.clone(type=1, template=False) |
|
|||
1187 |
|
||||
1188 | #Original configuration |
|
|||
1189 | conf = DevConfModel.objects.get(pk=id_conf) |
|
|||
1190 | else: |
|
1182 | else: | |
1191 | messages.error(request, conf.message) |
|
1183 | messages.error(request, conf.message) | |
1192 |
|
1184 | |||
@@ -1202,7 +1194,7 def dev_conf_read(request, id_conf): | |||||
1202 | if request.method=='GET': |
|
1194 | if request.method=='GET': | |
1203 |
|
1195 | |||
1204 | parms = conf.read_device() |
|
1196 | parms = conf.read_device() | |
1205 | conf.status_device() |
|
1197 | #conf.status_device() | |
1206 |
|
1198 | |||
1207 | if not parms: |
|
1199 | if not parms: | |
1208 | messages.error(request, conf.message) |
|
1200 | messages.error(request, conf.message) |
@@ -4,6 +4,7 import json | |||||
4 | import requests |
|
4 | import requests | |
5 | import numpy as np |
|
5 | import numpy as np | |
6 | from base64 import b64encode |
|
6 | from base64 import b64encode | |
|
7 | from struct import pack | |||
7 |
|
8 | |||
8 | from django.db import models |
|
9 | from django.db import models | |
9 | from django.core.urlresolvers import reverse |
|
10 | from django.core.urlresolvers import reverse | |
@@ -454,66 +455,114 class RCConfiguration(Configuration): | |||||
454 |
|
455 | |||
455 | def status_device(self): |
|
456 | def status_device(self): | |
456 |
|
457 | |||
457 | try: |
|
458 | try: | |
458 | req = requests.get(self.device.url) |
|
459 | req = requests.get(self.device.url('status')) | |
459 | payload = req.json() |
|
460 | payload = req.json() | |
460 | if payload['status']=='ok': |
|
461 | if payload['status']=='ok': | |
461 | self.device.status = 3 |
|
|||
462 | else: |
|
|||
463 | self.device.status = 1 |
|
462 | self.device.status = 1 | |
464 |
|
|
463 | else: | |
|
464 | self.device.status = 0 | |||
|
465 | except Exception as e: | |||
465 | self.device.status = 0 |
|
466 | self.device.status = 0 | |
|
467 | self.message = str(e) | |||
|
468 | return False | |||
466 |
|
469 | |||
467 | self.device.save() |
|
470 | self.device.save() | |
468 |
|
471 | return True | ||
469 | return self.device.status |
|
|||
470 |
|
472 | |||
471 |
|
473 | |||
472 | def reset_device(self): |
|
474 | def reset_device(self): | |
473 |
|
475 | |||
474 | payload = bytearray() |
|
476 | try: | |
475 | payload.extend(self.add_cmd('RESTART')) |
|
477 | req = requests.post(self.device.url('reset')) | |
476 | data = b64encode(payload) |
|
478 | if 'ok' in req.text: | |
477 | req = requests.put(self.device.url, data) |
|
479 | self.message = 'RC restarted' | |
|
480 | else: | |||
|
481 | self.message = 'RC restart not ok' | |||
|
482 | self.device.status = 4 | |||
|
483 | self.device.save() | |||
|
484 | except Exception as e: | |||
|
485 | self.message = str(e) | |||
|
486 | return False | |||
|
487 | ||||
|
488 | return True | |||
478 |
|
489 | |||
479 | if data==req.text.encode('utf8'): |
|
|||
480 | return 1 |
|
|||
481 | else: |
|
|||
482 | return 0 |
|
|||
483 |
|
||||
484 | def stop_device(self): |
|
490 | def stop_device(self): | |
485 |
|
491 | |||
486 | payload = bytearray() |
|
492 | try: | |
487 | payload.extend(self.add_cmd('DISABLE')) |
|
493 | req = requests.post(self.device.url('stop')) | |
488 | data = b64encode(payload) |
|
494 | if 'ok' in req.text: | |
489 | req = requests.put(self.device.url, data) |
|
495 | self.device.status = 2 | |
490 |
|
496 | self.device.save() | ||
491 | if data==req.text.encode('utf8'): |
|
497 | self.message = 'RC stopped' | |
492 |
|
|
498 | else: | |
493 | else: |
|
499 | self.message = 'RC stop not ok' | |
494 | return 0 |
|
500 | self.device.status = 4 | |
|
501 | self.device.save() | |||
|
502 | return False | |||
|
503 | except Exception as e: | |||
|
504 | self.message = str(e) | |||
|
505 | return False | |||
|
506 | ||||
|
507 | return True | |||
495 |
|
508 | |||
496 | def start_device(self): |
|
509 | def start_device(self): | |
497 |
|
510 | |||
498 | payload = bytearray() |
|
511 | try: | |
499 | payload.extend(self.add_cmd('ENABLE')) |
|
512 | req = requests.post(self.device.url('start')) | |
500 | data = b64encode(payload) |
|
513 | if 'ok' in req.text: | |
501 | req = requests.put(self.device.url, data) |
|
514 | self.device.status = 3 | |
502 |
|
515 | self.device.save() | ||
503 | if data==req.text.encode('utf8'): |
|
516 | self.message = 'RC running' | |
504 |
|
|
517 | else: | |
505 | else: |
|
518 | self.message = 'RC start not ok' | |
506 |
return |
|
519 | return False | |
|
520 | except Exception as e: | |||
|
521 | self.message = str(e) | |||
|
522 | return False | |||
|
523 | ||||
|
524 | return True | |||
507 |
|
525 | |||
508 | def write_device(self): |
|
526 | def write_device(self): | |
509 |
|
527 | |||
510 | data = b64encode(self.parms_to_binary(dat=False)) |
|
528 | values = zip(self.get_pulses(), | |
511 | req = requests.put(self.device.url, data) |
|
529 | [x-1 for x in self.get_delays()]) | |
512 | print(req.text) |
|
530 | payload = '' | |
513 | if data==req.text.encode('utf8'): |
|
531 | ||
514 | return 1 |
|
532 | for tup in values: | |
515 | else: |
|
533 | vals = pack('<HH', *tup) | |
516 | return 0 |
|
534 | payload += '\x05'+vals[0]+'\x04'+vals[1]+'\x05'+vals[2]+'\x05'+vals[3] | |
|
535 | ||||
|
536 | try: | |||
|
537 | ## reset | |||
|
538 | if not self.reset_device(): | |||
|
539 | return False | |||
|
540 | ## stop | |||
|
541 | if not self.stop_device(): | |||
|
542 | return False | |||
|
543 | ## write clock divider | |||
|
544 | req = requests.post(self.device.url('divisor'), | |||
|
545 | {'divisor': '{:d}'.format(self.clock_divider-1)}) | |||
|
546 | if 'ok' not in req.text: | |||
|
547 | self.message = 'Error configuring RC clock divider' | |||
|
548 | return False | |||
|
549 | ## write pulses & delays | |||
|
550 | req = requests.post(self.device.url('write'), data=b64encode(payload)) | |||
|
551 | if 'ok' in req.text: | |||
|
552 | self.device.status = 2 | |||
|
553 | self.device.save() | |||
|
554 | self.message = 'RC configured' | |||
|
555 | else: | |||
|
556 | self.device.status = 4 | |||
|
557 | self.device.save() | |||
|
558 | self.message = 'RC write not ok' | |||
|
559 | return False | |||
|
560 | ||||
|
561 | except Exception as e: | |||
|
562 | self.message = str(e) | |||
|
563 | return False | |||
|
564 | ||||
|
565 | return True | |||
517 |
|
566 | |||
518 |
|
567 | |||
519 | class RCLineCode(models.Model): |
|
568 | class RCLineCode(models.Model): | |
@@ -873,6 +922,6 class RCLine(models.Model): | |||||
873 |
|
922 | |||
874 | delays = len(delay) |
|
923 | delays = len(delay) | |
875 |
|
924 | |||
876 | Y = [(ipp*x+before+delay[x%delays], ipp*x+width+before+delay[x%delays]+after) for x in range(ntx)] |
|
925 | Y = [(int(ipp*x+before+delay[x%delays]+sync), int(ipp*x+width+before+delay[x%delays]+after+sync)) for x in range(ntx)] | |
877 |
|
926 | |||
878 | return Y |
|
927 | return Y |
@@ -7,7 +7,7 urlpatterns = ( | |||||
7 | url(r'^(?P<conf_id>-?\d+)/import/$', views.import_file, name='url_import_rc_conf'), |
|
7 | url(r'^(?P<conf_id>-?\d+)/import/$', views.import_file, name='url_import_rc_conf'), | |
8 | url(r'^(?P<conf_id>-?\d+)/edit/$', views.conf_edit, name='url_edit_rc_conf'), |
|
8 | url(r'^(?P<conf_id>-?\d+)/edit/$', views.conf_edit, name='url_edit_rc_conf'), | |
9 | url(r'^(?P<conf_id>-?\d+)/plot/$', views.plot_pulses, name='url_plot_rc_pulses'), |
|
9 | url(r'^(?P<conf_id>-?\d+)/plot/$', views.plot_pulses, name='url_plot_rc_pulses'), | |
10 | url(r'^(?P<conf_id>-?\d+)/plot2/$', views.plot_pulses2, name='url_plot_rc_pulses'), |
|
10 | url(r'^(?P<conf_id>-?\d+)/plot2/$', views.plot_pulses2, name='url_plot_rc_pulses2'), | |
11 | #url(r'^(?P<id_conf>-?\d+)/write/$', 'apps.main.views.dev_conf_write', name='url_write_rc_conf'), |
|
11 | #url(r'^(?P<id_conf>-?\d+)/write/$', 'apps.main.views.dev_conf_write', name='url_write_rc_conf'), | |
12 | #url(r'^(?P<id_conf>-?\d+)/read/$', 'apps.main.views.dev_conf_read', name='url_read_rc_conf'), |
|
12 | #url(r'^(?P<id_conf>-?\d+)/read/$', 'apps.main.views.dev_conf_read', name='url_read_rc_conf'), | |
13 |
|
13 |
@@ -1,134 +1,98 | |||||
1 | ''' |
|
1 | ''' | |
2 | Created on Dec 2, 2014 |
|
2 | API to configure new Radar controller | |
3 |
|
3 | |||
4 | @author: Miguel Urco |
|
|||
5 |
|
||||
6 | eth_device decorator is used to implement an api to ethernet devices. |
|
|||
7 | When eth_device decorator is used it adds two parameters to any function (ip and port) |
|
|||
8 |
|
||||
9 | #Definition |
|
|||
10 |
|
||||
11 | @eth_device |
|
|||
12 | def enable_rf() |
|
|||
13 | cmd = "xxxxx" |
|
|||
14 | payload = "xxxxxx" |
|
|||
15 |
|
||||
16 | return cmd, payload |
|
|||
17 |
|
||||
18 | #How to call this function: |
|
|||
19 | answer = enable_rf(ip, port) |
|
|||
20 |
|
4 | |||
|
5 | @author: Juan C. Espinoza | |||
21 | ''' |
|
6 | ''' | |
22 |
|
7 | |||
23 | from devices.jro_device import eth_device, IdClass |
|
8 | import os | |
24 |
|
9 | import json | ||
25 | ID_CLASS = IdClass["rc"] |
|
10 | import requests | |
26 |
|
11 | from struct import pack | ||
27 | CMD_RESET =0X01 |
|
12 | from base64 import b64encode | |
28 | CMD_ENABLE =0X02 |
|
13 | ||
29 | CMD_CHANGEIP =0X03 |
|
14 | class RCApi(object): | |
30 | CMD_STATUS =0X04 |
|
15 | ||
31 | CMD_DISABLE =0X02 |
|
16 | def __init__(self, ip, port=80): | |
32 | CMD_ECHO =0XFE |
|
17 | ||
33 |
|
18 | self.url = 'http://{}:{}/'.format(ip, port) | ||
34 | RC_CMD_RESET =0X10 |
|
19 | self.params = None | |
35 | RC_CMD_WRITE =0x50 |
|
20 | ||
36 | RC_CMD_READ =0x8000 |
|
21 | def load(self, filename): | |
37 |
|
22 | |||
38 | @eth_device(ID_CLASS) |
|
23 | self.params = json.load(open(filename)) | |
39 | def reset(): |
|
24 | print 'RC Configuration: {}'.format(self.params['name']) | |
40 |
|
25 | |||
41 | cmd = CMD_RESET |
|
26 | def status(self): | |
42 | payload = "" |
|
27 | ||
43 |
|
28 | url = os.path.join(self.url, 'status') | ||
44 | return cmd, payload |
|
29 | req = requests.get(url) | |
45 |
|
30 | return req.json() | ||
46 | @eth_device(ID_CLASS) |
|
31 | ||
47 | def change_ip(ip, mask="255.255.255.0", gateway="0.0.0.0"): |
|
32 | def read(self): | |
48 |
|
33 | |||
49 | cmd = CMD_CHANGEIP |
|
34 | url = os.path.join(self.url, 'read') | |
50 | payload = ip + '/' + mask + '/' + gateway |
|
35 | req = requests.get(url) | |
51 |
|
36 | return req.json() | ||
52 | return cmd, payload |
|
37 | ||
53 |
|
38 | def stop(self): | ||
54 | @eth_device(ID_CLASS) |
|
39 | ||
55 | def status(): |
|
40 | url = os.path.join(self.url, 'stop') | |
56 |
|
41 | req = requests.post(url) | ||
57 | cmd = CMD_STATUS |
|
42 | return req.json() | |
58 | payload = "" |
|
43 | ||
59 |
|
44 | def reset(self): | ||
60 | return cmd, payload |
|
45 | ||
61 |
|
46 | url = os.path.join(self.url, 'reset') | ||
62 | @eth_device(ID_CLASS) |
|
47 | req = requests.post(url) | |
63 | def echo(): |
|
48 | return req.json() | |
64 |
|
49 | |||
65 | cmd = CMD_ECHO |
|
50 | def start(self): | |
66 | payload = "" |
|
51 | ||
67 |
|
52 | url = os.path.join(self.url, 'start') | ||
68 | return cmd, payload |
|
53 | req = requests.post(url) | |
69 |
|
54 | return req.json() | ||
70 | @eth_device(ID_CLASS) |
|
55 | ||
71 | def read_all_device(): |
|
56 | def write(self): | |
|
57 | ||||
|
58 | url_write = os.path.join(self.url, 'write') | |||
|
59 | url_divider = os.path.join(self.url, 'divisor') | |||
|
60 | ||||
|
61 | values = zip(self.params['pulses'], | |||
|
62 | [x-1 for x in self.params['delays']]) | |||
|
63 | payload = '' | |||
|
64 | ||||
|
65 | for tup in values: | |||
|
66 | vals = pack('<HH', *tup) | |||
|
67 | payload += '\x05'+vals[0]+'\x04'+vals[1]+'\x05'+vals[2]+'\x04'+vals[3] | |||
|
68 | ||||
|
69 | req = requests.post(url_divider, | |||
|
70 | data={'divisor':int(self.params['clock_divider'])-1}) | |||
|
71 | ||||
|
72 | if 'ok' not in req.text: | |||
|
73 | print 'Error sending divider' | |||
|
74 | return False | |||
|
75 | ||||
|
76 | req = requests.post(url_write, | |||
|
77 | data=b64encode(payload)) | |||
|
78 | return req.json() | |||
72 |
|
79 | |||
73 | payload = "" |
|
80 | if __name__ == '__main__': | |
74 |
|
81 | |||
75 | return CR_CMD_READ, payload |
|
82 | ip = '10.10.10.100' | |
76 |
|
83 | filename = '/home/jespinoza/Downloads/rc_150EEJ.json' | ||
77 | @eth_device(ID_CLASS) |
|
|||
78 | def write_all_device(payload): |
|
|||
79 |
|
||||
80 | return CR_CMD_WRITE, payload |
|
|||
81 |
|
||||
82 | def read_config(ip, port): |
|
|||
83 | """ |
|
|||
84 | Output: |
|
|||
85 | parms : Dictionary with keys |
|
|||
86 |
|
||||
87 | """ |
|
|||
88 | payload = read_all_device(ip, port) |
|
|||
89 |
|
||||
90 | return data.rc_str_to_dict(payload) |
|
|||
91 |
|
||||
92 | def write_config(ip, port, parms): |
|
|||
93 | """ |
|
|||
94 | Input: |
|
|||
95 | ip : |
|
|||
96 | port : |
|
|||
97 | parms : Dictionary with keys |
|
|||
98 |
|
||||
99 | """ |
|
|||
100 |
|
||||
101 | payload = data.dict_to_rc_str(parms) |
|
|||
102 |
|
||||
103 | answer = write_all_device(ip, port, payload) |
|
|||
104 |
|
||||
105 | return answer |
|
|||
106 |
|
||||
107 | def __get_low_byte(valor): |
|
|||
108 |
|
||||
109 | return ord(valor & 0x00FF) |
|
|||
110 |
|
||||
111 | def __get_high_byte(valor): |
|
|||
112 |
|
||||
113 | return ord((valor & 0xFF00) >> 8) |
|
|||
114 |
|
||||
115 | @eth_device(ID_CLASS) |
|
|||
116 | def write_ram_memory(vector_valores, vector_tiempos): |
|
|||
117 |
|
||||
118 | l1 = len(vector_valores) |
|
|||
119 | l2 = len(vector_tiempos) |
|
|||
120 |
|
||||
121 | cad = "" |
|
|||
122 |
|
84 | |||
123 | for i in range(l1): |
|
85 | rc = RCApi(ip) | |
124 | cad += ord(84) + __get_low_byte(vector_valores[i]) + ord(85) + __get_high_byte(vector_valores[i]) + \ |
|
86 | rc.load(filename) | |
125 | ord(84) + __get_low_byte(vector_tiempos[i]) + ord(85) + __get_high_byte(vector_tiempos[i]) |
|
87 | ||
|
88 | print rc.status() | |||
|
89 | print rc.reset() | |||
|
90 | print rc.stop() | |||
|
91 | print rc.write() | |||
|
92 | print rc.start() | |||
|
93 | ||||
|
94 | ||||
|
95 | ||||
126 |
|
96 | |||
127 | return RC_CMD_WRITE, cad |
|
|||
128 |
|
97 | |||
129 | if __name__ == '__main__': |
|
|||
130 | ip = "10.10.20.150" |
|
|||
131 | port = 2000 |
|
|||
132 |
|
98 | |||
133 | print(status(ip, port)) |
|
|||
134 | print(read_config(ip, port)) |
|
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now