commit deeb335eca5b8cf4065be1307f140e0b5f7702ae Author: DBras Date: Mon Jun 10 10:50:30 2024 +0200 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4265746 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv/**/** diff --git a/2024_config_D3_Demo.pdf b/2024_config_D3_Demo.pdf new file mode 100644 index 0000000..9a5e13b Binary files /dev/null and b/2024_config_D3_Demo.pdf differ diff --git a/data/measurements/dummy_file b/data/measurements/dummy_file new file mode 100644 index 0000000..c81b66a --- /dev/null +++ b/data/measurements/dummy_file @@ -0,0 +1 @@ +Makes folder visible for git. \ No newline at end of file diff --git a/data/measurements/measurements.json b/data/measurements/measurements.json new file mode 100644 index 0000000..a5dcda9 --- /dev/null +++ b/data/measurements/measurements.json @@ -0,0 +1,2450 @@ +{"unit": "pcc_p", "value": -39.213, "time": 1717757673.495} +{"unit": "pcc_q", "value": 11.626, "time": 1717757673.675} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757673.651} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757673.651} +{"unit": "pv319_p", "value": 10.346, "time": 1717757673.615} +{"unit": "pv319_q", "value": 0.128, "time": 1717757673.615} +{"unit": "pv330_p", "value": 14.682, "time": 1717757673.08} +{"unit": "pv330_q", "value": -0.181, "time": 1717757673.08} +{"unit": "battery_p", "value": -0.221, "time": 1717757673.627} +{"unit": "battery_q", "value": 0.035, "time": 1717757673.627} +{"unit": "b2b_p", "value": 8.929, "time": 1717757673.621} +{"unit": "b2b_q", "value": -3.018, "time": 1717757673.621} +{"unit": "gaia_p", "value": 5.307, "time": 1717757673.581} +{"unit": "gaia_q", "value": -8.944, "time": 1717757673.581} +{"unit": "pcc_p", "value": -39.035, "time": 1717757674.75} +{"unit": "pcc_q", "value": 11.581, "time": 1717757674.75} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757674.721} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757674.721} +{"unit": "pv319_p", "value": 10.337, "time": 1717757674.823} +{"unit": "pv319_q", "value": 0.134, "time": 1717757674.823} +{"unit": "pv330_p", "value": 14.73, "time": 1717757674.629} +{"unit": "pv330_q", "value": -0.184, "time": 1717757674.629} +{"unit": "battery_p", "value": -0.222, "time": 1717757674.842} +{"unit": "battery_q", "value": 0.038, "time": 1717757674.842} +{"unit": "b2b_p", "value": 8.94, "time": 1717757674.86} +{"unit": "b2b_q", "value": -3.036, "time": 1717757674.86} +{"unit": "gaia_p", "value": 4.815, "time": 1717757674.644} +{"unit": "gaia_q", "value": -8.781, "time": 1717757674.644} +{"unit": "pcc_p", "value": -39.157, "time": 1717757675.831} +{"unit": "pcc_q", "value": 11.586, "time": 1717757675.831} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757675.971} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757675.971} +{"unit": "pv319_p", "value": 10.338, "time": 1717757675.85} +{"unit": "pv319_q", "value": 0.138, "time": 1717757675.85} +{"unit": "pv330_p", "value": 14.713, "time": 1717757675.836} +{"unit": "pv330_q", "value": -0.187, "time": 1717757675.836} +{"unit": "battery_p", "value": -0.219, "time": 1717757675.642} +{"unit": "battery_q", "value": 0.036, "time": 1717757675.642} +{"unit": "b2b_p", "value": 8.932, "time": 1717757675.967} +{"unit": "b2b_q", "value": -3.029, "time": 1717757675.967} +{"unit": "gaia_p", "value": 5.056, "time": 1717757675.982} +{"unit": "gaia_q", "value": -8.854, "time": 1717757675.982} +{"unit": "pcc_p", "value": -39.942, "time": 1717757676.921} +{"unit": "pcc_q", "value": 11.93, "time": 1717757676.921} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757677.036} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757677.036} +{"unit": "pv319_p", "value": 10.339, "time": 1717757677.015} +{"unit": "pv319_q", "value": 0.132, "time": 1717757677.015} +{"unit": "pv330_p", "value": 14.744, "time": 1717757676.989} +{"unit": "pv330_q", "value": -0.182, "time": 1717757676.989} +{"unit": "battery_p", "value": -0.221, "time": 1717757676.892} +{"unit": "battery_q", "value": 0.036, "time": 1717757676.892} +{"unit": "b2b_p", "value": 8.929, "time": 1717757677.094} +{"unit": "b2b_q", "value": -3.023, "time": 1717757677.094} +{"unit": "gaia_p", "value": 5.4, "time": 1717757677.048} +{"unit": "gaia_q", "value": -9.027, "time": 1717757677.048} +{"unit": "pcc_p", "value": -39.724, "time": 1717757678.161} +{"unit": "pcc_q", "value": 11.803, "time": 1717757678.161} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757678.111} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757678.111} +{"unit": "pv319_p", "value": 10.297, "time": 1717757678.167} +{"unit": "pv319_q", "value": 0.129, "time": 1717757678.167} +{"unit": "pv330_p", "value": 14.744, "time": 1717757676.989} +{"unit": "pv330_q", "value": -0.182, "time": 1717757676.989} +{"unit": "battery_p", "value": -0.22, "time": 1717757678.122} +{"unit": "battery_q", "value": 0.037, "time": 1717757678.122} +{"unit": "b2b_p", "value": 8.938, "time": 1717757678.156} +{"unit": "b2b_q", "value": -3.047, "time": 1717757678.156} +{"unit": "gaia_p", "value": 5.857, "time": 1717757678.195} +{"unit": "gaia_q", "value": -9.185, "time": 1717757678.195} +{"unit": "pcc_p", "value": -39.44, "time": 1717757679.251} +{"unit": "pcc_q", "value": 11.731, "time": 1717757679.251} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757679.191} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757679.191} +{"unit": "pv319_p", "value": 10.189, "time": 1717757679.215} +{"unit": "pv319_q", "value": 0.125, "time": 1717757679.215} +{"unit": "pv330_p", "value": 14.708, "time": 1717757678.339} +{"unit": "pv330_q", "value": -0.191, "time": 1717757678.339} +{"unit": "battery_p", "value": -0.219, "time": 1717757679.342} +{"unit": "battery_q", "value": 0.034, "time": 1717757679.342} +{"unit": "b2b_p", "value": 8.939, "time": 1717757679.163} +{"unit": "b2b_q", "value": -3.039, "time": 1717757679.163} +{"unit": "gaia_p", "value": 5.344, "time": 1717757679.435} +{"unit": "gaia_q", "value": -8.975, "time": 1717757679.435} +{"unit": "pcc_p", "value": -38.688, "time": 1717757680.326} +{"unit": "pcc_q", "value": 11.422, "time": 1717757680.326} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757680.431} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757680.431} +{"unit": "pv319_p", "value": 10.214, "time": 1717757680.382} +{"unit": "pv319_q", "value": 0.132, "time": 1717757680.382} +{"unit": "pv330_p", "value": 14.71, "time": 1717757680.292} +{"unit": "pv330_q", "value": -0.186, "time": 1717757680.292} +{"unit": "battery_p", "value": -0.221, "time": 1717757680.162} +{"unit": "battery_q", "value": 0.036, "time": 1717757680.162} +{"unit": "b2b_p", "value": 8.938, "time": 1717757680.46} +{"unit": "b2b_q", "value": -3.008, "time": 1717757680.46} +{"unit": "gaia_p", "value": 4.126, "time": 1717757680.448} +{"unit": "gaia_q", "value": -8.495, "time": 1717757680.448} +{"unit": "pcc_p", "value": -38.295, "time": 1717757681.421} +{"unit": "pcc_q", "value": 11.305, "time": 1717757681.596} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757681.511} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757681.511} +{"unit": "pv319_p", "value": 10.258, "time": 1717757681.523} +{"unit": "pv319_q", "value": 0.125, "time": 1717757681.523} +{"unit": "pv330_p", "value": 14.732, "time": 1717757681.334} +{"unit": "pv330_q", "value": -0.179, "time": 1717757681.334} +{"unit": "battery_p", "value": -0.221, "time": 1717757681.392} +{"unit": "battery_q", "value": 0.037, "time": 1717757681.392} +{"unit": "b2b_p", "value": 8.938, "time": 1717757681.364} +{"unit": "b2b_q", "value": -3.031, "time": 1717757681.686} +{"unit": "gaia_p", "value": 4.093, "time": 1717757681.585} +{"unit": "gaia_q", "value": -8.495, "time": 1717757681.585} +{"unit": "pcc_p", "value": -37.245, "time": 1717757682.671} +{"unit": "pcc_q", "value": 10.958, "time": 1717757682.671} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757682.581} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757682.756} +{"unit": "pv319_p", "value": 10.336, "time": 1717757682.557} +{"unit": "pv319_q", "value": 0.13, "time": 1717757682.557} +{"unit": "pv330_p", "value": 14.742, "time": 1717757682.556} +{"unit": "pv330_q", "value": -0.184, "time": 1717757682.556} +{"unit": "battery_p", "value": -0.221, "time": 1717757682.627} +{"unit": "battery_q", "value": 0.031, "time": 1717757682.627} +{"unit": "b2b_p", "value": 8.93, "time": 1717757681.686} +{"unit": "b2b_q", "value": -3.031, "time": 1717757681.686} +{"unit": "gaia_p", "value": 2.982, "time": 1717757682.779} +{"unit": "gaia_q", "value": -8.139, "time": 1717757682.779} +{"unit": "pcc_p", "value": -37.044, "time": 1717757683.756} +{"unit": "pcc_q", "value": 10.886, "time": 1717757683.756} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757683.846} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757683.846} +{"unit": "pv319_p", "value": 10.341, "time": 1717757683.726} +{"unit": "pv319_q", "value": 0.13, "time": 1717757683.726} +{"unit": "pv330_p", "value": 14.765, "time": 1717757683.558} +{"unit": "pv330_q", "value": -0.185, "time": 1717757683.558} +{"unit": "battery_p", "value": -0.221, "time": 1717757683.857} +{"unit": "battery_q", "value": 0.037, "time": 1717757683.857} +{"unit": "b2b_p", "value": 8.939, "time": 1717757683.781} +{"unit": "b2b_q", "value": -3.004, "time": 1717757683.781} +{"unit": "gaia_p", "value": 3.424, "time": 1717757683.713} +{"unit": "gaia_q", "value": -8.265, "time": 1717757683.713} +{"unit": "pcc_p", "value": -37.277, "time": 1717757684.826} +{"unit": "pcc_q", "value": 10.95, "time": 1717757684.826} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757684.936} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757684.936} +{"unit": "pv319_p", "value": 10.35, "time": 1717757684.829} +{"unit": "pv319_q", "value": 0.135, "time": 1717757685.032} +{"unit": "pv330_p", "value": 14.768, "time": 1717757684.979} +{"unit": "pv330_q", "value": -0.185, "time": 1717757684.979} +{"unit": "battery_p", "value": -0.221, "time": 1717757684.682} +{"unit": "battery_q", "value": 0.036, "time": 1717757684.682} +{"unit": "b2b_p", "value": 8.925, "time": 1717757685.001} +{"unit": "b2b_q", "value": -3.027, "time": 1717757685.001} +{"unit": "gaia_p", "value": 2.892, "time": 1717757684.881} +{"unit": "gaia_q", "value": -8.096, "time": 1717757684.881} +{"unit": "pcc_p", "value": -37.372, "time": 1717757686.111} +{"unit": "pcc_q", "value": 10.957, "time": 1717757686.111} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757686.006} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757686.006} +{"unit": "pv319_p", "value": 10.346, "time": 1717757685.967} +{"unit": "pv319_q", "value": 0.139, "time": 1717757685.967} +{"unit": "pv330_p", "value": 14.776, "time": 1717757686.19} +{"unit": "pv330_q", "value": -0.184, "time": 1717757686.19} +{"unit": "battery_p", "value": -0.221, "time": 1717757685.922} +{"unit": "battery_q", "value": 0.038, "time": 1717757685.922} +{"unit": "b2b_p", "value": 8.933, "time": 1717757686.205} +{"unit": "b2b_q", "value": -3.035, "time": 1717757686.205} +{"unit": "gaia_p", "value": 2.478, "time": 1717757686.256} +{"unit": "gaia_q", "value": -7.991, "time": 1717757686.256} +{"unit": "pcc_p", "value": -36.235, "time": 1717757687.261} +{"unit": "pcc_q", "value": 10.639, "time": 1717757687.261} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757687.256} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757687.256} +{"unit": "pv319_p", "value": 10.351, "time": 1717757687.133} +{"unit": "pv319_q", "value": 0.141, "time": 1717757687.133} +{"unit": "pv330_p", "value": 14.825, "time": 1717757687.247} +{"unit": "pv330_q", "value": -0.188, "time": 1717757687.247} +{"unit": "battery_p", "value": -0.221, "time": 1717757687.157} +{"unit": "battery_q", "value": 0.039, "time": 1717757687.157} +{"unit": "b2b_p", "value": 8.933, "time": 1717757686.797} +{"unit": "b2b_q", "value": -3.021, "time": 1717757686.797} +{"unit": "gaia_p", "value": 2.879, "time": 1717757687.184} +{"unit": "gaia_q", "value": -8.133, "time": 1717757687.184} +{"unit": "pcc_p", "value": -36.108, "time": 1717757688.341} +{"unit": "pcc_q", "value": 10.658, "time": 1717757688.341} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757688.341} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757688.341} +{"unit": "pv319_p", "value": 10.352, "time": 1717757688.178} +{"unit": "pv319_q", "value": 0.132, "time": 1717757688.178} +{"unit": "pv330_p", "value": 14.744, "time": 1717757687.559} +{"unit": "pv330_q", "value": -0.19, "time": 1717757687.559} +{"unit": "battery_p", "value": -0.222, "time": 1717757688.367} +{"unit": "battery_q", "value": 0.035, "time": 1717757688.367} +{"unit": "b2b_p", "value": 8.934, "time": 1717757688.461} +{"unit": "b2b_q", "value": -3.045, "time": 1717757688.461} +{"unit": "gaia_p", "value": 1.72, "time": 1717757688.431} +{"unit": "gaia_q", "value": -7.804, "time": 1717757688.431} +{"unit": "pcc_p", "value": -35.994, "time": 1717757689.426} +{"unit": "pcc_q", "value": 10.642, "time": 1717757689.426} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757689.486} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757689.486} +{"unit": "pv319_p", "value": 10.329, "time": 1717757689.57} +{"unit": "pv319_q", "value": 0.132, "time": 1717757689.57} +{"unit": "pv330_p", "value": 14.809, "time": 1717757689.388} +{"unit": "pv330_q", "value": -0.179, "time": 1717757689.388} +{"unit": "battery_p", "value": -0.222, "time": 1717757689.572} +{"unit": "battery_q", "value": 0.039, "time": 1717757689.572} +{"unit": "b2b_p", "value": 8.935, "time": 1717757689.663} +{"unit": "b2b_q", "value": -3.061, "time": 1717757689.663} +{"unit": "gaia_p", "value": 1.688, "time": 1717757689.558} +{"unit": "gaia_q", "value": -7.804, "time": 1717757689.558} +{"unit": "pcc_p", "value": -35.921, "time": 1717757690.676} +{"unit": "pcc_q", "value": 10.647, "time": 1717757690.676} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757690.726} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757690.726} +{"unit": "pv319_p", "value": 10.339, "time": 1717757690.58} +{"unit": "pv319_q", "value": 0.135, "time": 1717757690.58} +{"unit": "pv330_p", "value": 14.817, "time": 1717757690.647} +{"unit": "pv330_q", "value": -0.185, "time": 1717757690.647} +{"unit": "battery_p", "value": -0.222, "time": 1717757690.412} +{"unit": "battery_q", "value": 0.039, "time": 1717757690.412} +{"unit": "b2b_p", "value": 8.934, "time": 1717757690.628} +{"unit": "b2b_q", "value": -3.033, "time": 1717757690.82} +{"unit": "gaia_p", "value": 1.572, "time": 1717757690.698} +{"unit": "gaia_q", "value": -7.803, "time": 1717757690.698} +{"unit": "pcc_p", "value": -36.268, "time": 1717757691.776} +{"unit": "pcc_q", "value": 10.735, "time": 1717757691.776} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757691.786} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757691.786} +{"unit": "pv319_p", "value": 10.34, "time": 1717757691.752} +{"unit": "pv319_q", "value": 0.131, "time": 1717757691.752} +{"unit": "pv330_p", "value": 14.82, "time": 1717757691.788} +{"unit": "pv330_q", "value": -0.189, "time": 1717757691.788} +{"unit": "battery_p", "value": -0.222, "time": 1717757691.637} +{"unit": "battery_q", "value": 0.038, "time": 1717757691.637} +{"unit": "b2b_p", "value": 8.934, "time": 1717757691.535} +{"unit": "b2b_q", "value": -3.027, "time": 1717757691.535} +{"unit": "gaia_p", "value": 2.109, "time": 1717757691.915} +{"unit": "gaia_q", "value": -7.952, "time": 1717757691.915} +{"unit": "pcc_p", "value": -36.291, "time": 1717757692.866} +{"unit": "pcc_q", "value": 10.723, "time": 1717757692.866} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757692.871} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757692.871} +{"unit": "pv319_p", "value": 10.327, "time": 1717757692.826} +{"unit": "pv319_q", "value": 0.126, "time": 1717757692.826} +{"unit": "pv330_p", "value": 14.833, "time": 1717757692.757} +{"unit": "pv330_q", "value": -0.184, "time": 1717757692.757} +{"unit": "battery_p", "value": -0.221, "time": 1717757692.912} +{"unit": "battery_q", "value": 0.038, "time": 1717757692.912} +{"unit": "b2b_p", "value": 8.931, "time": 1717757692.677} +{"unit": "b2b_q", "value": -3.035, "time": 1717757692.677} +{"unit": "gaia_p", "value": 2.007, "time": 1717757693.114} +{"unit": "gaia_q", "value": -7.895, "time": 1717757693.114} +{"unit": "pcc_p", "value": -35.871, "time": 1717757694.136} +{"unit": "pcc_q", "value": 10.603, "time": 1717757694.136} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757694.126} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757694.126} +{"unit": "pv319_p", "value": 10.345, "time": 1717757693.968} +{"unit": "pv319_q", "value": 0.124, "time": 1717757693.968} +{"unit": "pv330_p", "value": 14.835, "time": 1717757694.153} +{"unit": "pv330_q", "value": -0.178, "time": 1717757694.153} +{"unit": "battery_p", "value": -0.221, "time": 1717757694.137} +{"unit": "battery_q", "value": 0.039, "time": 1717757694.137} +{"unit": "b2b_p", "value": 8.93, "time": 1717757694.207} +{"unit": "b2b_q", "value": -3.041, "time": 1717757694.207} +{"unit": "gaia_p", "value": 1.505, "time": 1717757694.252} +{"unit": "gaia_q", "value": -7.752, "time": 1717757694.252} +{"unit": "pcc_p", "value": -35.301, "time": 1717757695.216} +{"unit": "pcc_q", "value": 10.502, "time": 1717757695.216} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757695.201} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757695.201} +{"unit": "pv319_p", "value": 10.3, "time": 1717757695.177} +{"unit": "pv319_q", "value": 0.119, "time": 1717757695.177} +{"unit": "pv330_p", "value": 14.87, "time": 1717757695.051} +{"unit": "pv330_q", "value": -0.178, "time": 1717757695.051} +{"unit": "battery_p", "value": -0.222, "time": 1717757694.962} +{"unit": "battery_q", "value": 0.038, "time": 1717757694.962} +{"unit": "b2b_p", "value": 8.921, "time": 1717757695.221} +{"unit": "b2b_q", "value": -3.035, "time": 1717757695.221} +{"unit": "gaia_p", "value": 1.016, "time": 1717757694.999} +{"unit": "gaia_q", "value": -7.676, "time": 1717757695.38} +{"unit": "pcc_p", "value": -35.333, "time": 1717757696.306} +{"unit": "pcc_q", "value": 10.511, "time": 1717757696.306} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757696.271} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757696.271} +{"unit": "pv319_p", "value": 10.382, "time": 1717757696.177} +{"unit": "pv319_q", "value": 0.105, "time": 1717757696.177} +{"unit": "pv330_p", "value": 14.83, "time": 1717757696.374} +{"unit": "pv330_q", "value": -0.187, "time": 1717757696.374} +{"unit": "battery_p", "value": -0.222, "time": 1717757696.182} +{"unit": "battery_q", "value": 0.038, "time": 1717757696.182} +{"unit": "b2b_p", "value": 8.933, "time": 1717757696.371} +{"unit": "b2b_q", "value": -3.034, "time": 1717757696.371} +{"unit": "gaia_p", "value": 1.241, "time": 1717757696.495} +{"unit": "gaia_q", "value": -7.693, "time": 1717757696.495} +{"unit": "pcc_p", "value": -35.627, "time": 1717757697.411} +{"unit": "pcc_q", "value": 10.561, "time": 1717757697.411} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757697.506} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757697.506} +{"unit": "pv319_p", "value": 10.388, "time": 1717757697.222} +{"unit": "pv319_q", "value": 0.121, "time": 1717757697.222} +{"unit": "pv330_p", "value": 14.84, "time": 1717757697.538} +{"unit": "pv330_q", "value": -0.179, "time": 1717757697.538} +{"unit": "battery_p", "value": -0.221, "time": 1717757697.402} +{"unit": "battery_q", "value": 0.039, "time": 1717757697.402} +{"unit": "b2b_p", "value": 8.93, "time": 1717757697.386} +{"unit": "b2b_q", "value": -3.03, "time": 1717757697.386} +{"unit": "gaia_p", "value": 1.425, "time": 1717757697.524} +{"unit": "gaia_q", "value": -7.762, "time": 1717757697.524} +{"unit": "pcc_p", "value": -36.313, "time": 1717757698.516} +{"unit": "pcc_q", "value": 10.771, "time": 1717757698.516} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757698.596} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757698.596} +{"unit": "pv319_p", "value": 10.382, "time": 1717757698.618} +{"unit": "pv319_q", "value": 0.118, "time": 1717757698.618} +{"unit": "pv330_p", "value": 14.866, "time": 1717757698.657} +{"unit": "pv330_q", "value": -0.187, "time": 1717757698.657} +{"unit": "battery_p", "value": -0.222, "time": 1717757698.627} +{"unit": "battery_q", "value": 0.039, "time": 1717757698.627} +{"unit": "b2b_p", "value": 8.931, "time": 1717757697.661} +{"unit": "b2b_q", "value": -3.057, "time": 1717757697.661} +{"unit": "gaia_p", "value": 1.294, "time": 1717757698.649} +{"unit": "gaia_q", "value": -7.706, "time": 1717757698.649} +{"unit": "pcc_p", "value": -37.016, "time": 1717757699.601} +{"unit": "pcc_q", "value": 10.957, "time": 1717757699.601} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757699.666} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757699.666} +{"unit": "pv319_p", "value": 10.397, "time": 1717757699.769} +{"unit": "pv319_q", "value": 0.116, "time": 1717757699.769} +{"unit": "pv330_p", "value": 14.853, "time": 1717757699.796} +{"unit": "pv330_q", "value": -0.19, "time": 1717757699.796} +{"unit": "battery_p", "value": -0.222, "time": 1717757699.452} +{"unit": "battery_q", "value": 0.039, "time": 1717757699.452} +{"unit": "b2b_p", "value": 8.929, "time": 1717757699.708} +{"unit": "b2b_q", "value": -3.053, "time": 1717757699.708} +{"unit": "gaia_p", "value": 2.615, "time": 1717757699.691} +{"unit": "gaia_q", "value": -8.118, "time": 1717757699.691} +{"unit": "pcc_p", "value": -36.909, "time": 1717757700.866} +{"unit": "pcc_q", "value": 10.832, "time": 1717757700.866} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757700.746} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757700.746} +{"unit": "pv319_p", "value": 10.363, "time": 1717757700.767} +{"unit": "pv319_q", "value": 0.11, "time": 1717757700.767} +{"unit": "pv330_p", "value": 14.865, "time": 1717757700.936} +{"unit": "pv330_q", "value": -0.188, "time": 1717757700.936} +{"unit": "battery_p", "value": -0.219, "time": 1717757700.702} +{"unit": "battery_q", "value": 0.037, "time": 1717757700.702} +{"unit": "b2b_p", "value": 8.939, "time": 1717757700.764} +{"unit": "b2b_q", "value": -3.046, "time": 1717757700.764} +{"unit": "gaia_p", "value": 2.565, "time": 1717757700.863} +{"unit": "gaia_q", "value": -8.043, "time": 1717757700.863} +{"unit": "pcc_p", "value": -37.357, "time": 1717757701.936} +{"unit": "pcc_q", "value": 10.957, "time": 1717757701.936} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757701.996} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757701.996} +{"unit": "pv319_p", "value": 10.403, "time": 1717757701.815} +{"unit": "pv319_q", "value": 0.117, "time": 1717757701.815} +{"unit": "pv330_p", "value": 14.852, "time": 1717757701.779} +{"unit": "pv330_q", "value": -0.185, "time": 1717757701.779} +{"unit": "battery_p", "value": -0.22, "time": 1717757701.937} +{"unit": "battery_q", "value": 0.035, "time": 1717757701.937} +{"unit": "b2b_p", "value": 8.929, "time": 1717757701.964} +{"unit": "b2b_q", "value": -3.025, "time": 1717757701.964} +{"unit": "gaia_p", "value": 3.233, "time": 1717757702.051} +{"unit": "gaia_q", "value": -8.245, "time": 1717757702.051} +{"unit": "pcc_p", "value": -37.679, "time": 1717757703.021} +{"unit": "pcc_q", "value": 11.045, "time": 1717757703.021} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757703.056} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757703.056} +{"unit": "pv319_p", "value": 10.399, "time": 1717757702.946} +{"unit": "pv319_q", "value": 0.124, "time": 1717757702.946} +{"unit": "pv330_p", "value": 14.857, "time": 1717757703.06} +{"unit": "pv330_q", "value": -0.188, "time": 1717757703.06} +{"unit": "battery_p", "value": -0.222, "time": 1717757703.182} +{"unit": "battery_q", "value": 0.032, "time": 1717757703.182} +{"unit": "b2b_p", "value": 8.927, "time": 1717757702.169} +{"unit": "b2b_q", "value": -3.046, "time": 1717757703.325} +{"unit": "gaia_p", "value": 3.147, "time": 1717757703.185} +{"unit": "gaia_q", "value": -8.22, "time": 1717757703.185} +{"unit": "pcc_p", "value": -37.605, "time": 1717757704.351} +{"unit": "pcc_q", "value": 11.043, "time": 1717757704.351} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757704.321} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757704.321} +{"unit": "pv319_p", "value": 10.373, "time": 1717757704.377} +{"unit": "pv319_q", "value": 0.125, "time": 1717757704.377} +{"unit": "pv330_p", "value": 14.874, "time": 1717757704.388} +{"unit": "pv330_q", "value": -0.182, "time": 1717757704.388} +{"unit": "battery_p", "value": -0.221, "time": 1717757704.412} +{"unit": "battery_q", "value": 0.037, "time": 1717757704.412} +{"unit": "b2b_p", "value": 8.932, "time": 1717757704.575} +{"unit": "b2b_q", "value": -3.043, "time": 1717757704.575} +{"unit": "gaia_p", "value": 3.822, "time": 1717757704.387} +{"unit": "gaia_q", "value": -8.462, "time": 1717757704.387} +{"unit": "pcc_p", "value": -37.76, "time": 1717757705.596} +{"unit": "pcc_q", "value": 11.1, "time": 1717757705.596} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757705.666} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757705.666} +{"unit": "pv319_p", "value": 10.41, "time": 1717757705.577} +{"unit": "pv319_q", "value": 0.124, "time": 1717757705.577} +{"unit": "pv330_p", "value": 14.882, "time": 1717757705.589} +{"unit": "pv330_q", "value": -0.184, "time": 1717757705.589} +{"unit": "battery_p", "value": -0.222, "time": 1717757705.642} +{"unit": "battery_q", "value": 0.037, "time": 1717757705.642} +{"unit": "b2b_p", "value": 8.93, "time": 1717757705.676} +{"unit": "b2b_q", "value": -3.035, "time": 1717757705.676} +{"unit": "gaia_p", "value": 3.533, "time": 1717757705.648} +{"unit": "gaia_q", "value": -8.385, "time": 1717757705.648} +{"unit": "pcc_p", "value": -37.908, "time": 1717757706.666} +{"unit": "pcc_q", "value": 11.163, "time": 1717757706.666} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757706.751} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757706.751} +{"unit": "pv319_p", "value": 10.403, "time": 1717757706.544} +{"unit": "pv319_q", "value": 0.127, "time": 1717757706.544} +{"unit": "pv330_p", "value": 14.883, "time": 1717757706.78} +{"unit": "pv330_q", "value": -0.189, "time": 1717757706.78} +{"unit": "battery_p", "value": -0.221, "time": 1717757706.882} +{"unit": "battery_q", "value": 0.038, "time": 1717757706.882} +{"unit": "b2b_p", "value": 8.929, "time": 1717757706.735} +{"unit": "b2b_q", "value": -2.994, "time": 1717757706.966} +{"unit": "gaia_p", "value": 4.305, "time": 1717757706.896} +{"unit": "gaia_q", "value": -8.504, "time": 1717757706.896} +{"unit": "pcc_p", "value": -7.693, "time": 1717757707.941} +{"unit": "pcc_q", "value": 4.983, "time": 1717757707.941} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757708.011} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757708.011} +{"unit": "pv319_p", "value": 10.403, "time": 1717757708.024} +{"unit": "pv319_q", "value": 0.126, "time": 1717757708.024} +{"unit": "pv330_p", "value": 14.844, "time": 1717757707.343} +{"unit": "pv330_q", "value": -0.172, "time": 1717757707.343} +{"unit": "battery_p", "value": -0.222, "time": 1717757707.697} +{"unit": "battery_q", "value": 0.037, "time": 1717757707.697} +{"unit": "b2b_p", "value": -20.043, "time": 1717757707.589} +{"unit": "b2b_q", "value": 2.447, "time": 1717757707.589} +{"unit": "gaia_p", "value": 3.778, "time": 1717757707.897} +{"unit": "gaia_q", "value": -8.051, "time": 1717757707.897} +{"unit": "pcc_p", "value": -3.233, "time": 1717757709.021} +{"unit": "pcc_q", "value": 2.149, "time": 1717757709.021} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757709.081} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757709.081} +{"unit": "pv319_p", "value": 10.411, "time": 1717757708.976} +{"unit": "pv319_q", "value": 0.13, "time": 1717757708.976} +{"unit": "pv330_p", "value": 14.855, "time": 1717757709.149} +{"unit": "pv330_q", "value": -0.182, "time": 1717757709.149} +{"unit": "battery_p", "value": -0.222, "time": 1717757708.947} +{"unit": "battery_q", "value": 0.039, "time": 1717757708.947} +{"unit": "b2b_p", "value": -26.121, "time": 1717757709.166} +{"unit": "b2b_q", "value": 5.748, "time": 1717757709.166} +{"unit": "gaia_p", "value": 3.379, "time": 1717757709.05} +{"unit": "gaia_q", "value": -7.865, "time": 1717757709.05} +{"unit": "pcc_p", "value": -1.893, "time": 1717757710.106} +{"unit": "pcc_q", "value": 0.933, "time": 1717757710.286} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757710.161} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757710.161} +{"unit": "pv319_p", "value": 10.404, "time": 1717757710.323} +{"unit": "pv319_q", "value": 0.133, "time": 1717757710.323} +{"unit": "pv330_p", "value": 14.897, "time": 1717757710.176} +{"unit": "pv330_q", "value": -0.189, "time": 1717757710.176} +{"unit": "battery_p", "value": -0.222, "time": 1717757710.182} +{"unit": "battery_q", "value": 0.037, "time": 1717757710.182} +{"unit": "b2b_p", "value": -27.657, "time": 1717757710.261} +{"unit": "b2b_q", "value": 6.744, "time": 1717757710.261} +{"unit": "gaia_p", "value": 3.9, "time": 1717757710.296} +{"unit": "gaia_q", "value": -7.993, "time": 1717757710.296} +{"unit": "pcc_p", "value": -0.896, "time": 1717757711.351} +{"unit": "pcc_q", "value": 0.894, "time": 1717757711.351} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757711.406} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757711.406} +{"unit": "pv319_p", "value": 10.409, "time": 1717757711.425} +{"unit": "pv319_q", "value": 0.13, "time": 1717757711.425} +{"unit": "pv330_p", "value": 14.9, "time": 1717757711.338} +{"unit": "pv330_q", "value": -0.185, "time": 1717757711.338} +{"unit": "battery_p", "value": -0.221, "time": 1717757711.412} +{"unit": "battery_q", "value": 0.037, "time": 1717757711.412} +{"unit": "b2b_p", "value": -28.297, "time": 1717757711.411} +{"unit": "b2b_q", "value": 6.836, "time": 1717757711.411} +{"unit": "gaia_p", "value": 3.879, "time": 1717757711.523} +{"unit": "gaia_q", "value": -7.981, "time": 1717757711.523} +{"unit": "pcc_p", "value": -0.505, "time": 1717757712.406} +{"unit": "pcc_q", "value": 0.873, "time": 1717757712.406} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757712.466} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757712.466} +{"unit": "pv319_p", "value": 10.395, "time": 1717757712.521} +{"unit": "pv319_q", "value": 0.127, "time": 1717757712.521} +{"unit": "pv330_p", "value": 14.924, "time": 1717757712.34} +{"unit": "pv330_q", "value": -0.186, "time": 1717757712.34} +{"unit": "battery_p", "value": -0.222, "time": 1717757712.617} +{"unit": "battery_q", "value": 0.036, "time": 1717757712.617} +{"unit": "b2b_p", "value": -28.646, "time": 1717757712.593} +{"unit": "b2b_q", "value": 6.797, "time": 1717757712.593} +{"unit": "gaia_p", "value": 3.733, "time": 1717757712.587} +{"unit": "gaia_q", "value": -7.942, "time": 1717757712.587} +{"unit": "pcc_p", "value": 0.07, "time": 1717757713.661} +{"unit": "pcc_q", "value": 0.769, "time": 1717757713.661} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757713.536} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757713.536} +{"unit": "pv319_p", "value": 10.41, "time": 1717757713.616} +{"unit": "pv319_q", "value": 0.121, "time": 1717757713.616} +{"unit": "pv330_p", "value": 14.969, "time": 1717757713.701} +{"unit": "pv330_q", "value": -0.183, "time": 1717757713.701} +{"unit": "battery_p", "value": -0.222, "time": 1717757713.427} +{"unit": "battery_q", "value": 0.036, "time": 1717757713.427} +{"unit": "b2b_p", "value": -28.666, "time": 1717757712.966} +{"unit": "b2b_q", "value": 6.817, "time": 1717757712.966} +{"unit": "gaia_p", "value": 3.935, "time": 1717757713.649} +{"unit": "gaia_q", "value": -8.012, "time": 1717757713.649} +{"unit": "pcc_p", "value": 0.179, "time": 1717757714.736} +{"unit": "pcc_q", "value": 0.683, "time": 1717757714.736} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757714.796} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757714.796} +{"unit": "pv319_p", "value": 10.43, "time": 1717757714.78} +{"unit": "pv319_q", "value": 0.122, "time": 1717757714.78} +{"unit": "pv330_p", "value": 14.982, "time": 1717757714.8} +{"unit": "pv330_q", "value": -0.183, "time": 1717757714.8} +{"unit": "battery_p", "value": -0.221, "time": 1717757714.667} +{"unit": "battery_q", "value": 0.036, "time": 1717757714.667} +{"unit": "b2b_p", "value": -28.915, "time": 1717757714.711} +{"unit": "b2b_q", "value": 6.788, "time": 1717757714.711} +{"unit": "gaia_p", "value": 3.746, "time": 1717757714.715} +{"unit": "gaia_q", "value": -7.948, "time": 1717757714.715} +{"unit": "pcc_p", "value": 0.032, "time": 1717757716.001} +{"unit": "pcc_q", "value": 0.837, "time": 1717757716.001} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757715.866} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757716.046} +{"unit": "pv319_p", "value": 10.418, "time": 1717757716.008} +{"unit": "pv319_q", "value": 0.124, "time": 1717757716.008} +{"unit": "pv330_p", "value": 14.976, "time": 1717757715.937} +{"unit": "pv330_q", "value": -0.189, "time": 1717757715.937} +{"unit": "battery_p", "value": -0.221, "time": 1717757715.902} +{"unit": "battery_q", "value": 0.035, "time": 1717757715.902} +{"unit": "b2b_p", "value": -28.995, "time": 1717757715.986} +{"unit": "b2b_q", "value": 6.806, "time": 1717757715.986} +{"unit": "gaia_p", "value": 3.623, "time": 1717757716.114} +{"unit": "gaia_q", "value": -7.917, "time": 1717757716.114} +{"unit": "pcc_p", "value": 0.014, "time": 1717757717.081} +{"unit": "pcc_q", "value": 0.808, "time": 1717757717.081} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757717.126} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757717.126} +{"unit": "pv319_p", "value": 10.44, "time": 1717757717.133} +{"unit": "pv319_q", "value": 0.119, "time": 1717757717.133} +{"unit": "pv330_p", "value": 14.966, "time": 1717757717.189} +{"unit": "pv330_q", "value": -0.186, "time": 1717757717.189} +{"unit": "battery_p", "value": -0.22, "time": 1717757717.132} +{"unit": "battery_q", "value": 0.037, "time": 1717757717.132} +{"unit": "b2b_p", "value": -29.015, "time": 1717757717.061} +{"unit": "b2b_q", "value": 6.796, "time": 1717757717.061} +{"unit": "gaia_p", "value": 3.583, "time": 1717757717.096} +{"unit": "gaia_q", "value": -7.892, "time": 1717757717.096} +{"unit": "pcc_p", "value": -0.153, "time": 1717757718.161} +{"unit": "pcc_q", "value": 0.844, "time": 1717757718.161} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757718.206} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757718.206} +{"unit": "pv319_p", "value": 10.445, "time": 1717757718.325} +{"unit": "pv319_q", "value": 0.124, "time": 1717757718.325} +{"unit": "pv330_p", "value": 14.999, "time": 1717757718.028} +{"unit": "pv330_q", "value": -0.183, "time": 1717757718.028} +{"unit": "battery_p", "value": -0.221, "time": 1717757718.352} +{"unit": "battery_q", "value": 0.028, "time": 1717757718.352} +{"unit": "b2b_p", "value": -28.951, "time": 1717757718.252} +{"unit": "b2b_q", "value": 6.804, "time": 1717757718.252} +{"unit": "gaia_p", "value": 3.751, "time": 1717757718.296} +{"unit": "gaia_q", "value": -7.95, "time": 1717757718.296} +{"unit": "pcc_p", "value": -0.387, "time": 1717757719.296} +{"unit": "pcc_q", "value": 0.969, "time": 1717757719.296} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757719.451} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757719.451} +{"unit": "pv319_p", "value": 10.442, "time": 1717757719.326} +{"unit": "pv319_q", "value": 0.12, "time": 1717757719.326} +{"unit": "pv330_p", "value": 14.976, "time": 1717757719.389} +{"unit": "pv330_q", "value": -0.185, "time": 1717757719.389} +{"unit": "battery_p", "value": -0.221, "time": 1717757719.167} +{"unit": "battery_q", "value": 0.03, "time": 1717757719.167} +{"unit": "b2b_p", "value": -28.951, "time": 1717757718.252} +{"unit": "b2b_q", "value": 6.804, "time": 1717757718.252} +{"unit": "gaia_p", "value": 4.024, "time": 1717757719.37} +{"unit": "gaia_q", "value": -8.046, "time": 1717757719.37} +{"unit": "pcc_p", "value": -0.112, "time": 1717757720.556} +{"unit": "pcc_q", "value": 0.9, "time": 1717757720.556} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757720.541} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757720.541} +{"unit": "pv319_p", "value": 10.463, "time": 1717757720.616} +{"unit": "pv319_q", "value": 0.122, "time": 1717757720.616} +{"unit": "pv330_p", "value": 14.971, "time": 1717757720.579} +{"unit": "pv330_q", "value": -0.179, "time": 1717757720.579} +{"unit": "battery_p", "value": -0.221, "time": 1717757720.382} +{"unit": "battery_q", "value": 0.034, "time": 1717757720.382} +{"unit": "b2b_p", "value": -29.235, "time": 1717757720.621} +{"unit": "b2b_q", "value": 6.78, "time": 1717757720.621} +{"unit": "gaia_p", "value": 4.17, "time": 1717757720.45} +{"unit": "gaia_q", "value": -8.078, "time": 1717757720.45} +{"unit": "pcc_p", "value": -0.153, "time": 1717757721.626} +{"unit": "pcc_q", "value": 1.011, "time": 1717757721.626} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757721.676} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757721.676} +{"unit": "pv319_p", "value": 10.448, "time": 1717757721.747} +{"unit": "pv319_q", "value": 0.123, "time": 1717757721.747} +{"unit": "pv330_p", "value": 14.939, "time": 1717757721.673} +{"unit": "pv330_q", "value": -0.183, "time": 1717757721.673} +{"unit": "battery_p", "value": -0.222, "time": 1717757721.622} +{"unit": "battery_q", "value": 0.035, "time": 1717757721.622} +{"unit": "b2b_p", "value": -29.446, "time": 1717757721.62} +{"unit": "b2b_q", "value": 6.78, "time": 1717757721.82} +{"unit": "gaia_p", "value": 4.331, "time": 1717757721.782} +{"unit": "gaia_q", "value": -8.142, "time": 1717757721.782} +{"unit": "pcc_p", "value": -0.005, "time": 1717757722.702} +{"unit": "pcc_q", "value": 0.998, "time": 1717757722.702} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757722.756} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757722.756} +{"unit": "pv319_p", "value": 10.441, "time": 1717757722.553} +{"unit": "pv319_q", "value": 0.129, "time": 1717757722.553} +{"unit": "pv330_p", "value": 15.015, "time": 1717757721.98} +{"unit": "pv330_q", "value": -0.187, "time": 1717757721.98} +{"unit": "battery_p", "value": -0.221, "time": 1717757722.842} +{"unit": "battery_q", "value": 0.037, "time": 1717757722.842} +{"unit": "b2b_p", "value": -29.612, "time": 1717757722.5} +{"unit": "b2b_q", "value": 6.801, "time": 1717757722.5} +{"unit": "gaia_p", "value": 4.434, "time": 1717757722.984} +{"unit": "gaia_q", "value": -8.185, "time": 1717757722.984} +{"unit": "pcc_p", "value": 0.065, "time": 1717757723.982} +{"unit": "pcc_q", "value": 1.098, "time": 1717757723.982} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757724.011} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757724.011} +{"unit": "pv319_p", "value": 10.319, "time": 1717757723.97} +{"unit": "pv319_q", "value": 0.118, "time": 1717757723.97} +{"unit": "pv330_p", "value": 14.979, "time": 1717757723.935} +{"unit": "pv330_q", "value": -0.19, "time": 1717757723.935} +{"unit": "battery_p", "value": -0.221, "time": 1717757724.047} +{"unit": "battery_q", "value": 0.036, "time": 1717757724.047} +{"unit": "b2b_p", "value": -29.667, "time": 1717757724.072} +{"unit": "b2b_q", "value": 6.798, "time": 1717757724.072} +{"unit": "gaia_p", "value": 4.094, "time": 1717757724.065} +{"unit": "gaia_q", "value": -8.055, "time": 1717757724.065} +{"unit": "pcc_p", "value": -0.062, "time": 1717757725.052} +{"unit": "pcc_q", "value": 1.055, "time": 1717757725.052} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757725.081} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757725.081} +{"unit": "pv319_p", "value": 10.455, "time": 1717757725.078} +{"unit": "pv319_q", "value": 0.123, "time": 1717757725.078} +{"unit": "pv330_p", "value": 14.992, "time": 1717757724.974} +{"unit": "pv330_q", "value": -0.182, "time": 1717757725.229} +{"unit": "battery_p", "value": -0.221, "time": 1717757724.947} +{"unit": "battery_q", "value": 0.036, "time": 1717757724.947} +{"unit": "b2b_p", "value": -29.653, "time": 1717757725.073} +{"unit": "b2b_q", "value": 6.781, "time": 1717757725.261} +{"unit": "gaia_p", "value": 4.173, "time": 1717757725.25} +{"unit": "gaia_q", "value": -8.084, "time": 1717757725.25} +{"unit": "pcc_p", "value": -0.077, "time": 1717757726.147} +{"unit": "pcc_q", "value": 1.016, "time": 1717757726.147} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757726.151} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757726.151} +{"unit": "pv319_p", "value": 10.446, "time": 1717757726.217} +{"unit": "pv319_q", "value": 0.134, "time": 1717757726.217} +{"unit": "pv330_p", "value": 15.033, "time": 1717757726.19} +{"unit": "pv330_q", "value": -0.184, "time": 1717757726.19} +{"unit": "battery_p", "value": -0.221, "time": 1717757726.157} +{"unit": "battery_q", "value": 0.036, "time": 1717757726.157} +{"unit": "b2b_p", "value": -29.551, "time": 1717757726.268} +{"unit": "b2b_q", "value": 6.789, "time": 1717757726.268} +{"unit": "gaia_p", "value": 4.033, "time": 1717757726.229} +{"unit": "gaia_q", "value": -8.037, "time": 1717757726.229} +{"unit": "pcc_p", "value": 0.054, "time": 1717757727.397} +{"unit": "pcc_q", "value": 0.892, "time": 1717757727.397} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757727.391} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757727.391} +{"unit": "pv319_p", "value": 10.469, "time": 1717757727.133} +{"unit": "pv319_q", "value": 0.132, "time": 1717757727.133} +{"unit": "pv330_p", "value": 15.0, "time": 1717757727.228} +{"unit": "pv330_q", "value": -0.19, "time": 1717757727.228} +{"unit": "battery_p", "value": -0.222, "time": 1717757727.387} +{"unit": "battery_q", "value": 0.038, "time": 1717757727.387} +{"unit": "b2b_p", "value": -29.369, "time": 1717757727.501} +{"unit": "b2b_q", "value": 6.798, "time": 1717757727.501} +{"unit": "gaia_p", "value": 3.741, "time": 1717757727.482} +{"unit": "gaia_q", "value": -7.944, "time": 1717757727.482} +{"unit": "pcc_p", "value": -0.001, "time": 1717757728.477} +{"unit": "pcc_q", "value": 0.882, "time": 1717757728.477} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757728.466} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757728.466} +{"unit": "pv319_p", "value": 10.466, "time": 1717757728.578} +{"unit": "pv319_q", "value": 0.131, "time": 1717757728.578} +{"unit": "pv330_p", "value": 15.03, "time": 1717757728.582} +{"unit": "pv330_q", "value": -0.186, "time": 1717757728.582} +{"unit": "battery_p", "value": -0.221, "time": 1717757728.607} +{"unit": "battery_q", "value": 0.039, "time": 1717757728.607} +{"unit": "b2b_p", "value": -29.374, "time": 1717757727.862} +{"unit": "b2b_q", "value": 6.778, "time": 1717757727.862} +{"unit": "gaia_p", "value": 3.897, "time": 1717757728.296} +{"unit": "gaia_q", "value": -8.029, "time": 1717757728.659} +{"unit": "pcc_p", "value": -0.083, "time": 1717757729.552} +{"unit": "pcc_q", "value": 0.84, "time": 1717757729.552} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757729.556} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757729.556} +{"unit": "pv319_p", "value": 10.483, "time": 1717757729.626} +{"unit": "pv319_q", "value": 0.13, "time": 1717757729.626} +{"unit": "pv330_p", "value": 15.045, "time": 1717757729.717} +{"unit": "pv330_q", "value": -0.185, "time": 1717757729.717} +{"unit": "battery_p", "value": -0.221, "time": 1717757729.417} +{"unit": "battery_q", "value": 0.039, "time": 1717757729.417} +{"unit": "b2b_p", "value": -29.109, "time": 1717757729.574} +{"unit": "b2b_q", "value": 6.788, "time": 1717757729.574} +{"unit": "gaia_p", "value": 3.586, "time": 1717757729.462} +{"unit": "gaia_q", "value": -7.887, "time": 1717757729.462} +{"unit": "pcc_p", "value": 0.477, "time": 1717757730.802} +{"unit": "pcc_q", "value": 0.592, "time": 1717757730.802} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757730.806} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757730.806} +{"unit": "pv319_p", "value": 10.473, "time": 1717757730.777} +{"unit": "pv319_q", "value": 0.126, "time": 1717757730.777} +{"unit": "pv330_p", "value": 15.013, "time": 1717757730.849} +{"unit": "pv330_q", "value": -0.182, "time": 1717757730.849} +{"unit": "battery_p", "value": -0.22, "time": 1717757730.637} +{"unit": "battery_q", "value": 0.037, "time": 1717757730.637} +{"unit": "b2b_p", "value": -28.859, "time": 1717757730.864} +{"unit": "b2b_q", "value": 6.802, "time": 1717757730.864} +{"unit": "gaia_p", "value": 3.421, "time": 1717757730.785} +{"unit": "gaia_q", "value": -7.832, "time": 1717757730.785} +{"unit": "pcc_p", "value": 0.257, "time": 1717757731.877} +{"unit": "pcc_q", "value": 0.606, "time": 1717757731.877} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757731.896} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757731.896} +{"unit": "pv319_p", "value": 10.481, "time": 1717757731.747} +{"unit": "pv319_q", "value": 0.124, "time": 1717757731.747} +{"unit": "pv330_p", "value": 14.975, "time": 1717757731.975} +{"unit": "pv330_q", "value": -0.187, "time": 1717757731.975} +{"unit": "battery_p", "value": -0.222, "time": 1717757731.877} +{"unit": "battery_q", "value": 0.037, "time": 1717757731.877} +{"unit": "b2b_p", "value": -28.559, "time": 1717757731.972} +{"unit": "b2b_q", "value": 6.798, "time": 1717757731.972} +{"unit": "gaia_p", "value": 2.915, "time": 1717757731.987} +{"unit": "gaia_q", "value": -7.685, "time": 1717757731.987} +{"unit": "pcc_p", "value": -0.169, "time": 1717757732.972} +{"unit": "pcc_q", "value": 0.754, "time": 1717757732.972} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757732.976} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757732.976} +{"unit": "pv319_p", "value": 10.476, "time": 1717757732.828} +{"unit": "pv319_q", "value": 0.131, "time": 1717757732.828} +{"unit": "pv330_p", "value": 15.005, "time": 1717757733.03} +{"unit": "pv330_q", "value": -0.19, "time": 1717757733.03} +{"unit": "battery_p", "value": -0.222, "time": 1717757733.087} +{"unit": "battery_q", "value": 0.037, "time": 1717757733.087} +{"unit": "b2b_p", "value": -28.566, "time": 1717757732.807} +{"unit": "b2b_q", "value": 6.783, "time": 1717757732.807} +{"unit": "gaia_p", "value": 3.546, "time": 1717757733.064} +{"unit": "gaia_q", "value": -7.916, "time": 1717757733.064} +{"unit": "pcc_p", "value": 0.09, "time": 1717757734.052} +{"unit": "pcc_q", "value": 0.883, "time": 1717757734.227} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757734.241} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757734.241} +{"unit": "pv319_p", "value": 10.437, "time": 1717757734.217} +{"unit": "pv319_q", "value": 0.142, "time": 1717757734.217} +{"unit": "pv330_p", "value": 14.977, "time": 1717757734.357} +{"unit": "pv330_q", "value": -0.184, "time": 1717757734.357} +{"unit": "battery_p", "value": -0.222, "time": 1717757734.292} +{"unit": "battery_q", "value": 0.038, "time": 1717757734.292} +{"unit": "b2b_p", "value": -28.562, "time": 1717757733.341} +{"unit": "b2b_q", "value": 6.787, "time": 1717757733.341} +{"unit": "gaia_p", "value": 3.282, "time": 1717757734.174} +{"unit": "gaia_q", "value": -7.791, "time": 1717757734.174} +{"unit": "pcc_p", "value": 0.19, "time": 1717757735.562} +{"unit": "pcc_q", "value": 0.753, "time": 1717757735.562} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757735.496} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757735.496} +{"unit": "pv319_p", "value": 10.5, "time": 1717757735.587} +{"unit": "pv319_q", "value": 0.134, "time": 1717757735.587} +{"unit": "pv330_p", "value": 15.038, "time": 1717757735.639} +{"unit": "pv330_q", "value": -0.187, "time": 1717757735.639} +{"unit": "battery_p", "value": -0.223, "time": 1717757735.527} +{"unit": "battery_q", "value": 0.037, "time": 1717757735.527} +{"unit": "b2b_p", "value": -28.959, "time": 1717757735.832} +{"unit": "b2b_q", "value": 6.771, "time": 1717757735.832} +{"unit": "gaia_p", "value": 3.426, "time": 1717757735.801} +{"unit": "gaia_q", "value": -7.87, "time": 1717757735.801} +{"unit": "pcc_p", "value": -0.419, "time": 1717757736.817} +{"unit": "pcc_q", "value": 0.887, "time": 1717757737.007} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757736.922} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757736.922} +{"unit": "pv319_p", "value": 10.493, "time": 1717757736.751} +{"unit": "pv319_q", "value": 0.139, "time": 1717757736.751} +{"unit": "pv330_p", "value": 15.075, "time": 1717757736.978} +{"unit": "pv330_q", "value": -0.192, "time": 1717757736.978} +{"unit": "battery_p", "value": -0.22, "time": 1717757736.747} +{"unit": "battery_q", "value": 0.031, "time": 1717757737.157} +{"unit": "b2b_p", "value": -28.985, "time": 1717757737.063} +{"unit": "b2b_q", "value": 6.81, "time": 1717757737.063} +{"unit": "gaia_p", "value": 3.828, "time": 1717757737.059} +{"unit": "gaia_q", "value": -7.972, "time": 1717757737.059} +{"unit": "pcc_p", "value": -0.601, "time": 1717757738.092} +{"unit": "pcc_q", "value": 1.029, "time": 1717757738.092} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757738.241} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757738.241} +{"unit": "pv319_p", "value": 10.455, "time": 1717757738.129} +{"unit": "pv319_q", "value": 0.129, "time": 1717757738.129} +{"unit": "pv330_p", "value": 15.047, "time": 1717757738.342} +{"unit": "pv330_q", "value": -0.185, "time": 1717757738.342} +{"unit": "battery_p", "value": -0.221, "time": 1717757738.372} +{"unit": "battery_q", "value": 0.036, "time": 1717757738.372} +{"unit": "b2b_p", "value": -29.0, "time": 1717757738.164} +{"unit": "b2b_q", "value": 6.78, "time": 1717757738.164} +{"unit": "gaia_p", "value": 4.337, "time": 1717757738.196} +{"unit": "gaia_q", "value": -8.15, "time": 1717757738.196} +{"unit": "pcc_p", "value": 3.399, "time": 1717757739.527} +{"unit": "pcc_q", "value": 1.04, "time": 1717757739.527} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757739.491} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757739.491} +{"unit": "pv319_p", "value": 5.273, "time": 1717757739.524} +{"unit": "pv319_q", "value": 0.079, "time": 1717757739.524} +{"unit": "pv330_p", "value": 15.039, "time": 1717757739.541} +{"unit": "pv330_q", "value": -0.193, "time": 1717757739.541} +{"unit": "battery_p", "value": -0.221, "time": 1717757739.597} +{"unit": "battery_q", "value": 0.037, "time": 1717757739.597} +{"unit": "b2b_p", "value": -29.347, "time": 1717757739.632} +{"unit": "b2b_q", "value": 6.788, "time": 1717757739.632} +{"unit": "gaia_p", "value": 3.999, "time": 1717757739.648} +{"unit": "gaia_q", "value": -7.988, "time": 1717757739.648} +{"unit": "pcc_p", "value": -1.175, "time": 1717757740.612} +{"unit": "pcc_q", "value": 1.047, "time": 1717757740.612} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757740.561} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757740.561} +{"unit": "pv319_p", "value": 10.362, "time": 1717757740.577} +{"unit": "pv319_q", "value": 0.129, "time": 1717757740.577} +{"unit": "pv330_p", "value": 15.058, "time": 1717757740.69} +{"unit": "pv330_q", "value": -0.19, "time": 1717757740.69} +{"unit": "battery_p", "value": -0.221, "time": 1717757740.428} +{"unit": "battery_q", "value": 0.037, "time": 1717757740.428} +{"unit": "b2b_p", "value": -28.237, "time": 1717757740.708} +{"unit": "b2b_q", "value": 6.799, "time": 1717757740.708} +{"unit": "gaia_p", "value": 4.165, "time": 1717757740.78} +{"unit": "gaia_q", "value": -8.115, "time": 1717757740.78} +{"unit": "pcc_p", "value": -1.294, "time": 1717757741.692} +{"unit": "pcc_q", "value": 0.894, "time": 1717757741.692} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757741.811} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757741.811} +{"unit": "pv319_p", "value": 10.467, "time": 1717757741.745} +{"unit": "pv319_q", "value": 0.135, "time": 1717757741.745} +{"unit": "pv330_p", "value": 15.103, "time": 1717757741.759} +{"unit": "pv330_q", "value": -0.192, "time": 1717757741.759} +{"unit": "battery_p", "value": -0.221, "time": 1717757741.707} +{"unit": "battery_q", "value": 0.037, "time": 1717757741.707} +{"unit": "b2b_p", "value": -28.052, "time": 1717757741.858} +{"unit": "b2b_q", "value": 6.796, "time": 1717757741.858} +{"unit": "gaia_p", "value": 3.69, "time": 1717757741.849} +{"unit": "gaia_q", "value": -7.94, "time": 1717757741.849} +{"unit": "pcc_p", "value": -0.531, "time": 1717757742.972} +{"unit": "pcc_q", "value": 0.852, "time": 1717757742.972} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757742.886} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757742.886} +{"unit": "pv319_p", "value": 10.518, "time": 1717757742.838} +{"unit": "pv319_q", "value": 0.129, "time": 1717757742.838} +{"unit": "pv330_p", "value": 15.07, "time": 1717757742.559} +{"unit": "pv330_q", "value": -0.187, "time": 1717757742.559} +{"unit": "battery_p", "value": -0.222, "time": 1717757742.942} +{"unit": "battery_q", "value": 0.039, "time": 1717757742.942} +{"unit": "b2b_p", "value": -28.617, "time": 1717757742.721} +{"unit": "b2b_q", "value": 6.816, "time": 1717757742.721} +{"unit": "gaia_p", "value": 3.693, "time": 1717757743.097} +{"unit": "gaia_q", "value": -7.955, "time": 1717757743.097} +{"unit": "pcc_p", "value": -0.324, "time": 1717757744.057} +{"unit": "pcc_q", "value": 0.842, "time": 1717757744.057} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757744.136} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757744.136} +{"unit": "pv319_p", "value": 10.533, "time": 1717757744.124} +{"unit": "pv319_q", "value": 0.138, "time": 1717757744.124} +{"unit": "pv330_p", "value": 15.086, "time": 1717757744.181} +{"unit": "pv330_q", "value": -0.19, "time": 1717757744.181} +{"unit": "battery_p", "value": -0.223, "time": 1717757744.187} +{"unit": "battery_q", "value": 0.039, "time": 1717757744.187} +{"unit": "b2b_p", "value": -28.904, "time": 1717757744.065} +{"unit": "b2b_q", "value": 6.794, "time": 1717757744.065} +{"unit": "gaia_p", "value": 3.628, "time": 1717757743.918} +{"unit": "gaia_q", "value": -7.925, "time": 1717757743.918} +{"unit": "pcc_p", "value": -0.011, "time": 1717757745.157} +{"unit": "pcc_q", "value": 0.822, "time": 1717757745.157} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757745.221} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757745.221} +{"unit": "pv319_p", "value": 10.494, "time": 1717757745.146} +{"unit": "pv319_q", "value": 0.124, "time": 1717757745.146} +{"unit": "pv330_p", "value": 15.092, "time": 1717757745.26} +{"unit": "pv330_q", "value": -0.18, "time": 1717757745.26} +{"unit": "battery_p", "value": -0.221, "time": 1717757745.002} +{"unit": "battery_q", "value": 0.04, "time": 1717757745.002} +{"unit": "b2b_p", "value": -29.072, "time": 1717757745.226} +{"unit": "b2b_q", "value": 6.787, "time": 1717757745.226} +{"unit": "gaia_p", "value": 3.52, "time": 1717757745.051} +{"unit": "gaia_q", "value": -7.885, "time": 1717757745.051} +{"unit": "pcc_p", "value": 0.109, "time": 1717757746.232} +{"unit": "pcc_q", "value": 0.801, "time": 1717757746.232} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757746.316} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757746.316} +{"unit": "pv319_p", "value": 10.536, "time": 1717757746.169} +{"unit": "pv319_q", "value": 0.126, "time": 1717757746.169} +{"unit": "pv330_p", "value": 15.092, "time": 1717757746.358} +{"unit": "pv330_q", "value": -0.195, "time": 1717757746.358} +{"unit": "battery_p", "value": -0.22, "time": 1717757746.232} +{"unit": "battery_q", "value": 0.039, "time": 1717757746.232} +{"unit": "b2b_p", "value": -29.082, "time": 1717757746.231} +{"unit": "b2b_q", "value": 6.804, "time": 1717757746.231} +{"unit": "gaia_p", "value": 3.573, "time": 1717757746.312} +{"unit": "gaia_q", "value": -7.885, "time": 1717757746.312} +{"unit": "pcc_p", "value": 0.041, "time": 1717757747.322} +{"unit": "pcc_q", "value": 0.971, "time": 1717757747.507} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757747.381} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757747.381} +{"unit": "pv319_p", "value": 10.442, "time": 1717757747.247} +{"unit": "pv319_q", "value": 0.122, "time": 1717757747.247} +{"unit": "pv330_p", "value": 15.101, "time": 1717757747.539} +{"unit": "pv330_q", "value": -0.184, "time": 1717757747.539} +{"unit": "battery_p", "value": -0.223, "time": 1717757747.467} +{"unit": "battery_q", "value": 0.038, "time": 1717757747.467} +{"unit": "b2b_p", "value": -29.011, "time": 1717757747.173} +{"unit": "b2b_q", "value": 6.791, "time": 1717757747.173} +{"unit": "gaia_p", "value": 3.683, "time": 1717757747.524} +{"unit": "gaia_q", "value": -7.944, "time": 1717757747.524} +{"unit": "pcc_p", "value": -0.139, "time": 1717757748.567} +{"unit": "pcc_q", "value": 0.917, "time": 1717757748.567} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757748.621} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757748.621} +{"unit": "pv319_p", "value": 10.327, "time": 1717757748.381} +{"unit": "pv319_q", "value": 0.115, "time": 1717757748.381} +{"unit": "pv330_p", "value": 15.136, "time": 1717757748.557} +{"unit": "pv330_q", "value": -0.188, "time": 1717757748.557} +{"unit": "battery_p", "value": -0.22, "time": 1717757748.667} +{"unit": "battery_q", "value": 0.036, "time": 1717757748.667} +{"unit": "b2b_p", "value": -29.071, "time": 1717757748.501} +{"unit": "b2b_q", "value": 6.784, "time": 1717757748.772} +{"unit": "gaia_p", "value": 3.549, "time": 1717757748.67} +{"unit": "gaia_q", "value": -7.877, "time": 1717757748.67} +{"unit": "pcc_p", "value": -0.428, "time": 1717757749.652} +{"unit": "pcc_q", "value": 0.897, "time": 1717757749.652} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757749.696} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757749.696} +{"unit": "pv319_p", "value": 10.552, "time": 1717757749.77} +{"unit": "pv319_q", "value": 0.116, "time": 1717757749.77} +{"unit": "pv330_p", "value": 15.132, "time": 1717757749.776} +{"unit": "pv330_q", "value": -0.183, "time": 1717757749.776} +{"unit": "battery_p", "value": -0.22, "time": 1717757749.872} +{"unit": "battery_q", "value": 0.036, "time": 1717757749.872} +{"unit": "b2b_p", "value": -29.027, "time": 1717757749.821} +{"unit": "b2b_q", "value": 6.794, "time": 1717757749.821} +{"unit": "gaia_p", "value": 3.894, "time": 1717757749.774} +{"unit": "gaia_q", "value": -7.995, "time": 1717757749.774} +{"unit": "pcc_p", "value": -0.456, "time": 1717757750.912} +{"unit": "pcc_q", "value": 0.865, "time": 1717757750.912} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757750.956} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757750.956} +{"unit": "pv319_p", "value": 10.503, "time": 1717757750.92} +{"unit": "pv319_q", "value": 0.127, "time": 1717757750.92} +{"unit": "pv330_p", "value": 15.115, "time": 1717757750.981} +{"unit": "pv330_q", "value": -0.187, "time": 1717757750.981} +{"unit": "battery_p", "value": -0.22, "time": 1717757750.687} +{"unit": "battery_q", "value": 0.035, "time": 1717757750.687} +{"unit": "b2b_p", "value": -28.968, "time": 1717757751.015} +{"unit": "b2b_q", "value": 6.806, "time": 1717757751.015} +{"unit": "gaia_p", "value": 3.837, "time": 1717757750.9} +{"unit": "gaia_q", "value": -7.955, "time": 1717757750.9} +{"unit": "pcc_p", "value": -0.217, "time": 1717757752.017} +{"unit": "pcc_q", "value": 0.837, "time": 1717757752.017} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757752.036} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757752.036} +{"unit": "pv319_p", "value": 10.55, "time": 1717757751.842} +{"unit": "pv319_q", "value": 0.126, "time": 1717757751.842} +{"unit": "pv330_p", "value": 15.158, "time": 1717757752.136} +{"unit": "pv330_q", "value": -0.192, "time": 1717757752.136} +{"unit": "battery_p", "value": -0.22, "time": 1717757751.902} +{"unit": "battery_q", "value": 0.035, "time": 1717757751.902} +{"unit": "b2b_p", "value": -29.133, "time": 1717757752.173} +{"unit": "b2b_q", "value": 6.812, "time": 1717757752.173} +{"unit": "gaia_p", "value": 3.831, "time": 1717757752.178} +{"unit": "gaia_q", "value": -7.948, "time": 1717757752.178} +{"unit": "pcc_p", "value": -0.182, "time": 1717757753.092} +{"unit": "pcc_q", "value": 0.695, "time": 1717757753.272} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757753.156} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757753.156} +{"unit": "pv319_p", "value": 10.489, "time": 1717757753.263} +{"unit": "pv319_q", "value": 0.12, "time": 1717757753.263} +{"unit": "pv330_p", "value": 15.135, "time": 1717757753.137} +{"unit": "pv330_q", "value": -0.189, "time": 1717757753.137} +{"unit": "battery_p", "value": -0.221, "time": 1717757753.132} +{"unit": "battery_q", "value": 0.035, "time": 1717757753.132} +{"unit": "b2b_p", "value": -29.139, "time": 1717757752.499} +{"unit": "b2b_q", "value": 6.802, "time": 1717757752.499} +{"unit": "gaia_p", "value": 3.304, "time": 1717757753.298} +{"unit": "gaia_q", "value": -7.772, "time": 1717757753.298} +{"unit": "pcc_p", "value": 0.228, "time": 1717757754.352} +{"unit": "pcc_q", "value": 0.609, "time": 1717757754.352} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757754.416} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757754.416} +{"unit": "pv319_p", "value": 10.474, "time": 1717757754.351} +{"unit": "pv319_q", "value": 0.123, "time": 1717757754.351} +{"unit": "pv330_p", "value": 15.143, "time": 1717757754.391} +{"unit": "pv330_q", "value": -0.185, "time": 1717757754.391} +{"unit": "battery_p", "value": -0.223, "time": 1717757754.342} +{"unit": "battery_q", "value": 0.036, "time": 1717757754.342} +{"unit": "b2b_p", "value": -29.063, "time": 1717757753.773} +{"unit": "b2b_q", "value": 6.813, "time": 1717757753.773} +{"unit": "gaia_p", "value": 3.478, "time": 1717757754.301} +{"unit": "gaia_q", "value": -7.815, "time": 1717757754.301} +{"unit": "pcc_p", "value": 0.437, "time": 1717757755.437} +{"unit": "pcc_q", "value": 0.6, "time": 1717757755.437} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757755.496} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757755.496} +{"unit": "pv319_p", "value": 10.423, "time": 1717757755.525} +{"unit": "pv319_q", "value": 0.125, "time": 1717757755.525} +{"unit": "pv330_p", "value": 15.147, "time": 1717757755.471} +{"unit": "pv330_q", "value": -0.185, "time": 1717757755.471} +{"unit": "battery_p", "value": -0.221, "time": 1717757755.582} +{"unit": "battery_q", "value": 0.035, "time": 1717757755.582} +{"unit": "b2b_p", "value": -28.991, "time": 1717757755.533} +{"unit": "b2b_q", "value": 6.804, "time": 1717757755.533} +{"unit": "gaia_p", "value": 2.917, "time": 1717757755.523} +{"unit": "gaia_q", "value": -7.641, "time": 1717757755.523} +{"unit": "pcc_p", "value": 0.457, "time": 1717757756.512} +{"unit": "pcc_q", "value": 0.549, "time": 1717757756.512} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757756.571} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757756.571} +{"unit": "pv319_p", "value": 10.493, "time": 1717757756.329} +{"unit": "pv319_q", "value": 0.129, "time": 1717757756.329} +{"unit": "pv330_p", "value": 15.164, "time": 1717757756.554} +{"unit": "pv330_q", "value": -0.187, "time": 1717757756.554} +{"unit": "battery_p", "value": -0.221, "time": 1717757756.402} +{"unit": "battery_q", "value": 0.035, "time": 1717757756.402} +{"unit": "b2b_p", "value": -28.957, "time": 1717757756.62} +{"unit": "b2b_q", "value": 6.799, "time": 1717757756.62} +{"unit": "gaia_p", "value": 2.837, "time": 1717757756.65} +{"unit": "gaia_q", "value": -7.623, "time": 1717757756.65} +{"unit": "pcc_p", "value": -0.032, "time": 1717757757.773} +{"unit": "pcc_q", "value": 0.647, "time": 1717757757.773} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757757.636} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757757.636} +{"unit": "pv319_p", "value": 10.541, "time": 1717757757.517} +{"unit": "pv319_q", "value": 0.129, "time": 1717757757.517} +{"unit": "pv330_p", "value": 15.155, "time": 1717757757.74} +{"unit": "pv330_q", "value": -0.183, "time": 1717757757.74} +{"unit": "battery_p", "value": -0.222, "time": 1717757757.717} +{"unit": "battery_q", "value": 0.036, "time": 1717757757.717} +{"unit": "b2b_p", "value": -28.763, "time": 1717757757.689} +{"unit": "b2b_q", "value": 6.777, "time": 1717757757.689} +{"unit": "gaia_p", "value": 3.199, "time": 1717757757.697} +{"unit": "gaia_q", "value": -7.76, "time": 1717757757.697} +{"unit": "pcc_p", "value": 0.032, "time": 1717757758.842} +{"unit": "pcc_q", "value": 0.656, "time": 1717757758.842} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757758.881} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757758.881} +{"unit": "pv319_p", "value": 10.466, "time": 1717757758.924} +{"unit": "pv319_q", "value": 0.132, "time": 1717757758.924} +{"unit": "pv330_p", "value": 15.177, "time": 1717757758.74} +{"unit": "pv330_q", "value": -0.187, "time": 1717757758.74} +{"unit": "battery_p", "value": -0.222, "time": 1717757758.942} +{"unit": "battery_q", "value": 0.038, "time": 1717757758.942} +{"unit": "b2b_p", "value": -28.791, "time": 1717757758.883} +{"unit": "b2b_q", "value": 6.808, "time": 1717757758.883} +{"unit": "gaia_p", "value": 3.029, "time": 1717757758.779} +{"unit": "gaia_q", "value": -7.7, "time": 1717757758.779} +{"unit": "pcc_p", "value": -0.224, "time": 1717757759.917} +{"unit": "pcc_q", "value": 0.703, "time": 1717757759.917} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757759.951} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757759.951} +{"unit": "pv319_p", "value": 10.53, "time": 1717757759.927} +{"unit": "pv319_q", "value": 0.133, "time": 1717757759.927} +{"unit": "pv330_p", "value": 15.139, "time": 1717757760.046} +{"unit": "pv330_q", "value": -0.182, "time": 1717757760.046} +{"unit": "battery_p", "value": -0.219, "time": 1717757759.772} +{"unit": "battery_q", "value": 0.036, "time": 1717757759.772} +{"unit": "b2b_p", "value": -28.775, "time": 1717757760.104} +{"unit": "b2b_q", "value": 6.807, "time": 1717757760.104} +{"unit": "gaia_p", "value": 2.887, "time": 1717757759.882} +{"unit": "gaia_q", "value": -7.651, "time": 1717757759.882} +{"unit": "pcc_p", "value": 0.43, "time": 1717757761.177} +{"unit": "pcc_q", "value": 0.523, "time": 1717757761.177} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757761.191} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757761.191} +{"unit": "pv319_p", "value": 10.486, "time": 1717757761.134} +{"unit": "pv319_q", "value": 0.133, "time": 1717757761.134} +{"unit": "pv330_p", "value": 15.153, "time": 1717757761.201} +{"unit": "pv330_q", "value": -0.187, "time": 1717757761.201} +{"unit": "battery_p", "value": -0.22, "time": 1717757761.012} +{"unit": "battery_q", "value": 0.03, "time": 1717757761.012} +{"unit": "b2b_p", "value": -28.854, "time": 1717757761.291} +{"unit": "b2b_q", "value": 6.797, "time": 1717757761.291} +{"unit": "gaia_p", "value": 3.166, "time": 1717757761.117} +{"unit": "gaia_q", "value": -7.723, "time": 1717757761.117} +{"unit": "pcc_p", "value": 0.226, "time": 1717757762.252} +{"unit": "pcc_q", "value": 0.503, "time": 1717757762.252} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757762.271} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757762.271} +{"unit": "pv319_p", "value": 10.497, "time": 1717757762.017} +{"unit": "pv319_q", "value": 0.13, "time": 1717757762.017} +{"unit": "pv330_p", "value": 15.204, "time": 1717757762.138} +{"unit": "pv330_q", "value": -0.184, "time": 1717757762.138} +{"unit": "battery_p", "value": -0.22, "time": 1717757762.232} +{"unit": "battery_q", "value": 0.034, "time": 1717757762.232} +{"unit": "b2b_p", "value": -28.69, "time": 1717757762.41} +{"unit": "b2b_q", "value": 6.811, "time": 1717757762.41} +{"unit": "gaia_p", "value": 3.222, "time": 1717757762.191} +{"unit": "gaia_q", "value": -7.739, "time": 1717757762.191} +{"unit": "pcc_p", "value": 0.052, "time": 1717757763.322} +{"unit": "pcc_q", "value": 0.563, "time": 1717757763.322} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757763.331} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757763.331} +{"unit": "pv319_p", "value": 10.417, "time": 1717757763.437} +{"unit": "pv319_q", "value": 0.124, "time": 1717757763.437} +{"unit": "pv330_p", "value": 15.245, "time": 1717757763.483} +{"unit": "pv330_q", "value": -0.182, "time": 1717757763.483} +{"unit": "battery_p", "value": -0.222, "time": 1717757763.472} +{"unit": "battery_q", "value": 0.03, "time": 1717757763.472} +{"unit": "b2b_p", "value": -28.616, "time": 1717757763.094} +{"unit": "b2b_q", "value": 6.815, "time": 1717757763.094} +{"unit": "gaia_p", "value": 2.978, "time": 1717757763.298} +{"unit": "gaia_q", "value": -7.657, "time": 1717757763.298} +{"unit": "pcc_p", "value": 0.042, "time": 1717757764.402} +{"unit": "pcc_q", "value": 0.537, "time": 1717757764.402} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757764.406} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757764.586} +{"unit": "pv319_p", "value": 10.432, "time": 1717757764.571} +{"unit": "pv319_q", "value": 0.119, "time": 1717757764.571} +{"unit": "pv330_p", "value": 15.317, "time": 1717757764.558} +{"unit": "pv330_q", "value": -0.186, "time": 1717757764.558} +{"unit": "battery_p", "value": -0.223, "time": 1717757764.277} +{"unit": "battery_q", "value": 0.036, "time": 1717757764.277} +{"unit": "b2b_p", "value": -28.616, "time": 1717757763.094} +{"unit": "b2b_q", "value": 6.815, "time": 1717757763.094} +{"unit": "gaia_p", "value": 2.747, "time": 1717757764.323} +{"unit": "gaia_q", "value": -7.606, "time": 1717757764.323} +{"unit": "pcc_p", "value": -0.078, "time": 1717757765.657} +{"unit": "pcc_q", "value": 0.599, "time": 1717757765.657} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757765.661} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757765.661} +{"unit": "pv319_p", "value": 10.442, "time": 1717757765.578} +{"unit": "pv319_q", "value": 0.119, "time": 1717757765.578} +{"unit": "pv330_p", "value": 15.22, "time": 1717757765.744} +{"unit": "pv330_q", "value": -0.186, "time": 1717757765.744} +{"unit": "battery_p", "value": -0.221, "time": 1717757765.502} +{"unit": "battery_q", "value": 0.037, "time": 1717757765.502} +{"unit": "b2b_p", "value": -28.487, "time": 1717757765.663} +{"unit": "b2b_q", "value": 6.808, "time": 1717757765.663} +{"unit": "gaia_p", "value": 2.959, "time": 1717757765.697} +{"unit": "gaia_q", "value": -7.668, "time": 1717757765.697} +{"unit": "pcc_p", "value": 3.881, "time": 1717757766.732} +{"unit": "pcc_q", "value": 0.654, "time": 1717757766.732} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757766.736} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757766.736} +{"unit": "pv319_p", "value": 10.454, "time": 1717757766.851} +{"unit": "pv319_q", "value": 0.115, "time": 1717757766.851} +{"unit": "pv330_p", "value": 10.116, "time": 1717757766.792} +{"unit": "pv330_q", "value": -0.144, "time": 1717757766.792} +{"unit": "battery_p", "value": -0.222, "time": 1717757766.727} +{"unit": "battery_q", "value": 0.038, "time": 1717757766.727} +{"unit": "b2b_p", "value": -27.912, "time": 1717757766.874} +{"unit": "b2b_q", "value": 6.805, "time": 1717757766.874} +{"unit": "gaia_p", "value": 3.8, "time": 1717757766.9} +{"unit": "gaia_q", "value": -7.849, "time": 1717757766.9} +{"unit": "pcc_p", "value": -1.354, "time": 1717757767.862} +{"unit": "pcc_q", "value": 0.753, "time": 1717757767.862} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757767.971} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757767.971} +{"unit": "pv319_p", "value": 10.515, "time": 1717757768.031} +{"unit": "pv319_q", "value": 0.112, "time": 1717757768.031} +{"unit": "pv330_p", "value": 15.19, "time": 1717757767.984} +{"unit": "pv330_q", "value": -0.186, "time": 1717757767.984} +{"unit": "battery_p", "value": -0.221, "time": 1717757767.972} +{"unit": "battery_q", "value": 0.038, "time": 1717757767.972} +{"unit": "b2b_p", "value": -27.906, "time": 1717757767.73} +{"unit": "b2b_q", "value": 6.791, "time": 1717757767.73} +{"unit": "gaia_p", "value": 3.449, "time": 1717757768.144} +{"unit": "gaia_q", "value": -7.836, "time": 1717757768.144} +{"unit": "pcc_p", "value": 3.839, "time": 1717757769.113} +{"unit": "pcc_q", "value": 0.423, "time": 1717757769.113} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757769.091} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757769.091} +{"unit": "pv319_p", "value": 10.535, "time": 1717757769.062} +{"unit": "pv319_q", "value": 0.116, "time": 1717757769.062} +{"unit": "pv330_p", "value": 13.098, "time": 1717757769.078} +{"unit": "pv330_q", "value": -0.171, "time": 1717757769.078} +{"unit": "battery_p", "value": -0.222, "time": 1717757769.207} +{"unit": "battery_q", "value": 0.037, "time": 1717757769.207} +{"unit": "b2b_p", "value": -27.717, "time": 1717757768.597} +{"unit": "b2b_q", "value": 6.826, "time": 1717757768.597} +{"unit": "gaia_p", "value": 2.784, "time": 1717757769.318} +{"unit": "gaia_q", "value": -7.53, "time": 1717757769.318} +{"unit": "pcc_p", "value": 0.048, "time": 1717757770.203} +{"unit": "pcc_q", "value": 0.394, "time": 1717757770.203} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757770.341} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757770.341} +{"unit": "pv319_p", "value": 10.527, "time": 1717757770.369} +{"unit": "pv319_q", "value": 0.122, "time": 1717757770.369} +{"unit": "pv330_p", "value": 15.244, "time": 1717757770.336} +{"unit": "pv330_q", "value": -0.186, "time": 1717757770.336} +{"unit": "battery_p", "value": -0.22, "time": 1717757770.437} +{"unit": "battery_q", "value": 0.035, "time": 1717757770.437} +{"unit": "b2b_p", "value": -28.074, "time": 1717757770.315} +{"unit": "b2b_q", "value": 6.822, "time": 1717757770.473} +{"unit": "gaia_p", "value": 2.283, "time": 1717757770.3} +{"unit": "gaia_q", "value": -7.468, "time": 1717757770.3} +{"unit": "pcc_p", "value": 0.902, "time": 1717757771.473} +{"unit": "pcc_q", "value": 0.098, "time": 1717757771.473} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757771.401} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757771.401} +{"unit": "pv319_p", "value": 10.566, "time": 1717757771.437} +{"unit": "pv319_q", "value": 0.131, "time": 1717757771.437} +{"unit": "pv330_p", "value": 15.232, "time": 1717757771.558} +{"unit": "pv330_q", "value": -0.176, "time": 1717757771.558} +{"unit": "battery_p", "value": -0.22, "time": 1717757771.252} +{"unit": "battery_q", "value": 0.037, "time": 1717757771.252} +{"unit": "b2b_p", "value": -27.791, "time": 1717757771.277} +{"unit": "b2b_q", "value": 6.82, "time": 1717757771.277} +{"unit": "gaia_p", "value": 1.243, "time": 1717757771.542} +{"unit": "gaia_q", "value": -7.186, "time": 1717757771.542} +{"unit": "pcc_p", "value": 0.937, "time": 1717757772.543} +{"unit": "pcc_q", "value": 0.024, "time": 1717757772.543} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757772.476} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757772.651} +{"unit": "pv319_p", "value": 10.559, "time": 1717757772.579} +{"unit": "pv319_q", "value": 0.128, "time": 1717757772.579} +{"unit": "pv330_p", "value": 15.254, "time": 1717757772.637} +{"unit": "pv330_q", "value": -0.175, "time": 1717757772.637} +{"unit": "battery_p", "value": -0.222, "time": 1717757772.503} +{"unit": "battery_q", "value": 0.036, "time": 1717757772.503} +{"unit": "b2b_p", "value": -27.358, "time": 1717757772.59} +{"unit": "b2b_q", "value": 6.828, "time": 1717757772.59} +{"unit": "gaia_p", "value": 0.806, "time": 1717757772.698} +{"unit": "gaia_q", "value": -7.117, "time": 1717757772.698} +{"unit": "pcc_p", "value": 0.318, "time": 1717757773.613} +{"unit": "pcc_q", "value": 0.041, "time": 1717757773.613} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757773.716} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757773.716} +{"unit": "pv319_p", "value": 10.562, "time": 1717757773.779} +{"unit": "pv319_q", "value": 0.143, "time": 1717757773.779} +{"unit": "pv330_p", "value": 15.267, "time": 1717757773.738} +{"unit": "pv330_q", "value": -0.173, "time": 1717757773.738} +{"unit": "battery_p", "value": -0.221, "time": 1717757773.792} +{"unit": "battery_q", "value": 0.037, "time": 1717757773.792} +{"unit": "b2b_p", "value": -27.345, "time": 1717757772.789} +{"unit": "b2b_q", "value": 6.807, "time": 1717757772.789} +{"unit": "gaia_p", "value": 0.793, "time": 1717757773.779} +{"unit": "gaia_q", "value": -7.109, "time": 1717757773.779} +{"unit": "pcc_p", "value": 0.448, "time": 1717757774.858} +{"unit": "pcc_q", "value": -0.064, "time": 1717757774.858} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757774.786} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757774.786} +{"unit": "pv319_p", "value": 10.553, "time": 1717757774.785} +{"unit": "pv319_q", "value": 0.149, "time": 1717757774.785} +{"unit": "pv330_p", "value": 15.249, "time": 1717757774.79} +{"unit": "pv330_q", "value": -0.18, "time": 1717757774.79} +{"unit": "battery_p", "value": -0.219, "time": 1717757774.607} +{"unit": "battery_q", "value": 0.036, "time": 1717757774.607} +{"unit": "b2b_p", "value": -26.726, "time": 1717757774.868} +{"unit": "b2b_q", "value": 6.841, "time": 1717757774.868} +{"unit": "gaia_p", "value": 0.62, "time": 1717757774.941} +{"unit": "gaia_q", "value": -7.047, "time": 1717757774.941} +{"unit": "pcc_p", "value": 0.03, "time": 1717757775.948} +{"unit": "pcc_q", "value": 0.063, "time": 1717757775.948} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757775.876} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757775.876} +{"unit": "pv319_p", "value": 10.607, "time": 1717757775.988} +{"unit": "pv319_q", "value": 0.147, "time": 1717757775.988} +{"unit": "pv330_p", "value": 15.246, "time": 1717757775.977} +{"unit": "pv330_q", "value": -0.173, "time": 1717757775.977} +{"unit": "battery_p", "value": -0.221, "time": 1717757775.827} +{"unit": "battery_q", "value": 0.037, "time": 1717757775.827} +{"unit": "b2b_p", "value": -26.684, "time": 1717757775.925} +{"unit": "b2b_q", "value": 6.821, "time": 1717757776.063} +{"unit": "gaia_p", "value": 0.913, "time": 1717757775.916} +{"unit": "gaia_q", "value": -7.124, "time": 1717757775.916} +{"unit": "pcc_p", "value": -1.463, "time": 1717757777.029} +{"unit": "pcc_q", "value": 0.436, "time": 1717757777.029} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757776.956} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757776.956} +{"unit": "pv319_p", "value": 10.647, "time": 1717757777.129} +{"unit": "pv319_q", "value": 0.149, "time": 1717757777.129} +{"unit": "pv330_p", "value": 15.217, "time": 1717757777.055} +{"unit": "pv330_q", "value": -0.19, "time": 1717757777.055} +{"unit": "battery_p", "value": -0.221, "time": 1717757777.062} +{"unit": "battery_q", "value": 0.035, "time": 1717757777.062} +{"unit": "b2b_p", "value": -26.749, "time": 1717757777.064} +{"unit": "b2b_q", "value": 6.823, "time": 1717757777.064} +{"unit": "gaia_p", "value": 2.432, "time": 1717757777.099} +{"unit": "gaia_q", "value": -7.535, "time": 1717757777.099} +{"unit": "pcc_p", "value": -1.827, "time": 1717757778.133} +{"unit": "pcc_q", "value": 0.762, "time": 1717757778.133} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757778.201} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757778.201} +{"unit": "pv319_p", "value": 10.653, "time": 1717757778.125} +{"unit": "pv319_q", "value": 0.143, "time": 1717757778.125} +{"unit": "pv330_p", "value": 15.219, "time": 1717757778.229} +{"unit": "pv330_q", "value": -0.186, "time": 1717757778.229} +{"unit": "battery_p", "value": -0.222, "time": 1717757777.887} +{"unit": "battery_q", "value": 0.035, "time": 1717757778.287} +{"unit": "b2b_p", "value": -27.536, "time": 1717757778.263} +{"unit": "b2b_q", "value": 6.832, "time": 1717757778.263} +{"unit": "gaia_p", "value": 3.337, "time": 1717757778.186} +{"unit": "gaia_q", "value": -7.791, "time": 1717757778.186} +{"unit": "pcc_p", "value": -1.081, "time": 1717757779.223} +{"unit": "pcc_q", "value": 0.904, "time": 1717757779.223} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757779.276} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757779.276} +{"unit": "pv319_p", "value": 10.7, "time": 1717757779.237} +{"unit": "pv319_q", "value": 0.135, "time": 1717757779.237} +{"unit": "pv330_p", "value": 15.229, "time": 1717757778.884} +{"unit": "pv330_q", "value": -0.19, "time": 1717757778.884} +{"unit": "battery_p", "value": -0.221, "time": 1717757779.102} +{"unit": "battery_q", "value": 0.034, "time": 1717757779.102} +{"unit": "b2b_p", "value": -29.034, "time": 1717757779.275} +{"unit": "b2b_q", "value": 6.845, "time": 1717757779.275} +{"unit": "gaia_p", "value": 4.154, "time": 1717757779.19} +{"unit": "gaia_q", "value": -8.051, "time": 1717757779.19} +{"unit": "pcc_p", "value": -0.456, "time": 1717757780.288} +{"unit": "pcc_q", "value": 1.066, "time": 1717757780.288} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757780.351} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757780.351} +{"unit": "pv319_p", "value": 10.611, "time": 1717757780.371} +{"unit": "pv319_q", "value": 0.128, "time": 1717757780.371} +{"unit": "pv330_p", "value": 15.212, "time": 1717757780.36} +{"unit": "pv330_q", "value": -0.181, "time": 1717757780.36} +{"unit": "battery_p", "value": -0.221, "time": 1717757780.312} +{"unit": "battery_q", "value": 0.038, "time": 1717757780.312} +{"unit": "b2b_p", "value": -30.137, "time": 1717757780.422} +{"unit": "b2b_q", "value": 6.789, "time": 1717757780.422} +{"unit": "gaia_p", "value": 4.836, "time": 1717757780.45} +{"unit": "gaia_q", "value": -8.277, "time": 1717757780.45} +{"unit": "pcc_p", "value": -0.539, "time": 1717757781.538} +{"unit": "pcc_q", "value": 1.36, "time": 1717757781.538} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757781.591} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757781.591} +{"unit": "pv319_p", "value": 10.716, "time": 1717757781.526} +{"unit": "pv319_q", "value": 0.134, "time": 1717757781.526} +{"unit": "pv330_p", "value": 15.235, "time": 1717757781.384} +{"unit": "pv330_q", "value": -0.193, "time": 1717757781.384} +{"unit": "battery_p", "value": -0.221, "time": 1717757781.537} +{"unit": "battery_q", "value": 0.036, "time": 1717757781.537} +{"unit": "b2b_p", "value": -30.598, "time": 1717757781.636} +{"unit": "b2b_q", "value": 6.793, "time": 1717757781.636} +{"unit": "gaia_p", "value": 5.25, "time": 1717757781.586} +{"unit": "gaia_q", "value": -8.45, "time": 1717757781.586} +{"unit": "pcc_p", "value": -0.074, "time": 1717757782.623} +{"unit": "pcc_q", "value": 1.685, "time": 1717757782.623} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757782.665} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757782.665} +{"unit": "pv319_p", "value": 9.933, "time": 1717757782.642} +{"unit": "pv319_q", "value": 0.115, "time": 1717757782.642} +{"unit": "pv330_p", "value": 15.212, "time": 1717757782.603} +{"unit": "pv330_q", "value": -0.189, "time": 1717757782.603} +{"unit": "battery_p", "value": -0.222, "time": 1717757782.752} +{"unit": "battery_q", "value": 0.036, "time": 1717757782.752} +{"unit": "b2b_p", "value": -30.794, "time": 1717757782.705} +{"unit": "b2b_q", "value": 6.824, "time": 1717757782.705} +{"unit": "gaia_p", "value": 5.904, "time": 1717757782.699} +{"unit": "gaia_q", "value": -8.696, "time": 1717757782.699} +{"unit": "pcc_p", "value": 0.842, "time": 1717757783.793} +{"unit": "pcc_q", "value": 1.989, "time": 1717757783.793} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757783.746} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757783.746} +{"unit": "pv319_p", "value": 8.072, "time": 1717757783.725} +{"unit": "pv319_q", "value": 0.088, "time": 1717757783.725} +{"unit": "pv330_p", "value": 15.238, "time": 1717757783.473} +{"unit": "pv330_q", "value": -0.192, "time": 1717757783.473} +{"unit": "battery_p", "value": -0.221, "time": 1717757783.567} +{"unit": "battery_q", "value": 0.035, "time": 1717757783.567} +{"unit": "b2b_p", "value": -30.878, "time": 1717757783.692} +{"unit": "b2b_q", "value": 6.785, "time": 1717757783.692} +{"unit": "gaia_p", "value": 6.916, "time": 1717757783.915} +{"unit": "gaia_q", "value": -9.112, "time": 1717757783.915} +{"unit": "pcc_p", "value": 0.953, "time": 1717757784.873} +{"unit": "pcc_q", "value": 2.061, "time": 1717757784.873} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757784.816} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757784.816} +{"unit": "pv319_p", "value": 7.08, "time": 1717757784.864} +{"unit": "pv319_q", "value": 0.081, "time": 1717757784.864} +{"unit": "pv330_p", "value": 15.238, "time": 1717757783.473} +{"unit": "pv330_q", "value": -0.192, "time": 1717757783.473} +{"unit": "battery_p", "value": -0.22, "time": 1717757784.797} +{"unit": "battery_q", "value": 0.035, "time": 1717757784.797} +{"unit": "b2b_p", "value": -30.174, "time": 1717757785.023} +{"unit": "b2b_q", "value": 6.802, "time": 1717757785.023} +{"unit": "gaia_p", "value": 6.612, "time": 1717757784.69} +{"unit": "gaia_q", "value": -8.987, "time": 1717757784.69} +{"unit": "pcc_p", "value": 1.077, "time": 1717757785.963} +{"unit": "pcc_q", "value": 1.839, "time": 1717757785.963} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757785.966} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757785.966} +{"unit": "pv319_p", "value": 6.867, "time": 1717757785.985} +{"unit": "pv319_q", "value": 0.081, "time": 1717757785.985} +{"unit": "pv330_p", "value": 15.259, "time": 1717757786.033} +{"unit": "pv330_q", "value": -0.178, "time": 1717757786.033} +{"unit": "battery_p", "value": -0.221, "time": 1717757786.002} +{"unit": "battery_q", "value": 0.035, "time": 1717757786.002} +{"unit": "b2b_p", "value": -29.624, "time": 1717757786.132} +{"unit": "b2b_q", "value": 6.782, "time": 1717757786.132} +{"unit": "gaia_p", "value": 6.508, "time": 1717757786.052} +{"unit": "gaia_q", "value": -8.951, "time": 1717757786.052} +{"unit": "pcc_p", "value": 2.245, "time": 1717757787.053} +{"unit": "pcc_q", "value": 1.396, "time": 1717757787.053} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757787.211} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757787.211} +{"unit": "pv319_p", "value": 6.317, "time": 1717757786.953} +{"unit": "pv319_q", "value": 0.075, "time": 1717757786.953} +{"unit": "pv330_p", "value": 15.285, "time": 1717757787.089} +{"unit": "pv330_q", "value": -0.194, "time": 1717757787.089} +{"unit": "battery_p", "value": -0.221, "time": 1717757787.222} +{"unit": "battery_q", "value": 0.033, "time": 1717757787.222} +{"unit": "b2b_p", "value": -28.707, "time": 1717757787.284} +{"unit": "b2b_q", "value": 6.804, "time": 1717757787.284} +{"unit": "gaia_p", "value": 5.378, "time": 1717757787.099} +{"unit": "gaia_q", "value": -8.455, "time": 1717757787.099} +{"unit": "pcc_p", "value": 22.686, "time": 1717757788.298} +{"unit": "pcc_q", "value": 1.084, "time": 1717757788.298} +{"unit": "dumpload_p", "value": -20.674, "time": 1717757788.287} +{"unit": "dumpload_q", "value": 0.119, "time": 1717757788.287} +{"unit": "pv319_p", "value": 4.618, "time": 1717757788.379} +{"unit": "pv319_q", "value": 0.047, "time": 1717757788.379} +{"unit": "pv330_p", "value": 15.297, "time": 1717757788.308} +{"unit": "pv330_q", "value": -0.193, "time": 1717757788.308} +{"unit": "battery_p", "value": -0.222, "time": 1717757788.037} +{"unit": "battery_q", "value": 0.034, "time": 1717757788.037} +{"unit": "b2b_p", "value": -27.242, "time": 1717757788.378} +{"unit": "b2b_q", "value": 6.897, "time": 1717757788.378} +{"unit": "gaia_p", "value": 5.411, "time": 1717757788.299} +{"unit": "gaia_q", "value": -8.211, "time": 1717757788.299} +{"unit": "pcc_p", "value": 10.693, "time": 1717757789.373} +{"unit": "pcc_q", "value": 1.252, "time": 1717757789.373} +{"unit": "dumpload_p", "value": -20.876, "time": 1717757789.38} +{"unit": "dumpload_q", "value": 0.119, "time": 1717757789.38} +{"unit": "pv319_p", "value": 4.725, "time": 1717757789.422} +{"unit": "pv319_q", "value": 0.05, "time": 1717757789.422} +{"unit": "pv330_p", "value": 15.385, "time": 1717757789.304} +{"unit": "pv330_q", "value": -0.197, "time": 1717757789.304} +{"unit": "battery_p", "value": -0.22, "time": 1717757789.317} +{"unit": "battery_q", "value": 0.036, "time": 1717757789.317} +{"unit": "b2b_p", "value": -15.546, "time": 1717757789.444} +{"unit": "b2b_q", "value": 6.948, "time": 1717757789.444} +{"unit": "gaia_p", "value": 5.404, "time": 1717757789.355} +{"unit": "gaia_q", "value": -8.384, "time": 1717757789.355} +{"unit": "pcc_p", "value": 4.78, "time": 1717757790.458} +{"unit": "pcc_q", "value": 1.67, "time": 1717757790.458} +{"unit": "dumpload_p", "value": -20.955, "time": 1717757790.625} +{"unit": "dumpload_q", "value": 0.117, "time": 1717757790.625} +{"unit": "pv319_p", "value": 4.254, "time": 1717757790.582} +{"unit": "pv319_q", "value": 0.045, "time": 1717757790.582} +{"unit": "pv330_p", "value": 15.453, "time": 1717757790.638} +{"unit": "pv330_q", "value": -0.198, "time": 1717757790.638} +{"unit": "battery_p", "value": -0.22, "time": 1717757790.562} +{"unit": "battery_q", "value": 0.039, "time": 1717757790.562} +{"unit": "b2b_p", "value": -10.017, "time": 1717757790.666} +{"unit": "b2b_q", "value": 6.987, "time": 1717757790.666} +{"unit": "gaia_p", "value": 5.804, "time": 1717757790.65} +{"unit": "gaia_q", "value": -8.603, "time": 1717757790.65} +{"unit": "pcc_p", "value": 3.427, "time": 1717757791.713} +{"unit": "pcc_q", "value": 1.666, "time": 1717757791.713} +{"unit": "dumpload_p", "value": -20.986, "time": 1717757791.705} +{"unit": "dumpload_q", "value": 0.122, "time": 1717757791.705} +{"unit": "pv319_p", "value": 3.245, "time": 1717757791.553} +{"unit": "pv319_q", "value": 0.028, "time": 1717757791.842} +{"unit": "pv330_p", "value": 15.355, "time": 1717757791.737} +{"unit": "pv330_q", "value": -0.201, "time": 1717757791.737} +{"unit": "battery_p", "value": -0.221, "time": 1717757791.767} +{"unit": "battery_q", "value": 0.036, "time": 1717757791.767} +{"unit": "b2b_p", "value": -7.023, "time": 1717757791.862} +{"unit": "b2b_q", "value": 6.969, "time": 1717757791.862} +{"unit": "gaia_p", "value": 6.747, "time": 1717757791.851} +{"unit": "gaia_q", "value": -9.053, "time": 1717757791.851} +{"unit": "pcc_p", "value": 3.581, "time": 1717757792.788} +{"unit": "pcc_q", "value": 0.996, "time": 1717757792.788} +{"unit": "dumpload_p", "value": -21.0, "time": 1717757792.955} +{"unit": "dumpload_q", "value": 0.118, "time": 1717757792.955} +{"unit": "pv319_p", "value": 2.768, "time": 1717757792.969} +{"unit": "pv319_q", "value": 0.026, "time": 1717757792.969} +{"unit": "pv330_p", "value": 15.406, "time": 1717757792.937} +{"unit": "pv330_q", "value": -0.196, "time": 1717757792.937} +{"unit": "battery_p", "value": -0.221, "time": 1717757792.992} +{"unit": "battery_q", "value": 0.037, "time": 1717757792.992} +{"unit": "b2b_p", "value": -5.56, "time": 1717757792.676} +{"unit": "b2b_q", "value": 7.008, "time": 1717757792.676} +{"unit": "gaia_p", "value": 4.784, "time": 1717757792.915} +{"unit": "gaia_q", "value": -8.254, "time": 1717757792.915} +{"unit": "pcc_p", "value": 1.367, "time": 1717757794.043} +{"unit": "pcc_q", "value": 0.778, "time": 1717757794.043} +{"unit": "dumpload_p", "value": -21.018, "time": 1717757794.035} +{"unit": "dumpload_q", "value": 0.119, "time": 1717757794.035} +{"unit": "pv319_p", "value": 4.162, "time": 1717757793.979} +{"unit": "pv319_q", "value": 0.042, "time": 1717757793.979} +{"unit": "pv330_p", "value": 15.453, "time": 1717757794.036} +{"unit": "pv330_q", "value": -0.204, "time": 1717757794.036} +{"unit": "battery_p", "value": -0.222, "time": 1717757793.797} +{"unit": "battery_q", "value": 0.037, "time": 1717757793.797} +{"unit": "b2b_p", "value": -5.543, "time": 1717757793.174} +{"unit": "b2b_q", "value": 7.002, "time": 1717757793.174} +{"unit": "gaia_p", "value": 4.219, "time": 1717757793.915} +{"unit": "gaia_q", "value": -8.081, "time": 1717757793.915} +{"unit": "pcc_p", "value": -0.04, "time": 1717757795.108} +{"unit": "pcc_q", "value": 0.293, "time": 1717757795.108} +{"unit": "dumpload_p", "value": -21.039, "time": 1717757795.136} +{"unit": "dumpload_q", "value": 0.121, "time": 1717757795.136} +{"unit": "pv319_p", "value": 6.759, "time": 1717757795.171} +{"unit": "pv319_q", "value": 0.082, "time": 1717757795.171} +{"unit": "pv330_p", "value": 15.453, "time": 1717757794.934} +{"unit": "pv330_q", "value": -0.201, "time": 1717757794.934} +{"unit": "battery_p", "value": -0.221, "time": 1717757795.037} +{"unit": "battery_q", "value": 0.039, "time": 1717757795.037} +{"unit": "b2b_p", "value": -3.542, "time": 1717757795.224} +{"unit": "b2b_q", "value": 6.986, "time": 1717757795.224} +{"unit": "gaia_p", "value": 2.327, "time": 1717757795.328} +{"unit": "gaia_q", "value": -7.536, "time": 1717757795.328} +{"unit": "pcc_p", "value": -1.507, "time": 1717757796.193} +{"unit": "pcc_q", "value": 0.2, "time": 1717757796.193} +{"unit": "dumpload_p", "value": -21.062, "time": 1717757796.21} +{"unit": "dumpload_q", "value": 0.12, "time": 1717757796.21} +{"unit": "pv319_p", "value": 9.088, "time": 1717757796.327} +{"unit": "pv319_q", "value": 0.104, "time": 1717757796.327} +{"unit": "pv330_p", "value": 15.389, "time": 1717757796.408} +{"unit": "pv330_q", "value": -0.195, "time": 1717757796.408} +{"unit": "battery_p", "value": -0.221, "time": 1717757796.267} +{"unit": "battery_q", "value": 0.036, "time": 1717757796.267} +{"unit": "b2b_p", "value": -3.73, "time": 1717757796.277} +{"unit": "b2b_q", "value": 7.048, "time": 1717757796.277} +{"unit": "gaia_p", "value": 2.009, "time": 1717757796.298} +{"unit": "gaia_q", "value": -7.447, "time": 1717757796.298} +{"unit": "pcc_p", "value": -1.319, "time": 1717757797.448} +{"unit": "pcc_q", "value": 0.429, "time": 1717757797.448} +{"unit": "dumpload_p", "value": -21.046, "time": 1717757797.475} +{"unit": "dumpload_q", "value": 0.123, "time": 1717757797.475} +{"unit": "pv319_p", "value": 9.519, "time": 1717757797.437} +{"unit": "pv319_q", "value": 0.098, "time": 1717757797.437} +{"unit": "pv330_p", "value": 15.565, "time": 1717757797.558} +{"unit": "pv330_q", "value": -0.186, "time": 1717757797.558} +{"unit": "battery_p", "value": -0.22, "time": 1717757797.472} +{"unit": "battery_q", "value": 0.038, "time": 1717757797.472} +{"unit": "b2b_p", "value": -5.278, "time": 1717757797.398} +{"unit": "b2b_q", "value": 7.085, "time": 1717757797.398} +{"unit": "gaia_p", "value": 2.819, "time": 1717757797.515} +{"unit": "gaia_q", "value": -7.672, "time": 1717757797.515} +{"unit": "pcc_p", "value": -1.139, "time": 1717757798.523} +{"unit": "pcc_q", "value": 0.617, "time": 1717757798.523} +{"unit": "dumpload_p", "value": -21.037, "time": 1717757798.555} +{"unit": "dumpload_q", "value": 0.12, "time": 1717757798.555} +{"unit": "pv319_p", "value": 9.478, "time": 1717757798.579} +{"unit": "pv319_q", "value": 0.107, "time": 1717757798.579} +{"unit": "pv330_p", "value": 15.544, "time": 1717757798.657} +{"unit": "pv330_q", "value": -0.189, "time": 1717757798.657} +{"unit": "battery_p", "value": -0.221, "time": 1717757798.692} +{"unit": "battery_q", "value": 0.036, "time": 1717757798.692} +{"unit": "b2b_p", "value": -6.53, "time": 1717757798.711} +{"unit": "b2b_q", "value": 7.0, "time": 1717757798.711} +{"unit": "gaia_p", "value": 3.625, "time": 1717757798.683} +{"unit": "gaia_q", "value": -7.906, "time": 1717757798.683} +{"unit": "pcc_p", "value": -0.679, "time": 1717757799.688} +{"unit": "pcc_q", "value": 0.619, "time": 1717757799.688} +{"unit": "dumpload_p", "value": -21.043, "time": 1717757799.82} +{"unit": "dumpload_q", "value": 0.12, "time": 1717757799.82} +{"unit": "pv319_p", "value": 10.01, "time": 1717757799.78} +{"unit": "pv319_q", "value": 0.113, "time": 1717757799.78} +{"unit": "pv330_p", "value": 15.543, "time": 1717757798.937} +{"unit": "pv330_q", "value": -0.189, "time": 1717757798.937} +{"unit": "battery_p", "value": -0.223, "time": 1717757799.532} +{"unit": "battery_q", "value": 0.037, "time": 1717757799.532} +{"unit": "b2b_p", "value": -7.272, "time": 1717757799.83} +{"unit": "b2b_q", "value": 6.982, "time": 1717757799.83} +{"unit": "gaia_p", "value": 3.667, "time": 1717757799.709} +{"unit": "gaia_q", "value": -7.916, "time": 1717757799.709} +{"unit": "pcc_p", "value": -0.943, "time": 1717757800.943} +{"unit": "pcc_q", "value": 0.631, "time": 1717757800.943} +{"unit": "dumpload_p", "value": -21.04, "time": 1717757800.895} +{"unit": "dumpload_q", "value": 0.123, "time": 1717757800.895} +{"unit": "pv319_p", "value": 10.438, "time": 1717757800.851} +{"unit": "pv319_q", "value": 0.119, "time": 1717757801.018} +{"unit": "pv330_p", "value": 15.23, "time": 1717757800.943} +{"unit": "pv330_q", "value": -0.188, "time": 1717757800.943} +{"unit": "battery_p", "value": -0.222, "time": 1717757800.762} +{"unit": "battery_q", "value": 0.038, "time": 1717757800.762} +{"unit": "b2b_p", "value": -7.585, "time": 1717757801.031} +{"unit": "b2b_q", "value": 6.982, "time": 1717757801.031} +{"unit": "gaia_p", "value": 3.726, "time": 1717757800.939} +{"unit": "gaia_q", "value": -7.939, "time": 1717757800.939} +{"unit": "pcc_p", "value": -1.133, "time": 1717757802.013} +{"unit": "pcc_q", "value": 0.86, "time": 1717757802.013} +{"unit": "dumpload_p", "value": -21.03, "time": 1717757802.036} +{"unit": "dumpload_q", "value": 0.119, "time": 1717757802.036} +{"unit": "pv319_p", "value": 10.777, "time": 1717757802.143} +{"unit": "pv319_q", "value": 0.116, "time": 1717757802.143} +{"unit": "pv330_p", "value": 15.205, "time": 1717757802.099} +{"unit": "pv330_q", "value": -0.187, "time": 1717757802.099} +{"unit": "battery_p", "value": -0.22, "time": 1717757801.997} +{"unit": "battery_q", "value": 0.038, "time": 1717757801.997} +{"unit": "b2b_p", "value": -8.204, "time": 1717757802.089} +{"unit": "b2b_q", "value": 6.976, "time": 1717757802.089} +{"unit": "gaia_p", "value": 4.427, "time": 1717757802.184} +{"unit": "gaia_q", "value": -8.182, "time": 1717757802.184} +{"unit": "pcc_p", "value": -0.822, "time": 1717757803.273} +{"unit": "pcc_q", "value": 0.852, "time": 1717757803.273} +{"unit": "dumpload_p", "value": -21.008, "time": 1717757803.295} +{"unit": "dumpload_q", "value": 0.121, "time": 1717757803.295} +{"unit": "pv319_p", "value": 10.839, "time": 1717757803.227} +{"unit": "pv319_q", "value": 0.118, "time": 1717757803.227} +{"unit": "pv330_p", "value": 15.172, "time": 1717757803.076} +{"unit": "pv330_q", "value": -0.185, "time": 1717757803.076} +{"unit": "battery_p", "value": -0.22, "time": 1717757803.237} +{"unit": "battery_q", "value": 0.039, "time": 1717757803.237} +{"unit": "b2b_p", "value": -8.825, "time": 1717757803.224} +{"unit": "b2b_q", "value": 6.989, "time": 1717757803.224} +{"unit": "gaia_p", "value": 5.044, "time": 1717757803.316} +{"unit": "gaia_q", "value": -8.389, "time": 1717757803.316} +{"unit": "pcc_p", "value": -1.081, "time": 1717757804.348} +{"unit": "pcc_q", "value": 0.815, "time": 1717757804.523} +{"unit": "dumpload_p", "value": -21.014, "time": 1717757804.365} +{"unit": "dumpload_q", "value": 0.123, "time": 1717757804.55} +{"unit": "pv319_p", "value": 10.846, "time": 1717757804.379} +{"unit": "pv319_q", "value": 0.121, "time": 1717757804.57} +{"unit": "pv330_p", "value": 15.517, "time": 1717757804.365} +{"unit": "pv330_q", "value": -0.191, "time": 1717757804.365} +{"unit": "battery_p", "value": -0.221, "time": 1717757804.452} +{"unit": "battery_q", "value": 0.036, "time": 1717757804.452} +{"unit": "b2b_p", "value": -9.236, "time": 1717757804.29} +{"unit": "b2b_q", "value": 6.964, "time": 1717757804.29} +{"unit": "gaia_p", "value": 5.33, "time": 1717757804.356} +{"unit": "gaia_q", "value": -8.507, "time": 1717757804.356} +{"unit": "pcc_p", "value": 1.88, "time": 1717757805.589} +{"unit": "pcc_q", "value": 0.661, "time": 1717757805.589} +{"unit": "dumpload_p", "value": -20.965, "time": 1717757805.605} +{"unit": "dumpload_q", "value": 0.119, "time": 1717757805.605} +{"unit": "pv319_p", "value": 10.826, "time": 1717757805.725} +{"unit": "pv319_q", "value": 0.119, "time": 1717757805.725} +{"unit": "pv330_p", "value": 13.537, "time": 1717757805.79} +{"unit": "pv330_q", "value": -0.17, "time": 1717757805.79} +{"unit": "battery_p", "value": -0.22, "time": 1717757805.732} +{"unit": "battery_q", "value": 0.038, "time": 1717757805.732} +{"unit": "b2b_p", "value": -9.789, "time": 1717757805.752} +{"unit": "b2b_q", "value": 6.982, "time": 1717757805.752} +{"unit": "gaia_p", "value": 4.097, "time": 1717757805.777} +{"unit": "gaia_q", "value": -8.001, "time": 1717757805.777} +{"unit": "pcc_p", "value": 4.236, "time": 1717757806.853} +{"unit": "pcc_q", "value": 0.757, "time": 1717757806.853} +{"unit": "dumpload_p", "value": -20.917, "time": 1717757806.855} +{"unit": "dumpload_q", "value": 0.119, "time": 1717757806.855} +{"unit": "pv319_p", "value": 10.801, "time": 1717757806.841} +{"unit": "pv319_q", "value": 0.12, "time": 1717757806.841} +{"unit": "pv330_p", "value": 10.979, "time": 1717757806.737} +{"unit": "pv330_q", "value": -0.146, "time": 1717757806.737} +{"unit": "battery_p", "value": -0.221, "time": 1717757806.552} +{"unit": "battery_q", "value": 0.037, "time": 1717757806.552} +{"unit": "b2b_p", "value": -9.001, "time": 1717757806.913} +{"unit": "b2b_q", "value": 6.916, "time": 1717757806.913} +{"unit": "gaia_p", "value": 4.191, "time": 1717757806.851} +{"unit": "gaia_q", "value": -7.984, "time": 1717757806.851} +{"unit": "pcc_p", "value": 3.379, "time": 1717757807.938} +{"unit": "pcc_q", "value": 0.86, "time": 1717757807.938} +{"unit": "dumpload_p", "value": -20.925, "time": 1717757807.94} +{"unit": "dumpload_q", "value": 0.117, "time": 1717757807.94} +{"unit": "pv319_p", "value": 10.772, "time": 1717757807.971} +{"unit": "pv319_q", "value": 0.123, "time": 1717757807.971} +{"unit": "pv330_p", "value": 8.698, "time": 1717757807.983} +{"unit": "pv330_q", "value": -0.122, "time": 1717757807.983} +{"unit": "battery_p", "value": -0.221, "time": 1717757807.787} +{"unit": "battery_q", "value": 0.036, "time": 1717757807.787} +{"unit": "b2b_p", "value": -7.475, "time": 1717757807.98} +{"unit": "b2b_q", "value": 6.998, "time": 1717757807.98} +{"unit": "gaia_p", "value": 4.623, "time": 1717757808.051} +{"unit": "gaia_q", "value": -8.14, "time": 1717757808.051} +{"unit": "pcc_p", "value": 0.068, "time": 1717757809.029} +{"unit": "pcc_q", "value": 0.936, "time": 1717757809.029} +{"unit": "dumpload_p", "value": -20.961, "time": 1717757809.015} +{"unit": "dumpload_q", "value": 0.121, "time": 1717757809.015} +{"unit": "pv319_p", "value": 10.764, "time": 1717757808.925} +{"unit": "pv319_q", "value": 0.129, "time": 1717757809.136} +{"unit": "pv330_p", "value": 9.459, "time": 1717757808.867} +{"unit": "pv330_q", "value": -0.133, "time": 1717757809.156} +{"unit": "battery_p", "value": -0.222, "time": 1717757809.032} +{"unit": "battery_q", "value": 0.036, "time": 1717757809.032} +{"unit": "b2b_p", "value": -4.995, "time": 1717757809.118} +{"unit": "b2b_q", "value": 6.95, "time": 1717757809.118} +{"unit": "gaia_p", "value": 4.396, "time": 1717757808.982} +{"unit": "gaia_q", "value": -8.061, "time": 1717757808.982} +{"unit": "pcc_p", "value": -2.262, "time": 1717757810.103} +{"unit": "pcc_q", "value": 1.107, "time": 1717757810.103} +{"unit": "dumpload_p", "value": -20.982, "time": 1717757810.085} +{"unit": "dumpload_q", "value": 0.118, "time": 1717757810.085} +{"unit": "pv319_p", "value": 10.744, "time": 1717757810.187} +{"unit": "pv319_q", "value": 0.134, "time": 1717757810.187} +{"unit": "pv330_p", "value": 11.643, "time": 1717757809.861} +{"unit": "pv330_q", "value": -0.15, "time": 1717757809.861} +{"unit": "battery_p", "value": -0.219, "time": 1717757810.267} +{"unit": "battery_q", "value": 0.035, "time": 1717757810.267} +{"unit": "b2b_p", "value": -5.37, "time": 1717757810.17} +{"unit": "b2b_q", "value": 7.063, "time": 1717757810.17} +{"unit": "gaia_p", "value": 5.184, "time": 1717757810.068} +{"unit": "gaia_q", "value": -8.436, "time": 1717757810.068} +{"unit": "pcc_p", "value": -2.351, "time": 1717757811.193} +{"unit": "pcc_q", "value": 1.001, "time": 1717757811.193} +{"unit": "dumpload_p", "value": -20.976, "time": 1717757811.35} +{"unit": "dumpload_q", "value": 0.121, "time": 1717757811.35} +{"unit": "pv319_p", "value": 10.735, "time": 1717757811.218} +{"unit": "pv319_q", "value": 0.135, "time": 1717757811.218} +{"unit": "pv330_p", "value": 14.444, "time": 1717757811.191} +{"unit": "pv330_q", "value": -0.168, "time": 1717757811.191} +{"unit": "battery_p", "value": -0.22, "time": 1717757811.092} +{"unit": "battery_q", "value": 0.034, "time": 1717757811.092} +{"unit": "b2b_p", "value": -7.096, "time": 1717757811.426} +{"unit": "b2b_q", "value": 7.024, "time": 1717757811.426} +{"unit": "gaia_p", "value": 4.701, "time": 1717757811.452} +{"unit": "gaia_q", "value": -8.253, "time": 1717757811.452} +{"unit": "pcc_p", "value": -1.618, "time": 1717757812.443} +{"unit": "pcc_q", "value": 1.007, "time": 1717757812.443} +{"unit": "dumpload_p", "value": -20.958, "time": 1717757812.435} +{"unit": "dumpload_q", "value": 0.119, "time": 1717757812.435} +{"unit": "pv319_p", "value": 10.737, "time": 1717757812.151} +{"unit": "pv319_q", "value": 0.137, "time": 1717757812.151} +{"unit": "pv330_p", "value": 15.406, "time": 1717757812.476} +{"unit": "pv330_q", "value": -0.177, "time": 1717757812.476} +{"unit": "battery_p", "value": -0.22, "time": 1717757812.317} +{"unit": "battery_q", "value": 0.035, "time": 1717757812.317} +{"unit": "b2b_p", "value": -8.464, "time": 1717757812.374} +{"unit": "b2b_q", "value": 7.03, "time": 1717757812.374} +{"unit": "gaia_p", "value": 5.094, "time": 1717757812.503} +{"unit": "gaia_q", "value": -8.397, "time": 1717757812.503} +{"unit": "pcc_p", "value": -1.757, "time": 1717757813.513} +{"unit": "pcc_q", "value": 1.475, "time": 1717757813.513} +{"unit": "dumpload_p", "value": -20.954, "time": 1717757813.515} +{"unit": "dumpload_q", "value": 0.123, "time": 1717757813.515} +{"unit": "pv319_p", "value": 10.584, "time": 1717757813.62} +{"unit": "pv319_q", "value": 0.137, "time": 1717757813.62} +{"unit": "pv330_p", "value": 15.766, "time": 1717757813.537} +{"unit": "pv330_q", "value": -0.181, "time": 1717757813.537} +{"unit": "battery_p", "value": -0.222, "time": 1717757813.542} +{"unit": "battery_q", "value": 0.035, "time": 1717757813.542} +{"unit": "b2b_p", "value": -9.902, "time": 1717757813.54} +{"unit": "b2b_q", "value": 6.968, "time": 1717757813.54} +{"unit": "gaia_p", "value": 6.308, "time": 1717757813.694} +{"unit": "gaia_q", "value": -8.88, "time": 1717757813.694} +{"unit": "pcc_p", "value": -0.937, "time": 1717757814.589} +{"unit": "pcc_q", "value": 1.538, "time": 1717757814.589} +{"unit": "dumpload_p", "value": -20.934, "time": 1717757814.6} +{"unit": "dumpload_q", "value": 0.119, "time": 1717757814.6} +{"unit": "pv319_p", "value": 10.734, "time": 1717757814.591} +{"unit": "pv319_q", "value": 0.135, "time": 1717757814.591} +{"unit": "pv330_p", "value": 15.867, "time": 1717757814.653} +{"unit": "pv330_q", "value": -0.192, "time": 1717757814.653} +{"unit": "battery_p", "value": -0.22, "time": 1717757814.762} +{"unit": "battery_q", "value": 0.036, "time": 1717757814.762} +{"unit": "b2b_p", "value": -11.067, "time": 1717757814.683} +{"unit": "b2b_q", "value": 6.981, "time": 1717757814.683} +{"unit": "gaia_p", "value": 7.085, "time": 1717757814.858} +{"unit": "gaia_q", "value": -9.217, "time": 1717757814.858} +{"unit": "pcc_p", "value": -0.425, "time": 1717757815.704} +{"unit": "pcc_q", "value": 1.814, "time": 1717757815.889} +{"unit": "dumpload_p", "value": -20.915, "time": 1717757815.86} +{"unit": "dumpload_q", "value": 0.12, "time": 1717757815.86} +{"unit": "pv319_p", "value": 10.712, "time": 1717757815.897} +{"unit": "pv319_q", "value": 0.136, "time": 1717757815.897} +{"unit": "pv330_p", "value": 15.898, "time": 1717757815.159} +{"unit": "pv330_q", "value": -0.193, "time": 1717757815.159} +{"unit": "battery_p", "value": -0.221, "time": 1717757815.572} +{"unit": "battery_q", "value": 0.035, "time": 1717757815.572} +{"unit": "b2b_p", "value": -11.868, "time": 1717757815.932} +{"unit": "b2b_q", "value": 6.961, "time": 1717757815.932} +{"unit": "gaia_p", "value": 6.652, "time": 1717757815.912} +{"unit": "gaia_q", "value": -9.01, "time": 1717757815.912} +{"unit": "pcc_p", "value": 0.056, "time": 1717757816.964} +{"unit": "pcc_q", "value": 1.624, "time": 1717757816.964} +{"unit": "dumpload_p", "value": -20.878, "time": 1717757816.935} +{"unit": "dumpload_q", "value": 0.12, "time": 1717757816.935} +{"unit": "pv319_p", "value": 10.634, "time": 1717757816.754} +{"unit": "pv319_q", "value": 0.137, "time": 1717757816.754} +{"unit": "pv330_p", "value": 15.76, "time": 1717757816.986} +{"unit": "pv330_q", "value": -0.192, "time": 1717757816.986} +{"unit": "battery_p", "value": -0.219, "time": 1717757816.792} +{"unit": "battery_q", "value": 0.036, "time": 1717757816.792} +{"unit": "b2b_p", "value": -12.083, "time": 1717757816.913} +{"unit": "b2b_q", "value": 6.975, "time": 1717757816.913} +{"unit": "gaia_p", "value": 6.341, "time": 1717757817.096} +{"unit": "gaia_q", "value": -8.855, "time": 1717757817.096} +{"unit": "pcc_p", "value": -10.477, "time": 1717757818.039} +{"unit": "pcc_q", "value": 1.918, "time": 1717757818.039} +{"unit": "dumpload_p", "value": -10.486, "time": 1717757818.06} +{"unit": "dumpload_q", "value": 0.075, "time": 1717757818.06} +{"unit": "pv319_p", "value": 10.725, "time": 1717757818.17} +{"unit": "pv319_q", "value": 0.13, "time": 1717757818.17} +{"unit": "pv330_p", "value": 15.701, "time": 1717757818.118} +{"unit": "pv330_q", "value": -0.192, "time": 1717757818.118} +{"unit": "battery_p", "value": -0.22, "time": 1717757818.042} +{"unit": "battery_q", "value": 0.035, "time": 1717757818.042} +{"unit": "b2b_p", "value": -12.106, "time": 1717757817.687} +{"unit": "b2b_q", "value": 6.976, "time": 1717757817.687} +{"unit": "gaia_p", "value": 6.141, "time": 1717757817.975} +{"unit": "gaia_q", "value": -9.171, "time": 1717757818.264} +{"unit": "pcc_p", "value": -5.306, "time": 1717757819.114} +{"unit": "pcc_q", "value": 1.69, "time": 1717757819.114} +{"unit": "dumpload_p", "value": -10.079, "time": 1717757819.135} +{"unit": "dumpload_q", "value": 0.086, "time": 1717757819.31} +{"unit": "pv319_p", "value": 10.722, "time": 1717757819.326} +{"unit": "pv319_q", "value": 0.134, "time": 1717757819.326} +{"unit": "pv330_p", "value": 15.68, "time": 1717757819.344} +{"unit": "pv330_q", "value": -0.194, "time": 1717757819.344} +{"unit": "battery_p", "value": -0.223, "time": 1717757819.287} +{"unit": "battery_q", "value": 0.035, "time": 1717757819.287} +{"unit": "b2b_p", "value": -17.385, "time": 1717757819.216} +{"unit": "b2b_q", "value": 6.907, "time": 1717757819.216} +{"unit": "gaia_p", "value": 6.338, "time": 1717757819.254} +{"unit": "gaia_q", "value": -8.926, "time": 1717757819.254} +{"unit": "pcc_p", "value": -2.027, "time": 1717757820.379} +{"unit": "pcc_q", "value": 1.443, "time": 1717757820.379} +{"unit": "dumpload_p", "value": -10.054, "time": 1717757820.385} +{"unit": "dumpload_q", "value": 0.087, "time": 1717757820.385} +{"unit": "pv319_p", "value": 10.657, "time": 1717757820.428} +{"unit": "pv319_q", "value": 0.136, "time": 1717757820.428} +{"unit": "pv330_p", "value": 15.611, "time": 1717757820.462} +{"unit": "pv330_q", "value": -0.192, "time": 1717757820.462} +{"unit": "battery_p", "value": -0.221, "time": 1717757820.112} +{"unit": "battery_q", "value": 0.036, "time": 1717757820.112} +{"unit": "b2b_p", "value": -20.059, "time": 1717757820.476} +{"unit": "b2b_q", "value": 6.912, "time": 1717757820.476} +{"unit": "gaia_p", "value": 6.295, "time": 1717757820.372} +{"unit": "gaia_q", "value": -8.571, "time": 1717757820.572} +{"unit": "pcc_p", "value": -0.375, "time": 1717757821.464} +{"unit": "pcc_q", "value": 1.208, "time": 1717757821.464} +{"unit": "dumpload_p", "value": -10.041, "time": 1717757821.47} +{"unit": "dumpload_q", "value": 0.088, "time": 1717757821.47} +{"unit": "pv319_p", "value": 10.718, "time": 1717757821.554} +{"unit": "pv319_q", "value": 0.136, "time": 1717757821.554} +{"unit": "pv330_p", "value": 15.573, "time": 1717757821.542} +{"unit": "pv330_q", "value": -0.193, "time": 1717757821.542} +{"unit": "battery_p", "value": -0.222, "time": 1717757821.397} +{"unit": "battery_q", "value": 0.037, "time": 1717757821.397} +{"unit": "b2b_p", "value": -21.537, "time": 1717757821.623} +{"unit": "b2b_q", "value": 6.929, "time": 1717757821.623} +{"unit": "gaia_p", "value": 5.373, "time": 1717757821.572} +{"unit": "gaia_q", "value": -8.46, "time": 1717757821.572} +{"unit": "pcc_p", "value": 0.294, "time": 1717757822.549} +{"unit": "pcc_q", "value": 1.203, "time": 1717757822.729} +{"unit": "dumpload_p", "value": -10.037, "time": 1717757822.72} +{"unit": "dumpload_q", "value": 0.088, "time": 1717757822.72} +{"unit": "pv319_p", "value": 10.719, "time": 1717757822.615} +{"unit": "pv319_q", "value": 0.137, "time": 1717757822.615} +{"unit": "pv330_p", "value": 15.537, "time": 1717757822.565} +{"unit": "pv330_q", "value": -0.192, "time": 1717757822.565} +{"unit": "battery_p", "value": -0.221, "time": 1717757822.632} +{"unit": "battery_q", "value": 0.039, "time": 1717757822.632} +{"unit": "b2b_p", "value": -21.729, "time": 1717757822.679} +{"unit": "b2b_q", "value": 6.913, "time": 1717757822.679} +{"unit": "gaia_p", "value": 5.055, "time": 1717757822.654} +{"unit": "gaia_q", "value": -8.33, "time": 1717757822.654} +{"unit": "pcc_p", "value": 0.656, "time": 1717757823.799} +{"unit": "pcc_q", "value": 1.181, "time": 1717757823.799} +{"unit": "dumpload_p", "value": -10.035, "time": 1717757823.795} +{"unit": "dumpload_q", "value": 0.088, "time": 1717757823.795} +{"unit": "pv319_p", "value": 10.707, "time": 1717757823.773} +{"unit": "pv319_q", "value": 0.136, "time": 1717757823.773} +{"unit": "pv330_p", "value": 15.502, "time": 1717757823.901} +{"unit": "pv330_q", "value": -0.189, "time": 1717757823.901} +{"unit": "battery_p", "value": -0.221, "time": 1717757823.862} +{"unit": "battery_q", "value": 0.038, "time": 1717757823.862} +{"unit": "b2b_p", "value": -21.729, "time": 1717757822.679} +{"unit": "b2b_q", "value": 6.913, "time": 1717757822.679} +{"unit": "gaia_p", "value": 5.194, "time": 1717757823.9} +{"unit": "gaia_q", "value": -8.399, "time": 1717757823.9} +{"unit": "pcc_p", "value": 0.162, "time": 1717757824.884} +{"unit": "pcc_q", "value": 1.424, "time": 1717757824.884} +{"unit": "dumpload_p", "value": -10.038, "time": 1717757824.87} +{"unit": "dumpload_q", "value": 0.087, "time": 1717757824.87} +{"unit": "pv319_p", "value": 10.563, "time": 1717757824.926} +{"unit": "pv319_q", "value": 0.143, "time": 1717757824.926} +{"unit": "pv330_p", "value": 15.513, "time": 1717757824.452} +{"unit": "pv330_q", "value": -0.19, "time": 1717757824.452} +{"unit": "battery_p", "value": -0.221, "time": 1717757824.667} +{"unit": "battery_q", "value": 0.037, "time": 1717757824.667} +{"unit": "b2b_p", "value": -22.143, "time": 1717757824.915} +{"unit": "b2b_q", "value": 6.89, "time": 1717757824.915} +{"unit": "gaia_p", "value": 5.882, "time": 1717757825.003} +{"unit": "gaia_q", "value": -8.664, "time": 1717757825.003} +{"unit": "pcc_p", "value": 0.007, "time": 1717757825.944} +{"unit": "pcc_q", "value": 1.862, "time": 1717757826.124} +{"unit": "dumpload_p", "value": -10.038, "time": 1717757826.135} +{"unit": "dumpload_q", "value": 0.087, "time": 1717757826.135} +{"unit": "pv319_p", "value": 10.637, "time": 1717757825.979} +{"unit": "pv319_q", "value": 0.141, "time": 1717757826.17} +{"unit": "pv330_p", "value": 15.518, "time": 1717757826.078} +{"unit": "pv330_q", "value": -0.189, "time": 1717757826.184} +{"unit": "battery_p", "value": -0.22, "time": 1717757825.907} +{"unit": "battery_q", "value": 0.036, "time": 1717757825.907} +{"unit": "b2b_p", "value": -22.344, "time": 1717757826.103} +{"unit": "b2b_q", "value": 6.893, "time": 1717757826.103} +{"unit": "gaia_p", "value": 6.904, "time": 1717757826.052} +{"unit": "gaia_q", "value": -8.956, "time": 1717757826.253} +{"unit": "pcc_p", "value": -0.239, "time": 1717757827.199} +{"unit": "pcc_q", "value": 1.828, "time": 1717757827.199} +{"unit": "dumpload_p", "value": -10.028, "time": 1717757827.21} +{"unit": "dumpload_q", "value": 0.085, "time": 1717757827.21} +{"unit": "pv319_p", "value": 10.697, "time": 1717757827.155} +{"unit": "pv319_q", "value": 0.146, "time": 1717757827.155} +{"unit": "pv330_p", "value": 15.521, "time": 1717757827.302} +{"unit": "pv330_q", "value": -0.191, "time": 1717757827.302} +{"unit": "battery_p", "value": -0.22, "time": 1717757827.132} +{"unit": "battery_q", "value": 0.037, "time": 1717757827.132} +{"unit": "b2b_p", "value": -22.794, "time": 1717757827.114} +{"unit": "b2b_q", "value": 6.882, "time": 1717757827.114} +{"unit": "gaia_p", "value": 6.97, "time": 1717757827.3} +{"unit": "gaia_q", "value": -9.13, "time": 1717757827.3} +{"unit": "pcc_p", "value": 0.263, "time": 1717757828.279} +{"unit": "pcc_q", "value": 1.951, "time": 1717757828.279} +{"unit": "dumpload_p", "value": -10.022, "time": 1717757828.275} +{"unit": "dumpload_q", "value": 0.083, "time": 1717757828.275} +{"unit": "pv319_p", "value": 10.716, "time": 1717757828.332} +{"unit": "pv319_q", "value": 0.151, "time": 1717757828.332} +{"unit": "pv330_p", "value": 15.51, "time": 1717757828.446} +{"unit": "pv330_q", "value": -0.19, "time": 1717757828.446} +{"unit": "battery_p", "value": -0.221, "time": 1717757828.342} +{"unit": "battery_q", "value": 0.029, "time": 1717757828.342} +{"unit": "b2b_p", "value": -23.402, "time": 1717757828.338} +{"unit": "b2b_q", "value": 6.913, "time": 1717757828.338} +{"unit": "gaia_p", "value": 7.045, "time": 1717757828.454} +{"unit": "gaia_q", "value": -9.146, "time": 1717757828.454} +{"unit": "pcc_p", "value": 1.049, "time": 1717757829.519} +{"unit": "pcc_q", "value": 1.445, "time": 1717757829.519} +{"unit": "dumpload_p", "value": -10.011, "time": 1717757829.52} +{"unit": "dumpload_q", "value": 0.085, "time": 1717757829.52} +{"unit": "pv319_p", "value": 10.605, "time": 1717757829.422} +{"unit": "pv319_q", "value": 0.144, "time": 1717757829.422} +{"unit": "pv330_p", "value": 15.538, "time": 1717757828.967} +{"unit": "pv330_q", "value": -0.19, "time": 1717757828.967} +{"unit": "battery_p", "value": -0.221, "time": 1717757829.557} +{"unit": "battery_q", "value": 0.037, "time": 1717757829.557} +{"unit": "b2b_p", "value": -23.148, "time": 1717757829.568} +{"unit": "b2b_q", "value": 6.874, "time": 1717757829.568} +{"unit": "gaia_p", "value": 6.263, "time": 1717757829.253} +{"unit": "gaia_q", "value": -8.796, "time": 1717757829.253} +{"unit": "pcc_p", "value": 0.819, "time": 1717757830.619} +{"unit": "pcc_q", "value": 1.25, "time": 1717757830.619} +{"unit": "dumpload_p", "value": -10.01, "time": 1717757830.58} +{"unit": "dumpload_q", "value": 0.088, "time": 1717757830.58} +{"unit": "pv319_p", "value": 10.63, "time": 1717757830.526} +{"unit": "pv319_q", "value": 0.137, "time": 1717757830.526} +{"unit": "pv330_p", "value": 15.521, "time": 1717757830.418} +{"unit": "pv330_q", "value": -0.186, "time": 1717757830.418} +{"unit": "battery_p", "value": -0.221, "time": 1717757830.377} +{"unit": "battery_q", "value": 0.037, "time": 1717757830.377} +{"unit": "b2b_p", "value": -22.491, "time": 1717757830.686} +{"unit": "b2b_q", "value": 6.887, "time": 1717757830.686} +{"unit": "gaia_p", "value": 5.773, "time": 1717757830.776} +{"unit": "gaia_q", "value": -8.584, "time": 1717757830.776} +{"unit": "pcc_p", "value": -0.195, "time": 1717757831.729} +{"unit": "pcc_q", "value": 1.686, "time": 1717757831.729} +{"unit": "dumpload_p", "value": -10.009, "time": 1717757831.66} +{"unit": "dumpload_q", "value": 0.088, "time": 1717757831.66} +{"unit": "pv319_p", "value": 10.718, "time": 1717757831.673} +{"unit": "pv319_q", "value": 0.143, "time": 1717757831.673} +{"unit": "pv330_p", "value": 15.522, "time": 1717757831.591} +{"unit": "pv330_q", "value": -0.19, "time": 1717757831.591} +{"unit": "battery_p", "value": -0.22, "time": 1717757831.612} +{"unit": "battery_q", "value": 0.037, "time": 1717757831.612} +{"unit": "b2b_p", "value": -22.493, "time": 1717757831.666} +{"unit": "b2b_q", "value": 6.902, "time": 1717757831.666} +{"unit": "gaia_p", "value": 6.944, "time": 1717757831.852} +{"unit": "gaia_q", "value": -9.107, "time": 1717757831.852} +{"unit": "pcc_p", "value": 1.046, "time": 1717757832.799} +{"unit": "pcc_q", "value": 1.262, "time": 1717757832.799} +{"unit": "dumpload_p", "value": -10.003, "time": 1717757832.91} +{"unit": "dumpload_q", "value": 0.089, "time": 1717757832.91} +{"unit": "pv319_p", "value": 10.675, "time": 1717757832.802} +{"unit": "pv319_q", "value": 0.132, "time": 1717757832.802} +{"unit": "pv330_p", "value": 15.539, "time": 1717757832.831} +{"unit": "pv330_q", "value": -0.193, "time": 1717757832.831} +{"unit": "battery_p", "value": -0.221, "time": 1717757832.822} +{"unit": "battery_q", "value": 0.035, "time": 1717757832.822} +{"unit": "b2b_p", "value": -22.763, "time": 1717757832.391} +{"unit": "b2b_q", "value": 6.92, "time": 1717757832.391} +{"unit": "gaia_p", "value": 5.666, "time": 1717757833.0} +{"unit": "gaia_q", "value": -8.545, "time": 1717757833.0} +{"unit": "pcc_p", "value": 1.408, "time": 1717757833.874} +{"unit": "pcc_q", "value": 0.937, "time": 1717757833.874} +{"unit": "dumpload_p", "value": -10.001, "time": 1717757834.05} +{"unit": "dumpload_q", "value": 0.089, "time": 1717757834.05} +{"unit": "pv319_p", "value": 10.747, "time": 1717757833.962} +{"unit": "pv319_q", "value": 0.147, "time": 1717757833.962} +{"unit": "pv330_p", "value": 15.532, "time": 1717757833.939} +{"unit": "pv330_q", "value": -0.189, "time": 1717757833.939} +{"unit": "battery_p", "value": -0.219, "time": 1717757834.037} +{"unit": "battery_q", "value": 0.033, "time": 1717757834.037} +{"unit": "b2b_p", "value": -22.431, "time": 1717757834.024} +{"unit": "b2b_q", "value": 6.856, "time": 1717757834.024} +{"unit": "gaia_p", "value": 4.75, "time": 1717757833.993} +{"unit": "gaia_q", "value": -8.177, "time": 1717757833.993} +{"unit": "pcc_p", "value": 0.081, "time": 1717757835.134} +{"unit": "pcc_q", "value": 1.212, "time": 1717757835.134} +{"unit": "dumpload_p", "value": -9.999, "time": 1717757835.12} +{"unit": "dumpload_q", "value": 0.085, "time": 1717757835.12} +{"unit": "pv319_p", "value": 10.759, "time": 1717757835.265} +{"unit": "pv319_q", "value": 0.145, "time": 1717757835.265} +{"unit": "pv330_p", "value": 15.537, "time": 1717757834.876} +{"unit": "pv330_q", "value": -0.195, "time": 1717757834.876} +{"unit": "battery_p", "value": -0.219, "time": 1717757835.252} +{"unit": "battery_q", "value": 0.031, "time": 1717757835.252} +{"unit": "b2b_p", "value": -21.713, "time": 1717757835.17} +{"unit": "b2b_q", "value": 6.883, "time": 1717757835.368} +{"unit": "gaia_p", "value": 5.495, "time": 1717757835.3} +{"unit": "gaia_q", "value": -8.494, "time": 1717757835.3} +{"unit": "pcc_p", "value": 1.196, "time": 1717757836.394} +{"unit": "pcc_q", "value": 0.625, "time": 1717757836.394} +{"unit": "dumpload_p", "value": -9.997, "time": 1717757836.385} +{"unit": "dumpload_q", "value": 0.085, "time": 1717757836.385} +{"unit": "pv319_p", "value": 10.742, "time": 1717757836.26} +{"unit": "pv319_q", "value": 0.136, "time": 1717757836.26} +{"unit": "pv330_p", "value": 15.551, "time": 1717757835.403} +{"unit": "pv330_q", "value": -0.194, "time": 1717757835.403} +{"unit": "battery_p", "value": -0.221, "time": 1717757836.472} +{"unit": "battery_q", "value": 0.035, "time": 1717757836.472} +{"unit": "b2b_p", "value": -20.949, "time": 1717757836.432} +{"unit": "b2b_q", "value": 6.86, "time": 1717757836.432} +{"unit": "gaia_p", "value": 3.547, "time": 1717757836.453} +{"unit": "gaia_q", "value": -7.781, "time": 1717757836.453} +{"unit": "pcc_p", "value": 1.934, "time": 1717757837.474} +{"unit": "pcc_q", "value": 0.267, "time": 1717757837.474} +{"unit": "dumpload_p", "value": -9.99, "time": 1717757837.47} +{"unit": "dumpload_q", "value": 0.085, "time": 1717757837.47} +{"unit": "pv319_p", "value": 10.679, "time": 1717757837.594} +{"unit": "pv319_q", "value": 0.128, "time": 1717757837.594} +{"unit": "pv330_p", "value": 15.531, "time": 1717757837.466} +{"unit": "pv330_q", "value": -0.19, "time": 1717757837.466} +{"unit": "battery_p", "value": -0.221, "time": 1717757837.367} +{"unit": "battery_q", "value": 0.035, "time": 1717757837.367} +{"unit": "b2b_p", "value": -20.748, "time": 1717757837.098} +{"unit": "b2b_q", "value": 6.9, "time": 1717757837.098} +{"unit": "gaia_p", "value": 2.943, "time": 1717757837.652} +{"unit": "gaia_q", "value": -7.612, "time": 1717757837.652} +{"unit": "pcc_p", "value": -0.064, "time": 1717757838.749} +{"unit": "pcc_q", "value": 0.724, "time": 1717757838.749} +{"unit": "dumpload_p", "value": -10.008, "time": 1717757838.733} +{"unit": "dumpload_q", "value": 0.089, "time": 1717757838.733} +{"unit": "pv319_p", "value": 10.653, "time": 1717757838.726} +{"unit": "pv319_q", "value": 0.125, "time": 1717757838.726} +{"unit": "pv330_p", "value": 15.568, "time": 1717757838.578} +{"unit": "pv330_q", "value": -0.193, "time": 1717757838.835} +{"unit": "battery_p", "value": -0.221, "time": 1717757838.587} +{"unit": "battery_q", "value": 0.036, "time": 1717757838.587} +{"unit": "b2b_p", "value": -19.794, "time": 1717757838.826} +{"unit": "b2b_q", "value": 6.893, "time": 1717757838.826} +{"unit": "gaia_p", "value": 2.346, "time": 1717757838.654} +{"unit": "gaia_q", "value": -7.415, "time": 1717757838.654} +{"unit": "pcc_p", "value": -1.141, "time": 1717757839.824} +{"unit": "pcc_q", "value": 1.174, "time": 1717757839.824} +{"unit": "dumpload_p", "value": -10.02, "time": 1717757839.805} +{"unit": "dumpload_q", "value": 0.087, "time": 1717757839.805} +{"unit": "pv319_p", "value": 10.704, "time": 1717757839.926} +{"unit": "pv319_q", "value": 0.124, "time": 1717757839.926} +{"unit": "pv330_p", "value": 15.592, "time": 1717757839.383} +{"unit": "pv330_q", "value": -0.191, "time": 1717757839.383} +{"unit": "battery_p", "value": -0.222, "time": 1717757839.807} +{"unit": "battery_q", "value": 0.035, "time": 1717757839.807} +{"unit": "b2b_p", "value": -20.246, "time": 1717757839.904} +{"unit": "b2b_q", "value": 6.886, "time": 1717757839.904} +{"unit": "gaia_p", "value": 3.398, "time": 1717757839.695} +{"unit": "gaia_q", "value": -7.759, "time": 1717757839.695} +{"unit": "pcc_p", "value": -0.527, "time": 1717757840.909} +{"unit": "pcc_q", "value": 1.126, "time": 1717757840.909} +{"unit": "dumpload_p", "value": -10.007, "time": 1717757840.88} +{"unit": "dumpload_q", "value": 0.086, "time": 1717757841.055} +{"unit": "pv319_p", "value": 10.736, "time": 1717757841.042} +{"unit": "pv319_q", "value": 0.137, "time": 1717757841.042} +{"unit": "pv330_p", "value": 15.558, "time": 1717757840.898} +{"unit": "pv330_q", "value": -0.193, "time": 1717757840.898} +{"unit": "battery_p", "value": -0.22, "time": 1717757841.017} +{"unit": "battery_q", "value": 0.035, "time": 1717757841.017} +{"unit": "b2b_p", "value": -20.923, "time": 1717757841.107} +{"unit": "b2b_q", "value": 6.898, "time": 1717757841.107} +{"unit": "gaia_p", "value": 5.264, "time": 1717757841.121} +{"unit": "gaia_q", "value": -8.4, "time": 1717757841.121} +{"unit": "pcc_p", "value": -0.657, "time": 1717757842.169} +{"unit": "pcc_q", "value": 1.317, "time": 1717757842.169} +{"unit": "dumpload_p", "value": -10.006, "time": 1717757842.125} +{"unit": "dumpload_q", "value": 0.087, "time": 1717757842.125} +{"unit": "pv319_p", "value": 10.708, "time": 1717757841.841} +{"unit": "pv319_q", "value": 0.136, "time": 1717757841.841} +{"unit": "pv330_p", "value": 15.584, "time": 1717757842.23} +{"unit": "pv330_q", "value": -0.193, "time": 1717757842.23} +{"unit": "battery_p", "value": -0.219, "time": 1717757842.242} +{"unit": "battery_q", "value": 0.03, "time": 1717757842.242} +{"unit": "b2b_p", "value": -21.289, "time": 1717757842.263} +{"unit": "b2b_q", "value": 6.891, "time": 1717757842.263} +{"unit": "gaia_p", "value": 5.694, "time": 1717757842.254} +{"unit": "gaia_q", "value": -8.571, "time": 1717757842.254} +{"unit": "pcc_p", "value": -0.429, "time": 1717757843.249} +{"unit": "pcc_q", "value": 1.482, "time": 1717757843.249} +{"unit": "dumpload_p", "value": -10.007, "time": 1717757843.21} +{"unit": "dumpload_q", "value": 0.087, "time": 1717757843.21} +{"unit": "pv319_p", "value": 10.563, "time": 1717757843.329} +{"unit": "pv319_q", "value": 0.141, "time": 1717757843.329} +{"unit": "pv330_p", "value": 15.597, "time": 1717757843.208} +{"unit": "pv330_q", "value": -0.193, "time": 1717757843.208} +{"unit": "battery_p", "value": -0.22, "time": 1717757843.057} +{"unit": "battery_q", "value": 0.034, "time": 1717757843.057} +{"unit": "b2b_p", "value": -21.685, "time": 1717757843.164} +{"unit": "b2b_q", "value": 6.916, "time": 1717757843.164} +{"unit": "gaia_p", "value": 6.143, "time": 1717757843.253} +{"unit": "gaia_q", "value": -8.637, "time": 1717757843.465} +{"unit": "pcc_p", "value": -0.106, "time": 1717757844.324} +{"unit": "pcc_q", "value": 1.231, "time": 1717757844.499} +{"unit": "dumpload_p", "value": -9.999, "time": 1717757844.475} +{"unit": "dumpload_q", "value": 0.089, "time": 1717757844.475} +{"unit": "pv319_p", "value": 10.674, "time": 1717757844.419} +{"unit": "pv319_q", "value": 0.146, "time": 1717757844.419} +{"unit": "pv330_p", "value": 15.579, "time": 1717757844.102} +{"unit": "pv330_q", "value": -0.19, "time": 1717757844.102} +{"unit": "battery_p", "value": -0.221, "time": 1717757844.267} +{"unit": "battery_q", "value": 0.036, "time": 1717757844.267} +{"unit": "b2b_p", "value": -21.86, "time": 1717757844.475} +{"unit": "b2b_q", "value": 6.87, "time": 1717757844.475} +{"unit": "gaia_p", "value": 5.616, "time": 1717757844.522} +{"unit": "gaia_q", "value": -8.529, "time": 1717757844.522} +{"unit": "pcc_p", "value": -0.639, "time": 1717757845.584} +{"unit": "pcc_q", "value": 1.469, "time": 1717757845.584} +{"unit": "dumpload_p", "value": -10.002, "time": 1717757845.55} +{"unit": "dumpload_q", "value": 0.083, "time": 1717757845.55} +{"unit": "pv319_p", "value": 10.735, "time": 1717757845.62} +{"unit": "pv319_q", "value": 0.151, "time": 1717757845.62} +{"unit": "pv330_p", "value": 15.645, "time": 1717757845.031} +{"unit": "pv330_q", "value": -0.194, "time": 1717757845.031} +{"unit": "battery_p", "value": -0.221, "time": 1717757845.477} +{"unit": "battery_q", "value": 0.036, "time": 1717757845.477} +{"unit": "b2b_p", "value": -21.765, "time": 1717757845.649} +{"unit": "b2b_q", "value": 6.881, "time": 1717757845.649} +{"unit": "gaia_p", "value": 6.241, "time": 1717757845.652} +{"unit": "gaia_q", "value": -8.818, "time": 1717757845.652} +{"unit": "pcc_p", "value": -0.39, "time": 1717757846.824} +{"unit": "pcc_q", "value": 1.531, "time": 1717757846.824} +{"unit": "dumpload_p", "value": -10.005, "time": 1717757846.81} +{"unit": "dumpload_q", "value": 0.085, "time": 1717757846.81} +{"unit": "pv319_p", "value": 10.819, "time": 1717757846.756} +{"unit": "pv319_q", "value": 0.141, "time": 1717757846.756} +{"unit": "pv330_p", "value": 15.74, "time": 1717757846.869} +{"unit": "pv330_q", "value": -0.197, "time": 1717757846.869} +{"unit": "battery_p", "value": -0.221, "time": 1717757846.692} +{"unit": "battery_q", "value": 0.035, "time": 1717757846.692} +{"unit": "b2b_p", "value": -22.08, "time": 1717757846.724} +{"unit": "b2b_q", "value": 6.872, "time": 1717757846.724} +{"unit": "gaia_p", "value": 5.903, "time": 1717757846.769} +{"unit": "gaia_q", "value": -8.671, "time": 1717757846.769} +{"unit": "pcc_p", "value": -8.657, "time": 1717757847.929} +{"unit": "pcc_q", "value": 1.611, "time": 1717757847.929} +{"unit": "dumpload_p", "value": -1.585, "time": 1717757847.905} +{"unit": "dumpload_q", "value": -0.009, "time": 1717757847.905} +{"unit": "pv319_p", "value": 10.832, "time": 1717757847.909} +{"unit": "pv319_q", "value": 0.132, "time": 1717757847.909} +{"unit": "pv330_p", "value": 15.615, "time": 1717757847.939} +{"unit": "pv330_q", "value": -0.197, "time": 1717757847.939} +{"unit": "battery_p", "value": -0.221, "time": 1717757847.932} +{"unit": "battery_q", "value": 0.035, "time": 1717757847.932} +{"unit": "b2b_p", "value": -22.242, "time": 1717757847.501} +{"unit": "b2b_q", "value": 6.881, "time": 1717757847.501} +{"unit": "gaia_p", "value": 5.271, "time": 1717757848.053} +{"unit": "gaia_q", "value": -8.518, "time": 1717757848.053} +{"unit": "pcc_p", "value": -5.057, "time": 1717757849.179} +{"unit": "pcc_q", "value": 0.99, "time": 1717757849.179} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757849.195} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757849.195} +{"unit": "pv319_p", "value": 10.847, "time": 1717757849.182} +{"unit": "pv319_q", "value": 0.133, "time": 1717757849.182} +{"unit": "pv330_p", "value": 15.603, "time": 1717757849.156} +{"unit": "pv330_q", "value": -0.194, "time": 1717757849.156} +{"unit": "battery_p", "value": -0.22, "time": 1717757849.162} +{"unit": "battery_q", "value": 0.033, "time": 1717757849.162} +{"unit": "b2b_p", "value": -25.186, "time": 1717757849.328} +{"unit": "b2b_q", "value": 7.134, "time": 1717757849.328} +{"unit": "gaia_p", "value": 5.458, "time": 1717757849.271} +{"unit": "gaia_q", "value": -8.588, "time": 1717757849.271} +{"unit": "pcc_p", "value": -2.564, "time": 1717757850.244} +{"unit": "pcc_q", "value": 1.226, "time": 1717757850.244} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757850.265} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757850.265} +{"unit": "pv319_p", "value": 10.708, "time": 1717757850.328} +{"unit": "pv319_q", "value": 0.133, "time": 1717757850.328} +{"unit": "pv330_p", "value": 15.631, "time": 1717757850.301} +{"unit": "pv330_q", "value": -0.192, "time": 1717757850.301} +{"unit": "battery_p", "value": -0.219, "time": 1717757850.387} +{"unit": "battery_q", "value": 0.034, "time": 1717757850.387} +{"unit": "b2b_p", "value": -29.445, "time": 1717757850.414} +{"unit": "b2b_q", "value": 6.884, "time": 1717757850.414} +{"unit": "gaia_p", "value": 5.918, "time": 1717757850.382} +{"unit": "gaia_q", "value": -8.69, "time": 1717757850.382} +{"unit": "pcc_p", "value": -1.073, "time": 1717757851.499} +{"unit": "pcc_q", "value": 1.269, "time": 1717757851.499} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757851.51} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757851.51} +{"unit": "pv319_p", "value": 10.813, "time": 1717757851.349} +{"unit": "pv319_q", "value": 0.135, "time": 1717757851.349} +{"unit": "pv330_p", "value": 15.661, "time": 1717757851.432} +{"unit": "pv330_q", "value": -0.188, "time": 1717757851.432} +{"unit": "battery_p", "value": -0.219, "time": 1717757851.207} +{"unit": "battery_q", "value": 0.03, "time": 1717757851.207} +{"unit": "b2b_p", "value": -30.618, "time": 1717757851.476} +{"unit": "b2b_q", "value": 6.852, "time": 1717757851.476} +{"unit": "gaia_p", "value": 5.339, "time": 1717757851.569} +{"unit": "gaia_q", "value": -8.393, "time": 1717757851.569} +{"unit": "pcc_p", "value": 0.049, "time": 1717757852.579} +{"unit": "pcc_q", "value": 1.065, "time": 1717757852.579} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757852.585} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757852.585} +{"unit": "pv319_p", "value": 10.79, "time": 1717757852.421} +{"unit": "pv319_q", "value": 0.133, "time": 1717757852.421} +{"unit": "pv330_p", "value": 15.66, "time": 1717757852.547} +{"unit": "pv330_q", "value": -0.187, "time": 1717757852.547} +{"unit": "battery_p", "value": -0.219, "time": 1717757852.437} +{"unit": "battery_q", "value": 0.034, "time": 1717757852.437} +{"unit": "b2b_p", "value": -31.172, "time": 1717757852.516} +{"unit": "b2b_q", "value": 6.854, "time": 1717757852.516} +{"unit": "gaia_p", "value": 4.923, "time": 1717757852.654} +{"unit": "gaia_q", "value": -8.213, "time": 1717757852.654} +{"unit": "pcc_p", "value": 0.069, "time": 1717757853.674} +{"unit": "pcc_q", "value": 1.021, "time": 1717757853.674} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757853.665} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757853.665} +{"unit": "pv319_p", "value": 10.905, "time": 1717757853.727} +{"unit": "pv319_q", "value": 0.131, "time": 1717757853.727} +{"unit": "pv330_p", "value": 15.67, "time": 1717757853.58} +{"unit": "pv330_q", "value": -0.192, "time": 1717757853.58} +{"unit": "battery_p", "value": -0.222, "time": 1717757853.762} +{"unit": "battery_q", "value": 0.034, "time": 1717757853.762} +{"unit": "b2b_p", "value": -31.297, "time": 1717757852.888} +{"unit": "b2b_q", "value": 6.794, "time": 1717757852.888} +{"unit": "gaia_p", "value": 4.735, "time": 1717757853.854} +{"unit": "gaia_q", "value": -8.155, "time": 1717757853.854} +{"unit": "pcc_p", "value": 0.198, "time": 1717757854.764} +{"unit": "pcc_q", "value": 1.045, "time": 1717757854.764} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757854.76} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757854.76} +{"unit": "pv319_p", "value": 10.88, "time": 1717757854.78} +{"unit": "pv319_q", "value": 0.127, "time": 1717757854.78} +{"unit": "pv330_p", "value": 15.684, "time": 1717757854.545} +{"unit": "pv330_q", "value": -0.198, "time": 1717757854.545} +{"unit": "battery_p", "value": -0.222, "time": 1717757854.577} +{"unit": "battery_q", "value": 0.039, "time": 1717757854.577} +{"unit": "b2b_p", "value": -31.422, "time": 1717757854.769} +{"unit": "b2b_q", "value": 6.802, "time": 1717757854.769} +{"unit": "gaia_p", "value": 4.771, "time": 1717757854.871} +{"unit": "gaia_q", "value": -8.169, "time": 1717757854.871} +{"unit": "pcc_p", "value": -0.049, "time": 1717757855.864} +{"unit": "pcc_q", "value": 1.126, "time": 1717757855.864} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757856.01} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757856.01} +{"unit": "pv319_p", "value": 10.87, "time": 1717757855.933} +{"unit": "pv319_q", "value": 0.134, "time": 1717757855.933} +{"unit": "pv330_p", "value": 15.698, "time": 1717757855.945} +{"unit": "pv330_q", "value": -0.191, "time": 1717757855.945} +{"unit": "battery_p", "value": -0.221, "time": 1717757855.802} +{"unit": "battery_q", "value": 0.036, "time": 1717757855.802} +{"unit": "b2b_p", "value": -31.301, "time": 1717757855.814} +{"unit": "b2b_q", "value": 6.806, "time": 1717757855.814} +{"unit": "gaia_p", "value": 4.898, "time": 1717757856.015} +{"unit": "gaia_q", "value": -8.238, "time": 1717757856.015} +{"unit": "pcc_p", "value": 0.245, "time": 1717757857.119} +{"unit": "pcc_q", "value": 0.947, "time": 1717757857.119} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757857.07} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757857.07} +{"unit": "pv319_p", "value": 10.86, "time": 1717757857.147} +{"unit": "pv319_q", "value": 0.132, "time": 1717757857.147} +{"unit": "pv330_p", "value": 15.701, "time": 1717757857.142} +{"unit": "pv330_q", "value": -0.19, "time": 1717757857.142} +{"unit": "battery_p", "value": -0.221, "time": 1717757857.027} +{"unit": "battery_q", "value": 0.035, "time": 1717757857.027} +{"unit": "b2b_p", "value": -31.265, "time": 1717757857.173} +{"unit": "b2b_q", "value": 6.791, "time": 1717757857.173} +{"unit": "gaia_p", "value": 4.366, "time": 1717757857.263} +{"unit": "gaia_q", "value": -8.019, "time": 1717757857.263} +{"unit": "pcc_p", "value": -0.191, "time": 1717757858.184} +{"unit": "pcc_q", "value": 1.044, "time": 1717757858.184} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757858.155} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757858.335} +{"unit": "pv319_p", "value": 10.919, "time": 1717757858.22} +{"unit": "pv319_q", "value": 0.128, "time": 1717757858.22} +{"unit": "pv330_p", "value": 15.69, "time": 1717757858.338} +{"unit": "pv330_q", "value": -0.195, "time": 1717757858.338} +{"unit": "battery_p", "value": -0.219, "time": 1717757858.247} +{"unit": "battery_q", "value": 0.036, "time": 1717757858.247} +{"unit": "b2b_p", "value": -31.155, "time": 1717757858.388} +{"unit": "b2b_q", "value": 6.8, "time": 1717757858.388} +{"unit": "gaia_p", "value": 4.981, "time": 1717757858.332} +{"unit": "gaia_q", "value": -8.244, "time": 1717757858.332} +{"unit": "pcc_p", "value": -0.643, "time": 1717757859.264} +{"unit": "pcc_q", "value": 1.318, "time": 1717757859.439} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757859.415} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757859.415} +{"unit": "pv319_p", "value": 10.93, "time": 1717757859.429} +{"unit": "pv319_q", "value": 0.13, "time": 1717757859.429} +{"unit": "pv330_p", "value": 15.707, "time": 1717757858.98} +{"unit": "pv330_q", "value": -0.192, "time": 1717757858.98} +{"unit": "battery_p", "value": -0.222, "time": 1717757859.467} +{"unit": "battery_q", "value": 0.033, "time": 1717757859.467} +{"unit": "b2b_p", "value": -31.25, "time": 1717757859.569} +{"unit": "b2b_q", "value": 6.783, "time": 1717757859.569} +{"unit": "gaia_p", "value": 5.509, "time": 1717757859.513} +{"unit": "gaia_q", "value": -8.479, "time": 1717757859.513} +{"unit": "pcc_p", "value": -0.442, "time": 1717757860.52} +{"unit": "pcc_q", "value": 1.291, "time": 1717757860.52} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757860.5} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757860.5} +{"unit": "pv319_p", "value": 10.872, "time": 1717757860.533} +{"unit": "pv319_q", "value": 0.13, "time": 1717757860.533} +{"unit": "pv330_p", "value": 15.725, "time": 1717757860.009} +{"unit": "pv330_q", "value": -0.195, "time": 1717757860.676} +{"unit": "battery_p", "value": -0.222, "time": 1717757860.277} +{"unit": "battery_q", "value": 0.035, "time": 1717757860.687} +{"unit": "b2b_p", "value": -31.572, "time": 1717757860.523} +{"unit": "b2b_q", "value": 6.797, "time": 1717757860.523} +{"unit": "gaia_p", "value": 5.614, "time": 1717757860.569} +{"unit": "gaia_q", "value": -8.478, "time": 1717757860.569} +{"unit": "pcc_p", "value": -31.072, "time": 1717757861.585} +{"unit": "pcc_q", "value": 4.989, "time": 1717757861.585} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757861.76} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757861.76} +{"unit": "pv319_p", "value": 10.9, "time": 1717757861.728} +{"unit": "pv319_q", "value": 0.133, "time": 1717757861.728} +{"unit": "pv330_p", "value": 15.774, "time": 1717757861.703} +{"unit": "pv330_q", "value": -0.196, "time": 1717757861.703} +{"unit": "battery_p", "value": -0.22, "time": 1717757861.502} +{"unit": "battery_q", "value": 0.035, "time": 1717757861.502} +{"unit": "b2b_p", "value": -1.19, "time": 1717757861.62} +{"unit": "b2b_q", "value": 3.361, "time": 1717757861.62} +{"unit": "gaia_p", "value": 5.925, "time": 1717757861.769} +{"unit": "gaia_q", "value": -9.106, "time": 1717757861.92} +{"unit": "pcc_p", "value": -30.96, "time": 1717757862.84} +{"unit": "pcc_q", "value": 5.077, "time": 1717757862.84} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757862.825} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757862.825} +{"unit": "pv319_p", "value": 10.688, "time": 1717757862.82} +{"unit": "pv319_q", "value": 0.134, "time": 1717757862.82} +{"unit": "pv330_p", "value": 15.794, "time": 1717757862.792} +{"unit": "pv330_q", "value": -0.191, "time": 1717757862.792} +{"unit": "battery_p", "value": -0.22, "time": 1717757862.717} +{"unit": "battery_q", "value": 0.035, "time": 1717757862.717} +{"unit": "b2b_p", "value": -0.909, "time": 1717757862.687} +{"unit": "b2b_q", "value": 3.519, "time": 1717757862.687} +{"unit": "gaia_p", "value": 5.141, "time": 1717757863.054} +{"unit": "gaia_q", "value": -8.757, "time": 1717757863.054} +{"unit": "pcc_p", "value": -27.822, "time": 1717757863.945} +{"unit": "pcc_q", "value": 4.876, "time": 1717757863.945} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757864.085} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757864.085} +{"unit": "pv319_p", "value": 8.072, "time": 1717757863.982} +{"unit": "pv319_q", "value": 0.091, "time": 1717757863.982} +{"unit": "pv330_p", "value": 15.805, "time": 1717757863.993} +{"unit": "pv330_q", "value": -0.191, "time": 1717757863.993} +{"unit": "battery_p", "value": -0.221, "time": 1717757863.927} +{"unit": "battery_q", "value": 0.033, "time": 1717757863.927} +{"unit": "b2b_p", "value": -0.908, "time": 1717757864.086} +{"unit": "b2b_q", "value": 3.5, "time": 1717757864.086} +{"unit": "gaia_p", "value": 4.837, "time": 1717757864.172} +{"unit": "gaia_q", "value": -8.601, "time": 1717757864.172} +{"unit": "pcc_p", "value": -26.289, "time": 1717757865.03} +{"unit": "pcc_q", "value": 4.827, "time": 1717757865.03} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757865.205} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757865.205} +{"unit": "pv319_p", "value": 6.423, "time": 1717757864.994} +{"unit": "pv319_q", "value": 0.072, "time": 1717757865.24} +{"unit": "pv330_p", "value": 15.814, "time": 1717757864.267} +{"unit": "pv330_q", "value": -0.198, "time": 1717757864.267} +{"unit": "battery_p", "value": -0.219, "time": 1717757865.147} +{"unit": "battery_q", "value": 0.035, "time": 1717757865.147} +{"unit": "b2b_p", "value": -0.907, "time": 1717757865.239} +{"unit": "b2b_q", "value": 3.499, "time": 1717757865.239} +{"unit": "gaia_p", "value": 4.763, "time": 1717757864.956} +{"unit": "gaia_q", "value": -8.314, "time": 1717757865.344} +{"unit": "pcc_p", "value": -28.487, "time": 1717757866.275} +{"unit": "pcc_q", "value": 4.621, "time": 1717757866.275} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757866.27} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757866.27} +{"unit": "pv319_p", "value": 8.812, "time": 1717757866.241} +{"unit": "pv319_q", "value": 0.103, "time": 1717757866.241} +{"unit": "pv330_p", "value": 15.856, "time": 1717757866.279} +{"unit": "pv330_q", "value": -0.199, "time": 1717757866.279} +{"unit": "battery_p", "value": -0.221, "time": 1717757866.357} +{"unit": "battery_q", "value": 0.034, "time": 1717757866.357} +{"unit": "b2b_p", "value": -0.909, "time": 1717757866.315} +{"unit": "b2b_q", "value": 3.51, "time": 1717757866.315} +{"unit": "gaia_p", "value": 4.377, "time": 1717757866.384} +{"unit": "gaia_q", "value": -8.455, "time": 1717757866.384} +{"unit": "pcc_p", "value": -29.948, "time": 1717757867.36} +{"unit": "pcc_q", "value": 4.564, "time": 1717757867.36} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757867.33} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757867.33} +{"unit": "pv319_p", "value": 10.757, "time": 1717757867.318} +{"unit": "pv319_q", "value": 0.135, "time": 1717757867.318} +{"unit": "pv330_p", "value": 15.823, "time": 1717757867.339} +{"unit": "pv330_q", "value": -0.194, "time": 1717757867.541} +{"unit": "battery_p", "value": -0.221, "time": 1717757867.172} +{"unit": "battery_q", "value": 0.035, "time": 1717757867.172} +{"unit": "b2b_p", "value": -0.91, "time": 1717757867.536} +{"unit": "b2b_q", "value": 3.527, "time": 1717757867.536} +{"unit": "gaia_p", "value": 4.095, "time": 1717757867.568} +{"unit": "gaia_q", "value": -8.392, "time": 1717757867.568} +{"unit": "pcc_p", "value": -29.915, "time": 1717757868.615} +{"unit": "pcc_q", "value": 4.497, "time": 1717757868.615} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757868.595} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757868.595} +{"unit": "pv319_p", "value": 10.984, "time": 1717757868.581} +{"unit": "pv319_q", "value": 0.134, "time": 1717757868.581} +{"unit": "pv330_p", "value": 15.868, "time": 1717757868.38} +{"unit": "pv330_q", "value": -0.2, "time": 1717757868.738} +{"unit": "battery_p", "value": -0.221, "time": 1717757868.412} +{"unit": "battery_q", "value": 0.036, "time": 1717757868.412} +{"unit": "b2b_p", "value": -0.91, "time": 1717757867.994} +{"unit": "b2b_q", "value": 3.523, "time": 1717757867.994} +{"unit": "gaia_p", "value": 3.838, "time": 1717757868.77} +{"unit": "gaia_q", "value": -8.304, "time": 1717757868.77} +{"unit": "pcc_p", "value": -29.671, "time": 1717757869.705} +{"unit": "pcc_q", "value": 4.368, "time": 1717757869.705} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757869.84} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757869.84} +{"unit": "pv319_p", "value": 11.002, "time": 1717757869.82} +{"unit": "pv319_q", "value": 0.131, "time": 1717757869.82} +{"unit": "pv330_p", "value": 15.908, "time": 1717757869.56} +{"unit": "pv330_q", "value": -0.192, "time": 1717757869.56} +{"unit": "battery_p", "value": -0.219, "time": 1717757869.677} +{"unit": "battery_q", "value": 0.036, "time": 1717757869.677} +{"unit": "b2b_p", "value": -0.908, "time": 1717757869.846} +{"unit": "b2b_q", "value": 3.505, "time": 1717757869.846} +{"unit": "gaia_p", "value": 3.549, "time": 1717757869.87} +{"unit": "gaia_q", "value": -8.192, "time": 1717757869.87} +{"unit": "pcc_p", "value": -29.287, "time": 1717757870.795} +{"unit": "pcc_q", "value": 4.26, "time": 1717757870.795} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757870.91} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757870.91} +{"unit": "pv319_p", "value": 11.022, "time": 1717757870.985} +{"unit": "pv319_q", "value": 0.129, "time": 1717757870.985} +{"unit": "pv330_p", "value": 15.933, "time": 1717757870.88} +{"unit": "pv330_q", "value": -0.202, "time": 1717757870.88} +{"unit": "battery_p", "value": -0.22, "time": 1717757870.892} +{"unit": "battery_q", "value": 0.036, "time": 1717757870.892} +{"unit": "b2b_p", "value": -0.908, "time": 1717757870.97} +{"unit": "b2b_q", "value": 3.507, "time": 1717757870.97} +{"unit": "gaia_p", "value": 3.909, "time": 1717757871.053} +{"unit": "gaia_q", "value": -8.314, "time": 1717757871.053} +{"unit": "pcc_p", "value": -29.725, "time": 1717757872.045} +{"unit": "pcc_q", "value": 4.375, "time": 1717757872.045} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757872.0} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757872.0} +{"unit": "pv319_p", "value": 11.022, "time": 1717757871.733} +{"unit": "pv319_q", "value": 0.127, "time": 1717757872.136} +{"unit": "pv330_p", "value": 15.978, "time": 1717757871.992} +{"unit": "pv330_q", "value": -0.203, "time": 1717757871.992} +{"unit": "battery_p", "value": -0.22, "time": 1717757872.102} +{"unit": "battery_q", "value": 0.036, "time": 1717757872.102} +{"unit": "b2b_p", "value": -0.908, "time": 1717757872.185} +{"unit": "b2b_q", "value": 3.514, "time": 1717757872.185} +{"unit": "gaia_p", "value": 3.595, "time": 1717757872.173} +{"unit": "gaia_q", "value": -8.206, "time": 1717757872.173} +{"unit": "pcc_p", "value": -29.546, "time": 1717757873.125} +{"unit": "pcc_q", "value": 4.299, "time": 1717757873.125} +{"unit": "dumpload_p", "value": 0.0, "time": 1717757873.245} +{"unit": "dumpload_q", "value": 0.0, "time": 1717757873.245} +{"unit": "pv319_p", "value": 11.038, "time": 1717757873.22} +{"unit": "pv319_q", "value": 0.127, "time": 1717757873.22} +{"unit": "pv330_p", "value": 16.0, "time": 1717757873.277} +{"unit": "pv330_q", "value": -0.203, "time": 1717757873.277} +{"unit": "battery_p", "value": -0.221, "time": 1717757872.917} +{"unit": "battery_q", "value": 0.034, "time": 1717757872.917} +{"unit": "b2b_p", "value": -0.908, "time": 1717757873.298} +{"unit": "b2b_q", "value": 3.501, "time": 1717757873.298} +{"unit": "gaia_p", "value": 3.593, "time": 1717757873.269} +{"unit": "gaia_q", "value": -8.215, "time": 1717757873.269} diff --git a/data/setpoints/dummy_file b/data/setpoints/dummy_file new file mode 100644 index 0000000..c81b66a --- /dev/null +++ b/data/setpoints/dummy_file @@ -0,0 +1 @@ +Makes folder visible for git. \ No newline at end of file diff --git a/demo_datalogger.py b/demo_datalogger.py new file mode 100644 index 0000000..0d1e1e0 --- /dev/null +++ b/demo_datalogger.py @@ -0,0 +1,47 @@ +import syslab +from json import dump +from time import sleep, time + +# Defines location and name of the measurements file +LOG_FILE = f'data/measurements/measurements_{time():.00f}.json' +print(f"Logging to file {LOG_FILE}") + +# Set up a connection to the switchboards +sb3192 = syslab.SwitchBoard('319-2') +sb3193 = syslab.SwitchBoard('319-3') +sb33012 = syslab.SwitchBoard("330-12") +sb1172 = syslab.SwitchBoard('117-2') + +# Convenience function to +def take_measurements(): + measurements = { + "pcc_p": sb3192.getActivePower('Grid'), + "pcc_q": sb3192.getReactivePower('Grid'), + "pv319_p": sb3192.getActivePower('PV'), + "pv319_q": sb3192.getReactivePower('PV'), + "dumpload_p": sb3192.getActivePower('Dumpload'), + "dumpload_q": sb3192.getReactivePower('Dumpload'), + "gaia_p": sb33012.getActivePower('Gaia'), + "gaia_q": sb33012.getReactivePower('Gaia'), + "pv330_p": sb33012.getActivePower('PV_1'), + "pv330_q": sb33012.getReactivePower('PV_1'), + "b2b_p": sb3193.getActivePower('ABB_Sec'), + "b2b_q": sb3193.getReactivePower('ABB_Sec'), + "battery_p": sb1172.getActivePower('Battery'), + "battery_q": sb1172.getReactivePower('Battery'), + } + return [{'unit': k, 'value': meas.value, 'time': meas.timestampMicros/1e6} for k, meas in measurements.items()] + + +while True: + measurement = take_measurements() + + # Open the output file in "append" mode which adds lines to the end + with open(LOG_FILE, 'a') as file: + for m in measurement: + # Convert the dictionary m to a json string and put it + # in the file. + dump(m, file) + # Write a newline for each measurement to make loading easier + file.write('\n') + sleep(1) diff --git a/demo_plotter.py b/demo_plotter.py new file mode 100644 index 0000000..372f2fb --- /dev/null +++ b/demo_plotter.py @@ -0,0 +1,139 @@ +import pandas as pd +import json +import matplotlib.pyplot as plt +import os +from datetime import timedelta + +## Read the measurements data file ## +DATA_MEAS_DIR = 'data\measurements' +# Always plot latest datafile - replace [-1] with another index if you want to plot a specific file. +MEAS_LOG_FILE = sorted(os.listdir(DATA_MEAS_DIR))[-1] + +# Store each dictionary of the measurements json in a list +with open(os.path.join(DATA_MEAS_DIR, MEAS_LOG_FILE)) as f: + meas_data = [json.loads(line) for line in f] + +# Use setpoint logger (only necessary for part two of the exercise "collecting fresh data") +use_setpoint_log = False + + +## Read the setpoints data file ## +if use_setpoint_log: + DATA_SP_DIR = 'data\setpoints' + # Always plot latest datafile + SP_LOG_FILE = sorted(os.listdir(DATA_SP_DIR))[-1] + + # Store each dictionary of the setpoints json in a list + with open(os.path.join(DATA_SP_DIR, SP_LOG_FILE)) as f: + sp_data = [json.loads(line) for line in f] + + # Merge measurements and setpoints in one list + data = meas_data + sp_data + +else: + data = meas_data + + +# Construct a dataframe and pivot it to obtain a dataframe with a column per unit, and a row per timestamp. +df = pd.DataFrame.from_records(data) +df_pivot = df.pivot_table(values='value', columns='unit', index='time') + + +# Plot the data. Note, that the data will mostly not be plotted with lines. +plt.ion() # Turn interactive mode on +plt.figure() +ax1 = plt.subplot(211) # Make two separate figures +ax2 = plt.subplot(212) +df_pivot[[c for c in df_pivot.columns if "_p" in c]].plot(marker='.', ax=ax1, linewidth=3) +df_pivot[[c for c in df_pivot.columns if "_q" in c]].plot(marker='.', ax=ax2, linewidth=3) +plt.show(block=True) + + +## TODO Q1: Your code here + + + + + +## TODO Q2: +# Convert time column (index) of df_pivot to datetime +# TODO Your code here +# Hint1: You can use pandas to_numeric() to prepare the index for pandas to_datetime function +# Hint2: Remember to define the unit within pandas to_datetime function + +# Resample the data +# TODO Your code here + + +# Interpolate the measurements +# TODO Your code here +# Hint: For part two of the exercise ("collecting fresh data") the nan rows after a setpoint +# in the recorded step function should be filled with the value of the setpoint until the row of the next setpoint is reached +# You can use the df.fillna(method="ffill") function for that purpose. However, the measurements should still be interpolated! + + + +# Plot the resampled data +# TODO Your code here + + + +## TODO Q3: Your code here + + + + +## TODO Q4: Your code here + + + +## Part two: "Collecting fresh data" + +# Hint 1: You can build up on the "read_and_plot_data.py" from day 2 +# Hint 2: Yoy may want to store your response metric functions from day 2 in the "util.py" and import all of them with +# "from util import *" + +if use_setpoint_log: + + # Add a column to df_pivot containing the reference/target signal + # TODO your code here + + # Loop over all steps and extract T_1, T_2 and the step size + results = {} + + for idx in range(0, len(sp_data)-1): + label = f"Step_{sp_data[idx]['value']}kW" + + # Extract T_1 and T_2 from the setpoint JSON + # TODO your code here + + + # Change timestamp format + T_1 = pd.to_datetime(pd.to_numeric(T_1), unit="s").round("0.1S") + T_2 = pd.to_datetime(pd.to_numeric(T_2), unit="s").round("0.1S") + + # To ensure we are not considering values of the next load step + T_2 = T_2 - timedelta(seconds=0.2) + + + # define measured output y and target setpoint r + # TODO your code here + + # Derive step direction from the setpoint data + if ...: # TODO your code here + Positive_step = True + else: + Positive_step = False + + # Collect response metrics results + results[label] = { + # TODO your code here + } + + pd.DataFrame.from_dict(results).plot(kind='bar') + plt.title("Metrics") + plt.tight_layout() + plt.savefig('data/test_metrics'+MEAS_LOG_FILE[-10:]+'.png') + plt.show(block=True) + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0fd83a0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +beautifulsoup4==4.12.3 +certifi==2024.6.2 +charset-normalizer==3.3.2 +greenlet==3.0.3 +idna==3.7 +msgpack==1.0.8 +pynvim==0.5.0 +requests==2.32.3 +soupsieve==2.5 +# Editable Git install with no remote (syslab==0.3.0) +-e /home/daniel/Dropbox/DTU/F24/46045/syslab/syslab-python +urllib3==2.2.1 diff --git a/syslab-python/.gitignore b/syslab-python/.gitignore new file mode 100644 index 0000000..1edbec7 --- /dev/null +++ b/syslab-python/.gitignore @@ -0,0 +1,109 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +\.DS_Store + +# Pycharm +.idea/ \ No newline at end of file diff --git a/syslab-python/README.md b/syslab-python/README.md new file mode 100644 index 0000000..371e682 --- /dev/null +++ b/syslab-python/README.md @@ -0,0 +1,27 @@ +# SYSLAB Python Interface + +This project provides a Python interface to several SYSLAB components. + +## Required software + +- Python (>=3.7) +- python-requests (>=2.18) +- python-bs4 (>= 4.7) + +## Installation + +To install the package in your local path, run + +```shell + python setup.py install +``` + +Alternatively, copy the `syslab` folder into your project directory. + +# Contributors + +- Anders Thavlov: Initial implementation +- Oliver Gehrke +- Daniel Esteban Morales Bondy +- Tue Vissing Jensen +- Federico Zarelli diff --git a/syslab-python/example.py b/syslab-python/example.py new file mode 100644 index 0000000..ea97246 --- /dev/null +++ b/syslab-python/example.py @@ -0,0 +1,11 @@ +from syslab import SwitchBoard + +name = '319-2' +SB_connection = SwitchBoard(name) +print("Let's look at what is going on in the switchboard {}.".format(name)) + +for bay in range(SB_connection.getNumBays()): + print(SB_connection.getBayName(bay),' : ',SB_connection.getActivePower(bay)) + + + diff --git a/syslab-python/notes.md b/syslab-python/notes.md new file mode 100644 index 0000000..7065476 --- /dev/null +++ b/syslab-python/notes.md @@ -0,0 +1,99 @@ +# Notes on SOAP implementation + + - Units should reflect SOAP methods onto their own namespace (1) + - CompositeMeasurement should convert to/from SOAP + - Type checking via client.get\_type('ns0:compositeMeasurement').elements + - + +# Notes about this module - to be discussed + +SYSLAB\_Unit.py has a whole bunch of static methods - should these be split into a util library instead? +Generally, many places where static methods are used for things that should perhaps just be functions... + +The following files are empty: + - BattOpMode.py + - FlowBatteryState.py + - GaiaWindTurbine.py + + +To check the methods available, use, e.g.: +http://syslab-33.syslab.dk:8080/typebased_WebService_HeatSubstation/HeatSwitchboardWebService/716-h1/resourceNames + +To figure out this URL, look at software.xml for the corresponding machine, and use this template: + +http://(machineName).syslab.dk:(port)/(interfaceName)/(shortServerName)/(unitname)/resourceNames + +| field | corresponds to | notes | +| ----- | -------------- | ----- | +| machineName | N/A | Look this up on the wiki | +| port | N/A | Dynamically allocated, starting at 8080 - good luck! | +| interfaceName | typeBasedWebService, interfaceName | | +| shortServerName | typeBasedWebService, serverClass | Remove the "Server" at the end | +| unitname | dataLogger, unit | Also defined as "name" in hardware.xml | + + +------------------------- + + +SYSLAB COMMON +Broadcast event logger: + +Transcode to python: +https://git.elektro.dtu.dk/syslab/syslab-common/-/blob/master/src/main/java/risoe/syslab/comm/broadcast/BroadcastLogSender.java + +broadcast log sender +:: transcode the "send()" method to python. It byte-encodes the message for UDP. +Java: +send(String origin, byte[] origIP, long timestamp, int ploadType, String message, + int level, int flags, String[] tags) +------------ +Python: +----------. +def send(origin, origIP, timestamp, ploadType, message, level, flags, tags): + ploadbytes = message[:min(1024, len(message))].encode() + origbytes = origin[:min(32, len(origin))].encode() + tagbytes = tagsToBytes(tags, 256) + pktlen = 2 + 2 + 1 + len(origbytes) + 4 + 2 + 2 + 8 + 1 + 2 + len(ploadbytes) + len(tagbytes) + buf = bytearray(pktlen) + buf[0] = BroadcastLogConstants.BROADCASTLOG_PKTID >> 8 + buf[1] = BroadcastLogConstants.BROADCASTLOG_PKTID & 0xff + buf[2] = (pktlen >> 8) & 0xff + buf[3] = pktlen & 0xff + buf[4] = len(origbytes) + buf[5:5+len(origbytes)] = origbytes + writePtr = 5 + len(origbytes) + buf[writePtr:writePtr+4] = origIP + writePtr += 4 + buf[writePtr] = (level >> 8) & 0xff + buf[writePtr+1] = level & 0xff + buf[writePtr+2] = (flags >> 8) & 0xff + buf[writePtr+3] = flags & 0xff + for i in range(8): + buf[writePtr+7-i] = timestamp & 0xff + timestamp >>= 8 + writePtr += 8 + buf[writePtr] = ploadType & 0xff + buf[writePtr+1] = (len(ploadbytes) >> 8) & 0xff + buf[writePtr+2] = len(ploadbytes) & 0xff + buf[writePtr+3:writePtr+3+len(ploadbytes)] = ploadbytes + writePtr += len(ploadbytes) + buf[writePtr:writePtr+len(tagbytes)] = tagbytes + pack = n + pack = DatagramPacket(buf, len(buf), InetAddress.getByName("localhost"), 4445) + sock.send(pack) + + +------------ + +broadcast log receiver ++ needs a logger +listener ist interface for receiver + +gui wall (SYSLAB Userspacce) +broadcast log displet +https://git.elektro.dtu.dk/syslab/syslab-userspace/-/blob/master/src/main/java/risoe/syslab/gui/wall/displets/BroadcastLogDisplet.java +... maybe extend with simple log file writer. + + + + diff --git a/syslab-python/requirements.txt b/syslab-python/requirements.txt new file mode 100644 index 0000000..25cc339 --- /dev/null +++ b/syslab-python/requirements.txt @@ -0,0 +1,2 @@ +requests >= 2.18 +beautifulsoup4 >= 3.7 \ No newline at end of file diff --git a/syslab-python/setup.cfg b/syslab-python/setup.cfg new file mode 100644 index 0000000..43f9acc --- /dev/null +++ b/syslab-python/setup.cfg @@ -0,0 +1,10 @@ +[flake8] +ignore = +max-line-length = 79 +max-complexity = 11 + +[pytest] +addopts = --doctest-glob="*.rst" + +[wheel] +universal = True diff --git a/syslab-python/setup.py b/syslab-python/setup.py new file mode 100644 index 0000000..8c13382 --- /dev/null +++ b/syslab-python/setup.py @@ -0,0 +1,36 @@ +from setuptools import setup, find_packages + + +setup( + name='syslab', + version='0.3.0', + author='Tue Vissing Jensen', + author_email='tvjens at elektro.dtu.dk', + description=('SYSLAB webservice client library.'), + long_description=(''), + url='https://www.syslab.dk', + install_requires=[ + 'requests>=2.18', + 'beautifulsoup4>=3.7', + ], + packages=find_packages(exclude=['tests*']), + include_package_data=True, + entry_points={ + 'console_scripts': [ + ], + }, + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Intended Audience :: Science/Research', + 'License :: Other/Proprietary License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Topic :: Scientific/Engineering', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], +) diff --git a/syslab-python/syslab/__init__.py b/syslab-python/syslab/__init__.py new file mode 100644 index 0000000..c7b2d33 --- /dev/null +++ b/syslab-python/syslab/__init__.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +""" +SYSLAB library +~~~~~~~~~~~~~~~~~~~~~ + +SYSLAB is a Python 3 library for control and monitoring of units in SYSLAB. Usage of this library requires the running +computer to be located in the SYSLAB network, either physically or virtually, ie. using a VPN connection. + + +""" + +__title__ = 'syslab' +__version__ = '0.3.0' +__author__ = 'Anders Thavlov, Daniel Esteban Morales Bondy, Tue Vissing Jensen' + +__maintainer__ = 'Tue Vissing Jensen' +__license__ = '' +__copyright__ = 'DTU' + +# Raise error if we are not on Python 3 +import sys +if not (sys.version_info.major == 3 and sys.version_info.minor >= 5): + raise RuntimeError("Must be using at least Python 3.5") + +# DataTypes +from .core.datatypes import CompositeMeasurement +from .core.datatypes import CompositeBoolean + +# Physical units +from .physical import \ + SwitchBoard, \ + Dumpload, \ + Photovoltaics, \ + Battery, \ + HeatSwitchBoard, \ + WindTurbine, \ + DieselGenerator, \ + B2BConverter, \ + MeteoMast, \ + EVSE + +# Logging utilities +#from .comm.LogUtils import e \ No newline at end of file diff --git a/syslab-python/syslab/comm/LogUtils.py b/syslab-python/syslab/comm/LogUtils.py new file mode 100644 index 0000000..b755d43 --- /dev/null +++ b/syslab-python/syslab/comm/LogUtils.py @@ -0,0 +1,59 @@ +############################################################### +# SYSLAB remote logging utilities v0.9 +# collect and record log events +# +# Author: Kai Heussen +# Date: 2023/06/18 +############################################################### +import logging +import logging.handlers +import time +import syslab.config as config + +event_log_formats = '%(asctime)s - %(name)s - %(levelname)s - %(filename)s - %(funcName)s - %(message)s' +simple_log_formats = '%(asctime)s - %(name)s - %(message)s' + +def setup_event_logger(loggername=config.EVLOG_NAME, host='localhost', port=config.REMOTE_PORT_EV, level=logging.INFO, formats=simple_log_formats): # UDP + handler1 = logging.handlers.DatagramHandler(config.REMOTE_IP_EV, config.REMOTE_PORT_EV) + handler2 = logging.handlers.DatagramHandler(host, port) + handler3 = logging.StreamHandler() + formatter = logging.Formatter(formats) + handler3.setFormatter(formatter) + loggr = logging.getLogger(loggername) + loggr.setLevel(level) + loggr.addHandler(handler1) + loggr.addHandler(handler2) + loggr.addHandler(handler3) + return loggr + + +def setup_local_logger(loggername=config.EVLOG_NAME, host='localhost', port=config.REMOTE_PORT_EV, level=logging.INFO, formats=simple_log_formats): # UDP + handler = logging.handlers.DatagramHandler(host, port) + formatter = logging.Formatter(formats) + handler.setFormatter(formatter) + loggr = logging.getLogger(loggername) + loggr.setLevel(level) + loggr.addHandler(handler) + return loggr + + +def setup_udp_logger(loggername=config.SPLOG_NAME, host=config.REMOTE_IP_SP, port=config.REMOTE_PORT_SP, level=logging.INFO, formats=config.LOG_FORMATS): # UDP + handler = logging.handlers.DatagramHandler(host, port) + formatter = logging.Formatter(formats) + handler.setFormatter(formatter) + loggr = logging.getLogger(loggername) + loggr.setLevel(level) + loggr.addHandler(handler) + return loggr + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)-8s %(message)s') + + # logging.getLogger().addHandler(logging.handlers.DatagramHandler('10.42.242.3', 51010)) + logger = setup_local_logger() + while True: + logger.debug("This shouldn't show up") + logger.info("This should show up") + time.sleep(3) diff --git a/syslab-python/syslab/comm/__init__.py b/syslab-python/syslab/comm/__init__.py new file mode 100644 index 0000000..0c228b5 --- /dev/null +++ b/syslab-python/syslab/comm/__init__.py @@ -0,0 +1,2 @@ +#from BroadcastLogger import BroadcastLogSender +#from LogUtils import setup_udp_logger \ No newline at end of file diff --git a/syslab-python/syslab/comm/logrec.py b/syslab-python/syslab/comm/logrec.py new file mode 100644 index 0000000..6f35820 --- /dev/null +++ b/syslab-python/syslab/comm/logrec.py @@ -0,0 +1,80 @@ +############################################################### +# SYSLAB remote logger v0.9 +# utility to display and record log events +# usage: move bash to target log directory, then call +# python /syslab/comm/logrec.py +# +# Author: Kai Heussen +# Date: 2023/06/18 +############################################################### +import pickle +import logging +import socket +from syslab import config +# import sys +from time import time +# from json import dump + +BASIC = True +FILE = True +FILE_RAW = False # TODO: write a JSON formatter - based on https://stackoverflow.com/questions/50144628/python-logging-into-file-as-a-dictionary-or-json + +logtype = "EV" +time=time() +logfile =f"syslab_{logtype}_log_{time:.00f}.txt" +logfile_raw =f"syslab_{logtype}_log_{time:.00f}.raw" # not yet json + +DEFAULT_PORT = config.REMOTE_PORT_SP if logtype == "SP" else config.REMOTE_PORT_EV + +port = DEFAULT_PORT + +#simple_formats = '%(asctime)s - %(name)s - %(message)s' +simple_formats = '%(asctime)s - %(module)s - %(message)s' +#formats='foo: %(levelname)s - %(module)s.%(funcName)s - %(message)s' +event_log_formatter = '%(asctime)s - %(name)s - %(levelname)s - %(filename)s - %(module)s - %(funcName)s - %(message)s' + +formats = simple_formats +formatter = logging.Formatter(formats) + +if BASIC: + logging.basicConfig(format=formats) +else: + handler = logging.StreamHandler() + handler.setLevel(logging.DEBUG) + handler.setFormatter(formatter) + logging.getLogger().addHandler(handler) + +if FILE: + handler = logging.FileHandler(logfile) + handler.setLevel(logging.INFO) + handler.setFormatter(formatter) + logging.getLogger().addHandler(handler) + + +logger = logging.getLogger() + +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(('', port)) + +print(f'Listening for log records on port {port}...') +if FILE: + print(f'recording log entries in file: {logfile}') +if FILE_RAW: + print(f'recording raw log entries in file: {logfile_raw}') + +try: + while True: + d, _ = s.recvfrom(1024) + msg = pickle.loads(d[4:]) + logrec = logging.makeLogRecord(msg) + logger.handle(logrec) + if FILE_RAW: + with open(logfile_raw, 'a') as file: + # dump(logrec, file) # requires a JSON formatter + file.write(f'{logrec.__str__() }\n') # Write a newline for each measurement to make loading easier + #print(log) +finally: + s.close() + if FILE_RAW: + file.close() + diff --git a/syslab-python/syslab/config.py b/syslab-python/syslab/config.py new file mode 100644 index 0000000..3f2c2db --- /dev/null +++ b/syslab-python/syslab/config.py @@ -0,0 +1,19 @@ +DEBUG = False +SPLOG_NAME = 'SetPointLogger' +EVLOG_NAME = 'EventLog' +REMOTE_LOGGER = True +REMOTE_IP_SP = '10.42.242.3' # UI Machine 03 +REMOTE_IP_EV = '10.42.242.3' # UI Machine 03 +REMOTE_PORT_SP = 51010 +REMOTE_PORT_EV = 51020 +LOCAL_IP = 'localhost' +LOCAL_PORT_SP = 51010 +LOCAL_PORT_EV = 51020 +LOCAL_LOGGER = False +LOG_FORMATS = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # '%(asctime)s %(levelname)-8s %(message)s' +TEST_LOCAL = False +if TEST_LOCAL: + REMOTE_PORT_SP = LOCAL_PORT_SP + REMOTE_PORT_EV = LOCAL_PORT_EV + REMOTE_IP_SP = LOCAL_IP + REMOTE_IP_EV = LOCAL_IP diff --git a/syslab-python/syslab/core/SyslabUnit.py b/syslab-python/syslab/core/SyslabUnit.py new file mode 100644 index 0000000..5d7d832 --- /dev/null +++ b/syslab-python/syslab/core/SyslabUnit.py @@ -0,0 +1,306 @@ +from .datatypes import CompositeBoolean, BattOpMode, CompositeMeasurement +from .datatypes import FlowBatteryState, ConvState, ConvOpMode +from .datatypes import HeatCirculationPumpMode, HeatCirculationPumpState +import syslab.config as config + + +class SyslabUnit: + __HIDDEN_RESOURCES = { + 'authenticate': ('boolean', 'String', 'String'), + 'isAuthenticated': ('boolean',), + 'checkConnection': ('boolean',), + 'logout': ('void',)} + + def __init__(self, baseurl, units=None, which=None, host=None, port=None, unit_name=None, unit_type=""): + """ + Initialize a proxy to the given unit. + + :param baseurl: Example: 'http://{host}:{port}/typebased_WebService_Battery/VRBBatteryWebService/{unit_name}/' + :param units: dictionary of units to be loaded in the format {'which': ('host', 'port', 'unit_name')} + :param which: string indicating which unit we pick from _units_ + :param host: (optional) override host + :param port: (optional) override port + :param unit_name: (optional) override unit_name + :param unit_type: (optional) Used to indicate the type of the unit. + """ + + if units is None: + units = {} + + if which is not None: + if which not in units: + raise TypeError( + 'The {unit_type} should be one of: "{list_of_units}"'.format( + unit_type=unit_type, + list_of_units='", "'.join(units.keys()) + )) + else: + host, port, unit_name = units.get(which) + if host is not None: + host = host + if port is not None: + port = port + + assert host is not None, "When assigning custom port, host must not be None." + assert port is not None, "When assigning custom host, port must not be None." + assert unit_name is not None, "When assigning custom host and port, unit_name must not be None." + + url = baseurl.format(host=host, port=port, unit_name=unit_name) + print(url) + self._url = url + self._resources = parse_resources(url) + self._resources = {**self._resources, **SyslabUnit.__HIDDEN_RESOURCES} + # TODO: Do type checking on these types. Ignore for now + self._complex_arg_types = ['BattOpMode', 'CompositeMeasurement', 'HeatCirculationPumpMode'] + + + if config.REMOTE_LOGGER: + #import logger + if config.DEBUG: + print(f"Setting up remote logger with default IP: {config.REMOTE_IP} and port: {config.REMOTE_PORT}") + from uuid import uuid1, getnode + from ..comm.LogUtils import setup_udp_logger + self.__logger = setup_udp_logger(config.SPLOG_NAME) + self.__session_id = uuid1().__str__() + self.__host_id = getnode().__str__() + + # TODO: Set up local setpoints Logger + if config.LOCAL_LOGGER: + pass + + def __request_int(self, resource, method, args, body) -> int: + return int(send_request(self._url, resource, method, args, body)) + + def __request_float(self, resource, method, args, body) -> float: + return float(send_request(self._url, resource, method, args, body)) + + def __request_boolean(self, resource, method, args, body) -> bool: + return bool(send_request(self._url, resource, method, args, body)) + + def __request_string(self, resource, method, args, body) -> str: + return send_request(self._url, resource, method, args, body) + + def __request_void(self, resource, method, args, body) -> None: + send_request(self._url, resource, method, args, body) + + def __request_composite_boolean(self, resource, method, args, body) -> CompositeBoolean: + + if self.__get_resource_return_type(resource) == 'CompositeBoolean': + json_string = self.__request_string(resource, method, args, body) + result = CompositeBoolean.parseFromJSON(json_string) + else: + raise TypeError('Error: resource "{0}" does not return a CompositeBoolean.'.format(resource)) + + return result + + def __request_composite_measurement(self, resource, method, args, body) -> CompositeMeasurement: + + if self.__get_resource_return_type(resource) == 'CompositeMeasurement': + json_string = self.__request_string(resource, method, args, body) + result = CompositeMeasurement.parseFromJSON(json_string) + else: + raise TypeError('Error: resource "{0}" does not return a CompositeMeasurement.'.format(resource)) + + return result + + def _request_resource(self, resource, args=(), method='get', body=None, check_types=True): + if not type(resource) is str: + raise TypeError("Resource should be a string, found {0}.".format(type(resource))) + + if resource not in self._resources: + # print(resource) + # FIXME: Workaround for metmast, where string-based methods are suffixed by a '2' and int-based methods by a '1' + if not resource[-1] in ('1', '2'): + raise ValueError('Unknown resource: {0} - no such resource found for {1}'.format(resource, self._url)) + + if type(args) is int or type(args) is float or type(args) is str: + args = (args,) + + if config.DEBUG: + print("Resource: ", resource, " and args: ", args, "with method: ", method) + + if config.REMOTE_LOGGER and method == 'put': + logstr = f'SessionID:{self.__session_id}||SenderID:{self.__host_id}||Unit-URL:{self._url}||Setterfcn:{resource}||ArgValue:{args}' + self.__logger.info(logstr) + + return_type = self.__get_resource_return_type(resource) + self.__check_argument(resource, args) + result = None + + if check_types: + try: + if return_type == 'CompositeMeasurement': + result = self.__request_composite_measurement(resource, method, args, body) + elif return_type == 'CompositeBoolean': + result = self.__request_composite_boolean(resource, method, args, body) + elif return_type == 'String': + result = self.__request_string(resource, method, args, body) + elif return_type == 'int': + result = self.__request_int(resource, method, args, body) + elif return_type == 'boolean': + result = self.__request_boolean(resource, method, args, body) + elif return_type == 'float' or return_type == 'double': + result = self.__request_float(resource, method, args, body) + elif return_type == 'void': + self.__request_string(resource, method, args, body) + elif return_type == 'BattOpMode': + result = BattOpMode.parseFromJSON(self.__request_string(resource, method, args, body)) + elif return_type == 'FlowBatteryState': + result = FlowBatteryState.parseFromJSON(self.__request_string(resource, method, args, body)) + elif return_type == 'ConvState': + result = ConvState.parseFromJSON(self.__request_string(resource, method, args, body)) + elif return_type == 'EVSEState': + result = EVSEState.parseFromJSON(self.__request_string(resource, method, args, body)) + elif return_type == 'ConvOpMode': + result = ConvOpMode.parseFromJSON(self.__request_string(resource, method, args, body)) + elif return_type == 'HeatCirculationPumpMode': + result = HeatCirculationPumpMode.parseFromJSON(self.__request_string(resource, method, args, body)) + elif return_type == 'HeatCirculationPumpState': + result = HeatCirculationPumpState.parseFromJSON(self.__request_string(resource, method, args, body)) + elif return_type == 'WYEV' or return_type == 'WYEA' or return_type == 'DELV': + import json + result = json.loads(self.__request_string(resource, method, args, body)) + else: + raise TypeError( + 'TypeError: The return type {0} by {1} is unknown or not supported yet.'.format(return_type, + resource)) + except KeyError as e: + # raise e + raise ValueError('{0} - no such resource found for {1}'.format(resource, self._url)) + else: + import json + return_string = self.__request_string(resource, method, args, body) + try: + result = json.loads(return_string) + except RecursionError: + raise RecursionError( + "Maximum recursion depth exceeded while decoding " + "a JSON array from a unicode string. Length: {0}".format( + len(return_string))) + + return result + + def __get_resource_return_type(self, resource): + if not resource[-1] in ('1', '2'): + # FIXME: Workaround for metmast, where string-based methods are suffixed by a '2' and int-based methods by a '1' + return self._resources[resource][0] + else: + return self._resources[resource[:-1]][0] + + def __get_resource_argument_types(self, resource): + if not resource[-1] in ('1', '2'): + # FIXME: Workaround for metmast, where string-based methods are suffixed by a '2' and int-based methods by a '1' + return self._resources[resource][1:] + else: + return self._resources[resource[:-1]][1:] + + def __check_argument(self, resource, args): + arg_types = self.__get_resource_argument_types(resource) + try: + # TODO Ignore complex arguments (for now) + for complex_arg_type in self._complex_arg_types: + if complex_arg_type in arg_types: + return + + if len(args) == len(arg_types): + for arg, arg_type in zip(args, arg_types): + + if arg_type == 'String' and type(arg) is not str: + raise TypeError() + elif arg_type == 'int' and type(arg) is not int: + raise TypeError() + elif (arg_type == 'float' or arg_type == 'double') and type(arg) is not float and type( + arg) is not int: + raise TypeError() + else: + raise TypeError() + + except TypeError: + raise TypeError( + 'The resource "{0}" requires exactly {1} argument(s) of type {2}.'.format(resource, len(arg_types), + arg_types)) + + +def parse_resources(url): + import re + from ast import literal_eval + result = {} + resources = send_request(url, 'resourceNames', 'get') + resources = literal_eval(resources) + + for resource in resources: + try: + # TODO - handle java arrays, ie, [] + m = re.match("(\w+)\[?\]? (\w+)(\([\w ,]*\))?", resource).groups() + except AttributeError as e: + print(e) + continue + args = '' + + if m[2] is not None: + args = tuple(m[2].replace(' ', '').replace('(', '').replace(')', '').split(',')) + result[m[1]] = (m[0],) + args + else: + result[m[1]] = (m[0],) + if config.DEBUG: + print(f'Key: {m[1]} - {m[0]} -- {args}') + + return result + + +def send_request(url, resource, method, args=None, body=None): + from requests import request + from requests.exceptions import ConnectionError, RequestException + + if not type(url) is str: + raise TypeError('URL should be a string, found {0}'.format(type(url))) + if not type(resource) is str: + raise TypeError('URL should be a string, found {0}'.format(type(resource))) + + kwargs = {} + kwargs.setdefault('allow_redirects', True) + kwargs.setdefault('headers', {'accept': 'application/json', 'content-type': 'application/json'}) + + if not url.endswith('/'): + url += '/' + + full_url = url + resource + '/' + + if args is not None and len(args) > 0: + full_url += '/'.join(tuple(str(x) for x in args)) + + if config.DEBUG: + print(f'Calling: {full_url}') + + try: + response = request(method, full_url, data=body, **kwargs) + except ConnectionError as e: + print('Unable to connect to host: {0}.'.format(url)) + raise e + except RequestException as e: + print('Request error from host: {0}.'.format(full_url)) + raise e + + if 200 <= response.status_code < 300: + result = response.text + elif response.status_code == 404: + raise ConnectionError( + 'Resource not found on host or arguments are not formatted correctly: {0}'.format(response.text)) + elif response.status_code == 405: + raise ConnectionError('Method not allowed on host: \n {0}'.format(response.text)) + elif response.status_code == 500: + from pprint import pprint + # TODO: Handle exception + print('Exception on server:') + pprint(response.json()) + raise + else: + raise ConnectionError( + 'Unable to successfully connect to host. Returned with HTTP status code {0}.\n Content: {1}'.format( + response.status_code, response.text)) + + if config.DEBUG: + print(f'Succesfully called {full_url}') + print(f'Returned: {result}') + + return result diff --git a/syslab-python/syslab/core/__init__.py b/syslab-python/syslab/core/__init__.py new file mode 100644 index 0000000..1e136c6 --- /dev/null +++ b/syslab-python/syslab/core/__init__.py @@ -0,0 +1 @@ +from . import * diff --git a/syslab-python/syslab/core/datatypes/BattOpMode.py b/syslab-python/syslab/core/datatypes/BattOpMode.py new file mode 100644 index 0000000..285a31b --- /dev/null +++ b/syslab-python/syslab/core/datatypes/BattOpMode.py @@ -0,0 +1,47 @@ + +class BattOpMode: + + _OPMODES = { + 0: 'BATT_UNKNOWN', + 1: 'BATT_AUTO', + 2: 'BATT_AUTO_SUBMODE1', + 3: 'BATT_AUTO_SUBMODE2', + 4: 'BATT_AUTO_SUBMODE3', + 5: 'BATT_AUTO_SUBMODE4', + 6: 'BATT_MANUAL', + 7: 'BATT_OFF', + 8: 'BATT_NOBMS', + 9: 'BATT_WITHBMS', } + + def __init__(self, mode): + assert mode in BattOpMode.__OPMODES + self._mode = mode + + @property + def mode(self): + return self._mode + + def modeAsString(self): + return BattOpMode.__OPMODES[self.mode] + + def __str__(self): + return 'BattOpMode: {0} ({1})'.format(self.mode, BattOpMode.__OPMODES[self.mode]) + + def parseToJSON(self): + from json import dumps + jsonObj = {}; + jsonObj['mode'] = self.mode; + return dumps(jsonObj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + # result = None ## Appears unused + + try: + jsonObj = loads(jsonString) + return BattOpMode(jsonObj.get('mode')) + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(jsonString)) + raise e diff --git a/syslab-python/syslab/core/datatypes/CommonDeviceConfig.py b/syslab-python/syslab/core/datatypes/CommonDeviceConfig.py new file mode 100644 index 0000000..09fcbd2 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/CommonDeviceConfig.py @@ -0,0 +1,5 @@ + + +# TODO: Implement +class CommonDeviceConfig: + pass diff --git a/syslab-python/syslab/core/datatypes/CompositeBoolean.py b/syslab-python/syslab/core/datatypes/CompositeBoolean.py new file mode 100644 index 0000000..4caa4f9 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/CompositeBoolean.py @@ -0,0 +1,68 @@ +class CompositeBoolean: + def __init__(self, value, timestampMicros, timePrecision=0, quality=0, validity=0, source=0): + self._value = value + self._timestampMicros = timestampMicros + self._timePrecision = timePrecision if timePrecision != None else 0 + self._quality = quality if quality != None else 0 + self._validity = validity if validity != None else 0 + self._source = source if source != None else 0 + + @property + def value(self): + return self._value + + @property + def timestampMicros(self): + return self._timestampMicros + + @property + def timePrecision(self): + return self._timePrecision + + @property + def quality(self): + return self._quality + + @property + def validity(self): + return self._validity + + @property + def source(self): + return self._source + + def __repr__(self): + return "{0}(".format(self.__class__.__name__) + \ + "{0!r}, {1!r}, ".format(self._value, self._timestampMicros) + \ + "{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \ + "{0!r}, {1!r})".format(self._validity, self._source) + + def __str__(self): + from datetime import datetime + return 'CompositeBoolean: {0} (@time: {1} UTC) '.format(self.value, datetime.utcfromtimestamp(self.timestampMicros/1000000)) + + def parseToJSON(self): + from json import dumps + jsonObj = {}; + jsonObj['value'] = self.value; + jsonObj['timestampMicros'] = self.timestampMicros; + jsonObj['timePrecision'] = self.timePrecision + jsonObj['quality'] = self.quality + jsonObj['validity'] = self.validity + jsonObj['source'] = self.source; + return dumps(jsonObj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + + if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + try: + jsonObj = loads(jsonString) + result = CompositeBoolean(jsonObj.get('value'), jsonObj.get('timestampMicros'), + jsonObj.get('timePrecision'), + jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source')) + return result + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(jsonString)) + raise e diff --git a/syslab-python/syslab/core/datatypes/CompositeMeasurement.py b/syslab-python/syslab/core/datatypes/CompositeMeasurement.py new file mode 100644 index 0000000..76a5263 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/CompositeMeasurement.py @@ -0,0 +1,75 @@ +MICROS_PER_SECOND = 1000000 + +def none_to_zero(x): + if x is None: + return 0 + return x + +class CompositeMeasurement: + def __init__(self, value, timestampMicros, timePrecision=0, quality=0, validity=0, source=0): + self._value = value + self._timestampMicros = timestampMicros + self._timePrecision = none_to_zero(timePrecision) + self._quality = quality if quality != None else 0 + self._validity = validity if validity != None else 0 + self._source = source if source != None else 0 + + @property + def value(self): + return self._value + + @property + def timestampMicros(self): + return self._timestampMicros + + @property + def timePrecision(self): + return self._timePrecision + + @property + def quality(self): + return self._quality + + @property + def validity(self): + return self._validity + + @property + def source(self): + return self._source + + def __str__(self): + from datetime import datetime + return 'CompositeMeasurement: {0} (@time: {1})'.format(self.value, datetime.fromtimestamp(self.timestampMicros/MICROS_PER_SECOND)) + + def __repr__(self): + return "{0}(".format(self.__class__.__name__) + \ + "{0!r}, {1!r}, ".format(self._value, self._timestampMicros) + \ + "{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \ + "{0!r}, {1!r})".format(self._validity, self._source) + + def parseToJSON(self): + from json import dumps + jsonObj = {}; + jsonObj['value'] = self.value; + jsonObj['timestampMicros'] = self.timestampMicros; + jsonObj['timePrecision'] = self.timePrecision + jsonObj['quality'] = self.quality + jsonObj['validity'] = self.validity + jsonObj['source'] = self.source; + return dumps(jsonObj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + + if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + try: + jsonObj = loads(jsonString) + result = CompositeMeasurement(jsonObj.get('value'), jsonObj.get('timestampMicros'), + jsonObj.get('timePrecision'), + jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source')) + return result + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(jsonString)) + raise e diff --git a/syslab-python/syslab/core/datatypes/CompositeStatus.py b/syslab-python/syslab/core/datatypes/CompositeStatus.py new file mode 100644 index 0000000..e5af1e5 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/CompositeStatus.py @@ -0,0 +1,5 @@ +# TODO: Add Processing here + + +class CompositeStatus: + pass diff --git a/syslab-python/syslab/core/datatypes/ConverterTypes.py b/syslab-python/syslab/core/datatypes/ConverterTypes.py new file mode 100644 index 0000000..6073522 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/ConverterTypes.py @@ -0,0 +1,132 @@ +from .CompositeStatus import CompositeStatus + +class ConvOpMode: + + _OPMODES = { + 0: 'CONV_UNKNOWN', + 1: 'CONV_PQ', + 2: 'CONV_UF', + 3: 'CONV_OFF', + } + + def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0): + assert status in ConvOpMode._OPMODES + self._status = status + self._timestampMicros = timestampMicros + self._timePrecision = timePrecision + self._quality = quality + self._validity = validity + self._source = source + + @property + def mode(self) -> int: + return self._status + + def modeAsString(self): + return ConvOpMode._OPMODES[self.mode] + + def __str__(self): + return f'ConvOpMode({self.mode}; {ConvOpMode._OPMODES[self.mode]})' + + def parseToJSON(self): + from json import dumps + jsonObj = {'mode': self.mode} + return dumps(jsonObj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + if not type(jsonString) is str: + raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + try: + jsonObj = loads(jsonString) + return ConvOpMode(**jsonObj.get('mode')) + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(jsonString)) + raise e + + +class ConvState: + + _STATES = { + 0: 'CONV_UNKNOWN', + 1: 'CONV_STOPPED', + 2: 'CONV_STARTING', + 4: 'CONV_RUNNING', + 8: 'CONV_STOPPING', + 16: 'CONV_SYNCED', + 32: 'CONV_DROOP', + 64: 'CONV_LOADENABLED', + 128: 'CONV_INHIBITED', + 1024: 'CONV_READY', + 2048: 'CONV_WARNING', + 4096: 'CONV_ALARM'} + + def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0): + self._status = status + self._timestampMicros = timestampMicros + self._timePrecision = timePrecision + self._quality = quality + self._validity = validity + self._source = source + + @property + def status(self): + return self._status + + @property + def timestampMicros(self): + return self._timestampMicros + + @property + def timePrecision(self): + return self._timePrecision + + @property + def quality(self): + return self._quality + + @property + def validity(self): + return self._validity + + @property + def source(self): + return self._source + + def statusAsString(self): + return ConvState._STATES[self.status] + + def __repr__(self): + return "{0}(".format(self.__class__.__name__) + \ + "{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \ + "{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \ + "{0!r}, {1!r})".format(self._validity, self._source) + + def __str__(self): + from datetime import datetime + return 'FlowBatteryState: {0} : {1} (@time: {2}))'.format( + self._status, + # status & statuscode evaluates to True if statuscode is 2^k and in the binary expansion of status + ";".join([label for statuscode, label in ConvState._STATES.items() if self._status & statuscode]), + datetime.fromtimestamp(self.timestampMicros / 1000000)) + + def parseToJSON(self): + from json import dumps + json_obj = {'status': self._status} + return dumps(json_obj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + # result = None + try: + jsonObj = loads(jsonString).get('status') + return ConvState( + jsonObj.get('status'), jsonObj.get('timestampMicros'), + jsonObj.get('timePrecision'), + jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source')) + except JSONDecodeError as e: + print('Not a valid JSON string in parsing Converter Status: {0}.'.format(jsonString)) + raise e diff --git a/syslab-python/syslab/core/datatypes/EVSEState.py b/syslab-python/syslab/core/datatypes/EVSEState.py new file mode 100644 index 0000000..bb55ba3 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/EVSEState.py @@ -0,0 +1,83 @@ + +class EVSEState: + + _STATES = { + 0: 'EVSE_UNKNOWN', + 1: 'EVSE_NO_EV', + 2: 'EVSE_EV_STOPPED', + 4: 'EVSE_EV_READY', + 8: 'EVSE_EV_STARTING', + 16: 'EVSE_EV_CHARGING', + 32: 'EVSE_EV_DISCHARGING', + 64: 'EVSE_EV_PAUSED', + 128: 'EVSE_EV_ALARM', + 256: 'EVSE_EV_ESD', } + + def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0): + self._status = status + self._timestampMicros = timestampMicros + self._timePrecision = timePrecision + self._quality = quality + self._validity = validity + self._source = source + + @property + def status(self): + return self._status + + @property + def timestampMicros(self): + return self._timestampMicros + + @property + def timePrecision(self): + return self._timePrecision + + @property + def quality(self): + return self._quality + + @property + def validity(self): + return self._validity + + @property + def source(self): + return self._source + + def statusAsString(self): + return EVSEState.__STATES[self.status] + + def __repr__(self): + return "{0}(".format(self.__class__.__name__) + \ + "{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \ + "{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \ + "{0!r}, {1!r})".format(self._validity, self._source) + + def __str__(self): + from datetime import datetime + return 'EVSEState: {0} : {1} (@time: {2}))'.format( + self.status, + # status & statuscode evaluates to True if statuscode is 2^k and in the binary expansion of status + ";".join([label for statuscode, label in EVSEState._STATES.items() if self._status & statuscode]), + datetime.fromtimestamp(self.timestampMicros / 1000000)) + + def parseToJSON(self): + from json import dumps + jsonObj = {'status': self.status} + return dumps(jsonObj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + # result = None + try: + jsonObj = loads(jsonString).get('status') + return EVSEState( + jsonObj.get('status'), jsonObj.get('timestampMicros'), + jsonObj.get('timePrecision'), + jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source')) + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(jsonString)) + raise e diff --git a/syslab-python/syslab/core/datatypes/FlowBatteryState.py b/syslab-python/syslab/core/datatypes/FlowBatteryState.py new file mode 100644 index 0000000..6cc5a42 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/FlowBatteryState.py @@ -0,0 +1,88 @@ + +class FlowBatteryState: + + _STATES = { + 0: 'BATT_UNKNOWN', + 1: 'BATT_STOPPED', + 2: 'BATT_STARTING', + 4: 'BATT_FLOODING', + 8: 'BATT_RUNNING', + 16: 'BATT_STOPPING', + 32: 'BATT_DRAINING', + 64: 'BATT_PUMPSRAMP', + 128: 'BATT_TANKVALVESOP', + 256: 'BATT_PUMPSRUN', + 512: 'BATT_DCBREAKER', + 1024: 'PCS_READY', + 2048: 'PCS_RUNNING', + 4096: 'BATT_ALARM', + 8192: 'BATT_ESD'} + + def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0): + self._status = status + self._timestampMicros = timestampMicros + self._timePrecision = timePrecision + self._quality = quality + self._validity = validity + self._source = source + + @property + def status(self): + return self._status + + @property + def timestampMicros(self): + return self._timestampMicros + + @property + def timePrecision(self): + return self._timePrecision + + @property + def quality(self): + return self._quality + + @property + def validity(self): + return self._validity + + @property + def source(self): + return self._source + + def statusAsString(self): + return FlowBatteryState.__STATES[self.status] + + def __repr__(self): + return "{0}(".format(self.__class__.__name__) + \ + "{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \ + "{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \ + "{0!r}, {1!r})".format(self._validity, self._source) + + def __str__(self): + from datetime import datetime + return 'FlowBatteryState: {0} : {1} (@time: {2}))'.format( + self.status, + # status & statuscode evaluates to True if statuscode is 2^k and in the binary expansion of status + ";".join([label for statuscode, label in FlowBatteryState._STATES.items() if self._status & statuscode]), + datetime.fromtimestamp(self.timestampMicros / 1000000)) + + def parseToJSON(self): + from json import dumps + jsonObj = {'status': self.status} + return dumps(jsonObj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + # result = None + try: + jsonObj = loads(jsonString).get('status') + return FlowBatteryState( + jsonObj.get('status'), jsonObj.get('timestampMicros'), + jsonObj.get('timePrecision'), + jsonObj.get('quality'), jsonObj.get('validity'), jsonObj.get('source')) + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(jsonString)) + raise e diff --git a/syslab-python/syslab/core/datatypes/HeatCirculationPumpMode.py b/syslab-python/syslab/core/datatypes/HeatCirculationPumpMode.py new file mode 100644 index 0000000..0b08ccb --- /dev/null +++ b/syslab-python/syslab/core/datatypes/HeatCirculationPumpMode.py @@ -0,0 +1,89 @@ + +class HeatCirculationPumpMode: + + __OPMODES = { + -1: 'PUMP_MODE_UNKNOWN', + 0: 'PUMP_MODE_CONSTANT_SPEED', + 1: 'PUMP_MODE_CONSTANT_FREQUENCY', + 3: 'PUMP_MODE_CONSTANT_HEAD', + 4: 'PUMP_MODE_CONSTANT_PRESSURE', + 5: 'PUMP_MODE_CONSTANT_DIFF_PRESSURE', + 6: 'PUMP_MODE_PROPORTIONAL_PRESSURE', + 7: 'PUMP_MODE_CONSTANT_FLOW', + 8: 'PUMP_MODE_CONSTANT_TEMP', + 10: 'PUMP_MODE_CONSTANT_LEVEL', + 128: 'PUMP_MODE_AUTO_ADAPT', + 129: 'PUMP_MODE_FLOW_ADAPT' } + + __OPMODES_R = {v: k for k, v in __OPMODES.items()} + + def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0): + """ + @Input: + mode (int or str): Pump operating mode, can be both int and string + + To get list of available modes: HeatCirculationPumpMode.opmodes() + """ + if status in HeatCirculationPumpMode.__OPMODES: + self._status = status + elif mode in HeatCirculationPumpMode.__OPMODES_R: + self._status = HeatCirculationPumpMode.__OPMODES_R[status] + else: + raise AssertionError('HeatCirculationPumpMode not recognized: \'{0}\''.format(mode)) + self._timestampMicros = timestampMicros + self._timePrecision = timePrecision + self._quality = quality + self._validity = validity + self._source = source + + @staticmethod + def opmodes(): + return HeatCirculationPumpMode.__OPMODES.copy() + + @property + def mode(self): + return self._status + + @property + def modeAsString(self): + return HeatCirculationPumpMode.__OPMODES[self.mode] + + def __repr__(self): + return "{0}(".format(self.__class__.__name__) + \ + "{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \ + "{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \ + "{0!r}, {1!r})".format(self._validity, self._source) + def __str__(self): + return 'HeatCirculationPumpMode: {0} ({1})'.format(self.mode, self.modeAsString) + + def parseToJSON(self): + from json import dumps + jsonObj = {}; + jsonObj['mode'] = { + 'status': self._status, + 'timestampMicros': self._timestampMicros, + 'timePrecision': self._timePrecision, + 'quality': self._quality, + 'validity': self._validity, + 'source': self._source, + } + return dumps(jsonObj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + # result = None ## Appears unused + + try: + jsonObj = loads(jsonString).get('mode') + return HeatCirculationPumpMode( + jsonObj.get('status'), + jsonObj.get('timestampMicros'), + jsonObj.get('timePrecision'), + jsonObj.get('quality'), + jsonObj.get('validity'), + jsonObj.get('source')) + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(jsonString)) + raise e diff --git a/syslab-python/syslab/core/datatypes/HeatCirculationPumpState.py b/syslab-python/syslab/core/datatypes/HeatCirculationPumpState.py new file mode 100644 index 0000000..2628b16 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/HeatCirculationPumpState.py @@ -0,0 +1,83 @@ + +class HeatCirculationPumpState: + + __OPSTATES = { + -1: 'PUMP_STATE_UNKNOWN', + 0: 'PUMP_STATE_STOPPED', + 1: 'PUMP_STATE_RUNNING', + 2: 'PUMP_STATE_ERROR',} + __OPSTATES_R = {v: k for k, v in __OPSTATES.items()} + + def __init__(self, status, timestampMicros, timePrecision=0, quality=0, validity=0, source=0): + """ + @Input: + status (int or str): Pump operating mode, can be both int and string + + To get list of available modes: HeatCirculationPumpState.opstates() + """ + if status in HeatCirculationPumpState.__OPSTATES: + self._status = status + elif state in HeatCirculationPumpState.__OPSTATES_R: + self._status = HeatCirculationPumpState.__OPSTATES_R[status] + else: + raise AssertionError('HeatCirculationPumpState not recognized: \'{0}\''.format(mode)) + self._timestampMicros = timestampMicros + self._timePrecision = timePrecision + self._quality = quality + self._validity = validity + self._source = source + + + @staticmethod + def opstates(): + return HeatCirculationPumpState.__OPSTATES.copy() + + @property + def state(self): + return self._status + + @property + def stateAsString(self): + return HeatCirculationPumpState.__OPSTATES[self._status] + + def __repr__(self): + return "{0}(".format(self.__class__.__name__) + \ + "{0!r}, {1!r}, ".format(self._status, self._timestampMicros) + \ + "{0!r}, {1!r}, ".format(self._timePrecision, self._quality) + \ + "{0!r}, {1!r})".format(self._validity, self._source) + + + def __str__(self): + return 'HeatCirculationPumpState: {0} ({1})'.format(self.state, self.stateAsString) + + def parseToJSON(self): + from json import dumps + jsonObj = {} + jsonObj['state'] = { + 'status': self._status, + 'timestampMicros': self._timestampMicros, + 'timePrecision': self._timePrecision, + 'quality': self._quality, + 'validity': self._validity, + 'source': self._source, + } + return dumps(jsonObj) + + @staticmethod + def parseFromJSON(jsonString): + from json import JSONDecodeError, loads + if not type(jsonString) is str: raise TypeError('jsonString should be a string, found {0}'.format(type(jsonString))) + # result = None ## Appears unused + + try: + jsonObj = loads(jsonString).get('state') + return HeatCirculationPumpState( + jsonObj.get('status'), + jsonObj.get('timestampMicros'), + jsonObj.get('timePrecision'), + jsonObj.get('quality'), + jsonObj.get('validity'), + jsonObj.get('source')) + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(jsonString)) + raise e diff --git a/syslab-python/syslab/core/datatypes/Identifiers.py b/syslab-python/syslab/core/datatypes/Identifiers.py new file mode 100644 index 0000000..bb7367b --- /dev/null +++ b/syslab-python/syslab/core/datatypes/Identifiers.py @@ -0,0 +1,73 @@ +from dataclasses import dataclass +from . import CompositeMeasurement +from typing import Optional +# TODO: Add processing to these. + + +@dataclass +class DELV: + phaseAB: CompositeMeasurement + phaseBC: CompositeMeasurement + phaseCA: CompositeMeasurement + phaseAverage: CompositeMeasurement + + @staticmethod + def parseFromJSON(json_string): + from json import JSONDecodeError, loads + if not type(json_string) is str: + raise TypeError('jsonString should be a string, found {0}'.format(type(json_string))) + + try: + jsonObj = loads(json_string) + phaseABcm = CompositeMeasurement(**jsonObj.get('phaseAB')) + phaseBCcm = CompositeMeasurement(**jsonObj.get('phaseBC')) + phaseCAcm = CompositeMeasurement(**jsonObj.get('phaseCA')) + phaseAVGcm = CompositeMeasurement(**jsonObj.get('phaseAverage')) + return DELV(phaseABcm, phaseBCcm, phaseCAcm, phaseAVGcm) + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(json_string)) + raise e + + +class GPSL: + pass + + +class HLTH: + pass + + +class LNPL: + pass + + +class PNPL: + pass + + +@dataclass() +class WYEA: + phaseAB: CompositeMeasurement + phaseBC: CompositeMeasurement + phaseCA: CompositeMeasurement + neutral: Optional[CompositeMeasurement] = None + + @staticmethod + def parseFromJSON(json_string): + from json import JSONDecodeError, loads + if not type(json_string) is str: + raise TypeError('jsonString should be a string, found {0}'.format(type(json_string))) + + try: + jsonObj = loads(json_string) + phaseAcm = CompositeMeasurement(**jsonObj.get('phaseA')) + phaseBcm = CompositeMeasurement(**jsonObj.get('phaseB')) + phaseCcm = CompositeMeasurement(**jsonObj.get('phaseC')) + neutral = jsonObj.get('neutral') + if neutral is not None: + neutral = CompositeMeasurement(**neutral) + return WYEA(phaseAcm, phaseBcm, phaseCcm, neutral) + + except JSONDecodeError as e: + print('Not a valid JSON string: {0}.'.format(json_string)) + raise e diff --git a/syslab-python/syslab/core/datatypes/__init__.py b/syslab-python/syslab/core/datatypes/__init__.py new file mode 100644 index 0000000..56e0773 --- /dev/null +++ b/syslab-python/syslab/core/datatypes/__init__.py @@ -0,0 +1,16 @@ +__all__ = [] + +# Composite Data Types +from .CompositeBoolean import CompositeBoolean +from .CompositeMeasurement import CompositeMeasurement +from .CompositeStatus import CompositeStatus + +# SYSLAB-specific data types +from .Identifiers import DELV, GPSL, HLTH, LNPL, PNPL, WYEA + +# Unit-specific data types +from .BattOpMode import BattOpMode +from .FlowBatteryState import FlowBatteryState +from .HeatCirculationPumpMode import HeatCirculationPumpMode +from .HeatCirculationPumpState import HeatCirculationPumpState +from .ConverterTypes import ConvState, ConvOpMode diff --git a/syslab-python/syslab/get_flexhouse.py b/syslab-python/syslab/get_flexhouse.py new file mode 100644 index 0000000..e4bd1d6 --- /dev/null +++ b/syslab-python/syslab/get_flexhouse.py @@ -0,0 +1,27 @@ +def get_flexhouse(simulated=False, physical=False, simulate_on_dumpload_ID="", simulate_on_battery=False): + """ + Return an instantiated object which operates as a flexhouse. + input: + simulated (bool): Whether the flexhouse should be simulated + physical (bool): Whether the flexhouse should be the real flexhouse + simulate_on_dumpload_ID (string): The ID of the dumpload on which to simulate the flexhouse + simulate_on_battery (bool): Whether to simulate on battery + + return: + flexhouse: A flexhouse object + """ + + if simulated: + if simulate_on_battery: + from .virtual.FlexHouse_sim_batt import FlexHouse_sim_batt + return FlexHouse_sim_batt('batt1') + else: + from .virtual.FlexHouse_sim import FlexHouse_sim + assert simulate_on_dumpload_ID != "", "Must supply an ID string for the dumpload used in Flexhouse simulation if not simulating on battery" + return FlexHouse_sim(simulate_on_dumpload_ID) + elif physical: + from .physical.FlexHouse_real import FlexHouse_real + return FlexHouse_real() + else: + raise Exception('Must define if FlexHouse instance is real or simulated') + diff --git a/syslab-python/syslab/physical/B2BConverter.py b/syslab-python/syslab/physical/B2BConverter.py new file mode 100644 index 0000000..a2ca48e --- /dev/null +++ b/syslab-python/syslab/physical/B2BConverter.py @@ -0,0 +1,258 @@ +from ..core.SyslabUnit import SyslabUnit +from ..core.datatypes import CompositeMeasurement, CompositeBoolean, CompositeStatus +from ..core.datatypes import CommonDeviceConfig +from ..core.datatypes import DELV, WYEA, GPSL, PNPL, LNPL, HLTH, ConvOpMode, ConvState +from typing import List, Union +from time import time + + +def cast_to_cm(m: Union[CompositeMeasurement, float]): + if type(m) == float: + # TODO: Is there a better way to estimate precision of time.time? + request = CompositeMeasurement(m, timestampMicros=time()*1e6, timePrecision=1000) + elif type(m) == CompositeMeasurement: + request = m + else: + raise TypeError(f"Unknown request type: {type(m)}") + return request + + +class B2BConverter(SyslabUnit): + """ + Class covering back-to-back converters in SYSLAB. + + A full list of available switchboards can be found by calling 'SwitchBoard.getAvailableSwitchBoards()' + + Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments. + """ + + _CONVERTERS = { + 'ABBB2B': ('syslab-04.syslab.dk', '8080', 'B2BConverter'), + } + MAXP = 60 + MAXQ = 60 + + @staticmethod + def getAvailableUnits(): + return list(B2BConverter.__CONVERTERS.keys()) + + def __init__(self, which=None, host=None, port=None, unit_name=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_ABBConverter/ABBConverterWebService/{unit_name}/' + + super().__init__( + baseurl=baseurl, + which=which, + units=self._CONVERTERS, + host=host, + port=port, + unit_name=unit_name, + unit_type="B2BConverter") + + # Inventory functions + + # Configuration class used for HMI layout + def getNodeConfiguration(self, ) -> CommonDeviceConfig: + return self._request_resource('getNodeConfiguration') + + # Component description + def getConverterName(self, ) -> str: + return self._request_resource('getConverterName') + + def getConverterLogicalNameplate(self, ) -> LNPL: + raise NotImplementedError + + def getConverterPhysicalNameplate(self, ) -> PNPL: + raise NotImplementedError + + def getConverterHealth(self, ) -> HLTH: + raise NotImplementedError + + def getGPSLocation(self, ) -> GPSL: + raise NotImplementedError + + # Operating characteristics (corresponding to COPR/DRAT node) + def getRatedP(self, ) -> float: + raise NotImplementedError + # return self._request_resource('getRatedP') + + def getRatedS(self, ) -> float: + raise NotImplementedError + # return self._request_resource('getRatedS') + + def getRatedQ(self, ) -> float: + raise NotImplementedError + # return self._request_resource('getRatedQ') + + def getRatedU(self, ) -> float: + raise NotImplementedError + # return self._request_resource('getRatedU') + + def getRatedI(self, ) -> float: + raise NotImplementedError + # return self._request_resource('getRatedI') + + def getRatedf(self, ) -> float: + raise NotImplementedError + # return self._request_resource('getRatedf') + + # Operating mode settings (corresponding to DOPM node) + def getAvailableOperatingModes(self, ) -> List[ConvOpMode]: + raise NotImplementedError + + def getCurrentOperatingMode(self, ) -> ConvOpMode: + return self._request_resource('getCurrentOperatingMode') + + def setOperatingMode(self, mode: ConvOpMode) -> None: + raise NotImplementedError + + def isPSetpointEnabled(self, ) -> CompositeBoolean: + return self._request_resource('isPSetpointEnabled') + + def isQSetpointEnabled(self, ) -> CompositeBoolean: + return self._request_resource('isQSetpointEnabled') + + def isUSetpointEnabled(self, ) -> CompositeBoolean: + return self._request_resource('isUSetpointEnabled') + + def isfSetpointEnabled(self, ) -> CompositeBoolean: + return self._request_resource('isfSetpointEnabled') + + # Status information (corresponding to DPST node) + def getConverterStatus(self, ) -> ConvState: + return self._request_resource('getConverterStatus') + + # Alarms information + def hasActiveFault(self, ) -> CompositeBoolean: + raise NotImplementedError + + def hasActiveWarning(self, ) -> CompositeBoolean: + raise NotImplementedError + + def resetAlarms(self, ) -> None: + raise NotImplementedError + + def getActiveEventCode(self, ) -> CompositeStatus: + raise NotImplementedError + + # DER controller characteristics (corresponding to DRCT node) + def setActivePower(self, m: Union[CompositeMeasurement, float]) -> None: + """ + Send a request for the converter's output active power. Requires the converter to be in PQ mode. + + :param m: [kW] Requested active power. If a float, will be converted to a CompositeMeasurement with the current system time as timestamp.) + :return: None + """ + + P_UpperLimit = 15.0 + P_LowerLimit = -15.0 + + return self._request_resource('setActivePower', (), 'put', cast_to_cm(max(P_LowerLimit, min(m, P_UpperLimit))).parseToJSON()) + + def setFrequency(self, m: Union[CompositeMeasurement, float]) -> None: + """ + Send a request for the converter's output power. Requires the converter to be in UF mode. + + :param m: [Hz] Requested active power. If a float, will be converted to a CompositeMeasurement with the current system time as timestamp.) + :return: None + """ + return self._request_resource('seFrequency', (), 'put', cast_to_cm(m).parseToJSON()) + + def setReactivePower(self, m: Union[CompositeMeasurement, float]) -> None: + """ + Send a request for the converter's output reactive power. Requires the converter to be in PQ mode. + + :param m: [kVA] Requested active power. If a float, will be converted to a CompositeMeasurement with the current system time as timestamp.) + :return: None + """ + + Q_UpperLimit = 15.0 + Q_LowerLimit = -15.0 + + return self._request_resource('setReactivePower', (), 'put', cast_to_cm(max(Q_LowerLimit, min(m, Q_UpperLimit))).parseToJSON()) + + def setVoltage(self, m: Union[CompositeMeasurement, float]) -> None: + """ + Send a request for the converter's output voltage. Requires the converter to be in UF mode. + + :param m: [V] Requested active power. If a float, will be converted to a CompositeMeasurement with the current system time as timestamp.) + :return: None + """ + return self._request_resource('setVoltage', (), 'put', cast_to_cm(m).parseToJSON()) + + def getActivePowerSetpoint(self, ) -> CompositeMeasurement: + raise NotImplementedError + + def getFrequencySetpoint(self, ) -> CompositeMeasurement: + raise NotImplementedError + + def getReactivePowerSetpoint(self, ) -> CompositeMeasurement: + raise NotImplementedError + + def getVoltageSetpoint(self, ) -> CompositeMeasurement: + raise NotImplementedError + + # Synchronisation (corresponding to RSYN node) + def synchronize(self, ) -> None: + raise NotImplementedError + + def unsynchronize(self, ) -> None: + raise NotImplementedError + + def setDroopEnable(self, b: CompositeBoolean) -> None: + raise NotImplementedError + + def setLoadEnable(self, b: CompositeBoolean) -> None: + raise NotImplementedError + + def getVoltageDroopPct(self, ) -> CompositeMeasurement: + raise NotImplementedError + + def getFrequencyDroopPct(self, ) -> CompositeMeasurement: + raise NotImplementedError + + def setVoltageDroopPct(self, pct: CompositeMeasurement) -> None: + raise NotImplementedError + + def setFrequencyDroopPct(self, pct: CompositeMeasurement) -> None: + raise NotImplementedError + + # Reciprocating Engine (corresponds to DCIP node) + def startConverter(self, ) -> None: + return self._request_resource('startConverter', (), 'put') + + def softStopConverter(self, ) -> None: + return self._request_resource('softStopConverter', (), 'put') + + def stopConverter(self, ) -> None: + return self._request_resource('stopConverter', (), 'put') + + # AC quantities (corresponds to MMXU nodes) + def getActivePowerOutput(self, ) -> CompositeMeasurement: + return self._request_resource('getActivePowerOutput') + + def getReactivePowerOutput(self, ) -> CompositeMeasurement: + return self._request_resource('getReactivePowerOutput') + + def getOutputFrequency(self, ) -> CompositeMeasurement: + return self._request_resource('getOutputFrequency') + + def getOutputInterphaseVoltages(self, ) -> DELV: + return self._request_resource('getOutputInterphaseVoltages') + + def getOutputPhaseCurrents(self, ) -> WYEA: + raise NotImplementedError + + def getRectifierInterphaseVoltages(self, ) -> DELV: + return self._request_resource('getRectifierInterphaseVoltages') + + def getRectifierPhaseCurrents(self, ) -> WYEA: + return self._request_resource('getRectifierPhaseCurrents') + + def getSyncBusInterphaseVoltages(self, ) -> DELV: + raise NotImplementedError + + +class ABBB2BConverter(B2BConverter): + def __init__(self): + super(ABBB2BConverter, self).__init__(which='ABBB2B') diff --git a/syslab-python/syslab/physical/Battery.py b/syslab-python/syslab/physical/Battery.py new file mode 100644 index 0000000..1da016b --- /dev/null +++ b/syslab-python/syslab/physical/Battery.py @@ -0,0 +1,85 @@ +from ..core.SyslabUnit import SyslabUnit +from ..core.datatypes.FlowBatteryState import FlowBatteryState +from ..core.datatypes.BattOpMode import BattOpMode + +class Battery(SyslabUnit): + """The Battery class represents a battery in SYSLAB. + The Battery class is instantiated using a string with the unique name of the battery, ie. 'which' + + A full list of available batteries can be found by calling 'Battery.getAvailableBatteries()' + + Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments. + """ + + __BATTERIES = { + 'batt1': ('syslab-12.syslab.dk', '8080', 'batt1'), + 'battemu': ('syslab-31.syslab.dk', '8080', 'battemu'), + 'battfh1': ('syslab-s01.syslab.dk', '8080', 'battfh1'), + 'simlab-15': ('192.168.0.115', '8080', 'batt1'), + 'vbatt1': ('simlab-12', '8080', 'batt1'), + } + + def __init__(self, which=None, host=None, port=None, unitname=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_Battery/VRBBatteryWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__BATTERIES, + host=host, + port=port, + unit_name=unitname, + unit_type="Battery") + + def startBattery(self): + return self._request_resource('startBattery', (), 'put') + + def stopBattery(self): + return self._request_resource('stopBattery', (), 'put') + + def getActivePower(self): + return self._request_resource('getACActivePower') + + def setActivePower(self, setPoint): + + P_UpperLimit = 15.0 + P_LowerLimit = -15.0 + + return self._request_resource('setP', (max(P_LowerLimit, min(setPoint, P_UpperLimit))), 'put') + + def getReactivePower(self): + return self._request_resource('getACReactivePower') + + def setReactivePower(self, setPoint): + return self._request_resource('setQ', (setPoint), 'put') + + def getFrequency(self): + return self._request_resource('getACFrequency') + + def getRemainingFloodTime(self): + return min(self._request_resource('getRemainingFloodTime'), self._request_resource('getRemainingDrainTime')) + + def getCurrentOperatingMode(self): + return self._request_resource('getCurrentOperatingMode') + + def getCurrentOperatingState(self): + return self._request_resource('getCurrentOperatingState') + + def setOperatingMode(self, mode): + return self._request_resource('setOperatingMode', (), 'put', BattOpMode(mode).parseToJSON()) + + def getSOC(self): + return self._request_resource('getSOC') + + def getRatedActivePower(self): + return self._request_resource('getRatedP') + + def getRatedReactivePower(self): + return self._request_resource('getRatedQ') + + def getName(self): + return self._request_resource('getBatteryName') + + @staticmethod + def getAvailableBatteries(): + return list(Battery.__BATTERIES.keys()) diff --git a/syslab-python/syslab/physical/DieselGenerator.py b/syslab-python/syslab/physical/DieselGenerator.py new file mode 100644 index 0000000..ef5b606 --- /dev/null +++ b/syslab-python/syslab/physical/DieselGenerator.py @@ -0,0 +1,100 @@ +from ..core.SyslabUnit import SyslabUnit + + +class DieselGenerator(SyslabUnit): + """The DieselGenerator class represents a photovoltaic panel array in SYSLAB. + The DieselGenerator class is instantiated using a string with the unique name of the dumpload, ie. 'which' + + A full list of available panel arrays can be found by calling 'DieselGenerator.getAvailableDieselGenerator()' + + Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments. + """ + + __UNITS = { + 'diesel319': ('syslab-02.syslab.dk', '8080', 'genset1'), + } + + def __init__(self, which=None, host=None, port=None, unitname=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_Diesel/DEIFDieselGensetWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__UNITS, + host=host, + port=port, + unit_name=unitname, + unit_type="DieselGenerator") + + def getRatedP(self): + return self._request_resource('getRatedP') + + def getRatedQ(self): + return self._request_resource('getRatedQ') + + def getRatedS(self): + return self._request_resource('getRatedS') + + def getRatedU(self): + return self._request_resource('getRatedU') + + def getRatedf(self): + return self._request_resource('getRatedf') + + def isGeneratorBreakerClosed(self): + return self._request_resource('isGeneratorBreakerClosed') + + def isGensetRunning(self): + return self._request_resource('isGensetRunning') + + def isGensetSynchronized(self): + return self._request_resource('isGensetSynchronized') + + def isSynchronising(self): + return self._request_resource('isSynchronising') + + def closeGB(self): + return self._request_resource('closeGB', method='put') + + def openGB(self): + return self._request_resource('openGB', method='put') + + def startGenset(self): + return self._request_resource('startGenset', method='put') + + def stopGenset(self): + return self._request_resource('stopGenset', method='put') + + def setTargetActivePower(self, setpoint): + return self._request_resource('setTargetActivePower', setpoint, 'put') + + def setTargetReactivePower(self, setpoint): + return self._request_resource('setTargetReactivePower', setpoint, 'put') + + def getActivePower(self): + return self._request_resource('getActivePower') + + def getReactivePower(self): + return self._request_resource('getReactivePower') + + #TODO: Implement + def getCurrentGensetMode(self): + pass + + #TODO: Implement + def getCurrentRunningMode(self): + pass + + #TODO: Implement + def setGensetMode(self, mode): + pass + + #TODO: Implement + def setRunningMode(self, mode): + pass + + @staticmethod + def getAvailableDieselGenerators(): + return list(DieselGenerator.__UNITS.keys()) + + diff --git a/syslab-python/syslab/physical/Dumpload.py b/syslab-python/syslab/physical/Dumpload.py new file mode 100644 index 0000000..dd67582 --- /dev/null +++ b/syslab-python/syslab/physical/Dumpload.py @@ -0,0 +1,112 @@ +from ..core.SyslabUnit import SyslabUnit +import warnings + +class Dumpload(SyslabUnit): + """The Dumpload class represents a dumpload in SYSLAB. + The Dumpload class is instantiated using a string with the unique name of the dumpload, ie. 'which' + + A full list of available dumploads can be found by calling 'Dumpload.getAvailableDumpLoads()' + + Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments. + """ + + + __DUMPLOADS = { + 'mobload1': ('syslab-16.syslab.dk', '8080', 'mobload1'), + 'mobload2': ('syslab-17.syslab.dk', '8080', 'mobload2'), + 'mobload3': ('syslab-18.syslab.dk', '8080', 'mobload3'), + 'load1': ('syslab-05.syslab.dk', '8080', 'load1'), + 'vmobload1': ('simlab-16', '8080', 'mobload1'), + 'vmobload2': ('simlab-17', '8080', 'mobload2'), + 'vmobload3': ('simlab-18', '8080', 'mobload3'), + 'vload1': ('simlab-05', '8080', 'load1'), + 'simlab-05': ('192.168.0.105', '8080', 'mobload2'), + 'simlab-11': ('192.168.0.111', '8080', 'mobload1'), + 'simlab-12': ('192.168.0.112', '8080', 'load1'), + } + + def __init__(self, which=None, host=None, port=None, unitname=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_Load/GenericLoadWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__DUMPLOADS, + host=host, + port=port, + unit_name=unitname, + unit_type="Battery") + + def startLoad(self): + return self._request_resource('startLoad', (), 'put') + + def stopLoad(self): + return self._request_resource('stopLoad', (), 'put') + + def getPowerSetPoint(self): + return self._request_resource('getConstantP') + + def setPowerSetPoint(self, setPoint): + """ + Set the active power setpoint for the load. + + Inputs: + setPoint (float): Requested power setpoint + Outputs: + Ack (bool): Acknowledgement of receiver + """ + + P_UpperLimit = 15.0 + P_LowerLimit = 0 + + return self._request_resource('setConstantP', max(P_LowerLimit, min(setPoint, P_UpperLimit)), 'put') + + def getActivePower(self): + return self._request_resource('getActivePower') + + def getReactivePower(self): + """ + Get the reactive power draw from the load. + + Outputs: + Q (CompositeMeasurement): Current reactive power draw, calculated from active power draw (see note) + + NOTE: This is a theoretical value calculated from the relation + Q = Q_r * sin (pi * P /P_r) + where P_r and Q_r are the rated active and reactive power draw. + For control purposes, use the measured value from the switchboard + instead to get an actual measurement. + """ + warnings.warn("The output of getReactivePower from the Dumpload class is calculated from the active power draw rather than a measured value. For control purposes, use the measured power draw on the switchboard.") + return self._request_resource('getReactivePower') + + def getRatedPower(self): + return self._request_resource('getRatedP') + + def getRatedReactivePower(self): + return self._request_resource('getRatedQ') + + def getName(self): + return self._request_resource('getLoadName') + + def isLoadOn(self): + return self._request_resource('isLoadOn') + + @staticmethod + def getAvailableDumploads(): + return list(Dumpload.__DUMPLOADS.keys()) + + +class MobileLoad1(Dumpload): + def __init__(self): + super(MobileLoad1, self).__init__("mobload1") + + +class MobileLoad2(Dumpload): + def __init__(self): + super(MobileLoad2, self).__init__("mobload2") + + +class MobileLoad3(Dumpload): + def __init__(self): + super(MobileLoad3, self).__init__("mobload3") diff --git a/syslab-python/syslab/physical/EVSE.py b/syslab-python/syslab/physical/EVSE.py new file mode 100644 index 0000000..bce38fa --- /dev/null +++ b/syslab-python/syslab/physical/EVSE.py @@ -0,0 +1,213 @@ +from ..core.SyslabUnit import SyslabUnit +from ..core.datatypes import CompositeMeasurement +from typing import Union +from time import time + +def cast_to_cm(m: Union[CompositeMeasurement, float]): + if type(m) == float: + # TODO: Is there a better way to estimate precision of time.time? + request = CompositeMeasurement(m, timestampMicros=time()*1e6, timePrecision=1000) + elif type(m) == CompositeMeasurement: + request = m + else: + raise TypeError(f"Unknown request type: {type(m)}") + return request + +class EVSE(SyslabUnit): + """The EVSE class represents a charging post in SYSLAB. + The EVSE class is instantiated using a string with the unique name of the charging post, ie. 'which' + + A full list of available charging posts can be found by calling 'EVSE.getAvailableChargingPosts()' + + Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments. + """ + + __CHARGINGPOSTS = { + 'V2G-319': ('syslab-35.syslab.dk', '8080', 'Endesa_V2G'), + 'EVSE-NEVIC-6': ('10.42.245.96', '8080', 'EVSE_NEVIC_6'), + } + + def __init__(self, which=None, host=None, port=None, unitname=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_EVSE/EVSEWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__CHARGINGPOSTS, + host=host, + port=port, + unit_name=unitname, + unit_type="EVSE") + + def isEVPresent(self): + return self._request_resource('isEVPresent') + + def canRemoteStartStop(self): + return self._request_resource('canRemoteStartStop') + + def canLimitChargePower(self): + return self._request_resource('canLimitChargePower') + + def canDischarge(self): + return self._request_resource('canDischarge') + + def hasSOC(self): + return self._request_resource('hasSOC') + + def hasACMeasurements(self): + return self._request_resource('hasACMeasurements') + + def hasDCMeasurements(self): + return self._request_resource('hasDCMeasurements') + + def getAvailableOperatingStates(self): + return self._request_resource('getAvailableOperatingStates') + + def getCurrentOperatingState(self): + return self._request_resource('getCurrentOperatingState') + + def getACActivePower(self): + return self._request_resource('getACActivePower') + + def getACReactivePower(self): + return self._request_resource('getACReactivePower') + + def getACFrequency(self): + return self._request_resource('getACFrequency') + + def getACVoltages(self): + return self._request_resource('getACVoltages') + + def getACCurrents(self): + return self._request_resource('getACCurrents') + + def getSOC(self): + return self._request_resource('getSOC') + + def getMinSOC(self): + return self._request_resource('getMinSOC') + + def getMaxSOC(self): + return self._request_resource('getMaxSOC') + + def startCharge(self): + return self._request_resource('startCharge', (), 'put') + + def stopCharge(self): + return self._request_resource('stopCharge', (), 'put') + + def startDischarge(self): + return self._request_resource('startDischarge', (), 'put') + + def stopDischarge(self): + return self._request_resource('stopDischarge', (), 'put') + + def getMinimumChargePower(self): + return self._request_resource('getMinimumChargePower') + + def getMaximumChargePower(self): + return self._request_resource('getMaximumChargePower') + + def getMinimumDischargePower(self): + return self._request_resource('getMinimumDischargePower') + + def getMaximumDischargePower(self): + return self._request_resource('getMaximumDischargePower') + + def setPowerSetpoint(self, setPoint): + + P_UpperLimit = 10.0 + P_LowerLimit = -10.0 + + return self._request_resource('setP', (), 'put', cast_to_cm(max(P_LowerLimit, min(setPoint, P_UpperLimit))).parseToJSON()) + + def isPSetpointEnabled(self): + return self._request_resource('isPSetpointEnabled') + + def getActiveEnergyImport(self): # TODO not working/implemented + return self._request_resource('getActiveEnergyImport') + + def getActiveEnergyExport(self): # TODO not working/implemented + return self._request_resource('getActiveEnergyExport') + + def getReactiveEnergyImport(self): # TODO not working/implemented + return self._request_resource('getReactiveEnergyImport') + + def getReactiveEnergyExport(self): # TODO not working/implemented + return self._request_resource('getReactiveEnergyExport') + + def getNodeConfiguration(self): # TODO not working/implemented + return self._request_resource('getNodeConfiguration') + + def getEVSEName(self): + return self._request_resource('getEVSEName') + + def getEVSELogicalNameplate(self): # TODO not working/implemented + return self._request_resource('getEVSELogicalNameplate') + + def getEVSEPhysicalNameplate(self): # TODO not working/implemented + return self._request_resource('getEVSEPhysicalNameplate') + + def getEVSEHealth(self): # TODO not working/implemented + return self._request_resource('getEVSEHealth') + + def getGPSLocation(self): # TODO not working/implemented + return self._request_resource('getGPSLocation') + + def getNumberOfEvents(self): # TODO not working/implemented + return self._request_resource('getNumberOfEvents') + + def getEvents(self): # TODO not working/implemented + return self._request_resource('getEvents') + + def getNumberOfAlarms(self): # TODO not working/implemented + return self._request_resource('getNumberOfAlarms') + + def getAlarms(self): # TODO not working/implemented + return self._request_resource('getAlarms') + + def getNumberOfUnacknowledgedAlarms(self): # TODO not working/implemented + return self._request_resource('getNumberOfUnacknowledgedAlarms') + + def getUnacknowledgedAlarms(self): # TODO not working/implemented + return self._request_resource('getUnacknowledgedAlarms') + + def acknowledgeAlarms(self): # TODO not working/implemented + return self._request_resource('acknowledgeAlarms') + + def getRatedP(self): + return self._request_resource('getRatedP') + + def getRatedQ(self): + return self._request_resource('getRatedQ') + + def getRatedS(self): + return self._request_resource('getRatedS') + + def getRatedU(self): + return self._request_resource('getRatedU') + + def getRatedf(self): + return self._request_resource('getRatedf') + + def getInverterName(self): # TODO not working/implemented + return self._request_resource('getInverterName') + + def getInverterLogicalNameplate(self): # TODO not working/implemented + return self._request_resource('getInverterLogicalNameplate') + + def getInverterHealth(self): # TODO not working/implemented + return self._request_resource('getInverterHealth') + + def getDCVoltage(self): # TODO not working/implemented + return self._request_resource('getDCVoltage') + + def getDCPower(self): # TODO not working/implemented + return self._request_resource('getDCPower') + + def isDCContactorClosed(self): # TODO not working/implemented + return self._request_resource('isDCContactorClosed') + + @staticmethod + def getAvailableEVSE(): + return list(EVSE.__CHARGINGPOSTS.keys()) diff --git a/syslab-python/syslab/physical/HeatSwitchBoard.py b/syslab-python/syslab/physical/HeatSwitchBoard.py new file mode 100644 index 0000000..6de8eb1 --- /dev/null +++ b/syslab-python/syslab/physical/HeatSwitchBoard.py @@ -0,0 +1,164 @@ +from ..core.SyslabUnit import SyslabUnit +from ..core.datatypes import CompositeMeasurement, HeatCirculationPumpMode, HeatCirculationPumpState + + +class Valve: + """ + Convenience class which calls methods on its associated heatswitchboard + """ + def __init__(self, label, heatswitchboard): + self.label = label + self.heatswitchboard = heatswitchboard + #TODO: Add methods + +class Pump: + """ + Convenience class which calls methods on its associated heatswitchboard + """ + def __init__(self, label, heatswitchboard): + self.label = label + self.heatswitchboard = heatswitchboard + #TODO: Add methods + +class Meter: + """ + Convenience class which calls methods on its associated heatswitchboard + """ + def __init__(self, label, heatswitchboard): + self.label = label + self.heatswitchboard = heatswitchboard + #TODO: Add methods + + +class HeatSwitchBoard(SyslabUnit): + """ + The HeatSwitchBoard class represents a HeatSwitchBoard in SYSLAB. + The HeatSwitchBoard class is instantiated using a string with the unique name of the switchboard, ie. 'which' + + A full list of available switchboards can be found by calling 'SwitchBoard.getAvailableSwitchBoards()' + + Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments. + """ + + __HEAT_SWITCH_BOARDS = { + '716-h1': ('syslab-33.syslab.dk', '8080', '716-h1'), + } + + @staticmethod + def getAvailableHeatSwitchBoards(): + return list(HeatSwitchBoard.__HEAT_SWITCH_BOARDS.keys()) + + + def __init__(self, which=None, host=None, port=None, unitname=None): + + + baseurl = f'http://{host}:{port}/typebased_WebService_HeatSubstation/HeatSwitchboardWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__HEAT_SWITCH_BOARDS, + host=host, + port=port, + unit_name=unitname, + unit_type="HeatSwitchBoard") + + # Inventory functions + + #TODO Handle these in core/SYSLAB_unit.py by properly handling arrays. This is a bodge for now. + def getHeatMeters(self): + result = self._request_resource('getHeatMeters', check_types=False) + return result + + #TODO, P1: Awaiting Valve[] + def getValves(self): + raise NotImplementedError + + # Valve functions + + def getValvePosition(self, valve) -> CompositeMeasurement: + return self._request_resource('getValvePosition', valve) + + #TODO: Implement + #def setValvePosition(self, valve: str, position: float, timestamp=0): + def setValvePosition(self, valve: str, position: CompositeMeasurement): + """ + Sets *valve* to *position*. Position must be float in the + interval from 0.0 to 1.0 + Position = 0.0 => *valve* fully closed + Position = 1.0 => *valve* fully open + """ + assert 0.0 <= position.value and position.value <= 1.0 + return self._request_resource('setValvePosition', (valve), 'put', + position.parseToJSON()) + #CompositeMeasurement(value=position, timestampMicros=timestamp).parseToJSON()) + + # Meter functions + + def getBackTemperature(self, meter) -> CompositeMeasurement: + return self._request_resource('getBackTemperature', meter) + + def getFlow(self, meter) -> CompositeMeasurement: + return self._request_resource('getFlow', meter) + + def getThermalPower(self, meter) -> CompositeMeasurement: + return self._request_resource('getThermalPower', meter) + + def getFwdTemperature(self, meter) -> CompositeMeasurement: + return self._request_resource('getFwdTemperature', meter) + + def getPressure(self, meter) -> CompositeMeasurement: + return self._request_resource('getPressure', meter) + + def getVolume(self, meter) -> CompositeMeasurement: + return self._request_resource('getVolume', meter) + + def getMass(self, meter) -> CompositeMeasurement: + return self._request_resource('getMass', meter) + + def getHeatEnergy(self, meter) -> CompositeMeasurement: + return self._request_resource('getHeatEnergy', meter) + + def getCoolingEnergy(self, meter) -> CompositeMeasurement: + return self._request_resource('getCoolingEnergy', meter) + + # Pump functions + + def getPumpState(self, pump) -> HeatCirculationPumpState: + return self._request_resource('getPumpState', pump) + + def getPumpControlMode(self, pump) -> HeatCirculationPumpMode: + return self._request_resource('getPumpControlMode', pump) + + def setPumpControlMode(self, pump, mode: HeatCirculationPumpMode): + return self._request_resource('setPumpControlMode', (pump), 'put', mode.parseToJSON()) + + def setPumpMaxFlow(self, pump, limit: CompositeMeasurement): + return self._request_resource('setPumpMaxFlow', (pump), 'put', limit.parseToJSON()) + + def startPump(self, pump): + return self._request_resource('startPump', pump) + + def stopPump(self, pump): + return self._request_resource('stopPump', pump) + + #TODO: Split into three functions that check against current pump mode. + def setPumpSetpoint(self, pump, setpoint: CompositeMeasurement): + """ + Sets the target for the pump. + NOTE: How to interpret the setpoint depends on the mode which the pump is in. + Here be dragons. + """ + return self._request_resource('setPumpSetpoint', (pump), 'put', setpoint.parseToJSON()) + + def getPumpHead(self, pump) -> CompositeMeasurement: + return self._request_resource('getPumpHead', pump) + + def getPumpFlow(self, pump) -> CompositeMeasurement: + return self._request_resource('getPumpFlow', pump) + + def getPumpRPM(self, pump) -> CompositeMeasurement: + return self._request_resource('getPumpRPM', pump) + + def getPumpRelPerformance(self, pump) -> CompositeMeasurement: + return self._request_resource('getPumpPerformance', pump) + diff --git a/syslab-python/syslab/physical/MeteoMast.py b/syslab-python/syslab/physical/MeteoMast.py new file mode 100644 index 0000000..87d80b4 --- /dev/null +++ b/syslab-python/syslab/physical/MeteoMast.py @@ -0,0 +1,72 @@ +from ..core.SyslabUnit import SyslabUnit + + +class MeteoMast(SyslabUnit): + __METMASTS = { + 'metmast1': ('syslab-13.syslab.dk', '8080', 'meteo1'), + } + + def __init__(self, which=None, host=None, port=None, unitname=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_Meteo/MeteoStationWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__METMASTS, + host=host, + port=port, + unit_name=unitname, + unit_type="MeteoMast") + + + + def getAirPressure(self, instrumentIndex: int or str): + if type(instrumentIndex) is str: + return self._request_resource('getAirPressure2', instrumentIndex) + if type(instrumentIndex) is int: + return self._request_resource('getAirPressure1', instrumentIndex) + + def getAirTemperature(self, instrumentIndex: int or str): + if type(instrumentIndex) is str: + return self._request_resource('getAirTemperature2', instrumentIndex) + if type(instrumentIndex) is int: + return self._request_resource('getAirTemperature1', instrumentIndex) + + def getHeightAboveGround(self): + return self._request_resource('getHeightAboveGround') + + def getInsolation(self, instrumentIndex: int or str): + if type(instrumentIndex) is str: + return self._request_resource('getInsolation2', instrumentIndex) + if type(instrumentIndex) is int: + return self._request_resource('getInsolation1', instrumentIndex) + + def getMeteoGPSLocation(self): + return self._request_resource('getMeteoGPSLocation') + + def getRelativeHumidity(self, instrumentIndex: int or str): + if type(instrumentIndex) is str: + return self._request_resource('getRelativeHumidity2', instrumentIndex) + if type(instrumentIndex) is int: + return self._request_resource('getRelativeHumidity1', instrumentIndex) + + def getWindDirection(self, instrumentIndex: int or str): + if type(instrumentIndex) is str: + return self._request_resource('getWindDirection2', instrumentIndex) + if type(instrumentIndex) is int: + return self._request_resource('getWindDirection1', instrumentIndex) + + def getWindSpeed(self, instrumentIndex: int or str): + if type(instrumentIndex) is str: + return self._request_resource('getWindSpeed2', instrumentIndex) + if type(instrumentIndex) is int: + return self._request_resource('getWindSpeed1', instrumentIndex) + + def getInstrumentNames(self): + return self._request_resource('getInstrumentNames') + + def getSupportedInstrumentTypes(self): + return self._request_resource('getSupportedInstrumentTypes') + + def getInstrumentNamesForType(self, instrumentType: str): + return self._request_resource('getInstrumentNamesForType', instrumentType) diff --git a/syslab-python/syslab/physical/Photovoltaics.py b/syslab-python/syslab/physical/Photovoltaics.py new file mode 100644 index 0000000..2915c78 --- /dev/null +++ b/syslab-python/syslab/physical/Photovoltaics.py @@ -0,0 +1,61 @@ +from ..core.SyslabUnit import SyslabUnit + + +class Photovoltaics(SyslabUnit): + """The Photovoltaics class represents a photovoltaic panel array in SYSLAB. + The Photovoltaics class is instantiated using a string with the unique name of the dumpload, ie. 'which' + + A full list of available panel arrays can be found by calling 'Photovoltaics.getAvailablePhotovoltaics()' + + Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments. + """ + + __PHOTOVOLTAICS = { + 'pv319': ('syslab-24.syslab.dk', '8080', 'pv319'), + 'pv715': ('syslab-10.syslab.dk', '8080', 'pv715'), + 'pv117': ('syslab-07.syslab.dk', '8080', 'pv117'), + 'simlab-03': ('192.168.0.103', '8080', 'pv715'), + 'simlab-13': ('192.168.0.113', '8080', 'pv319'), + 'vpv319': ('simlab-24', '8080', 'pv319'), + 'vpv715': ('simlab-10', '8080', 'pv715'), + } + + def __init__(self, which=None, host=None, port=None, unitname=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_PV/PVSystemWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__PHOTOVOLTAICS, + host=host, + port=port, + unit_name=unitname, + unit_type="Photovoltaics") + + def getACActivePower(self): + return self._request_resource('getACActivePower') + + def getACReactivePower(self): + return self._request_resource('getACReactivePower') + + def getPacLimit(self): + return self._request_resource('getPacLimit') + + def getQSetpoint(self): + return self._request_resource('getQSetpoint') + + def getRatedPower(self): + return self._request_resource('getRatedP') + + def getName(self): + return self._request_resource('getSystemName') + + def setPacLimit(self, setPoint): + return self._request_resource('setPacLimit', setPoint, 'put') + + def setQSetpoint(self, Q): + return self._request_resource('setQ', Q, 'put') + + @staticmethod + def getAvailablePhotovoltaics(): + return list(Photovoltaics.__PHOTOVOLTAICS.keys()) diff --git a/syslab-python/syslab/physical/SwitchBoard.py b/syslab-python/syslab/physical/SwitchBoard.py new file mode 100644 index 0000000..383fc79 --- /dev/null +++ b/syslab-python/syslab/physical/SwitchBoard.py @@ -0,0 +1,144 @@ +from ..core.SyslabUnit import SyslabUnit + + +class SwitchBoard(SyslabUnit): + """ + The SwitchBoard class represents a SwitchBoard in SYSLAB. + The SwitchBoard class is instantiated using a string with the unique name of the switchboard, ie. 'which' + + A full list of available switchboards can be found by calling 'SwitchBoard.getAvailableSwitchBoards()' + + Alternatively, the user may specify a host and port to connect to via the *host* and *port* arguments. + """ + + __SWITCH_BOARDS = { + '319-2':('syslab-01.syslab.dk', '8080', '319-2'), + '319-3':('syslab-52.syslab.dk', '8080', '319-3'), + '117-2':('syslab-11.syslab.dk', '8080', '117-2'), + '117-4':('syslab-11.syslab.dk', '8080', '117-4'), + '117-5':('syslab-11.syslab.dk', '8080', '117-5'), + '117-6':('syslab-26.syslab.dk', '8080', '117-6'), + '715-2':('syslab-09.syslab.dk', '8080', '715-2'), + '716-2':('syslab-29.syslab.dk', '8080', '716-2'), + 'simlab-00': ('192.168.0.1', '8080', '319-2'), + 'simlab-10': ('192.168.0.2', '8080', '319-2'), + 'simlab-20': ('192.168.0.3', '8080', '319-2'), + 'vswitchboard': ('simlab-01', '8080', '319-2') + } + + def __init__(self, which=None, host=None, port=None, unitname=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_Substation/StandardSubstationWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__SWITCH_BOARDS, + host=host, + port=port, + unit_name=unitname, + unit_type="SwitchBoard") + + def getName(self): + return self._request_resource('getSwitchboardName') + + def getActivePower(self, instrName): + return self._request_resource('getActivePower', instrName) + + def getPhaseActivePower(self, instrName): + return self._request_resource('getPhaseActivePower', instrName) + + def getReactivePower(self, instrName): + return self._request_resource('getReactivePower', instrName) + + def getPhaseReactivePower(self, instrName): + return self._request_resource('getPhaseReactivePower', instrName) + + def getApparentPower(self, instrName): + return self._request_resource('getApparentPower', instrName) + + def getPhaseApparentPower(self, instrName): + return self._request_resource('getPhaseApparentPower', instrName) + + def getPowerFactor(self, instrName): + return self._request_resource('getPowerFactor', instrName) + + def getPhasePowerFactor(self, instrName): + return self._request_resource('getPhasePowerFactor', instrName) + + def getActiveEnergyExport(self, instrName): + return self._request_resource('getActiveEnergyExport', instrName) + + def getActiveEnergyImport(self, instrName): + return self._request_resource('getActiveEnergyImport', instrName) + + def getReactiveEnergyExport(self, instrName): + return self._request_resource('getReactiveEnergyExport', instrName) + + def getReactiveEnergyImport(self, instrName): + return self._request_resource('getReactiveEnergyImport', instrName) + + def isAuthenticated(self): + return self._request_resource('isAuthenticated') + + def getBreakerState(self, breakerName): + return self._request_resource('getBreakerState', breakerName) + + def getFrequency(self, instrName): + return self._request_resource('getFrequency', instrName) + + def getBayNames(self): + return self._request_resource('getBayNames') + + def getBreakerName(self, busbarName, bayName): + return self._request_resource('getBreakerName', (busbarName, bayName)) + + def getInstrumentNames(self, bayName): + return self._request_resource('getInstrumentNamesPerBay', bayName) + + def getBusbarNames(self): + return self._request_resource('getBusbarNames') + + def getInterphaseVoltage(self, instrName): + return self._request_resource('getInterphaseVoltage', instrName) + + def getInterphaseVoltages(self, instrName): + return self._request_resource('getInterphaseVoltages', instrName) + + def getPhaseVoltage(self, instrName): + return self._request_resource('getPhaseVoltage', instrName) + + def getPhaseVoltages(self, instrName): + return self._request_resource('getPhaseVoltages', instrName) + + def getVoltageImbalance(self, instrName): + return self._request_resource('getVoltageImbalance', instrName) + + def getPhaseCurrent(self, instrName): + return self._request_resource('getPhaseCurrent', instrName) + + def getNeutralCurrent(self, instrName): + return self._request_resource('getNeutralCurrent', instrName) + + def getPhaseCurrents(self, instrName): + return self._request_resource('getPhaseCurrents', instrName) + + def getCurrentImbalance(self, instrName): + return self._request_resource('getCurrentImbalance', instrName) + + def authenticate(self, user, password): + return self._request_resource('authenticate', (user, password), 'put') + + def logout(self): + return self._request_resource('logout', (), 'put') + + def closeBreaker(self, breakerName): + return self._request_resource('closeBreaker', breakerName, 'put') + + def openBreaker(self, breakerName): + return self._request_resource('openBreaker', (breakerName), 'put') + + @staticmethod + def getAvailableSwitchBoards(): + return list(SwitchBoard.__SWITCH_BOARDS.keys()) + + diff --git a/syslab-python/syslab/physical/WindTurbine.py b/syslab-python/syslab/physical/WindTurbine.py new file mode 100644 index 0000000..64c8f33 --- /dev/null +++ b/syslab-python/syslab/physical/WindTurbine.py @@ -0,0 +1,36 @@ +from ..core.SyslabUnit import SyslabUnit + + +class WindTurbine(SyslabUnit): + __TURBINES = { + 'gaia1': ('syslab-03.syslab.dk', '8080', 'gaia1'), + 'simlab-01': ('192.168.0.101', '8080', 'gaia1'), + 'vgaia1': ('simlab-03', '8080', 'gaia1'), + } + + def __init__(self, which=None, host=None, port=None, unitname=None): + + baseurl = 'http://{host}:{port}/typebased_WebService_WTGS/GaiaWindTurbineWebService/{unit_name}/' + super().__init__( + baseurl=baseurl, + which=which, + units=self.__TURBINES, + host=host, + port=port, + unit_name=unitname, + unit_type="WindTurbine") + + def getGeneratorName(self): + return self._request_resource('getGeneratorName') + + def getName(self): + return self.getGeneratorName() + + def getActivePower(self): + return self._request_resource('getActivePower') + + def getReactivePower(self): + return self._request_resource('getReactivePower') + + def getWindspeed(self): + return self._request_resource('getWindspeedOutsideNacelle') diff --git a/syslab-python/syslab/physical/__init__.py b/syslab-python/syslab/physical/__init__.py new file mode 100644 index 0000000..e663243 --- /dev/null +++ b/syslab-python/syslab/physical/__init__.py @@ -0,0 +1,10 @@ +from .Battery import Battery +from .DieselGenerator import DieselGenerator +from .Dumpload import Dumpload +from .HeatSwitchBoard import HeatSwitchBoard +from .MeteoMast import MeteoMast +from .Photovoltaics import Photovoltaics +from .SwitchBoard import SwitchBoard +from .WindTurbine import WindTurbine +from .B2BConverter import B2BConverter +from .EVSE import EVSE diff --git a/syslab-python/syslab/virtual/FlexHouse_real.py b/syslab-python/syslab/virtual/FlexHouse_real.py new file mode 100644 index 0000000..25fb997 --- /dev/null +++ b/syslab-python/syslab/virtual/FlexHouse_real.py @@ -0,0 +1,47 @@ +class FlexHouse_real(): + def __init__(self): + pass + + def setActivePower(self, power_reference): + if power_reference < -10: + power_reference = -10 + + if power_reference > 0: + raise ValueError("Positive power means production") + self.__setValue('flexHousePowerRef_kW',-power_reference) + + def getTemperature(self): + return self.__getValue('flexHouseTemperature_C') + + def getActivePower(self): + return self.__getValue('flexHousePower_kW') + + def __getValue(self, key): + from requests import get, auth + import re + + if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key))) + if key is '': raise TypeError('Key should be an empty string') + + url = 'http://whiteboard.syslab.dk/wbget.php' + r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + entries = r.text.split('\n') + + result = None + + for entry in entries: + entry = entry.rstrip().lstrip() + g = re.match('SYSLAB@(\d+):{0}=(.+);'.format(key), entry) + if g is None: + continue + else: + g = g.groups() + result = g[1] + break + + return result + + def __setValue(selv, key, value): + from requests import post, auth + url = '{0}{1}?source=SYSLAB&{2}={3}'.format('http://whiteboard.syslab.dk/', 'wbset.php', key, str(value)) + post(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) \ No newline at end of file diff --git a/syslab-python/syslab/virtual/FlexHouse_sim.py b/syslab-python/syslab/virtual/FlexHouse_sim.py new file mode 100644 index 0000000..3b0720d --- /dev/null +++ b/syslab-python/syslab/virtual/FlexHouse_sim.py @@ -0,0 +1,51 @@ +from ..physical.Dumpload import Dumpload +from ..virtual.MetMast import MetMast +from datetime import datetime + +class FlexHouse_sim(Dumpload): + def __init__(self, which='localhost'): + super().__init__(which) + self.internal_temperature = 20 # Celsius + self.time = datetime.now() + self.mt = MetMast() + + # The variables below are only relevant for implementing an advanced house model. + #self.__Th = 20 # internal heater state + #self.__Te = 19 # envelope state + #self.__Aw = 14.351 # m^2 + #self.__Ce = 4.741 # kWh/C + #self.__Ch = 0.00225 # kWh/Cst + #self.__Ci = 2.555 # kWh/C + #self.__Rea = 3.265 # C/kW + #self.__Rie = 0.817 # C/kW + #self.__Ria = 37.005 # C/kW + #self.__Rih = 140.44 # C/kW + + def setPowerConsumption(self, power_reference): + if power_reference > 10: + self.startLoad() + power_reference = 10.0 + if power_reference < 0: + raise ValueError("Negative power means production") + if power_reference == 0: + self.stopLoad() + self.setPowerSetPoint(power_reference) + + def step_sim(self): + time_now = datetime.now() + time_delta = (time_now - self.time).total_seconds() + T_ambient = self.mt.getTemperature() + irradiance = self.mt.getInsolation()/1000 + + # Below is a simple house model. A more complex one can be implemented + self.internal_temperature += 0.5*(self.getActivePower().value/3600*time_delta) - 0.00025*((self.internal_temperature-T_ambient)*time_delta) + 0.001*irradiance*time_delta + #self.__Th = 20 # internal heater state + #self.__Te = 19 # envelope state + self.time = time_now + + def getTemperature(self): + self.step_sim() + return self.internal_temperature + + + diff --git a/syslab-python/syslab/virtual/FlexHouse_sim_batt.py b/syslab-python/syslab/virtual/FlexHouse_sim_batt.py new file mode 100644 index 0000000..b1ce14b --- /dev/null +++ b/syslab-python/syslab/virtual/FlexHouse_sim_batt.py @@ -0,0 +1,52 @@ +from ..physical.Battery import Battery +from datetime import datetime + + +class FlexHouse_sim_batt(Battery): + def __init__(self, which='localhost'): + super().__init__(which) + self.internal_temperature = 20 # Celsius + self.time = datetime.now() + self.loss = 1 # kW + if self.getRemainingFloodTime() == 300: + #print('Battery is not started. I will initiate start sequence, please retry in 5 minutes') + self.startBattery() + raise SystemError('Battery is not started. I will initiate start sequence, please retry in 5 minutes') + if (self.getRemainingFloodTime() > 1) and (self.getRemainingFloodTime() < 300): + #print('Battery is not ready yet') + raise SystemError('Battery is not ready yet, please wait {} seconds'.format(self.getRemainingFloodTime())) + + # The variables below are only relevant for implementing and advanced house model. + #self.__Th = 20 # internal heater state + #self.__Te = 19 # envelope state + #self.__Aw = 14.351 # m^2 + #self.__Ce = 4.741 # kWh/C + #self.__Ch = 0.00225 # kWh/C + #self.__Ci = 2.555 # kWh/C + #self.__Rea = 3.265 # C/kW + #self.__Rie = 0.817 # C/kW + #self.__Ria = 37.005 # C/kW + #self.__Rih = 140.44 # C/kW + + def setPowerConsumption(self, power_reference): + if power_reference > 10: + power_reference = 10 + if power_reference < 0: + raise ValueError("Negative power means production") + self.setActivePower(power_reference) + + def step_sim(self): + time_now = datetime.now() + time_delta = (time_now - self.time).total_seconds() + # Below is a simple house model. A more complex one can be implemented + self.internal_temperature += 0.5*(self.getActivePower().value/3600*time_delta) - 2*(self.loss/3600*time_delta) + #self.__Th = 20 # internal heater state + #self.__Te = 19 # envelope state + self.time = time_now + + def getTemperature(self): + self.step_sim() + return self.internal_temperature + + + diff --git a/syslab-python/syslab/virtual/MetMast_wb.py b/syslab-python/syslab/virtual/MetMast_wb.py new file mode 100644 index 0000000..8389c17 --- /dev/null +++ b/syslab-python/syslab/virtual/MetMast_wb.py @@ -0,0 +1,40 @@ +class MetMast(): + def __init__(self): + pass + + def getTemperature(self): + return self.__getValue('OutsideTemperature') + + def getInsolation(self): + return self.__getValue('Insolation') + + def getWindDirection(self): + return self.__getValue('WindDirection') + + def getWindSpeed(self): + return self.__getValue('WindSpeed') + + def __getValue(self, key): + from requests import get, auth + import re + + if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key))) + assert key is not '', 'Key should not be an empty string' + + url = 'http://whiteboard.syslab.dk/wbget.php' + r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + entries = r.text.split('\n') + + result = None + + for entry in entries: + entry = entry.rstrip().lstrip() + g = re.match('SYSLAB@(\d+):{0}=(.+);'.format(key), entry) + if g is None: + continue + else: + g = g.groups() + result = g[1] + break + + return result diff --git a/syslab-python/syslab/virtual/WaterBoiler.py b/syslab-python/syslab/virtual/WaterBoiler.py new file mode 100644 index 0000000..1ef630e --- /dev/null +++ b/syslab-python/syslab/virtual/WaterBoiler.py @@ -0,0 +1,28 @@ +from ..physical.Dumpload import Dumpload +from datetime import datetime + + +class WaterBoiler(Dumpload): + + def __init__(self, which='localhost'): + super().__init__(which) + + self.energy_state = 10 # kWh + self.energy_max = 15 + self.time = datetime.now() + self.loss = 1 # kW + + def step_sim(self): + time_now = datetime.now() + time_delta = (time_now - self.time).total_seconds() + power = self.getActivePower().value + self.energy_state += (power - self.loss)/3600*time_delta + self.time = time_now + + def getSOC(self): + self.step_sim() + if self.energy_state > self.energy_max: + self.energy_state = self.energy_max + elif self.energy_state < 0: + self.energy_state = 0.0 + return self.energy_state diff --git a/syslab-python/syslab/virtual/__init__.py b/syslab-python/syslab/virtual/__init__.py new file mode 100644 index 0000000..1e136c6 --- /dev/null +++ b/syslab-python/syslab/virtual/__init__.py @@ -0,0 +1 @@ +from . import * diff --git a/syslab-python/syslab/whiteboard/CommModule.py b/syslab-python/syslab/whiteboard/CommModule.py new file mode 100644 index 0000000..16f5096 --- /dev/null +++ b/syslab-python/syslab/whiteboard/CommModule.py @@ -0,0 +1,143 @@ +from syslab.whiteboard.WhiteBoardEntry import WhiteBoardEntry + +class CommModule(): + __BASE_URL = 'http://whiteboard.syslab.dk/' + + def __init__(self, namespace): + if not type(namespace) is str or len(namespace) == 0: raise TypeError('Namespace should be an non-empty string, found "{0}"'.format(type(namespace))) + + self.__namespace = namespace + self.__entries = {} + + def appendValue(self, key, value): + values = self.getList(key) + + if values == None: + self.publishToWhiteBoardServer(key, '[{0}]'.format(str(value))) + else: + values.append(value) + self.publishList(key, values) + + def getList(self, key): + from ast import literal_eval + if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key))) + + entry = self.getFromWhiteBoardServer(key) + + values = None + if entry is not None: + values_str = str(entry.value) + values_str = values_str.replace('[','["').replace(']','"]'.replace(',','","')) + values = literal_eval(values_str) + if type(values) is not list: + values = [values, ] + return values + + def getAllEntries(self): + return self.__entries + + def update(self): + from requests import get, auth + import re + + url = CommModule.__BASE_URL + 'wbget.php' + r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + entries = r.text.split('\n') + + result = {} + for entry in entries: + entry = entry.rstrip().lstrip() + g = re.match('SYSLAB@(\d+):{0}::(\w+)=(.+);'.format(self.__namespace), entry) + if g is None: + continue + else: + g = g.groups() + self.__entries[g[1]] = WhiteBoardEntry(g[1], g[2], g[0]) + + def printEntries(self): + if len(self.__entries)==0: + print('No WhiteBoard entries found for namespace: {0}'.format(self.__namespace)) + return + + for e in self.__entries: + print(e) + + def publishList(self, key, values): + from requests import post, auth + if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key))) + if not type(values) is list: raise TypeError('Values should be represented by a list'.format(type(values))) + + values_str = "[{0}]".format(",".join(map(str,values))) + + url = '{0}{1}?source=SYSLAB&{2}::{3}={4}'.format(CommModule.__BASE_URL, 'wbset.php', self.__namespace, str(key), values_str) + post(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + + def getFromWhiteBoardServer(self, key): + """ + Go in and read/write the value from a key from the whiteboard. The value + should use decimal dot, not comma! + """ + from requests import get, auth + import re + + if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key))) + if key is '': raise TypeError('Key should be an empty string') + + url = CommModule.__BASE_URL+'wbget.php' + r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + entries = r.text.split('\n') + + result = None + + for entry in entries: + entry = entry.rstrip().lstrip() + g = re.match('SYSLAB@(\d+):{0}::{1}=(.+);'.format(self.__namespace, key), entry) + if g is None: + continue + else: + g = g.groups() + result = WhiteBoardEntry(key, g[1], g[0]) + break + + return result + + def publishToWhiteBoardServer(self, key, value): + from requests import post, auth + if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key))) + if type(value) is list or type(value) is dict or type(value) is tuple: raise TypeError('This function only supports single values, found {0}'.format(type(value))) + + url = '{0}{1}?source=SYSLAB&{2}::{3}={4}'.format(CommModule.__BASE_URL, 'wbset.php', self.__namespace, str(key), str(value)) + post(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + + def getPoolKeys(self): + from requests import get, auth + import re + + url = CommModule.__BASE_URL+'wbget.php' + r = get(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + entries = r.text.split('\n') + + result = {} + for entry in entries: + entry = entry.rstrip().lstrip() + g = re.match('SYSLAB@(\d+):{0}::(\w+)=(.+)'.format(self.__namespace), entry) + if g is None: + continue + else: + g = g.groups() + result[g[1]] = g[2] + + return result + + def clearKey(self, key): + from requests import post, auth + if not type(key) is str: raise TypeError('Key should be a string, found {0}'.format(type(key))) + + url = '{0}{1}?source=SYSLAB&key={2}::{3}'.format(CommModule.__BASE_URL, 'wbclean.php', self.__namespace, str(key)) + post(url, auth=auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + + def clearAllKeys(self): + entries = self.getPoolKeys() + + for key in entries.keys(): + self.clearKey(key) diff --git a/syslab-python/syslab/whiteboard/WhiteBoardEntry.py b/syslab-python/syslab/whiteboard/WhiteBoardEntry.py new file mode 100644 index 0000000..54587a3 --- /dev/null +++ b/syslab-python/syslab/whiteboard/WhiteBoardEntry.py @@ -0,0 +1,21 @@ +class WhiteBoardEntry: + def __init__(self, key, value, time): + self.__key = key + self.__value = value + self.__time = int(time) + + @property + def key(self): + return self.__key + + @property + def value(self): + return self.__value + + @property + def time(self): + return self.__time + + def __str__(self): + from datetime import datetime + return 'WhiteBoardEntry: {0} -> {1} (@time: {2})'.format(self.key, self.value, datetime.fromtimestamp(self.time)) diff --git a/syslab-python/syslab/whiteboard/Whiteboard.py b/syslab-python/syslab/whiteboard/Whiteboard.py new file mode 100644 index 0000000..24a2347 --- /dev/null +++ b/syslab-python/syslab/whiteboard/Whiteboard.py @@ -0,0 +1,23 @@ +from bs4 import BeautifulSoup +import requests + +def getFromWhiteBoardServer(key): + """ + Go in and read/write the value from a key from the whiteboard. The value + should use decimal dot, not comma! + """ + + url = 'http://whiteboard.syslab.dk/wbget.php?mode=html' + r = requests.get(url, auth=requests.auth.HTTPBasicAuth('twinPV99', 'twinPV99')) + data = r.text + print(data) + soup = BeautifulSoup(data) + table = soup.find('table') + key = table.find('td', text=key) + value = key.findNext('td') + return value.text + + +def publishToWhiteBoardServer(key, value): + url = 'http://whiteboard.syslab.dk/wbset.php?source=SYSLAB&' + key + '=' + str(value) + r = requests.post(url, auth=requests.auth.HTTPBasicAuth('twinPV99', 'twinPV99')) diff --git a/syslab-python/syslab/whiteboard/__init__.py b/syslab-python/syslab/whiteboard/__init__.py new file mode 100644 index 0000000..3a95699 --- /dev/null +++ b/syslab-python/syslab/whiteboard/__init__.py @@ -0,0 +1,3 @@ +from .CommModule import CommModule +from .WhiteBoardEntry import WhiteBoardEntry +from .Whiteboard import publishToWhiteBoardServer, getFromWhiteBoardServer diff --git a/syslab-python/syslab_ctrl_log_SP_1687041679.json b/syslab-python/syslab_ctrl_log_SP_1687041679.json new file mode 100644 index 0000000..e69de29 diff --git a/syslab-python/tests/__init__.py b/syslab-python/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/syslab-python/tests/runall.py b/syslab-python/tests/runall.py new file mode 100644 index 0000000..e69de29 diff --git a/util.py b/util.py new file mode 100644 index 0000000..321e0e9 --- /dev/null +++ b/util.py @@ -0,0 +1,5 @@ +def clamp(a, x, b): + """ + Restrict x to lie in the range [a, b] + """ + return max(a, min(x, b))