Skip to content

Commit 7c7b47a

Browse files
committed
v0.7.2
1 parent 8992274 commit 7c7b47a

File tree

9 files changed

+84
-20
lines changed

9 files changed

+84
-20
lines changed

.pylintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ disable=
2323
broad-exception-caught,
2424
broad-exception-raised,
2525
logging-not-lazy,
26+
logging-fstring-interpolation,
2627
fixme # temporary
2728

2829
[STRING]

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.7.2] - 2025-04-04
9+
10+
### Fixed
11+
12+
- Changed handling of an exception that could occur if the http response sensor was never added as a configured entity. Instead of an exception now only an info log message is shown.
13+
14+
### Added
15+
16+
- Added an option to change what should be shown in the http response sensor and media player title if no match has been found for the configured regular expression
17+
818
## [0.7.1] - 2025-03-16
919

1020
### Changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ If you activate the option to ignore HTTP requests errors in the integration set
123123

124124
#### Show (parts) of the server response text in the remote ui
125125

126-
The integration exposes a sensor entity that shows the text of the response body from the last executed http request command. Responses are also used for the media_title attribute of the associated request method's media player entity. This allows you to add a media player widget to an activity and see the response within an activity because you can't add sensors to activities as there's no widget for them.
126+
The integration exposes a sensor entity that shows the text of the response body from the last executed http request command. Responses are also used for the media_title attribute of the associated request method's media player entity. This allows you to add a media player widget to an activity and see the response within an activity because you can't add sensors to activities as there's no widget for them. If the sensor entity has not been added as a configured entity in the web configurator a info message will be shown in the integration log.
127127

