Skip to content

error when receiving POST messages from my PWS #2573

@DOSprojects

Description

@DOSprojects

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    issueSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions