openems icon indicating copy to clipboard operation
openems copied to clipboard

Implement Generic HTTP Worker

Open nicoketzer opened this issue 1 year ago • 4 comments

Implements a generic-http-worker that can be used like an API by other components.

Main Idea

For this purpose, a string[] array is passed to the constructor (+timeout). Then each element of the array is always processed "in a circle".

An external component can get the last available result via get_last_by_id or get_last.

The get_last_by_id function refers to the ID of the element of the String[] array that was passed to the constructor. If for some reason this ID is not unambiguously available in the code of the main component, you can also pass the actual URL via get_last and then the ID will be determined automatically.

With get_last_error() it can be queried whether an error has occurred. This can be used e.g. to trigger a SlaveCommunicationError in the "main component".

Example - How to Call the worker

[...]
import io.openems.edge.io.generic_http_worker.generic_http_worker
[...]

String[] urls = new String[]{"http://[device_ip]/[some_url]","http://[another_device]/[another_url]",[...]};
int timeout = 2500;

generic_http_worker worker = new generic_http_worker(urls, timeout);

[...]

If you now at some Point in the Code run "this.worker.get_last_by_id(0)" you get the last Result of the First Element of "urls". If you alter the 0 to a 1 than you would get the last Result of the second element.

If you want to ensure that the possible Communication Error of one Component does not affect the other calls, you could also call it like:

[...]
String[] url1 = new String[]{"[First_Device]/[Some_Path]"};
String[] url2 = new String[]{"[Second_Device]/[Some_Path]"};
int timeout = 2500;

generic_http_worker worker1 = new generic_http_worker(url1,timeout);
generic_http_worker worker2 = new generic_http_worker(url2, timeout);
[...]

In each case, you have to activate the worker via the standard "activate()" function of the AbstractCycleWorker (also the "deactivate()" function if you disable the Main-Component). Also, you have to call the "triggerNextRun()" function on each of your Workers in the "handleEvent()" part of the Main-Component.

External Interface Option

The send_external_url function provides a way to temporarily execute an additional URL for the current worker from outside. This function is needed e.g. for relay implementations because otherwise a separate handler similar to the "send_req" function would have to be implemented for switching on and off.

Important: This function does not take advantage of the worker, but actually blocks until a response is available. I made this decision because theoretically it is possible that the feedback of the temporary call is needed for further processing. If you use the worker for this and the further processing happens immediately (which is mostly the case) the worker would return a "no_value" which would hinder the further processing.

Possible Feedback-Values

The following results can occur with get_last or get_last_by_id: undefined com_error no_value Exception [result of the last query]

Further Information

In a separate pull request, I will add components that use this generic-http-worker interface.

The advantage of this is that the handleEvent() - function of the main component does not block and therefore the whole OpenEMS cycle does not have to wait for single requests or answers of external components.

The implementation has been extensively tested. Currently, this also runs on my own energy storage without problems. This is used here by several components (These components are carried together in a separate pull). The Cycle blocked before this Project for up to 5 Seconds because of HTTP-Calls to external Devices that took longer than the OpenEMS-Cycle. Now the Cycle can always end at it´s defined time.

Many greetings, Nico

nicoketzer avatar Mar 12 '23 01:03 nicoketzer

Please see #2099 or #2100 for practical Examples of the generic_http_worker

nicoketzer avatar Mar 12 '23 02:03 nicoketzer

I have also already rewritten and tested the code for #2099 and #2100. However, I suspend the pushes into these pull requests until this pull has been finalized and completed.

nicoketzer avatar Mar 12 '23 14:03 nicoketzer

@sfeilmeier The bridge idea also came to me, but I have dismissed this already. In a TCP/IP based environment, it usually does not happen that a connection can be used for multiple purposes.

In the sense of this development it is that one or more URL's can be passed and these then deliver a return value. A case where 2 different components send the same request to the same external device does not occur to me now (except e.g. it is 2x "Shelly Plug" on the same IP created, but this is rather pointless).

I think it would make more sense to build another component as a bridge over this worker (e.g. "Datafetcher"). This then uses the GenericHttpWorker as a basis and provides the queried data as a bridge for several other components.

nicoketzer avatar Mar 12 '23 14:03 nicoketzer

Have just found a bug. Tasks that are passed to the worker via the push_task function can no longer be deleted and therefore remain forever. If you try to switch on a relay via push_task and then switch it off, both tasks remain, and it goes into a bouncing cycle. The error lies in the delete_task and is already located and fixed by me. There will be a new commit that solves this problem in the near future. Tests with the new version that fixes this bug are currently running. If these tests are successful, the commit will come on GitHub.

nicoketzer avatar Jun 06 '23 19:06 nicoketzer