main.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. #!/usr/bin/env python3
  2. import argparse
  3. import asyncio
  4. import signal
  5. import aiomqtt
  6. from bleak import BleakScanner
  7. from bleak.exc import BleakError, BleakDeviceNotFoundError
  8. from src.homeassistant import MqttSensor
  9. from src.bleclient import BleClient, Result
  10. from src.variables import variables
  11. send_config = True
  12. request_interval = 20 # In seconds
  13. reconnect_interval = 5 # In seconds
  14. async def request_and_publish_details(sensor: MqttSensor, mppt: BleClient) -> None:
  15. global send_config
  16. details = await mppt.request_details()
  17. if details:
  18. print(f"Battery: {details['battery_percentage'].value}% ({details['battery_voltage'].value}V)")
  19. if send_config:
  20. print(f"configuring {len(details)}/{len(variables)} entities")
  21. await sensor.store_config(details)
  22. send_config = False
  23. await sensor.publish(details)
  24. else:
  25. print("No values recieved")
  26. async def run_mppt(sensor: MqttSensor, address: str):
  27. try:
  28. async with BleClient(address) as mppt:
  29. while True:
  30. await request_and_publish_details()
  31. await asyncio.sleep(request_interval)
  32. except BleakDeviceNotFoundError:
  33. print(f"BLE device with address {address} was not found")
  34. await asyncio.sleep(reconnect_interval)
  35. except BleakError as e:
  36. print(f"BLE error occurred: {e}")
  37. await asyncio.sleep(reconnect_interval)
  38. async def run_mqtt(address, host, port, username, password):
  39. while True:
  40. try:
  41. async with MqttSensor(hostname=host, port=port, username=username, password=password) as sensor:
  42. print(f"Connected to MQTT broker at {host}:{port}")
  43. while True:
  44. await run_mppt(sensor, address)
  45. except aiomqtt.MqttError as error:
  46. print(f'Error "{error}". Reconnecting in {reconnect_interval} seconds.')
  47. await asyncio.sleep(reconnect_interval)
  48. except asyncio.CancelledError:
  49. raise # Re-raise the CancelledError to stop the task
  50. except Exception as e:
  51. print(f"An error occurred during BLE communication: {e}")
  52. await asyncio.sleep(reconnect_interval)
  53. async def main(*args):
  54. try:
  55. loop = asyncio.get_running_loop()
  56. task = loop.create_task(run_mqtt(*args))
  57. # Setup signal handler to cancel the task on termination
  58. for signame in {'SIGINT', 'SIGTERM'}:
  59. loop.add_signal_handler(getattr(signal, signame),
  60. task.cancel)
  61. await task # Wait for the task to complete
  62. except asyncio.CancelledError:
  63. pass # Task was cancelled, no need for an error message
  64. async def list_services(address):
  65. async with BleClient(address) as mppt:
  66. await mppt.list_services()
  67. async def scan_for_devices():
  68. devices = await BleakScanner.discover()
  69. if not devices:
  70. print("No BLE devices found.")
  71. else:
  72. print("Available BLE devices:")
  73. for device in devices:
  74. print(f"{device.address} - {device.name}")
  75. return devices
  76. if __name__ == '__main__':
  77. parser = argparse.ArgumentParser(description='Solarlife MPPT BLE Client')
  78. parser.add_argument('address', help='BLE device address')
  79. parser.add_argument('--host', help='MQTT broker host', default='localhost')
  80. parser.add_argument('--port', help='MQTT broker port', default=1883, type=int)
  81. parser.add_argument('--username', help='MQTT username')
  82. parser.add_argument('--password', help='MQTT password')
  83. parser.add_argument('--list-services', help='List GATT services', action='store_true')
  84. parser.add_argument('--scan', help='Scan for bluetooth devices', action='store_true')
  85. args = parser.parse_args()
  86. if args.scan:
  87. asyncio.run(scan_for_devices())
  88. elif args.list_services:
  89. asyncio.run(list_services(args.address))
  90. else:
  91. asyncio.run(main(args.address, args.host, args.port, args.username, args.password))