Skip to content

Commit f821708

Browse files
authored
Merge pull request #2613 from makermelissa/main
Update and Lint MacroPad RPC Home Assistant code to work with settings.toml
2 parents 64d7df9 + d17a78b commit f821708

File tree

4 files changed

+117
-84
lines changed

4 files changed

+117
-84
lines changed

MacroPad_RPC_Home_Assistant/boot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55
import usb_cdc
66

7-
usb_cdc.enable(console=True, data=True)
7+
usb_cdc.enable(console=True, data=True)

MacroPad_RPC_Home_Assistant/code.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
"""
55
Home Assistant Remote Procedure Call for MacroPad.
66
"""
7+
import os
78
import time
89
import displayio
910
import terminalio
1011
from adafruit_display_shapes.rect import Rect
11-
from rpc import RpcClient, RpcError
1212
from adafruit_display_text import label
1313
from adafruit_macropad import MacroPad
14-
from secrets import secrets
14+
from rpc import RpcClient, RpcError, MqttError
1515

1616
macropad = MacroPad()
1717
rpc = RpcClient()
@@ -22,27 +22,37 @@
2222
KEY_LABELS = ("Demo", "Office")
2323
UPDATE_DELAY = 0.25
2424
NEOPIXEL_COLORS = {
25-
"OFF": 0xff0000,
26-
"ON": 0x00ff00,
25+
"OFF": 0xFF0000,
26+
"ON": 0x00FF00,
2727
}
2828

