pvblocks 0.1.6__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.
- pvblocks/__init__.py +2 -0
- pvblocks/__main__.py +6 -0
- pvblocks/constants.py +49 -0
- pvblocks/exceptions.py +36 -0
- pvblocks/pvblocks_api.py +351 -0
- pvblocks/pvblocks_system.py +540 -0
- pvblocks-0.1.6.dist-info/METADATA +21 -0
- pvblocks-0.1.6.dist-info/RECORD +13 -0
- pvblocks-0.1.6.dist-info/WHEEL +5 -0
- pvblocks-0.1.6.dist-info/licenses/LICENSE +21 -0
- pvblocks-0.1.6.dist-info/top_level.txt +3 -0
- test.py +69 -0
- test_api.py +148 -0
pvblocks/__init__.py
ADDED
pvblocks/__main__.py
ADDED
pvblocks/constants.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
IDLE_COMMAND = 0
|
|
2
|
+
BLINK_COMMAND = 1
|
|
3
|
+
VOLTAGE_COMMAND = 2
|
|
4
|
+
MPP_COMMAND = 3
|
|
5
|
+
READ_COMMAND = 4
|
|
6
|
+
CURVE_COMMAND = 5
|
|
7
|
+
TRANSFER_CURVE_COMMAND = 6
|
|
8
|
+
EXTERNAL_MPP_COMMAND = 7
|
|
9
|
+
TRIGGERED_CURVE_COMMAND = 8
|
|
10
|
+
GET_STATUS = 13
|
|
11
|
+
WRITE_EEPROM_COMMAND = 14
|
|
12
|
+
SET_TRIGGER_COMMAND = 15
|
|
13
|
+
READ_EEPROM_COMMAND = 16
|
|
14
|
+
UPDATE_CONFIG_COMMAND = 17
|
|
15
|
+
GET_CONFIG_COMMAND = 18
|
|
16
|
+
START_FIRMWARE_UPDATE = 19
|
|
17
|
+
ENABLE_FAST_COMMUNICATIONS = 20
|
|
18
|
+
DISABLE_BROADCAST = 21
|
|
19
|
+
SELF_RESET_CMD = 24
|
|
20
|
+
TRIGGERED_READ_COMMAND = 50
|
|
21
|
+
ALIVE = 100
|
|
22
|
+
LIST_MODULES = 101
|
|
23
|
+
OPEN_MODULE = 106
|
|
24
|
+
CLOSE_MODULE = 107
|
|
25
|
+
RESET_MODULE = 108
|
|
26
|
+
RESET_CONTROLLER = 109
|
|
27
|
+
TRIGGER_ALL = 110
|
|
28
|
+
BROADCAST_THRESHOLD_EXCEEDED = 111
|
|
29
|
+
ALIVE_RR1701 = 200
|
|
30
|
+
CURVE_RUNNING = 250
|
|
31
|
+
RR1700 = 0
|
|
32
|
+
RR1701 = 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
IVMPP_READ_IVPOINT = 20
|
|
36
|
+
IVMPP_APPLY_STATE = 21
|
|
37
|
+
PV_IRR_READ_IRRADIANCES = 30
|
|
38
|
+
|
|
39
|
+
VOC = 0
|
|
40
|
+
ISC = 1
|
|
41
|
+
MPP = 2
|
|
42
|
+
VOLTAGE_BIAS = 3
|
|
43
|
+
|
|
44
|
+
ISC_TO_VOC = 0
|
|
45
|
+
SWEEP_ISC_TO_VOC = 0
|
|
46
|
+
SWEEP_VOC_TO_ISC = 1
|
|
47
|
+
EXTENT_CURVE_DELAY = 2
|
|
48
|
+
SWEEP_VOC_ISC_VOC = 4
|
|
49
|
+
SWEEP_ISC_VOC_ISC = 8
|
pvblocks/exceptions.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class NoResponseException(Exception):
|
|
2
|
+
'''No response from system.'''
|
|
3
|
+
def __str__(self):
|
|
4
|
+
return self.__doc__
|
|
5
|
+
|
|
6
|
+
class UnexpectedResponseException(Exception):
|
|
7
|
+
'''Unexpected response from system.'''
|
|
8
|
+
def __str__(self):
|
|
9
|
+
return self.__doc__
|
|
10
|
+
|
|
11
|
+
class NoReadDataImplementedException(Exception):
|
|
12
|
+
'''No read_data implemented for this blocktype.'''
|
|
13
|
+
|
|
14
|
+
def __str__(self):
|
|
15
|
+
return self.__doc__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CannotOpenBlockException(Exception):
|
|
19
|
+
'''Cannot open PVBlock.'''
|
|
20
|
+
|
|
21
|
+
def __str__(self):
|
|
22
|
+
return self.__doc__
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MethodNotSupportedException(Exception):
|
|
26
|
+
'''Method not supported for PVBlock.'''
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
return self.__doc__
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PvBlocksIsNoneException(Exception):
|
|
33
|
+
'''PVBlock is None'''
|
|
34
|
+
|
|
35
|
+
def __str__(self):
|
|
36
|
+
return self.__doc__
|
pvblocks/pvblocks_api.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
from . import VERSION
|
|
2
|
+
from . import exceptions
|
|
3
|
+
from . import constants
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
EndOfLine = '\r\n'
|
|
8
|
+
|
|
9
|
+
def show_version():
|
|
10
|
+
return VERSION
|
|
11
|
+
|
|
12
|
+
def get_channel_number(usbNr, boardNr, channelNr):
|
|
13
|
+
return channelNr + 1 + (boardNr*8) + usbNr*32
|
|
14
|
+
|
|
15
|
+
def extract_hex_values(guid_string):
|
|
16
|
+
xx = guid_string[6:8]
|
|
17
|
+
yy = guid_string[21:23]
|
|
18
|
+
return int(xx, 16), int(yy, 16)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# (usb nr, board nr, channel nr), with slotnr = None for a temperature sensor
|
|
22
|
+
def GetPosition(guid_string):
|
|
23
|
+
(BlockNr, UsbNr) = extract_hex_values(guid_string)
|
|
24
|
+
|
|
25
|
+
if BlockNr > 100:
|
|
26
|
+
return (UsbNr, int((BlockNr -101)/8), (BlockNr -101)%8)
|
|
27
|
+
|
|
28
|
+
return (UsbNr, BlockNr-64, None)
|
|
29
|
+
|
|
30
|
+
def create_rr1741_sensors(input_array):
|
|
31
|
+
result = []
|
|
32
|
+
for md in input_array:
|
|
33
|
+
sens = md['sensors'][0]
|
|
34
|
+
result.append({'id': sens['id'], 'name': sens['name'], 'description': sens['description']})
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
def create_rr1727_sensors(input_array):
|
|
38
|
+
result = []
|
|
39
|
+
for sens in input_array:
|
|
40
|
+
result.append({'id': sens['id'], 'name': sens['name'], 'description': sens['description']})
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PvBlocksApi(object):
|
|
45
|
+
TYPES = {
|
|
46
|
+
20: 'IV/MPP IV-Curve control and measure PvBlock',
|
|
47
|
+
27: 'IV/MPP IV-Curve control and measure PvBlock',
|
|
48
|
+
30: 'PV-IRR 4x analog voltage readout block',
|
|
49
|
+
40: 'PV-TEMP 4x Pt100 readout block',
|
|
50
|
+
41: 'PV-TEMP 2x Thermocouple T readout block',
|
|
51
|
+
50: 'PV-MOD digital modbus module'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def __init__(self, host, api_key):
|
|
55
|
+
self.PVBlocksUrl = 'http://' + host
|
|
56
|
+
self.APIkey = api_key
|
|
57
|
+
self.Blocks = []
|
|
58
|
+
self.PvBaseSystemType = constants.RR1700
|
|
59
|
+
self.token = ''
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _url(self, path):
|
|
63
|
+
return self.PVBlocksUrl + '/v1' + path
|
|
64
|
+
|
|
65
|
+
def get_token(self):
|
|
66
|
+
resp = requests.post(self._url('/authentication/Login'), json={'key': self.APIkey})
|
|
67
|
+
if resp.status_code != 200:
|
|
68
|
+
# This means something went wrong.
|
|
69
|
+
raise Exception('POST /authentication/Login {}'.format(resp.status_code))
|
|
70
|
+
else:
|
|
71
|
+
return resp.json()['bearer']
|
|
72
|
+
|
|
73
|
+
def get(self, endpoint, expected_response_code=200, json_response=True):
|
|
74
|
+
resp = requests.get(self._url(endpoint), headers={'Authorization': 'Bearer ' + self.token})
|
|
75
|
+
if resp.status_code != expected_response_code:
|
|
76
|
+
self.token = self.get_token()
|
|
77
|
+
resp = requests.get(
|
|
78
|
+
self._url(endpoint), headers={'Authorization': 'Bearer ' + self.token})
|
|
79
|
+
if resp.status_code != expected_response_code:
|
|
80
|
+
raise Exception('GET /' + endpoint + '{}'.format(resp.status_code))
|
|
81
|
+
else:
|
|
82
|
+
if json_response:
|
|
83
|
+
return resp.json()
|
|
84
|
+
else:
|
|
85
|
+
pass
|
|
86
|
+
def post(self, endpoint, payload, expected_response_code=201, json_response=True):
|
|
87
|
+
resp = requests.post(self._url(endpoint), headers={'Authorization': 'Bearer ' + self.token}, json=payload)
|
|
88
|
+
if resp.status_code != expected_response_code:
|
|
89
|
+
self.token = self.get_token()
|
|
90
|
+
resp = requests.post(
|
|
91
|
+
self._url(endpoint), headers={'Authorization': 'Bearer ' + self.token}, json=payload)
|
|
92
|
+
if resp.status_code != expected_response_code:
|
|
93
|
+
raise Exception('POST /' + endpoint + '{}'.format(resp.status_code))
|
|
94
|
+
else:
|
|
95
|
+
if json_response:
|
|
96
|
+
return resp.json()
|
|
97
|
+
else:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
def put(self, endpoint, payload, expected_response_code=204):
|
|
101
|
+
resp = requests.put(self._url(endpoint), headers={'Authorization': 'Bearer ' + self.token}, json=payload)
|
|
102
|
+
if resp.status_code != expected_response_code:
|
|
103
|
+
self.token = self.get_token()
|
|
104
|
+
resp = requests.put(
|
|
105
|
+
self._url(endpoint), headers={'Authorization': 'Bearer ' + self.token}, json=payload)
|
|
106
|
+
if resp.status_code != expected_response_code:
|
|
107
|
+
raise Exception('PUT /' + endpoint + '{}'.format(resp.status_code))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def delete(self, endpoint, expected_response_code=204):
|
|
111
|
+
resp = requests.delete(self._url(endpoint), headers={'Authorization': 'Bearer ' + self.token})
|
|
112
|
+
if resp.status_code != expected_response_code:
|
|
113
|
+
self.token = self.get_token()
|
|
114
|
+
resp = requests.delete(
|
|
115
|
+
self._url(endpoint), headers={'Authorization': 'Bearer ' + self.token})
|
|
116
|
+
if resp.status_code != expected_response_code:
|
|
117
|
+
raise Exception('DEL /' + endpoint + '{}'.format(resp.status_code))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_api_version(self):
|
|
122
|
+
resp = requests.get(self._url('/info'))
|
|
123
|
+
if resp.status_code != 200:
|
|
124
|
+
# This means something went wrong.
|
|
125
|
+
raise Exception('GET /info {}'.format(resp.status_code))
|
|
126
|
+
else:
|
|
127
|
+
return resp.json()['version']
|
|
128
|
+
|
|
129
|
+
def Online(self):
|
|
130
|
+
return self.get_api_version() == 'v1'
|
|
131
|
+
|
|
132
|
+
def Init(self):
|
|
133
|
+
if self.Online():
|
|
134
|
+
count = self.scan_blocks()
|
|
135
|
+
print('Scanned {} blocks'.format(count))
|
|
136
|
+
else:
|
|
137
|
+
print('System not online')
|
|
138
|
+
|
|
139
|
+
def get_pvdevices(self):
|
|
140
|
+
endpoint = '/PvDevice'
|
|
141
|
+
return self.get(endpoint)
|
|
142
|
+
|
|
143
|
+
def create_pvdevice(self, label):
|
|
144
|
+
endpoint = '/PvDevice'
|
|
145
|
+
payload = {
|
|
146
|
+
"Name": label,
|
|
147
|
+
"Serial": "",
|
|
148
|
+
"Manufacturer": "",
|
|
149
|
+
"ManufacturerCode": "",
|
|
150
|
+
"Material": "",
|
|
151
|
+
"IsBiFacial": False,
|
|
152
|
+
"Voc": 0,
|
|
153
|
+
"Isc": 0,
|
|
154
|
+
"Power": 0,
|
|
155
|
+
"Alpha": 0,
|
|
156
|
+
"Beta": 0,
|
|
157
|
+
"TemperatureCoefficient": 0,
|
|
158
|
+
"Area": 0,
|
|
159
|
+
"CellCount": 0,
|
|
160
|
+
"StringCount": 0,
|
|
161
|
+
"TemperatureId": 0,
|
|
162
|
+
"IrradianceId": 0
|
|
163
|
+
}
|
|
164
|
+
return self.post(endpoint, payload)
|
|
165
|
+
|
|
166
|
+
def delete_pvdevice(self, id):
|
|
167
|
+
endpoint = '/PvDevice/{}'.format(id)
|
|
168
|
+
self.delete(endpoint)
|
|
169
|
+
|
|
170
|
+
def get_pvblocks(self):
|
|
171
|
+
endpoint = '/Block'
|
|
172
|
+
return self.get(endpoint)
|
|
173
|
+
|
|
174
|
+
def list_all_unique_identifiers(self):
|
|
175
|
+
blocks = self.get_pvblocks()
|
|
176
|
+
result = []
|
|
177
|
+
for b in blocks:
|
|
178
|
+
result.append((b['uniqueIdentifier']))
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
def scan_blocks(self):
|
|
182
|
+
blks = self.get_pvblocks()
|
|
183
|
+
module_count = len(blks)
|
|
184
|
+
self.Blocks = []
|
|
185
|
+
for b in blks:
|
|
186
|
+
(usb, board, channel) = GetPosition(b['uniqueIdentifier'])
|
|
187
|
+
if b['type'] == 'RR-1727':
|
|
188
|
+
sensors = create_rr1727_sensors(b['measurementDevices'][0]['sensors'])
|
|
189
|
+
if b['type'] == 'RR-1741':
|
|
190
|
+
sensors = create_rr1741_sensors(b['measurementDevices'])
|
|
191
|
+
|
|
192
|
+
self.Blocks.append({ "label": b["label"], "id": b["id"],"guid": b['uniqueIdentifier'],
|
|
193
|
+
"usbNr": usb, "boardNr": board, "channelNr": channel,
|
|
194
|
+
"type": b['type'], "sensors": sensors, 'commands': b['availableCommands']})
|
|
195
|
+
return module_count
|
|
196
|
+
|
|
197
|
+
def reset_block(self, guid):
|
|
198
|
+
endpoint = '/Hardware/%s/reset' % (guid)
|
|
199
|
+
return self.get(endpoint, expected_response_code=204, json_response=False)
|
|
200
|
+
|
|
201
|
+
def write_block_label(self, id, label):
|
|
202
|
+
endpoint = '/Block/Label/{}'.format(id)
|
|
203
|
+
payload = {'position': 0, 'label': label}
|
|
204
|
+
return self.post(endpoint, payload, expected_response_code=200)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def write_rr1727_default_sweep(self, id, points, integration_cycles, sweepType ):
|
|
208
|
+
endpoint = '/Command/updateIvCurveParameters/%d' % (id)
|
|
209
|
+
payload = {'points': points, 'delay': integration_cycles, 'sweepstyle': sweepType}
|
|
210
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def read_rr1727_calibration_values(self, guid):
|
|
214
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
215
|
+
payload = {'CommandName': 'ReadFloatEeprom', 'Parameters': {'count': 4, 'address': 4}}
|
|
216
|
+
return self.post(endpoint, payload, expected_response_code=200)['1']
|
|
217
|
+
|
|
218
|
+
def write_rr1727_calibration_values(self, guid, A, B, C, D):
|
|
219
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
220
|
+
payload = {'CommandName': 'WriteFloatEeprom', 'Parameters': {'flt': A, 'address': 4}}
|
|
221
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
222
|
+
payload = {'CommandName': 'WriteFloatEeprom', 'Parameters': {'flt': B, 'address': 8}}
|
|
223
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
224
|
+
payload = {'CommandName': 'WriteFloatEeprom', 'Parameters': {'flt': C, 'address': 12}}
|
|
225
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
226
|
+
payload = {'CommandName': 'WriteFloatEeprom', 'Parameters': {'flt': D, 'address': 16}}
|
|
227
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
228
|
+
self.reset_block(guid)
|
|
229
|
+
|
|
230
|
+
def get_schedules(self):
|
|
231
|
+
endpoint = '/Pipeline'
|
|
232
|
+
return self.get(endpoint)
|
|
233
|
+
|
|
234
|
+
def create_schedule(self, interval='* * * * *', daylightOnly=False):
|
|
235
|
+
if interval == 1:
|
|
236
|
+
crontab = '* * * * *'
|
|
237
|
+
else:
|
|
238
|
+
crontab ='*/%d * * * *' % (interval)
|
|
239
|
+
|
|
240
|
+
description = 'Execute every %d minutes' % (interval)
|
|
241
|
+
if daylightOnly:
|
|
242
|
+
description += ' during daylight'
|
|
243
|
+
|
|
244
|
+
endpoint = "/Pipeline"
|
|
245
|
+
payload = {'description': description, 'daylightOnly': daylightOnly, 'cronTabs': [crontab]}
|
|
246
|
+
return self.post(endpoint, payload, expected_response_code=201)
|
|
247
|
+
|
|
248
|
+
def delete_schedule(self, id):
|
|
249
|
+
endpoint = '/Pipeline/{}'.format(id)
|
|
250
|
+
self.delete(endpoint)
|
|
251
|
+
|
|
252
|
+
def enable_scheduler(self):
|
|
253
|
+
endpoint = '/Pipeline/enable'
|
|
254
|
+
self.post(endpoint, {}, expected_response_code=204, json_response=False)
|
|
255
|
+
|
|
256
|
+
def disable_scheduler(self):
|
|
257
|
+
endpoint = '/Pipeline/disable'
|
|
258
|
+
self.post(endpoint, {}, expected_response_code=204, json_response=False)
|
|
259
|
+
|
|
260
|
+
def update_sensor_description(self, id, label):
|
|
261
|
+
endpoint = '/Sensor/{}'.format(id)
|
|
262
|
+
original = self.get(endpoint)
|
|
263
|
+
sensor = {'description': label,
|
|
264
|
+
'enabled': original['enabled'],
|
|
265
|
+
'unit': original['unit'],
|
|
266
|
+
'calibration': original['calibration'],
|
|
267
|
+
'options': original['options'],
|
|
268
|
+
'name': original['name'] }
|
|
269
|
+
self.put(endpoint, sensor)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def attach_sensor_to_pvdevice(self, sensor_id, pvdevice_id):
|
|
273
|
+
endpoint = '/Sensor/%d/attach/%d' % (sensor_id, pvdevice_id)
|
|
274
|
+
payload = {}
|
|
275
|
+
self.post(endpoint, payload, expected_response_code=201)
|
|
276
|
+
|
|
277
|
+
def add_command_to_schedule(self, schedule_id, blockId, command):
|
|
278
|
+
endpoint = '/Pipeline/%d/command' % (schedule_id)
|
|
279
|
+
payload = { 'pvBlockId': blockId, 'commandName': command['name'], 'parameters': command['defaultParameters'], 'withTrigger': command['defaultWithTrigger']}
|
|
280
|
+
self.post(endpoint, payload, expected_response_code=201)
|
|
281
|
+
|
|
282
|
+
# State definitions:
|
|
283
|
+
# Voc = 0,
|
|
284
|
+
# Isc = 1,
|
|
285
|
+
# Mpp = 2,
|
|
286
|
+
# Vbias = 3
|
|
287
|
+
|
|
288
|
+
def write_rr1727_state(self, guid, state, voltageBias=0, store=True):
|
|
289
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
290
|
+
payload = {'CommandName': 'ApplyState', 'Parameters': {'state': state, 'voltageBias': voltageBias}}
|
|
291
|
+
self.post(endpoint, payload, expected_response_code=200)
|
|
292
|
+
if store:
|
|
293
|
+
endpoint = '/Hardware/%s/storeIvMppState' % (guid)
|
|
294
|
+
payload = {'guid': guid, 'state': state, 'vbias': voltageBias}
|
|
295
|
+
self.put(endpoint, payload, expected_response_code=201)
|
|
296
|
+
|
|
297
|
+
def ApplyVoc(self, guid):
|
|
298
|
+
self.write_rr1727_state(guid, 0, store=False)
|
|
299
|
+
|
|
300
|
+
def ApplyIsc(self, guid):
|
|
301
|
+
self.write_rr1727_state(guid, 1, store=False)
|
|
302
|
+
|
|
303
|
+
def write_rr1727_integration_time(self, guid, integration_time):
|
|
304
|
+
if integration_time not in [1,4,9,15]:
|
|
305
|
+
raise ValueError("Integration time must be in [1,4,9,15]")
|
|
306
|
+
|
|
307
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
308
|
+
payload = {'CommandName': 'WriteEeprom', 'Parameters': {'data': str(integration_time), 'address': 116}}
|
|
309
|
+
self.post(endpoint, payload, expected_response_code=200)
|
|
310
|
+
self.reset_block(guid)
|
|
311
|
+
|
|
312
|
+
def read_rr1727_integration_time(self, guid):
|
|
313
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
314
|
+
payload = {'CommandName': 'ReadEeprom', 'Parameters': {'length': 1, 'address': 116}}
|
|
315
|
+
return self.post(endpoint, payload, expected_response_code=200)['1'][0]
|
|
316
|
+
|
|
317
|
+
def read_rr1727_mpp_values(self, guid):
|
|
318
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
319
|
+
payload = {'CommandName': 'ReadFloatEeprom', 'Parameters': {'count': 4, 'address': 64}}
|
|
320
|
+
return self.post(endpoint, payload, expected_response_code=200)['1']
|
|
321
|
+
|
|
322
|
+
def write_rr1727_mpp_values(self, guid, p1, p2, p3, p4):
|
|
323
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
324
|
+
payload = {'CommandName': 'WriteFloatEeprom', 'Parameters': {'flt': p1, 'address': 64}}
|
|
325
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
326
|
+
payload = {'CommandName': 'WriteFloatEeprom', 'Parameters': {'flt': p2, 'address': 68}}
|
|
327
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
328
|
+
payload = {'CommandName': 'WriteFloatEeprom', 'Parameters': {'flt': p3, 'address': 72}}
|
|
329
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
330
|
+
payload = {'CommandName': 'WriteFloatEeprom', 'Parameters': {'flt': p4, 'address': 76}}
|
|
331
|
+
self.post(endpoint, payload, expected_response_code=200, json_response=False)
|
|
332
|
+
endpoint = '/Hardware/%s/refresh-eeprom' % (guid)
|
|
333
|
+
self.get(endpoint, expected_response_code=204, json_response=False)
|
|
334
|
+
|
|
335
|
+
def read_rr1741_temperatures(self, guid):
|
|
336
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
337
|
+
payload = {'CommandName': 'GetTemperatures', 'Parameters': {'direct': True}}
|
|
338
|
+
result = self.post(endpoint, payload, expected_response_code=200)
|
|
339
|
+
return [result['1']['temperature'], result['2']['temperature']]
|
|
340
|
+
|
|
341
|
+
def read_rr1727_ivpoint(self, guid):
|
|
342
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
343
|
+
payload = {'CommandName': 'MeasureDirectIvPoint'}
|
|
344
|
+
result = self.post(endpoint, payload, expected_response_code=200)
|
|
345
|
+
return [result['1']['ivpoint']['i'], result['1']['ivpoint']['v']]
|
|
346
|
+
|
|
347
|
+
def sweep_rr1727_ivcurve(self, guid, points, integration_cycles, sweepType):
|
|
348
|
+
endpoint = '/Hardware/%s/sendCommand' % (guid)
|
|
349
|
+
payload = {'CommandName': 'StartIvCurve', 'Parameters': {'points': points, 'delay': integration_cycles, 'sweepstyle': sweepType}}
|
|
350
|
+
result = self.post(endpoint, payload, expected_response_code=200)
|
|
351
|
+
return {'Voltages': result['1']['Voltages'], 'Currents': result['1']['Currents']}
|
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
from . import VERSION
|
|
2
|
+
from . import exceptions
|
|
3
|
+
from . import constants
|
|
4
|
+
import serial
|
|
5
|
+
import uuid
|
|
6
|
+
import struct
|
|
7
|
+
from time import sleep
|
|
8
|
+
from enum import IntEnum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def show_version():
|
|
13
|
+
return VERSION
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ReadSerial(ser):
|
|
17
|
+
out = []
|
|
18
|
+
while ser.inWaiting() > 0:
|
|
19
|
+
out.append(ser.read(1)[0])
|
|
20
|
+
return out
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PvBlocks(object):
|
|
24
|
+
TYPES = {
|
|
25
|
+
20: 'IV/MPP IV-Curve control and measure PvBlock',
|
|
26
|
+
27: 'IV/MPP IV-Curve control and measure PvBlock',
|
|
27
|
+
30: 'PV-IRR 4x analog voltage readout block',
|
|
28
|
+
40: 'PV-TEMP 4x Pt100 readout block',
|
|
29
|
+
41: 'PV-TEMP 2x Thermocouple T readout block',
|
|
30
|
+
50: 'PV-MOD digital modbus module'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def __init__(self, serialport):
|
|
34
|
+
# type: (UART_Adapter) -> None
|
|
35
|
+
self.uart = serial.Serial(serialport,
|
|
36
|
+
baudrate=115200,
|
|
37
|
+
bytesize=serial.EIGHTBITS,
|
|
38
|
+
parity=serial.PARITY_NONE,
|
|
39
|
+
stopbits=serial.STOPBITS_ONE,
|
|
40
|
+
timeout=1)
|
|
41
|
+
self.Blocks = []
|
|
42
|
+
self.IvMppBlocks = []
|
|
43
|
+
self.PvIrrBlocks = []
|
|
44
|
+
self.PvBaseSystemType = constants.RR1700
|
|
45
|
+
|
|
46
|
+
def init_system(self):
|
|
47
|
+
self.uart.write(serial.to_bytes([1, constants.ALIVE]))
|
|
48
|
+
sleep(0.5)
|
|
49
|
+
bts = ReadSerial(self.uart)
|
|
50
|
+
if len(bts) != 2:
|
|
51
|
+
raise exceptions.NoResponseException()
|
|
52
|
+
|
|
53
|
+
if bts[1] == constants.ALIVE_RR1701:
|
|
54
|
+
self.PvBaseSystemType = constants.RR1701
|
|
55
|
+
|
|
56
|
+
return bts[0] == 3 and bts[1] == constants.ALIVE or bts[1] == constants.ALIVE_RR1701
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def close_system(self):
|
|
60
|
+
self.uart.close()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def scan_blocks(self):
|
|
64
|
+
self.uart.write(serial.to_bytes([1, constants.LIST_MODULES]))
|
|
65
|
+
sleep(2)
|
|
66
|
+
bts = ReadSerial(self.uart)
|
|
67
|
+
|
|
68
|
+
if (bts[0] != 3) or (bts[1] != constants.LIST_MODULES):
|
|
69
|
+
raise exceptions.UnexpectedResponseException()
|
|
70
|
+
|
|
71
|
+
module_count = bts[3]
|
|
72
|
+
self.Blocks = []
|
|
73
|
+
self.IvMppBlocks = []
|
|
74
|
+
self.PvIrrBlocks = []
|
|
75
|
+
for index in range(module_count):
|
|
76
|
+
match bts[(index * 9) + 4 + 8]:
|
|
77
|
+
case 20:
|
|
78
|
+
blck = IvMpp(bts[(index * 9) + 4: (index * 9) + 13], self.uart)
|
|
79
|
+
self.IvMppBlocks.append(blck)
|
|
80
|
+
case 27:
|
|
81
|
+
blck = IvMpp27(bts[(index * 9) + 4: (index * 9) + 13], self.uart)
|
|
82
|
+
self.IvMppBlocks.append(blck)
|
|
83
|
+
case 30:
|
|
84
|
+
blck = PvIrr(bts[(index * 9) + 4: (index * 9) + 13], self.uart)
|
|
85
|
+
self.PvIrrBlocks.append(blck)
|
|
86
|
+
case _:
|
|
87
|
+
blck = PvBlock(bts[(index * 9) + 4: (index * 9) + 13], self.uart)
|
|
88
|
+
self.Blocks.append(blck)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
return module_count > 0
|
|
92
|
+
|
|
93
|
+
def reset_controller(self):
|
|
94
|
+
if not self.uart.is_open:
|
|
95
|
+
self.uart.open()
|
|
96
|
+
|
|
97
|
+
self.uart.write(serial.to_bytes([1, constants.RESET_CONTROLLER]))
|
|
98
|
+
sleep(3)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class PvBlock(object):
|
|
104
|
+
def __init__(self, bytes, uart):
|
|
105
|
+
self.bytes = bytes[0:8]
|
|
106
|
+
id = int.from_bytes(bytearray(self.bytes), 'little')
|
|
107
|
+
self.Guid = uuid.UUID(int=id)
|
|
108
|
+
self.Type = bytes[8]
|
|
109
|
+
self.uart = uart
|
|
110
|
+
self.node = 2
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def reset_block(self):
|
|
114
|
+
self.uart.write(serial.to_bytes([1,
|
|
115
|
+
constants.RESET_MODULE,
|
|
116
|
+
self.bytes[0],
|
|
117
|
+
self.bytes[1],
|
|
118
|
+
self.bytes[2],
|
|
119
|
+
self.bytes[3],
|
|
120
|
+
self.bytes[4],
|
|
121
|
+
self.bytes[5],
|
|
122
|
+
self.bytes[6],
|
|
123
|
+
self.bytes[7]]))
|
|
124
|
+
sleep(0.5)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def open(self):
|
|
128
|
+
self.uart.write(serial.to_bytes([1,
|
|
129
|
+
constants.OPEN_MODULE,
|
|
130
|
+
0,
|
|
131
|
+
self.bytes[0],
|
|
132
|
+
self.bytes[1],
|
|
133
|
+
self.bytes[2],
|
|
134
|
+
self.bytes[3],
|
|
135
|
+
self.bytes[4],
|
|
136
|
+
self.bytes[5],
|
|
137
|
+
self.bytes[6],
|
|
138
|
+
self.bytes[7]]))
|
|
139
|
+
sleep(0.5)
|
|
140
|
+
bts = ReadSerial(self.uart)
|
|
141
|
+
|
|
142
|
+
return len(bts) == 3
|
|
143
|
+
|
|
144
|
+
def close(self):
|
|
145
|
+
self.uart.write(serial.to_bytes([1,
|
|
146
|
+
constants.CLOSE_MODULE,
|
|
147
|
+
self.bytes[0],
|
|
148
|
+
self.bytes[1],
|
|
149
|
+
self.bytes[2],
|
|
150
|
+
self.bytes[3],
|
|
151
|
+
self.bytes[4],
|
|
152
|
+
self.bytes[5],
|
|
153
|
+
self.bytes[6],
|
|
154
|
+
self.bytes[7]]))
|
|
155
|
+
sleep(0.5)
|
|
156
|
+
bts = ReadSerial(self.uart)
|
|
157
|
+
|
|
158
|
+
return len(bts) == 3
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def read_statusbyte(self):
|
|
165
|
+
self.open()
|
|
166
|
+
self.uart.write(serial.to_bytes([self.node, constants.GET_STATUS]))
|
|
167
|
+
sleep(0.5)
|
|
168
|
+
bts = ReadSerial(self.uart)
|
|
169
|
+
self.close()
|
|
170
|
+
if len(bts) < 10:
|
|
171
|
+
raise exceptions.UnexpectedResponseException()
|
|
172
|
+
return StatusByte(bts)
|
|
173
|
+
|
|
174
|
+
def get_info(self):
|
|
175
|
+
status = self.read_statusbyte()
|
|
176
|
+
d = {'firmware': status.firmware, 'hardware': status.hardware}
|
|
177
|
+
return d
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class IvMpp(PvBlock):
|
|
183
|
+
def read_ivpoint(self):
|
|
184
|
+
|
|
185
|
+
self.open()
|
|
186
|
+
self.uart.write(serial.to_bytes([2, constants.READ_COMMAND]))
|
|
187
|
+
sleep(0.5)
|
|
188
|
+
bts = ReadSerial(self.uart)
|
|
189
|
+
self.close()
|
|
190
|
+
if len(bts) < 15:
|
|
191
|
+
raise exceptions.UnexpectedResponseException()
|
|
192
|
+
|
|
193
|
+
if bts[2] != 12:
|
|
194
|
+
raise exceptions.UnexpectedResponseException()
|
|
195
|
+
|
|
196
|
+
r1 = int.from_bytes(bts[3:7], "little") / 10000.0
|
|
197
|
+
r2 = int.from_bytes(bts[7:11], "little") / 100000.0
|
|
198
|
+
ivpoint = IvPoint(r1, r2)
|
|
199
|
+
|
|
200
|
+
return ivpoint
|
|
201
|
+
|
|
202
|
+
def ApplyVoc(self):
|
|
203
|
+
self.open()
|
|
204
|
+
self.uart.write(serial.to_bytes([2, constants.IDLE_COMMAND]))
|
|
205
|
+
sleep(0.5)
|
|
206
|
+
self.close()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def ApplyMpp(self):
|
|
210
|
+
self.open()
|
|
211
|
+
self.uart.write(serial.to_bytes([2, constants.MPP_COMMAND]))
|
|
212
|
+
sleep(0.5)
|
|
213
|
+
self.close()
|
|
214
|
+
|
|
215
|
+
def ApplyIsc(self):
|
|
216
|
+
voltage = 0.0;
|
|
217
|
+
self.open()
|
|
218
|
+
bytes = list(((int)(1000 * voltage)).to_bytes(4, "little"))
|
|
219
|
+
self.uart.write(serial.to_bytes([2, constants.VOLTAGE_COMMAND, bytes[0], bytes[1], bytes[2], bytes[3]]))
|
|
220
|
+
sleep(0.5)
|
|
221
|
+
self.close()
|
|
222
|
+
|
|
223
|
+
def ApplyVoltageBias(self, voltage):
|
|
224
|
+
self.open()
|
|
225
|
+
bytes = list(((int)(1000 * voltage)).to_bytes(4, "little"))
|
|
226
|
+
self.uart.write(
|
|
227
|
+
serial.to_bytes([2, constants.VOLTAGE_COMMAND, bytes[0], bytes[1], bytes[2], bytes[3]]))
|
|
228
|
+
sleep(0.5)
|
|
229
|
+
self.close()
|
|
230
|
+
|
|
231
|
+
def measure_ivcurve(self, points, delay_ms, sweepstyle):
|
|
232
|
+
self.open()
|
|
233
|
+
|
|
234
|
+
self.uart.write(
|
|
235
|
+
serial.to_bytes([2, constants.SET_TRIGGER_COMMAND, 0]))
|
|
236
|
+
|
|
237
|
+
sleep(0.5)
|
|
238
|
+
|
|
239
|
+
self.uart.write(
|
|
240
|
+
serial.to_bytes([2, constants.CURVE_COMMAND, points, delay_ms, 0, 0, 0, 0, sweepstyle]))
|
|
241
|
+
|
|
242
|
+
while self.uart.inWaiting() != 3:
|
|
243
|
+
sleep(0.01)
|
|
244
|
+
|
|
245
|
+
bts = ReadSerial(self.uart)
|
|
246
|
+
|
|
247
|
+
status = self.read_statusbyte()
|
|
248
|
+
while status.mode == 5:
|
|
249
|
+
sleep(0.5)
|
|
250
|
+
status = self.read_statusbyte()
|
|
251
|
+
|
|
252
|
+
self.close()
|
|
253
|
+
points_measured = status.statusbytes[0]
|
|
254
|
+
curve = self.transfer_curve(points_measured)
|
|
255
|
+
|
|
256
|
+
return curve
|
|
257
|
+
|
|
258
|
+
def transfer_curve(self, points):
|
|
259
|
+
self.open()
|
|
260
|
+
self.uart.write(serial.to_bytes([2, constants.TRANSFER_CURVE_COMMAND]))
|
|
261
|
+
sleep(0.5)
|
|
262
|
+
availablebytes = 8 + (points * 8) + 1
|
|
263
|
+
toread = self.uart.inWaiting()
|
|
264
|
+
while toread != availablebytes:
|
|
265
|
+
toread = self.uart.inWaiting()
|
|
266
|
+
print(toread)
|
|
267
|
+
sleep(0.1)
|
|
268
|
+
bts = ReadSerial(self.uart)
|
|
269
|
+
self.close()
|
|
270
|
+
|
|
271
|
+
voltages = []
|
|
272
|
+
currents = []
|
|
273
|
+
|
|
274
|
+
for i in range(1, int((availablebytes - 1)/8)):
|
|
275
|
+
index = (i * 8) + 1
|
|
276
|
+
voltages.append(int.from_bytes(bts[index:(index+4)], "little") / 10000.0)
|
|
277
|
+
index = index + 4
|
|
278
|
+
currents.append(int.from_bytes(bts[index:(index+4)], "little") / 100000.0)
|
|
279
|
+
|
|
280
|
+
return {'voltages': voltages, 'currents': currents}
|
|
281
|
+
|
|
282
|
+
def read_calibration(self):
|
|
283
|
+
c = {'A': 0.0, 'B': 0.0, 'C': 0.0, 'D': 0.0}
|
|
284
|
+
bts = self.read_eeprom(4, 16)
|
|
285
|
+
c['A'] = struct.unpack('<f', bytearray(bts[0:4]))
|
|
286
|
+
c['B'] = struct.unpack('<f', bytearray(bts[4:8]))
|
|
287
|
+
c['C'] = struct.unpack('<f', bytearray(bts[8:12]))
|
|
288
|
+
c['D'] = struct.unpack('<f', bytearray(bts[12:16]))
|
|
289
|
+
|
|
290
|
+
return c
|
|
291
|
+
|
|
292
|
+
def read_eeprom(self, address, length):
|
|
293
|
+
bts = list(address.to_bytes(2, 'little'))
|
|
294
|
+
self.open()
|
|
295
|
+
self.uart.write(
|
|
296
|
+
serial.to_bytes([2, constants.READ_EEPROM_COMMAND, length, bts[0], bts[1]]))
|
|
297
|
+
|
|
298
|
+
while self.uart.inWaiting() != length + 3:
|
|
299
|
+
sleep(0.01)
|
|
300
|
+
|
|
301
|
+
bts = ReadSerial(self.uart)
|
|
302
|
+
self.close()
|
|
303
|
+
return bts[3:]
|
|
304
|
+
|
|
305
|
+
def write_voltage_calibration(self, A, B):
|
|
306
|
+
ba = list(bytearray(struct.pack("<f", A)))
|
|
307
|
+
self.write_eeprom(ba, 4)
|
|
308
|
+
ba = list(bytearray(struct.pack("<f", B)))
|
|
309
|
+
self.write_eeprom(ba, 8)
|
|
310
|
+
self.reset_block()
|
|
311
|
+
sleep(3)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def write_current_calibration(self,C, D):
|
|
315
|
+
ba = list(bytearray(struct.pack("<f", C)))
|
|
316
|
+
self.write_eeprom(ba, 12)
|
|
317
|
+
ba = list(bytearray(struct.pack("<f", D)))
|
|
318
|
+
self.write_eeprom(ba, 16)
|
|
319
|
+
self.reset_block()
|
|
320
|
+
sleep(3)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def write_eeprom(self, data, address):
|
|
324
|
+
bts = list(address.to_bytes(self.node, 'little'))
|
|
325
|
+
tx = [self.node, constants.WRITE_EEPROM_COMMAND, len(data), bts[0], bts[1]]
|
|
326
|
+
|
|
327
|
+
self.open()
|
|
328
|
+
self.uart.write(serial.to_bytes(tx + data))
|
|
329
|
+
self.close()
|
|
330
|
+
sleep(0.5)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class IvMpp27(PvBlock):
|
|
334
|
+
def __init__(self, bytes, uart):
|
|
335
|
+
super().__init__(bytes, uart)
|
|
336
|
+
self.node = bytes[0]
|
|
337
|
+
|
|
338
|
+
def reset(self):
|
|
339
|
+
self.open()
|
|
340
|
+
self.uart.write(serial.to_bytes([self.node, constants.SELF_RESET_CMD]))
|
|
341
|
+
sleep(0.5)
|
|
342
|
+
self.close()
|
|
343
|
+
|
|
344
|
+
def read_ivpoint(self):
|
|
345
|
+
|
|
346
|
+
self.open()
|
|
347
|
+
self.uart.write(serial.to_bytes([self.node, constants.READ_COMMAND]))
|
|
348
|
+
sleep(0.5)
|
|
349
|
+
bts = ReadSerial(self.uart)
|
|
350
|
+
self.close()
|
|
351
|
+
if len(bts) < 15:
|
|
352
|
+
raise exceptions.UnexpectedResponseException()
|
|
353
|
+
|
|
354
|
+
if bts[2] != 12:
|
|
355
|
+
raise exceptions.UnexpectedResponseException()
|
|
356
|
+
|
|
357
|
+
r1 = int.from_bytes(bts[3:7], "little") / 10000.0
|
|
358
|
+
r2 = int.from_bytes(bts[7:11], "little") / 100000.0
|
|
359
|
+
ivpoint = IvPoint(r1, r2)
|
|
360
|
+
|
|
361
|
+
return ivpoint
|
|
362
|
+
|
|
363
|
+
def ApplyVoc(self):
|
|
364
|
+
self.open()
|
|
365
|
+
self.uart.write(serial.to_bytes([self.node, constants.IDLE_COMMAND]))
|
|
366
|
+
sleep(0.5)
|
|
367
|
+
self.close()
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def ApplyMpp(self):
|
|
371
|
+
self.open()
|
|
372
|
+
self.uart.write(serial.to_bytes([self.node, constants.MPP_COMMAND]))
|
|
373
|
+
sleep(0.5)
|
|
374
|
+
self.close()
|
|
375
|
+
|
|
376
|
+
def ApplyIsc(self):
|
|
377
|
+
voltage = 0.0;
|
|
378
|
+
self.open()
|
|
379
|
+
bytes = list(((int)(1000 * voltage)).to_bytes(4, "little"))
|
|
380
|
+
self.uart.write(serial.to_bytes([self.node, constants.VOLTAGE_COMMAND, bytes[0], bytes[1], bytes[2], bytes[3]]))
|
|
381
|
+
sleep(0.5)
|
|
382
|
+
self.close()
|
|
383
|
+
|
|
384
|
+
def ApplyVoltageBias(self, voltage):
|
|
385
|
+
self.open()
|
|
386
|
+
bytes = list(((int)(1000 * voltage)).to_bytes(4, "little"))
|
|
387
|
+
self.uart.write(
|
|
388
|
+
serial.to_bytes([self.node, constants.VOLTAGE_COMMAND, bytes[0], bytes[1], bytes[2], bytes[3]]))
|
|
389
|
+
sleep(0.5)
|
|
390
|
+
self.close()
|
|
391
|
+
|
|
392
|
+
def measure_ivcurve(self, points, delay_ms, sweepstyle):
|
|
393
|
+
self.open()
|
|
394
|
+
|
|
395
|
+
self.uart.write(
|
|
396
|
+
serial.to_bytes([self.node, constants.SET_TRIGGER_COMMAND, 0]))
|
|
397
|
+
|
|
398
|
+
sleep(0.5)
|
|
399
|
+
|
|
400
|
+
self.uart.write(
|
|
401
|
+
serial.to_bytes([self.node, constants.CURVE_COMMAND, points, delay_ms, 0, 0, 0, 0, sweepstyle]))
|
|
402
|
+
|
|
403
|
+
while self.uart.inWaiting() != 3:
|
|
404
|
+
sleep(0.01)
|
|
405
|
+
|
|
406
|
+
bts = ReadSerial(self.uart)
|
|
407
|
+
|
|
408
|
+
status = self.read_statusbyte()
|
|
409
|
+
while status.mode == 5:
|
|
410
|
+
sleep(0.5)
|
|
411
|
+
status = self.read_statusbyte()
|
|
412
|
+
|
|
413
|
+
self.close()
|
|
414
|
+
points_measured = status.statusbytes[0]
|
|
415
|
+
curve = self.transfer_curve(points_measured)
|
|
416
|
+
|
|
417
|
+
return curve
|
|
418
|
+
|
|
419
|
+
def transfer_curve(self, points):
|
|
420
|
+
self.open()
|
|
421
|
+
self.uart.write(serial.to_bytes([self.node, constants.TRANSFER_CURVE_COMMAND]))
|
|
422
|
+
sleep(0.5)
|
|
423
|
+
availablebytes = 8 + (points * 8) + 1
|
|
424
|
+
toread = self.uart.inWaiting()
|
|
425
|
+
while toread != availablebytes:
|
|
426
|
+
toread = self.uart.inWaiting()
|
|
427
|
+
print(toread)
|
|
428
|
+
sleep(0.1)
|
|
429
|
+
bts = ReadSerial(self.uart)
|
|
430
|
+
self.close()
|
|
431
|
+
|
|
432
|
+
voltages = []
|
|
433
|
+
currents = []
|
|
434
|
+
|
|
435
|
+
for i in range(1, int((availablebytes - 1)/8)):
|
|
436
|
+
index = (i * 8) + 1
|
|
437
|
+
voltages.append(int.from_bytes(bts[index:(index+4)], "little") / 10000.0)
|
|
438
|
+
index = index + 4
|
|
439
|
+
currents.append(int.from_bytes(bts[index:(index+4)], "little") / 100000.0)
|
|
440
|
+
|
|
441
|
+
return {'voltages': voltages, 'currents': currents}
|
|
442
|
+
|
|
443
|
+
def read_calibration(self):
|
|
444
|
+
c = {'A': 0.0, 'B': 0.0, 'C': 0.0, 'D': 0.0}
|
|
445
|
+
bts = self.read_eeprom(4, 16)
|
|
446
|
+
|
|
447
|
+
c['A'] = struct.unpack('<f', bytearray(bts[0:4]))
|
|
448
|
+
c['B'] = struct.unpack('<f', bytearray(bts[4:8]))
|
|
449
|
+
c['C'] = struct.unpack('<f', bytearray(bts[8:12]))
|
|
450
|
+
c['D'] = struct.unpack('<f', bytearray(bts[12:16]))
|
|
451
|
+
|
|
452
|
+
return c
|
|
453
|
+
|
|
454
|
+
def write_voltage_calibration(self, A, B):
|
|
455
|
+
ba = list(bytearray(struct.pack("<f", A)))
|
|
456
|
+
self.write_eeprom(ba, 4)
|
|
457
|
+
ba = list(bytearray(struct.pack("<f", B)))
|
|
458
|
+
self.write_eeprom(ba, 8)
|
|
459
|
+
self.reset()
|
|
460
|
+
sleep(3)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def write_current_calibration(self,C, D):
|
|
464
|
+
ba = list(bytearray(struct.pack("<f", C)))
|
|
465
|
+
self.write_eeprom(ba, 12)
|
|
466
|
+
ba = list(bytearray(struct.pack("<f", D)))
|
|
467
|
+
self.write_eeprom(ba, 16)
|
|
468
|
+
self.reset()
|
|
469
|
+
sleep(3)
|
|
470
|
+
|
|
471
|
+
def read_eeprom(self, address, length):
|
|
472
|
+
bts = list(address.to_bytes(self.node, 'little'))
|
|
473
|
+
self.open()
|
|
474
|
+
self.uart.write(
|
|
475
|
+
serial.to_bytes([self.node, constants.READ_EEPROM_COMMAND, length, bts[0], bts[1]]))
|
|
476
|
+
|
|
477
|
+
while self.uart.inWaiting() != length + 3:
|
|
478
|
+
sleep(0.01)
|
|
479
|
+
|
|
480
|
+
bts = ReadSerial(self.uart)
|
|
481
|
+
self.close()
|
|
482
|
+
return bts[3:]
|
|
483
|
+
|
|
484
|
+
def write_eeprom(self, data, address):
|
|
485
|
+
bts = list(address.to_bytes(self.node, 'little'))
|
|
486
|
+
tx = [self.node, constants.WRITE_EEPROM_COMMAND, len(data), bts[0], bts[1]]
|
|
487
|
+
|
|
488
|
+
self.open()
|
|
489
|
+
self.uart.write(serial.to_bytes(tx + data))
|
|
490
|
+
self.close()
|
|
491
|
+
sleep(0.5)
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class PvIrr(PvBlock):
|
|
498
|
+
|
|
499
|
+
def ReadIrradiances(self):
|
|
500
|
+
self.open()
|
|
501
|
+
self.uart.write(serial.to_bytes([2, constants.READ_COMMAND]))
|
|
502
|
+
sleep(0.5)
|
|
503
|
+
bts = ReadSerial(self.uart)
|
|
504
|
+
if len(bts) < 10:
|
|
505
|
+
raise exceptions.UnexpectedResponseException()
|
|
506
|
+
|
|
507
|
+
irradiances = []
|
|
508
|
+
|
|
509
|
+
r1 = int.from_bytes(bts[3:7], "little") / 1000.0
|
|
510
|
+
r2 = int.from_bytes(bts[7:11], "little") / 1000.0
|
|
511
|
+
irradiances.append(r1)
|
|
512
|
+
irradiances.append(r2)
|
|
513
|
+
if bts[2] == 16:
|
|
514
|
+
r3 = int.from_bytes(bts[11:15], "little") / 1000.0
|
|
515
|
+
r4 = int.from_bytes(bts[15:19], "little") / 1000.0
|
|
516
|
+
irradiances.append(r3)
|
|
517
|
+
irradiances.append(r4)
|
|
518
|
+
|
|
519
|
+
self.close()
|
|
520
|
+
return irradiances
|
|
521
|
+
|
|
522
|
+
class StatusByte(object):
|
|
523
|
+
def __init__(self, bytes):
|
|
524
|
+
self.blocktype = bytes[2]
|
|
525
|
+
self.token = bytes[3]
|
|
526
|
+
self.mode = bytes[4]
|
|
527
|
+
self.statusbytes = bytes[5:]
|
|
528
|
+
self.firmware = bytes[9]
|
|
529
|
+
self.hardware = bytes[8]
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
class IvPoint(object):
|
|
533
|
+
def __init__(self, voltage, current):
|
|
534
|
+
self.voltage = voltage
|
|
535
|
+
self.current = current
|
|
536
|
+
self.power = voltage * current
|
|
537
|
+
|
|
538
|
+
def __str__(self):
|
|
539
|
+
return "(%f, %f)" % (self.voltage, self.current)
|
|
540
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pvblocks
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Python package to directly control pvblocks modules
|
|
5
|
+
Author-email: Erik Haverkamp <erik@rera.nl>
|
|
6
|
+
Project-URL: Homepage, https://github.com/rerasolutions/pvblocks-python
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
### pvblocks
|
|
16
|
+
|
|
17
|
+
Package to control pvblocks modules directly
|
|
18
|
+
|
|
19
|
+
requires packages:
|
|
20
|
+
|
|
21
|
+
pyserial
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
test.py,sha256=HEBnuUicKLXo0hPGxfWnqHrRBd4EGFpRdnY7hAZQ9Jw,1576
|
|
2
|
+
test_api.py,sha256=3LTVZCHltluplbRMYdgIU3NBaq13rc9_-mCJbD_91bE,5747
|
|
3
|
+
pvblocks/__init__.py,sha256=deDwZUUNy-NL9BMy8q5Yj1y9Wsp8t_Y_IQ452_W7z_c,42
|
|
4
|
+
pvblocks/__main__.py,sha256=fhvwtf6NzUeUsjP_ARtvxcmYjAvfwqz9_K1bHFwEjUg,84
|
|
5
|
+
pvblocks/constants.py,sha256=E7LsCFWXIRHceIMWbGrGgE0fQqMnKZySIp_RtthU-xM,977
|
|
6
|
+
pvblocks/exceptions.py,sha256=we35JF-svaCDAFYqKFlDCp7ZwbCU0FQe7nksKpUfPnQ,850
|
|
7
|
+
pvblocks/pvblocks_api.py,sha256=XHfmDOrfPoLO05emOmKcTND4fBXy0tyIGc0NPV2eYKM,15113
|
|
8
|
+
pvblocks/pvblocks_system.py,sha256=-CarH4lrseNLvAqgUpWsug6WpAvQ3E8JPVDr2JPOq2M,17076
|
|
9
|
+
pvblocks-0.1.6.dist-info/licenses/LICENSE,sha256=eLQBGuCvsZgLd0xvjckcr-aG-1UB7JzD5VE52GsskPU,1091
|
|
10
|
+
pvblocks-0.1.6.dist-info/METADATA,sha256=-PMGbegokiqmAwPAkdCQGCTl6uWIkCKuYztEUEXMCyQ,593
|
|
11
|
+
pvblocks-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
pvblocks-0.1.6.dist-info/top_level.txt,sha256=Y8qcy__xcs9-JbDqIFw7FATtYTlhjqQ6rMpvigmGXAU,23
|
|
13
|
+
pvblocks-0.1.6.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rerasolutions
|
|
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.
|
test.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from pvblocks import pvblocks_system
|
|
2
|
+
from pvlib.ivtools.utils import rectify_iv_curve
|
|
3
|
+
import numpy as np
|
|
4
|
+
import time
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
print("PV-Blocks version: " + pvblocks_system.show_version())
|
|
8
|
+
|
|
9
|
+
pvblocks = pvblocks_system.PvBlocks('COM8')
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
|
|
12
|
+
if pvblocks.init_system():
|
|
13
|
+
print("init ok")
|
|
14
|
+
else:
|
|
15
|
+
print("failed")
|
|
16
|
+
|
|
17
|
+
print("scanning available blocks")
|
|
18
|
+
|
|
19
|
+
start_time = time.time()
|
|
20
|
+
if pvblocks.scan_blocks():
|
|
21
|
+
print("ok")
|
|
22
|
+
else:
|
|
23
|
+
print("failed")
|
|
24
|
+
end_time = time.time()
|
|
25
|
+
elapsed_time = end_time - start_time
|
|
26
|
+
print(f"Elapsed Time: {elapsed_time} seconds")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
iv_mpp = None
|
|
30
|
+
|
|
31
|
+
for b in pvblocks.Blocks:
|
|
32
|
+
print(pvblocks_system.PvBlocks.TYPES[b.Type])
|
|
33
|
+
|
|
34
|
+
for b in pvblocks.IvMppBlocks:
|
|
35
|
+
print(pvblocks_system.PvBlocks.TYPES[b.Type])
|
|
36
|
+
|
|
37
|
+
for b in pvblocks.PvIrrBlocks:
|
|
38
|
+
print(pvblocks_system.PvBlocks.TYPES[b.Type])
|
|
39
|
+
|
|
40
|
+
if len(pvblocks.IvMppBlocks) > 0:
|
|
41
|
+
iv_mpp = pvblocks.IvMppBlocks[0]
|
|
42
|
+
|
|
43
|
+
#print(iv_mpp.get_info())
|
|
44
|
+
ivpoint = iv_mpp.read_ivpoint()
|
|
45
|
+
print(ivpoint)
|
|
46
|
+
|
|
47
|
+
print(iv_mpp.read_calibration())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
#print('measure iv curve 200 points, 20ms')
|
|
52
|
+
#start_time = time.time()
|
|
53
|
+
#curve = iv_mpp.measure_ivcurve(200, 20, 0)
|
|
54
|
+
#end_time = time.time()
|
|
55
|
+
#elapsed_time = end_time - start_time
|
|
56
|
+
#print(f"Elapsed Time: {elapsed_time} seconds")
|
|
57
|
+
#(v, i) = rectify_iv_curve(curve['voltages'], curve['currents'])
|
|
58
|
+
#p = v * i
|
|
59
|
+
#voc = v[-1]
|
|
60
|
+
#isc = i[0]
|
|
61
|
+
#index_max = np.argmax(p)
|
|
62
|
+
#impp = i[index_max]
|
|
63
|
+
#vmpp = v[index_max]
|
|
64
|
+
#pmax = p[index_max]
|
|
65
|
+
#ff = pmax/(voc * isc)
|
|
66
|
+
#pmax = p[index_max]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
pvblocks.close_system()
|
test_api.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
|
|
2
|
+
host = ''
|
|
3
|
+
apikey = ''
|
|
4
|
+
|
|
5
|
+
from pvblocks import pvblocks_api
|
|
6
|
+
from pvblocks import constants
|
|
7
|
+
print(pvblocks_api.show_version())
|
|
8
|
+
pvblocks = pvblocks_api.PvBlocksApi(host ,apikey)
|
|
9
|
+
pvblocks.Init()
|
|
10
|
+
|
|
11
|
+
def DeleteAllPvDevices():
|
|
12
|
+
for s in pvblocks.get_pvdevices():
|
|
13
|
+
pvblocks.delete_pvdevice(s['id'])
|
|
14
|
+
|
|
15
|
+
def DeleteAllSchedules():
|
|
16
|
+
for s in pvblocks.get_schedules():
|
|
17
|
+
pvblocks.delete_schedule(s['id'])
|
|
18
|
+
|
|
19
|
+
def RecreateSchedules():
|
|
20
|
+
TemperatureScheduleId = pvblocks.create_schedule(1, False)['id']
|
|
21
|
+
IvPointScheduleId = pvblocks.create_schedule(1, False)['id']
|
|
22
|
+
IvCurveScheduleId = pvblocks.create_schedule(5, False)['id']
|
|
23
|
+
return (TemperatureScheduleId, IvPointScheduleId, IvCurveScheduleId)
|
|
24
|
+
|
|
25
|
+
def AssignTemperatureToSchedule(scheduleId):
|
|
26
|
+
for b in pvblocks.Blocks:
|
|
27
|
+
if b['type'] == "RR-1741":
|
|
28
|
+
pvblocks.add_command_to_schedule(scheduleId, b['id'], b['commands'][0])
|
|
29
|
+
|
|
30
|
+
def AssignTIvCurveToSchedule(scheduleId):
|
|
31
|
+
for b in pvblocks.Blocks:
|
|
32
|
+
if b['type'] == "RR-1727":
|
|
33
|
+
for c in b['commands']:
|
|
34
|
+
if c['name'] == 'StartIvCurve':
|
|
35
|
+
pvblocks.add_command_to_schedule(scheduleId, b['id'], c)
|
|
36
|
+
|
|
37
|
+
def AssignTIvPointToSchedule(scheduleId):
|
|
38
|
+
for b in pvblocks.Blocks:
|
|
39
|
+
if b['type'] == "RR-1727":
|
|
40
|
+
for c in b['commands']:
|
|
41
|
+
if c['name'] == 'MeasureIvPoint':
|
|
42
|
+
pvblocks.add_command_to_schedule(scheduleId, b['id'], c)
|
|
43
|
+
|
|
44
|
+
def RecreateBlockLabels():
|
|
45
|
+
for b in pvblocks.Blocks:
|
|
46
|
+
if b['type'] == "RR-1727":
|
|
47
|
+
channel = pvblocks_api.get_channel_number(b['usbNr'], b['boardNr'], b['channelNr'])
|
|
48
|
+
label = "IVMPP-{}".format(channel)
|
|
49
|
+
print(label)
|
|
50
|
+
pvblocks.write_block_label(b['id'], label)
|
|
51
|
+
for s in b['sensors']:
|
|
52
|
+
if s['name'] == 'ivcurve':
|
|
53
|
+
pvblocks.update_sensor_description(s['id'], "ivcurve-{}".format(channel))
|
|
54
|
+
else:
|
|
55
|
+
pvblocks.update_sensor_description(s['id'], "ivpoint-{}".format(channel))
|
|
56
|
+
|
|
57
|
+
if b['type'] == "RR-1741":
|
|
58
|
+
location = 4* b['usbNr'] + b['boardNr'] + 1
|
|
59
|
+
label = "IVTEMP-{}".format(location)
|
|
60
|
+
print(label)
|
|
61
|
+
pvblocks.write_block_label(b['id'], label)
|
|
62
|
+
cnt = 1
|
|
63
|
+
for s in b['sensors']:
|
|
64
|
+
pvblocks.update_sensor_description(s['id'], "TC-{}".format(cnt))
|
|
65
|
+
cnt = cnt + 1
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def RecreatePvDevices():
|
|
69
|
+
for b in pvblocks.Blocks:
|
|
70
|
+
if b['type'] == "RR-1727":
|
|
71
|
+
channel = pvblocks_api.get_channel_number(b['usbNr'], b['boardNr'], b['channelNr'])
|
|
72
|
+
label = "PvDevice-{}".format(channel)
|
|
73
|
+
pvblocks.create_pvdevice(label)
|
|
74
|
+
|
|
75
|
+
def RecreatePvDevicesAndAssign():
|
|
76
|
+
board_tc_sensor_ids = {}
|
|
77
|
+
for b in pvblocks.Blocks:
|
|
78
|
+
if b['type'] == "RR-1741":
|
|
79
|
+
board_tc_sensor_ids['boardNr{}'.format(b['boardNr'])] = [b['sensors'][0]['id'], b['sensors'][1]['id']]
|
|
80
|
+
|
|
81
|
+
for b in pvblocks.Blocks:
|
|
82
|
+
if b['type'] == "RR-1727":
|
|
83
|
+
channel = pvblocks_api.get_channel_number(b['usbNr'], b['boardNr'], b['channelNr'])
|
|
84
|
+
label = "PvDevice-{}".format(channel)
|
|
85
|
+
dev = pvblocks.create_pvdevice(label)
|
|
86
|
+
pvblocks.attach_sensor_to_pvdevice(b['sensors'][0]['id'] ,dev['id'])
|
|
87
|
+
pvblocks.attach_sensor_to_pvdevice(b['sensors'][1]['id'], dev['id'])
|
|
88
|
+
tc1_id = board_tc_sensor_ids['boardNr{}'.format(b['boardNr'])][0]
|
|
89
|
+
tc2_id = board_tc_sensor_ids['boardNr{}'.format(b['boardNr'])][1]
|
|
90
|
+
if b['channelNr'] < 4:
|
|
91
|
+
pvblocks.attach_sensor_to_pvdevice(tc1_id, dev['id'])
|
|
92
|
+
else:
|
|
93
|
+
pvblocks.attach_sensor_to_pvdevice(tc2_id, dev['id'])
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def SetStateForAllRr1727(state, voltageBias = 0, block_list = None):
|
|
97
|
+
if block_list is None:
|
|
98
|
+
block_list = pvblocks.Blocks
|
|
99
|
+
|
|
100
|
+
for b in block_list:
|
|
101
|
+
if b['type'] == "RR-1727":
|
|
102
|
+
pvblocks.write_rr1727_state(b['guid'], state, voltageBias)
|
|
103
|
+
|
|
104
|
+
def SetSweepParametersForAllRr1727(points, integration_cycles, sweep_type, block_list = None):
|
|
105
|
+
if block_list is None:
|
|
106
|
+
block_list = pvblocks.Blocks
|
|
107
|
+
|
|
108
|
+
for b in block_list:
|
|
109
|
+
if b['type'] == "RR-1727":
|
|
110
|
+
pvblocks.write_rr1727_default_sweep(b['id'], points, integration_cycles, sweep_type)
|
|
111
|
+
|
|
112
|
+
def SetCalibrationValuesForAllRr1727(A, B, C, D, block_list = None):
|
|
113
|
+
if block_list is None:
|
|
114
|
+
block_list = pvblocks.Blocks
|
|
115
|
+
|
|
116
|
+
for b in block_list:
|
|
117
|
+
print(b['label'])
|
|
118
|
+
if b['type'] == "RR-1727":
|
|
119
|
+
pvblocks.write_rr1727_calibration_values(b['guid'], A, B, C, D)
|
|
120
|
+
|
|
121
|
+
def SetMppParametersForAllRr1727(p1, p2, p3, p4, block_list = None):
|
|
122
|
+
if block_list is None:
|
|
123
|
+
block_list = pvblocks.Blocks
|
|
124
|
+
|
|
125
|
+
for b in block_list:
|
|
126
|
+
print(b['label'])
|
|
127
|
+
if b['type'] == "RR-1727":
|
|
128
|
+
pvblocks.write_rr1727_mpp_values(b['guid'], p1, p2, p3, p4)
|
|
129
|
+
|
|
130
|
+
def ShowBlocks(block_list = None):
|
|
131
|
+
if block_list is None:
|
|
132
|
+
block_list = pvblocks.Blocks
|
|
133
|
+
|
|
134
|
+
for b in block_list:
|
|
135
|
+
print(b['label'])
|
|
136
|
+
|
|
137
|
+
# DeleteAllPvDevices()
|
|
138
|
+
# RecreateBlockLabels()
|
|
139
|
+
# RecreatePvDevicesAndAssign()
|
|
140
|
+
# DeleteAllSchedules()
|
|
141
|
+
# (TemperatureScheduleId, IvPointScheduleId, IvCurveScheduleId) = RecreateSchedules()
|
|
142
|
+
# AssignTemperatureToSchedule(TemperatureScheduleId)
|
|
143
|
+
# AssignTIvCurveToSchedule(IvCurveScheduleId)
|
|
144
|
+
# AssignTIvPointToSchedule(IvPointScheduleId)
|
|
145
|
+
# SetStateForAllRr1727(constants.MPP)
|
|
146
|
+
# SetSweepParametersForAllRr1727(200, 4, constants.SWEEP_ISC_TO_VOC)
|
|
147
|
+
# SetCalibrationValuesForAllRr1727(0.125, 0, 10, 0)
|
|
148
|
+
# SetMppParametersForAllRr1727(0.75, 0, 0.01, 100)
|