sciveo 0.1.73__tar.gz → 0.1.74__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.
Files changed (177) hide show
  1. {sciveo-0.1.73 → sciveo-0.1.74}/PKG-INFO +1 -1
  2. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/cli.py +11 -3
  3. sciveo-0.1.74/sciveo/monitoring/power/ems300.py +107 -0
  4. sciveo-0.1.74/sciveo/monitoring/power/tools.py +394 -0
  5. sciveo-0.1.74/sciveo/version.py +2 -0
  6. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo.egg-info/PKG-INFO +1 -1
  7. sciveo-0.1.73/sciveo/monitoring/power/ems300.py +0 -108
  8. sciveo-0.1.73/sciveo/monitoring/power/tools.py +0 -378
  9. sciveo-0.1.73/sciveo/version.py +0 -2
  10. {sciveo-0.1.73 → sciveo-0.1.74}/README.md +0 -0
  11. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/__init__.py +0 -0
  12. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/api/__init__.py +0 -0
  13. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/api/base.py +0 -0
  14. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/api/predictors.py +0 -0
  15. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/api/server.py +0 -0
  16. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/api/upload.py +0 -0
  17. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/common/__init__.py +0 -0
  18. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/common/configuration.py +0 -0
  19. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/common/model.py +0 -0
  20. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/common/optimizers.py +0 -0
  21. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/common/sampling.py +0 -0
  22. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/content/__init__.py +0 -0
  23. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/content/dataset.py +0 -0
  24. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/content/experiment.py +0 -0
  25. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/content/project.py +0 -0
  26. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/content/runner.py +0 -0
  27. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/db/__init__.py +0 -0
  28. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/db/read_table.py +0 -0
  29. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/__init__.py +0 -0
  30. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/capture/__init__.py +0 -0
  31. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/capture/cam.py +0 -0
  32. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/capture/motion_detection.py +0 -0
  33. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/capture/nvr.py +0 -0
  34. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/capture/readers.py +0 -0
  35. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/__init__.py +0 -0
  36. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/base.py +0 -0
  37. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/encoders/__init__.py +0 -0
  38. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/encoders/base.py +0 -0
  39. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/encoders/normalizer.py +0 -0
  40. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/nlp/__init__.py +0 -0
  41. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/nlp/search.py +0 -0
  42. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/time_series/__init__.py +0 -0
  43. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/time_series/dataset.py +0 -0
  44. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/time_series/predictor.py +0 -0
  45. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/time_series/trainer.py +0 -0
  46. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/ml/time_series/window_generator.py +0 -0
  47. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/__init__.py +0 -0
  48. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/base.py +0 -0
  49. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/job_daemon.py +0 -0
  50. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/layouts/__init__.py +0 -0
  51. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/layouts/base.py +0 -0
  52. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/pipeline.py +0 -0
  53. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/postprocessors/__init__.py +0 -0
  54. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/postprocessors/base.py +0 -0
  55. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/postprocessors/default.py +0 -0
  56. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/__init__.py +0 -0
  57. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/audio/__init__.py +0 -0
  58. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/audio/audio.py +0 -0
  59. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/audio/audio_extractor_process.py +0 -0
  60. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/aws.py +0 -0
  61. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/base.py +0 -0
  62. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/file/__init__.py +0 -0
  63. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/file/archive.py +0 -0
  64. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/__init__.py +0 -0
  65. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/album.py +0 -0
  66. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/album_in_image.py +0 -0
  67. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/depth_esimation.py +0 -0
  68. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/embeddings.py +0 -0
  69. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/filters.py +0 -0
  70. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/generators.py +0 -0
  71. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/histogram.py +0 -0
  72. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/mask.py +0 -0
  73. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/object_detection.py +0 -0
  74. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/resize.py +0 -0
  75. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/segmentation.py +0 -0
  76. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/image/watermark.py +0 -0
  77. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/media_info.py +0 -0
  78. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/nlp/__init__.py +0 -0
  79. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/nlp/address.py +0 -0
  80. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/qr.py +0 -0
  81. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/sci/__init__.py +0 -0
  82. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/sci/base.py +0 -0
  83. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/sci/dataset.py +0 -0
  84. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/sci/time_series/__init__.py +0 -0
  85. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/sci/time_series/predictor.py +0 -0
  86. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/sci/time_series/trainer.py +0 -0
  87. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/tpu_base.py +0 -0
  88. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/video/__init__.py +0 -0
  89. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/video/generators.py +0 -0
  90. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/video/motion_detection.py +0 -0
  91. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/video/resize.py +0 -0
  92. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/video/video_album.py +0 -0
  93. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/video/video_frames.py +0 -0
  94. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/processors/video/video_resample.py +0 -0
  95. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/queues.py +0 -0
  96. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/server.py +0 -0
  97. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/web/__init__.py +0 -0
  98. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/pipelines/web/server.py +0 -0
  99. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/tools/__init__.py +0 -0
  100. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/media/tools/video_interactive.py +0 -0
  101. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/__init__.py +0 -0
  102. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/base.py +0 -0
  103. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/dataset/__init__.py +0 -0
  104. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/dataset/object_detection.py +0 -0
  105. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/evaluation/__init__.py +0 -0
  106. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/evaluation/markdown.py +0 -0
  107. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/evaluation/object_detection.py +0 -0
  108. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/images/__init__.py +0 -0
  109. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/images/base.py +0 -0
  110. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/images/description.py +0 -0
  111. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/images/embeddings.py +0 -0
  112. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/images/object_detection.py +0 -0
  113. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/images/tools.py +0 -0
  114. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/images/transformers.py +0 -0
  115. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/nlp/__init__.py +0 -0
  116. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/nlp/embeddings.py +0 -0
  117. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/nlp/tokenizers/__init__.py +0 -0
  118. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/nlp/tokenizers/bpe.py +0 -0
  119. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/video/__init__.py +0 -0
  120. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/ml/video/description.py +0 -0
  121. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/monitoring/__init__.py +0 -0
  122. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/monitoring/monitor.py +0 -0
  123. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/monitoring/power/__init__.py +0 -0
  124. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/monitoring/power/sim.py +0 -0
  125. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/monitoring/start.py +0 -0
  126. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/monitoring/watchdog/__init__.py +0 -0
  127. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/monitoring/watchdog/base.py +0 -0
  128. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/monitoring/watchdog/process.py +0 -0
  129. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/network/__init__.py +0 -0
  130. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/network/camera.py +0 -0
  131. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/network/sniffer.py +0 -0
  132. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/network/tools.py +0 -0
  133. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/__init__.py +0 -0
  134. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/array.py +0 -0
  135. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/aws/__init__.py +0 -0
  136. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/aws/priority_queue.py +0 -0
  137. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/aws/s3.py +0 -0
  138. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/common.py +0 -0
  139. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/complexity.py +0 -0
  140. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/compress.py +0 -0
  141. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/configuration.py +0 -0
  142. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/crypto.py +0 -0
  143. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/daemon.py +0 -0
  144. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/draw/__init__.py +0 -0
  145. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/draw/contours.py +0 -0
  146. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/formating.py +0 -0
  147. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/hardware.py +0 -0
  148. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/http.py +0 -0
  149. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/logger.py +0 -0
  150. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/os.py +0 -0
  151. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/queue.py +0 -0
  152. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/random.py +0 -0
  153. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/remote.py +0 -0
  154. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/simple_counter.py +0 -0
  155. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/synchronized.py +0 -0
  156. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/timers.py +0 -0
  157. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/tools/totp.py +0 -0
  158. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/web/__init__.py +0 -0
  159. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo/web/common.py +0 -0
  160. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo.egg-info/SOURCES.txt +0 -0
  161. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo.egg-info/dependency_links.txt +0 -0
  162. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo.egg-info/entry_points.txt +0 -0
  163. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo.egg-info/requires.txt +0 -0
  164. {sciveo-0.1.73 → sciveo-0.1.74}/sciveo.egg-info/top_level.txt +0 -0
  165. {sciveo-0.1.73 → sciveo-0.1.74}/setup.cfg +0 -0
  166. {sciveo-0.1.73 → sciveo-0.1.74}/setup.py +0 -0
  167. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_complexity.py +0 -0
  168. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_compress.py +0 -0
  169. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_configuration.py +0 -0
  170. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_crypto.py +0 -0
  171. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_eval_markdown.py +0 -0
  172. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_ml_datasets.py +0 -0
  173. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_monitoring.py +0 -0
  174. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_runner.py +0 -0
  175. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_sampling.py +0 -0
  176. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_tokenizers.py +0 -0
  177. {sciveo-0.1.73 → sciveo-0.1.74}/test/test_totp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sciveo
3
- Version: 0.1.73
3
+ Version: 0.1.74
4
4
  Description-Content-Type: text/markdown
5
5
  Provides-Extra: mon
6
6
  Provides-Extra: net
@@ -29,7 +29,7 @@ def main():
29
29
  choices=[
30
30
  'init', 'monitor', 'scan', 'nvr', 'predictors-server',
31
31
  'media-server', 'media-run',
32
- 'watchdog'
32
+ 'watchdog',
33
33
  ],
34
34
  help='Command to execute')
35
35
 
@@ -54,12 +54,20 @@ def main():
54
54
  parser.add_argument('--threshold', type=float, default=None, help='Threshold')
55
55
  parser.add_argument('--execute', type=str, default=None, help='Execute command')
56
56
  parser.add_argument('--pid', type=int, default=None, help='Process PID')
57
+ parser.add_argument('--serial', type=str, default=None, help='Serial Name')
57
58
 
58
59
  args = parser.parse_args()
59
60
 
60
61
  if args.command == 'monitor':
61
- from sciveo.monitoring.start import MonitorStart
62
- MonitorStart(period=args.period, block=args.block, output_path=args.output_path)()
62
+ if args.src is None:
63
+ from sciveo.monitoring.start import MonitorStart
64
+ MonitorStart(period=args.period, block=args.block, output_path=args.output_path)()
65
+ elif args.src.startswith("power"):
66
+ from sciveo.monitoring.power.ems300 import PowerEMS300
67
+ mon = PowerEMS300(serial=args.serial, host=args.host, port=args.port, period=args.period, output_path=args.output_path)
68
+ mon.start()
69
+ while(True):
70
+ time.sleep(3600)
63
71
  elif args.command == 'scan':
64
72
  from sciveo.network.tools import NetworkTools
65
73
  host=args.host
