tsmppt60-driver 0.2.2__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.
@@ -0,0 +1,106 @@
1
+ from tsmppt60_driver.base import ManagementBase
2
+ from tsmppt60_driver.status import (
3
+ BatteryStatus,
4
+ CountersStatus,
5
+ OperatingConditions,
6
+ SolarArrayStatus,
7
+ TemperaturesStatus,
8
+ )
9
+
10
+
11
+ """TS-MPPT-60 driver library to get all devices status data."""
12
+
13
+
14
+ class SystemStatus(object):
15
+ """This is class to get the system status of TS-MPPT-60. Use this like below with dict object.
16
+
17
+ print(SystemStatus("192.168.1.20").get())
18
+
19
+ {'Amp Hours': {'group': 'Counter', 'unit': 'Ah', 'value': 18097.9},
20
+ 'Array Current': {'group': 'Array', 'unit': 'A', 'value': 1.4},
21
+ 'Array Voltage': {'group': 'Array', 'unit': 'V', 'value': 53.41},
22
+ 'Battery Voltage': {'group': 'Battery', 'unit': 'V', 'value': 23.93},
23
+ 'Charge Current': {'group': 'Battery', 'unit': 'A', 'value': 3.2},
24
+ 'Heat Sink Temperature': {'group': 'Temperature', 'unit': 'C', ...},
25
+ 'Kilowatt Hours': {'group': 'Counter', 'unit': 'kWh', 'value': 237.0},
26
+ 'Target Voltage': {'group': 'Battery', 'unit': 'V', 'value': 28.6}}
27
+
28
+ The above data is limited information. You can disable the limitter
29
+ like below.
30
+
31
+ print(SystemStatus("192.168.1.20").get(False))
32
+
33
+ {'Amp Hours': {'group': 'Counter', 'unit': 'Ah', 'value': 18097.8},
34
+ 'Array Current': {'group': 'Array', 'unit': 'A', 'value': 1.3},
35
+ 'Array Voltage': {'group': 'Array', 'unit': 'V', 'value': 53.41},
36
+ 'Battery Temperature': {'group': 'Temperature', 'unit': 'C', ...},
37
+ 'Battery Voltage': {'group': 'Battery', 'unit': 'V', 'value': 24.01},
38
+ 'Charge Current': {'group': 'Battery', 'unit': 'A', 'value': 3.2},
39
+ 'Heat Sink Temperature': {'group': 'Temperature', 'unit': 'C', ...},
40
+ 'Kilowatt Hours': {'group': 'Counter', 'unit': 'kWh', 'value': 237.0},
41
+ 'Output Power': {'group': 'Battery', 'unit': 'W', 'value': 76.0},
42
+ 'Sweep Pmax': {'group': 'Array', 'unit': 'W', 'value': 73.0},
43
+ 'Sweep Vmp': {'group': 'Array', 'unit': 'V', 'value': 53.41},
44
+ 'Sweep Voc': {'group': 'Array', 'unit': 'V', 'value': 60.05},
45
+ 'Target Voltage': {'group': 'Battery', 'unit': 'V', 'value': 28.6},
46
+ 'LED State': {'group': 'Condition', 'value': 11, 'unit': ''},
47
+ 'Charge State': {'group': 'Condition', 'value': 3, 'unit': ''}}
48
+ """
49
+
50
+ def __init__(self, host):
51
+ """Initialize class object.
52
+
53
+ Keyword arguments:
54
+ host -- TS-MPPT-60 host address like "192.168.1.20"
55
+ """
56
+ _mb = ManagementBase(host)
57
+
58
+ self._devices = (
59
+ BatteryStatus(_mb),
60
+ SolarArrayStatus(_mb),
61
+ TemperaturesStatus(_mb),
62
+ CountersStatus(_mb),
63
+ OperatingConditions(_mb),
64
+ )
65
+
66
+ def get(self, is_limit=True):
67
+ """Get and return all status of devices like the below dict.
68
+
69
+ {
70
+ "Battery Voltage":{
71
+ "group": "Battery",
72
+ "value": 12.1,
73
+ "unit": "V"},
74
+ "Charge Current":{
75
+ "group": "Battery",
76
+ "value": 8.4,
77
+ "unit": "A"}
78
+ }
79
+
80
+ Keyword arguments:
81
+ is_limit -- limit the number of getting status
82
+ """
83
+ status_dict = {}
84
+
85
+ for device in self._devices:
86
+ for status in device.get_status_all(is_limit):
87
+ label = status.pop("label")
88
+ status_dict[label] = status
89
+
90
+ return status_dict
91
+
92
+ def __len__(self):
93
+ return len(self._devices)
94
+
95
+ def __iter__(self):
96
+ self._index = 0
97
+ return self
98
+
99
+ def __next__(self):
100
+ if self._index >= len(self._devices):
101
+ raise StopIteration
102
+
103
+ stat_obj = self._devices[self._index]
104
+ self._index += 1
105
+
106
+ return stat_obj
@@ -0,0 +1,266 @@
1
+ import logging
2
+
3
+ import requests
4
+
5
+
6
+ """TS-MPPT-60 driver's base modules."""
7
+
8
+
9
+ class Logger(object):
10
+ """Logger base class for this module."""
11
+
12
+ _FORMAT_LOG_MSG = "%(asctime)s %(name)s %(levelname)s: %(message)s"
13
+ _FORMAT_LOG_DATE = "%Y/%m/%d %p %l:%M:%S"
14
+
15
+ def __init__(self, log_file_path=None, debug=False):
16
+ """Initialize Logger class object.
17
+
18
+ Keyword arguments:
19
+ log_file_path -- Path to record log file.
20
+ debug -- If True, logging is enabled.
21
+ """
22
+ self.logger = logging.getLogger(type(self).__name__)
23
+
24
+ handler = logging.StreamHandler()
25
+ formatter = logging.Formatter(fmt=self._FORMAT_LOG_MSG, datefmt=self._FORMAT_LOG_DATE)
26
+ handler.setFormatter(formatter)
27
+ self.logger.addHandler(handler)
28
+
29
+ if log_file_path:
30
+ handler = logging.FileHandler(log_file_path, mode="a")
31
+ handler.setFormatter(formatter)
32
+ self.logger.addHandler(handler)
33
+
34
+ if debug:
35
+ self.logger.setLevel(logging.DEBUG)
36
+ else:
37
+ self.logger.setLevel(logging.INFO)
38
+
39
+
40
+ class ModbusRegisterTable(object):
41
+ """MODBUS register settings table."""
42
+
43
+ VOLTAGE_SCALING_HIGH = (0x0000, "", "Voltage Scaling High", 1)
44
+ VOLTAGE_SCALING_LOW = (0x0001, "", "Voltage Scaling Low", 1)
45
+ CURRENT_SCALING_HIGH = (0x0002, "", "Current Scaling High", 1)
46
+ CURRENT_SCALING_LOW = (0x0003, "", "Current Scaling Low", 1)
47
+ VOLTAGE_SCALING = (0x0000, "", "Voltage Scaling", 2)
48
+ CURRENT_SCALING = (0x0002, "", "Current Scaling", 2)
49
+ SOFTWARE_VERSION = (0x0004, "Numbers", "Software Version", 1)
50
+
51
+ BATTERY_VOLTAGE = (0x0026, "V", "Battery Voltage", 1)
52
+ CHARGING_CURRENT = (0x0027, "A", "Charge Current", 1)
53
+ TARGET_REGULATION_VOLTAGE = (0x0033, "V", "Target Voltage", 1)
54
+ OUTPUT_POWER = (0x003A, "W", "Output Power", 1)
55
+ ARRAY_VOLTAGE = (0x001B, "V", "Array Voltage", 1)
56
+ ARRAY_CURRENT = (0x001D, "A", "Array Current", 1)
57
+ VMP_LAST_SWEEP = (0x003D, "V", "Sweep Vmp", 1)
58
+ VOC_LAST_SWEEP = (0x003E, "V", "Sweep Voc", 1)
59
+ POWER_LAST_SWEEP = (0x003C, "W", "Sweep Pmax", 1)
60
+ HEATSINK_TEMP = (0x0023, "C", "Heat Sink Temperature", 1)
61
+ BATTERY_TEMP = (0x0025, "C", "Battery Temperature", 1)
62
+ AH_CHARGE_RESETABLE = (0x0034, "Ah", "Amp Hours", 2)
63
+ KWH_CHARGE_RESETABLE = (0x0038, "kWh", "Kilowatt Hours", 1)
64
+
65
+ LED_STATE = (0x0031, "Numbers", "LED State", 1)
66
+ CHARGE_STATE = (0x0032, "Numbers", "Charge State", 1)
67
+
68
+
69
+ class ManagementBase(object):
70
+ """Class to get raw data from TS-MPPT-60. MODBUS ID is fixed to 1 as written on data sheet TSMPPT.APP_.Modbus.EN_.10.2.pdf.
71
+
72
+ Keyword arguments:
73
+ host -- host name like dummy.co.jp
74
+ cgi -- cgi script file name
75
+ debug -- debug message output is enabled if True
76
+ """
77
+
78
+ _ID_MODBUS = 0x01
79
+
80
+ def __init__(self, host, cgi="MBCSV.cgi", debug=False):
81
+ """Initialize class object.
82
+
83
+ Keyword arguments:
84
+ host -- Host address like "192.168.1.20" of TS-MPPT-60 live view
85
+ cgi -- CGI file name to get the information
86
+ debug -- If True, logging is enabled.
87
+ """
88
+ self._logger = logging.getLogger(type(self).__name__)
89
+ self._logger.addHandler(logging.StreamHandler())
90
+
91
+ if debug:
92
+ self._logger.setLevel(logging.DEBUG)
93
+
94
+ self._url = "http://" + host + "/" + cgi
95
+ self._vscale = self._compute_scaler(ModbusRegisterTable.VOLTAGE_SCALING)
96
+ self._iscale = self._compute_scaler(ModbusRegisterTable.CURRENT_SCALING)
97
+
98
+ def _get(self, addr, reg, mbid=_ID_MODBUS, field=4):
99
+ """Get and return raw data string like "1,4,1,1,1" against MBID, Address, Register, and Field.
100
+
101
+ Keyword arguments:
102
+ addr -- Address to get information
103
+ reg -- Register to get information
104
+ mbid -- MBID
105
+ field -- Field to get information
106
+
107
+ >>> mb._get(addr=0x0000, reg=1)
108
+ '1,4,2,0,0'
109
+ >>> mb._get(addr=0x0001, reg=1)
110
+ '1,4,2,0,0'
111
+ """
112
+ params = []
113
+ params.append("ID=" + str(mbid))
114
+ params.append("F=" + str(field))
115
+ params.append("AHI=" + str(addr >> 8))
116
+ params.append("ALO=" + str(addr & 255))
117
+ params.append("RHI=" + str(reg >> 8))
118
+ params.append("RLO=" + str(reg & 255))
119
+
120
+ res = requests.get("{0}?{1}".format(self._url, "&".join(params)), timeout=(5, 15))
121
+
122
+ return res.text
123
+
124
+ def _read_modbus(self, address, register, mbid=_ID_MODBUS):
125
+ """Read and return the value string with short integer (ex. 16bit value) against MBID, Address, and Register.
126
+
127
+ Keyword arguments:
128
+ address -- Address to get information
129
+ register -- Register to get information
130
+ mbid -- MBID
131
+
132
+ >>> mb._read_modbus(0x0000, 1)
133
+ [0]
134
+ >>> mb._read_modbus(0x0001, 1)
135
+ [0]
136
+ """
137
+ raw_value_str = self._get(address, register, mbid)
138
+ raw_values = [int(v) for v in raw_value_str.split(",")]
139
+ idx_max = raw_values[2]
140
+ raw_values = raw_values[3:]
141
+ idx = 0
142
+ ret = []
143
+
144
+ while idx < idx_max:
145
+ ret_short = raw_values[idx] << 8
146
+ idx += 1
147
+ ret_short += raw_values[idx]
148
+ idx += 1
149
+ ret.append(ret_short)
150
+
151
+ return ret
152
+
153
+ def _compute_scaler(self, param):
154
+ """Compute and return the voltage/current scaler as written on data sheet page 8 or 25.
155
+ This will be called only once when initializing this object.
156
+
157
+ Vscaling = whole.fraction = [V_PU hi].[V_PU lo]
158
+
159
+ Example:
160
+ Address:Value(hex):Variable Name
161
+ V_PU HI byte:0x004E = 78
162
+ V_PU LO byte:0x03A6 = 934
163
+
164
+ V_PU lo must be shifted by 16 (divided by 2^16)
165
+ and then added to V_PU hi Vscaling = 78 + 934/65536 = 78.01425
166
+
167
+ Keyword arguments:
168
+ param -- register to get a value
169
+ >>> mb._compute_scaler(ModbusRegisterTable.VOLTAGE_SCALING)
170
+ 0.0
171
+ >>> mb._compute_scaler(ModbusRegisterTable.CURRENT_SCALING)
172
+ 0.0
173
+ """
174
+ values = self._read_modbus(param[0], param[3])
175
+ return float(values[0]) + (float(values[1]) / pow(2, 16))
176
+
177
+ def get_raw_value(self, address, register):
178
+ """Return a raw value against address got from TS-MPPT-60.
179
+
180
+ Keyword arguments:
181
+ address -- address to get a value
182
+ register -- register to get a value
183
+
184
+ Returns:
185
+ Raw value as integer type.
186
+
187
+ >>> mb.get_raw_value(0x0026, 1)
188
+ 0
189
+ >>> mb.get_raw_value(0x0027, 1)
190
+ 0
191
+ """
192
+ values = self._read_modbus(address, register)
193
+
194
+ if register > 1:
195
+ raw_value = (values[0] << 16) | (values[1] & 0xFFFF)
196
+ else:
197
+ raw_value = values[0]
198
+ raw_value &= 0xFFFF
199
+ if raw_value & 0x8000:
200
+ raw_value ^= 0xFFFF
201
+ raw_value = -1 * (raw_value + 1)
202
+
203
+ return raw_value
204
+
205
+ def get_scaled_value(self, address, scale_factor, register):
206
+ """Calculate and return a scaled status value against address got from TS-MPPT-60.
207
+
208
+ Keyword arguments:
209
+ address -- address to get a value
210
+ scale_factor -- unit string
211
+ register -- register to get a value
212
+
213
+ Returns:
214
+ Scaled value like 12.4 against your expecting.
215
+
216
+ >>> mb.get_scaled_value(0x0026, "V", 1)
217
+ 0.0
218
+ >>> mb.get_scaled_value(0x0027, "A", 1)
219
+ 0.0
220
+ """
221
+ raw_value = self.get_raw_value(address, register)
222
+
223
+ if scale_factor == "V":
224
+ scaled_value = raw_value * self._vscale / pow(2, 15)
225
+ elif scale_factor == "A":
226
+ scaled_value = raw_value * self._iscale / pow(2, 15)
227
+ elif scale_factor == "W":
228
+ wscale = self._iscale * self._vscale
229
+ scaled_value = raw_value * wscale / pow(2, 17)
230
+ elif scale_factor == "Ah":
231
+ scaled_value = raw_value / 10.0
232
+ else:
233
+ scaled_value = raw_value
234
+
235
+ return round(scaled_value, 2)
236
+
237
+
238
+ if __name__ == "__main__":
239
+ import doctest
240
+
241
+ try:
242
+ from unittest.mock import patch
243
+ except ImportError:
244
+ from mock import patch
245
+
246
+ class DummyRequest(object):
247
+ """Dummy response class against requests.Response."""
248
+
249
+ pass
250
+
251
+ class DummyResponse(object):
252
+ """Dummy response class against requests.Response."""
253
+
254
+ pass
255
+
256
+ dummy_host = "dummy.co.jp"
257
+
258
+ req = DummyRequest()
259
+ req.url = "http://" + dummy_host + "/dummy.cgi"
260
+ res = DummyResponse()
261
+ res.request = req
262
+ res.text = "1,4,2,0,0"
263
+
264
+ with patch("requests.get", returns=res) as _m:
265
+ _m.return_value = res
266
+ doctest.testmod(verbose=True, extraglobs={"mb": ManagementBase(host=dummy_host)})
@@ -0,0 +1,319 @@
1
+ import logging
2
+
3
+ from tsmppt60_driver.base import ModbusRegisterTable
4
+
5
+
6
+ """TS-MPPT-60 driver's internal modules inherites base modules."""
7
+
8
+
9
+ class ChargeControllerStatus(object):
10
+ """Abstract class to get data about charge controller status."""
11
+
12
+ def __init__(self, mb, group, debug=False):
13
+ """Initialize class object.
14
+
15
+ Keyword arguments:
16
+ mb -- instance of ManagementBase class.
17
+ group -- string to indicate this instance name.
18
+ debug -- If True, logging is enabled.
19
+ """
20
+ self._mb = mb
21
+ self._group = group
22
+
23
+ handler = logging.StreamHandler()
24
+ handler.setFormatter(
25
+ logging.Formatter("%(asctime)s %(name)s %(levelname)s: %(message)s", "%Y/%m/%d %p %l:%M:%S")
26
+ )
27
+
28
+ self._logger = logging.getLogger(type(self).__name__)
29
+ self._logger.addHandler(handler)
30
+
31
+ if debug:
32
+ self._logger.setLevel(logging.DEBUG)
33
+
34
+ def __repr__(self):
35
+ return self._group
36
+
37
+ def __str__(self):
38
+ return self._group
39
+
40
+ def get_status(self, address, scale_factor, label, register):
41
+ """
42
+ Get and return a data against the specified address, register, etc. like below.
43
+
44
+ {
45
+ "group": "battery",
46
+ "label": "Battery Voltage",
47
+ "value": 12.1,
48
+ "unit": "V"
49
+ }
50
+
51
+ Keyword arguments:
52
+ address -- address to get a value
53
+ scale_factor -- unit string
54
+ label -- label string of got value
55
+ register -- register to get a value
56
+ """
57
+ ret_values = {}
58
+ ret_values["group"] = self._group
59
+ ret_values["label"] = label
60
+ ret_values["value"] = self._mb.get_scaled_value(address, scale_factor, register)
61
+ ret_values["unit"] = scale_factor
62
+
63
+ return ret_values
64
+
65
+ def get_status_all(self, is_limit=True):
66
+ """
67
+ Get and return all data against the inherited class's paramter list.
68
+
69
+ { "group": "Battery",
70
+ "label": "Battery Voltage",
71
+ "value": 12.1,
72
+ "unit": "V"
73
+ },
74
+ {
75
+ "group": "Battery",
76
+ "label": "Charge Current",
77
+ "value": 8.4,
78
+ "unit": "A"
79
+ }
80
+
81
+ Keyword arguments:
82
+ is_limit -- limit the number of getting status
83
+ """
84
+ return [self.get_status(*param) for param in self.get_params(is_limit)]
85
+
86
+ def get_params(self, is_limit=True):
87
+ """Get and return a list of all params of the inherited class's group.
88
+
89
+ Keyword arguments:
90
+ is_limit -- limit the number of getting status
91
+ ((61, "V", "Sweep Vmp", 1),
92
+ (62, "V", "Sweep Voc", 1),
93
+ (60, "W", "Sweep Pmax", 1))
94
+ """
95
+ raise NotImplementedError
96
+
97
+
98
+ class BatteryStatus(ChargeControllerStatus):
99
+ """
100
+ This class gives the following battery status.
101
+
102
+ * battery voltage
103
+ * target voltage
104
+ * charge current
105
+ * output power
106
+ """
107
+
108
+ def __init__(self, mb):
109
+ """Initialize BatteryStatus class object.
110
+
111
+ Keyword arguments:
112
+ mb -- instance of ManagementBase class
113
+ """
114
+ ChargeControllerStatus.__init__(self, mb, "Battery")
115
+
116
+ def get_params(self, is_limit=True):
117
+ """Get and return a list of all params to get the battery status. The param is consisted by (address, scale_factor, label, register).
118
+
119
+ Keyword arguments:
120
+ is_limit -- limit the number of getting status
121
+
122
+ >>> bat.get_params() == [
123
+ ... (38, "V", "Battery Voltage", 1),
124
+ ... (51, "V", "Target Voltage", 1),
125
+ ... (39, "A", "Charge Current", 1),
126
+ ... ]
127
+ True
128
+ >>> bat.get_params(True) == [
129
+ ... (38, "V", "Battery Voltage", 1),
130
+ ... (51, "V", "Target Voltage", 1),
131
+ ... (39, "A", "Charge Current", 1),
132
+ ... ]
133
+ True
134
+ >>> bat.get_params(False) == [
135
+ ... (38, "V", "Battery Voltage", 1),
136
+ ... (51, "V", "Target Voltage", 1),
137
+ ... (39, "A", "Charge Current", 1),
138
+ ... (58, "W", "Output Power", 1),
139
+ ... ]
140
+ True
141
+ """
142
+ params = [
143
+ ModbusRegisterTable.BATTERY_VOLTAGE,
144
+ ModbusRegisterTable.TARGET_REGULATION_VOLTAGE,
145
+ ModbusRegisterTable.CHARGING_CURRENT,
146
+ ]
147
+
148
+ if not is_limit:
149
+ params.append(ModbusRegisterTable.OUTPUT_POWER)
150
+
151
+ return params
152
+
153
+
154
+ class SolarArrayStatus(ChargeControllerStatus):
155
+ """Class to get data about solar array.
156
+
157
+ * array voltage
158
+ * array current
159
+ * sweep vmp
160
+ * sweep voc
161
+ * sweep pmax
162
+ """
163
+
164
+ def __init__(self, mb):
165
+ """Initialize SolarArrayStatus class object.
166
+
167
+ Keyword arguments:
168
+ mb -- instance of ManagementBase class
169
+ """
170
+ ChargeControllerStatus.__init__(self, mb, "Array")
171
+
172
+ def get_params(self, is_limit=True):
173
+ """Get and return a list of all params to get the solar array status. The param is consisted by (address, scale_factor, label, register).
174
+
175
+ Keyword arguments:
176
+ is_limit -- limit the number of getting status
177
+
178
+ >>> array.get_params() == [(27, "V", "Array Voltage", 1), (29, "A", "Array Current", 1)]
179
+ True
180
+ >>> array.get_params(True) == [(27, "V", "Array Voltage", 1), (29, "A", "Array Current", 1)]
181
+ True
182
+ >>> array.get_params(False) == [
183
+ ... (27, "V", "Array Voltage", 1),
184
+ ... (29, "A", "Array Current", 1),
185
+ ... (61, "V", "Sweep Vmp", 1),
186
+ ... (62, "V", "Sweep Voc", 1),
187
+ ... (60, "W", "Sweep Pmax", 1),
188
+ ... ]
189
+ True
190
+ """
191
+ params = [ModbusRegisterTable.ARRAY_VOLTAGE, ModbusRegisterTable.ARRAY_CURRENT]
192
+
193
+ if not is_limit:
194
+ params.extend(
195
+ [
196
+ ModbusRegisterTable.VMP_LAST_SWEEP,
197
+ ModbusRegisterTable.VOC_LAST_SWEEP,
198
+ ModbusRegisterTable.POWER_LAST_SWEEP,
199
+ ]
200
+ )
201
+
202
+ return params
203
+
204
+
205
+ class TemperaturesStatus(ChargeControllerStatus):
206
+ """Class to get data about temperatures sensors.
207
+
208
+ * heat sink temperature
209
+ * battery temperature
210
+ """
211
+
212
+ def __init__(self, mb):
213
+ """Initialize SolarArrayStatus class object.
214
+
215
+ Keyword arguments:
216
+ mb -- instance of ManagementBase class
217
+ """
218
+ ChargeControllerStatus.__init__(self, mb, "Temperature")
219
+
220
+ def get_params(self, is_limit=True):
221
+ """Get and return a list of all params to get the temperatures. The param is consisted by (address, scale_factor, label, register).
222
+
223
+ Keyword arguments:
224
+ is_limit -- limit the number of getting status
225
+
226
+ >>> temp.get_params() == [(35, "C", "Heat Sink Temperature", 1)]
227
+ True
228
+ >>> temp.get_params(True) == [(35, "C", "Heat Sink Temperature", 1)]
229
+ True
230
+ >>> temp.get_params(False) == [(35, "C", "Heat Sink Temperature", 1), (37, "C", "Battery Temperature", 1)]
231
+ True
232
+ """
233
+ params = [ModbusRegisterTable.HEATSINK_TEMP]
234
+
235
+ if not is_limit:
236
+ params.append(ModbusRegisterTable.BATTERY_TEMP)
237
+
238
+ return params
239
+
240
+
241
+ class CountersStatus(ChargeControllerStatus):
242
+ """
243
+ Class to get data about resettable counters.
244
+
245
+ * amp hours
246
+ * kilowatt hours
247
+ """
248
+
249
+ def __init__(self, mb):
250
+ """
251
+ Initialize CountersStaus class object.
252
+
253
+ Keyword arguments:
254
+ mb -- instance of ManagementBase class
255
+ """
256
+ ChargeControllerStatus.__init__(self, mb, "Counter")
257
+
258
+ def get_params(self, is_limit=True):
259
+ """Get and return a list of all params to get the counters. The param is consisted by (address, scale_factor, label, register).
260
+
261
+ Keyword arguments:
262
+ is_limit -- limit the number of getting status
263
+
264
+ >>> count.get_params() == ((52, "Ah", "Amp Hours", 2), (56, "kWh", "Kilowatt Hours", 1))
265
+ True
266
+ >>> count.get_params(True) == ((52, "Ah", "Amp Hours", 2), (56, "kWh", "Kilowatt Hours", 1))
267
+ True
268
+ >>> count.get_params(False) == ((52, "Ah", "Amp Hours", 2), (56, "kWh", "Kilowatt Hours", 1))
269
+ True
270
+ """
271
+ return (ModbusRegisterTable.AH_CHARGE_RESETABLE, ModbusRegisterTable.KWH_CHARGE_RESETABLE)
272
+
273
+
274
+ class OperatingConditions(ChargeControllerStatus):
275
+ """
276
+ Class to get data about controller's operating conditions.
277
+
278
+ * LED state
279
+ * Charge state
280
+ """
281
+
282
+ def __init__(self, mb):
283
+ """
284
+ Initialize OperatingConditions class object.
285
+
286
+ Keyword arguments:
287
+ mb -- instance of ManagementBase class
288
+ """
289
+ ChargeControllerStatus.__init__(self, mb, "Condition")
290
+
291
+ def get_params(self, is_limit=True):
292
+ """Get and return a list of all params to get the conditions. The param is consisted by (address, scale_factor, label, register).
293
+
294
+ Keyword arguments:
295
+ is_limit -- limit the number of getting status
296
+
297
+ >>> condition.get_params() == ((49, "Numbers", "LED State", 1), (50, "Numbers", "Charge State", 1))
298
+ True
299
+ >>> condition.get_params(True) == ((49, "Numbers", "LED State", 1), (50, "Numbers", "Charge State", 1))
300
+ True
301
+ >>> condition.get_params(False) == ((49, "Numbers", "LED State", 1), (50, "Numbers", "Charge State", 1))
302
+ True
303
+ """
304
+ return (ModbusRegisterTable.LED_STATE, ModbusRegisterTable.CHARGE_STATE)
305
+
306
+
307
+ if __name__ == "__main__":
308
+ import doctest
309
+
310
+ doctest.testmod(
311
+ verbose=True,
312
+ extraglobs={
313
+ "bat": BatteryStatus(None),
314
+ "array": SolarArrayStatus(None),
315
+ "temp": TemperaturesStatus(None),
316
+ "count": CountersStatus(None),
317
+ "condition": OperatingConditions(None),
318
+ },
319
+ )
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: tsmppt60-driver
3
+ Version: 0.2.2
4
+ Summary: Python module to get status of your solar charge controller TS-MPPT-60.
5
+ License: GPLv2
6
+ Author: Takashi Ando
7
+ Author-email: odo5522@gmail.com
8
+ Requires-Python: >=3.11,<3.15
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
18
+ Requires-Dist: requests (>=2.32,<3.0)
19
+ Description-Content-Type: text/markdown
20
+
21
+ # TS-MPPT-60 driver module
22
+
23
+ This is python driver module to get the following status of TS-MPPT-60.
24
+
25
+ * Amp Hours
26
+ * Array Current
27
+ * Array Voltage
28
+ * Battery Temperature
29
+ * Battery Voltage
30
+ * Charge Current
31
+ * Heat Sink Temperature
32
+ * Kilowatt Hours
33
+ * Output Power
34
+ * Sweep Pmax
35
+ * Sweep Vmp
36
+ * Sweep Voc
37
+ * Target Voltage
38
+
39
+ # Requirement
40
+
41
+ * requests
42
+
43
+ # How to install
44
+
45
+ ```bash
46
+ pip install tsmppt60-driver
47
+ ```
48
+
49
+ # How to use
50
+
51
+ SystemStatus class object is iterator containing all live status data of TS-MPPT-60. Try the following line.
52
+
53
+ ```python
54
+ print(SystemStatus("192.168.1.20").get())
55
+ ```
56
+
57
+ The result is like following.
58
+
59
+ ```
60
+ {'Amp Hours': {'group': 'Counter', 'unit': 'Ah', 'value': 18097.9},
61
+ 'Array Current': {'group': 'Array', 'unit': 'A', 'value': 1.4},
62
+ 'Array Voltage': {'group': 'Array', 'unit': 'V', 'value': 53.41},
63
+ 'Battery Voltage': {'group': 'Battery', 'unit': 'V', 'value': 23.93},
64
+ 'Charge Current': {'group': 'Battery', 'unit': 'A', 'value': 3.2},
65
+ 'Heat Sink Temperature': {'group': 'Temperature', 'unit': 'C', ...},
66
+ 'Kilowatt Hours': {'group': 'Counter', 'unit': 'kWh', 'value': 237.0},
67
+ 'Target Voltage': {'group': 'Battery', 'unit': 'V', 'value': 28.6}}
68
+ ```
69
+
70
+ The above data is limited information. You can disable the limitter by setting False to the second argument as SystemStatus() class.
71
+
72
+ ```python
73
+ print(SystemStatus("192.168.1.20").get(False))
74
+ ```
75
+
76
+ The result is like following.
77
+
78
+ ```
79
+ {'Amp Hours': {'group': 'Counter', 'unit': 'Ah', 'value': 18097.8},
80
+ 'Array Current': {'group': 'Array', 'unit': 'A', 'value': 1.3},
81
+ 'Array Voltage': {'group': 'Array', 'unit': 'V', 'value': 53.41},
82
+ 'Battery Temperature': {'group': 'Temperature', 'unit': 'C', ...},
83
+ 'Battery Voltage': {'group': 'Battery', 'unit': 'V', 'value': 24.01},
84
+ 'Charge Current': {'group': 'Battery', 'unit': 'A', 'value': 3.2},
85
+ 'Heat Sink Temperature': {'group': 'Temperature', 'unit': 'C', ...},
86
+ 'Kilowatt Hours': {'group': 'Counter', 'unit': 'kWh', 'value': 237.0},
87
+ 'Output Power': {'group': 'Battery', 'unit': 'W', 'value': 76.0},
88
+ 'Sweep Pmax': {'group': 'Array', 'unit': 'W', 'value': 73.0},
89
+ 'Sweep Vmp': {'group': 'Array', 'unit': 'V', 'value': 53.41},
90
+ 'Sweep Voc': {'group': 'Array', 'unit': 'V', 'value': 60.05},
91
+ 'Target Voltage': {'group': 'Battery', 'unit': 'V', 'value': 28.6}}
92
+ ```
93
+
94
+ JSON string can be got with following code.
95
+
96
+ ```bash
97
+ $ python -c "from tsmppt60_driver import *; import json; print(json.dumps(SystemStatus('192.168.1.20').get()))" | jq '."Battery Voltage"'
98
+
99
+ {
100
+ "group": "Battery",
101
+ "value": 28.6,
102
+ "unit": "V"
103
+ }
104
+ ```
105
+
@@ -0,0 +1,6 @@
1
+ tsmppt60_driver/__init__.py,sha256=rQVEYSNs57k8gBg6_X9WLkQ9aNc3-Kx_37G0xy4eiYM,3828
2
+ tsmppt60_driver/base.py,sha256=IHQ6syynGfhlVV_bHfTYKUW0CKv1r4bYfB9T5IRu2pE,8726
3
+ tsmppt60_driver/status.py,sha256=nrz7gb0KaD0bLZ5g6mjOxOgkMYPq6vCr2jeoZjZdVTc,9854
4
+ tsmppt60_driver-0.2.2.dist-info/METADATA,sha256=arGVJKWP5Jvgfa_2VyK1bvsO3NF5qHKMOo3S3GOm2VU,3310
5
+ tsmppt60_driver-0.2.2.dist-info/WHEEL,sha256=EGEvSphFYqXKs23-kQBeyNoJP1nrT8ZJKQoi5p5DYL8,88
6
+ tsmppt60_driver-0.2.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.4.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any