128-
The output can be parsed to only show a specific part of the response message using regular expressions. These can be configured in the advanced setup. Sites like [regex101.com](https://regex101.com) or the AI model of your choice can help you with finding matching expressions. By default the complete response message will be used if no regular expression has been set or no matches have been found.
128+
The output can be parsed to only show a specific part of the response message using regular expressions. These can be configured in the advanced setup. Sites like [regex101.com](https://regex101.com) or the AI model of your choice can help you with finding matching expressions. By default the complete response message will be used if no regular expression has been set or no matches have been found. The advanced setup has an option to use an empty response or show an error message instead if no match has been found.
129129

130130
#### Legacy syntax
131131

driver.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"driver_id": "requests",
3-
"version": "0.7.1",
4-
"release_date": "2025-03-16",
3+
"version": "0.7.2",
4+
"release_date": "2025-04-04",
55
"min_core_api": "0.24.3",
66
"name": {
77
"en": "HTTP requests, WoL & text over TCP",
@@ -10,7 +10,7 @@
1010
"icon": "uc:webhook",
1111
"description": {
1212
"en": "A network multi tool integration to send HTTP get/post/patch/put/delete/head requests, wake-on-LAN magic packets and text over tcp",
13-
"de": "Eine Netzwerk Multitool-Integration zum Senden von HTTP Get/Post/Patch/Put/Delete/Head-Anfragen, Wake-on-LAN Magic Pakets und Text über TCP"
13+
"de": "Eine Netzwerk Multi-Tool-Integration zum Senden von HTTP Get/Post/Patch/Put/Delete/Head-Anfragen, Wake-on-LAN Magic Pakets und Text über TCP"
1414
},
1515
"port": 9081,
1616
"developer": {

intg-requests/config.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ class Setup:
2525
"rq_fire_and_forget": False,
2626
"rq_legacy": False,
2727
"rq_response_regex": "",
28+
"rq_response_nomatch_option": "full",
29+
"rq_response_nomatch_dropdown_items": [
30+
{"id": "full", "label": {"en": "Full", "de": "Komplett"}},
31+
{"id": "empty", "label": {"en": "Empty", "de": "Leer"}},
32+
{"id": "error", "label": {"en": "Error", "de": "Fehler"}}
33+
],
2834
"id-rq-sensor": "http-response",
2935
"name-rq-sensor": {
3036
"en": "HTTP Request Response",
@@ -51,9 +57,10 @@ class Setup:
5157
},
5258
}
5359
__setters = ["standby", "setup_complete", "setup_reconfigure", "tcp_text_timeout", "rq_timeout", "rq_user_agent", "rq_ssl_verify", \
54-
"rq_fire_and_forget", "rq_legacy", "rq_response_regex", "bundle_mode", "cfg_path"]
60+
"rq_fire_and_forget", "rq_legacy", "rq_response_regex", "rq_response_nomatch_option", "bundle_mode", "cfg_path"]
5561
#Skip runtime only related values in config file
56-
__storers = ["setup_complete", "tcp_text_timeout", "rq_timeout", "rq_user_agent", "rq_ssl_verify", "rq_fire_and_forget", "rq_legacy", "rq_response_regex"]
62+
__storers = ["setup_complete", "tcp_text_timeout", "rq_timeout", "rq_user_agent", "rq_ssl_verify", "rq_fire_and_forget", "rq_legacy", \
63+
"rq_response_regex", "rq_response_nomatch_option"]
5764

5865
all_cmds = ["get", "post", "patch", "put", "delete", "head", "wol", "tcp-text"]
5966
rq_ids = [__conf["id-rq-sensor"], __conf["id-get"], __conf["id-post"], __conf["id-patch"], __conf["id-put"], __conf["id-delete"], __conf["id-head"]]

intg-requests/driver.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,12 @@ async def on_subscribe_entities(entity_ids: list[str]) -> None:
147147
"""
148148
_LOG.info("Received subscribe entities event for entity ids: " + str(entity_ids))
149149

150-
config.Setup.set("standby", False)
150+
#TODO #WAIT Add api.configured_entities.add(definition) when the unsubscribe event is handled by the integration API
151151

152+
config.Setup.set("standby", False)
152153

153154

155+
# Doesn't work as the unsubscribe event is not handled by the integration API
154156
@api.listens_to(ucapi.Events.UNSUBSCRIBE_ENTITIES)
155157
async def on_unsubscribe_entities(entity_ids: list[str]) -> None:
156158
"""
@@ -162,6 +164,8 @@ async def on_unsubscribe_entities(entity_ids: list[str]) -> None:
162164
"""
163165
_LOG.info("Unsubscribe entities event for entity ids: %s", entity_ids)
164166

167+
#TODO #WAIT Add api.configured_entities.remove(entity_ids) when the unsubscribe event is handled by the integration API
168+
165169

166170

167171
def setup_logger():

intg-requests/media_player.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131

3232
def get_mac(param: str):
33-
"""Accepts mac, ip addresses or hostnames. Get the mac address or checks if the mac address is valid.\
33+
"""Accepts mac, ip addresses or host names. Get the mac address or checks if the mac address is valid.\
3434
If the mac address can not be discovered a value error is raised"""
3535

3636
try:
@@ -63,7 +63,7 @@ def get_mac(param: str):
6363
_LOG.info("Got mac address from entered ipv4 ip: " + param)
6464
if param == "" or param is None:
6565
raise OSError("Could not convert ipv4 with getmac module. Discover the mac address from an ip address or a hostname may not work on all systems. \
66-
Please refer to the getmac supported platforms (https://github.com/GhostofGoes/getmac?tab=readme-ov-file#platforms-currently-supported)")
66+
Please refer to the getmac supported platforms (https://github.com/GhostofGoes/getmac?tab=readme-ov-file#platforms-currently-supported)")
6767
except AddressValueError:
6868
try:
6969
IPv6Address(param)
@@ -75,7 +75,7 @@ def get_mac(param: str):
7575
_LOG.info("Got mac address from entered ipv6 ip: " + param)
7676
if param == "" or param is None:
7777
raise OSError("Could not convert ipv6 with getmac module. Discover the mac address from an ip address or a hostname may not work on all systems. \
78-
Please refer to the getmac supported platforms (https://github.com/GhostofGoes/getmac?tab=readme-ov-file#platforms-currently-supported)")
78+
Please refer to the getmac supported platforms (https://github.com/GhostofGoes/getmac?tab=readme-ov-file#platforms-currently-supported)")
7979
except AddressValueError as v:
8080
raise ValueError(v) from v
8181

@@ -133,6 +133,7 @@ def parse_rq_response(response: str):
133133
"""Parse http request response with configured regular expression"""
134134

135135
regex = config.Setup.get("rq_response_regex")
136+
nomatch_option = config.Setup.get("rq_response_nomatch_option")
136137

137138
if regex == "":
138139
parsed_response = response
@@ -143,9 +144,18 @@ def parse_rq_response(response: str):
143144
parsed_response = match.group(1)
144145
_LOG.debug("Parsed response from configured regex: " + parsed_response)
145146
else:
146-
parsed_response = response
147-
_LOG.warning("No matches found in the http request response for the regular expression " + regex + ". \
148-
The complete response will be used")
147+
_LOG.warning("No matches found in the http request response for the regular expression " + regex)
148+
149+
if nomatch_option == "full":
150+
_LOG.debug("The full response will be used instead")
151+
parsed_response = response
152+
elif nomatch_option == "error":
153+
_LOG.debug("An error message will be used instead")
154+
#TODO #WAIT Add a translation for the error message when get_localization_cfg() is implemented in the Python API
155+
parsed_response = "No match found"
156+
elif nomatch_option == "empty":
157+
_LOG.debug("An empty response will be used instead")
158+
parsed_response = ""
149159

150160
return parsed_response
151161

@@ -158,7 +168,12 @@ def update_response(method:str, response: str):
158168

159169
parsed_response = parse_rq_response(response)
160170

161-
sensor.update_rq_sensor(config.Setup.get("id-rq-sensor"), parsed_response)
171+
try:
172+
sensor.update_rq_sensor(config.Setup.get("id-rq-sensor"), parsed_response)
173+
except ModuleNotFoundError as f:
174+
_LOG.info(f)
175+
except Exception as e:
176+
_LOG.error(e)
162177

163178
update_rq_media_widget(entity_id, parsed_response)
164179

@@ -445,7 +460,8 @@ async def mp_cmd_assigner(entity_id: str, cmd_name: str, params: dict[str, Any]
445460

446461
try:
447462
#TODO Run via asyncio.gather() to prevent potential blocking the event loop
448-
send_magic_packet(*macs, **params) #Unpack macs list with * and params dicts list with **
463+
await asyncio.gather(asyncio.to_thread(send_magic_packet, *macs, *params), asyncio.sleep(0)) #Unpack macs list with * and params dicts list with **
464+
#send_magic_packet(*macs, **params) #Unpack macs list with * and params dicts list with **
449465
except ValueError as v:
450466
_LOG.error(v)
451467
return ucapi.StatusCodes.BAD_REQUEST

intg-requests/sensor.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
_LOG = logging.getLogger(__name__)
1010

11-
#TODO Add possibility to add additional sensor entities in advanced setup. Each sensor then can be linked to a specific http request command by using a special command parameter
12-
#TODO Use media player title, artist, album or media type to also show the response because sensors cant be added to activities and have no widgets
11+
#TODO Add possibility to add additional sensor entities in advanced setup.
12+
# Each sensor then can be linked to a specific http request command by using a special command parameter
1313

1414

1515
async def add_rq_sensor(ent_id: str, name: str):
@@ -42,6 +42,6 @@ def update_rq_sensor(entity_id: str, response: str):
4242
raise Exception("Error while updating sensor value for entity id " + entity_id) from e
4343

4444
if not api_update_attributes:
45-
raise Exception("Sensor entity " + entity_id + " not found. Please make sure it's added as a configured entity on the remote")
45+
raise ModuleNotFoundError("Sensor entity " + entity_id + " not found or added as configured entity. Skipping update.")
4646

4747
_LOG.info("Updated http request response sensor value to " + response)

intg-requests/setup.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,18 @@ async def handle_driver_setup(msg: ucapi.DriverSetupRequest,) -> ucapi.SetupActi
8989
#TODO Remove legacy syntax in a future version
9090
rq_legacy = config.Setup.get("rq_legacy")
9191
rq_response_regex = config.Setup.get("rq_response_regex")
92+
rq_response_nomatch_option = config.Setup.get("rq_response_nomatch_option")
93+
rq_response_nomatch_dropdown_items = config.Setup.get("rq_response_nomatch_dropdown_items")
9294
except ValueError as v:
9395
_LOG.error(v)
9496

97+
#TODO Remove legacy syntax in a future version
9598
_LOG.debug("Currently stored - tcp_text_timeout: " + str(tcp_text_timeout) + " , rq_timeout: " + str(rq_timeout) + " , \
9699
rq_ssl_verify: " + str(rq_ssl_verify) + " , rq_fire_and_forget: " + str(rq_fire_and_forget) + ", \
97-
rq_user_agent: " + str(rq_user_agent) + ", rq_legacy: " + str(rq_legacy) + ", rq_response_regex: " + str(rq_response_regex))
100+
rq_user_agent: " + str(rq_user_agent) + ", rq_legacy: " + str(rq_legacy) + ", rq_response_regex: " + str(rq_response_regex) + ", \
101+
rq_response_options: " + str(rq_response_nomatch_option))
98102

103+
#TODO Remove legacy syntax in a future version
99104
return ucapi.RequestUserInput(
100105
{
101106
"en": "Configuration",
@@ -174,6 +179,18 @@ async def handle_driver_setup(msg: ucapi.DriverSetupRequest,) -> ucapi.SetupActi
174179
}
175180
},
176181
},
182+
{
183+
"id": "rq_response_nomatch_option",
184+
"label": {
185+
"en": "Response if no match for the regular expression has been found:",
186+
"de": "Antwort, falls keine Übereinstimmung mit dem regulären Ausdruck gefunden wurde:"
187+
},
188+
"field": {"dropdown": {
189+
"value": rq_response_nomatch_dropdown_items[0]["id"],
190+
"items": rq_response_nomatch_dropdown_items
191+
}
192+
},
193+
},
177194
{
178195
"id": "rq_ssl_verify",
179196
"label": {
@@ -239,6 +256,7 @@ async def handle_user_data_response(msg: ucapi.UserDataResponse) -> ucapi.Setup
239256
#TODO Remove legacy syntax in a future version
240257
rq_legacy = msg.input_values["rq_legacy"]
241258
rq_response_regex = msg.input_values["rq_response_regex"]
259+
rq_response_nomatch_option = msg.input_values["rq_response_nomatch_option"]
242260

243261
rq_timeout = int(rq_timeout)
244262
tcp_text_timeout = int(tcp_text_timeout)
@@ -275,6 +293,14 @@ async def handle_user_data_response(msg: ucapi.UserDataResponse) -> ucapi.Setup
275293
return ucapi.SetupError()
276294
_LOG.info("Http request response regular expression: \"" + str(rq_response_regex) + "\"")
277295

296+
try:
297+
config.Setup.set("rq_response_nomatch_option", rq_response_nomatch_option)
298+
except Exception as e:
299+
_LOG.error(e)
300+
config.Setup.set("setup_complete", False)
301+
return ucapi.SetupError()
302+
_LOG.info("Http request response option: \"" + str(rq_response_nomatch_option) + "\"")
303+
278304
if rq_ssl_verify == "true": #Boolean in quotes as all values are returned as strings
279305
try:
280306
config.Setup.set("rq_ssl_verify", True)

0 commit comments

Comments
 (0)