pqopen-lib 0.7.2__tar.gz → 0.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pqopen-lib
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: A power quality processing library for calculating parameters from waveform data
5
5
  Project-URL: Homepage, https://github.com/DaqOpen/pqopen-lib
6
6
  Project-URL: Issues, https://github.com/DaqOpen/pqopen-lib/issues
@@ -493,13 +493,13 @@ class PowerSystem(object):
493
493
  if "w_pos" in self._calc_channels["multi_period"]["energy"]:
494
494
  prev_w_pos_value = self._calc_channels["multi_period"]["energy"]["w_pos"].last_sample_value
495
495
  energy = (stop_sidx - start_sidx)/self._samplerate/3600*p_sum if p_sum > 0 else 0.0 # Energy in Wh
496
- self._calc_channels["multi_period"]["energy"]["w_pos"].put_data_single(stop_sidx, prev_w_pos_value + energy)
496
+ self._calc_channels["multi_period"]["energy"]["w_pos"].put_data_single(stop_sidx, prev_w_pos_value + float(energy))
497
497
 
498
498
  # Calculate Negative Energy
499
499
  if "w_neg" in self._calc_channels["multi_period"]["energy"]:
500
500
  prev_w_neg_value = self._calc_channels["multi_period"]["energy"]["w_neg"].last_sample_value
501
501
  energy = -(stop_sidx - start_sidx)/self._samplerate/3600*p_sum if p_sum < 0 else 0.0 # Energy in Wh
502
- self._calc_channels["multi_period"]["energy"]["w_neg"].put_data_single(stop_sidx, prev_w_neg_value + energy)
502
+ self._calc_channels["multi_period"]["energy"]["w_neg"].put_data_single(stop_sidx, prev_w_neg_value + float(energy))
503
503
 
504
504
  # Calculate unbalance (3-phase)
505
505
  if "unbal_0" in self._calc_channels["multi_period"]["voltage"]:
@@ -342,6 +342,14 @@ class StorageController(object):
342
342
  topic_prefix=ep_config.get("topic_prefix", "pqopen/data"),
343
343
  qos=ep_config.get("qos", 0))
344
344
  self._configured_eps["ha_mqtt"] = ha_mqtt_storage_endpoint
345
+ elif ep_type == "victron_mqtt":
346
+ victron_mqtt_storage_endpoint = VictronStorageEndpoint(name="victron_mqtt",
347
+ device_id=device_id,
348
+ mqtt_host=ep_config.get("hostname", "localhost"),
349
+ client_id=ep_config.get("client_id", "pqopen-victron"),
350
+ topic_prefix=ep_config.get("topic_prefix", "pqopen/meter"),
351
+ qos=ep_config.get("qos", 0))
352
+ self._configured_eps["victron_mqtt"] = victron_mqtt_storage_endpoint
345
353
  else:
346
354
  raise NotImplementedError(f"{ep_type:s} not implemented")
347
355
  for sp_name, sp_config in storage_plans.items():
@@ -572,7 +580,53 @@ class HomeAssistantStorageEndpoint(StorageEndpoint):
572
580
  self._client.publish(self._topic_prefix, json.dumps(data).encode("utf-8"), qos=self._qos)
573
581
  logger.debug("Published HA-MQTT Message")
574
582
 
575
-
583
+ class VictronStorageEndpoint(StorageEndpoint):
584
+ """Represents a MQTT endpoint (MQTT) for Victron Power Meter Emulation.
585
+ https://github.com/mr-manuel/venus-os_dbus-mqtt-grid
586
+ """
587
+ def __init__(self,
588
+ name: str,
589
+ device_id: str,
590
+ mqtt_host: str,
591
+ client_id: str,
592
+ topic_prefix: str = "pqopen/victron",
593
+ qos: int = 0):
594
+ """ Create an endpoint for Victron via MQTT
595
+
596
+ Parameters:
597
+ name: The name of the endpoint
598
+ device_id: The device Id
599
+ mqtt_host: hostname of the MQTT broker.
600
+ client_id: name to be used for mqtt client identification
601
+ topic_prefix: topic prefix before device-id, no trailing /
602
+ qos: mqtt quality of service. valid values 0, 1, 2
603
+ """
604
+ super().__init__(name, measurement_id=None)
605
+ self._device_id = device_id
606
+ self._topic_prefix = topic_prefix
607
+ self._qos = qos
608
+ self._client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id, clean_session=True)
609
+ self._client.on_connect = self._on_connect
610
+ self._client.connect_async(host=mqtt_host)
611
+ self._client.loop_start()
612
+
613
+ def _on_connect(self, client, userdata, flags, reason_code, properties):
614
+ logger.debug("Victron-MQTT Connected")
615
+
616
+ def write_aggregated_data(self, data: dict, timestamp_us: int, interval_seconds: int, **kwargs):
617
+ """ Write an aggregated data message
618
+
619
+ Parameters:
620
+ data: The data object to be sent
621
+ timestamp_us: Timestamp (in µs) of the data set
622
+ interval_seconds: Aggregation intervall, used as data tag
623
+ """
624
+ if "P" in data:
625
+ payload = {"grid": {"power": data["P"]}}
626
+ self._client.publish(self._topic_prefix, json.dumps(payload).encode("utf-8"), qos=self._qos)
627
+ logger.debug("Published Victron MQTT Message")
628
+ else:
629
+ logger.debug("Can't publish Victron MQTT Message, P not found")
576
630
 
577
631
  class CsvStorageEndpoint(StorageEndpoint):
578
632
  """Represents a csv storage endpoint"""
@@ -12,7 +12,7 @@ packages = ["pqopen"]
12
12
 
13
13
  [project]
14
14
  name = "pqopen-lib"
15
- version = "0.7.2"
15
+ version = "0.7.4"
16
16
  dependencies = [
17
17
  "numpy",
18
18
  "daqopen-lib",
@@ -451,6 +451,37 @@ class TestPowerSystemCalculationThreePhase(unittest.TestCase):
451
451
  p_neg, sidx = self.power_system.output_channels["P_neg"].read_data_by_acq_sidx(0, u1_values.size)
452
452
  self.assertIsNone(np.testing.assert_allclose(p_neg, expected_p_neg, rtol=0.01))
453
453
 
454
+ def test_energy_calc_pos_highoffset(self):
455
+ t = np.linspace(0, 1, int(self.power_system._samplerate), endpoint=False)
456
+ u1_values = 1.0*np.sqrt(2)*np.sin(2*np.pi*50*t)
457
+ u2_values = 1.0*np.sqrt(2)*np.sin(2*np.pi*50*t - 120*np.pi/180)
458
+ u3_values = 1.0*np.sqrt(2)*np.sin(2*np.pi*50*t + 120*np.pi/180)
459
+ i1_values = 1.0*np.sqrt(2)*np.sin(2*np.pi*50*t)
460
+ i2_values = 1.0*np.sqrt(2)*np.sin(2*np.pi*50*t -120*np.pi/180)
461
+ i3_values = 1.0*np.sqrt(2)*np.sin(2*np.pi*50*t+ 120*np.pi/180)
462
+
463
+ Path(SCRIPT_DIR+"/data_files/energy.json").write_text(json.dumps({"W_pos": 1_000_000.1, "W_neg": 10}))
464
+
465
+ expected_w_pos = np.array([1, 2, 3, 4])*3*0.2/3600 + 1_000_000.1
466
+ expected_w_neg = np.array([1, 2, 3, 4])*0.0/3600 + 10
467
+
468
+ self.power_system.enable_energy_channels(Path(SCRIPT_DIR+"/data_files/energy.json"))
469
+ self.u1_channel.put_data(u1_values)
470
+ self.u2_channel.put_data(u2_values)
471
+ self.u3_channel.put_data(u3_values)
472
+
473
+ self.i1_channel.put_data(i1_values)
474
+ self.i2_channel.put_data(i2_values)
475
+ self.i3_channel.put_data(i3_values)
476
+
477
+ self.power_system.process()
478
+
479
+ # Check Energy W_pos
480
+ w_pos, sidx = self.power_system.output_channels["W_pos"].read_data_by_acq_sidx(0, u1_values.size)
481
+ self.assertIsNone(np.testing.assert_array_almost_equal(w_pos, expected_w_pos))
482
+ w_neg, sidx = self.power_system.output_channels["W_neg"].read_data_by_acq_sidx(0, u1_values.size)
483
+ self.assertIsNone(np.testing.assert_array_almost_equal(w_neg, expected_w_neg))
484
+
454
485
  class TestPowerSystemNperSync(unittest.TestCase):
455
486
  def setUp(self):
456
487
  self.u_channel = AcqBuffer()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes