/ MacroPad_RPC_Home_Assistant / rpc_ha_server.py
rpc_ha_server.py
  1  # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import time
  6  import json
  7  import ssl
  8  import socket
  9  import adafruit_minimqtt.adafruit_minimqtt as MQTT
 10  from rpc import RpcServer
 11  
 12  mqtt_client = None
 13  mqtt_connected = False
 14  last_mqtt_messages = {}
 15  
 16  # For program flow purposes, we do not want these functions to be called remotely
 17  PROTECTED_FUNCTIONS = ["main", "handle_rpc"]
 18  
 19  def connect(mqtt_client, userdata, flags, rc):
 20      global mqtt_connected
 21      mqtt_connected = True
 22  
 23  def disconnect(mqtt_client, userdata, rc):
 24      global mqtt_connected
 25      mqtt_connected = False
 26  
 27  def message(client, topic, message):
 28      last_mqtt_messages[topic] = message
 29  
 30  class MqttError(Exception):
 31      """For MQTT Specific Errors"""
 32      pass
 33  
 34  # Default to 1883 as SSL on CPython is not currently supported
 35  def mqtt_init(broker, port=1883, username=None, password=None):
 36      global mqtt_client, mqtt_connect_info
 37      mqtt_client = MQTT.MQTT(
 38          broker=broker,
 39          port=port,
 40          username=username,
 41          password=password,
 42          socket_pool=socket,
 43          ssl_context=ssl.create_default_context(),
 44      )
 45  
 46      mqtt_client.on_connect = connect
 47      mqtt_client.on_disconnect = disconnect
 48      mqtt_client.on_message = message
 49  
 50  def mqtt_connect():
 51      mqtt_client.connect()
 52  
 53  def mqtt_publish(topic, payload):
 54      if mqtt_client is None:
 55          raise MqttError("MQTT is not initialized")
 56      try:
 57          return_val = mqtt_client.publish(topic, json.dumps(payload))
 58      except BrokenPipeError:
 59          time.sleep(0.5)
 60          mqtt_client.connect()
 61          return_val = mqtt_client.publish(topic, json.dumps(payload))
 62      return return_val
 63  
 64  def mqtt_subscribe(topic):
 65      if mqtt_client is None:
 66          raise MqttError("MQTT is not initialized")
 67      return mqtt_client.subscribe(topic)
 68  
 69  def mqtt_get_last_value(topic):
 70      """Return the last value we have received regarding a topic"""
 71      if topic in last_mqtt_messages.keys():
 72          return last_mqtt_messages[topic]
 73      return None
 74  
 75  def is_running():
 76      return True
 77  
 78  def handle_rpc(packet):
 79      """This function will verify good data in packet,
 80      call the method with parameters, and generate a response
 81      packet as the return value"""
 82      print("Received packet")
 83      func_name = packet['function']
 84      if func_name in PROTECTED_FUNCTIONS:
 85          return rpc.create_response_packet(error=True, message=f"{func_name}'() is a protected function and can not be called.")
 86      if func_name not in globals():
 87          return rpc.create_response_packet(error=True, message=f"Function {func_name}() not found")
 88      try:
 89          return_val = globals()[func_name](*packet['args'], **packet['kwargs'])
 90      except MqttError as err:
 91          return rpc.create_response_packet(error=True, error_type="MQTT", message=str(err))
 92  
 93      packet = rpc.create_response_packet(return_val=return_val)
 94      return packet
 95  
 96  def main():
 97      """Command line, entry point"""
 98      global mqtt_connected
 99      while True:
100          rpc.loop(0.25)
101          if mqtt_connected and mqtt_client is not None:
102              try:
103                  mqtt_client.loop(0.5)
104              except AttributeError:
105                  mqtt_connected = False
106  
107  if __name__ == '__main__':
108      rpc = RpcServer(handle_rpc)
109      try:
110          print(f"Listening for RPC Calls, to stop press \"CTRL+C\"")
111          main()
112      except KeyboardInterrupt:
113          print("")
114          print(f"Caught interrupt, exiting...")
115      rpc.close_serial()