29-
class MqttError(Exception):
30-
"""For MQTT Specific Errors"""
31-
pass
3229
# Set up displayio group with all the labels
3330
group = displayio.Group()
3431
for key_index in range(12):
3532
x = key_index % 3
3633
y = key_index // 3
37-
group.append(label.Label(terminalio.FONT, text=(str(KEY_LABELS[key_index]) if key_index < len(KEY_LABELS) else ''), color=0xFFFFFF,
38-
anchored_position=((macropad.display.width - 1) * x / 2,
39-
macropad.display.height - 1 -
40-
(3 - y) * 12),
41-
anchor_point=(x / 2, 1.0)))
34+
group.append(
35+
label.Label(
36+
terminalio.FONT,
37+
text=(str(KEY_LABELS[key_index]) if key_index < len(KEY_LABELS) else ""),
38+
color=0xFFFFFF,
39+
anchored_position=(
40+
(macropad.display.width - 1) * x / 2,
41+
macropad.display.height - 1 - (3 - y) * 12,
42+
),
43+
anchor_point=(x / 2, 1.0),
44+
)
45+
)
4246
group.append(Rect(0, 0, macropad.display.width, 12, fill=0xFFFFFF))
43-
group.append(label.Label(terminalio.FONT, text='Home Assistant', color=0x000000,
44-
anchored_position=(macropad.display.width//2, -2),
45-
anchor_point=(0.5, 0.0)))
47+
group.append(
48+
label.Label(
49+
terminalio.FONT,
50+
text="Home Assistant",
51+
color=0x000000,
52+
anchored_position=(macropad.display.width // 2, -2),
53+
anchor_point=(0.5, 0.0),
54+
)
55+
)
4656
macropad.display.show(group)
4757

4858
def rpc_call(function, *args, **kwargs):
@@ -54,16 +64,22 @@ def rpc_call(function, *args, **kwargs):
5464
return response["return_val"]
5565

5666
def mqtt_init():
57-
rpc_call("mqtt_init", secrets["mqtt_broker"], username=secrets["mqtt_username"], password=secrets["mqtt_password"], port=secrets["mqtt_port"])
67+
rpc_call(
68+
"mqtt_init",
69+
os.getenv("MQTT_BROKER"),
70+
username=os.getenv("MQTT_USERNAME"),
71+
password=os.getenv("MQTT_PASSWORD"),
72+
port=os.getenv("MQTT_PORT"),
73+
)
5874
rpc_call("mqtt_connect")
5975

60-
def update_key(key_number):
61-
if key_number < len(SUBSCRIBE_TOPICS):
62-
switch_state = rpc_call("mqtt_get_last_value", SUBSCRIBE_TOPICS[key_number])
76+
def update_key(key_id):
77+
if key_id < len(SUBSCRIBE_TOPICS):
78+
switch_state = rpc_call("mqtt_get_last_value", SUBSCRIBE_TOPICS[key_id])
6379
if switch_state is not None:
64-
macropad.pixels[key_number] = NEOPIXEL_COLORS[switch_state]
80+
macropad.pixels[key_id] = NEOPIXEL_COLORS[switch_state]
6581
else:
66-
macropad.pixels[key_number] = 0
82+
macropad.pixels[key_id] = 0
6783

6884
server_is_running = False
6985
print("Waiting for server...")
@@ -93,7 +109,11 @@ def update_key(key_number):
93109
last_macropad_encoder_value = macropad.encoder
94110

95111
macropad.encoder_switch_debounced.update()
96-
if macropad.encoder_switch_debounced.pressed and "key_number" not in output and ENCODER_ITEM is not None:
112+
if (
113+
macropad.encoder_switch_debounced.pressed
114+
and "key_number" not in output
115+
and ENCODER_ITEM is not None
116+
):
97117
output["key_number"] = ENCODER_ITEM
98118

99119
if output:

MacroPad_RPC_Home_Assistant/rpc.py

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,45 @@
77

88
import time
99
import json
10+
1011
try:
1112
import serial
1213
import adafruit_board_toolkit.circuitpython_serial
14+
1315
json_decode_exception = json.decoder.JSONDecodeError
1416
except ImportError:
1517
import usb_cdc as serial
18+
1619
json_decode_exception = ValueError
1720

18-
RESPONSE_TIMEOUT=5
19-
DATA_TIMEOUT=0.5
21+
RESPONSE_TIMEOUT = 5
22+
DATA_TIMEOUT = 0.5
23+
2024

2125
class RpcError(Exception):
2226
"""For RPC Specific Errors"""
23-
pass
27+
28+
class MqttError(Exception):
29+
"""For MQTT Specific Errors"""
2430

2531
class _Rpc:
2632
def __init__(self):
2733
self._serial = None
2834

2935
@staticmethod
30-
def create_response_packet(error=False, error_type="RPC", message=None, return_val=None):
36+
def create_response_packet(
37+
error=False, error_type="RPC", message=None, return_val=None
38+
):
3139
return {
3240
"error": error,
3341
"error_type": error_type if error else None,
3442
"message": message,
35-
"return_val": return_val
43+
"return_val": return_val,
3644
}
3745

3846
@staticmethod
39-
def create_request_packet(function, args=[], kwargs={}):
40-
return {
41-
"function": function,
42-
"args": args,
43-
"kwargs": kwargs
44-
}
47+
def create_request_packet(function, args=[], kwargs={}): # pylint: disable=dangerous-default-value
48+
return {"function": function, "args": args, "kwargs": kwargs}
4549

4650
def _wait_for_packet(self, timeout=None):
4751
incoming_packet = b""
@@ -51,11 +55,16 @@ def _wait_for_packet(self, timeout=None):
5155
if incoming_packet:
5256
data_start_time = time.monotonic()
5357
while not self._serial.in_waiting:
54-
if incoming_packet and (time.monotonic() - data_start_time) >= DATA_TIMEOUT:
58+
if (
59+
incoming_packet
60+
and (time.monotonic() - data_start_time) >= DATA_TIMEOUT
61+
):
5562
incoming_packet = b""
5663
if not incoming_packet and timeout is not None:
5764
if (time.monotonic() - response_start_time) >= timeout:
58-
return self.create_response_packet(error=True, message="Timed out waiting for response")
65+
return self.create_response_packet(
66+
error=True, message="Timed out waiting for response"
67+
)
5968
time.sleep(0.001)
6069
data = self._serial.read(self._serial.in_waiting)
6170
if data:
@@ -66,13 +75,13 @@ def _wait_for_packet(self, timeout=None):
6675
if sorted(tuple(packet.keys())) == sorted(self._packet_format()):
6776
return packet
6877
except json_decode_exception:
69-
pass # Incomplete packet
78+
pass # Incomplete packet
7079

7180
class RpcClient(_Rpc):
7281
def __init__(self):
7382
super().__init__()
7483
self._serial = serial.data
75-
84+
7685
def _packet_format(self):
7786
return self.create_response_packet().keys()
7887

@@ -97,13 +106,14 @@ def init_serial(self, baudrate):
97106
return serial.Serial(
98107
port,
99108
baudrate,
100-
parity='N',
109+
parity="N",
101110
rtscts=False,
102111
xonxoff=False,
103112
exclusive=True,
104113
)
105114

106-
def detect_port(self):
115+
@staticmethod
116+
def detect_port():
107117
"""
108118
Detect the port automatically
109119
"""
@@ -113,14 +123,16 @@ def detect_port(self):
113123
if len(ports) > 1:
114124
print("Multiple devices detected, using the first detected port.")
115125
return ports[0]
116-
raise RuntimeError("Unable to find any CircuitPython Devices with the CDC Data port enabled.")
126+
raise RuntimeError(
127+
"Unable to find any CircuitPython Devices with the CDC Data port enabled."
128+
)
117129

118130
def loop(self, timeout=None):
119131
packet = self._wait_for_packet(timeout)
120132
if "error" not in packet:
121133
response_packet = self._handler(packet)
122134
self._serial.write(bytes(json.dumps(response_packet), "utf-8"))
123-
135+
124136
def close_serial(self):
125137
if self._serial is not None:
126-
self._serial.close()
138+
self._serial.close()

MacroPad_RPC_Home_Assistant/rpc_ha_server.py

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,29 @@
77
import ssl
88
import socket
99
import adafruit_minimqtt.adafruit_minimqtt as MQTT
10-
from rpc import RpcServer
10+
from rpc import RpcServer, MqttError
1111

12-
mqtt_client = None
13-
mqtt_connected = False
12+
mqtt_status = {
13+
"connected": False,
14+
"client": None,
15+
}
1416
last_mqtt_messages = {}
1517

1618
# For program flow purposes, we do not want these functions to be called remotely
1719
PROTECTED_FUNCTIONS = ["main", "handle_rpc"]
1820

19-
def connect(mqtt_client, userdata, flags, rc):
20-
global mqtt_connected
21-
mqtt_connected = True
21+
def connect(_mqtt_client, _userdata, _flags, _rc):
22+
mqtt_status["connected"] = True
2223

23-
def disconnect(mqtt_client, userdata, rc):
24-
global mqtt_connected
25-
mqtt_connected = False
24+
def disconnect(_mqtt_client, _userdata, _rc):
25+
mqtt_status["connected"] = False
2626

27-
def message(client, topic, message):
28-
last_mqtt_messages[topic] = message
27+
def message(_client, topic, payload):
28+
last_mqtt_messages[topic] = payload
2929

30-
class MqttError(Exception):
31-
"""For MQTT Specific Errors"""
32-
pass
33-
34-
# Default to 1883 as SSL on CPython is not currently supported
30+
# Default to 1883 since SSL on CPython is not currently supported
3531
def mqtt_init(broker, port=1883, username=None, password=None):
36-
global mqtt_client, mqtt_connect_info
37-
mqtt_client = MQTT.MQTT(
32+
mqtt_status["client"] = MQTT.MQTT(
3833
broker=broker,
3934
port=port,
4035
username=username,
@@ -43,28 +38,28 @@ def mqtt_init(broker, port=1883, username=None, password=None):
4338
ssl_context=ssl.create_default_context(),
4439
)
4540

46-
mqtt_client.on_connect = connect
47-
mqtt_client.on_disconnect = disconnect
48-
mqtt_client.on_message = message
41+
mqtt_status["client"].on_connect = connect
42+
mqtt_status["client"].on_disconnect = disconnect
43+
mqtt_status["client"].on_message = message
4944

5045
def mqtt_connect():
51-
mqtt_client.connect()
46+
mqtt_status["client"].connect()
5247

5348
def mqtt_publish(topic, payload):
54-
if mqtt_client is None:
49+
if mqtt_status["client"] is None:
5550
raise MqttError("MQTT is not initialized")
5651
try:
57-
return_val = mqtt_client.publish(topic, json.dumps(payload))
52+
return_val = mqtt_status["client"].publish(topic, json.dumps(payload))
5853
except BrokenPipeError:
5954
time.sleep(0.5)
60-
mqtt_client.connect()
61-
return_val = mqtt_client.publish(topic, json.dumps(payload))
55+
mqtt_status["client"].connect()
56+
return_val = mqtt_status["client"].publish(topic, json.dumps(payload))
6257
return return_val
6358

6459
def mqtt_subscribe(topic):
65-
if mqtt_client is None:
60+
if mqtt_status["client"] is None:
6661
raise MqttError("MQTT is not initialized")
67-
return mqtt_client.subscribe(topic)
62+
return mqtt_status["client"].subscribe(topic)
6863

6964
def mqtt_get_last_value(topic):
7065
"""Return the last value we have received regarding a topic"""
@@ -80,36 +75,42 @@ def handle_rpc(packet):
8075
call the method with parameters, and generate a response
8176
packet as the return value"""
8277
print("Received packet")
83-
func_name = packet['function']
78+
func_name = packet["function"]
8479
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.")
80+
return rpc.create_response_packet(
81+
error=True,
82+
message=f"{func_name}'() is a protected function and can not be called.",
83+
)
8684
if func_name not in globals():
87-
return rpc.create_response_packet(error=True, message=f"Function {func_name}() not found")
85+
return rpc.create_response_packet(
86+
error=True, message=f"Function {func_name}() not found"
87+
)
8888
try:
89-
return_val = globals()[func_name](*packet['args'], **packet['kwargs'])
89+
return_val = globals()[func_name](*packet["args"], **packet["kwargs"])
9090
except MqttError as err:
91-
return rpc.create_response_packet(error=True, error_type="MQTT", message=str(err))
91+
return rpc.create_response_packet(
92+
error=True, error_type="MQTT", message=str(err)
93+
)
9294

9395
packet = rpc.create_response_packet(return_val=return_val)
9496
return packet
9597

9698
def main():
9799
"""Command line, entry point"""
98-
global mqtt_connected
99100
while True:
100101
rpc.loop(0.25)
101-
if mqtt_connected and mqtt_client is not None:
102+
if mqtt_status["connected"] and mqtt_status["client"] is not None:
102103
try:
103-
mqtt_client.loop(0.5)
104+
mqtt_status["client"].loop(0.5)
104105
except AttributeError:
105-
mqtt_connected = False
106+
mqtt_status["connected"] = False
106107

107-
if __name__ == '__main__':
108+
if __name__ == "__main__":
108109
rpc = RpcServer(handle_rpc)
109110
try:
110-
print(f"Listening for RPC Calls, to stop press \"CTRL+C\"")
111+
print('Listening for RPC Calls, to stop press "CTRL+C"')
111112
main()
112113
except KeyboardInterrupt:
113114
print("")
114-
print(f"Caught interrupt, exiting...")
115-
rpc.close_serial()
115+
print("Caught interrupt, exiting...")
116+
rpc.close_serial()

0 commit comments

Comments
 (0)