Skip to content

[wip] HTTP API device info and status (json only) #1022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from

Conversation

mcspr
Copy link
Collaborator

@mcspr mcspr commented Jul 3, 2018

Resolves #446, #886

Add two api endpoints - one to get device info and another one to get it's status.

JSON only. Albeit having methods to register apis, they are limited to 15 bytes of data. Because ws methods do so nicely fit here, maybe http api can use them too (for example, different endpoints for sensors and one that merges them all via simple loop feeding json object)

@mcspr mcspr changed the title HTTP API device info and status (json only) [wip] HTTP API device info and status (json only) Jul 6, 2018
@mcspr
Copy link
Collaborator Author

mcspr commented Sep 12, 2018

I need to finish this <_>. Either as strictly json api or current combined form-data / json

One thing i noticed about arduinojson - some class implementing Print can be used to output data. In this case it can be used to print form-data. If this is still a requirement.
https://github.com/bblanchon/ArduinoJson/blob/fa1a40ac6e63c18583540806d4fc63297ab5f99e/src/ArduinoJson/Serialization/JsonWriter.hpp#L16-L24

root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
root["network"] = getNetwork();
root["deviceip"] = getIP();
Copy link
Contributor

@soif soif Dec 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you get the API answer, you ALREADY know the IP address, don't you?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but that would mean additional step to get (m)DNS response?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really used with mDNS timing because I use a BIND dns server (with 2 slaves) at home ...yes, i'm a geek ;-)...and in this case query times are totally negligibles. Anyway mDNS queries should not be that long too... whenever someone really need to resolve the IP.

I'm just trying to save on precious ESP memory.

@soif
Copy link
Contributor

soif commented Dec 7, 2019

It would be great if you could merge this. 👍 💯

BTW would you please add the Domoticz_idx:
When owning a lot of Espurna boxes (like me), its really a pain to maintain a list of Domoticz Ids <-> Espurna IPs. Adding this would allow to easily build a script to build such a list.

I might even be able to develop a nice icingaweb2 module, to print these IDs in each host information, in Icinga web interface.

Keep Up the good work! Cheers!

@mcspr
Copy link
Collaborator Author

mcspr commented Dec 7, 2019

Hey, thanks, glad to hear it :)

This just needs a small rebase to be able to run with refactored API (373fc53). I see that the PR adds some code into the lambda, which was considerably heavy on memory.

Will domoticz stuff reuse the same model and integrate _domoticzWebSocketOnConnected() as (for example) /api/domoticz endpoint?

void _domoticzWebSocketOnConnected(JsonObject& root) {
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& relays = root.createNestedArray("dczRelays");
for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i));
}
#if SENSOR_SUPPORT
_sensorWebSocketMagnitudes(root, "dcz");
#endif
}
#endif // WEB_SUPPORT

Also note of the /discover endpoint (without any api or http auth, ref #1933 (comment))

@soif
Copy link
Contributor

soif commented Dec 8, 2019

You might also integrate this directly in the /api/device endpoint?
Maybe as a nested array, ie:

.....
device: "DEVICE"
.....
domoticz: {
     Enabled: true,
     TopicIn: "/topic/xxx",
     TopicOut: "/topic/yyy",
     RelayIds:[100,101]
}
....

Thus avoiding multiple queries to the API, to get all the device Information... Those ESPs webservers are not that fast, and I guess that it stress them a litte bit to do all the http stuff, while still trying to get good timings for much important features, like being reactive on mqtt commands.

@mcspr
Copy link
Collaborator Author

mcspr commented Dec 8, 2019

But does this mean to stuff all of the data from ws callbacks into a single endpoint? Any way to just use ws directly if that's the case? It already works and will stream all of the available modules data + status
Nested stuff also looks almost as in vue refactor, so i'd look at the callback structure there.

HTTP also allows some args passing, client could just request what it wants?
e.g. /api/device?modules=dcz,relay,led,..., device will just ignore any unknown ones

@soif
Copy link
Contributor

soif commented Dec 8, 2019

The idea is to get the maximum of information from a single endpoint (if it is possible, regarding memory issues on the esp), thus to get all the configuration (ie all settings, and maybe also runtime values like load, used memory... and maybe also sensor values or relay states) you would only need to send one and only one http request, ie /api/info?key=xxx, instead of having to send multiple different requests (separate endpoint or separate GET queries), to not stress the esp too long.

From a client perspective to fetch API response, it is as simple as (ie in php):

$array=json_decode(file_get_content("http://esp.local/api/info?key=xxx"));

that's it!

I guess (i'm not experienced with WS) that relying on websocket, from the client perspective, needs a much longer/complicated code (example not tested) to perform the same thing, and you'd need to document the ws port, ws key, and ws messages used... not really easy.

IMHO the HTTP API is the right way to go.

Splitting into different enpoints would be only required if it cause memory problem on the Esp to send all data from a single endpoint.

Then other endpoints would only be needed to POST actions to be performed by the API, ie "switch relay0 ON", "reboot esp", "Change BSSID" etc...

@mcspr
Copy link
Collaborator Author

mcspr commented Dec 11, 2019

Resulting JSON is big though.

Async server is a bit weird here, because the general approach is to just dump serialized JSON string in a separate buffer (JsonBuffer keeps data -> resulting string, so we take twice the space. 5...10KB total depending on the device flavour). This was already done for /ws in 1.13.5 and the problem was that we can't do a second request, because we hit OOM most of the time. Thus now we only send small objects and one at a time.

I do see a solution though, but I am a bit worried that we stretch ESP8266 threading model again :>
AsyncWebServer stuff can periodically poll some buffers for data to send back, but the main issue is that ArduinoJson serializer expects us to finish all of it in the same serialize() call. Arduino Print abstractions can do some work here, but I am not yet sure how

upd: Not familiar with the PHP ecosystem, but I would expect there are a bunch of WS libs already built to avoid using that half baked Stack Overflow stuff?

@mcspr
Copy link
Collaborator Author

mcspr commented May 22, 2020

Re:

I do see a solution though, but I am a bit worried that we stretch ESP8266 threading model again :>
AsyncWebServer stuff can periodically poll some buffers for data to send back, but the main issue is that ArduinoJson serializer expects us to finish all of it in the same serialize() call. Arduino Print abstractions can do some work here, but I am not yet sure how

#2247 API handler for command line does exactly this. We capture request object when something connects to us and create a 'task' that gives us a generic Arduino's Print interface, which we can use to print / printf / write(byte) / etc. into. HTTP response is chunked, so we don't need to know the resulting size beforehand.

Following existing implementations with JSON in the name, https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/src/AsyncJson.h does not implement chunked encoding and is limited by the 1KB (lwip v2 536 mss) / 3KB (lwip v1 & v2 high bandwidth) response, including headers.

I don't think I happened to find any JSON generator that can work in-place, most (if not all) implement a temporary buffer. Writing raw bytes will work (e.g. printer.printf("{\"a\": %d, \"b\": %d}", getA(), getB());), nesting might become tricky though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

REST API: status entry point
2 participants