-
Notifications
You must be signed in to change notification settings - Fork 436
Description
What happened?
Receiving data from my PWS (personal weather station) gives an error
2026-03-14 11:18:00.250597 ERROR AppDaemon: ------------------------------------------------------------
2026-03-14 11:18:00.250953 ERROR AppDaemon: Unexpected error during API call
2026-03-14 11:18:00.251287 ERROR AppDaemon: ------------------------------------------------------------
2026-03-14 11:18:00.252903 ERROR AppDaemon: Traceback (most recent call last):
File "/usr/local/lib/python3.12/site-packages/appdaemon/http.py", line 887, in call_app_endpoint
ret, code = await self.dispatch_app_endpoint(endpoint, request)
^^^^^^^^^
ValueError: not enough values to unpack (expected 2, got 0)
2026-03-14 11:18:00.253242 ERROR AppDaemon: ------------------------------------------------------------
Analysis of the messages the PWS pushes:
headers: "rawHeaders":["HOST","","Connection","Close","Content-Type","application/x-www-form-urlencoded","Content-Length","474"]
data: {"PASSKEY":"xxxxxxxxxxx","stationtype":"EasyWeatherV1.7.5", "dateutc":"2026-03-11 21:45:28","tempinf":"72.5", ...}
My appdeamon code (just basic):
from typing import Literal
import appdaemon.plugins.hass.hassapi as hass
class Pws(hass.Hass):
def initialize(self):
http_endpoint = self.args.get('http_endpoint', 'pwsdata')
self.register_endpoint(self._httpHandler, http_endpoint, methods=['POST'])
self.log(f'Endpoint {http_endpoint} registered', level='INFO')
def _httpHandler(self, data, **kwargs) -> tuple[dict[str, str], Literal[200]]:
self.log(f'-------------- Received data --------------')
self.log(f'data: {data}')
self.log(f'kwargs: {kwargs}')
self.log(f'-------------------------------------------')
#for k,v in data.items():
# self.log(f'arg: {k}={v}')
return {'status': 'ok'}, 200
I run appdaemon as docker container. As info: I made the appdeamon code available for my VScode editor:
From the vs terminal run:
docker cp 420_appdaemon:/usr/local/lib/python3.12/site-packages/appdaemon ~/Desktop/docker/athome/420_appdaemon/
then add in this to the docker-compose in volumes:
- ${BASEDIR}appdaemon/appdaemon:/usr/local/lib/python3.12/site-packages/appdaemon
Modified 2 methods in http.py
The first one (call_app_endpoint) is merely cosmetic:
@securedata
# @next-release get requests object in somehow
async def call_app_endpoint(self, request):
endpoint = request.match_info.get("endpoint")
try:
ret, code = await self.dispatch_app_endpoint(endpoint, request)
except Exception:
self.logger.error("-" * 60)
self.logger.error("Unexpected error during API call")
self.logger.error(traceback.format_exc())
self.logger.error("-" * 60)
return self.get_response(request, 500, "Internal Server Error")
# Normalize return values; Ensure the body is always JSON-serializable
if not isinstance(ret, dict):
ret = {"result": ret}
# Standardized error handling
if code == 404:
return self.get_response(request, 404, "Endpoint not found")
if code == 500:
return self.get_response(request, 500, "Internal Server Error")
# Log success or non-fatal errors
status_text = "OK" if 200 <= code < 300 else "ERROR"
self.access.info("API Call to %s: status: %s %s", endpoint, code, status_text)
# Always return JSON
return web.json_response(ret, status=code, dumps=utils.convert_json)
But the second (dispatch_app_endpoint) was needed to get my code working.
It uses the header content type to call the appropriate request method.
I'm not an expert, so probably this isn't completely monkey-proof, but it worked for me.
An an extra feature I added an extra argument that can be set in the register_endpoint call.
If return_as_text=True, then the dispatch_app_endpoint function returns a string (and does not look at the header).
This to deal with some poor designed IOT device - interfaces.
async def dispatch_app_endpoint(self, endpoint, request):
callback = None
rargs = {"request": request}
# Find the endpoint callback
for name in self.app_endpoints:
if callback:
break
for data in self.app_endpoints.get(name, {}).values():
if data["endpoint"] == endpoint:
callback = data["callback"]
rargs.update(data.get("kwargs", {}))
break
# No endpoint found
if callback is None:
return self.get_response(request, 404, "Endpoint not found")
return_as_text = rargs.get("return_as_text", False)
if return_as_text:
#self.logger.info("Endpoint '%s' is configured to return text response", endpoint)
args = {"text": await request.text()}
elif request.method == "POST": # Parse POST arguments safely
try:
ctype = (request.content_type or "").split(";")[0]
self.logger.info(f"Received POST with content type '{ctype}'")
if ctype == "application/json":
args = await request.json()
elif ctype in ("application/x-www-form-urlencoded", "multipart/form-data"):
args = dict(await request.post())
elif ctype == "text/plain":
args = {"text": await request.text()}
else: # Unknown or missing content type
body = await request.text()
args = {"raw": body} if body else {}
self.logger.info(f"Received args: '{args}'")
except json.decoder.JSONDecodeError:
return self.get_response(request, 400, "JSON Decode Error")
else: # GET → query parameters
args = request.query
# Call the endpoint callback
use_dictionary_unpacking = utils.has_expanded_kwargs(callback)
try:
if asyncio.iscoroutinefunction(callback):
if use_dictionary_unpacking:
return await callback(args, **rargs)
else:
return await callback(args, rargs)
else:
if use_dictionary_unpacking:
return await utils.run_in_executor(self, callback, args, **rargs)
else:
return await utils.run_in_executor(self, callback, args, rargs)
except Exception:
self.logger.error("-" * 60)
self.logger.error("Unexpected error during endpoint callback")
self.logger.error(traceback.format_exc())
self.logger.error("-" * 60)
return self.get_response(request, 500, "Internal Server Error")
also tested with:
>curl -X POST "http://<appdaemon IP>:5050/api/appdaemon/pwsdata" -i -H "Content-Type: application/json" -d "{\"key1\":\"value1\",\"key2\":\"value2\"}"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 16
Date: Sat, 14 Mar 2026 12:42:51 GMT
Server: Python/3.12 aiohttp/3.11.12
{"status": "ok"}
>curl -X POST "http://<appdaemon IP>:5050/api/appdaemon/pwsdata" -i -H "Content-Type: text/plain" -d "{\"key1\":\"value1\",\"key2\":\"value2\"}"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 16
Date: Sat, 14 Mar 2026 12:43:24 GMT
Server: Python/3.12 aiohttp/3.11.12
{"status": "ok"}
>curl -X POST "http://<appdaemon IP>:5050/api/appdaemon/pwsdata" -i -d "{\"key1\":\"value1\",\"key2\":\"value2\"}"
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 16
Date: Sat, 14 Mar 2026 12:43:34 GMT
Server: Python/3.12 aiohttp/3.11.12
{"status": "ok"}
Version
4.5.11
Installation type
Docker container
Relevant log output
2026-03-14 11:18:00.250597 ERROR AppDaemon: ------------------------------------------------------------
2026-03-14 11:18:00.250953 ERROR AppDaemon: Unexpected error during API call
2026-03-14 11:18:00.251287 ERROR AppDaemon: ------------------------------------------------------------
2026-03-14 11:18:00.252903 ERROR AppDaemon: Traceback (most recent call last):
File "/usr/local/lib/python3.12/site-packages/appdaemon/http.py", line 887, in call_app_endpoint
ret, code = await self.dispatch_app_endpoint(endpoint, request)
^^^^^^^^^
ValueError: not enough values to unpack (expected 2, got 0)
2026-03-14 11:18:00.253242 ERROR AppDaemon: ------------------------------------------------------------Relevant code in the app or config file that caused the issue
Anything else?
No response