@@ -0,0 +1,107 @@
1
+ #
2
+ # Stanislav Georgiev, Softel Labs
3
+ #
4
+ # This is a proprietary file and may not be copied,
5
+ # distributed, or modified without express permission
6
+ # from the owner. For licensing inquiries, please
7
+ # contact s.georgiev@softel.bg.
8
+ #
9
+ # 2025
10
+ #
11
+
12
+ import os
13
+ import json
14
+ import datetime
15
+ import time
16
+ from pymodbus.client import ModbusTcpClient
17
+ from sciveo.tools.logger import *
18
+ from sciveo.tools.daemon import DaemonBase
19
+ # from sciveo.api.base import APIRemoteClient
20
+ from sciveo.monitoring.power.tools import *
21
+
22
+
23
+ class PowerEMS300(DaemonBase):
24
+ def __init__(self, serial, host, port=502, device_id=247, delay=0.01, period=30, output_path=None):
25
+ super().__init__(period=period)
26
+ self.serial = serial
27
+ self.host = host
28
+ self.port = port
29
+ self.device_id = device_id
30
+ self.delay = delay
31
+ self.client = None
32
+
33
+ if output_path is not None and os.path.isdir(output_path):
34
+ self.output_path = os.path.join(output_path, f"{self.serial}.json")
35
+ else:
36
+ self.output_path = output_path
37
+
38
+ # self.api = APIRemoteClient()
39
+ self.client = ModbusTcpClient(host=self.host, port=self.port)
40
+
41
+ def connect(self):
42
+ if not self.client.connected:
43
+ if self.client.connect():
44
+ info(type(self.client).__name__, "connected", (self.host, self.port))
45
+ else:
46
+ error("Connect FAIL", (self.host, self.port))
47
+ return self.client.connected
48
+
49
+ def send(self, data):
50
+ api_result = self.api.POST_SCI("monitor", {"data": data})
51
+ debug(data, "api_result", api_result)
52
+
53
+ def output_write(self, data):
54
+ if os.path.isfile(self.output_path):
55
+ with open(self.output_path, "rb+") as fp:
56
+ fp.seek(-2, 2)
57
+ fp.truncate()
58
+ with open(self.output_path, "a") as fp:
59
+ fp.write(f", {json.dumps(data)}]\n")
60
+ else:
61
+ with open(self.output_path, "w") as fp:
62
+ fp.write(json.dumps([data]) + "\n")
63
+
64
+ def save(self, data):
65
+ try:
66
+ if self.output_path is not None:
67
+ info("save", self.output_path, data)
68
+ self.output_write(data)
69
+ except Exception as e:
70
+ error(e, "writing failed for", self.output_path)
71
+
72
+ def loop(self):
73
+ if not self.connect():
74
+ warning("Not connected", (self.host, self.port))
75
+ return
76
+
77
+ try:
78
+ data = {
79
+ "local_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
80
+ "serial": self.serial,
81
+ }
82
+
83
+ current_info = [8026, 8044, 8060, 8776, 8778, 10101, 10103, 10105, 10713, 10715, 10716, 10945]
84
+ for addr in current_info:
85
+ result = read_input_registers(self.client, EMS300_REG_MAP, addr, self.device_id)
86
+ if result is not None:
87
+ value, name, dtype = result
88
+ debug(f"{name}: {value}")
89
+ data[name] = value
90
+ time.sleep(self.delay)
91
+
92
+ # self.send(data)
93
+ self.save(data)
94
+ except Exception as e:
95
+ exception(e)
96
+
97
+ def close(self):
98
+ self.client.close()
99
+
100
+
101
+ if __name__ == "__main__":
102
+
103
+ mon = PowerEMS300(serial="EMS300-1", host="192.168.86.184", port=502, period=30, output_path="ems300.json")
104
+ mon.start()
105
+
106
+ while(True):
107
+ time.sleep(30)
@@ -0,0 +1,394 @@
1
+ #
2
+ # Stanislav Georgiev, Softel Labs
3
+ #
4
+ # This is a proprietary file and may not be copied,
5
+ # distributed, or modified without express permission
6
+ # from the owner. For licensing inquiries, please
7
+ # contact s.georgiev@softel.bg.
8
+ #
9
+ # 2025
10
+ #
11
+
12
+ import struct
13
+ import math
14
+ from sciveo.tools.logger import *
15
+
16
+
17
+ def to_u16(regs):
18
+ return regs[0] & 0xFFFF
19
+
20
+ def to_s16(regs):
21
+ v = regs[0] & 0xFFFF
22
+ return v - 0x10000 if v & 0x8000 else v
23
+
24
+ def to_u32_le_words(regs):
25
+ # device transmits U32 double-word as little-endian word order:
26
+ # Example in doc: 0x01020304 transmitted as 03,04,01,02 => regs[0]=0x0304, regs[1]=0x0102
27
+ return ((regs[1] & 0xFFFF) << 16) | (regs[0] & 0xFFFF)
28
+
29
+ def to_s32_le_words(regs):
30
+ v = to_u32_le_words(regs)
31
+ return v - 0x100000000 if v & 0x80000000 else v
32
+
33
+ def to_u64_be(regs):
34
+ # U64 big-endian: regs[0] = highest word
35
+ return ((regs[0] & 0xFFFF) << 48) | ((regs[1] & 0xFFFF) << 32) | ((regs[2] & 0xFFFF) << 16) | (regs[3] & 0xFFFF)
36
+
37
+ def to_s64_be(regs):
38
+ v = to_u64_be(regs)
39
+ return v - 0x10000000000000000 if v & (1 << 63) else v
40
+
41
+ def float_from_words_be(regs):
42
+ # regs[0] = high word, regs[1] = low word -> bytes: [hi_hi, hi_lo, lo_hi, lo_lo]
43
+ b = (int(regs[0]) & 0xFFFF).to_bytes(2, "big") + (int(regs[1]) & 0xFFFF).to_bytes(2, "big")
44
+ return struct.unpack(">f", b)[0]
45
+
46
+ def float_from_words_le(regs):
47
+ # little-endian word order: regs[0] = low word, regs[1] = high word
48
+ # reconstruct big-endian 4 bytes by swapping words
49
+ b = (int(regs[1]) & 0xFFFF).to_bytes(2, "big") + (int(regs[0]) & 0xFFFF).to_bytes(2, "big")
50
+ return struct.unpack(">f", b)[0]
51
+
52
+ def to_float_best(regs):
53
+ """
54
+ Try decoding FLOAT taking into account vendor word-order quirks.
55
+ Prefer little-endian word order (as documented for U32), but fall back
56
+ to big-endian word order if the result looks more reasonable.
57
+ """
58
+ try:
59
+ val_le = float_from_words_le(regs)
60
+ except Exception:
61
+ val_le = None
62
+ try:
63
+ val_be = float_from_words_be(regs)
64
+ except Exception:
65
+ val_be = None
66
+
67
+ def good(v):
68
+ return v is not None and math.isfinite(v)
69
+
70
+ if good(val_le) and not good(val_be):
71
+ return val_le
72
+ if good(val_be) and not good(val_le):
73
+ return val_be
74
+ if good(val_le) and good(val_be):
75
+ if abs(val_le) < abs(val_be):
76
+ return val_le
77
+ return val_be
78
+ return None
79
+
80
+ # Known register map (protocol addresses as in documentation). Format: addr: (type, factor, name)
81
+ EMS300_REG_MAP = {
82
+ 8018: ("U32", 1, "LC Total Number"),
83
+ 8020: ("U32", 1, "Total Number of Available LC"),
84
+ 8022: ("FLOAT", 1000, "Total Rated Power of Energy Storage"),
85
+ 8026: ("U32", 0.1, "Actual SOC"),
86
+ 8038: ("S32", 1, "Energy Storage Power"),
87
+ 8040: ("U32", 1, "Available Time for Maximum Power Discharging"),
88
+ 8042: ("U32", 1, "Available Time for Maximum Power Charging"),
89
+ 8044: ("S32", 1, "Plant Active Power"),
90
+ 8046: ("U32", 0.001, "Grid Frequency"),
91
+ 8048: ("S32", 1, "Plant Reactive Power"),
92
+ 8050: ("U32", 1, "BusVoltage"),
93
+ 8052: ("S32", 0.001, "Power Factor"),
94
+ 8056: ("S32", 1, "Photovoltaic Power Rating"),
95
+ 8058: ("S32", 1, "PV Available Reactive Power"),
96
+ 8060: ("S32", 1, "PV Active Power"),
97
+ 8062: ("S32", 1, "PV Reactive Power"),
98
+ 8064: ("U32", 1, "PV Subarray Number"),
99
+ 8070: ("U32", 1, "SOC Upper Limit Level 1 Protection"),
100
+ 8072: ("U32", 1, "SOC Lower Limit Level 1 Protection"),
101
+ 8074: ("U32", 1, "Total Field Charge Capacity"),
102
+ 8076: ("U32", 1, "Total Field Discharge Capacity"),
103
+ 8092: ("U32", 1, "Available Time of Real-time Charging"),
104
+ 8094: ("U32", 1, "Available Time of Real-time Discharging"),
105
+ 8098: ("U16", 1, "Charging Complete"),
106
+ 8099: ("U16", 1, "Discharge Completed"),
107
+ 8100: ("U16", 1, "Charge Locked"),
108
+ 8101: ("U16", 1, "Discharge Locked"),
109
+ 8297: ("U16", 1, "Plant Stop in Running Status"),
110
+ 8298: ("U16", 1, "Plant Standby in Running Status"),
111
+ 8299: ("U16", 1, "Plant Discharge in Running Status"),
112
+ 8300: ("U16", 1, "Plant Charge in Running Status"),
113
+ 8301: ("U16", 1, "Plant Fault in Running Status"),
114
+ 8485: ("FLOAT", 1, "Residual Capacity of Energy Storage"),
115
+ 8487: ("U16", 0.1, "Battery Voltage"),
116
+ 8488: ("FLOAT", 1000, "Active Load"),
117
+ 8490: ("FLOAT", 1000, "Rated Capacity of Energy Storage"),
118
+ 8492: ("U16", 1, "System Operating Status"),
119
+ 8493: ("U64", 1, "Start and Stop Process Status Word"),
120
+ 8638: ("FLOAT", 1, "Total DC Side Power of ESS"),
121
+ 8726: ("U16", 1, "Number of On-grid Devices"),
122
+ 8727: ("U16", 1, "Number of Off-grid Devices"),
123
+ 8728: ("FLOAT", 1, "Energy Storage SOC"),
124
+ 8733: ("FLOAT", 1, "Total Active Power of Chargers in the Plant"),
125
+ 8735: ("FLOAT", 1, "Total Energy Delivered by Chargers in the Plant"),
126
+ 8752: ("FLOAT", 1, "Upper Limit of Available Active Power of Plant"),
127
+ 8754: ("FLOAT", 1, "Lower Limit of Available Active Power of Plant"),
128
+ 8756: ("FLOAT", 1, "Available Plant Reactive Power"),
129
+ 8758: ("FLOAT", 0.1, "Active Power of Energy Storage"),
130
+ 8760: ("FLOAT", 0.1, "Reactive Power of Energy Storage"),
131
+ 8762: ("U32", 0.1, "Maximum SOC"),
132
+ 8766: ("U32", 0.1, "Minimum SOC"),
133
+ 8768: ("U32", 0.1, "Maximum Charging Power Allowed"),
134
+ 8770: ("U32", 0.1, "Maximum Discharging Power"),
135
+ 8772: ("U32", 0.1, "ESS Daily Charge"),
136
+ 8774: ("U32", 0.1, "ESS Daily Discharge"),
137
+ 8776: ("U32", 0.1, "System Dischargeable Energy"),
138
+ 8778: ("U32", 0.1, "System Chargeable Capacity"),
139
+ 9082: ("FLOAT", 1, "Dynamic Allowable Charging Power"),
140
+ 9084: ("FLOAT", 1, "Dynamic Allowable Discharging Power"),
141
+ 9086: ("U16", 1, "ESS Operating Status"),
142
+ 9087: ("FLOAT", 1, "ESS Power Factor"),
143
+ 9089: ("FLOAT", 1, "ESS SOH"),
144
+ 9115: ("FLOAT", 1, "ESS Grid-Connection Point Meter Total Daily Charge"),
145
+ 9117: ("FLOAT", 1, "ESS Grid-Connection Point Meter Total Daily Discharge"),
146
+ 9119: ("U16", 1, "ESS Fault"),
147
+ 9120: ("U16", 1, "Energy Storage Alarm"),
148
+ 9121: ("U16", 1, "ESS Communication Failure"),
149
+ 9122: ("U16", 1, "ESS HVAC Fault"),
150
+ 9424: ("U16", 1, "Active Power Limit Ratio"),
151
+ 8102: ("U16", 1, "Communication Status"),
152
+ 8302: ("U16", 1, "Device Alarm"),
153
+ 8303: ("U16", 1, "Device Fault"),
154
+ 8347: ("U16", 1, "Device Model"),
155
+ 8341: ("FLOAT", 1, "Daily Yield, kWh"),
156
+ 8379: ("U16", 1, "SN Code"),
157
+ 8389: ("U16", 1, "ETH1 mac"),
158
+ 8421: ("U16", 1, "ETH2 mac"),
159
+ 8453: ("U16", 1, "ETH3 mac"),
160
+ 8730: ("U16", 1, "Serial Port Communication Interrupted"),
161
+ 8731: ("U16", 1, "TCP Communication Interrupted"),
162
+ 8732: ("U16", 1, "Host EMS Communication Fault"),
163
+ 10601: ("U16", 1, "DI1 Value"),
164
+ 8554: ("U16", 1, "Host and Client Status"),
165
+ 8555: ("U16", 1, "Cascading"),
166
+ 8700: ("U16", 1, "Debugging Mode"),
167
+ 8711: ("U16", 1, "ESS Connection Point"),
168
+ 8712: ("U16", 1, "PV Connection Point"),
169
+ 8682: ("FLOAT", 1, "Replacement Percentage"),
170
+ 8713: ("U16", 1, "Energy Storage Data Source"),
171
+ 9826: ("U16", 1, "Select Power Distribution Strategy"),
172
+ 8570: ("U16", 1, "Mode selection"),
173
+ 8780: ("U16", 1, "Time-of-Use Power Period 1 Time Type"),
174
+ 8781: ("U16", 1, "Start Time of Time of Use Power Period 1"),
175
+ 8782: ("U16", 1, "End Time of Time of Use Power Period 1"),
176
+ 8783: ("FLOAT", 1, "Power Setting of Time of Use Power Period 1"),
177
+ 8785: ("U16", 1, "Time-of-Use Power Period 1 Charging/Discharging Status"),
178
+ 8894: ("U16", 1, "Time-of-Use Power Period 20 Time Type"),
179
+ 8895: ("U16", 1, "Start Time of Time of Use Power Period 20"),
180
+ 8896: ("U16", 1, "End Time of Time of Use Power Period 20"),
181
+ 8897: ("FLOAT", 1, "Power Setting of Time of Use Power Period 20"),
182
+ 8899: ("U16", 1, "Time-of-Use Power Period 20 Charging/Discharging Status"),
183
+ 9425: ("U16", 1, "Time-of-Use Power Minute/Second Mode"),
184
+ 9426: ("U16", 1, "Time-of-Use Power Second-Mode Period 1 Time Type"),
185
+ 9427: ("U32", 1, "Time-of-Use Power Second-Mode Period 1 Start Time"),
186
+ 9429: ("U32", 1, "Time-of-Use Power Second-Mode Period 1 End Time"),
187
+ 9431: ("FLOAT", 1, "Time-of-Use Power Second-Mode Period 1 Power"),
188
+ 9433: ("U16", 1, "Time-of-Use Power Second-Mode Period 1 Charging/Discharging Status"),
189
+ 9578: ("U16", 1, "Time-of-Use Power Second-Mode Period 20 Time Type"),
190
+ 9579: ("U32", 1, "Time-of-Use Power Second-Mode Period 20 Start Time"),
191
+ 9581: ("U32", 1, "Time-of-Use Power Second-Mode Period 20 End Time"),
192
+ 9583: ("FLOAT", 1, "Time-of-Use Power Second-Mode Period 20 Power"),
193
+ 9585: ("U16", 1, "Time-of-Use Power Second-Mode Period 20 Charging/Discharging Status"),
194
+ 9123: ("FLOAT", 1, "Remotely Set Time-of-Use Power Period 1 Start Time"),
195
+ 9125: ("FLOAT", 1, "Remotely Set Time-of-Use Power Period 1 End Time"),
196
+ 9127: ("FLOAT", 1, "Remotely Set Time-of-Use Power Period 1 Power"),
197
+ 9417: ("FLOAT", 1, "Remotely Set Time-of-Use Power Period 50 Start Time"),
198
+ 9419: ("FLOAT", 1, "Remotely Set Time-of-Use Power Period 50 End Time"),
199
+ 9421: ("FLOAT", 1, "Remotely Set Time-of-Use Power Period 50 Power"),
200
+ 8103: ("U16", 1, "Active Power Control Running Status"),
201
+ 8547: ("FLOAT", 1000, "Setting Feedback Value of Active Power"),
202
+ 8560: ("FLOAT", 1, "Active Power Control Dead Zone"),
203
+ 8710: ("U16", 1, "Scheduled Object"),
204
+ 9423: ("U16", 1, "Control Priority"),
205
+ 8714: ("U16", 1, "Operation Mode upon Communication Interruption"),
206
+ 8565: ("U32", 1, "Communication Interruption Time"),
207
+ 8567: ("U32", 1, "Communication Interruption Counting Period"),
208
+ 8569: ("U16", 1, "Communication Interruption Counts"),
209
+ 8105: ("U32", 1, "AGC_PLAN_001"),
210
+ 8107: ("U32", 1, "AGC_PLAN_002"),
211
+ 8293: ("U32", 1, "AGC_PLAN_095"),
212
+ 8295: ("U32", 1, "AGC_PLAN_096"),
213
+ 8304: ("U16", 1, "AGC Allow Control"),
214
+ 8305: ("U16", 1, "AGC Remote/Local Status"),
215
+ 8306: ("U16", 1, "AVC Turn Up Lock"),
216
+ 8307: ("U16", 1, "AVC Turn Down Lock"),
217
+ 8308: ("U16", 1, "AVC Allow Control"),
218
+ 8309: ("U16", 1, "AVC Remote/Local Status"),
219
+ 8104: ("U16", 1, "Reactive Power Control Running Status"),
220
+ 8549: ("FLOAT", 1000, "Setting Feedback Value of Reactive Power"),
221
+ 8562: ("FLOAT", 1, "Reactive Power Control Dead Zone"),
222
+ 8090: ("S32", 0.001, "Local Power Factor Set Value"),
223
+ 8689: ("U16", 1, "Execute Strategy Only When ESS is Discharging"),
224
+ 8693: ("FLOAT", 1, "Charge Step Factor"),
225
+ 8695: ("FLOAT", 1, "Discharge Step Factor"),
226
+ 8697: ("U16", 1, "Adjustment Period"),
227
+ 8698: ("FLOAT", 1, "System Discharge Threshold"),
228
+ 8706: ("FLOAT", 1, "PV Active Power Ratio K1"),
229
+ 8708: ("FLOAT", 1, "PV Active Power Ratio K2"),
230
+ 9827: ("FLOAT", 1, "Regular Load Power"),
231
+ 9829: ("FLOAT", 1, "SOC Threshold to Stop Supplying Power to Charger"),
232
+ 9831: ("FLOAT", 1, "Proportion of Stable Loads"),
233
+ 9836: ("U16", 1, "VSG Parameter Management"),
234
+ 9837: ("FLOAT", 1, "Output Voltage Setpoint"),
235
+ 9839: ("FLOAT", 1, "Output Voltage Setpoint (Hz)"),
236
+ 9841: ("FLOAT", 1, "Active Power-Frequency Droop Coefficient"),
237
+ 9843: ("FLOAT", 1, "Reactive Power-Voltage Droop Coefficient"),
238
+ 9845: ("FLOAT", 1, "Sent Active Power Setpoint"),
239
+ 9847: ("FLOAT", 1, "Sent Reactive Power Setpoint"),
240
+ 8640: ("U16", 1, "Frequency Droop Control Enable/Disable"),
241
+ 8642: ("U16", 1, "Frequency Response Type"),
242
+ 8310: ("U16", 1, "Frequency Control Running Flag"),
243
+ 8311: ("U32", 1, "Primary Frequency Regulation Control"),
244
+ 8313: ("U16", 1, "Primary Frequency Regulation Exit Signal"),
245
+ 8314: ("U16", 1, "Number of FM Algorithm Segments"),
246
+ 8315: ("U32", 0.001, "Rated Frequency of Grid-connection Point"),
247
+ 8317: ("U32", 0.01, "Frequency Regulation Coefficient"),
248
+ 8319: ("U32", 0.001, "Frequency Regulation Dead Zone"),
249
+ 8321: ("U32", 0.001, "Maximum Active Power Output"),
250
+ 8323: ("U32", 0.001, "Maximum Active Power Absorbed"),
251
+ 8325: ("U32", 0.001, "Lower Limit of Active Power Limit Coefficient"),
252
+ 8327: ("U32", 0.001, "Upper Limit of Active Power Limit Coefficient"),
253
+ 8329: ("U32", 0.01, "Frequency Regulation II Regulation Coefficient"),
254
+ 8331: ("U32", 0.001, "Frequency Regulation II Dead Zone"),
255
+ 8333: ("U32", 0.01, "Active Power Before Primary Frequency Regulation"),
256
+ 8335: ("U32", 0.01, "Active Power After Primary Frequency Regulation"),
257
+ 8645: ("FLOAT", 1, "Contingency FCAS Action Deadband fd"),
258
+ 8647: ("FLOAT", 1, "Contingency FCAS Maximum Output Active Power PA"),
259
+ 8649: ("FLOAT", 1, "Contingency FCAS Maximum Absorption Active Power PB"),
260
+ 8651: ("FLOAT", 1, "Contingency FCAS Frequency Deviation Threshold for Full-Scale Response"),
261
+ 8655: ("U16", 1, "Demand Control"),
262
+ 8656: ("U16", 1, "Select Strategy"),
263
+ 8657: ("U16", 1, "Allow ESS to Discharge or Not"),
264
+ 8658: ("FLOAT", 1, "Max. Allowable Demand"),
265
+ 8660: ("FLOAT", 1, "Lower Limit for Demand Control"),
266
+ 8662: ("U16", 1, "Reverse Power Protection"),
267
+ 8663: ("U16", 1, "Allow ESS charging during reverse power flow"),
268
+ 8664: ("FLOAT", 1, "Target Active Power of Reverse Power Protection"),
269
+ 8337: ("U32", 1, "Transformer Capacity"),
270
+ 8339: ("FLOAT", 1, "Active Power Conversion Coefficient"),
271
+ 8653: ("U16", 1, "Overload Protection"),
272
+ 8654: ("U16", 1, "Dynamic Capacity Expansion"),
273
+ 8666: ("U16", 1, "Power Backup"),
274
+ 8679: ("FLOAT", 1, "Recharging Power"),
275
+ 9833: ("FLOAT", 1, "K Value When Prioritizing ESS Charge"),
276
+ 8066: ("U32", 1, "SOC Upper Limit"),
277
+ 8068: ("U32", 1, "SOC Lower Limit"),
278
+ 8690: ("U32", 1, "SOC Balancing"),
279
+ 8667: ("FLOAT", 1, "SOC Upper Limit Level 1 Protection"),
280
+ 8669: ("FLOAT", 1, "SOC Upper Limit Level 1 Recovery"),
281
+ 8671: ("FLOAT", 1, "SOC Lower Limit Level 1 Recovery"),
282
+ 8673: ("FLOAT", 1, "SOC Lower Limit Level 1 Protection"),
283
+ 8675: ("FLOAT", 1, "SOC Lower Limit Level 2 Recovery"),
284
+ 8677: ("FLOAT", 1, "SOC Lower Limit Level 2 Protection"),
285
+ 8553: ("U16", 1, "Grid-connected/Off-grid Startup"),
286
+ 8715: ("U16", 1, "Inverter Operating Mode"),
287
+ 8718: ("FLOAT", 1, "Black Start SOC Threshold"),
288
+ 8720: ("U16", 1, "Minimum Number of Running Units"),
289
+ 8721: ("FLOAT", 1, "Load Rated Power"),
290
+ 8723: ("U16", 1, "System Waiting time"),
291
+ 8724: ("U16", 1, "System Timeout"),
292
+ 8725: ("U16", 1, "Charging Initialization Mode"),
293
+ 9855: ("U16", 1, "Auto-start on Recovery from System Fault"),
294
+ 9854: ("U16", 1, "Function Mode"),
295
+ 10001: ("U16", 1, "Total Number of Connected Devices"),
296
+ 10002: ("U16", 1, "Total Number of Fault Count"),
297
+ 10101: ("S32", 0.01, "Total Active Power"),
298
+ 10103: ("S32", 0.01, "Gateway Meter Active Power"),
299
+ 10105: ("S32", 0.01, "Load Power"),
300
+ 10107: ("U32", 0.1, "Gateway Meter Import Energy"),
301
+ 10109: ("U32", 0.1, "Gateway Meter Export Energy"),
302
+ 10501: ("U16", 1, "Device Type Code"),
303
+ 10502: ("U32", 1, "Protocol No."),
304
+ 10504: ("U32", 1, "Protocol Version No."),
305
+ 10506: ("U32", 1, "DI Status"),
306
+ 10701: ("U32", 0.1, "Max. Total Rated Active Power"),
307
+ 10703: ("U32", 0.1, "Total Battery Rated Capacity"),
308
+ 10705: ("U32", 0.1, "Battery Charge/Discharge Maximum Description"),
309
+ 10707: ("U16", 0.1, "Maximum Battery Charge Power"),
310
+ 10708: ("U16", 0.1, "Minimum Battery Charge Power"),
311
+ 10709: ("U16", 0.1, "Maximum Battery Discharge Power"),
312
+ 10710: ("U16", 0.1, "Minimum Battery Discharge Power"),
313
+ 10713: ("S32", 0.01, "Battery Power"),
314
+ 10715: ("U16", 0.1, "Battery level (SOC)"),
315
+ 10716: ("U32", 0.1, "Daily Battery Charging Energy"),
316
+ 10718: ("U32", 0.1, "Daily Battery Discharging Energy"),
317
+ 10720: ("U64", 0.1, "Total Battery Charging Energy"),
318
+ 10724: ("U64", 0.1, "Total battery discharge"),
319
+ 10901: ("U16", 1, "Maximum Total PV Rated Active Power"),
320
+ 10902: ("U16", 1, "Minimum Total PV Rated Active Power"),
321
+ 10903: ("U16", 1, "Maximum Total PV Rated Reactive Power"),
322
+ 10904: ("S16", 1, "Minimum Total PV Rated Reactive Power"),
323
+ 10905: ("U16", 1, "Set Total Inverter Active Power"),
324
+ 10906: ("S16", 1, "Set Total Inverter Reactive Power"),
325
+ 10909: ("S64", 1, "P-total-PV"),
326
+ 10913: ("U32", 0.1, "Daily PV yield"),
327
+ 10915: ("S64", 1, "Q-total-PV"),
328
+ 10919: ("U64", 0.1, "Total PV Yield"),
329
+ 10923: ("U32", 0.1, "PV Min. adjustable active power"),
330
+ 10925: ("U32", 0.1, "PV Max. adjustable active power"),
331
+ 10927: ("S32", 0.1, "PV Min. Adjustable Reactive Power"),
332
+ 10929: ("S32", 0.1, "PV Max. Adjustable Reactive Power"),
333
+ 10931: ("S32", 0.1, "PV Rated Active Power"),
334
+ 10933: ("S32", 0.1, "PV Rated Reactive Power"),
335
+ 10935: ("U16", 1, "Total Grid-Connected Inverter Devices"),
336
+ 10936: ("U16", 1, "Total Off-Grid Inverter Devices"),
337
+ 10937: ("U64", 0.1, "Monthly PV Yield"),
338
+ 10941: ("U64", 0.1, "PV Annual Yield"),
339
+ 10945: ("U64", 1, "PV Apparent Power"),
340
+ 10949: ("U16", 0.1, "PV Active Power Dispatching Ratio"),
341
+ 11804: ("U16", 1, "2030.5 Feed-in Control Method"),
342
+ 11805: ("S32", 0.01, "2030.5 Feed-in Limitation Value"),
343
+ 11807: ("S16", 0.1, "2030.5 Feed-in Limitation Ratio"),
344
+ }
345
+
346
+ def count_for_type(t):
347
+ return {"U16": 1, "S16": 1, "U32": 2, "S32": 2, "U64": 4, "S64": 4, "FLOAT": 2}.get(t, 1)
348
+
349
+ def decode_registers(regs, dtype):
350
+ if dtype == "U16":
351
+ return to_u16(regs)
352
+ if dtype == "S16":
353
+ return to_s16(regs)
354
+ if dtype == "U32":
355
+ return to_u32_le_words(regs)
356
+ if dtype == "S32":
357
+ return to_s32_le_words(regs)
358
+ if dtype == "U64":
359
+ return to_u64_be(regs)
360
+ if dtype == "S64":
361
+ return to_s64_be(regs)
362
+ if dtype == "FLOAT":
363
+ return to_float_best(regs)
364
+ return None
365
+
366
+ def read_input_registers(client, REG_MAP, addr, device_id):
367
+ try:
368
+ dtype, factor, name = REG_MAP[addr]
369
+ count = count_for_type(dtype)
370
+ protocol_addr = addr - 1
371
+ rr = client.read_input_registers(address=protocol_addr, count=count, device_id=device_id)
372
+ if rr is None:
373
+ warning(f"No response for {addr} ({name})")
374
+ return None
375
+ if hasattr(rr, "isError") and rr.isError():
376
+ warning(f"Modbus exception at {addr} ({name}): {rr}")
377
+ return None
378
+ regs = getattr(rr, "registers", None)
379
+ if not regs or len(regs) < count:
380
+ warning(f"Incomplete registers at {addr} ({name}): {regs}")
381
+ return None
382
+ val_raw = decode_registers(regs, dtype)
383
+ if val_raw is None:
384
+ warning(f"Unable to decode {addr} ({name}) regs={regs}")
385
+ return None
386
+ value = val_raw * factor
387
+ # format integer-looking floats without trailing .0
388
+ if isinstance(value, float) and value.is_integer():
389
+ value = int(value)
390
+ debug(f"{addr} {name}: raw={regs} -> {value} (factor={factor}, type={dtype})")
391
+ except Exception as e:
392
+ exception(e, f"read_input_registers::reading {addr} ({name})")
393
+ return None
394
+ return value, name, dtype
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = '0.1.74'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sciveo
3
- Version: 0.1.73
3
+ Version: 0.1.74
4
4
  Description-Content-Type: text/markdown
