mqtt.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import os
  2. import paho.mqtt.client as mqtt
  3. import json
  4. import time
  5. from dotenv import load_dotenv
  6. # Load the .env file
  7. load_dotenv()
  8. # Define the MQTT server details
  9. broker = os.environ.get("broker")
  10. port = int(os.environ.get("port"))
  11. # MQTT username and password
  12. username = os.environ.get("username")
  13. password = os.environ.get("password")
  14. def create_client() -> mqtt.Client:
  15. """Create an MQTT client and connect it to the broker
  16. Returns:
  17. mqtt.Client: Connected MQTT client instance
  18. """
  19. # Create a new MQTT client instance
  20. client = mqtt.Client()
  21. # Set username and password
  22. client.username_pw_set(username, password)
  23. # Connect to the MQTT broker
  24. client.connect(broker, port, 60)
  25. return client
  26. def create_config(client: mqtt.Client) -> None:
  27. """Create Home Assistant discovery topics
  28. Args:
  29. client (mqtt.Client): MQTT Client
  30. """
  31. # Device-specific information for multiple sensors
  32. node_id = "floppy_player" # Unique device ID
  33. # Define discovery and state topics for each sensor
  34. discovery_topic_disc = f"homeassistant/sensor/floppy_player/current_disc/config"
  35. discovery_topic_disc_type = (
  36. f"homeassistant/sensor/floppy_player/current_disc_type/config"
  37. )
  38. discovery_topic_disc_id = (
  39. f"homeassistant/sensor/floppy_player/current_disc_id/config"
  40. )
  41. current_disc_state_topic_disc = (
  42. f"homeassistant/sensor/floppy_player/current_disc/state"
  43. )
  44. current_disc_type_state_topic_disc = (
  45. f"homeassistant/sensor/floppy_player/current_disc_type/state"
  46. )
  47. current_disc_id_state_topic_disc = (
  48. f"homeassistant/sensor/floppy_player/current_disc_id/state"
  49. )
  50. discovery_topic_status = f"homeassistant/sensor/floppy_player/status/config"
  51. state_topic_status = f"homeassistant/sensor/floppy_player/status/state"
  52. # Sensor 1: current_disc (a text-based sensor)
  53. current_disc_config = {
  54. "name": "Current Disc",
  55. "state_topic": current_disc_state_topic_disc,
  56. "value_template": "{{ value }}", # Textual value
  57. "unique_id": f"{node_id}_current_disc",
  58. "device": {
  59. "identifiers": [node_id],
  60. "name": "Floppy Player",
  61. "model": "v1",
  62. "manufacturer": "Karl",
  63. },
  64. }
  65. current_disc_type_state_config = {
  66. "name": "Current Disc Type",
  67. "state_topic": current_disc_type_state_topic_disc,
  68. "value_template": "{{ value }}", # Textual value
  69. "unique_id": f"{node_id}_current_disc_type",
  70. "device": {
  71. "identifiers": [node_id],
  72. "name": "Floppy Player",
  73. "model": "v1",
  74. "manufacturer": "Karl",
  75. },
  76. }
  77. current_disc_id_state_config = {
  78. "name": "Current Disc Id",
  79. "state_topic": current_disc_id_state_topic_disc,
  80. "value_template": "{{ value }}", # Textual value
  81. "unique_id": f"{node_id}_current_disc_id",
  82. "device": {
  83. "identifiers": [node_id],
  84. "name": "Floppy Player",
  85. "model": "v1",
  86. "manufacturer": "Karl",
  87. },
  88. }
  89. # Sensor 2: status (another text-based sensor)
  90. status_config = {
  91. "name": "Device Status",
  92. "state_topic": state_topic_status,
  93. "value_template": "{{ value }}", # Textual value
  94. "unique_id": f"{node_id}_status",
  95. "device": {
  96. "identifiers": [node_id],
  97. "name": "Floppy Player",
  98. "model": "v1",
  99. "manufacturer": "Karl",
  100. },
  101. }
  102. client.publish(discovery_topic_disc, json.dumps(current_disc_config), retain=True)
  103. client.publish(
  104. discovery_topic_disc_type,
  105. json.dumps(current_disc_type_state_config),
  106. retain=True,
  107. )
  108. client.publish(
  109. discovery_topic_disc_id, json.dumps(current_disc_id_state_config), retain=True
  110. )
  111. client.publish(discovery_topic_status, json.dumps(status_config), retain=True)
  112. def check_current_disc(client: mqtt.Client) -> dict:
  113. """Check the current loaded disc, disc type, and disc ID by subscribing to the retained messages on the state topics.
  114. Args:
  115. client (mqtt.Client): MQTT Client
  116. Returns:
  117. dict: A dictionary containing the current disc, disc type, and disc ID.
  118. """
  119. def on_message(client, userdata, message):
  120. """Callback function to handle received MQTT messages."""
  121. topic = message.topic
  122. userdata[topic] = message.payload.decode()
  123. # Create a dictionary to store the messages
  124. userdata = {
  125. "homeassistant/sensor/floppy_player/current_disc/state": None,
  126. "homeassistant/sensor/floppy_player/current_disc_type/state": None,
  127. "homeassistant/sensor/floppy_player/current_disc_id/state": None,
  128. }
  129. # Set the user data and the on_message callback
  130. client.user_data_set(userdata)
  131. client.on_message = on_message
  132. # Subscribe to the topics
  133. current_disc_state_topic_disc = (
  134. "homeassistant/sensor/floppy_player/current_disc/state"
  135. )
  136. current_disc_type_state_topic_disc = (
  137. "homeassistant/sensor/floppy_player/current_disc_type/state"
  138. )
  139. current_disc_id_state_topic_disc = (
  140. "homeassistant/sensor/floppy_player/current_disc_id/state"
  141. )
  142. topics = [
  143. current_disc_state_topic_disc,
  144. current_disc_type_state_topic_disc,
  145. current_disc_id_state_topic_disc,
  146. ]
  147. for topic in topics:
  148. client.subscribe(topic)
  149. # Run the loop manually until we receive all messages or timeout
  150. timeout = 5 # Timeout after 5 seconds
  151. while any(value is None for value in userdata.values()) and timeout > 0:
  152. client.loop(timeout=0.1) # Process network events for a short time
  153. timeout -= 0.1
  154. # Check if we received all messages
  155. if any(value is None for value in userdata.values()):
  156. raise TimeoutError("Some retained messages were not found.")
  157. # Return the relevant messages
  158. return {
  159. "name": userdata[current_disc_state_topic_disc],
  160. "type": userdata[current_disc_type_state_topic_disc],
  161. "id": userdata[current_disc_id_state_topic_disc],
  162. }
  163. def check_current_status(client: mqtt.Client) -> str:
  164. """Check the current player status by subscribing to the retained message on the state topic.
  165. Args:
  166. client (mqtt.Client): MQTT Client
  167. Returns:
  168. str: Current object at current_disc/state
  169. """
  170. def on_message(client, userdata, message):
  171. """Callback function to handle received MQTT messages."""
  172. userdata['message'] = message.payload.decode()
  173. # Create a dictionary to store the message
  174. userdata = {'message': None}
  175. # Set the user data and the on_message callback
  176. client.user_data_set(userdata)
  177. client.on_message = on_message
  178. # Subscribe to the topic
  179. client.subscribe("homeassistant/sensor/floppy_player/status/state")
  180. # Run the loop manually until we receive a message
  181. timeout = 5 # Timeout after 5 seconds
  182. while userdata['message'] is None and timeout > 0:
  183. client.loop(timeout=0.1) # Process network events for a short time
  184. timeout -= 0.1
  185. # Check if we received the message
  186. if userdata['message'] is None:
  187. raise TimeoutError("No retained message was found.")
  188. return userdata['message']
  189. def update_disc(client: mqtt.Client, disc_message: list) -> None:
  190. """Update current disc.
  191. Args:
  192. client (mqtt.Client): MQTT Client
  193. disc_message (dict): Current disc information
  194. """
  195. # Publish disc name with QoS 1
  196. client.publish(
  197. "homeassistant/sensor/floppy_player/current_disc/state",
  198. disc_message[1],
  199. qos=1,
  200. retain=True
  201. )
  202. time.sleep(0.1) # Small delay to ensure messages are sent in order
  203. # Publish disc type with QoS 1
  204. client.publish(
  205. "homeassistant/sensor/floppy_player/current_disc_type/state",
  206. disc_message[2],
  207. qos=1,
  208. retain=True
  209. )
  210. time.sleep(0.1)
  211. # Publish disc ID with QoS 1
  212. client.publish(
  213. "homeassistant/sensor/floppy_player/current_disc_id/state",
  214. disc_message[3],
  215. qos=1,
  216. retain=True
  217. )
  218. print(f"Published current disc with QoS 1: {disc_message}")
  219. def control_player(client: mqtt.Client, state: str) -> None:
  220. """Control the player
  221. Args:
  222. client (mqtt.Client): MQTT Client
  223. state (str): Player State
  224. """
  225. client.publish(
  226. "homeassistant/sensor/floppy_player/status/state", state, retain=True
  227. )
  228. print(f"Published status: {state}")