main.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. #!/usr/bin/env python3
  2. import argparse
  3. import asyncio
  4. import signal
  5. import json
  6. import paho.mqtt.client as mqtt
  7. from bleak.exc import BleakError, BleakDeviceNotFoundError
  8. from bleclient import BleClient
  9. client = mqtt.Client()
  10. send_config = True
  11. def details_handler(details):
  12. if details:
  13. print(f"Battery: {details['battery_percentage']}% ({details['battery_voltage']}V)")
  14. mqtt_publish(details, client)
  15. else:
  16. print("No values recieved")
  17. def mqtt_publish(details, client):
  18. global send_config
  19. # Define the base topic for MQTT Discovery
  20. base_topic = "homeassistant"
  21. # Define the device information
  22. device_info = {
  23. "identifiers": ["solarlife_mppt_ble"],
  24. "name": "Solarlife MPPT",
  25. "manufacturer": "Solarlife",
  26. }
  27. # Publish each item in the details dictionary to its own MQTT topic
  28. for key, value in details.items():
  29. state_topic = f"{base_topic}/sensor/solarlife/{key}/state"
  30. topic = f"{base_topic}/sensor/solarlife/{key}/config"
  31. # Create the MQTT Discovery payload
  32. payload = {
  33. "name": f"Solarlife {key.replace('_', ' ').title()}",
  34. "device": device_info,
  35. "unique_id": f"solarlife_{key}",
  36. "state_topic": state_topic,
  37. "unit_of_measurement": BleClient.get_unit_of_measurement(key)
  38. }
  39. if "daily_energy" in key:
  40. payload['device_class'] = "energy"
  41. payload['state_class'] = "total_increasing"
  42. elif "total_energy" in key:
  43. payload['device_class'] = "energy"
  44. payload['state_class'] = "total"
  45. elif "voltage" in key:
  46. payload['device_class'] = "voltage"
  47. payload['state_class'] = "measurement"
  48. elif "current" in key:
  49. payload['device_class'] = "current"
  50. payload['state_class'] = "measurement"
  51. elif "power" in key:
  52. payload['device_class'] = "power"
  53. payload['state_class'] = "measurement"
  54. elif "temperature" in key:
  55. payload['device_class'] = "temperature"
  56. payload['state_class'] = "measurement"
  57. elif key == "battery_percentage":
  58. payload['device_class'] = "battery"
  59. payload['state_class'] = "measurement"
  60. # Publish the MQTT Discovery payload
  61. if send_config:
  62. client.publish(topic, payload=json.dumps(payload), retain=True)
  63. # Publish the entity state
  64. client.publish(state_topic, payload=str(value))
  65. send_config = False
  66. async def main(address, host, port, username, password):
  67. client.username_pw_set(username, password) # Set MQTT username and password
  68. async def run_mppt():
  69. while True:
  70. try:
  71. client.connect(host, port) # Connect to the MQTT broker
  72. break # Connection successful, exit the loop
  73. except asyncio.CancelledError:
  74. raise # Re-raise the CancelledError to stop the task
  75. except Exception as e:
  76. print(f"An error occurred while connecting to MQTT broker: {e}")
  77. await asyncio.sleep(5) # Wait for 5 seconds before retrying
  78. while True:
  79. try:
  80. async with BleClient(address) as mppt:
  81. mppt.on_details_received = details_handler
  82. await mppt.request_details()
  83. while True:
  84. await asyncio.sleep(20.0)
  85. try:
  86. await mppt.request_details()
  87. except BleakError as e:
  88. print(f"BLE error occurred: {e}")
  89. # Handle the BLE error accordingly, e.g., reconnect or terminate the task
  90. break
  91. except asyncio.CancelledError:
  92. raise # Re-raise the CancelledError to stop the task
  93. except BleakDeviceNotFoundError:
  94. print(f"BLE device with address {address} was not found")
  95. await asyncio.sleep(5) # Wait for 5 seconds before retrying
  96. except Exception as e:
  97. print(f"An error occurred during BLE communication: {e}")
  98. await asyncio.sleep(5) # Wait for 5 seconds before retrying
  99. try:
  100. loop = asyncio.get_running_loop()
  101. task = loop.create_task(run_mppt())
  102. # Setup signal handler to cancel the task on termination
  103. for signame in {'SIGINT', 'SIGTERM'}:
  104. loop.add_signal_handler(getattr(signal, signame),
  105. task.cancel)
  106. await task # Wait for the task to complete
  107. except asyncio.CancelledError:
  108. pass # Task was cancelled, no need for an error message
  109. if __name__ == '__main__':
  110. parser = argparse.ArgumentParser(description='Solarlife MPPT BLE Client')
  111. parser.add_argument('address', help='BLE device address')
  112. parser.add_argument('--host', help='MQTT broker host', default='localhost')
  113. parser.add_argument('--port', help='MQTT broker port', default=1883, type=int)
  114. parser.add_argument('--username', help='MQTT username')
  115. parser.add_argument('--password', help='MQTT password')
  116. args = parser.parse_args()
  117. asyncio.run(main(args.address, args.host, args.port, args.username, args.password))