5
5
  Provides-Extra: mon
6
6
  Provides-Extra: net
@@ -1,108 +0,0 @@
1
- #
2
- # Stanislav Georgiev, Softel Labs
3
- #
4
- # This is a proprietary file and may not be copied,
5
- # distributed, or modified without express permission
6
- # from the owner. For licensing inquiries, please
7
- # contact s.georgiev@softel.bg.
8
- #
9
- # 2025
10
- #
11
-
12
- import time
13
- from pymodbus.client import ModbusTcpClient
14
- from sciveo.tools.logger import *
15
- from sciveo.tools.daemon import DaemonBase
16
- from sciveo.monitoring.power.tools import *
17
-
18
-
19
- class PowerEMS300(DaemonBase):
20
- def __init__(self, host, port=502, device_id=247, delay=0.01, period=30):
21
- super().__init__(period=period)
22
- self.host = host
23
- self.port = port
24
- self.device_id = device_id
25
- self.delay = delay
26
- self.client = None
27
-
28
- self.client = ModbusTcpClient(host=self.host, port=self.port)
29
- self.connected = False
30
-
31
- def connect(self):
32
- if not (self.connected or self.client.connect()):
33
- error("Connect FAIL", (self.host, self.port))
34
- self.connected = False
35
- else:
36
- self.connected = True
37
- return self.connected
38
-
39
- def loop(self):
40
- self.connect()
41
- if not self.connected:
42
- warning("Not connected", (self.host, self.port))
43
- return
44
-
45
- try:
46
- # iterate known addresses in map (sorted)
47
- for addr in sorted(REG_MAP.keys()):
48
- dtype, factor, name = REG_MAP[addr]
49
- count = count_for_type(dtype)
50
- protocol_addr = addr - 1 # doc addresses start at 1; pymodbus expects zero-based
51
- try:
52
- rr = self.client.read_input_registers(address=protocol_addr, count=count, device_id=self.device_id)
53
- if rr is None:
54
- warning(f"No response for {addr} ({name})")
55
- continue
56
- if hasattr(rr, "isError") and rr.isError():
57
- warning(f"Modbus exception at {addr} ({name}): {rr}")
58
- continue
59
- regs = getattr(rr, "registers", None)
60
- if not regs or len(regs) < count:
61
- warning(f"Incomplete registers at {addr} ({name}): {regs}")
62
- continue
63
-
64
- val_raw = decode_registers(regs, dtype)
65
- if val_raw is None:
66
- warning(f"Unable to decode {addr} ({name}) regs={regs}")
67
- continue
68
- # apply factor
69
- value = val_raw * factor
70
- # format integer-looking floats without trailing .0
71
- if isinstance(value, float) and value.is_integer():
72
- value = int(value)
73
- info(f"{addr} {name}: raw={regs} -> {value} (factor={factor}, type={dtype})")
74
- except Exception as e:
75
- error(f"Error reading {addr} ({name}): {e}")
76
- time.sleep(self.delay)
77
-
78
- # Optionally scan remaining addresses 8000..8200 for any non-zero single-register values
79
- info("\nQuick scan for single-register non-zero values (8000..8200):")
80
- for addr in range(8000, 8201):
81
- if addr in REG_MAP:
82
- continue
83
- try:
84
- rr = self.client.read_input_registers(address=addr - 1, count=1, device_id=self.device_id)
85
- if rr is None:
86
- continue
87
- if hasattr(rr, "isError") and rr.isError():
88
- continue
89
- regs = getattr(rr, "registers", None)
90
- if regs and regs[0] != 0:
91
- info(f"{addr}: {regs[0]} (0x{regs[0]:04X})")
92
- except Exception:
93
- pass
94
- time.sleep(0.005)
95
- except Exception as e:
96
- exception(e)
97
-
98
- def close(self):
99
- self.client.close()
100
-
101
-
102
- if __name__ == "__main__":
103
-
104
- mon = PowerEMS300(host="localhost", port=1502, period=10)
105
- mon.start()
106
-
107
- while(True):
108
- time.sleep(30)