diff --git a/.gitignore b/.gitignore index 06c2ec35..71384602 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ server/src/.vscode/ **/.vscode **/.cache **.project -**jsconfig.json \ No newline at end of file +**jsconfig.json +**/.env diff --git a/README.md b/README.md index 1da7c5ec..36704a33 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ libgcrypt, libpthread, libglib-2.0, libyaml, * KIELER command line compiler: [kico.jar](https://rtsys.informatik.uni-kiel.de/~kieler/files/nightly/sccharts/cli/) * Path to the folder that contains `kico.jar` has to be defined in the environment variable `KIELER_PATH` * BahnDSL command line compiler: [bahnc](https://github.com/trinnguyen/bahndsl) - * Path to the folder that contains the `bahnc` has to be defined in the environment variable `BAHNDSL_PATH` + * Path to the folder that contains the `bahnc` has to be defined in the environment variable `BAHNC_PATH` * SCCharts verifier: [SWTbahn Verifier (SWT internal repository)](https://gitlab.rz.uni-bamberg.de/swt/swtbahn-verifier) #### Command Line Client @@ -70,9 +70,9 @@ client from a different working directory for the current session, otherwise it won't find the configuration file. If you really want to use two logically different clients on the same device, you could just run the clients from different working directories which will lead to distinct configs. -3. Now you can use the commands from the categories `swtbahn admin`, -`swtbahn controller`, `swtbahn driver` and `swtbahn monitor`. If the system was -not started by `swtbahn admin startup`, all the other commands won't work. +3. Now you can use the commands from the roles `admin`, +`controller`, `driver` and `monitor`. If the system was +not started by `admin startup`, all the other commands won't work. For example: `./swtbahn admin startup` @@ -230,12 +230,16 @@ As an example, when a request is made to set point10 to the normal state, the re > _Intervening log messages from internal processing_ > LOG_NOTICE: `Request: Set point - point: point10 state: normal - finish` -If the above request was instead made with an unsupported state, e.g., `foobar`, then the request handler would generate the following log messages to say that the processing was stopped because of invalid parameters: +If the above request was instead made with an unsupported state, e.g., `foobar`, then the request handler would generate the following log messages to say that the processing was stopped because of invalid parameter values: > LOG_NOTICE: `Request: Set point - point: point10 state: foobar - start` > _Intervening log messages from internal processing_ -> LOG_ERR: `Request: Set point - point: point10 state: foobar - invalid parameters - abort` +> LOG_ERR: `Request: Set point - point: point10 state: foobar - invalid parameter values - abort` -If the above request forgot to specify the state, i.e., the state parameter is `null`, then the request handler would only generate the following log message to say that the parameter validation failed: +If the above request forgot to specify the state, i.e., the state parameter is `null`, then the request handler would only generate the following log message to say that the parameter is missing: -> LOG_ERR: `Request: Set point - invalid parameters` \ No newline at end of file +> LOG_ERR: `Request: Set point - missing parameter state` + +And if sending this info to the client failed, the following would be logged: + +> LOG_ERR: `Request: Set point - missing parameter state - but sending msg to client failed` \ No newline at end of file diff --git a/client/swtbahn b/client/swtbahn index e75fb83c..696d3c60 100755 --- a/client/swtbahn +++ b/client/swtbahn @@ -2,7 +2,7 @@ """ -Copyright (C) 2017 University of Bamberg, Software Technologies Research Group +Copyright (C) 2025 University of Bamberg, Software Technologies Research Group , This file is part of the SWTbahn command line interface (swtbahn-cli), which is @@ -24,10 +24,11 @@ The following people contributed to the conception and realization of the present swtbahn-cli (in alphabetic order by surname): - Nicolas Gross +- Bernhard Luedtke """ -import click, yaml, requests +import click, yaml, json, requests @@ -43,13 +44,11 @@ session_id = 0 grab_id = 0 server = "" - - # ----------------------- # --- config handling --- # ----------------------- -def create_config(hostname, port, default_output): +def create_config(hostname, port, default_output, do_test_connection=True): try: data = {'hostname': hostname, 'port': port, 'default_track_output': default_output, 'session_id': 0, @@ -60,20 +59,22 @@ def create_config(hostname, port, default_output): except Exception as e: click.echo(e, err=True) return - global server - server = "http://" + hostname + ":" + str(port) - click.echo("Connection test:") - try: - response = requests.get(server) - if (response.status_code != requests.codes.ok): - click.echo("No SWTbahn server on " + server, err=True) - else: - click.echo("Running SWTbahn server found on " + server) - except requests.exceptions.RequestException as e: - click.echo(e, err=True) + if do_test_connection: + global server + server = "http://" + hostname + ":" + str(port) + click.echo("Connection test:") + try: + response = requests.get(server) + if (response.status_code != 200): + click.echo("No SWTbahn server on " + server, err=True) + else: + click.echo("SWTbahn server found on " + server) + except requests.exceptions.RequestException as e: + click.echo(e, err=True) def update_session_and_grab_id(session_id, grab_id): + global config_file try: with open(config_file) as infile: config = yaml.safe_load(infile) @@ -85,22 +86,36 @@ def update_session_and_grab_id(session_id, grab_id): click.echo(e, err=True) +# Returns True if config is valid, otherwise returns False def parse_config(): + global config_file, hostname, port, default_track_output, session_id, grab_id, server try: with open(config_file) as infile: config = yaml.safe_load(infile) - global hostname, port, default_track_output, session_id, grab_id, server hostname = config['hostname'] port = config['port'] default_track_output = config['default_track_output'] session_id = config['session_id'] grab_id = config['grab_id'] server = "http://" + hostname + ":" + str(port) - return False - except Exception as e: return True + except Exception as e: + return False +# ----------------------- +# --- logging helpers --- +# ----------------------- +def log_common_feedback_err_msg(response_text:str, internal_err=False): + response_json: dict = json.loads(response_text) + click.echo(("Internal error" if internal_err else "Error") + ", server msg: " + + response_json.get("msg", ""), err=True) + +def log_not_running_or_unknown_err(status_code:int): + if (status_code == 503): + click.echo("System is not running.", err=True) + else: + click.echo(f"Unknown error, reply status code: {status_code}", err=True) # ------------------------------ # --- command line interface --- @@ -108,19 +123,38 @@ def parse_config(): @click.group(help="Command-line interface for controlling the SWTbahn. " - "Use the config command first to set the hostname and port") + "Use the config command first to set the hostname and port." + "Use the reset_grab_and_session_id command if the server says they are invalid, " + "this may happen if an admin force-releases a train you've grabbed.") def cli(): pass -@click.command(help="Configure hostname and port") -@click.argument('hostname') -@click.argument('port', type=click.IntRange(0, 65535)) -@click.argument('default_track_output') +@click.command(help="Configure hostname, port, and track output") +@click.argument('hostname', required=True) +@click.argument('port', type=click.IntRange(0, 65535), required=True) +@click.argument('default_track_output', default="master") def config(hostname, port, default_track_output): create_config(hostname, port, default_track_output) +@click.command(help="Reset your train ownership (grab and session id). " + "Do NOT do this if you still have proper control over the train!") +def reset_grab_and_session_id(): + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) + else: + global server, session_id, grab_id + try: + # to be safe, try to release the train with the current grab id before resetting it + requests.post(server + "/driver/release-train", + data = {'session-id': session_id, 'grab-id': grab_id}) + # Reset the session_id and grab_id + update_session_and_grab_id(0, -1) + except Exception as e: + click.echo(f"reset_grab_and_session_id failed. Error: {e}", err=True) + + # --- admin --- @click.group(help="Admin functionality") @@ -130,42 +164,46 @@ def admin(): @click.command(help="Start up the SWTbahn") def startup(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/admin/startup") - if (response.status_code == requests.codes.ok): - click.echo("System has started up\n") + if (response.status_code == 200): + click.echo("System has started up.") + elif (response.status_code == 409): + click.echo("System already running!") + elif (response.status_code == 500): + log_common_feedback_err_msg(response.text, internal_err=True) else: - click.echo("System already running!\n", err=True) + click.echo(f"Unknown error, reply status code: {response.status_code}", err=True) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Shut down the SWTbahn") def shutdown(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/admin/shutdown") - if (response.status_code == requests.codes.ok): - click.echo("System is stopping\n") + if (response.status_code == 200): + click.echo("System has been stopped.") update_session_and_grab_id(0, -1) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) -@click.command(help="Set the power state of a track output") +@click.command(help="Set the power state of all track outputs") @click.argument('state', type=click.Choice(['off', 'stop', 'soft_stop', 'go'])) def set_track_output(state): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: @@ -177,75 +215,83 @@ def set_track_output(state): } response = requests.post(server + "/admin/set-track-output", data = {'state': mapping.get(state)}) - if (response.status_code == requests.codes.ok): - click.echo("Track output state set\n") + if (response.status_code == 200): + click.echo(f"Track output state set to {state}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) + @click.command(help="Set whether plugins will be verified upon upload") @click.argument('verification-option', type=click.Choice(['true', 'false'])) def set_verification_option(verification_option): - if(parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/admin/set-verification-option", data = {'verification-option': verification_option}) - if (response.status_code == requests.codes.ok): - click.echo("Verification option set to " + verification_option + "\n") + if (response.status_code == 200): + click.echo(f"Verification option set to {verification_option}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) + @click.command(help="Set the url of the verification server to use for verification on upload") @click.argument('verification-url', type=click.STRING) def set_verification_url(verification_url): - if(parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/admin/set-verification-url", data = {'verification-url': verification_url}) - if (response.status_code == requests.codes.ok): - click.echo("Verification url set to " + verification_url + "\n") + if (response.status_code == 200): + click.echo(f"Verification url set to {verification_url}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running!\n", err=True) + click.echo(f"Unknown error, reply status code: {response.status_code}", err=True) except requests.exceptions.RequestException as e: click.echo(e, err=True) + @click.command(name="release", help="Release a train") @click.argument('train') def admin_release(train): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/admin/release-train", - data = {'train': train}) - if (response.status_code == requests.codes.ok): - click.echo("Released grabbed train\n") + response = requests.post(server + "/admin/release-train", data = {'train': train}) + if (response.status_code == 200): + click.echo(f"Released grabbed train {train}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or train not grabbed!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(name="set-dcc-speed", help="Set the dcc speed step of a train") -@click.option('--backwards', '-b', help="The rear end of the train moves forward", - is_flag=True) -@click.option('--track_output', '-t', help="Use another track output than the " - "default one", default="") +@click.option('--backwards', '-b', help="The rear end of the train moves forward", is_flag=True) +@click.option('--track_output', '-t', help="Use another track output than the default one", default="") @click.argument('train') @click.argument('speed', type=click.IntRange(0, 126)) def admin_set_dcc_speed(train, backwards, speed, track_output): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server, default_track_output if (backwards and speed > 0): @@ -254,12 +300,14 @@ def admin_set_dcc_speed(train, backwards, speed, track_output): track_output = default_track_output try: response = requests.post(server + "/admin/set-dcc-train-speed", - data = {'train': train, 'speed': speed, 'track-output': track_output}) - if (response.status_code == requests.codes.ok): - click.echo("DCC train speed set to " + str(speed) + "\n") + data = {'train': train, 'speed': speed, + 'track-output': default_track_output}) + if (response.status_code == 200): + click.echo(f"DCC speed of {train} set to {speed}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid track output!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -274,37 +322,39 @@ def controller(): @click.command(help="Release a route") @click.argument('route-id', type=int) def release_route(route_id): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: try: response = requests.post(server + "/controller/release-route", - data = {'route-id': route_id}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Route " + str(route_id) + " released\n") + data = {'route-id': route_id}) + if (response.status_code == 200): + click.echo(f"Route {route_id} released.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid track output!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) -@click.command(help="Switch a point") +@click.command(help="Set a point") @click.argument('point') @click.argument('state') def set_point(point, state): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/controller/set-point", - data = {'point': point, 'state': state}) - if (response.status_code == requests.codes.ok): - click.echo("Point " + point + " set to " + state + " \n") + data = {'point': point, 'state': state}) + if (response.status_code == 200): + click.echo(f"Point {point} set to {state}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -313,18 +363,19 @@ def set_point(point, state): @click.argument('signal') @click.argument('state') def set_signal(signal, state): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/controller/set-signal", - data = {'signal': signal, 'state': state}) - if (response.status_code == requests.codes.ok): - click.echo("Signal " + signal + " set to " + state + " \n") + data = {'signal': signal, 'state': state}) + if (response.status_code == 200): + click.echo(f"Signal {signal} set to {state}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -333,35 +384,38 @@ def set_signal(signal, state): @click.argument('peripheral') @click.argument('state') def set_peripheral(peripheral, state): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/controller/set-peripheral", - data = {'peripheral': peripheral, 'state': state}) - if (response.status_code == requests.codes.ok): - click.echo("Peripheral " + peripheral + " set to " + state + " \n") + data = {'peripheral': peripheral, 'state': state}) + if (response.status_code == 200): + click.echo(f"Peripheral {peripheral} set to {state}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Get the interlocker in use") def get_interlocker(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/controller/get-interlocker") - if (response.status_code == requests.codes.ok): - click.echo(response.text + "\n") + response = requests.get(server + "/controller/get-interlocker") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo("Interlocker in use: " + response_json.get("interlocker", None)) + elif (response.status_code == 404): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -369,18 +423,20 @@ def get_interlocker(): @click.command(help="Set an interlocker to use") @click.argument('interlocker') def set_interlocker(interlocker): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/controller/set-interlocker", - data = {'interlocker': interlocker}) - if (response.status_code == requests.codes.ok): - click.echo("Interlocker set to " + interlocker + " \n") + data = {'interlocker': interlocker}) + if (response.status_code == 200): + click.echo(f"Interlocker set to {interlocker}.") + elif (response.status_code == 400 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -388,37 +444,43 @@ def set_interlocker(interlocker): @click.command(help="Unset an interlocker from use") @click.argument('interlocker') def unset_interlocker(interlocker): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/controller/unset-interlocker", - data = {'interlocker': interlocker}) - if (response.status_code == requests.codes.ok): - click.echo("Interlocker " + interlocker + " is unset \n") + data = {'interlocker': interlocker}) + if (response.status_code == 200): + click.echo(f"Interlocker {interlocker} has been unset.") + elif (response.status_code == 400 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) -@click.command(help="Upload an interlocker. Absolute path to an BahnDSL (*.bahn) file") +@click.command(help="Upload an interlocker") @click.argument('filepath', type=click.Path(exists=True)) def upload_interlocker(filepath): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/upload/interlocker", - files = {'file': open(filepath, 'r')}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Interlocker uploaded\n") + files = {'file': open(filepath, 'r')}) + if (response.status_code == 200): + click.echo("Interlocker has been uploaded.") + elif (response.status_code == 400 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 500): + log_common_feedback_err_msg(response.text, internal_err=True) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -426,35 +488,21 @@ def upload_interlocker(filepath): @click.command(help="Delete an interlocker") @click.argument('interlocker-name') def delete_interlocker(interlocker_name): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/upload/remove-interlocker", - data = {'interlocker-name': interlocker_name}) - if (response.status_code == requests.codes.ok): - click.echo("Interlocker " + interlocker_name + " deleted\n") - else: - click.echo("Interlocker still in use, or system not running or invalid state!\n", - err=True) - except requests.exceptions.RequestException as e: - click.echo(e, err=True) - - -@click.command(help="Get a list of interlockers") -def get_interlocker_list(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) - else: - global server - try: - response = requests.post(server + "/upload/refresh-interlockers") - if (response.status_code == requests.codes.ok): - click.echo(response.text.replace(",", "\n")) + data = {'interlocker-name': interlocker_name}) + if (response.status_code == 200): + click.echo(f"Interlocker {interlocker_name} deleted.") + elif (response.status_code == 400 or + response.status_code == 404 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -469,57 +517,60 @@ def driver(): @click.command(help="Grab a train") @click.argument('train') @click.option('--engine', '-e', help="The train engine behaviour to use", - default="libtrain_engine_default (unremovable)") + default="libtrain_engine_default (unremovable)") def grab(train, engine): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global session_id, grab_id, server if (session_id == 0 and grab_id == -1): try: response = requests.post(server + "/driver/grab-train", data = {'train': train, 'engine': engine}) - if (response.status_code == requests.codes.ok): - ids = response.text.split(",") - update_session_and_grab_id(int(ids[0]), int(ids[1])) - click.echo("Grabbed train " + train + "\n") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + update_session_and_grab_id(response_json["session-id"], response_json["grab-id"]) + click.echo("Grabbed train " + train) + click.echo(f" Debug information: session-id ({response_json["session-id"]})") + click.echo(f" Debug information: grab-id ({response_json["grab-id"]})") + elif (response.status_code == 400 or + response.status_code == 404 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 500): + log_common_feedback_err_msg(response.text, internal_err=True) else: - click.echo("System not running or train not available!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) else: - click.echo("You can only grab one train!") - return - - -def process_id_feedback(feedback, success_message): - if (feedback == "invalid session id"): - update_session_and_grab_id(0, -1) - click.echo("Session id has become invalid\n", err=True) - elif (feedback == "invalid grab id"): - update_session_and_grab_id(0, -1) - click.echo("Grab id was invalid\n", err=True) - else: - click.echo(success_message) + click.echo("You already have a train grabbed! " + "Release it before grabbing a new train.\n" + "If your grab-id or session-id is not valid, i.e., if you do not actually " + "have control over a train, then please re-run the " + "reset_grab_and_session_id command.", + err=True) @click.command(help="Release your train") def release(): global session_id, grab_id, server - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) elif (session_id == 0 and grab_id == -1): click.echo("No train grabbed!") else: try: response = requests.post(server + "/driver/release-train", - data = {'session-id': session_id, 'grab-id': grab_id}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Released grabbed train\n") + data = {'session-id': session_id, 'grab-id': grab_id}) + if (response.status_code == 200): + click.echo("Released grabbed train.") update_session_and_grab_id(0, -1) + elif (response.status_code == 400 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -529,46 +580,50 @@ def release(): @click.argument('destination') def request_route(source, destination): global session_id, grab_id, server - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) elif (session_id == 0 and grab_id == -1): click.echo("No train grabbed!") else: try: response = requests.post(server + "/driver/request-route", - data = {'session-id': session_id, 'grab-id': grab_id, - 'source': source, 'destination': destination}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Route " + response.text + - " from " + source + " to " + destination + " has been granted\n") + data = {'session-id': session_id, 'grab-id': grab_id, + 'source': source, 'destination': destination}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + route_id = response_json["granted-route-id"] + click.echo(f"Route {route_id} from {source} to {destination} has been granted.") + elif (response.status_code == 400 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) else: - click.echo("Route from " + source + " to " + destination + - " has not been granted,\n" + - "or system not running or invalid track output!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) -@click.command(help="Request a specific route ID for your train ") +@click.command(help="Request a specific route by its ID for your train") @click.argument('route-id') def request_route_id(route_id): global session_id, grab_id, server - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) elif (session_id == 0 and grab_id == -1): click.echo("No train grabbed!") else: try: - response = requests.post(server + "/driver/request-route-id", - data = {'session-id': session_id, 'grab-id': grab_id, - 'route-id': route_id}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Route " + route_id + " has been granted\n") + response = requests.post(server + "/driver/request-route-by-id", + data = {'session-id': session_id, 'grab-id': grab_id, + 'route-id': route_id}) + if (response.status_code == 200): + click.echo(f"Route {route_id} has been granted.") + elif (response.status_code == 400 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 500): + log_common_feedback_err_msg(response.text, internal_err=True) else: - click.echo("Route " + route_id + " has not been granted,\n" + - "or system not running or invalid track output!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -578,56 +633,61 @@ def request_route_id(route_id): @click.argument('route-id') def direction(train, route_id): global server - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: try: response = requests.post(server + "/driver/direction", - data = {'session-id': session_id, 'train': train, - 'route-id': route_id}) - if (response.status_code == requests.codes.ok): - click.echo(response.text) + data = {'session-id': session_id, 'train': train, + 'route-id': route_id}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(f"Direction: {response_json["direction"]}.") + elif (response.status_code == 400 or + response.status_code == 404): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Drive along a granted route in automatic or manual mode") -@click.argument('mode') +@click.argument('mode', type=click.Choice(['manual', 'automatic'])) @click.argument('route-id') def drive_route(mode, route_id): global session_id, grab_id, server - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) elif (session_id == 0 and grab_id == -1): click.echo("No train grabbed!") + elif ((not mode == "manual") and (not mode == "automatic")): + click.echo("Incorrect mode! Mode shall be either manual or automatic.") else: try: response = requests.post(server + "/driver/drive-route", - data = {'session-id': session_id, 'grab-id': grab_id, - 'mode': mode, 'route-id': route_id}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Driving along route " + route_id + - " completed\n") + data = {'session-id': session_id, 'grab-id': grab_id, + 'mode': mode, 'route-id': route_id}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(f"Driving route {route_id} completed, server msg: {response_json.get("msg", "")}") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("Could not drive along route " + route_id + ",\n" + - "or system not running or invalid track output!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Set the dcc speed step of your train") -@click.option('--backwards', '-b', help="The rear end of the train moves forward", - is_flag=True) -@click.option('--track_output', '-t', help="Use another track output than the " - "default one", default="") +@click.option('--backwards', '-b', help="The rear end of the train moves forward", is_flag=True) +@click.option('--track_output', '-t', help="Use another track output than the default one", + default="") @click.argument('speed', type=click.IntRange(0, 126)) def set_dcc_speed(backwards, speed, track_output): global session_id, grab_id, server, default_track_output - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) elif (session_id == 0 and grab_id == -1): click.echo("No train grabbed!") else: @@ -637,28 +697,27 @@ def set_dcc_speed(backwards, speed, track_output): track_output = default_track_output try: response = requests.post(server + "/driver/set-dcc-train-speed", - data = {'session-id': session_id, 'grab-id': grab_id, - 'speed': speed, 'track-output': track_output}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "DCC train speed set to " + - str(speed) + "\n") + data = {'session-id': session_id, 'grab-id': grab_id, + 'speed': speed, 'track-output': track_output}) + if (response.status_code == 200): + click.echo(f"DCC train speed set to {speed} on track-output {track_output}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid track output!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Set the calibrated speed step of your train") -@click.option('--backwards', '-b', help="The rear end of the train moves forward", - is_flag=True) -@click.option('--track_output', '-t', help="Use another track output than the " - "default one", default="") +@click.option('--backwards', '-b', help="The rear end of the train moves forward", is_flag=True) +@click.option('--track_output', '-t', help="Use another track output than the default one", + default="") @click.argument('speed', type=click.IntRange(0, 9)) def set_calibrated_speed(backwards, speed, track_output): global session_id, grab_id, server, default_track_output - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) elif (session_id == 0 and grab_id == -1): click.echo("No train grabbed!") else: @@ -668,25 +727,25 @@ def set_calibrated_speed(backwards, speed, track_output): track_output = default_track_output try: response = requests.post(server + "/driver/set-calibrated-train-speed", - data = {'session-id': session_id, 'grab-id': grab_id, - 'speed': speed, 'track-output': track_output}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Calibrated train speed set " - "to " + str(speed) + "\n") + data = {'session-id': session_id, 'grab-id': grab_id, + 'speed': speed, 'track-output': track_output}) + if (response.status_code == 200): + click.echo(f"Calibrated train speed set to {speed} on track-output {track_output}.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid track output!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Emergency stop your train") -@click.option('--track_output', '-t', help="Use another track output than the " - "default one", default="") +@click.option('--track_output', '-t', help="Use another track output than the default one", + default="") def emergency_stop(track_output): global session_id, grab_id, server, default_track_output - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) elif (session_id == 0 and grab_id == -1): click.echo("No train grabbed!") else: @@ -694,26 +753,27 @@ def emergency_stop(track_output): track_output = default_track_output try: response = requests.post(server + "/driver/set-train-emergency-stop", - data = {'session-id': session_id, 'grab-id': grab_id, - 'track-output': track_output}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Train emergency stopped\n") + data = {'session-id': session_id, 'grab-id': grab_id, + 'track-output': track_output}) + if (response.status_code == 200): + click.echo("Train emergency stopped.") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running or invalid track output!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Set a peripheral of your train") -@click.option('--track_output', '-t', help="Use another track output than the " - "default one", default="") +@click.option('--track_output', '-t', help="Use another track output than the default one", + default="") @click.argument('peripheral') @click.argument('state', type=click.Choice(['off', 'on'])) def set_train_peripheral(peripheral, state, track_output): global session_id, grab_id, server, default_track_output - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) elif (session_id == 0 and grab_id == -1): click.echo("No train grabbed!") else: @@ -725,34 +785,46 @@ def set_train_peripheral(peripheral, state, track_output): 'on': 1, } response = requests.post(server + "/driver/set-train-peripheral", - data = {'session-id': session_id, 'grab-id': grab_id, - 'peripheral': peripheral, 'state': mapping.get(state), - 'track-output': track_output}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Train peripheral " - + peripheral + " set to " + state + "\n") + data = {'session-id': session_id, 'grab-id': grab_id, + 'peripheral': peripheral, 'state': mapping.get(state), + 'track-output': track_output}) + if (response.status_code == 200): + click.echo(f"Train peripheral {peripheral} set to {state}") + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) else: - click.echo("System not running, invalid peripheral or invalid " - "track output!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) -@click.command(help="Upload a train engine. Absolute path to an SCCharts (*.sctx) file") +@click.command(help="Upload a train engine") @click.argument('filepath', type=click.Path(exists=True)) def upload_engine(filepath): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/upload/engine", - files = {'file': open(filepath, 'r')}) - if (response.status_code == requests.codes.ok): - process_id_feedback(response.text, "Train engine uploaded\n") + files = {'file': open(filepath, 'r')}) + if (response.status_code == 200): + click.echo("Train engine uploaded.") + elif (response.status_code == 400): + response_json: dict = json.loads(response.text) + click.echo("Error, server msg: " + response_json.get("msg", ""), err=True) + # If the verification failed, the feedback can be quite long. + if (response_json.get("status", True) == False): + # easiest for now to directly print the whole verifiedproperties object + click.echo("Verification of at least one property failed. " + "Now printing info on all properties:") + click.echo(json.dumps(response_json["verifiedproperties"], indent=4, default=str)) + elif (response.status_code == 409): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 500): + log_common_feedback_err_msg(response.text, internal_err=True) else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -760,35 +832,21 @@ def upload_engine(filepath): @click.command(help="Delete a train engine") @click.argument('engine-name') def delete_engine(engine_name): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/upload/remove-engine", - data = {'engine-name': engine_name}) - if (response.status_code == requests.codes.ok): - click.echo("Train engine " + engine_name + " deleted\n") + data = {'engine-name': engine_name}) + if (response.status_code == 200): + click.echo(f"Train engine {engine_name} deleted.") + elif (response.status_code == 400 or + response.status_code == 404 or + response.status_code == 409): + log_common_feedback_err_msg(response.text) else: - click.echo("Train engine still in use, or system not running or invalid state!\n", - err=True) - except requests.exceptions.RequestException as e: - click.echo(e, err=True) - - -@click.command(help="Get a list of train engines") -def get_engine_list(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) - else: - global server - try: - response = requests.post(server + "/upload/refresh-engines") - if (response.status_code == requests.codes.ok): - click.echo(response.text.replace(",", "\n")) - else: - click.echo("System not running or invalid state!\n", - err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -801,34 +859,37 @@ def monitor(): @click.command(help="Get the name of the platform/railway that is being run") def get_platform_name(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.get(server + "/monitor/platform-name") - if (response.status_code == requests.codes.ok): - click.echo("Platform name: " + response.text) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(f"Platform name: {response_json["platform-name"]}") else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) + @click.command(help="Get a list of trains") def get_trains(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/trains") - if (response.status_code == requests.codes.ok): + response = requests.get(server + "/monitor/trains") + if (response.status_code == 200): if (response.text == ""): - click.echo("No trains available") + click.echo("No trains available.") else: - click.echo(response.text) + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -836,17 +897,43 @@ def get_trains(): @click.command(help="Get the state of a train") @click.argument('train') def get_train_state(train): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/monitor/train-state", - data = {'train': train}) - if (response.status_code == requests.codes.ok): - click.echo(response.text) + data = {'train': train}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 404): + click.echo(f"Error, train {train} was not found.", err=True) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running or train unknown!\n", err=True) + log_not_running_or_unknown_err(response.status_code) + except requests.exceptions.RequestException as e: + click.echo(e, err=True) + + +@click.command(help="Get the state of all trains") +def get_trains_states(): + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) + else: + global server + try: + response = requests.get(server + "/monitor/train-states") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) + else: + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -854,65 +941,161 @@ def get_train_state(train): @click.command(help="Get a list of peripherals of a train") @click.argument('train') def get_train_peripherals(train): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.post(server + "/monitor/train-peripherals", - data = {'train': train}) - if (response.status_code == requests.codes.ok): - click.echo(response.text) + data = {'train': train}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running or train unknown!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) -@click.command(help="Get a list of track outputs") +@click.command(help="Get a list of train engines") +def get_engine_list(): + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) + else: + global server + try: + response = requests.get(server + "/monitor/engines") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) + else: + log_not_running_or_unknown_err(response.status_code) + except requests.exceptions.RequestException as e: + click.echo(e, err=True) + + +@click.command(help="Get a list of interlockers") +def get_interlocker_list(): + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) + else: + global server + try: + response = requests.get(server + "/monitor/interlockers") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) + else: + log_not_running_or_unknown_err(response.status_code) + except requests.exceptions.RequestException as e: + click.echo(e, err=True) + + +@click.command(help="Get a list of track outputs with their state") def get_track_outputs(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/track-outputs") - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.get(server + "/monitor/track-outputs") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Get a list of points") def get_points(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/points") - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.get(server + "/monitor/points") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Get a list of signals") def get_signals(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/signals") - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.get(server + "/monitor/signals") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) + except requests.exceptions.RequestException as e: + click.echo(e, err=True) + + +@click.command(help="Get detailed information on a point") +@click.argument('point') +def get_point_details(point): + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) + else: + global server + try: + response = requests.post(server + "/monitor/point-details", data = {'point': point}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) + else: + log_not_running_or_unknown_err(response.status_code) + except requests.exceptions.RequestException as e: + click.echo(e, err=True) + + +@click.command(help="Get detailed information on a signal") +@click.argument('signal') +def get_signal_details(signal): + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) + else: + global server + try: + response = requests.post(server + "/monitor/signal-details", data = {'signal': signal}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) + else: + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -920,17 +1103,23 @@ def get_signals(): @click.command(help="Get the aspects of a point") @click.argument('point') def get_point_aspects(point): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/point-aspects", - data = {'point': point}) - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.post(server + "/monitor/point-aspects", data = {'point': point}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 404): + click.echo(f"Error, point {point} was not found.") + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running or point unknown!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -938,110 +1127,134 @@ def get_point_aspects(point): @click.command(help="Get the aspects of a signal") @click.argument('signal') def get_signal_aspects(signal): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/signal-aspects", - data = {'signal': signal}) - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.post(server + "/monitor/signal-aspects", data = {'signal': signal}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 404): + click.echo(f"Error, signal {signal} was not found.") + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running or signal unknown!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Get a list of segments") def get_segments(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/segments") - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.get(server + "/monitor/segments") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Get a list of reversers") def get_reversers(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/reversers") - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.get(server + "/monitor/reversers") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message " + "or unable to get reverser state update.", err=True) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Get a list of peripherals") def get_peripherals(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/peripherals") - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.get(server + "/monitor/peripherals") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) -@click.command(help="Get whether plugins will be verified upon upload") + +@click.command(help="Get whether train engines will be verified upon upload") def get_verification_option(): - if(parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.get(server + "/monitor/verification-option") - if (response.status_code == requests.codes.ok): - click.echo(response.text + "\n") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) else: - click.echo("System not running!\n", err=True) + click.echo(f"Unknown error, reply status code: {response.status_code}", err=True) except requests.exceptions.RequestException as e: click.echo(e, err=True) + @click.command(help="Get the url to the verification server") def get_verification_url(): - if(parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: response = requests.get(server + "/monitor/verification-url") - if (response.status_code == requests.codes.ok): - click.echo(response.text + "\n") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) else: - click.echo("System not running!\n", err=True) + click.echo(f"Unknown error, reply status code: {response.status_code}", err=True) except requests.exceptions.RequestException as e: click.echo(e, err=True) + @click.command(help="Get a list of granted routes") def get_granted_routes(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/granted-routes") - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.get(server + "/monitor/granted-routes") + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -1049,48 +1262,55 @@ def get_granted_routes(): @click.command(help="Get the details of a route") @click.argument('route-id') def get_route(route_id): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/route", - data = {'route-id': route_id}) - if (response.status_code == requests.codes.ok): - click.echo(response.text) + response = requests.post(server + "/monitor/route", data = {'route-id': route_id}) + if (response.status_code == 200): + response_json: dict = json.loads(response.text) + click.echo(json.dumps(response_json, indent=4, default=str)) + elif (response.status_code == 400): + log_common_feedback_err_msg(response.text) + elif (response.status_code == 404): + click.echo(f"Route with ID {route_id} was not found.", err=True) + elif (response.status_code == 500): + click.echo("Internal error, server was unable to build a reply message.", err=True) else: - click.echo("System not running or route unknown!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @click.command(help="Get debug information") def get_debug_info(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/debug") - if (response.status_code == requests.codes.ok): + response = requests.get(server + "/monitor/debug") + if (response.status_code == 200): click.echo(response.text) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) + @click.command(help="Get extra debug information") def get_debug_info_extra(): - if (parse_config()): - click.echo("Corrupt config, please run the config command", err=True) + if (not parse_config()): + click.echo("Corrupt config, please run the config command.", err=True) else: global server try: - response = requests.post(server + "/monitor/debug_extra") - if (response.status_code == requests.codes.ok): + response = requests.get(server + "/monitor/debug-extra") + if (response.status_code == 200): click.echo(response.text) else: - click.echo("System not running!\n", err=True) + log_not_running_or_unknown_err(response.status_code) except requests.exceptions.RequestException as e: click.echo(e, err=True) @@ -1098,6 +1318,7 @@ def get_debug_info_extra(): # --- assign commands to groups --- cli.add_command(config) +cli.add_command(reset_grab_and_session_id) cli.add_command(admin) cli.add_command(controller) cli.add_command(driver) @@ -1120,7 +1341,7 @@ controller.add_command(set_interlocker) controller.add_command(unset_interlocker) controller.add_command(upload_interlocker) controller.add_command(delete_interlocker) -controller.add_command(get_interlocker_list) + driver.add_command(grab) driver.add_command(release) @@ -1134,15 +1355,19 @@ driver.add_command(emergency_stop) driver.add_command(set_train_peripheral) driver.add_command(upload_engine) driver.add_command(delete_engine) -driver.add_command(get_engine_list) monitor.add_command(get_platform_name) monitor.add_command(get_trains) monitor.add_command(get_train_state) +monitor.add_command(get_trains_states) monitor.add_command(get_train_peripherals) +monitor.add_command(get_engine_list) +monitor.add_command(get_interlocker_list) monitor.add_command(get_track_outputs) monitor.add_command(get_points) monitor.add_command(get_signals) +monitor.add_command(get_point_details) +monitor.add_command(get_signal_details) monitor.add_command(get_point_aspects) monitor.add_command(get_signal_aspects) monitor.add_command(get_segments) diff --git a/configurations/swtbahn-full/bidib_board_config.yml b/configurations/swtbahn-full/bidib_board_config.yml index 6e308102..fc96f4a6 100644 --- a/configurations/swtbahn-full/bidib_board_config.yml +++ b/configurations/swtbahn-full/bidib_board_config.yml @@ -21,6 +21,9 @@ boards: unique-id: 0x45000D6B0031EF - id: slave unique-id: 0x42000D6700EAEB + features: + - number: 0x03 + value: 0x14 - id: onecontrol3 unique-id: 0x45000D8D00BDF1 - id: onecontrol4 diff --git a/configurations/swtbahn-full/config.bahn b/configurations/swtbahn-full/config.bahn index 2bd26a9e..10e20c46 100644 --- a/configurations/swtbahn-full/config.bahn +++ b/configurations/swtbahn-full/config.bahn @@ -4,7 +4,7 @@ module SWTbahnFull # Long partition master 0xDA000D6800B4F0 features - 0x03:0x14 # seckack on, 200ms + 0x03:0x14 # secack on, 200ms 0x6E:0x00 # track output default off end onecontrol1 0x45000D8D00C1F1 @@ -16,6 +16,9 @@ module SWTbahnFull # Short partition slave 0x42000D6700EAEB + features + 0x03:0x14 # secack on, 200ms + end onecontrol3 0x45000D8D00BDF1 onecontrol4 0x45000D8D0003F2 lightcontrol5 0x45000D6B002AEF @@ -162,7 +165,7 @@ module SWTbahnFull end reversers master - reverser 30051 block14 + reverser 30051 block14 end signals lightcontrol1 diff --git a/server/doc/api/API-Readme.md b/server/doc/api/API-Readme.md new file mode 100644 index 00000000..960b47d4 --- /dev/null +++ b/server/doc/api/API-Readme.md @@ -0,0 +1,8 @@ +# Notes Regarding API & API Usage +- Always analyze the returned HTTP status code first. Information about success or failure of a request to an endpoint is encoded in the HTTP status code of the reply. + +# Notes Regarding OpenAPI +- For tools, specs etc. (also regarding the OpenAPI file here, `openapi_swtbahn_V`), see: + - [API spec version 3.0.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md) + - [OpenAPI website](https://www.openapis.org/) + - [Tool used to edit the spec file](https://mermade.github.io/openapi-gui/#) (see also the [github repo](https://github.com/mermade/openapi-gui)) \ No newline at end of file diff --git a/server/doc/api/openapi_swtbahn_V1.json b/server/doc/api/openapi_swtbahn_V1.json new file mode 100644 index 00000000..9d11add0 --- /dev/null +++ b/server/doc/api/openapi_swtbahn_V1.json @@ -0,0 +1,3207 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "SWTbahn", + "version": "1.0.0", + "contact": {}, + "license": { + "name": "GPL-3.0" + }, + "description": "SWTbahn server offering an API for interacting with an SWTbahn model railway." + }, + "paths": { + "/admin/startup": { + "post": { + "summary": "Startup the SWTbahn", + "description": "Attempts to start up the SWTbahn to get it running; i.e., initialize connection to hardware, parse config files, and so on. This starts the server session if successful.", + "parameters": [], + "operationId": "admin-startup", + "responses": { + "200": { + "description": "Success" + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "SWTbahn is already running", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "500": { + "description": "SWTbahn unable to startup due to internal error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + } + }, + "security": [] + } + }, + "/admin/shutdown": { + "post": { + "summary": "Shutdown the SWTbahn", + "description": "Attempts to shut down the SWTbahn (if it is running); i.e., close connection to hardware, stop running internal threads, and so on. This stops the current server session if successful.", + "parameters": [], + "operationId": "admin-shutdown", + "responses": { + "200": { + "description": "Success" + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [] + } + }, + "/admin/set-track-output": { + "post": { + "summary": "Set the track output state", + "description": "Set the track output state for all track outputs", + "parameters": [], + "operationId": "admin-set-track-output", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_state" + } + } + }, + "description": "the state that all track outputs shall be set to" + }, + "security": [] + } + }, + "/admin/set-verification-option": { + "post": { + "summary": "Set the verification option", + "description": "Set the verification option to true or false, i.e., enable or disable the verification of train engines when they are uploaded. Note that verification will only work if a verification url to the swtbahn-verifier (or equivalent) server has been set.", + "parameters": [], + "operationId": "admin-set-verification-option", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_verification-option" + } + } + }, + "description": "the new value of the verification option (true or false)" + }, + "security": [] + } + }, + "/admin/set-verification-url": { + "post": { + "summary": "Set the verification url", + "description": "Set the verification url, i.e., the path for the websocket connection to the verification server for train engines.", + "parameters": [], + "operationId": "admin-set-verification-url", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_verification-url" + } + } + }, + "description": "the new value of the verification url (should start with ws://)" + }, + "security": [] + } + }, + "/admin/release-train": { + "post": { + "summary": "Release a train", + "description": "Force-release a train that is currently grabbed by someone.", + "parameters": [], + "operationId": "admin-release-train", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid train, or train not currently grabbed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_train" + } + } + }, + "description": "the train that shall be released" + }, + "security": [] + } + }, + "/admin/set-dcc-train-speed": { + "post": { + "summary": "Set the dcc speed of a train", + "description": "Set the dcc speed of a train. This is set directly via bidib, i.e., it does not use the train engine dynamic container. Note that this may lead to inconsistencies/the train changing its speed to something else after this command due to a change in the output of the train engine dynamic container.", + "parameters": [], + "operationId": "admin-set-dcc-train-speed", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid train, invalid speed, or invalid track output", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_train_speed_track-output" + } + } + }, + "description": "the train whose speed shall be set, the speed in the range [-126, 126] , and the track output (usually 'master')." + }, + "security": [] + } + }, + "/controller/release-route": { + "post": { + "summary": "releases a route", + "description": "releases the specified route that is currently granted to a train", + "parameters": [], + "operationId": "controller-release-route", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_route-id" + } + } + }, + "description": "route which shall be released." + }, + "security": [] + } + }, + "/controller/set-point": { + "post": { + "summary": "set the state/aspect of a point", + "description": "", + "parameters": [], + "operationId": "controller-set-point", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown Point", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_point_state" + } + } + }, + "description": "point whose state shall be set, and state to set." + }, + "security": [] + } + }, + "/controller/set-signal": { + "post": { + "summary": "set the state/aspect of a signal", + "description": "", + "parameters": [], + "operationId": "controller-set-signal", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown signal", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_signal_state" + } + } + }, + "description": "signal whose state shall be set, and state to set." + }, + "security": [] + } + }, + "/controller/set-peripheral": { + "post": { + "summary": "set the state/aspect of a peripheral", + "description": "", + "parameters": [], + "operationId": "controller-set-peripheral", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_peripheral_state" + } + } + }, + "description": "peripheral whose state shall be set, and state to set." + }, + "security": [] + } + }, + "/controller/set-interlocker": { + "post": { + "summary": "set the interlocker to be used by the SWTbahn", + "description": "", + "parameters": [], + "operationId": "controller-set-interlocker", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter or no more free interlocker instances available", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Another interlocker is already set", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_interlocker" + } + } + }, + "description": "ID of the interlocker to be used by the SWTbahn." + }, + "security": [] + } + }, + "/controller/unset-interlocker": { + "post": { + "summary": "unset the interlocker currently used by the SWTbahn", + "description": "", + "parameters": [], + "operationId": "controller-unset-interlocker", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "no interlocker currently set, or currently set interlocker has a different name", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_interlocker" + } + } + }, + "description": "ID of the interlocker currently used by the SWTbahn that shall be unset." + }, + "security": [] + } + }, + "/controller/get-interlocker": { + "get": { + "summary": "get the ID of the interlocker currently used by the SWTbahn", + "description": "", + "parameters": [], + "operationId": "controller-get-interlocker", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_get-interlocker" + } + } + } + }, + "404": { + "description": "no interlocker currently set", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/driver/grab-train": { + "post": { + "summary": "grab a train", + "description": "Grab a train (to take temporary ownership). If successful, a grab-id (that identifies this temporary ownership) and a session-id (server session ID to detect server restarts which invalidate train ownerships) are returned. The ownership lasts until it is released (by an admin or by the driver), or until the server session ends.", + "parameters": [], + "operationId": "driver-grab-train", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_grab-train" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown train or invalid train state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Train already grabbed or max. no of grabbed trains reached", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "500": { + "description": "Internal error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_train_engine" + } + } + }, + "description": "A train engine ID has to be specified along with the ID of the train to grab.\nThe train engine determines how the train reacts to speed and direction requests/commands sent via the appropriate API endpoint.\nThe normal train engine is called 'libtrain_engine_default (unremovable)'." + } + } + }, + "/driver/release-train": { + "post": { + "summary": "release a train", + "description": "Release a train that was grabbed with a specific grab-id in the current server session.", + "parameters": [], + "operationId": "driver-release-train", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Train is not currently grabbed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_session-id_grab-id" + } + } + }, + "description": "The grab-id has to be specified which identifies the train ownership that is to be released.\nThe session-id has to be specified to make sure this train ownership was granted in the same server session." + } + } + }, + "/driver/request-route": { + "post": { + "summary": "request a route", + "description": "Request a route from a source signal to a destination signal, to be driven with the grabbed train. If successful, a route is granted and its ID is returned.", + "parameters": [], + "operationId": "driver-request-route", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_request-route" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter(s), or other reason for no route being granted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Route to be granted is in conflict with other granted route or in conflict with state of the track", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_session-id_grab-id_source_destination" + } + } + }, + "description": "The grab-id has to be specified which identifies the train ownership, i.e., the train with which the route is to be driven.\nThe session-id has to be specified to make sure the train ownership was granted in the same server session.\nThe source (destination) signal is the start (end) of the desired route. In case of composite signals (two signals on one post/mast), usually the specific ID of the non-distant signal has to be provided." + } + } + }, + "/driver/request-route-by-id": { + "post": { + "summary": "request a route by its ID", + "description": "Request the route with a specific route-id, to be driven with the grabbed train. If successful, the route with the specified ID is granted.", + "parameters": [], + "operationId": "driver-request-route-by-id", + "responses": { + "200": { + "description": "Success (route is granted)" + }, + "400": { + "description": "Invalid or missing parameter(s), or other reason for no route being granted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Route to be granted is in conflict with other granted route or in conflict with state of the track, or route is already granted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "500": { + "description": "Internal error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_session-id_grab-id_route-id" + } + } + }, + "description": "The grab-id has to be specified which identifies the train ownership, i.e., the train with which the route is to be driven.\nThe session-id has to be specified to make sure the train ownership was granted in the same server session.\nThe route-id identifies which route is being requested." + } + } + }, + "/driver/direction": { + "post": { + "summary": "get the direction for driving a route", + "description": "Get the direction that the specified train would need to drive (forwards or backwards) to drive along the specified route.", + "parameters": [], + "operationId": "driver-direction", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_direction" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown route", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_train_route-id" + } + } + }, + "description": "The train for which the direction is to be determined and the route-id of the route for which the driving direction is to be determined." + } + } + }, + "/driver/drive-route": { + "post": { + "summary": "drive a route", + "description": "Drive the specified route with the grabbed train, either in manual mode (speed controlled by driver via separate set-speed commands) or in automatic mode (train is driven automatically along the route).\nNote that this request is blocking until the route is released (either manually through appropriate command or automatically when end of the route is reached), or an internal error occurs.\nThis only works if the route is currently granted to the grabbed train.", + "parameters": [], + "operationId": "driver-drive-route", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_session-id_grab-id_route-id_mode" + } + } + }, + "description": "The session-id of the current server session. \nThe grab-id that identifies the ownership of the train with which the route is driven.\nThe route-id that identifies the route to drive.\nThe mode that specifies whether driving is manual or automatic." + } + } + }, + "/driver/set-dcc-train-speed": { + "post": { + "summary": "set the dcc speed of a train", + "description": "Set the requested speed (and thus also direction) of the grabbed train to a valid dcc speed. The speed request is passed to the train engine dynamic container.", + "parameters": [], + "operationId": "driver-set-dcc-train-speed", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_session-id_grab-id_speed_track-output" + } + } + }, + "description": "The session-id of the current server session. \nThe grab-id that identifies the ownership of the train whose speed is to be set.\nThe dcc speed to set.\nThe track output for which the train shall be set to the given speed (usually 'master')." + } + } + }, + "/driver/set-calibrated-train-speed": { + "post": { + "summary": "set the speed of a train to a calibrated level", + "description": "Sets the speed of the grabbed train to a calibrated level (levels defined in config files)(and thus also direction). In contrast to driver-set-dcc-train-speed, this bypasses the train engine dynamic container, i.e., the speed is set directly via bidib. Note that this may lead to inconsistencies/the train changing its speed to something else after this command due to a change in the output of the train engine dynamic container.", + "parameters": [], + "operationId": "driver-set-calibrated-train-speed", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_session-id_grab-id_speed_track-output__for-calibrated-speed" + } + } + }, + "description": "The session-id of the current server session. \nThe grab-id that identifies the ownership of the train whose speed is to be set.\nThe calibrated speed level to set.\nThe track output for which the train shall be set to the given speed (usually 'master')." + } + } + }, + "/driver/set-train-emergency-stop": { + "post": { + "summary": "set a train to emergency stop", + "description": "Emergency stops the grabbed train. This bypasses the train engine dynamic container, i.e., the emergency stop is set directly via bidib. Note that this may lead to inconsistencies/the train starting to drive again after the emergency stop due to a change in the output of the train engine dynamic container.", + "parameters": [], + "operationId": "driver-set-train-emergency-stop", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_session-id_grab-id_track-output" + } + } + }, + "description": "The session-id of the current server session. \nThe grab-id that identifies the ownership of the train which is to be stopped.\nThe track output for which the train shall be stopped (usually 'master')." + } + } + }, + "/driver/set-train-peripheral": { + "post": { + "summary": "set the state of a train peripheral", + "description": "Set the state of the peripheral of the grabbed train", + "parameters": [], + "operationId": "driver-set-train-peripheral", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter(s)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_session-id_grab-id_peripheral_state_track-output" + } + } + }, + "description": "The session-id of the current server session. \nThe grab-id that identifies the ownership of the train whose peripheral is to be set.\nThe peripheral whose state to set.\nThe state to set the peripheral to (string with '0' or '1' for on or off respectively).\nThe track output for which the train's peripheral shall be set (usually 'master')." + } + } + }, + "/monitor/platform-name": { + "get": { + "summary": "get the name of the SWTbahn (platform) currently being operated", + "description": "", + "parameters": [], + "operationId": "monitor-platform-name", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_platform-name" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/trains": { + "get": { + "summary": "get info of trains", + "description": "Gets information (ID, grab status, on-track status) of trains, for all trains defined in the configuration files of the SWTbahn platform being operated", + "parameters": [], + "operationId": "monitor-trains", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_trains" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/train-state": { + "post": { + "summary": "get info on state of specific train", + "description": "Gets information on state of a specific train", + "parameters": [], + "operationId": "monitor-train-state", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_train-state" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown train" + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_train" + } + } + }, + "description": "The name/ID of the train to get info on its state" + } + } + }, + "/monitor/train-states": { + "get": { + "summary": "get info on state of all trains", + "description": "Gets information on state of all trains", + "parameters": [], + "operationId": "monitor-train-states", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_train-states" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/train-peripherals": { + "post": { + "summary": "get info on peripherals of a specific train", + "description": "", + "parameters": [], + "operationId": "monitor-train-peripherals", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_train-peripherals" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown train" + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_train" + } + } + }, + "description": "The name/ID of the train whose peripherals to get info on" + } + } + }, + "/monitor/engines": { + "get": { + "summary": "get names of available train engines (behavior models)", + "description": "", + "parameters": [], + "operationId": "monitor-engines", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_engines" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/interlockers": { + "get": { + "summary": "get names of available interlockers", + "description": "", + "parameters": [], + "operationId": "monitor-interlockers", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_interlockers" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/track-outputs": { + "get": { + "summary": "get names of available track ouputs", + "description": "", + "parameters": [], + "operationId": "monitor-track-outputs", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_track-outputs" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/points": { + "get": { + "summary": "get info on points", + "description": "Get information (ID, state, and optionally whether the current target state is reached) on all points", + "parameters": [], + "operationId": "monitor-points", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_points" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/signals": { + "get": { + "summary": "get info on signals", + "description": "Get information (ID, state) on all signals", + "parameters": [], + "operationId": "monitor-signals", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_signals" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/point-details": { + "post": { + "summary": "get detailed info on a specific point", + "description": "", + "parameters": [], + "operationId": "monitor-point-details", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_point-details" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown point" + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_point" + } + } + }, + "description": "The name/ID of the point" + } + } + }, + "/monitor/signal-details": { + "post": { + "summary": "get detailed info on a specific signal - NOT YET IMPLEMENTED", + "description": "", + "parameters": [], + "operationId": "monitor-signal-details", + "responses": { + "501": { + "description": "NOT YET IMPLEMENTED" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_signal" + } + } + }, + "description": "The name/ID of the signal" + } + } + }, + "/monitor/point-aspects": { + "post": { + "summary": "get info on aspects (states) a specific point supports", + "description": "", + "parameters": [], + "operationId": "monitor-point-aspects", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_accessory-aspects" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown point" + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_point" + } + } + }, + "description": "The name/ID of the point" + } + } + }, + "/monitor/signal-aspects": { + "post": { + "summary": "get info on aspects (states) a specific signal supports", + "description": "", + "parameters": [], + "operationId": "monitor-signal-aspects", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_accessory-aspects" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown signal" + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_signal" + } + } + }, + "description": "The name/ID of the signal" + } + } + }, + "/monitor/segments": { + "get": { + "summary": "get info on segments", + "description": "Get info on segments (ID, and, if occupied, who/what is occupying them)", + "parameters": [], + "operationId": "monitor-segments", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_segments" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/reversers": { + "get": { + "summary": "get info on reversers", + "description": "Get info on reversers (ID, state)", + "parameters": [], + "operationId": "monitor-reversers", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_reversers" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message or unable to get reverser state update" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/peripherals": { + "get": { + "summary": "get info on peripherals", + "description": "Get info on peripherals (ID, state ID, state value)", + "parameters": [], + "operationId": "monitor-peripherals", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_peripherals" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/verification-option": { + "get": { + "summary": "get the current verification option setting", + "description": "", + "parameters": [], + "operationId": "monitor-verification-option", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_verification-option" + } + } + } + }, + "405": { + "description": "Method not allowed" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/verification-url": { + "get": { + "summary": "get the current verification url", + "description": "", + "parameters": [], + "operationId": "monitor-verification-url", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_verification-url" + } + } + } + }, + "405": { + "description": "Method not allowed" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/granted-routes": { + "get": { + "summary": "get info on granted routes", + "description": "Get info on granted routes, with ID of route and the train (ID) they are granted to", + "parameters": [], + "operationId": "monitor-granted-routes", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_granted-routes" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {} + } + }, + "/monitor/route": { + "post": { + "summary": "get info on a specific route", + "description": "Get (detailed) info on a specific route, including conflicting route IDs etc.; reply can be quite big", + "parameters": [], + "operationId": "monitor-route", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_route" + } + } + } + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Unknown route" + }, + "405": { + "description": "Method not allowed" + }, + "500": { + "description": "Server unable to build reply message or other internal error" + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_route-id" + } + } + }, + "description": "The name/ID of the route" + } + } + }, + "/upload/engine": { + "post": { + "summary": "upload a train engine (behavior) model", + "description": "Upload a train engine behavior model in form of an .sctx (SCCharts) file.", + "parameters": [], + "operationId": "upload-engine", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter, or verification of train engine failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reply_engine__for-upload" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Engine with same name exists or no engine (container) slot is available", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "500": { + "description": "Engine could not be compiled to a shared library", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/param_file" + } + } + }, + "description": "The .sctx file containing the train engine." + } + } + }, + "/upload/remove-engine": { + "post": { + "summary": "remove a train engine (behavior) model", + "description": "Remove a train engine (behavior) model from those that are available on the server. Note that the default engines which are not uploaded by users are not removable.", + "parameters": [], + "operationId": "upload-remove-engine", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter, or engine is unremovable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Engine to remove not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Engine is still being used", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_engine-name" + } + } + }, + "description": "The name of the train engine to remove." + } + } + }, + "/upload/interlocker": { + "post": { + "summary": "upload an interlocker", + "description": "Upload a interlocker in form of a .bahn (BahnDSL) file.", + "parameters": [], + "operationId": "upload-interlocker", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Interlocker with same name exists or no interlocker (container) slot is available", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "500": { + "description": "Interlocker could not be compiled to a shared library", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/param_file" + } + } + }, + "description": "The .bahn file containing the interlocker." + } + } + }, + "/upload/remove-interlocker": { + "post": { + "summary": "remove an interlocker", + "description": "Remove an interlocker from those that are available on the server. Note that the default interlockers which are not uploaded by users are not removable.", + "parameters": [], + "operationId": "upload-remove-interlocker", + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Invalid or missing parameter, or interlocker is unremovable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "404": { + "description": "Interlocker to remove not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "405": { + "description": "Method not allowed" + }, + "409": { + "description": "Interlocker is still being used", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/common_feedback" + } + } + } + }, + "503": { + "description": "SWTbahn not running" + } + }, + "security": [], + "callbacks": {}, + "requestBody": { + "required": true, + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/param_interlocker-name" + } + } + }, + "description": "The name of the interlocker to remove." + } + } + } + }, + "security": [], + "servers": [], + "components": { + "links": {}, + "callbacks": {}, + "schemas": { + "common_feedback": { + "title": "common_feedback", + "description": "simple object with feedback message", + "type": "object", + "properties": { + "msg": { + "type": "string", + "minLength": 1 + } + } + }, + "param_train": { + "title": "param_train", + "type": "object", + "properties": { + "train": { + "description": "name of the train", + "type": "string", + "minLength": 1 + } + } + }, + "param_verification-url": { + "title": "param_verification-url", + "type": "object", + "properties": { + "verification-url": { + "description": "verification server url incl. port", + "type": "string", + "minLength": 1 + } + } + }, + "param_verification-option": { + "title": "param_verification-option", + "type": "object", + "properties": { + "verification-option": { + "description": "verification option value string (value should be 'true' or 'false')", + "type": "string", + "minLength": 1, + "pattern": "^(true|false)$" + } + } + }, + "param_state": { + "title": "param_state", + "type": "object", + "properties": { + "state": { + "description": "state", + "type": "string", + "minLength": 1 + } + } + }, + "param_route-id": { + "title": "param_route-id", + "type": "object", + "properties": { + "route-id": { + "description": "ID of the route", + "type": "string", + "minLength": 1 + } + } + }, + "param_point_state": { + "title": "param_point_state", + "type": "object", + "properties": { + "point": { + "description": "ID of a point", + "type": "string", + "minLength": 1 + }, + "state": { + "description": "desired point state", + "type": "string", + "minLength": 1 + } + } + }, + "param_signal_state": { + "title": "param_signal_state", + "type": "object", + "properties": { + "signal": { + "description": "ID of a signal", + "type": "string", + "minLength": 1 + }, + "state": { + "description": "desired signal state", + "type": "string", + "minLength": 1 + } + } + }, + "param_peripheral_state": { + "title": "param_peripheral_state", + "type": "object", + "properties": { + "peripheral": { + "description": "ID of a peripheral", + "type": "string", + "minLength": 1 + }, + "state": { + "description": "desired peripheral state", + "type": "string", + "minLength": 1 + } + } + }, + "param_interlocker": { + "title": "param_interlocker", + "type": "object", + "properties": { + "interlocker": { + "description": "ID of an interlocker", + "type": "string", + "minLength": 1 + } + } + }, + "reply_get-interlocker": { + "title": "reply_get-interlocker", + "description": "interlocker", + "type": "object", + "properties": { + "interlocker": { + "description": "the ID of the interlocker that is currently set", + "type": "string", + "minLength": 1 + } + } + }, + "param_train_engine": { + "title": "param_train_engine", + "type": "object", + "properties": { + "train": { + "description": "name of a train", + "type": "string", + "minLength": 1 + }, + "engine": { + "description": "name of a (train) engine", + "type": "string", + "minLength": 1 + } + } + }, + "param_session-id_grab-id": { + "title": "param_session-id_grab-id", + "type": "object", + "properties": { + "session-id": { + "description": "session-id", + "type": "string", + "minLength": 1 + }, + "grab-id": { + "description": "grab-id", + "type": "string", + "minLength": 1 + } + } + }, + "reply_grab-train": { + "title": "reply_grab-train", + "type": "object", + "properties": { + "session-id": { + "type": "integer", + "description": "identifier for the current server session", + "minimum": 1 + }, + "grab-id": { + "type": "integer", + "description": "identifier for this grabbing/ownership of a train", + "minimum": 0 + } + } + }, + "param_session-id_grab-id_source_destination": { + "title": "param_session-id_grab-id_source_destination", + "type": "object", + "properties": { + "session-id": { + "description": "session-id", + "type": "string", + "minLength": 1 + }, + "grab-id": { + "description": "grab-id", + "type": "string", + "minLength": 1 + }, + "source": { + "description": "name of the source signal", + "type": "string", + "minLength": 1 + }, + "destination": { + "description": "name of the destination signal", + "type": "string", + "minLength": 1 + } + } + }, + "reply_request-route": { + "title": "reply_request-route", + "type": "object", + "properties": { + "granted-route-id": { + "description": "ID of the granted route", + "type": "string", + "minLength": 1 + } + } + }, + "param_session-id_grab-id_route-id": { + "title": "param_session-id_grab-id_route-id", + "type": "object", + "properties": { + "session-id": { + "description": "session-id", + "type": "string", + "minLength": 1 + }, + "grab-id": { + "description": "grab-id", + "type": "string", + "minLength": 1 + }, + "route-id": { + "description": "ID of the route", + "type": "string", + "minLength": 1 + } + } + }, + "param_train_route-id": { + "title": "param_train_route-id", + "type": "object", + "properties": { + "train": { + "description": "name of a train", + "type": "string", + "minLength": 1 + }, + "route-id": { + "description": "ID of the route that shall be considered for driving direction", + "type": "string", + "minLength": 1 + } + } + }, + "reply_direction": { + "title": "reply_direction", + "type": "object", + "properties": { + "direction": { + "type": "string", + "pattern": "^(forwards|backwards)$", + "description": "direction of train considering its location and route to be driven" + } + } + }, + "param_session-id_grab-id_route-id_mode": { + "title": "param_session-id_grab-id_route-id_mode", + "type": "object", + "properties": { + "session-id": { + "description": "session-id", + "type": "string", + "minLength": 1 + }, + "grab-id": { + "description": "grab-id", + "type": "string", + "minLength": 1 + }, + "route-id": { + "description": "ID of the route", + "type": "string", + "minLength": 1 + }, + "mode": { + "type": "string", + "pattern": "^(manual|automatic)$", + "description": "driving mode for the route, either manual or automatic" + } + } + }, + "param_session-id_grab-id_speed_track-output": { + "title": "param_session-id_grab-id_speed_track-output", + "type": "object", + "properties": { + "session-id": { + "description": "session-id", + "type": "string", + "minLength": 1 + }, + "grab-id": { + "description": "grab-id", + "type": "string", + "minLength": 1 + }, + "speed": { + "description": "speed to set the train to, in range [-126,126]", + "type": "string", + "minLength": 1 + }, + "track-output": { + "description": "the name of the track output node (usually master)", + "type": "string", + "minLength": 1 + } + } + }, + "param_train_speed_track-output": { + "title": "param_train_speed_track-output", + "type": "object", + "properties": { + "train": { + "description": "name of a train", + "type": "string", + "minLength": 1 + }, + "speed": { + "description": "speed to set the train to, in range [-126,126]", + "type": "string", + "minLength": 1 + }, + "track-output": { + "description": "the name of the track output node (usually master)", + "type": "string", + "minLength": 1 + } + } + }, + "param_session-id_grab-id_speed_track-output__for-calibrated-speed": { + "title": "param_session-id_grab-id_speed_track-output__for-calibrated-speed", + "type": "object", + "properties": { + "session-id": { + "description": "session-id", + "type": "string", + "minLength": 1 + }, + "grab-id": { + "description": "grab-id", + "type": "string", + "minLength": 1 + }, + "speed": { + "description": "speed to set the train to, in range [-9,9], depending on the calibration levels configured for the grabbed train", + "type": "string", + "minLength": 1 + }, + "track-output": { + "description": "the name of the track output node (usually master)", + "type": "string", + "minLength": 1 + } + } + }, + "param_session-id_grab-id_track-output": { + "title": "param_session-id_grab-id_track-output", + "type": "object", + "properties": { + "session-id": { + "description": "session-id", + "type": "string", + "minLength": 1 + }, + "grab-id": { + "description": "grab-id", + "type": "string", + "minLength": 1 + }, + "track-output": { + "description": "the name of the track output node (usually master)", + "type": "string", + "minLength": 1 + } + } + }, + "param_session-id_grab-id_peripheral_state_track-output": { + "title": "param_session-id_grab-id_peripheral_state_track-output", + "type": "object", + "properties": { + "session-id": { + "description": "session-id", + "type": "string", + "minLength": 1 + }, + "grab-id": { + "description": "grab-id", + "type": "string", + "minLength": 1 + }, + "peripheral": { + "description": "ID of the train peripheral", + "type": "string", + "minLength": 1 + }, + "state": { + "description": "desired peripheral state (either 0 (off) or 1 (on))", + "type": "string", + "minLength": 1 + }, + "track-output": { + "description": "the name of the track output node (usually master)", + "type": "string", + "minLength": 1 + } + } + }, + "param_signal": { + "title": "param_signal", + "type": "object", + "properties": { + "signal": { + "description": "ID of a signal", + "type": "string", + "minLength": 1 + } + } + }, + "param_point": { + "title": "param_point", + "type": "object", + "properties": { + "point": { + "description": "ID of a point", + "type": "string", + "minLength": 1 + } + } + }, + "reply_route": { + "title": "reply_route", + "description": "Info on a specific route", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the route", + "minLength": 1 + }, + "source_signal": { + "type": "string", + "minLength": 1 + }, + "destination_signal": { + "type": "string", + "minLength": 1 + }, + "orientation": { + "type": "string", + "pattern": "^(anticlockwise|clockwise)$" + }, + "length": { + "type": "number", + "description": "length, usually in centimeters (depends on config)" + }, + "path": { + "type": "array", + "minItems": 1, + "uniqueItems": false, + "description": "path of the route, in order of intended traversal, including signals that are passed", + "items": { + "type": "string", + "minLength": 1 + } + }, + "sections": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "signals": { + "type": "array", + "minItems": 2, + "description": "Includes at least source and destination signals, plus any passed non-distant signals", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "points": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "conflicting_route_ids": { + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "granted_conflicting_route_ids": { + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "clear": { + "type": "boolean" + }, + "granted_to_train": { + "type": "string", + "minLength": 0, + "description": "Contains name of the train if the route is currently granted, otherwise empty" + } + } + }, + "reply_granted-routes": { + "title": "reply_granted-routes", + "description": "List of granted routes, each entry has the route ID and name of train which the route is granted to", + "type": "object", + "properties": { + "granted-routes": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "train": { + "type": "string", + "minLength": 1 + } + } + } + } + } + }, + "reply_verification-url": { + "title": "reply_verification-url", + "description": "Verification Server URL", + "type": "object", + "properties": { + "verification-url": { + "type": "string", + "minLength": 0 + } + } + }, + "reply_verification-option": { + "title": "reply_verification-option", + "description": "State of verification option, i.e., whether it is enabled or disabled", + "type": "object", + "properties": { + "verification-enabled": { + "type": "boolean" + } + } + }, + "reply_peripherals": { + "title": "reply_peripherals", + "description": "List of peripherals, with their identifier and info on their state id and state value", + "type": "object", + "properties": { + "peripherals": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "state-id": { + "type": "string", + "minLength": 0 + }, + "state-value": { + "type": "integer" + } + } + } + } + } + }, + "reply_reversers": { + "title": "reply_reversers", + "description": "List of reversers of the platform with their state", + "type": "object", + "properties": { + "reversers": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "state": { + "type": "string", + "pattern": "^(on|off|unknown)$" + } + } + } + } + } + }, + "reply_segments": { + "title": "reply_segments", + "description": "List of segments, with info on occupancy per segment if it is occupied", + "type": "object", + "properties": { + "segments": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "occupied-by": { + "description": "list of occupiers (names of train(s) or 'unknown' if occupier is not identifiable)", + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + } + } + } + } + }, + "reply_point-details": { + "title": "reply_point-details", + "description": "Detailed information on a point", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "aspects": { + "type": "array", + "minItems": 0, + "description": "list of aspects, i.e., states, that this point can be set to. currently allows answers with no aspects in the list, can occur if low-level querying of aspects fails", + "items": { + "type": "string" + } + }, + "state": { + "type": "string", + "minLength": 0 + }, + "segment": { + "type": "string", + "minLength": 1 + }, + "occupied": { + "type": "boolean" + }, + "target_state_reached": { + "type": "boolean", + "description": "This is optional, as DCC-accessories (so possibly points) don't provide this info. True if execution state is reached or reached_verified, otherwise false." + } + }, + "required": [ + "id", + "aspects", + "state", + "segment", + "occupied" + ] + }, + "reply_signals": { + "title": "reply_signals", + "description": "List of signals, consisting of the signal ID and its state", + "type": "object", + "properties": { + "signals": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "state": { + "type": "string", + "minLength": 0 + } + } + } + } + } + }, + "reply_points": { + "title": "reply_points", + "description": "List of points, consisting of the point ID, its state, and the target_state_reached status if available", + "type": "object", + "properties": { + "points": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "required": [ + "id", + "state" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "state": { + "type": "string", + "minLength": 0 + }, + "target_state_reached": { + "type": "boolean", + "description": "This is optional, as DCC-accessories (so possibly points) don't provide this info. True if execution state is reached or reached_verified, otherwise false." + } + } + } + } + } + }, + "reply_track-outputs": { + "title": "reply_track-outputs", + "description": "List of track outputs with their identifier and their state", + "type": "object", + "properties": { + "track-outputs": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "state": { + "type": "string", + "minLength": 0 + } + } + } + } + } + }, + "reply_interlockers": { + "title": "reply_interlockers", + "description": "List of interlocker identifiers", + "type": "object", + "properties": { + "interlockers": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, + "reply_engines": { + "title": "reply_engines", + "description": "List of train engine identifiers (i.e., those of available train behaviour models, not physical trains)", + "type": "object", + "properties": { + "engines": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, + "reply_train-peripherals": { + "title": "reply_train-peripherals", + "description": "List of peripherals of a train with their state", + "type": "object", + "properties": { + "train-peripherals": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "state": { + "type": "string", + "pattern": "^(on|off|unknown)$" + } + } + } + } + } + }, + "reply_train-state": { + "title": "reply_train-state", + "description": "state of a train", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "grabbed": { + "type": "boolean" + }, + "orientation": { + "type": "string", + "pattern": "^(left|right)$" + }, + "direction": { + "type": "string", + "pattern": "^(forwards|backwards)$" + }, + "speed_step": { + "type": "number" + }, + "detected_kmh_speed": { + "type": "number", + "description": "Some trains don't report their km/h speed; in that case the value is 0 or this field is not present" + }, + "route_id": { + "type": "string", + "description": "empty string or not present if no route is granted to this train. Otherwise route id.", + "minLength": 0 + }, + "on_track": { + "type": "boolean" + }, + "occupied_segments": { + "type": "array", + "description": "occupied_segments is only included if on_track is true.", + "items": { + "type": "string" + } + }, + "occupied_blocks": { + "type": "array", + "description": "occupied_blocks is only included if on_track is true.", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "grabbed", + "orientation", + "direction", + "speed_step", + "on_track" + ] + }, + "reply_trains": { + "title": "reply_trains", + "description": "List of trains, each entry has train ID, grabbed status and on_track status", + "type": "object", + "properties": { + "trains": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "grabbed": { + "type": "boolean" + }, + "on_track": { + "type": "boolean" + } + } + } + } + } + }, + "reply_platform-name": { + "title": "reply_platform-name", + "type": "object", + "properties": { + "platform-name": { + "type": "string", + "description": "name of the SWTbahn (platform) which is being operated", + "minLength": 1 + } + } + }, + "reply_accessory-aspects": { + "title": "reply_accessory-aspects", + "description": "List of aspects (i.e., states) that a specific accessory (e.g., point, signal) can be set to", + "type": "object", + "properties": { + "aspects": { + "type": "array", + "minItems": 0, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, + "param_file": { + "title": "param_file", + "type": "object", + "properties": { + "file": { + "description": "file. Attach via your request libraries form data, try with default format/encoding", + "type": "string", + "format": "binary" + } + } + }, + "reply_engine__for-upload": { + "title": "reply_engine__for-upload", + "description": "Result info of uploading an engine", + "type": "object", + "properties": { + "msg": { + "type": "string", + "description": "(error) message", + "minLength": 1 + }, + "status": { + "type": "boolean", + "description": "true if all properties of the engine model hold, false otherwise" + }, + "__MESSAGE_TYPE__": { + "type": "string", + "description": "this can safely be ignored" + }, + "verifiedproperties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "property": { + "type": "object", + "properties": { + "formula": { + "type": "string", + "description": "formula of the property" + }, + "name": { + "type": "string", + "description": "name of the property" + }, + "type": { + "type": "string", + "description": "type of the property (ltl or invariant)" + } + } + }, + "resultcode": { + "type": "string", + "pattern": "^(proven|disproven|unknown)$", + "description": "result of verification of this property" + }, + "verificationlog": { + "type": "string", + "description": "Base64 encoded String. If decoded, this will give the verification log from nuXmv, including counterexamples at the end if possible. The string might also be compressed with zlib (uncompress after initial base64 decode), but default is uncompressed." + }, + "verificationmessage": { + "type": "string", + "description": "Textual description of verification verdict for this property" + } + } + } + } + }, + "required": [ + "msg" + ] + }, + "param_engine-name": { + "title": "param_engine-name", + "type": "object", + "properties": { + "engine-name": { + "description": "name of a (train) engine", + "type": "string", + "minLength": 1 + } + } + }, + "param_interlocker-name": { + "title": "param_interlocker-name", + "type": "object", + "properties": { + "interlocker-name": { + "description": "name of an interlocker", + "type": "string", + "minLength": 1 + } + } + }, + "reply_train-states": { + "title": "reply_train-states", + "description": "List of train states", + "type": "object", + "properties": { + "train-states": { + "type": "array", + "uniqueItems": true, + "minItems": 0, + "items": { + "$ref": "#/components/schemas/reply_train-state" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/server/src/assets/game/script-game.js b/server/src/assets/game/script-game.js index 0e0e0dba..38c47124 100644 --- a/server/src/assets/game/script-game.js +++ b/server/src/assets/game/script-game.js @@ -20,31 +20,27 @@ var signalFlagMap = null; // Global access variable for signal flag mapping // If the server does reply with the platform name, tries to load the destinations and the flags // mapped for the respective platform. If this fails, does not perform a retry. function tryLoadDestinationsAndFlagMap() { - // Try to get the platform name from the server, retry every 2 seconds on failure. let retryDelay = 2000; - fetch(serverAddress + '/monitor/platform-name',) + fetch(serverAddress + '/monitor/platform-name') .then(response => { if (response.status !== 200) { console.log("Getting platform name failed: Retrying in %d ms", retryDelay); setTimeout(() => tryLoadDestinationsAndFlagMap(), retryDelay); } else { - // response.text() gives a promise, not immediately a string, therefore pass - // it to the next then block via return. - return response.text(); + return response.json(); } }) - .then(responseText => { - // Check if the previous then block actually returned something (i.e., if server - // responded with a reply with code 200) - if (responseText !== undefined) { - console.log("Platform name received from server:", responseText); - platform = responseText.toLowerCase(); - // Set the possible destinations and signal flag map for the SWTbahn platform. + .then(data => { + if (data !== undefined && data["platform-name"]) { + console.log("Platform name received from server:", data["platform-name"]); + let platform = data["platform-name"].toLowerCase(); allPossibleDestinations = eval("allPossibleDestinations_" + platform); signalFlagMap = eval("signalFlagMap_" + platform); } }) - .catch(error => console.error("tryLoadDestinationsAndFlagMap (get platform name) err:", error)); + .catch(error => { + console.error("tryLoadDestinationsAndFlagMap (get platform name) err:", error); + }); } // Returns the destinations possible from a given block @@ -55,13 +51,6 @@ function getDestinations(blockId) { return allPossibleDestinations[blockId]; } -// Destructures a route into its destination signal and route details -function unpackRoute(route) { - for (let destinationSignal in route) { - return [destinationSignal, route[destinationSignal]]; - } - return null; -} function disableAllDestinationButtons() { driver.clearUpdatePossibleDestinationsInterval(); @@ -72,28 +61,23 @@ function disableAllDestinationButtons() { } } -function setDestinationButton(choice, route) { - const [destinationSignal, routeDetails] = unpackRoute(route); - const destination = destinationSignal.replace(/(a|b)$/, ''); +function setDestinationButton(routeIndex, route, destinationSignal) { + // "Clean" the "a" or "b" suffix of composite signal if present + const destinationSigCleaned = destinationSignal.replace(/(a|b)$/, ''); // Route details are stored in the value parameter of the destination button - $(`#${destinationNamePrefix}${choice}`).val(JSON.stringify(route)); - $(`#${destinationNamePrefix}${choice}`).attr("class", signalFlagMap[destination]); + $(`#${destinationNamePrefix}${routeIndex}`).val(JSON.stringify(route)); + $(`#${destinationNamePrefix}${routeIndex}`).attr("class", signalFlagMap[destinationSigCleaned]); } -function setDestinationButtonAvailable(choice, route) { - setDestinationButton(choice, route); - $(`#${destinationNamePrefix}${choice}`).removeClass("flagThemeDisabled"); +function setDestinationButtonAvailable(routeIndex, route, destinationSignal) { + setDestinationButton(routeIndex, route, destinationSignal); + $(`#${destinationNamePrefix}${routeIndex}`).removeClass("flagThemeDisabled"); } -function setDestinationButtonUnavailable(choice, route) { - setDestinationButton(choice, route); - $(`#${destinationNamePrefix}${choice}`).addClass("flagThemeDisabled"); -} - -function setChosenTrain(trainId) { - const imageName = `train-${trainId.replace("_", "-")}.jpg` - $("#chosenTrain").attr("src", imageName); +function setDestinationButtonUnavailable(routeIndex, route, destinationSignal) { + setDestinationButton(routeIndex, route, destinationSignal); + $(`#${destinationNamePrefix}${routeIndex}`).addClass("flagThemeDisabled"); } // Periodically update the availability of a blocks possible destinations. @@ -105,66 +89,78 @@ function updatePossibleDestinations(blockId) { disableAllDestinationButtons(); // Set up a timer interval to periodically update the availability - const updatePossibleDestinationsTimeout = 1000; + const updatePossibleDestinationsTimeout = 1000; // 1000ms driver.updatePossibleDestinationsInterval = setInterval(() => { - console.log("Checking available destinations ..."); - - const routes = getDestinations(blockId); - if (routes == null) { + const routesFromCurrentBlock = getDestinations(blockId); + if (routesFromCurrentBlock == null) { + console.warn('updatePossibleDestinations: routesFromCurrentBlock is NULL'); return; } - Object.keys(routes).forEach((destinationSignal, choice) => { - const route = { }; - route[destinationSignal] = routes[destinationSignal]; - - const [_destinationSignal, routeDetails] = unpackRoute(route); - let routeId = routeDetails["route-id"]; + // Object.keys foreach + // -> routeIndex is the index of the route with destination signal `destinationSignal` + // in the collection `routesFromCurrentBlock` + Object.keys(routesFromCurrentBlock).forEach((destinationSignal, routeIndex) => { + let route = {}; + route[destinationSignal] = routesFromCurrentBlock[destinationSignal]; + route['destSignalID'] = destinationSignal; + ///TODO: check if this works (changed/simplified) + const routeId = route[destinationSignal]["route-id"]; updateDestinationAvailabilityPromise( routeId, // route is available - () => setDestinationButtonAvailable(choice, route), + () => setDestinationButtonAvailable(routeIndex, route, destinationSignal), // route is unavailable - () => setDestinationButtonUnavailable(choice, route) + () => setDestinationButtonUnavailable(routeIndex, route, destinationSignal) ); }); }, updatePossibleDestinationsTimeout); } -// Server request for a route's status and then determine whether the -// route is available +// Request for a route's status and then determine whether the route is available function updateDestinationAvailabilityPromise(routeId, available, unavailable) { return $.ajax({ type: 'POST', url: serverAddress + '/monitor/route', crossDomain: true, - data: { - 'route-id': routeId, - }, + data: { 'route-id': routeId }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - const isNotConflicting = responseData.includes("granted conflicting route ids: none"); - const isRouteClear = responseData.includes("route clear: yes"); - const isNotGranted = responseData.includes("granted train: none"); + success: (responseText) => { + const responseJson = JSON.parse(responseText); + const noConflicts = Array.isArray(responseJson.granted_conflicting_route_ids) && + responseJson.granted_conflicting_route_ids.length === 0; + const isClear = responseJson.clear === true; + const isNotGranted = responseJson.granted_to_train === ""; + const isAvailable = noConflicts && isClear && isNotGranted; - const isAvailable = isNotConflicting && isRouteClear && isNotGranted; if (isAvailable) { available(); } else { unavailable(); } }, - error: (responseData, textStatus, errorThrown) => { - setResponseDanger('#serverResponse', - '😢 There was a problem checking the destinations', + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/monitor/route error with returned message:", responseJson.msg); + } else { + console.warn("/monitor/route error with no message, status:", jqXHR.status); + } + unavailable(); + setResponseDanger( + '#serverResponse', + '😢 There was a problem checking the destinations', '😢 Es ist ein Problem beim Überprüfen der Ziele aufgetreten', 'Sorry' ); } }); - } +function setChosenTrain(trainId) { + const imageName = `train-${trainId.replace("_", "-")}.jpg` + $("#chosenTrain").attr("src", imageName); +} /************************************************** * Train speed UI elements @@ -223,41 +219,47 @@ function setResponse(responseId, messageEn, messageDe, callback) { } function speak(text) { - var msg = new SpeechSynthesisUtterance(text); + if (!text) { + // Don't speak if text is not truthy, i.e., undefined, null, or empty. + return; + } + ///TODO: Check if this works, it was `var msg` before. + let speakMsg = new SpeechSynthesisUtterance(text); + //speakMsg.lang = (language == 'en') ? "en-US" : "de-DE"; + ///NOTE: At the moment, all voice messages are in german or "german-ish" (like "Sorry") + speakMsg.lang = "de-DE"; for (const voice of window.speechSynthesis.getVoices()) { if (voice.lang == "de-DE") { - msg.voice = voice; + speakMsg.voice = voice; break; } } - window.speechSynthesis.speak(msg); + window.speechSynthesis.speak(speakMsg); } function setResponseDanger(responseId, messageEn, messageDe, messageSpeak) { - setResponse(responseId, messageEn, messageDe, function() { + setResponse(responseId, messageEn, messageDe, function () { $(responseId).parent().addClass('alert-danger'); $(responseId).parent().addClass('alert-danger-blink'); $(responseId).parent().removeClass('alert-success'); }); - speak(messageSpeak); } function setResponseSuccess(responseId, messageEn, messageDe, messageSpeak) { - setResponse(responseId, messageEn, messageDe, function() { + setResponse(responseId, messageEn, messageDe, function () { $(responseId).parent().removeClass('alert-danger'); $(responseId).parent().removeClass('alert-danger-blink'); $(responseId).parent().addClass('alert-success'); }); - speak(messageSpeak); } const modalMessages = { drivingInfringement: { title: { - de: '👎 Fahrt nicht zulässig!', - en: '👎 Driving Infringement!' + de: 'Unsichere Fahrt! 👎', + en: 'Driving Infringement! 👎' }, body: { de: 'Du hast deinen Zug nicht vor dem Zielsignal gestoppt!



Glücklicherweise konnten wir deinen Zug stoppen, bevor dieser mit einem anderen kollidieren oder die Schienen beschädigen konnte.', @@ -274,8 +276,8 @@ const modalMessages = { en: 'Continue Driving' }, body: { - de: 'Du hast dein Ziel noch nicht erreicht. Bitte fahr weiter 😀', - en: 'You have not yet reached your destination. Please continue driving 😀' + de: 'Du hast dein Ziel noch nicht erreicht. Bitte fahr weiter.', + en: 'You have not yet reached your destination. Please continue driving.' }, button: { de: 'Verstanden', @@ -288,8 +290,8 @@ const modalMessages = { en: 'Destination Reached!' }, body: { - de: '🥳 Du hast deinen Zug zur deiner ausgewählten Station gefahren', - en: '🥳 You drove your train to your chosen destination!' + de: 'Du hast deinen Zug zur deiner ausgewählten Station gefahren! 🥳', + en: 'You drove your train to your chosen destination! 🥳' }, button: { de: 'Super!', @@ -309,7 +311,7 @@ function setModal(message) { $('#serverModalTitle').text(title); $('#serverModalBody').html(body); $('#serverModalButton').text(button); - + let modalElement = document.getElementById('serverModal'); let modal = bootstrap.Modal.getOrCreateInstance(modalElement); modal.show(); @@ -318,14 +320,12 @@ function setModal(message) { function setModalDanger(message, messageSpeak) { setModal(message); $('#serverModal .modal-content').addClass('modal-danger'); - speak(messageSpeak); } function setModalSuccess(message, messageSpeak) { setModal(message); $('#serverModal .modal-content').addClass('modal-success'); - speak(messageSpeak); } @@ -373,16 +373,16 @@ class Driver { drivingTimer = new Timer(); } - reset() { + resetTrainSession() { this.sessionId = 0; this.grabId = -1; } - get hasValidTrainSession() { + hasValidTrainSession() { return (this.sessionId != 0 && this.grabId != -1) } - get hasRouteGranted() { + hasRouteGranted() { return (this.routeDetails != null); } @@ -401,70 +401,86 @@ class Driver { clearInterval(this.destinationReachedInterval); } - // Server request for the train's current block + // Request for the train's current block updateCurrentBlockPromise() { return $.ajax({ type: 'POST', url: serverAddress + '/monitor/train-state', crossDomain: true, - data: { - 'train': this.trainId - }, + data: { 'train': this.trainId }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - const regexMatch = /on block: (.*?) /g.exec(responseData); - this.currentBlock = regexMatch[1]; + success: (responseText) => { + const responseJson = JSON.parse(responseText); + if (responseJson.on_track && Array.isArray(responseJson.occupied_blocks) + && responseJson.occupied_blocks.length > 0) { + this.currentBlock = responseJson.occupied_blocks[0]; + } }, - error: (responseData, textStatus, errorThrown) => { - setResponseDanger('#serverResponse', - '😢 Could not find your train', - '😢 Dein Zug konnte nicht gefunden werden', + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/monitor/train-state error with returned message:", responseJson.msg); + } else { + console.warn("/monitor/train-state error with no message, status:", jqXHR.status); + } + setResponseDanger('#serverResponse', + 'Could not find your train 😢', + 'Dein Zug konnte nicht gefunden werden 😢', '' ); } }); } - // Server request for a train's status and then execute the callback handlers - trainIsAvailablePromise(trainId, success, error) { + + // Request for a train's status and then execute the callback handlers + trainIsAvailablePromise(trainId, successCallback, errorCallback) { return $.ajax({ type: 'POST', url: serverAddress + '/monitor/train-state', crossDomain: true, data: { 'train': trainId }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - if (responseData.includes("grabbed: no") && !responseData.includes("on segment: no")) { - success(); + success: (responseText) => { + const responseJson = JSON.parse(responseText); + if (responseJson.grabbed === false && responseJson.on_track === true) { + successCallback(); } else { - error(); + errorCallback(); } }, - error: (responseData, textStatus, errorThrown) => { - // Do nothing + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/monitor/train-state error with returned message:", responseJson.msg); + } else { + console.warn("/monitor/train-state error with no message, status:", jqXHR.status); + } + errorCallback(); } }); } + // Update the styling of the train selection buttons based on the train availabilities updateTrainAvailability() { $('.selectTrainButton').prop("disabled", true); - + const trainAvailabilityTimeout = 1000; this.trainAvailabilityInterval = setInterval(() => { - console.log("Checking available trains ... "); // Enable a train if it is on the tracks and has not been grabbed $('.selectTrainButton').each((index, obj) => { let trainId = obj.id; this.trainIsAvailablePromise( trainId, - () => { + // Success Callback + () => { $(obj).prop("disabled", false); $(obj).removeClass("btn-danger"); $(obj).addClass("btn-primary"); $($(obj).parent(".card-body").parent(".card")).removeClass("unavailableTrain"); - + function setVisibility(isShow) { if (isShow) { return ""; @@ -472,22 +488,23 @@ class Driver { return "style='display: none;'"; } } - $(obj).children().each(function() { + $(obj).children().each(function () { switch ($(this).attr('lang')) { case "de": $(this).html(`Klicke um den Zug zu fahrenZug fahren`); - break; + break; case "en": $(this).html(`Click to drive this trainDrive this train`); - break; + break; } }); }, + // Error Callback () => { $(obj).prop("disabled", true); $(obj).removeClass("btn-primary"); $(obj).addClass("btn-danger"); $($(obj).parent(".card-body").parent(".card")).addClass("unavailableTrain"); - $(obj).children().each(function() { - switch($(this).attr('lang')){ + $(obj).children().each(function () { + switch ($(this).attr('lang')) { case "de": $(this).text("Nicht verfügbar"); break; case "en": $(this).text("Unavailable"); break; } @@ -499,7 +516,7 @@ class Driver { } - // Server request to grab a train + // Request to grab a train grabTrainPromise() { return $.ajax({ type: 'POST', @@ -510,24 +527,34 @@ class Driver { 'engine': this.trainEngine }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - const responseDataSplit = responseData.split(','); - this.sessionId = responseDataSplit[0]; - this.grabId = responseDataSplit[1]; - - setResponseSuccess('#serverResponse', '😁 Your train is ready', '😁 Dein Zug ist bereit', ''); + success: (responseText) => { + const responseJson = JSON.parse(responseText); + this.sessionId = responseJson['session-id']; + this.grabId = responseJson['grab-id']; + setResponseSuccess('#serverResponse', + 'Your train is ready 😁', + 'Dein Zug ist bereit 😁', + '' + ); }, - error: (responseData, textStatus, errorThrown) => { - setResponseDanger('#serverResponse', - '😢 There was a problem starting your train', - '😢 Es ist ein Problem beim starten deines Zuges aufgetreten', + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/driver/grab-train error with returned message:", responseJson.msg); + } else { + console.warn("/driver/grab-train error with no message, status:", jqXHR.status); + } + setResponseDanger('#serverResponse', + 'There was a problem starting your train 😢', + 'Es ist ein Problem beim Starten deines Zuges aufgetreten 😢', '' ); } }); } - // Server request for the train's physical driving direction of its granted route + + // Request for the train's physical driving direction of its granted route updateDrivingDirectionPromise() { return $.ajax({ type: 'POST', @@ -538,16 +565,28 @@ class Driver { 'route-id': this.routeDetails["route-id"] }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - this.drivingIsForwards = responseData.includes("forwards"); + success: (responseText) => { + const responseJson = JSON.parse(responseText); + this.drivingIsForwards = responseJson.direction === "forwards"; }, - error: (responseData, textStatus, errorThrown) => { - setResponseDanger('#serverResponse', '😢 Could not find your train', '😢 Dein Zug konnte nicht gefunden werden', ''); + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/driver/direction error with returned message:", responseJson.msg); + } else { + console.warn("/driver/direction error with no message, status:", jqXHR.status); + } + setResponseDanger('#serverResponse', + 'Could not find your train 😢', + 'Dein Zug konnte nicht gefunden werden 😢', + '' + ); } }); } - // Server request to set the train's speed + + // Request to set the train's speed setTrainSpeedPromise(speed) { return $.ajax({ type: 'POST', @@ -560,17 +599,24 @@ class Driver { 'track-output': this.trackOutput }, dataType: 'text', - error: (responseData, textStatus, errorThrown) => { - setResponseDanger('#serverResponse', - '😢 There was a problem setting the speed of your train', - '😢 Es ist ein Problem beim Einstellen der Geschwindigkeit deines Zuges aufgetreten', + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/driver/set-dcc-train-speed error with returned message:", responseJson.msg); + } else { + console.warn("/driver/set-dcc-train-speed error with no message, status:", jqXHR.status); + } + setResponseDanger('#serverResponse', + 'There was a problem setting the speed of your train 😢', + 'Es ist ein Problem beim Einstellen der Geschwindigkeit aufgetreten 😢', '' ); } }); } - // Server request for the train's current segment and then determine + + // Request for the train's current segment and then determine // whether to show the destination reached button enableDestinationReachedPromise() { const destinationReachedTimeout = 100; @@ -579,44 +625,59 @@ class Driver { type: 'POST', url: serverAddress + '/monitor/train-state', crossDomain: true, - data: { - 'train': this.trainId - }, + data: { 'train': this.trainId }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - const matches = /on segment: (.*?) -/g.exec(responseData); // Get all Segment IDS as String - const segmentIDs = matches[1]; - const segments = segmentIDs.split(", "); // Splits them into Array - - // Show the destination reached button when the train is only on the - // main segment of the destination - // Take into account that a main segment could be split into a/b segments + success: (responseText) => { + const responseJson = JSON.parse(responseText); + if (!(responseJson.on_track) || !Array.isArray(responseJson.occupied_segments)) { + return; + } + + const segmentIDs = responseJson.occupied_segments; + const segments = segmentIDs.map(s => s.replace(/(a|b)$/, '')); + + ///NOTE: Adjusted this to enable DestinationReached, when the train + // occupies the expected destination main segment (this.routeDetails['segment']) + // AND the train occupies 3 segments or less. + // Previously, DestinationReached would only be enabled if the train + // *exclusively* occupied the destination main segment. This was problematic + // for routes with a short main segment on the block at end of the route. + // Drivers were getting a warning for stopping, even though they were already + // close to the destination signal. + ///TODO: Test if this can be simplified by `&& this.routeDetails['segment'] in segments` + if (segments.length <= 3) { + for (let index in segments) { + if (segments[index] === this.routeDetails['segment']) { + this.clearDestinationReachedInterval(); + this.isDestinationReached = true; + $('#endGameButton').show(); + $(window).unbind('beforeunload', pageRefreshWarning); + return; + } + } + } + /* OLD VERSION - SEE NOTE ABOVE for (let index in segments) { - segments[index] = segments[index].replace(/(a|b)$/, ''); if (segments[index] != this.routeDetails['segment']) { return; } } - - if(segments[0] == this.routeDetails['segment']) { + if (segments[0] == this.routeDetails['segment']) { this.clearDestinationReachedInterval(); this.isDestinationReached = true; $('#endGameButton').show(); - - // The page can be refreshed without ill consequences. - // The train will stop sensibly on the main segment of the destination $(window).unbind('beforeunload', pageRefreshWarning); - } + }*/ } }); }, destinationReachedTimeout); } - + disableDestinationReached() { this.isDestinationReached = false; } - // Server request to release the train + // Request to release the train releaseTrainPromise() { return $.ajax({ type: 'POST', @@ -627,48 +688,65 @@ class Driver { 'grab-id': this.grabId }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - this.reset(); + success: (_responseText) => { + console.log("Train was released: ", this.trainId); + this.resetTrainSession(); this.trainId = null; }, - error: (responseData, textStatus, errorThrown) => { - setResponseDanger('#serverResponse', - '🤔 There was a problem ending your turn', - '🤔 Es ist ein Problem beim Beenden deiner Runde aufgetreten', + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/driver/release-train error with returned message:", responseJson.msg); + } else { + console.warn("/driver/release-train error with no message, status:", jqXHR.status); + } + setResponseDanger('#serverResponse', + 'There was a problem ending your turn 🤔', + 'Es ist ein Problem beim Beenden deiner Runde aufgetreten 🤔', '' ); } }); } - // Server request for a specific route ID - requestRouteIdPromise(routeDetails) { + // Request for a specific route ID + requestRouteIdPromise(pRouteDetails) { return $.ajax({ type: 'POST', - url: serverAddress + '/driver/request-route-id', + url: serverAddress + '/driver/request-route-by-id', crossDomain: true, data: { 'session-id': this.sessionId, 'grab-id': this.grabId, - 'route-id': routeDetails['route-id'] + 'route-id': pRouteDetails['route-id'] }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - this.routeDetails = routeDetails; - setResponseSuccess('#serverResponse', - '🥳 Start driving your train to your chosen destination', - '🥳 Fahr deinen Zug zum ausgewählten Ziel', + success: (_responseText) => { + this.routeDetails = pRouteDetails; + setResponseSuccess('#serverResponse', + 'Start driving your train to your chosen destination 🥳', + 'Fahr deinen Zug zum ausgewählten Ziel 🥳', '' ); }, - error: (responseData, textStatus, errorThrown) => { + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/driver/request-route-by-id error with returned message:", responseJson.msg); + } else { + console.warn("/driver/request-route-by-id error with no message, status:", jqXHR.status); + } this.routeDetails = null; - setResponseDanger('#serverResponse', "This route is not available", "Diese Route ist zurzeit leider nicht verfügbar", 'Sorry'); + setResponseDanger('#serverResponse', + 'This route is currently not available', + 'Diese Route ist zurzeit leider nicht verfügbar', + 'Sorry' + ); } }); } - // Server request to manually drive the granted route + // Request to manually drive the granted route driveRoutePromise() { return $.ajax({ type: 'POST', @@ -681,38 +759,60 @@ class Driver { 'mode': 'manual' }, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - if (!this.hasValidTrainSession) { - // Ignore, driver has ended their trip - } else if (!this.hasRouteGranted) { - setModalSuccess(modalMessages.drivingSuccess, 'Juhuu!!'); + success: (_responseText) => { + if (!this.hasValidTrainSession()) { + // Driver has already released the train + console.log("driveRoutePromise called, success, but no valid train session."); + } else if (!this.hasRouteGranted()) { + // if (!this.hasRouteGranted()) is true when driveRoute returns from server, + // that means the *client* (aka the state in this driver object) has already + // released the route explicitly (which is done if the client clicks on "stop" + // whilst on the "destination segment"). Meaning, the driver *has* stopped + // the train, and driveRoute has now returned from the server -> this means + // that the driver has most likely stopped the train before the server had to + // stop it (server stops train if the destination signal is passed). + setModalSuccess(modalMessages.drivingSuccess, 'Juhuu!'); } else { + // driveRoute returns, and driver has valid train session, and the *client* + // (aka the state in this driver object) "thinks" that the route has NOT + // been released yet. Means, the driver has not pressed "stop" whilst on the + // "destination segment", otherwise the client would have released the route + // already. So, the server must have stopped the train when it passed the + // destination signal. + // Note that the server then automatically has already released the route, + // so calling that again is not necessary. this.routeDetails = null; - - // Copy the driving infringement message and fill in the destination flag + const destinationClass = $('#destination').attr('class'); let drivingInfringement = JSON.parse(JSON.stringify(modalMessages.drivingInfringement)); drivingInfringement['body']['de'] = drivingInfringement['body']['de'].replace('${destination}', destinationClass); drivingInfringement['body']['en'] = drivingInfringement['body']['en'].replace('${destination}', destinationClass); - setModalDanger(drivingInfringement, 'STOP, STOP, STOP'); + setModalDanger(drivingInfringement, 'Zielsignal verpasst!'); } }, - error: (responseData, textStatus, errorThrown) => { - setResponseDanger('#serverResponse', - '😢 Route to your chosen destination is unavailable', - '😢 Die Route zu deinem ausgewählten Ziel ist aktuell nicht verfügbar', + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/driver/drive-route error with returned message:", responseJson.msg); + } else { + console.warn("/driver/drive-route error with no message, status:", jqXHR.status); + } + setResponseDanger('#serverResponse', + 'Route to your chosen destination is currently unavailable 😢', + 'Die Route zu deinem ausgewählten Ziel ist aktuell nicht verfügbar 😢', 'Sorry' ); } }); } - // Server request to release the granted route + + // Request to release the granted route releaseRoutePromise() { - if (!this.hasRouteGranted) { + if (!this.hasRouteGranted()) { return; } - + const routeId = this.routeDetails["route-id"]; this.routeDetails = null; @@ -721,34 +821,50 @@ class Driver { url: serverAddress + '/controller/release-route', crossDomain: true, data: { 'route-id': routeId }, - dataType: 'text' + dataType: 'text', + error: (jqXHR) => { + if ('msg' in jqXHR.responseText) { + const responseJson = JSON.parse(responseText); + console.warn("/controller/release-route error with returned message:", responseJson.msg); + } else { + console.warn("/controller/release-route error with no message, status:", jqXHR.status); + } + } }); } + // Manage the business logic of manually driving a granted route async driveToPromise(route) { - if (!this.hasValidTrainSession) { - setResponseDanger('#serverResponse', '😢 Could not find your train', '😢 Dein Zug konnte nicht gefunden werden', ''); + if (!this.hasValidTrainSession()) { + setResponseDanger('#serverResponse', + 'Could not find your train 😢', + 'Dein Zug konnte nicht gefunden werden 😢', + '' + ); return; } - + const lock = await Mutex.lock(); - - if (this.hasRouteGranted) { + + if (this.hasRouteGranted()) { + // If train already has a route, return. Mutex.unlock(lock); return; } - const [destinationSignal, routeDetails] = unpackRoute(route); - console.log(routeDetails); - - this.requestRouteIdPromise(routeDetails) // 1. Ensure that the chosen destination is still available + const destinationSignal = route['destSignalID']; + const pRouteDetails = route[destinationSignal]; + console.log("driveToPromise: driving a route to signal: ", destinationSignal); + + // Note: this.routeDetails is set to pRouteDetails in this.requestRouteIdPromise success handler! + this.requestRouteIdPromise(pRouteDetails) // 1. Ensure that the chosen destination is still available .then(() => this.updateDrivingDirectionPromise()) // 2. Obtain the physical driving direction .then(() => $('#destinationsForm').hide()) // 3. Prevent the driver from choosing another destination .then(() => disableAllDestinationButtons()) .then(() => Mutex.unlock(lock)) .then(() => this.setTrainSpeedPromise(1)) // 4. Update the train lights to indicate the physical driving direction - .then(() => wait(1)) + .then(() => wait(1)) // 1ms .then(() => this.setTrainSpeedPromise(0)) .then(() => setChosenDestination(destinationSignal)) // 5. Show the chosen destination and possible train speeds to the driver .then(() => enableSpeedButtons(destinationSignal)) @@ -760,7 +876,7 @@ class Driver { .then(() => clearChosenDestination()) .then(() => { - if (!this.hasValidTrainSession) { // 9. Check whether the driver has quit their session + if (!this.hasValidTrainSession()) { // 9. Check whether the driver has quit their session this.clearDestinationReachedInterval(); throw new Error("Driver ended their session"); } @@ -784,12 +900,16 @@ function startGameLogic() { // FIXME: On iOS, speech synthesis only works if it is first triggered by the user. speak(""); - if (driver.hasValidTrainSession) { - setResponseDanger('#serverResponse', 'You are already driving a train!', 'Du fährst aktuell schon einen Zug!', 'STOP!') + if (driver.hasValidTrainSession()) { + setResponseDanger('#serverResponse', + 'You are already driving a train!', + 'Du fährst aktuell schon einen Zug!', + '' + ); return; } - setResponseSuccess('#serverResponse', '⏳ Waiting ...', '⏳ Warten ...'); + setResponseSuccess('#serverResponse', '⏳ Waiting ...', '⏳ Warten ...', ''); driver.grabTrainPromise() .then(() => $('#trainSelection').hide()) @@ -806,7 +926,7 @@ function endGameLogic() { $('#destinationsForm').hide(); driver.clearUpdatePossibleDestinationsInterval(); driver.clearDestinationReachedInterval(); - driver.reset(); + driver.resetTrainSession(); disableAllDestinationButtons(); driver.disableDestinationReached(); disableSpeedButtons(); @@ -852,7 +972,7 @@ function initialise() { language = 'de'; $('span:lang(de):not(span span)').show(); $('span:lang(en):not(span span)').hide(); - + // Handle language selection. $('#changeLang').click(function () { language = (language == 'en') ? 'de' : 'en'; @@ -864,7 +984,7 @@ function initialise() { isEasyMode = false; $('.normal').show(); $('.easy').hide(); - + // Handle the verbosity selection. $('#changeTips').click(function (event) { isEasyMode = !isEasyMode @@ -897,7 +1017,11 @@ function initialise() { // Should check if button is enabled here if (destinationButton.hasClass("flagThemeDisabled")) { console.log("Route Driving was not attempted because it is disabled"); - setResponseDanger('#serverResponse', "This route is not available", "Diese Route ist zurzeit leider nicht verfügbar", 'Sorry'); + setResponseDanger('#serverResponse', + 'This route is currently not available', + 'Diese Route ist zurzeit leider nicht verfügbar', + '' + ); } else { const route = JSON.parse(destinationButton.val()); driver.driveToPromise(route); @@ -907,7 +1031,9 @@ function initialise() { disableSpeedButtons(); - // Initialise the click handler of each speed button. + // Initialise the click handler of each speed button. + // `speed` here matching the button IDs, as you can see in the definition of `speedButtons` + // and the HTML file. speedButtons.forEach(speed => { const speedButton = $(`#${speed}`); speedButton.click(function () { @@ -916,6 +1042,8 @@ function initialise() { $('#endGameButton').hide(); if (speedButton.val() == '0') { + // Driver has stopped the train, but has not reached the destination segment yet. + // -> tell the driver to continue driving. setModal(modalMessages.drivingContinue); } @@ -923,28 +1051,33 @@ function initialise() { // The train might not stop sensibly on the main segment of the destination $(window).bind("beforeunload", pageRefreshWarning); } else if (speedButton.val() == '0') { + // Destination is reached and driver stops the train -> release route. driver.releaseRoutePromise(); } }); }); $('#endGameButton').click(function () { - if (!driver.hasValidTrainSession) { + if (!driver.hasValidTrainSession()) { endGameLogic(); return; } - setResponseSuccess('#serverResponse', '⏳ Waiting ...', '⏳ Warten ...'); + setResponseSuccess('#serverResponse', 'Waiting... ⏳', 'Warten...⏳', ''); driver.setTrainSpeedPromise(0) - .then(() => wait(500)) + .then(() => wait(500)) // 500 ms .always(() => { driver.releaseTrainPromise(); driver.releaseRoutePromise(); endGameLogic(); }); - setResponseSuccess('#serverResponse', '😀 Thank you for playing', '😀 Danke fürs Spielen', 'Danke fürs Spielen'); + setResponseSuccess('#serverResponse', + 'Thank you for playing 😀', + 'Danke fürs Spielen 😀', + 'Danke fürs Spielen' + ); }); } @@ -960,12 +1093,18 @@ $(document).ready(() => { // Before page unload (refresh or close) behaviour function pageRefreshWarning(event) { event.preventDefault(); - console.log("Before unloading"); - // Most web browsers will display a generic message instead!! - const message = "Are you sure you want to refresh or leave this page? " + - "Leaving this page without ending your game will prevent others from grabbing your train 😕"; - return event.returnValue = message; + let warnmessage = ""; + if (language == 'de') { + warnmessage = "Bist du dir sicher, dass du die Seite neu laden oder verlassen willst? " + + "Das Verlassen oder neu Laden wird den Zug unverfügbar machen! " + + "Bitte nur dann durchführen, wenn du darum gebeten wirst."; + } else { + warnmessage = "Are you sure you want to refresh or leave this page? " + + "Leaving this page without ending your game will prevent others from grabbing your train. " + + "Please only do so if requested to."; + } + return event.returnValue = warnmessage; } // During page unload (refresh or close) behaviour @@ -973,12 +1112,12 @@ function pageRefreshWarning(event) { // Promises will not be executed! $(window).on("unload", (event) => { console.log("Unloading"); - if (driver.hasRouteGranted) { + if (driver.hasRouteGranted()) { const formData = new FormData(); formData.append('route-id', driver.routeDetails['route-id']); navigator.sendBeacon(serverAddress + '/controller/release-route', formData); } - if (driver.hasValidTrainSession) { + if (driver.hasValidTrainSession()) { const formData = new FormData(); formData.append('session-id', driver.sessionId); formData.append('grab-id', driver.grabId); diff --git a/server/src/assets/script.js b/server/src/assets/script.js index 917e0f83..f35a986a 100644 --- a/server/src/assets/script.js +++ b/server/src/assets/script.js @@ -7,6 +7,80 @@ var verificationObj = {}; var trainIsForwards = true; var lastSetSpeed = 0; // Only needed when swapping train direction +var customCode_500ServerUnableReplyMsg = {500: "Server unable to build reply message"}; + +const speeds = [ + "0", + "10", + "20", + "30", + "40", + "50", + "60", + "70", + "80", + "90", + "100", + "110", + "120", + "126" +] + +// Helpers + +function showInfoInElem(elementID, textToShow) { + $(elementID).parent().removeClass('alert-danger'); + $(elementID).parent().addClass('alert-success'); + $(elementID).text(textToShow); +} + +function showErrorInElem(elementID, textToShow) { + $(elementID).parent().removeClass('alert-success'); + $(elementID).parent().addClass('alert-danger'); + $(elementID).text(textToShow); +} + +function getErrorMessage(jqXHR, customCodes = {}) { + try { + if (jqXHR.responseText.length > 0) { + const respJson = JSON.parse(jqXHR.responseText); + // If response has a "msg" field, that's the error msg (by convention in swtbahn-cli) + if (respJson.msg) { + return respJson.msg; + } + } + } catch (parseError) { + console.log("Unable to parse jqXHR to JSON."); + } + + // Check if customCodes contains the status code + if (customCodes.hasOwnProperty(jqXHR.status)) { + return customCodes[jqXHR.status]; + } + + // Fallback to default messages + switch (jqXHR.status) { + case 405: + return "Method not allowed"; + case 503: + return "SWTbahn not running"; + default: + return `${jqXHR.status} - Error message not defined`; + } +} + +//From https://github.com/eligrey/FileSaver.js/wiki/FileSaver.js-Example +function SaveAsFile(content, filename, contentTypeOptions) { + try { + var b = new Blob([content], { type: contentTypeOptions }); + saveAs(b, filename); + } catch (e) { + console.log("SaveAsFile Failed to save the file. Error msg: " + e); + } +} + +// State Updates + function updateTrainIsForwards() { $.ajax({ type: 'POST', @@ -14,504 +88,716 @@ function updateTrainIsForwards() { crossDomain: true, data: { 'train': trainId }, dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - trainIsForwards = responseData.includes('direction: forwards'); + success: function (responseText) { + const responseJson = JSON.parse(responseText); + trainIsForwards = responseJson.direction === 'forwards'; } }); } function updateTrainGrabbedState() { + // Called from client.html - TODO: Test if the return is needed. return $.ajax({ - type: 'POST', + type: 'GET', url: '/monitor/trains', crossDomain: true, - data: null, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - const trains = responseData.split(/\r?\n|\r|\n/g); - trains.forEach((train) => { - const trainId = train.match(/^\w+_\w+/g)[0]; - const isGrabbed = train.includes('yes'); - + success: function (responseText) { + const responseJson = JSON.parse(responseText); + responseJson['trains'].forEach((train) => { + const trainId = train.id; + const isGrabbed = train.grabbed; if (isGrabbed) { $(`#releaseTrainButton_${trainId}`).show(); } else { $(`#releaseTrainButton_${trainId}`).hide(); } }); - }, - error: (responseData, textStatus, errorThrown) => { - // Do nothing } }); } function updateGrantedRoutes(htmlElement) { return $.ajax({ - type: 'POST', + type: 'GET', url: '/monitor/granted-routes', crossDomain: true, - data: null, dataType: 'text', - success: (responseData, textStatus, jqXHR) => { + success: function (responseText) { + const responseJson = JSON.parse(responseText); htmlElement.empty(); - - if (responseData.includes('No granted routes')) { + if (!responseJson['granted-routes'] || responseJson['granted-routes'].length === 0) { htmlElement.html('
  • No granted routes
  • '); return; - } - - const routes = responseData.split(/\r?\n|\r|\n/g); - routes.forEach((route) => { - const routeId = route.match(/\d+/g)[0]; - const trainId = route.match(/\w+_\w+$/g)[0]; - + } + responseJson['granted-routes'].forEach((route) => { + const routeId = route['id']; + const trainId = route.train; const routeText = `route ${routeId} granted to ${trainId}`; const releaseButton = ``; htmlElement.append(`
  • ${routeText} ${releaseButton}
  • `); }); - $('.grantedRoute').click(function (event) { adminReleaseRoute(event.currentTarget.value); }); + } + }); +} + +// Connectivity + +function pingServer () { + $('#pingResponse').text('Waiting'); + $.ajax({ + type: 'GET', + url: '/', + crossDomain: true, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#pingResponse", "OK"); + }, + error: function (jqXHR) { + showErrorInElem("#pingResponse", "Error"); + } + }); +} + +// Startup/Shutdown + +function startupServer () { + $('#startupShutdownResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/admin/startup', + crossDomain: true, + data: null, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#startupShutdownResponse", "Success"); + }, + error: function (jqXHR) { + showErrorInElem("#startupShutdownResponse", getErrorMessage(jqXHR)); + } + }); +} + +function shutdownServer () { + $('#startupShutdownResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/admin/shutdown', + crossDomain: true, + data: null, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#startupShutdownResponse", "Success"); + // Reset SessionId and GrabId on shutdown + sessionId = 0; + grabId = -1; + $('#sessionGrabId').text('Session ID: ' + sessionId + ', Grab ID: ' + grabId); + }, + error: function (jqXHR) { + showErrorInElem("#startupShutdownResponse", getErrorMessage(jqXHR)); + } + }); +} + +// Train Driver + +function grabTrain () { + $('#grabTrainResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/driver/grab-train', + crossDomain: true, + data: { 'train': trainId, 'engine': trainEngine }, + dataType: 'text', + success: function (responseText) { + const responseJson = JSON.parse(responseText); + sessionId = responseJson['session-id']; + grabId = responseJson['grab-id']; + $('#sessionGrabId').text('Session ID: ' + sessionId + ', Grab ID: ' + grabId); + showInfoInElem("#grabTrainResponse", "Grabbed"); + updateTrainIsForwards(); + }, + error: function (jqXHR) { + showErrorInElem("#grabTrainResponse", getErrorMessage(jqXHR)); + } + }); +} + +function releaseTrain () { + $('#grabTrainResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/driver/release-train', + crossDomain: true, + data: { 'session-id': sessionId, 'grab-id': grabId }, + dataType: 'text', + success: function (_responseText) { + sessionId = 0; + grabId = -1; + $('#sessionGrabId').text('Session ID: ' + sessionId + ', Grab ID: ' + grabId); + showInfoInElem("#grabTrainResponse", "Released"); + }, + error: function (jqXHR) { + showErrorInElem("#grabTrainResponse", getErrorMessage(jqXHR)); + } + }); +} + +function setTrainSpeedDCC (speed) { + $('#driveTrainResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/driver/set-dcc-train-speed', + crossDomain: true, + data: { + 'session-id': sessionId, + 'grab-id': grabId, + 'speed': speed, + 'track-output': trackOutput + }, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#driveTrainResponse", 'DCC train speed set to ' + speed); + lastSetSpeed = speed; + }, + error: function (jqXHR) { + showErrorInElem("#driveTrainResponse", getErrorMessage(jqXHR)); + } + }); +} + +function requestRoute (source, destination) { + $.ajax({ + type: 'POST', + url: '/driver/request-route', + crossDomain: true, + data: { + 'session-id': sessionId, + 'grab-id': grabId, + 'source': source, + 'destination': destination + }, + dataType: 'text', + success: function (responseText) { + const responseJson = JSON.parse(responseText); + showInfoInElem("#routeResponse", + 'Route ' + responseJson['granted-route-id'] + ' granted'); + $('#routeId').val(responseJson['granted-route-id']); + }, + error: function (jqXHR) { + showErrorInElem("#routeResponse", getErrorMessage(jqXHR)); + } + }); +} + +function getInterlockerThenRequestRoute (source, destination) { + $('#routeResponse').text('Waiting'); + $.ajax({ + type: 'GET', + url: '/controller/get-interlocker', + crossDomain: true, + dataType: 'text', + success: function (_responseText) { + requestRoute(source, destination); + }, + error: function (jqXHR) { + showErrorInElem("#routeResponse", getErrorMessage(jqXHR)); + } + }); +} + +function driveRouteIntern (routeId, mode) { + $.ajax({ + type: 'POST', + url: '/driver/drive-route', + crossDomain: true, + data: { + 'session-id': sessionId, + 'grab-id': grabId, + 'route-id': routeId, + 'mode': mode + }, + dataType: 'text', + success: function (responseText) { + const responseJson = JSON.parse(responseText); + showInfoInElem("#routeResponse", responseJson['msg']); + $('#routeId').val("None"); }, - error: (responseData, textStatus, errorThrown) => { - // Do nothing + error: function (jqXHR) { + showErrorInElem("#routeResponse", getErrorMessage(jqXHR)); } }); } -// Admin control of granted routes +function driveRoute(routeId, mode) { + if (isNaN(routeId)) { + showErrorInElem("#routeResponse", 'Route "' + routeId + '" is not a number!'); + } else if (sessionId != 0 && grabId != -1) { + driveRouteIntern(routeId, mode); + } else { + showErrorInElem("#routeResponse", "No train grabbed!"); + } +} + +// Admin + function adminReleaseRoute(routeId) { $('#routeId').val(routeId); $('#releaseRouteButton').click(); } +function adminSetTrainSpeed (trainId, speed) { + $.ajax({ + type: 'POST', + url: '/admin/set-dcc-train-speed', + crossDomain: true, + data: { + 'train': trainId, + 'speed': speed, + 'track-output': trackOutput + }, + dataType: 'text', + success: function (_responseText) { + console.log("adminSetTrainSpeed succeeded."); + }, + error: function (jqXHR) { + console.log(`adminSetTrainSpeed failed: ${jqXHR.status} - ${getErrorMessage(jqXHR)}`); + } + }); +} + +function adminReleaseTrain (trainId) { + return $.ajax({ + type: 'POST', + url: '/admin/release-train', + crossDomain: true, + data: { + 'train': trainId + }, + dataType: 'text', + success: function (_responseText) { + console.log("adminReleaseTrain succeeded."); + }, + error: function (jqXHR) { + console.log("adminReleaseTrain failed."); + console.log(`adminReleaseTrain failed: ${jqXHR.status} - ${getErrorMessage(jqXHR)}`); + } + }); +} + +// Controller + +function releaseRoute (routeId) { + $.ajax({ + type: 'POST', + url: '/controller/release-route', + crossDomain: true, + data: { 'route-id': routeId }, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#routeResponse", 'Route ' + routeId + ' released'); + $('#routeId').val("None"); + }, + error: function (jqXHR) { + showErrorInElem("#routeResponse", getErrorMessage(jqXHR)); + } + }); +} + +function setPoint (pointId, pointPosition) { + $('#setPointResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/controller/set-point', + crossDomain: true, + data: { 'point': pointId, 'state': pointPosition }, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#setPointResponse", 'Point ' + pointId + ' set to ' + pointPosition); + }, + error: function (jqXHR) { + showErrorInElem("#setPointResponse", getErrorMessage(jqXHR, {404: "Unknown Point"})); + } + }); +} + +function setSignal (signalId, signalAspect) { + $('#setSignalResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/controller/set-signal', + crossDomain: true, + data: { 'signal': signalId, 'state': signalAspect }, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#setSignalResponse", 'Signal ' + signalId + ' set to ' + signalAspect); + }, + error: function (jqXHR) { + showErrorInElem("#setSignalResponse", getErrorMessage(jqXHR, {404: "Unknown Signal"})); + } + }); +} + +function setPeripheralState (peripheralId, peripheralAspect) { + $('#setPeripheralResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/controller/set-peripheral', + crossDomain: true, + data: { 'peripheral': peripheralId, 'state': peripheralAspect }, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#setPeripheralResponse", + 'Peripheral ' + peripheralId + ' set to ' + peripheralAspect); + }, + error: function (jqXHR) { + showErrorInElem("#setPeripheralResponse", getErrorMessage(jqXHR)); + } + }); +} + +// Engine and Interlocker + +function uploadEngine (file) { + $('#uploadResponse').text('Waiting'); + var formData = new FormData(); + formData.append('file', file); + $.ajax({ + type: 'POST', + url: '/upload/engine', + crossDomain: true, + data: formData, + processData: false, + contentType: false, + cache: false, + dataType: 'text', + success: function (_responseText) { + console.log("Upload Success, refreshing engines"); + refreshEnginesList(); + $('#verificationLogDownloadButton').hide(); + $('#clearVerificationMsgButton').hide(); + showInfoInElem("#uploadResponse", 'Engine ' + file.name + ' ready for use'); + }, + error: function (jqXHR) { + console.log("Engine Upload Failed"); + try { + var resJson = JSON.parse(jqXHR.responseText, null, 2); + var msg = "Server Message: " + resJson["msg"]; + msg += "\nList of Properties:" + resJson["verifiedproperties"].forEach(element => { + msg += "\n-" + element["property"]["name"] + ": " + element["verificationmessage"] + }); + verificationObj = resJson; + showErrorInElem("#uploadResponse", msg); + $('#verificationLogDownloadButton').show(); + } catch (e) { + console.log("Unable to parse server's reply in upload-engine failure case: " + e); + showErrorInElem("#uploadResponse", getErrorMessage(jqXHR)); + } + $('#clearVerificationMsgButton').show(); + } + }); +} + +function refreshEnginesList() { + $('#refreshRemoveEngineResponse').text('Waiting'); + $.ajax({ + type: 'GET', + url: '/monitor/engines', + crossDomain: true, + dataType: 'text', + success: function (responseText) { + const responseJson = JSON.parse(responseText); + var engineList = responseJson['engines']; + + var selectedGrabEngine = $("#grabEngine"); + var selectAvailableEngines = $("#availableEngines"); + + selectedGrabEngine.empty(); + selectAvailableEngines.empty(); + + $.each(engineList, function (key, value) { + selectedGrabEngine.append(new Option(value)); + selectAvailableEngines.append(new Option(value)); + }); + + showInfoInElem("#refreshRemoveEngineResponse", "Refreshed list of train engines"); + }, + error: function (jqXHR) { + showErrorInElem("#refreshRemoveEngineResponse", + getErrorMessage(jqXHR, customCode_500ServerUnableReplyMsg)); + } + }); +} + +function removeEngine (engineName) { + $('#refreshRemoveEngineResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/upload/remove-engine', + crossDomain: true, + data: { 'engine-name': engineName }, + dataType: 'text', + success: function (_responseText) { + console.log("Engine removal successful, now auto-updating available engines."); + refreshEnginesList(); + showInfoInElem("#refreshRemoveEngineResponse", 'Engine ' + engineName + ' removed'); + }, + error: function (jqXHR) { + showErrorInElem("#refreshRemoveEngineResponse", getErrorMessage(jqXHR)); + } + }); +} + +function uploadInterlocker(file) { + var formData = new FormData(); + formData.append('file', file); + $('#uploadResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/upload/interlocker', + crossDomain: true, + data: formData, + processData: false, + contentType: false, + cache: false, + dataType: 'text', + success: function (_responseText) { + console.log("Successfuly uploaded interlocker, now refreshing interlocker list."); + refreshInterlockersList(); + showInfoInElem("#uploadResponse", 'Interlocker ' + file.name + ' ready for use'); + }, + error: function (jqXHR) { + showErrorInElem("#uploadResponse", getErrorMessage(jqXHR)); + } + }); +} + +function refreshInterlockersList() { + $('#refreshRemoveInterlockerResponse').text('Waiting'); + $.ajax({ + type: 'GET', + url: '/monitor/interlockers', + crossDomain: true, + dataType: 'text', + success: function (responseText) { + const responseJson = JSON.parse(responseText); + var interlockerList = responseJson['interlockers']; + + var selectAvailableInterlockers = $("#availableInterlockers"); + selectAvailableInterlockers.empty(); + $.each(interlockerList, function (key, value) { + selectAvailableInterlockers.append(new Option(value)); + }); + + showInfoInElem("#refreshRemoveInterlockerResponse", "Refreshed list of interlockers"); + }, + error: function (jqXHR) { + showErrorInElem("#refreshRemoveInterlockerResponse", + 'Unable to refresh list of interlockers: ' + + getErrorMessage(jqXHR, customCode_500ServerUnableReplyMsg)); + } + }); +} + +function removeInterlocker (interlockerName) { + $('#refreshRemoveInterlockerResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/upload/remove-interlocker', + crossDomain: true, + data: { 'interlocker-name': interlockerName }, + dataType: 'text', + success: function (_responseText) { + console.log("Removed interlocker success, now refreshing interlocker list"); + refreshInterlockersList(); + showInfoInElem("#refreshRemoveInterlockerResponse", + 'Interlocker ' + interlockerName + ' removed'); + }, + error: function (jqXHR) { + showErrorInElem("#refreshRemoveInterlockerResponse", getErrorMessage(jqXHR)); + } + }); +} + +function setInterlocker (interlockerName) { + $('#refreshRemoveInterlockerResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/controller/set-interlocker', + crossDomain: true, + data: { 'interlocker': interlockerName }, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#refreshRemoveInterlockerResponse", + 'Interlocker ' + interlockerName + ' is set'); + console.log("Set Interlocker Success, now refreshing interlocker list"); + refreshInterlockersList(); + showInfoInElem("#interlockerInUse", interlockerName); + }, + error: function (jqXHR) { + showErrorInElem("#refreshRemoveInterlockerResponse", getErrorMessage(jqXHR)); + } + }); +} + +function unsetInterlocker (interlockerName) { + $('#refreshRemoveInterlockerResponse').text('Waiting'); + $.ajax({ + type: 'POST', + url: '/controller/unset-interlocker', + crossDomain: true, + data: { 'interlocker': interlockerName }, + dataType: 'text', + success: function (_responseText) { + showInfoInElem("#refreshRemoveInterlockerResponse", + 'Interlocker ' + interlockerName + ' is unset'); + console.log("Unset interlocker success, refreshing interlocker list"); + refreshInterlockersList(); + showErrorInElem("#interlockerInUse", "No interlocker is set"); + }, + error: function (jqXHR) { + showErrorInElem("#refreshRemoveInterlockerResponse", getErrorMessage(jqXHR)); + } + }); +} + +// Init $(document).ready( function () { $('#verificationLogDownloadButton').hide(); $('#clearVerificationMsgButton').hide(); - - // Configuration + $('#pingButton').click(function () { - $('#pingResponse').text('Waiting'); - $.ajax({ - type: 'POST', - url: '/', - crossDomain: true, - data: null, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#pingResponse').parent().removeClass('alert-danger'); - $('#pingResponse').parent().addClass('alert-success'); - $('#pingResponse').text('OK'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#pingResponse').parent().removeClass('alert-success'); - $('#pingResponse').parent().addClass('alert-danger'); - $('#pingResponse').text('Error'); - } - }); + pingServer(); }); + // Startup/Shutdown + $('#startupButton').click(function () { - $('#startupShutdownResponse').text('Waiting'); - $.ajax({ - type: 'POST', - url: '/admin/startup', - crossDomain: true, - data: null, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#startupShutdownResponse').parent().removeClass('alert-danger'); - $('#startupShutdownResponse').parent().addClass('alert-success'); - $('#startupShutdownResponse').text('OK'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#startupShutdownResponse').parent().removeClass('alert-success'); - $('#startupShutdownResponse').parent().addClass('alert-danger'); - $('#startupShutdownResponse').text('System already running!'); - } - }); + startupServer(); }); $('#shutdownButton').click(function () { - $('#startupShutdownResponse').text('Waiting'); - $.ajax({ - type: 'POST', - url: '/admin/shutdown', - crossDomain: true, - data: null, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#startupShutdownResponse').parent().removeClass('alert-danger'); - $('#startupShutdownResponse').parent().addClass('alert-success'); - $('#startupShutdownResponse').text('OK'); - - sessionId = 0; - grabId = -1; - $('#sessionGrabId') - .text('Session ID: ' + sessionId + ', Grab ID: ' + grabId); - }, - error: function (responseData, textStatus, errorThrown) { - $('#startupShutdownResponse').parent().removeClass('alert-success'); - $('#startupShutdownResponse').parent().addClass('alert-danger'); - $('#startupShutdownResponse').text('System not running!'); - } - }); + shutdownServer(); }); - // Train Driver + $('#grabTrainButton').click(function () { - $('#grabTrainResponse').text('Waiting'); trainId = $('#grabTrainId option:selected').text(); trainEngine = $('#grabEngine option:selected').text(); if (sessionId == 0 && grabId == -1) { - $.ajax({ - type: 'POST', - url: '/driver/grab-train', - crossDomain: true, - data: { 'train': trainId, 'engine': trainEngine }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - responseDataSplit = responseData.split(','); - sessionId = responseDataSplit[0]; - grabId = responseDataSplit[1]; - $('#sessionGrabId') - .text('Session ID: ' + sessionId + ', Grab ID: ' + grabId); - $('#grabTrainResponse').text('Grabbed'); - $('#grabTrainResponse').parent().removeClass('alert-danger'); - $('#grabTrainResponse').parent().addClass('alert-success'); - updateTrainIsForwards(); - }, - error: function (responseData, textStatus, errorThrown) { - $('#grabTrainResponse') - .text('System not running or train not available!'); - $('#grabTrainResponse').parent().addClass('alert-danger'); - $('#grabTrainResponse').parent().removeClass('alert-success'); - } - }); + grabTrain(); } else { - $('#grabTrainResponse').text('You can only grab one train!'); - $('#grabTrainResponse').parent().addClass('alert-danger'); - $('#grabTrainResponse').parent().removeClass('alert-success'); + showErrorInElem("#grabTrainResponse", "You can only grab one train."); } }); $('#releaseTrainButton').click(function () { - $('#grabTrainResponse').text('Waiting'); if (sessionId != 0 && grabId != -1) { - $.ajax({ - type: 'POST', - url: '/driver/release-train', - crossDomain: true, - data: { 'session-id': sessionId, 'grab-id': grabId }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - sessionId = 0; - grabId = -1; - $('#sessionGrabId') - .text('Session ID: ' + sessionId + ', Grab ID: ' + grabId); - $('#grabTrainResponse').text('Released grabbed train'); - $('#grabTrainResponse').parent().removeClass('alert-danger'); - $('#grabTrainResponse').parent().addClass('alert-success'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#grabTrainResponse').parent().addClass('alert-danger'); - $('#grabTrainResponse').parent().removeClass('alert-success'); - $('#grabTrainResponse').text('System not running or train still moving!'); - } - }); + releaseTrain(); } else { - $('#grabTrainResponse').parent().addClass('alert-danger'); - $('#grabTrainResponse').parent().removeClass('alert-success'); - $('#grabTrainResponse').text('No grabbed train!'); + showErrorInElem("#grabTrainResponse", "No train grabbed!"); } }); - const speeds = [ - "0", - "10", - "20", - "30", - "40", - "50", - "60", - "70", - "80", - "90", - "100", - "110", - "120", - "126" - ] - $('#speedMinus').click(function () { - speed = $('#dccSpeed').val(); - position = speeds.indexOf(speed); + var speed = $('#dccSpeed').val(); + var position = speeds.indexOf(speed); if (position > 0) { $('#dccSpeed:text').val(speeds[position - 1]); } }); $('#speedPlus').click(function () { - speed = $('#dccSpeed').val(); - position = speeds.indexOf(speed); + var speed = $('#dccSpeed').val(); + var position = speeds.indexOf(speed); if (position < speeds.length - 1) { $('#dccSpeed:text').val(speeds[position + 1]); } }); - + $('#swapDirection').click(function () { trainIsForwards = !trainIsForwards; - enteredSpeed = $('#dccSpeed').val(); + var enteredSpeed = $('#dccSpeed').val(); if (lastSetSpeed == 0) { + // This is done to trigger the train head/rear-lights to switch + // even if train is currently set to speed 0. $('#dccSpeed').val(1); $('#driveTrainButton').click(); $('#dccSpeed').val(0); - setTimeout(function() { + setTimeout(function () { $('#driveTrainButton').click(); + // after swapping direction, reinstate speed displayed in `#dccSpeed` before swapping. $('#dccSpeed').val(enteredSpeed); }, 100 /* milliseconds */); } else { $('#dccSpeed').val(Math.abs(lastSetSpeed)); $('#driveTrainButton').click(); + // after swapping direction, reinstate speed displayed in `#dccSpeed` before swapping. $('#dccSpeed').val(enteredSpeed); } }); $('#driveTrainButton').click(function () { - $('#driveTrainResponse').text('Waiting'); - speed = $('#dccSpeed').val(); + var speed = $('#dccSpeed').val(); speed = trainIsForwards ? speed : -speed; if (sessionId != 0 && grabId != -1) { - $.ajax({ - type: 'POST', - url: '/driver/set-dcc-train-speed', - crossDomain: true, - data: { - 'session-id': sessionId, - 'grab-id': grabId, - 'speed': speed, - 'track-output': trackOutput - }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#driveTrainResponse').text('DCC train speed set to ' + speed); - $('#driveTrainResponse').parent().removeClass('alert-danger'); - $('#driveTrainResponse').parent().addClass('alert-success'); - lastSetSpeed = speed; - }, - error: function (responseData, textStatus, errorThrown) { - $('#driveTrainResponse') - .text('System not running or invalid track output!'); - $('#driveTrainResponse').parent().addClass('alert-danger'); - $('#driveTrainResponse').parent().removeClass('alert-success'); - } - }); + setTrainSpeedDCC(speed); } else { - $('#driveTrainResponse').text('You must have a grabbed train!'); - $('#driveTrainResponse').parent().addClass('alert-danger'); - $('#driveTrainResponse').parent().removeClass('alert-success'); + showErrorInElem("#driveTrainResponse", "No train grabbed!"); } }); $('#stopTrainButton').click(function () { - enteredSpeed = $('#dccSpeed').val(); + var enteredSpeed = $('#dccSpeed').val(); $('#dccSpeed').val(0); $('#driveTrainButton').click(); + // after stopping, reinstate speed displayed in `#dccSpeed` before stopping. $('#dccSpeed').val(enteredSpeed); }); $('#requestRouteButton').click(function () { - $('#routeResponse').text('Waiting'); - source = $('#signalIdFrom').val(); - destination = $('#signalIdTo').val(); + let source = $('#signalIdFrom').val(); + let destination = $('#signalIdTo').val(); if (sessionId != 0 && grabId != -1) { - $.ajax({ - type: 'POST', - url: '/controller/get-interlocker', - crossDomain: true, - data: null, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $.ajax({ - type: 'POST', - url: '/driver/request-route', - crossDomain: true, - data: { - 'session-id': sessionId, - 'grab-id': grabId, - 'source': source, - 'destination': destination - }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#routeResponse').text('Route ' + responseData + ' granted'); - $('#routeResponse').parent().removeClass('alert-danger'); - $('#routeResponse').parent().addClass('alert-success'); - - $('#routeId').val(responseData); - }, - error: function (responseData, textStatus, errorThrown) { - $('#routeResponse').text(responseData.responseText); - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - } - }); - - }, - error: function (responseData, textStatus, errorThrown) { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse').text("No interlocker set!"); - } - }); + getInterlockerThenRequestRoute(source, destination); } else { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse').text('You must have a grabbed train!'); + showErrorInElem("#routeResponse", "No train grabbed!"); } }); - - /* $('#requestRouteButton').click(function () { + $('#automaticDriveRouteButton').click(function () { $('#routeResponse').text('Waiting'); - source = $('#signalIdFrom').val(); - destination = $('#signalIdTo').val(); - if (sessionId != 0 && grabId != -1) { - ajaxGetInterlocker() - .then(ajaxRequestRoute); - } else { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse').text('You must have a grabbed train!'); - } + var routeId = $('#routeId').val(); + driveRoute(routeId, "automatic"); }); - function ajaxGetInterlocker(){ - return $.ajax({ - type: 'POST', - url: '/controller/get-interlocker', - crossDomain: true, - data: { - 'session-id': sessionId, - 'grab-id': grabId, - 'source': source, - 'destination': destination - }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#requestRouteButton').removeClass('btn-outline-danger'); - $('#requestRouteButton').addClass('btn-outline-primary'); - - }, - error: function (responseData, textStatus, errorThrown) { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse').text("No interlocker set!"); - } - }); - } - - function ajaxRequestRoute(responseData, textStatus, jqXHR){ - return $.ajax({ - type: 'POST', - url: '/driver/request-route', - crossDomain: true, - data: { - 'session-id': sessionId, - 'grab-id': grabId, - 'source': source, - 'destination': destination - }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - routeId = responseData; - $('routeId').val(routeId); - $('#routeResponse').parent().removeClass('alert-danger'); - $('#routeResponse').parent().addClass('alert-success'); - $('#routeResponse').text('Route ' + responseData + ' granted'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse').text(responseData.responseText); - } - }); - } */ - - //From https://github.com/eligrey/FileSaver.js/wiki/FileSaver.js-Example - function SaveAsFile(content, filename, contentTypeOptions) { - try { - var b = new Blob([content], {type:contentTypeOptions}); - saveAs(b, filename); - } catch (e) { - console.log("SaveAsFile Failed to save the file. Error msg: " + e); - } - } - - function driveRoute(routeId, mode) { - if (isNaN(routeId)) { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse').text('Route \"' + routeId + '\" is not a number!'); - - return; - } - - if (sessionId != 0 && grabId != -1) { - $.ajax({ - type: 'POST', - url: '/driver/drive-route', - crossDomain: true, - data: { - 'session-id': sessionId, - 'grab-id': grabId, - 'route-id': routeId, - 'mode': mode - }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#routeResponse').text(responseData); - $('#routeResponse').parent().removeClass('alert-danger'); - $('#routeResponse').parent().addClass('alert-success'); - - $('#routeId').val("None"); - }, - error: function (responseData, textStatus, errorThrown) { - $('#routeResponse').text('Route could not be driven!'); - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - } - }); - } else { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse').text('You must have a grabbed train!'); - } - } - - $('#automaticDriveRouteButton').click(function () { + $('#manualDriveRouteButton').click(function () { $('#routeResponse').text('Waiting'); var routeId = $('#routeId').val(); - driveRoute(routeId, "automatic"); + driveRoute(routeId, "manual"); }); - + + // Verification + $('#clearVerificationMsgButton').click(function () { $('#verificationLogDownloadButton').hide(); $('#clearVerificationMsgButton').hide(); $('#uploadResponse').text(''); $('#uploadResponse').parent().removeClass('alert-danger'); }); - + $('#verificationLogDownloadButton').click(function () { - //Create zip file that contains the logs - //then trigger download of that file. + // Create zip file that contains the logs + // then trigger download of that file. try { - logList = ""; + let logList = ""; verificationObj["verifiedproperties"].forEach(element => { logList += atob(element["verificationlog"]) + "\n\n"; }); @@ -521,179 +807,31 @@ $(document).ready( } }); - $('#manualDriveRouteButton').click(function () { - $('#routeResponse').text('Waiting'); - var routeId = $('#routeId').val(); - driveRoute(routeId, "manual"); - }); - - // Custom Engines $('#uploadEngineButton').click(function () { - $('#uploadResponse').text('Waiting'); var files = $('#selectUploadFile').prop('files'); if (files.length != 1) { - $('#uploadResponse').parent().removeClass('alert-success'); - $('#uploadResponse').parent().addClass('alert-danger'); - $('#uploadResponse').text('Select an SCCharts file!'); - return; + showErrorInElem("#uploadResponse", "No SCCharts file selected!"); + } else { + uploadEngine(files[0]); } - - var file = files[0]; - var formData = new FormData(); - formData.append('file', file); - $.ajax({ - type: 'POST', - url: '/upload/engine', - crossDomain: true, - data: formData, - processData: false, - contentType: false, - cache: false, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - console.log("Upload Success"); - refreshEnginesList(); - $('#uploadResponse').parent().removeClass('alert-danger'); - $('#uploadResponse').parent().addClass('alert-success'); - $('#uploadResponse').text('Engine ' + file.name + ' ready for use'); - $('#verificationLogDownloadButton').hide(); - $('#clearVerificationMsgButton').hide(); - }, - error: function (responseData, textStatus, errorThrown) { - console.log("Upload Failed"); - try { - var resJson = JSON.parse(responseData.responseText.toString(), null, 2); - var msg = "Server Message: " + resJson["message"]; - msg += "\nList of Properties:" - resJson["verifiedproperties"].forEach(element => { - msg += "\n-" + element["property"]["name"] + ": " + element["verificationmessage"] - }); - verificationObj = resJson; - $('#uploadResponse').text(msg); - $('#verificationLogDownloadButton').show(); - } catch (e) { - console.log("Unable to parse server's reply in upload-engine failure case: " + e); - $('#uploadResponse').text(responseData.responseText.toString()); - } - $('#uploadResponse').parent().removeClass('alert-success'); - $('#uploadResponse').parent().addClass('alert-danger'); - $('#clearVerificationMsgButton').show(); - } - }); }); - - - - function refreshEnginesList() { - $.ajax({ - type: 'POST', - url: '/upload/refresh-engines', - crossDomain: true, - data: null, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - var engineList = responseData.split(","); - - var selectGrabEngines = $("#grabEngine"); - var selectAvailableEngines = $("#availableEngines"); - - selectGrabEngines.empty(); - selectAvailableEngines.empty(); - - $.each(engineList, function (key, value) { - selectGrabEngines.append(new Option(value)); - selectAvailableEngines.append(new Option(value)); - }); - - $('#refreshRemoveEngineResponse').parent().removeClass('alert-danger'); - $('#refreshRemoveEngineResponse').parent().addClass('alert-success'); - $('#refreshRemoveEngineResponse').text('Refreshed list of train engines'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#refreshRemoveEngineResponse').parent().removeClass('alert-success'); - $('#refreshRemoveEngineResponse').parent().addClass('alert-danger'); - $('#refreshRemoveEngineResponse').text('Unable to refresh list of train engines'); - } - }); - } $('#refreshEnginesButton').click(function () { - $('#refreshRemoveEngineResponse').text('Waiting'); refreshEnginesList(); }); $('#removeEngineButton').click(function () { - $('#refreshRemoveEngineResponse').text('Waiting'); var engineName = $('#availableEngines option:selected').text(); if (engineName.search("unremovable") != -1) { - $('#refreshRemoveEngineResponse').parent().removeClass('alert-success'); - $('#refreshRemoveEngineResponse').parent().addClass('alert-danger'); - $('#refreshRemoveEngineResponse').text('Engine ' + engineName + ' is unremovable!'); - return; + showErrorInElem("#refreshRemoveEngineResponse", 'Engine ' + engineName + ' is unremovable!'); + } else { + removeEngine(engineName); } - - $.ajax({ - type: 'POST', - url: '/upload/remove-engine', - crossDomain: true, - data: { 'engine-name': engineName }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - refreshEnginesList(); - $('#refreshRemoveEngineResponse').parent().removeClass('alert-danger'); - $('#refreshRemoveEngineResponse').parent().addClass('alert-success'); - $('#refreshRemoveEngineResponse').text('Engine ' + engineName + ' removed'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#refreshRemoveEngineResponse').parent().removeClass('alert-success'); - $('#refreshRemoveEngineResponse').parent().addClass('alert-danger'); - $('#refreshRemoveEngineResponse').text(responseData.responseText); - } - }); }); - // Admin control of grabbed trains and train speed - - function adminSetTrainSpeed(trainId, speed) { - return $.ajax({ - type: 'POST', - url: '/admin/set-dcc-train-speed', - crossDomain: true, - data: { - 'train': trainId, - 'speed': speed, - 'track-output': trackOutput - }, - dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - // Do nothing - }, - error: (responseData, textStatus, errorThrown) => { - // Do nothing - } - }); - } - - function adminReleaseTrain(trainId) { - return $.ajax({ - type: 'POST', - url: '/admin/release-train', - crossDomain: true, - data: { - 'train': trainId - }, - dataType: 'text', - success: (responseData, textStatus, jqXHR) => { - // Do nothing - }, - error: (responseData, textStatus, errorThrown) => { - // Do nothing - } - }); - } - + ///TODO: Move this when merging in the the 134-/135-branches. const trainIds = [ 'cargo_db', 'cargo_green', @@ -701,344 +839,125 @@ $(document).ready( 'regional_odeg', 'regional_brengdirect' ]; - + trainIds.forEach((trainId) => { $(`#driveTrainButton_${trainId}`).click(function () { const speed = $(`#dccSpeed_${trainId}`).val(); adminSetTrainSpeed(trainId, speed); }); - + $(`#releaseTrainButton_${trainId}`).click(function () { adminReleaseTrain(trainId); }); }); - - // Controller - + // Controller + $('#releaseRouteButton').click(function () { $('#routeResponse').text('Waiting'); var routeId = $('#routeId').val(); if (isNaN(routeId)) { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse').text('Route \"' + routeId + '\" is not a number!'); - - return; + showErrorInElem("#routeResponse", 'Route \"' + routeId + '\" is not a number!'); + } else { + releaseRoute(routeId); } - - $.ajax({ - type: 'POST', - url: '/controller/release-route', - crossDomain: true, - data: { 'route-id': routeId }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#routeResponse').parent().removeClass('alert-danger'); - $('#routeResponse').parent().addClass('alert-success'); - $('#routeResponse').text('Route ' + routeId + ' released'); - - $('#routeId').val("None"); - }, - error: function (responseData, textStatus, errorThrown) { - $('#routeResponse').parent().removeClass('alert-success'); - $('#routeResponse').parent().addClass('alert-danger'); - $('#routeResponse') - .text('System not running or invalid track output!'); - } - }); }); - - function setPointAjax(pointId, pointPosition) { - $.ajax({ - type: 'POST', - url: '/controller/set-point', - crossDomain: true, - data: { 'point': pointId, 'state': pointPosition }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#setPointResponse') - .text('Point ' + pointId + ' set to ' + pointPosition); - $('#setPointResponse').parent().removeClass('alert-danger'); - $('#setPointResponse').parent().addClass('alert-success'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#setPointResponse').text('System not running or invalid position!'); - $('#setPointResponse').parent().removeClass('alert-success'); - $('#setPointResponse').parent().addClass('alert-danger'); - } - }); - } $('#setPointButton').click(function () { - $('#setPointResponse').text('Waiting'); var pointId = $('#pointId').val(); var pointPosition = $("#pointPosition option:selected").text(); - setPointAjax(pointId, pointPosition); + setPoint(pointId, pointPosition); }); - + $('#setPointButtonNormal').click(function () { - $('#setPointResponse').text('Waiting'); var pointId = $('#pointId').val(); var pointPosition = 'normal'; - setPointAjax(pointId, pointPosition); + setPoint(pointId, pointPosition); }); $('#setPointButtonReverse').click(function () { - $('#setPointResponse').text('Waiting'); var pointId = $('#pointId').val(); var pointPosition = 'reverse'; - setPointAjax(pointId, pointPosition); + setPoint(pointId, pointPosition); }); - function setSignalAjax(signalId, signalAspect) { - $.ajax({ - type: 'POST', - url: '/controller/set-signal', - crossDomain: true, - data: { 'signal': signalId, 'state': signalAspect }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#setSignalResponse') - .text('Signal ' + signalId + ' set to ' + signalAspect); - $('#setSignalResponse').parent().removeClass('alert-danger'); - $('#setSignalResponse').parent().addClass('alert-success'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#setSignalResponse').text('System not running or invalid aspect!'); - $('#setSignalResponse').parent().removeClass('alert-success'); - $('#setSignalResponse').parent().addClass('alert-danger'); - } - }); - } - $('#setSignalButton').click(function () { - $('#setSignalResponse').text('Waiting'); var signalId = $('#signalId').val(); var signalAspect = $("#signalAspect option:selected").text(); - setSignalAjax(signalId, signalAspect); + setSignal(signalId, signalAspect); }); $('#setSignalButtonRed').click(function () { - $('#setSignalResponse').text('Waiting'); var signalId = $('#signalId').val(); var signalAspect = 'aspect_stop'; - setSignalAjax(signalId, signalAspect); + setSignal(signalId, signalAspect); }); $('#setSignalButtonYellow').click(function () { - $('#setSignalResponse').text('Waiting'); var signalId = $('#signalId').val(); var signalAspect = 'aspect_caution'; - setSignalAjax(signalId, signalAspect); + setSignal(signalId, signalAspect); }); $('#setSignalButtonGreen').click(function () { - $('#setSignalResponse').text('Waiting'); var signalId = $('#signalId').val(); var signalAspect = 'aspect_go'; - setSignalAjax(signalId, signalAspect); + setSignal(signalId, signalAspect); }); $('#setSignalButtonWhite').click(function () { - $('#setSignalResponse').text('Waiting'); var signalId = $('#signalId').val(); var signalAspect = 'aspect_shunt'; - setSignalAjax(signalId, signalAspect); + setSignal(signalId, signalAspect); }); - + $('#setPeripheralStateButton').click(function () { - $('#setPeripheralResponse').text('Waiting'); var peripheralId = $('#peripheralId').val(); var peripheralAspect = $('#peripheralState').val(); - $.ajax({ - type: 'POST', - url: '/controller/set-peripheral', - crossDomain: true, - data: { 'peripheral': peripheralId, 'state': peripheralAspect }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - $('#setPeripheralResponse') - .text('Peripheral ' + peripheralId + ' set to ' + peripheralAspect); - $('#setPeripheralResponse').parent().removeClass('alert-danger'); - $('#setPeripheralResponse').parent().addClass('alert-success'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#setPeripheralResponse').text('System not running or invalid aspect!'); - $('#setPeripheralResponse').parent().removeClass('alert-success'); - $('#setPeripheralResponse').parent().addClass('alert-danger'); - } - }); + setPeripheralState(peripheralId, peripheralAspect); }); - // Custom Interlockers + $('#uploadInterlockerButton').click(function () { - $('#uploadResponse').text('Waiting'); var files = $('#selectUploadFile').prop('files'); if (files.length != 1) { - $('#uploadResponse').parent().removeClass('alert-success'); - $('#uploadResponse').parent().addClass('alert-danger'); - $('#uploadResponse').text('Select a BahnDSL file!'); - return; + showErrorInElem("#uploadResponse", "No BahnDSL file selected!"); + } else { + uploadInterlocker(files[0]); } - var file = files[0]; - var formData = new FormData(); - formData.append('file', file); - $.ajax({ - type: 'POST', - url: '/upload/interlocker', - crossDomain: true, - data: formData, - processData: false, - contentType: false, - cache: false, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - refreshInterlockersList(); - $('#uploadResponse').parent().removeClass('alert-danger'); - $('#uploadResponse').parent().addClass('alert-success'); - $('#uploadResponse') - .text('Interlocker ' + file.name + ' ready for use'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#uploadResponse').parent().removeClass('alert-success'); - $('#uploadResponse').parent().addClass('alert-danger'); - $('#uploadResponse').text(responseData.responseText); - } - }); }); - function refreshInterlockersList() { - $.ajax({ - type: 'POST', - url: '/upload/refresh-interlockers', - crossDomain: true, - data: null, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - var interlockerList = responseData.split(","); - - var selectAvailableInterlockers = $("#availableInterlockers"); - - selectAvailableInterlockers.empty(); - - $.each(interlockerList, function (key, value) { - selectAvailableInterlockers.append(new Option(value)); - }); - - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-success'); - $('#refreshRemoveInterlockerResponse').text('Refreshed list of interlockers'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-success'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').text('Unable to refresh list of interlockers'); - } - }); - - } $('#refreshInterlockersButton').click(function () { - $('#refreshRemoveInterlockerResponse').text('Waiting'); refreshInterlockersList(); }); $('#removeInterlockerButton').click(function () { - $('#refreshRemoveInterlockerResponse').text('Waiting'); var interlockerName = $('#availableInterlockers option:selected').text(); if (interlockerName.search("unremovable") != -1) { - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-success'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').text('Interlocker ' + interlockerName + ' is unremovable!'); - return; + showErrorInElem("#refreshRemoveInterlockerResponse", + 'Interlocker ' + interlockerName + ' is unremovable!'); + } else { + removeInterlocker(interlockerName); } - $.ajax({ - type: 'POST', - url: '/upload/remove-interlocker', - crossDomain: true, - data: { 'interlocker-name': interlockerName }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - refreshInterlockersList(); - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-success'); - $('#refreshRemoveInterlockerResponse') - .text('Interlocker ' + interlockerName + ' removed'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-success'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').text(responseData.responseText); - } - }); }); $('#setInterlockerButton').click(function () { - $('#refreshRemoveInterlockerResponse').text('Waiting'); var interlockerName = $('#availableInterlockers option:selected').text(); - $.ajax({ - type: 'POST', - url: '/controller/set-interlocker', - crossDomain: true, - data: { 'interlocker': interlockerName }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - refreshInterlockersList(); - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-success'); - $('#refreshRemoveInterlockerResponse') - .text('Interlocker ' + interlockerName + ' is set'); - - $('#interlockerInUse').parent().removeClass('alert-danger'); - $('#interlockerInUse').parent().addClass('alert-success'); - $('#interlockerInUse').text(interlockerName); - }, - error: function (responseData, textStatus, errorThrown) { - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-success'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').text('Unable to set interlocker'); - } - }); + setInterlocker(interlockerName); }); $('#unsetInterlockerButton').click(function () { - $('#refreshRemoveInterlockerResponse').text('Waiting'); var interlockerName = $('#interlockerInUse').text(); - $.ajax({ - type: 'POST', - url: '/controller/unset-interlocker', - crossDomain: true, - data: { 'interlocker': interlockerName }, - dataType: 'text', - success: function (responseData, textStatus, jqXHR) { - refreshInterlockersList(); - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-success'); - $('#refreshRemoveInterlockerResponse') - .text('Interlocker ' + interlockerName + ' is unset'); - - $('#interlockerInUse').parent().removeClass('alert-success'); - $('#interlockerInUse').parent().addClass('alert-danger'); - $('#interlockerInUse').text('No interlocker set!'); - }, - error: function (responseData, textStatus, errorThrown) { - $('#refreshRemoveInterlockerResponse').parent().removeClass('alert-success'); - $('#refreshRemoveInterlockerResponse').parent().addClass('alert-danger'); - $('#refreshRemoveInterlockerResponse').text('Unable to unset interlocker'); - } - }); + unsetInterlocker(interlockerName); }); // File chooser button for Driver and Controller + $('#selectUploadFile').change(function () { $('#selectUploadFileResponse').text(this.files[0].name); - - $('#uploadResponse').text('Selected ' + this.files[0].name); - $('#uploadResponse').parent().removeClass('alert-danger'); - $('#uploadResponse').parent().addClass('alert-success'); + showInfoInElem("#uploadResponse", 'Selected ' + this.files[0].name); }); - } ); diff --git a/server/src/bahn_data_util.c b/server/src/bahn_data_util.c index 7d3e9c4c..66f44703 100644 --- a/server/src/bahn_data_util.c +++ b/server/src/bahn_data_util.c @@ -54,10 +54,8 @@ typedef enum { t_config_data config_data = {}; -// Needed to temporarily store new strings created by config_get_... -GArray *cached_allocated_str = NULL; - -char *static_empty_str = ""; +// Needed to temporarily store new strings created by track_state_get_value +GArray *cached_allocated_str_array = NULL; bool bahn_data_util_initialise_config(const char *config_dir) { if (!interlocking_table_initialise(config_dir)) { @@ -77,27 +75,42 @@ void bahn_data_util_free_config() { } bool string_equals(const char *str1, const char *str2) { - return strcmp(str1, str2) == 0; + if (str1 != NULL && str2 != NULL) { + return strcmp(str1, str2) == 0; + } else { + return false; + } } void bahn_data_util_init_cached_track_state() { - cached_allocated_str = g_array_sized_new(FALSE, FALSE, sizeof(char *), 16); + if (cached_allocated_str_array != NULL) { + syslog_server(LOG_ERR, + "bahn data util init cached track state - cache is not NULL, " + "either concurrent usage or someone didn't free the cache correctly!"); + return; + } + cached_allocated_str_array = g_array_sized_new(FALSE, FALSE, sizeof(char *), 16); } void bahn_data_util_free_cached_track_state() { - if (cached_allocated_str != NULL) { - g_array_free(cached_allocated_str, true); - cached_allocated_str = NULL; + if (cached_allocated_str_array != NULL) { + for (int i = 0; i < cached_allocated_str_array->len; ++i) { + free(g_array_index(cached_allocated_str_array, char *, i)); + } + g_array_free(cached_allocated_str_array, true); + cached_allocated_str_array = NULL; } } -void add_cache_str(char *state) { - if (cached_allocated_str != NULL) { - g_array_append_val(cached_allocated_str, state); +static void add_cache_str(char *state) { + if (cached_allocated_str_array != NULL) { + g_array_append_val(cached_allocated_str_array, state); + } else { + syslog_server(LOG_ERR, "bahn data util add cache str - cache is NULL!"); } } -e_config_type get_config_type(const char *type) { +static e_config_type get_config_type(const char *type) { if (string_equals(type, "modulename")) { return TYPE_MODULE_NAME; } else if (string_equals(type, "route")) { @@ -129,7 +142,7 @@ e_config_type get_config_type(const char *type) { return TYPE_NOT_SUPPORTED; } -void *get_object(e_config_type config_type, const char *id) { +static void *get_object(e_config_type config_type, const char *id) { GHashTable *tb = NULL; switch (config_type) { case TYPE_MODULE_NAME: @@ -173,29 +186,36 @@ void *get_object(e_config_type config_type, const char *id) { break; } - // check if (tb != NULL) { if (g_hash_table_contains(tb, id)) { return g_hash_table_lookup(tb, id); } + } else { + syslog_server(LOG_ERR, "bahn data util get object - unknown config type"); } - return NULL; } int interlocking_table_get_routes(const char *src_signal_id, const char *dst_signal_id, char *route_ids[]) { - GArray *arr = interlocking_table_get_route_ids(src_signal_id, dst_signal_id); + if (src_signal_id == NULL || dst_signal_id == NULL) { + syslog_server(LOG_ERR, "interlocking table get routes: invalid (NULL) parameters"); + return 0; + } + const GArray *arr = interlocking_table_get_route_ids(src_signal_id, dst_signal_id); if (arr != NULL) { for (int i = 0; i < arr->len; ++i) { route_ids[i] = g_array_index(arr, char *, i); } return arr->len; } - return 0; } -int get_route_array_string_value(t_interlocking_route *route, const char *prop_name, char* data[]) { +static int get_route_array_string_value(t_interlocking_route *route, const char *prop_name, char* data[]) { + if (route == NULL || prop_name == NULL) { + syslog_server(LOG_ERR, "Get route array string value: invalid (NULL) parameters"); + return 0; + } if (string_equals(prop_name, "path")) { if (route->path != NULL) { for (int i = 0; i < route->path->len; ++i) { @@ -237,6 +257,10 @@ int get_route_array_string_value(t_interlocking_route *route, const char *prop_n } char *config_get_scalar_string_value(const char *type, const char *id, const char *prop_name) { + if (type == NULL || id == NULL || prop_name == NULL) { + syslog_server(LOG_ERR, "Get scalar string: invalid (NULL) parameters"); + return ""; + } e_config_type config_type = get_config_type(type); void *obj = get_object(config_type, id); char *result = NULL; @@ -254,14 +278,12 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "train")) { result = ((t_interlocking_route *) obj)->train; } - break; case TYPE_SEGMENT: if (string_equals(prop_name, "id")) { result = ((t_config_segment *) obj)->id; } - break; case TYPE_REVERSER: @@ -272,7 +294,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "block")) { result = ((t_config_reverser *) obj)->block; } - break; case TYPE_SIGNAL: @@ -283,7 +304,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "type")) { result = ((t_config_signal *) obj)->type; } - break; case TYPE_POINT: @@ -298,7 +318,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "reverse")) { result = ((t_config_point *) obj)->reverse_aspect; } - break; case TYPE_PERIPHERAL: @@ -309,7 +328,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "type")) { result = ((t_config_peripheral *) obj)->type; } - break; case TYPE_TRAIN: @@ -318,7 +336,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "type")) { result = ((t_config_train *) obj)->type; } - break; case TYPE_BLOCK: @@ -327,7 +344,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "direction")) { result = ((t_config_block *) obj)->direction; } - break; case TYPE_CROSSING: @@ -336,7 +352,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "segment")) { result = ((t_config_crossing *) obj)->main_segment; } - break; case TYPE_SIGNAL_TYPE: @@ -345,7 +360,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "initial")) { result = ((t_config_signal_type *) obj)->initial; } - break; case TYPE_COMPOSITE_SIGNAL: @@ -360,7 +374,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "distant")) { result = ((t_config_composite_signal *) obj)->distant; } - break; case TYPE_PERIPHERAL_TYPE: @@ -369,7 +382,6 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } else if (string_equals(prop_name, "initial")) { result = ((t_config_peripheral_type *) obj)->initial; } - break; default: @@ -377,12 +389,16 @@ char *config_get_scalar_string_value(const char *type, const char *id, const cha } } - result = result != NULL ? result : static_empty_str; + result = result != NULL ? result : ""; syslog_server(LOG_DEBUG, "Get scalar string: %s %s.%s => \"%s\"", type, id, prop_name, result); return result; } float config_get_scalar_float_value(const char *type, const char *id, const char *prop_name) { + if (type == NULL || id == NULL || prop_name == NULL) { + syslog_server(LOG_ERR, "Get scalar float: invalid (NULL) parameters"); + return 0; + } e_config_type config_type = get_config_type(type); void *obj = get_object(config_type, id); float result = 0; @@ -401,7 +417,7 @@ float config_get_scalar_float_value(const char *type, const char *id, const char result = ((t_config_block *) obj)->limit_speed; } break; - + case TYPE_SEGMENT: if (string_equals(prop_name, "length")) { result = ((t_config_segment *) obj)->length; @@ -426,6 +442,10 @@ float config_get_scalar_float_value(const char *type, const char *id, const char } bool config_get_scalar_bool_value(const char *type, const char *id, const char *prop_name) { + if (type == NULL || id == NULL || prop_name == NULL) { + syslog_server(LOG_ERR, "Get scalar bool: invalid (NULL) parameters"); + return ""; + } e_config_type config_type = get_config_type(type); void *obj = get_object(config_type, id); bool result = false; @@ -446,6 +466,10 @@ bool config_get_scalar_bool_value(const char *type, const char *id, const char * } int config_get_array_string_value(const char *type, const char *id, const char *prop_name, char *data[]) { + if (type == NULL || id == NULL || prop_name == NULL) { + syslog_server(LOG_ERR, "Get array string: invalid (NULL) parameters"); + return 0; + } e_config_type config_type = get_config_type(type); void *obj = get_object(config_type, id); int result = 0; @@ -460,21 +484,18 @@ int config_get_array_string_value(const char *type, const char *id, const char * if (string_equals(prop_name, "aspects")) { arr = ((t_config_signal *) obj)->aspects; } - break; case TYPE_PERIPHERAL: if (string_equals(prop_name, "aspects")) { arr = ((t_config_peripheral *) obj)->aspects; } - break; case TYPE_TRAIN: if (string_equals(prop_name, "peripherals")) { arr = ((t_config_train *) obj)->peripherals; } - break; case TYPE_BLOCK: @@ -487,21 +508,18 @@ int config_get_array_string_value(const char *type, const char *id, const char * } else if (string_equals(prop_name, "overlaps")) { arr = ((t_config_block *) obj)->overlaps; } - break; case TYPE_SIGNAL_TYPE: if (string_equals(prop_name, "aspects")) { arr = ((t_config_signal_type *) obj)->aspects; } - break; case TYPE_PERIPHERAL_TYPE: if (string_equals(prop_name, "aspects")) { arr = ((t_config_peripheral_type *) obj)->aspects; } - break; default: @@ -521,6 +539,10 @@ int config_get_array_string_value(const char *type, const char *id, const char * } int config_get_array_int_value(const char *type, const char *id, const char *prop_name, int data[]) { + if (type == NULL || id == NULL || prop_name == NULL) { + syslog_server(LOG_ERR, "Get array int value: invalid (NULL) parameters"); + return 0; + } e_config_type config_type = get_config_type(type); void *obj = get_object(config_type, id); int result = 0; @@ -559,6 +581,10 @@ int config_get_array_bool_value(const char *type, const char *id, const char *pr } bool config_set_scalar_string_value(const char *type, const char *id, const char *prop_name, char *value) { + if (type == NULL || id == NULL || prop_name == NULL) { + syslog_server(LOG_ERR, "Config set scalar string value: invalid (NULL) parameters"); + return false; + } e_config_type config_type = get_config_type(type); void *obj = get_object(config_type, id); bool result = false; @@ -566,12 +592,21 @@ bool config_set_scalar_string_value(const char *type, const char *id, const char if (string_equals(prop_name, "train")) { // Set train t_interlocking_route *route = (t_interlocking_route *) obj; - route->train = strdup(value); - if (value != NULL && route->train == NULL) { + if (route->train != NULL) { syslog_server(LOG_ERR, - "config set scalar string value: unable to allocate memory for route->train"); + "config set scalar string value: not allowed to " + "overwrite route->train for route-id %s", + route->id); + } else { + route->train = strdup(value); + if (value != NULL && route->train == NULL) { + syslog_server(LOG_ERR, + "config set scalar string value: " + "unable to allocate memory for route->train"); + } else { + result = true; + } } - result = true; } } @@ -581,9 +616,9 @@ bool config_set_scalar_string_value(const char *type, const char *id, const char return result; } -e_config_type get_track_state_type(const char *id) { +static e_config_type get_track_state_accessory_type(const char *id) { if (id == NULL) { - syslog_server(LOG_ERR, "Get track state: %s is NULL", id); + syslog_server(LOG_ERR, "Get accessory type: parameter id is NULL"); return TYPE_NOT_SUPPORTED; } @@ -595,7 +630,7 @@ e_config_type get_track_state_type(const char *id) { return TYPE_PERIPHERAL; } - syslog_server(LOG_ERR, "Get track state: %s could not be found", id); + syslog_server(LOG_ERR, "Get accessory type: %s could not be found", id); return TYPE_NOT_SUPPORTED; } @@ -607,25 +642,28 @@ e_config_type get_track_state_type(const char *id) { * @param value stop, go, caution, or shunt * @return true of success, otherwise false */ -char *get_signal_state(const char *id) { - t_config_signal *signal = get_object(TYPE_SIGNAL, id); - if (signal == NULL) +static char *get_signal_state(const char *id) { + if (id == NULL) { + syslog_server(LOG_ERR, "Get signal state: invalid (NULL) id"); return ""; - - const char *type = signal->type; - + } + const t_config_signal *signal = get_object(TYPE_SIGNAL, id); + if (signal == NULL) { + return ""; + } + // load raw state char *raw_state = NULL; t_bidib_unified_accessory_state_query state_query = bidib_get_signal_state(id); if (state_query.known) { if (state_query.board_accessory_state.state_id == NULL) { - syslog_server(LOG_ERR, "get signal state: board accessory state id is NULL"); + syslog_server(LOG_ERR, "Get signal state: board accessory state id is NULL"); bidib_free_unified_accessory_state_query(state_query); return NULL; } raw_state = strdup(state_query.board_accessory_state.state_id); if (raw_state == NULL) { - syslog_server(LOG_ERR, "get signal state: unable to allocate memory for raw_state"); + syslog_server(LOG_ERR, "Get signal state: unable to allocate memory for raw_state"); bidib_free_unified_accessory_state_query(state_query); return NULL; } @@ -636,33 +674,33 @@ char *get_signal_state(const char *id) { char *result = NULL; if (string_equals(raw_state, "aspect_stop")) { - if (string_equals(type, "entry") - || string_equals(type, "exit") - || string_equals(type, "block") - || string_equals(type, "distant") - || string_equals(type, "shunting") - || string_equals(type, "halt")) { + if (string_equals(signal->type, "entry") + || string_equals(signal->type, "exit") + || string_equals(signal->type, "block") + || string_equals(signal->type, "distant") + || string_equals(signal->type, "shunting") + || string_equals(signal->type, "halt")) { result = "stop"; } } else if (string_equals(raw_state, "aspect_go")) { - if (string_equals(type, "entry") - || string_equals(type, "exit") - || string_equals(type, "block") - || string_equals(type, "distant")) { + if (string_equals(signal->type, "entry") + || string_equals(signal->type, "exit") + || string_equals(signal->type, "block") + || string_equals(signal->type, "distant")) { result = "go"; } } else if (string_equals(raw_state, "aspect_caution")) { - if (string_equals(type, "entry") - || string_equals(type, "exit") - || string_equals(type, "distant")) { + if (string_equals(signal->type, "entry") + || string_equals(signal->type, "exit") + || string_equals(signal->type, "distant")) { result = "caution"; } } else if (string_equals(raw_state, "aspect_shunt")) { - if (string_equals(type, "exit") - || string_equals(type, "shunting")) { + if (string_equals(signal->type, "exit") + || string_equals(signal->type, "shunting")) { result = "shunt"; } @@ -672,7 +710,11 @@ char *get_signal_state(const char *id) { return result; } -bool set_signal_raw_aspect(t_config_signal *signal, const char *value) { +static bool set_signal_raw_aspect(const t_config_signal *signal, const char *value) { + if (signal == NULL || value == NULL) { + syslog_server(LOG_ERR, "Set signal raw aspect: invalid (NULL) parameters"); + return false; + } if (signal->aspects != NULL) { for (int i = 0; i < signal->aspects->len; ++i) { char *aspect = g_array_index(signal->aspects, char *, i); @@ -683,7 +725,6 @@ bool set_signal_raw_aspect(t_config_signal *signal, const char *value) { } } } - return false; } @@ -695,11 +736,16 @@ bool set_signal_raw_aspect(t_config_signal *signal, const char *value) { * @param value stop, go, caution, shunt * @return true if successful, otherwise false */ -bool set_signal_state(const char *id, const char *value) { - t_config_signal *signal = get_object(TYPE_SIGNAL, id); - if (signal == NULL) +static bool set_signal_state(const char *id, const char *value) { + if (id == NULL || value == NULL) { + syslog_server(LOG_ERR, "Set signal state: invalid (NULL) parameters"); return false; - + } + const t_config_signal *signal = get_object(TYPE_SIGNAL, id); + if (signal == NULL) { + return false; + } + if (string_equals(value, "stop")) { if (string_equals(signal->type, "entry") || string_equals(signal->type, "exit") @@ -710,11 +756,7 @@ bool set_signal_state(const char *id, const char *value) { return set_signal_raw_aspect(signal, "aspect_stop"); } - - return false; - } - - if (string_equals(value, "go")) { + } else if (string_equals(value, "go")) { if (string_equals(signal->type, "entry") || string_equals(signal->type, "exit") || string_equals(signal->type, "distant") @@ -722,35 +764,37 @@ bool set_signal_state(const char *id, const char *value) { return set_signal_raw_aspect(signal, "aspect_go"); } - - return false; - } - - if (string_equals(value, "caution")) { + } else if (string_equals(value, "caution")) { if (string_equals(signal->type, "entry") || string_equals(signal->type, "exit") || string_equals(signal->type, "distant")) { return set_signal_raw_aspect(signal, "aspect_caution"); } - - return false; - } - - if (string_equals(value, "shunt")) { + } else if (string_equals(value, "shunt")) { if (string_equals(signal->type, "exit") || string_equals(signal->type, "shunting")) { return set_signal_raw_aspect(signal, "aspect_shunt"); } - - return false; } return false; } -bool set_peripheral_raw_aspect(t_config_peripheral *peripheral, const char *value) { +/** + * Set the peripheral aspect to some value + * + * @param peripheral peripheral whose aspect to set + * @param value value of the aspect + * @return true valid params + * @return false invalid params + */ +static bool set_peripheral_raw_aspect(t_config_peripheral *peripheral, const char *value) { + if (peripheral == NULL || value == NULL) { + syslog_server(LOG_ERR, "Set peripheral raw aspect: invalid (NULL) parameters"); + return false; + } if (peripheral->aspects != NULL) { for (int i = 0; i < peripheral->aspects->len; ++i) { char *aspect = g_array_index(peripheral->aspects, char *, i); @@ -773,32 +817,33 @@ bool set_peripheral_raw_aspect(t_config_peripheral *peripheral, const char *valu * @param value on or off * @return true if successful, otherwise false */ -bool set_peripheral_state(const char *id, const char *value) { - t_config_peripheral *peripheral = get_object(TYPE_PERIPHERAL, id); - if (peripheral == NULL) +static bool set_peripheral_state(const char *id, const char *value) { + if (id == NULL || value == NULL) { + syslog_server(LOG_ERR, "Set peripheral state: invalid (NULL) parameters"); return false; - - if (string_equals(value, "on")) { - if (string_equals(peripheral->type, "onebit")) { - return set_peripheral_raw_aspect(peripheral, "high"); - } - + } + t_config_peripheral *peripheral = get_object(TYPE_PERIPHERAL, id); + if (peripheral == NULL) { return false; } - if (string_equals(value, "off")) { - if (string_equals(peripheral->type, "onebit")) { + if (string_equals(peripheral->type, "onebit")) { + if (string_equals(value, "on")) { + return set_peripheral_raw_aspect(peripheral, "high"); + } else if (string_equals(value, "off")) { return set_peripheral_raw_aspect(peripheral, "low"); } - - return false; } return false; } char *track_state_get_value(const char *id) { + if (id == NULL) { + syslog_server(LOG_ERR, "Track state get value: invalid (NULL) id"); + return ""; + } char *result = NULL; - e_config_type config_type = get_track_state_type(id); + e_config_type config_type = get_track_state_accessory_type(id); void *obj = get_object(config_type, id); if (obj != NULL) { if (config_type == TYPE_POINT) { @@ -813,7 +858,7 @@ char *track_state_get_value(const char *id) { } bidib_free_unified_accessory_state_query(state_query); } else if (config_type == TYPE_SIGNAL) { - result = get_signal_state(id); + result = strdup(get_signal_state(id)); } else if (config_type == TYPE_PERIPHERAL) { t_bidib_peripheral_state_query state_query = bidib_get_peripheral_state(id); if (state_query.available) { @@ -829,9 +874,11 @@ char *track_state_get_value(const char *id) { } if (result != NULL) { + // Add to cache such that the memory can be freed later via + // bahn_data_util_free_cached_track_state add_cache_str(result); } else { - result = static_empty_str; + result = ""; } syslog_server(LOG_DEBUG, "Get track state: %s => %s", id, result); @@ -839,7 +886,11 @@ char *track_state_get_value(const char *id) { } bool track_state_set_value(const char *id, const char *value) { - e_config_type config_type = get_track_state_type(id); + if (id == NULL || value == NULL) { + syslog_server(LOG_ERR, "Track state set value: invalid (NULL) parameters"); + return false; + } + e_config_type config_type = get_track_state_accessory_type(id); bool result = false; switch (config_type) { case TYPE_POINT: @@ -873,28 +924,53 @@ bool track_state_set_value(const char *id, const char *value) { } bool is_segment_occupied(const char *id) { + if (id == NULL) { + return false; + } bool result = false; if (g_hash_table_contains(config_data.table_segments, id)) { t_bidib_segment_state_query state_query = bidib_get_segment_state(id); result = state_query.known && state_query.data.occupied; bidib_free_segment_state_query(state_query); + } else { + syslog_server(LOG_WARNING, "Is segment occupied: unknown segment %s", id); } return result; } bool is_type_segment(const char *id) { + if (id == NULL) { + return false; + } bool result = g_hash_table_contains(config_data.table_segments, id); return result; } bool is_type_signal(const char *id) { + if (id == NULL) { + return false; + } bool result = g_hash_table_contains(config_data.table_signals, id); return result; } +bool is_type_point(const char *id) { + if (id == NULL) { + return false; + } + bool result = g_hash_table_contains(config_data.table_points, id); + + syslog_server(LOG_DEBUG, "Is %s a point: %s", id, result ? "true" : "false"); + return result; +} + int train_state_get_speed(const char *train_id) { + if (train_id == NULL) { + syslog_server(LOG_ERR, "Train state get speed (km/h): invalid (NULL) train_id"); + return 0; + } int result = 0; if (g_hash_table_contains(config_data.table_trains, train_id)) { t_bidib_train_speed_kmh_query kmh_query = bidib_get_train_speed_kmh(train_id); @@ -910,6 +986,10 @@ int train_state_get_speed(const char *train_id) { } bool train_state_set_speed(const char *train_id, int speed) { + if (train_id == NULL) { + syslog_server(LOG_ERR, "Train state set speed: invalid (NULL) train_id"); + return false; + } bool result = false; if (g_hash_table_contains(config_data.table_trains, train_id)) { const int grab_id = train_get_grab_id(train_id); @@ -927,7 +1007,16 @@ bool train_state_set_speed(const char *train_id, int speed) { return result; } +bool train_known(const char *train_id) { + // Shorthand/alias for checking if a train with a certain ID is defined in the config + return train_id != NULL && g_hash_table_contains(config_data.table_trains, train_id); +} + char *config_get_point_position(const char *route_id, const char *point_id) { + if (route_id == NULL || point_id == NULL) { + syslog_server(LOG_ERR, "Get route point position: invalid (NULL) parameters"); + return ""; + } void *obj = get_object(TYPE_ROUTE, route_id); char *result = NULL; @@ -942,12 +1031,16 @@ char *config_get_point_position(const char *route_id, const char *point_id) { } } - result = result != NULL ? result : static_empty_str; + result = result != NULL ? result : ""; syslog_server(LOG_DEBUG, "Get route point position: %s.%s => %s", route_id, point_id, result); return result; } char *config_get_block_id_of_segment(const char *seg_id) { + if (seg_id == NULL) { + syslog_server(LOG_ERR, "Get block id of segment: invalid (NULL) seg_id"); + return ""; + } GHashTableIter iterator; g_hash_table_iter_init(&iterator, config_data.table_blocks); @@ -977,13 +1070,12 @@ char *config_get_block_id_of_segment(const char *seg_id) { } } } - - return NULL; + return ""; } char *config_get_module_name() { if (config_data.module_name == NULL) { - return static_empty_str; + return ""; } else { return config_data.module_name; } diff --git a/server/src/bahn_data_util.h b/server/src/bahn_data_util.h index d65a4518..b0f636b6 100644 --- a/server/src/bahn_data_util.h +++ b/server/src/bahn_data_util.h @@ -30,16 +30,51 @@ #include +/** + * @brief Initialize the interlocking table and load/parse config files from the config-directory. + * + * @param config_dir directory containing the config files + * @return true if initialization succeeded, otherwise false + */ bool bahn_data_util_initialise_config(const char *config_dir); +/** + * @brief Free all loaded config data (incl. interlocking table) + * + */ void bahn_data_util_free_config(); +/** + * @brief Returns true if str1 is (lexic.) equal to str2. + * Returns false if either parameter is NULL. + * + */ bool string_equals(const char *str1, const char *str2); +/** + * @brief Initialize the cached-track-state, has to be called before one or more calls to + * `track_state_get_value`. + * + */ void bahn_data_util_init_cached_track_state(); +/** + * @brief Frees the cached-track-state, has to be called after one or more calls to + * `track_state_get_value` (invalidates/frees any strings returned by track_state_get_value). + * + */ void bahn_data_util_free_cached_track_state(); +/** + * @brief Get the route ids of routes that start at a specific source signal and end at a specific + * destination signal. Resulting list/array of route ids is written to out-param `route_ids`. + * Caller is responsible to ensure `route_ids` is big enough to hold all ids. + * + * @param src_signal_id source signal + * @param dst_signal_id destination signal + * @param route_ids out-parameter, ids of routes... + * @return int amount of routes found and written to route_ids. + */ int interlocking_table_get_routes(const char *src_signal_id, const char *dst_signal_id, char *route_ids[]); char *config_get_scalar_string_value(const char *type, const char *id, const char *prop_name); @@ -52,6 +87,8 @@ bool config_get_scalar_bool_value(const char *type, const char *id, const char * int config_get_array_string_value(const char *type, const char *id, const char *prop_name, char *data[]); +int config_get_array_int_value(const char *type, const char *id, const char *prop_name, int data[]); + int config_get_array_float_value(const char *type, const char *id, const char *prop_name, float data[]); int config_get_array_bool_value(const char *type, const char *id, const char *prop_name, bool data[]); @@ -68,10 +105,14 @@ bool is_type_segment(const char *id); bool is_type_signal(const char *id); +bool is_type_point(const char *id); + int train_state_get_speed(const char *train_id); bool train_state_set_speed(const char *train_id, int speed); +bool train_known(const char *train_id); + char *config_get_point_position(const char *route_id, const char *point_id); char *config_get_block_id_of_segment(const char *seg_id); diff --git a/server/src/check_route_sectional/check_route_sectional_direct.c b/server/src/check_route_sectional/check_route_sectional_direct.c index ef29280f..6ce1a871 100644 --- a/server/src/check_route_sectional/check_route_sectional_direct.c +++ b/server/src/check_route_sectional/check_route_sectional_direct.c @@ -80,13 +80,13 @@ static const char* crossing2[7] = {"signal9", "signal14", "signal15", "signal24" // Don't forget to update this when changing the entry signal arrays above! // -> If we ever get constexpr or consteval, shall compute this value at compile-time -static const size_t longest_entry_signals_array_len = 14; +static const unsigned int longest_entry_signals_array_len = 14; static const char** entry_signals_mapping[44] = {block1, block2, block3, block4, block5, block6, block7, block8and15, block9, block10, block11, block12to13, block14, block16to17, block18, block19to22, p1, p2, p3, p4, p5, p6to7, p8to9, p10, p11, p12, p13, p14, p15to16, p17, p18a, p18b, p19, p20to21, p22, p23, p24, p25, p26, p27, p28, p29, crossing1, crossing2}; -// Returns size_t between 0 and 43 (both inclusive) if segment_id is valid; returns -// 65535 (minimum max of size_t) if segment_id unknown or NULL -size_t entry_signals_lookup(const char* segment_id); +// Returns unsigned int between 0 and 43 (both inclusive) if segment_id is valid; returns +// 65535 if segment_id unknown or NULL +unsigned int entry_signals_lookup(const char* segment_id); // If any signal controlling entry into the railway network section in which segment_id lies // is in a permissive aspect (GO, SHUNT), return true. @@ -97,7 +97,7 @@ bool is_any_entry_signal_permissive(const char* segment_id); // and the last path item of type signal whose index is lower than path_segment_index is occupied. // If route has no path elements, or if path_segment_index is larger than the length of the // route, returns false. -bool is_any_segment_after_preceding_signal_until_segment_occupied(const t_interlocking_route *route, size_t path_segment_index); +bool is_any_segment_after_preceding_signal_until_segment_occupied(const t_interlocking_route *route, unsigned int path_segment_index); @@ -111,20 +111,21 @@ bool is_route_conflict_safe_sectional(const char *granted_route_id, const char * } bool encountered_signal = true; // Search backwards in granted route to minimize duplicate lookups - for (size_t gr_i = granted_route->path->len; gr_i > 0; --gr_i) { + for (unsigned int gr_i = granted_route->path->len; gr_i > 0; --gr_i) { const char* gr_path_item = g_array_index(granted_route->path, char*, gr_i - 1); if (gr_path_item == NULL) { continue; } if (is_type_signal(gr_path_item)) { // Further optimization: Only set encountered_signal to true if this is NOT a distant signal. + // -> but distant signals are currently (Jan. 2025) not contained in route def., so doesnt matter. encountered_signal = true; continue; } if (!is_type_segment(gr_path_item)) { continue; } - for (size_t re_i = 0; re_i < requested_route->path->len; ++re_i) { + for (unsigned int re_i = 0; re_i < requested_route->path->len; ++re_i) { const char* re_path_item = g_array_index(requested_route->path, char*, re_i); if (re_path_item == NULL) { continue; @@ -146,7 +147,7 @@ bool is_route_conflict_safe_sectional(const char *granted_route_id, const char * } -bool is_any_segment_after_preceding_signal_until_segment_occupied(const t_interlocking_route *route, size_t path_segment_index) { +bool is_any_segment_after_preceding_signal_until_segment_occupied(const t_interlocking_route *route, unsigned int path_segment_index) { if (route == NULL || route->path == NULL || route->path->len == 0) { return false; } @@ -154,8 +155,8 @@ bool is_any_segment_after_preceding_signal_until_segment_occupied(const t_interl return false; } // Loop from index to start checking at, decrementing until first signal is encountered. - for (long long i = path_segment_index; i >= 0; --i) { - const char* path_item = g_array_index(route->path, char*, (size_t) i); + for (long i = (long) path_segment_index; i >= 0; --i) { + const char* path_item = g_array_index(route->path, char*, i); if (is_type_segment(path_item)) { // return true if segment is occupied, otherwise continue searching. if (is_segment_occupied(path_item)) { @@ -174,7 +175,7 @@ bool is_any_entry_signal_permissive(const char* segment_id) { return false; } // Query for the entry signals relevant for segment_id - size_t entry_signals_lookup_index = entry_signals_lookup(segment_id); + unsigned int entry_signals_lookup_index = entry_signals_lookup(segment_id); if (entry_signals_lookup_index >= 65535) { return false; } @@ -183,7 +184,7 @@ bool is_any_entry_signal_permissive(const char* segment_id) { return false; } // Check state of each entry signal - for (size_t sig_i = 0; sig_i < longest_entry_signals_array_len; ++sig_i) { + for (unsigned int sig_i = 0; sig_i < longest_entry_signals_array_len; ++sig_i) { const char* entry_signal_item = entry_signals_for_segment[sig_i]; if (strcmp(entry_signal_item, "_end_") == 0) { // End of entry_signals_for_segment @@ -208,7 +209,7 @@ bool is_any_entry_signal_permissive(const char* segment_id) { } -size_t entry_signals_lookup(const char* segment_id) { +unsigned int entry_signals_lookup(const char* segment_id) { if (segment_id == NULL) { return 65535; } else if (strcmp(segment_id,"seg1") == 0 || strcmp(segment_id,"seg2") == 0 || strcmp(segment_id,"seg3") == 0) { // block 1 diff --git a/server/src/communication_utils.c b/server/src/communication_utils.c new file mode 100644 index 00000000..e7aec353 --- /dev/null +++ b/server/src/communication_utils.c @@ -0,0 +1,122 @@ +/* + * + * Copyright (C) 2024 University of Bamberg, Software Technologies Research Group + * , + * + * This file is part of the SWTbahn command line interface (swtbahn-cli), which is + * a client-server application to interactively control a BiDiB model railway. + * + * swtbahn-cli is licensed under the GNU GENERAL PUBLIC LICENSE (Version 3), see + * the LICENSE file at the project's top-level directory for details or consult + * . + * + * swtbahn-cli is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or any later version. + * + * swtbahn-cli is a RESEARCH PROTOTYPE and distributed WITHOUT ANY WARRANTY, without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * The following people contributed to the conception and realization of the + * present swtbahn-cli (in alphabetic order by surname): + * + * - Bernhard Luedtke + * + */ + +#include "communication_utils.h" + +#include "server.h" // for logging + +#include + +bool send_common_feedback(onion_response *res, int status_code, const char* message) { + return send_single_str_field_feedback(res, status_code, "msg", message); +} + +bool send_common_feedback_miss_param_helper(onion_response *res, int status_code, const char* param_name) { + if (res == NULL) { + return false; + } + onion_response_set_code(res, status_code); + if (param_name != NULL && strlen(param_name) > 0) { + return onion_response_printf(res, "{\"msg\":\"missing parameter %s\"}", param_name) >= 0; + } else { + return true; + } +} + +/// NOTE: This function / helper might better fit into a general util class for handlers, +/// but we don't have that. +bool handle_param_miss_check(onion_response *res, const char *request_log_name, + const char *param_name, const char *param_value) { + if (param_value != NULL) { + return false; + } + if (send_common_feedback_miss_param_helper(res, HTTP_BAD_REQUEST, param_name)) { + syslog_server(LOG_ERR, "Request: %s - missing parameter %s", request_log_name, param_name); + } else { + syslog_server(LOG_ERR, + "Request: %s - missing parameter %s - but sending msg to client failed", + request_log_name, param_name); + } + return true; +} + +bool send_some_gstring_and_free(onion_response *res, int status_code, GString *gstr) { + if (res == NULL) { + if (gstr != NULL) { + g_string_free(gstr, true); + gstr = NULL; + } + return false; + } + onion_response_set_code(res, status_code); + if (gstr == NULL) { + return true; + } + bool ret = onion_response_printf(res, "%s", gstr->str) >= 0; + g_string_free(gstr, true); + gstr = NULL; + return ret; +} + +bool send_single_str_field_feedback(onion_response *res, int status_code, const char* field_name, + const char* field_value) { + if (res == NULL) { + return false; + } + onion_response_set_code(res, status_code); + if (field_name != NULL && strlen(field_name) > 0 + && field_value != NULL && strlen(field_value) > 0) { + return onion_response_printf(res, "{\"%s\":\"%s\"}", field_name, field_value) >= 0; + } else { + return true; + } +} + +bool send_some_cstring(onion_response *res, int status_code, const char *cstr) { + if (res == NULL) { + return false; + } + onion_response_set_code(res, status_code); + if (cstr == NULL) { + return true; + } else { + return onion_response_printf(res, "%s", cstr) >= 0; + } +} + +onion_connection_status handle_req_run_or_method_fail(onion_response *res, bool is_running, + const char *caller_logname) { + if (is_running) { + syslog_server(LOG_WARNING, "Request: %s - wrong request type", caller_logname); + onion_response_set_code(res, HTTP_METHOD_NOT_ALLOWED); + return OCS_NOT_IMPLEMENTED; + } else { + syslog_server(LOG_ERR, "Request: %s - system not running", caller_logname); + onion_response_set_code(res, HTTP_SERVICE_UNAVAILABLE); + return OCS_PROCESSED; + } +} diff --git a/server/src/communication_utils.h b/server/src/communication_utils.h new file mode 100644 index 00000000..f1fe2057 --- /dev/null +++ b/server/src/communication_utils.h @@ -0,0 +1,133 @@ +/* + * + * Copyright (C) 2024 University of Bamberg, Software Technologies Research Group + * , + * + * This file is part of the SWTbahn command line interface (swtbahn-cli), which is + * a client-server application to interactively control a BiDiB model railway. + * + * swtbahn-cli is licensed under the GNU GENERAL PUBLIC LICENSE (Version 3), see + * the LICENSE file at the project's top-level directory for details or consult + * . + * + * swtbahn-cli is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or any later version. + * + * swtbahn-cli is a RESEARCH PROTOTYPE and distributed WITHOUT ANY WARRANTY, without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * The following people contributed to the conception and realization of the + * present swtbahn-cli (in alphabetic order by surname): + * + * - Bernhard Luedtke + * + */ + +#ifndef JSON_COMMUNICATION_UTILS_H +#define JSON_COMMUNICATION_UTILS_H + +#include +#include +#include + +// The onion library we use has no define for the 409 code. Add it here. +#define CUSTOM_HTTP_CODE_CONFLICT 409 + +/** + * @brief Constructs the common-feedback json and sends it via the response res. + * + * @param res the response over which to send. Shall not be NULL, otherwise no sending takes place. + * @param status_code http status code to set for the response to be sent + * @param message intended value of the string "msg" field in the json to be sent. + * If message is NULL or empty, no response will be sent at all, just the code will be set. + * @return true if parameters were valid + * @return false if parameters were invalid or an internal error occurred (nothing will be sent) + */ +bool send_common_feedback(onion_response *res, int status_code, const char* message); + +/** + * @brief Constructs the common-feedback json with field prefilled for a missing parameter msg, + * and sends it via the response res. + * + * @param res the response over which to send. Shall not be NULL, otherwise no sending takes place. + * @param status_code http status code to set for the response to be sent + * @param param_name name of the missing parameter + * @return true if parameters were valid + * @return false if parameters were invalid or an internal error occurred (nothing will be sent) + */ +bool send_common_feedback_miss_param_helper(onion_response *res, int status_code, const char* param_name); + +/** + * @brief Checks if a parameter is missing by way of checking if param_value is NULL. + * If it is NULL, sends common response with parameter missing message, and logs to syslog + * + * @param res the response over which to send if the parameter is missing. + * @param request_log_name the request calling this check, used for logging + * @param param_name name of the parameter being checked + * @param param_value value of the parameter + * @return true if the parameter (value) is missing/NULL + * @return false otherwise. + */ +bool handle_param_miss_check(onion_response *res, const char *request_log_name, + const char *param_name, const char *param_value); + +/** + * @brief Sends the content of gstr via the response res. Then free's the GString, + * i.e., this is a transfer of ownership. + * + * @param res the response over which to send. Shall not be NULL. + * @param status_code http status code to set for the response to be sent + * @param gstr the gstring whose contents to send. If it is NULL, only the code will be set, nothing + * will be sent. The gstr gstring will be free'd if it is not null, regardless of the return value. + * @return true if parameters were valid and sending succeeded, + * @return false otherwise. + */ +bool send_some_gstring_and_free(onion_response *res, int status_code, GString *gstr); + +/** + * @brief Constructs and sends a json with a single field, named by field_name, and its string value + * given by field_value. Sets passed status code before sending anything. + * Note: if the field value is empty, nothing will be sent (only the status code will be set). + * + * @param res the response over which to send. Shall not be NULL. + * @param status_code http status code to set for the response to be sent + * @param field_name the field identifier + * @param field_value the field value + * @return true if parameters were valid + * @return false otherwise. + */ +bool send_single_str_field_feedback(onion_response *res, int status_code, const char* field_name, + const char* field_value); + +// This does not free the passed string to be sent (in contrast to the gstring version). +/** + * @brief Like send_some_gstring_and_free, but with a c-style 0-terminated string, + * but does NOT free the string. + * + * @param res the response over which to send. Shall not be NULL. + * @param status_code http status code to set for the response to be sent + * @param cstr the c-style 0-terminated string whose contents to send. + * If this is NULL, only the code will be set, nothing will be sent. + * @return true + * @return false + */ +bool send_some_cstring(onion_response *res, int status_code, const char *cstr); + +/** + * @brief Helper for logging to syslog and responding to request + * that either the system is not running (but it has to be running for the request in question) + * or that an incorrect HTTP method was used. + * + * @param res the response over which to send. Shall not be NULL. + * @param is_running whether the system is running or not. Pass `true` if this does not matter + * for the request in question. + * @param caller_logname name of the request endpoint to log + * @return onion_connection_status OCS_NOT_IMPLEMENTED if wrong request type, + * otherwise OCS_PROCESSED + */ +onion_connection_status handle_req_run_or_method_fail(onion_response *res, bool is_running, + const char *caller_logname); + +#endif // JSON_COMMUNICATION_UTILS_H \ No newline at end of file diff --git a/server/src/dyn_containers.forec b/server/src/dyn_containers.forec index 64f463e7..4d78f27a 100644 --- a/server/src/dyn_containers.forec +++ b/server/src/dyn_containers.forec @@ -929,7 +929,7 @@ thread interlockerInstance0(void) { // Reset the interlocker so that it can execute its algorithm from the start dynlib_interlocker_reset(&interlockers[forec_intern_output_interlocker_instance_0.interlocker_type], &interlockerInstanceData[instance]); - forec_intern_output_interlocker_instance_0.has_reset = true; + forec_intern_output_interlocker_instance_0.has_reset = true; if (interlockerInstanceData[instance].route_id == NULL) { interlockerInstanceData[instance].route_id = ""; @@ -1023,7 +1023,7 @@ thread interlockerInstance1(void) { // Reset the interlocker so that it can execute its algorithm from the start dynlib_interlocker_reset(&interlockers[forec_intern_output_interlocker_instance_1.interlocker_type], &interlockerInstanceData[instance]); - forec_intern_output_interlocker_instance_1.has_reset = true; + forec_intern_output_interlocker_instance_1.has_reset = true; if (interlockerInstanceData[instance].route_id == NULL) { interlockerInstanceData[instance].route_id = ""; @@ -1116,7 +1116,7 @@ thread interlockerInstance2(void) { // Reset the interlocker so that it can execute its alglorithm from the start dynlib_interlocker_reset(&interlockers[forec_intern_output_interlocker_instance_2.interlocker_type], &interlockerInstanceData[instance]); - forec_intern_output_interlocker_instance_2.has_reset = true; + forec_intern_output_interlocker_instance_2.has_reset = true; if (interlockerInstanceData[instance].route_id == NULL) { interlockerInstanceData[instance].route_id = ""; @@ -1209,7 +1209,7 @@ thread interlockerInstance3(void) { // Reset the interlocker so that it can execute its alglorithm from the start dynlib_interlocker_reset(&interlockers[forec_intern_output_interlocker_instance_3.interlocker_type], &interlockerInstanceData[instance]); - forec_intern_output_interlocker_instance_3.has_reset = true; + forec_intern_output_interlocker_instance_3.has_reset = true; if (interlockerInstanceData[instance].route_id == NULL) { interlockerInstanceData[instance].route_id = ""; @@ -1331,7 +1331,7 @@ void copyInterlockerInputs(t_forec_intern_input_interlocker *internal, void copyInterlockerOutputs(struct t_interlocker_io *external, t_forec_intern_output_interlocker *internal) { - external->output_in_use = internal->in_use; + external->output_in_use = internal->in_use; strncpy(external->output_name, internal->name, name_max); } @@ -1468,7 +1468,7 @@ void printout(const char *threadName, const dynlib_status status, const dynlib_d break; case (DYNLIB_COMPILE_SHARED_BAHNDSL_ERR): syslog_server(LOG_ERR, "%s: Could not compile BahnDSL file into shared library '%s'", threadName, library->filepath); - break; + break; case (DYNLIB_COMPILE_SHARED_SCCHARTS_ERR): syslog_server(LOG_ERR, "%s: Could not compile C file into shared library '%s'", threadName, library->filepath); break; diff --git a/server/src/dyn_containers_interface.c b/server/src/dyn_containers_interface.c index cc07351a..516ca612 100644 --- a/server/src/dyn_containers_interface.c +++ b/server/src/dyn_containers_interface.c @@ -44,7 +44,7 @@ pthread_mutex_t dyn_containers_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_t dyn_containers_thread; static pthread_t dyn_containers_actuate_thread; -t_dyn_containers_interface *dyn_containers_interface; +t_dyn_containers_interface *dyn_containers_interface = NULL; #define MICROSECOND 1 static const int let_period_us = 10000 * MICROSECOND; // 0.01 seconds @@ -55,14 +55,16 @@ static const key_t shm_key = 1234; long long dyn_containers_actuate_reaction_counter = 0; -void dyn_containers_reset_interface( - t_dyn_containers_interface * const dyn_containers_interface) { +static void dyn_containers_reset_interface(t_dyn_containers_interface *dyn_containers_interface) { + if (dyn_containers_interface == NULL) { + return; + } dyn_containers_interface->running = false; dyn_containers_interface->terminate = false; - + dyn_containers_interface->let_period_us = let_period_us; - for (size_t i = 0; i < TRAIN_ENGINE_COUNT_MAX; i++) { + for (int i = 0; i < TRAIN_ENGINE_COUNT_MAX; i++) { dyn_containers_interface->train_engines_io[i] = (struct t_train_engine_io) { .input_load = false, @@ -73,7 +75,7 @@ void dyn_containers_reset_interface( .output_name = "" }; } - for (size_t i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { + for (int i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { dyn_containers_interface->train_engine_instances_io[i] = (struct t_train_engine_instance_io) { .input_grab = false, @@ -92,7 +94,7 @@ void dyn_containers_reset_interface( }; } - for (size_t i = 0; i < INTERLOCKER_COUNT_MAX; i++) { + for (int i = 0; i < INTERLOCKER_COUNT_MAX; i++) { dyn_containers_interface->interlockers_io[i] = (struct t_interlocker_io) { .input_load = false, @@ -101,7 +103,7 @@ void dyn_containers_reset_interface( .output_in_use = false }; } - for (size_t i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { + for (int i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { dyn_containers_interface->interlocker_instances_io[i] = (struct t_interlocker_instance_io) { .input_grab = false, @@ -112,7 +114,7 @@ void dyn_containers_reset_interface( .input_src_signal_id = "", .input_dst_signal_id = "", .input_train_id = "", - + .output_in_use = false, .output_terminated = false, .output_interlocker_type = -1 @@ -120,6 +122,45 @@ void dyn_containers_reset_interface( } } +static void dyn_actuate_specific_engine(int index_in_grabbed_trains) { + int i = index_in_grabbed_trains; + if (i >= TRAIN_ENGINE_INSTANCE_COUNT_MAX) { + return; + } else if (!grabbed_trains[i].is_valid || grabbed_trains[i].name == NULL) { + return; + } + + const int dyn_cont_eng_instance = grabbed_trains[i].dyn_containers_engine_instance; + + struct t_train_engine_instance_io *eng_instance = + &dyn_containers_interface->train_engine_instances_io[dyn_cont_eng_instance]; + if (eng_instance->output_in_use) { + if (eng_instance->output_nominal_speed != eng_instance->output_nominal_speed_pre + || eng_instance->output_nominal_forwards != eng_instance->output_nominal_forwards_pre) { + if (bidib_set_train_speed(grabbed_trains[i].name->str, + eng_instance->output_nominal_forwards + ? eng_instance->output_nominal_speed + : -eng_instance->output_nominal_speed, + grabbed_trains[i].track_output)) { + syslog_server(LOG_ERR, + "Dyn containers actuate - train: %s - unable to set train speed", + grabbed_trains[i].name->str); + } else { + bidib_flush(); + syslog_server(LOG_NOTICE, + "Dyn containers actuate - train: %s speed: %d - set train speed", + grabbed_trains[i].name->str, + eng_instance->output_nominal_forwards + ? eng_instance->output_nominal_speed + : -eng_instance->output_nominal_speed); + eng_instance->output_nominal_speed_pre = eng_instance->output_nominal_speed; + eng_instance->output_nominal_forwards_pre = eng_instance->output_nominal_forwards; + } + } + } + +} + // Execute the outputs of the train engines and interlockers via the BiDiB library static void *dyn_containers_actuate(void *_) { while (!running) { @@ -130,63 +171,30 @@ static void *dyn_containers_actuate(void *_) { do { pthread_mutex_lock(&grabbed_trains_mutex); pthread_mutex_lock(&dyn_containers_mutex); - - for (size_t i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { - if (grabbed_trains[i].is_valid && grabbed_trains[i].name != NULL) { - const int dyn_containers_engine_instance = - grabbed_trains[i].dyn_containers_engine_instance; - - struct t_train_engine_instance_io * const engine_instance = - &dyn_containers_interface->train_engine_instances_io[dyn_containers_engine_instance]; - if (engine_instance->output_in_use) { - if (engine_instance->output_nominal_speed != engine_instance->output_nominal_speed_pre - || engine_instance->output_nominal_forwards != engine_instance->output_nominal_forwards_pre) { - if (bidib_set_train_speed(grabbed_trains[i].name->str, - engine_instance->output_nominal_forwards - ? engine_instance->output_nominal_speed - : -engine_instance->output_nominal_speed, - grabbed_trains[i].track_output)) { - syslog_server(LOG_ERR, - "Dyn containers actuate - train: %s - invalid parameters", - grabbed_trains[i].name->str); - } else { - bidib_flush(); - syslog_server(LOG_NOTICE, - "Dyn containers actuate - train: %s speed: %d - set train speed", - grabbed_trains[i].name->str, - engine_instance->output_nominal_forwards - ? engine_instance->output_nominal_speed - : -engine_instance->output_nominal_speed); - engine_instance->output_nominal_speed_pre = engine_instance->output_nominal_speed; - engine_instance->output_nominal_forwards_pre = engine_instance->output_nominal_forwards; - } - } - } - } + + for (int i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { + dyn_actuate_specific_engine(i); } pthread_mutex_unlock(&dyn_containers_mutex); pthread_mutex_unlock(&grabbed_trains_mutex); - + dyn_containers_actuate_reaction_counter++; usleep(let_period_us); } while (running); - + dyn_containers_interface->terminate = true; - // TODO: Ensure that all trains really stop + /// TODO: Ensure that all trains really stop pthread_exit(NULL); } int dyn_containers_start(void) { - dyn_containers_shm_create(&shm_config, shm_permissions, shm_key, - &dyn_containers_interface); + dyn_containers_shm_create(&shm_config, shm_permissions, shm_key, &dyn_containers_interface); dyn_containers_reset_interface(dyn_containers_interface); - pthread_create(&dyn_containers_thread, NULL, - forec_dyn_containers, NULL); - pthread_create(&dyn_containers_actuate_thread, NULL, - dyn_containers_actuate, NULL); + pthread_create(&dyn_containers_thread, NULL, forec_dyn_containers, NULL); + pthread_create(&dyn_containers_actuate_thread, NULL, dyn_containers_actuate, NULL); return 0; } @@ -199,45 +207,38 @@ void dyn_containers_stop(void) { syslog_server(LOG_NOTICE, "Closed dynamic library containers"); } -const bool dyn_containers_is_running(void) { - return dyn_containers_interface->running; +bool dyn_containers_is_running(void) { + return dyn_containers_interface != NULL && dyn_containers_interface->running; } // General function to obtain a shared memory segment based on a given key -void dyn_containers_shm_create(t_dyn_shm_config * const shm_config, - const int shm_permissions, const key_t shm_key, - t_dyn_containers_interface ** const shm_payload) { +void dyn_containers_shm_create(t_dyn_shm_config *shm_config, int shm_permissions, key_t shm_key, + t_dyn_containers_interface **shm_payload) { // Create our shared memory segment with the given shmKey. shm_config->size = 1 * sizeof(t_dyn_containers_interface); shm_config->permissions = shm_permissions; shm_config->key = shm_key; - shm_config->shmid = shmget(shm_config->key, shm_config->size, - shm_config->permissions); + shm_config->shmid = shmget(shm_config->key, shm_config->size, shm_config->permissions); if (shm_config->shmid == -1) { int error_number = errno; - syslog_server(LOG_ERR, - "Error getting shared memory segment: errono %d", - error_number); + syslog_server(LOG_ERR, "Error getting shared memory segment: errono %d", error_number); return; } - + // Attach the shared memory segment to our data space *shm_payload = shmat(shm_config->shmid, NULL, 0); if (shm_payload == (void *) -1) { - syslog_server(LOG_ERR, - "Error attaching shared memory segment to process' data space"); + syslog_server(LOG_ERR, "Error attaching shared memory segment to process' data space"); return; - } + } } // Detaches the shared memory segment from our data space -void dyn_containers_shm_detach(t_dyn_containers_interface ** const shm_payload) { +void dyn_containers_shm_detach(t_dyn_containers_interface **shm_payload) { if (shmdt(*shm_payload) == -1) { int error_number = errno; - syslog_server(LOG_ERR, - "Error detaching shared memory segment: errono %d", - error_number); + syslog_server(LOG_ERR, "Error detaching shared memory segment: errono %d", error_number); return; } @@ -245,24 +246,29 @@ void dyn_containers_shm_detach(t_dyn_containers_interface ** const shm_payload) } // Deletes the shared memory segment from our data space -void dyn_containers_shm_delete(t_dyn_shm_config * const shm_config) { +void dyn_containers_shm_delete(t_dyn_shm_config *shm_config) { + if (shm_config == NULL) { + syslog_server(LOG_ERR, + "Error deleting shared memory segment: invalid (NULL) shm_config"); + return; + } shm_config->shmid = shmctl(shm_config->shmid, IPC_RMID, NULL); if (shm_config->shmid == -1) { int error_number = errno; - syslog_server(LOG_ERR, - "Error deleting shared memory segment: errono %d", - error_number); + syslog_server(LOG_ERR, "Error deleting shared memory segment: errono %d", error_number); return; } } // Finds the first available slot for a train engine -// Can only be called while the dyn_containers_mutex is locked -const int dyn_containers_get_free_engine_slot(void) { +// Shall only be called while the dyn_containers_mutex is locked +int dyn_containers_get_free_engine_slot(void) { + if (dyn_containers_interface == NULL) { + return -1; + } for (int i = 0; i < TRAIN_ENGINE_COUNT_MAX; i++) { - struct t_train_engine_io * const train_engine_io = - &dyn_containers_interface->train_engines_io[i]; - if (!train_engine_io->output_in_use) { + struct t_train_engine_io *tr_eng_io = &dyn_containers_interface->train_engines_io[i]; + if (!tr_eng_io->output_in_use) { return i; } } @@ -270,112 +276,118 @@ const int dyn_containers_get_free_engine_slot(void) { } // Finds the slot of a train engine -// Can only be called while the dyn_containers_mutex is locked -const int dyn_containers_get_engine_slot(const char name[]) { +// Shall only be called while the dyn_containers_mutex is locked +int dyn_containers_get_engine_slot(const char name[]) { + if (dyn_containers_interface == NULL) { + return -1; + } for (int i = 0; i < TRAIN_ENGINE_COUNT_MAX; i++) { - struct t_train_engine_io * const train_engine_io = - &dyn_containers_interface->train_engines_io[i]; - if (train_engine_io->output_in_use && - strcmp(train_engine_io->output_name, name) == 0) { + struct t_train_engine_io *tr_eng_io = &dyn_containers_interface->train_engines_io[i]; + if (tr_eng_io->output_in_use && strcmp(tr_eng_io->output_name, name) == 0) { return i; } } return -1; } - // Loads train engine into specified slot -// Can only be called while the dyn_containers_mutex is locked -void dyn_containers_set_engine(const int engine_slot, const char filepath[]) { - struct t_train_engine_io * const train_engine_io = - &dyn_containers_interface->train_engines_io[engine_slot]; - train_engine_io->input_load = true; - train_engine_io->input_unload = false; - strcpy(train_engine_io->input_filepath, filepath); +// Shall only be called while the dyn_containers_mutex is locked +void dyn_containers_set_engine(int engine_slot, const char filepath[]) { + if (dyn_containers_interface == NULL) { + return; + } + struct t_train_engine_io *tr_eng_io = &dyn_containers_interface->train_engines_io[engine_slot]; + tr_eng_io->input_load = true; + tr_eng_io->input_unload = false; + strcpy(tr_eng_io->input_filepath, filepath); pthread_mutex_unlock(&dyn_containers_mutex); syslog_server(LOG_NOTICE, - "Waiting for train engine %s to be dynamically loaded into slot %d", - filepath, engine_slot); - while (!train_engine_io->output_in_use) { + "Waiting for train engine %s to be dynamically loaded into slot %d", + filepath, engine_slot); + while (!tr_eng_io->output_in_use) { usleep(let_period_us); } pthread_mutex_lock(&dyn_containers_mutex); - train_engine_io->input_load = false; + tr_eng_io->input_load = false; syslog_server(LOG_NOTICE, - "Train engine %s has been dynamically loaded into engine slot %d", - filepath, engine_slot); + "Train engine %s has been dynamically loaded into engine slot %d", + filepath, engine_slot); } // Unloads train engine at specified slot -// Can only be called while the dyn_containers_mutex is locked -bool dyn_containers_free_engine(const int engine_slot) { +// Shall only be called while the dyn_containers_mutex is locked +bool dyn_containers_free_engine(int engine_slot) { + if (dyn_containers_interface == NULL) { + return false; + } // Check that no instance of the train engine is in use - for (size_t i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { - struct t_train_engine_instance_io * const engine_instance = - &dyn_containers_interface->train_engine_instances_io[i]; - if (engine_instance->output_in_use && - engine_instance->output_train_engine_type == engine_slot) { + for (int i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { + struct t_train_engine_instance_io *eng_instance = + &dyn_containers_interface->train_engine_instances_io[i]; + if (eng_instance->output_in_use && eng_instance->output_train_engine_type == engine_slot) { return false; } } - + // Unload the train engine - struct t_train_engine_io * const train_engine_io = - &dyn_containers_interface->train_engines_io[engine_slot]; - train_engine_io->input_load = false; - train_engine_io->input_unload = true; - strcpy(train_engine_io->input_filepath, ""); + struct t_train_engine_io *tr_eng_io = &dyn_containers_interface->train_engines_io[engine_slot]; + tr_eng_io->input_load = false; + tr_eng_io->input_unload = true; + strcpy(tr_eng_io->input_filepath, ""); pthread_mutex_unlock(&dyn_containers_mutex); syslog_server(LOG_NOTICE, - "Waiting for train engine %s at slot %d to be unloaded", - train_engine_io->input_filepath, engine_slot); - while (train_engine_io->output_in_use) { + "Waiting for train engine %s at slot %d to be unloaded", + tr_eng_io->input_filepath, engine_slot); + while (tr_eng_io->output_in_use) { usleep(let_period_us); } pthread_mutex_lock(&dyn_containers_mutex); - train_engine_io->input_unload = false; - syslog_server(LOG_NOTICE, - "Unloaded train engine at slot %d", - engine_slot); + tr_eng_io->input_unload = false; + syslog_server(LOG_NOTICE, "Unloaded train engine at slot %d", engine_slot); return true; } -GString *dyn_containers_get_train_engines(void) { - GString *train_engine_names = g_string_new(NULL); - int i = 0; - for (i = 0; i < TRAIN_ENGINE_COUNT_MAX; i++) { - struct t_train_engine_io * const train_engine_io = - &dyn_containers_interface->train_engines_io[i]; - if (!train_engine_io->output_in_use) { +GArray *dyn_containers_get_train_engines_arr(void) { + if (dyn_containers_interface == NULL) { + return NULL; + } + GArray *train_engine_names = g_array_new(FALSE, FALSE, sizeof(char *)); + pthread_mutex_lock(&dyn_containers_mutex); + for (int i = 0; i < TRAIN_ENGINE_COUNT_MAX; ++i) { + const struct t_train_engine_io *tr_eng_io = &dyn_containers_interface->train_engines_io[i]; + if (!tr_eng_io->output_in_use) { continue; } - // Copy string - if (i != 0) { - g_string_append(train_engine_names, ","); + char *name = strdup(tr_eng_io->output_name); + if (name != NULL) { + g_array_append_val(train_engine_names, name); + } else { + syslog_server(LOG_ERR, "dyn_containers_get_train_engines_arr unable to allocate memory"); } - g_string_append(train_engine_names, train_engine_io->output_name); } + pthread_mutex_unlock(&dyn_containers_mutex); return train_engine_names; } -int dyn_containers_set_train_engine_instance(t_train_data * const grabbed_train, - const char *train, const char *engine) { - if (engine == NULL) { - syslog_server(LOG_ERR, "Could not set train engine because engine was NULL"); +int dyn_containers_set_train_engine_instance(t_train_data *grabbed_train, + const char *train, const char *engine) { + if (engine == NULL || grabbed_train == NULL) { + syslog_server(LOG_ERR, "Set train engine instance: invalid (NULL) parameters"); + return 1; + } else if (dyn_containers_interface == NULL) { return 1; } pthread_mutex_lock(&dyn_containers_mutex); int train_engine_type = -1; for (int i = 0; i < TRAIN_ENGINE_COUNT_MAX; i++) { - struct t_train_engine_io * const train_engine_io = - &dyn_containers_interface->train_engines_io[i]; + struct t_train_engine_io *train_engine_io = &dyn_containers_interface->train_engines_io[i]; if (strcmp(train_engine_io->output_name, engine) == 0) { train_engine_type = i; break; } } - + if (train_engine_type == -1) { pthread_mutex_unlock(&dyn_containers_mutex); syslog_server(LOG_ERR, "Engine %s could not be found", engine); @@ -383,21 +395,21 @@ int dyn_containers_set_train_engine_instance(t_train_data * const grabbed_train, } for (int i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { - struct t_train_engine_instance_io * const train_engine_instance_io = - &dyn_containers_interface->train_engine_instances_io[i]; - if (!train_engine_instance_io->output_in_use) { - train_engine_instance_io->input_grab = true; - train_engine_instance_io->input_train_engine_type = train_engine_type; - train_engine_instance_io->input_requested_speed = 0; - train_engine_instance_io->input_requested_forwards = true; + struct t_train_engine_instance_io *tr_eng_instance_io = + &dyn_containers_interface->train_engine_instances_io[i]; + if (!tr_eng_instance_io->output_in_use) { + tr_eng_instance_io->input_grab = true; + tr_eng_instance_io->input_train_engine_type = train_engine_type; + tr_eng_instance_io->input_requested_speed = 0; + tr_eng_instance_io->input_requested_forwards = true; pthread_mutex_unlock(&dyn_containers_mutex); - + do { usleep(let_period_us); - } while (!train_engine_instance_io->output_in_use); + } while (!tr_eng_instance_io->output_in_use); pthread_mutex_lock(&dyn_containers_mutex); - train_engine_instance_io->input_grab = false; + tr_eng_instance_io->input_grab = false; pthread_mutex_unlock(&dyn_containers_mutex); grabbed_train->dyn_containers_engine_instance = i; @@ -405,53 +417,58 @@ int dyn_containers_set_train_engine_instance(t_train_data * const grabbed_train, return 0; } } - + pthread_mutex_unlock(&dyn_containers_mutex); syslog_server(LOG_ERR, "No engine instances available for train %s", train); return 1; } -void dyn_containers_free_train_engine_instance(const int dyn_containers_engine_instance) { +void dyn_containers_free_train_engine_instance(int dyn_containers_engine_instance) { if (dyn_containers_interface == NULL) { return; } - struct t_train_engine_instance_io * const train_engine_instance_io = + struct t_train_engine_instance_io *tr_eng_instance_io = &dyn_containers_interface->train_engine_instances_io[dyn_containers_engine_instance]; - + pthread_mutex_lock(&dyn_containers_mutex); - train_engine_instance_io->input_release = true; + tr_eng_instance_io->input_release = true; pthread_mutex_unlock(&dyn_containers_mutex); - + do { usleep(let_period_us); - } while (train_engine_instance_io->output_in_use); + } while (tr_eng_instance_io->output_in_use); pthread_mutex_lock(&dyn_containers_mutex); - train_engine_instance_io->input_release = false; + tr_eng_instance_io->input_release = false; pthread_mutex_unlock(&dyn_containers_mutex); - - syslog_server(LOG_NOTICE, "Train instance %d released", dyn_containers_engine_instance); + + syslog_server(LOG_NOTICE, "Train engine instance %d released", dyn_containers_engine_instance); } -void dyn_containers_set_train_engine_instance_inputs(const int dyn_containers_engine_instance, - const int requested_speed, - const char requested_forwards) { - struct t_train_engine_instance_io * const train_engine_instance_io = - &dyn_containers_interface->train_engine_instances_io[dyn_containers_engine_instance]; - +void dyn_containers_set_train_engine_instance_inputs(int dyn_containers_engine_instance, + int requested_speed, + bool requested_forwards) { + if (dyn_containers_interface == NULL) { + return; + } + struct t_train_engine_instance_io *tr_eng_instance_io = + &dyn_containers_interface->train_engine_instances_io[dyn_containers_engine_instance]; + pthread_mutex_lock(&dyn_containers_mutex); - train_engine_instance_io->input_requested_speed = requested_speed; - train_engine_instance_io->input_requested_forwards = requested_forwards; + tr_eng_instance_io->input_requested_speed = requested_speed; + tr_eng_instance_io->input_requested_forwards = requested_forwards; pthread_mutex_unlock(&dyn_containers_mutex); } // Finds the first available slot for a interlocker -// Can only be called while the dyn_containers_mutex is locked -const int dyn_containers_get_free_interlocker_slot(void) { +// Shall only be called while the dyn_containers_mutex is locked +int dyn_containers_get_free_interlocker_slot(void) { + if (dyn_containers_interface == NULL) { + return -1; + } for (int i = 0; i < INTERLOCKER_COUNT_MAX; i++) { - struct t_interlocker_io * const interlocker_io = - &dyn_containers_interface->interlockers_io[i]; + struct t_interlocker_io *interlocker_io = &dyn_containers_interface->interlockers_io[i]; if (!interlocker_io->output_in_use) { return i; } @@ -460,13 +477,14 @@ const int dyn_containers_get_free_interlocker_slot(void) { } // Finds the slot of a interlocker -// Can only be called while the dyn_containers_mutex is locked -const int dyn_containers_get_interlocker_slot(const char name[]) { +// Shall only be called while the dyn_containers_mutex is locked +int dyn_containers_get_interlocker_slot(const char name[]) { + if (dyn_containers_interface == NULL) { + return -1; + } for (int i = 0; i < INTERLOCKER_COUNT_MAX; i++) { - struct t_interlocker_io * const interlocker_io = - &dyn_containers_interface->interlockers_io[i]; - if (interlocker_io->output_in_use && - strcmp(interlocker_io->output_name, name) == 0) { + const struct t_interlocker_io *interlocker_io = &dyn_containers_interface->interlockers_io[i]; + if (interlocker_io->output_in_use && strcmp(interlocker_io->output_name, name) == 0) { return i; } } @@ -474,10 +492,13 @@ const int dyn_containers_get_interlocker_slot(const char name[]) { } // Loads interlocker into specified slot -// Can only be called while the dyn_containers_mutex is locked -void dyn_containers_set_interlocker(const int interlocker_slot, const char filepath[]) { - struct t_interlocker_io * const interlocker_io = - &dyn_containers_interface->interlockers_io[interlocker_slot]; +// Shall only be called while the dyn_containers_mutex is locked +void dyn_containers_set_interlocker(int interlocker_slot, const char filepath[]) { + if (dyn_containers_interface == NULL) { + return; + } + struct t_interlocker_io *interlocker_io = + &dyn_containers_interface->interlockers_io[interlocker_slot]; interlocker_io->input_load = true; interlocker_io->input_unload = false; strcpy(interlocker_io->input_filepath, filepath); @@ -496,45 +517,67 @@ void dyn_containers_set_interlocker(const int interlocker_slot, const char filep } // Unloads interlocker at specified slot -// Can only be called while the dyn_containers_mutex is locked -bool dyn_containers_free_interlocker(const int interlocker_slot) { +// Shall only be called while the dyn_containers_mutex is locked +bool dyn_containers_free_interlocker(int interlocker_slot) { + if (dyn_containers_interface == NULL) { + return false; + } // Check that no instance of the interlocker is in use - for (size_t i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { - struct t_interlocker_instance_io * const interlocker_instance = - &dyn_containers_interface->interlocker_instances_io[i]; + for (int i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { + struct t_interlocker_instance_io *interlocker_instance = + &dyn_containers_interface->interlocker_instances_io[i]; if (interlocker_instance->output_in_use && interlocker_instance->output_interlocker_type == interlocker_slot) { return false; } } - + // Unload the interlocker - struct t_interlocker_io * const interlocker_io = - &dyn_containers_interface->interlockers_io[interlocker_slot]; + struct t_interlocker_io *interlocker_io = + &dyn_containers_interface->interlockers_io[interlocker_slot]; interlocker_io->input_load = false; interlocker_io->input_unload = true; pthread_mutex_unlock(&dyn_containers_mutex); syslog_server(LOG_NOTICE, - "Waiting for interlocker %s at slot %d to be unloaded", - interlocker_io->input_filepath, interlocker_slot); + "Waiting for interlocker %s at slot %d to be unloaded", + interlocker_io->input_filepath, interlocker_slot); strcpy(interlocker_io->input_filepath, ""); while (interlocker_io->output_in_use) { usleep(let_period_us); } pthread_mutex_lock(&dyn_containers_mutex); interlocker_io->input_unload = false; - syslog_server(LOG_NOTICE, - "Unloaded interlocker at slot %d", - interlocker_slot); + syslog_server(LOG_NOTICE, "Unloaded interlocker at slot %d", interlocker_slot); return true; } +GArray *dyn_containers_get_interlockers_arr(void) { + if (dyn_containers_interface == NULL) { + return NULL; + } + GArray *interlocker_names = g_array_new(FALSE, FALSE, sizeof(char *)); + pthread_mutex_lock(&dyn_containers_mutex); + for (int i = 0; i < INTERLOCKER_COUNT_MAX; ++i) { + const struct t_interlocker_io *interlocker_io = + &dyn_containers_interface->interlockers_io[i]; + if (!interlocker_io->output_in_use) { + continue; + } + char *name = strdup(interlocker_io->output_name); + if (name != NULL) { + g_array_append_val(interlocker_names, name); + } else { + syslog_server(LOG_ERR, "dyn_containers_get_interlockers_arr unable to allocate memory"); + } + } + pthread_mutex_unlock(&dyn_containers_mutex); + return interlocker_names; +} + GString *dyn_containers_get_interlockers(void) { GString *interlocker_names = g_string_new(NULL); - int i = 0; - for (i = 0; i < INTERLOCKER_COUNT_MAX; i++) { - struct t_interlocker_io * const interlocker_io = - &dyn_containers_interface->interlockers_io[i]; + for (int i = 0; i < INTERLOCKER_COUNT_MAX; i++) { + struct t_interlocker_io *interlocker_io = &dyn_containers_interface->interlockers_io[i]; if (!interlocker_io->output_in_use) { continue; } @@ -547,24 +590,29 @@ GString *dyn_containers_get_interlockers(void) { return interlocker_names; } -int dyn_containers_set_interlocker_instance(t_interlocker_data * const interlocker_instance, +int dyn_containers_set_interlocker_instance(t_interlocker_data *interlocker_instance, const char *interlocker) { if (interlocker == NULL) { - syslog_server(LOG_ERR, "Could not set interlocker because it was NULL"); + syslog_server(LOG_ERR, "Set interlocker instance: invalid (NULL) interlocker"); + return 1; + } else if (interlocker_instance == NULL) { + syslog_server(LOG_ERR, + "Set interlocker instance: invalid (NULL) interlocker_instance"); + return 1; + } else if (dyn_containers_interface == NULL) { return 1; } pthread_mutex_lock(&dyn_containers_mutex); int interlocker_type = -1; for (int i = 0; i < INTERLOCKER_COUNT_MAX; i++) { - struct t_interlocker_io * const interlocker_io = - &dyn_containers_interface->interlockers_io[i]; + struct t_interlocker_io *interlocker_io = &dyn_containers_interface->interlockers_io[i]; if (strcmp(interlocker_io->output_name, interlocker) == 0) { interlocker_type = i; break; } } - + if (interlocker_type == -1) { pthread_mutex_unlock(&dyn_containers_mutex); syslog_server(LOG_ERR, "Interlocker %s could not be found", interlocker); @@ -572,14 +620,14 @@ int dyn_containers_set_interlocker_instance(t_interlocker_data * const interlock } for (int i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { - struct t_interlocker_instance_io * const interlocker_instance_io = - &dyn_containers_interface->interlocker_instances_io[i]; + struct t_interlocker_instance_io *interlocker_instance_io = + &dyn_containers_interface->interlocker_instances_io[i]; if (!interlocker_instance_io->output_in_use) { interlocker_instance_io->input_grab = true; interlocker_instance_io->input_interlocker_type = interlocker_type; interlocker_instance_io->input_reset = false; pthread_mutex_unlock(&dyn_containers_mutex); - + do { usleep(let_period_us); } while (!interlocker_instance_io->output_in_use); @@ -589,7 +637,9 @@ int dyn_containers_set_interlocker_instance(t_interlocker_data * const interlock pthread_mutex_unlock(&dyn_containers_mutex); interlocker_instance->dyn_containers_interlocker_instance = i; - syslog_server(LOG_NOTICE, "Interlocker %d in use by instance %d", interlocker_type, *interlocker_instance); + syslog_server(LOG_NOTICE, + "Interlocker %d in use by instance %d", + interlocker_type, i); return 0; } } @@ -599,18 +649,22 @@ int dyn_containers_set_interlocker_instance(t_interlocker_data * const interlock return 1; } -void dyn_containers_free_interlocker_instance(t_interlocker_data * const interlocker_instance) { - if (dyn_containers_interface == NULL) { +void dyn_containers_free_interlocker_instance(t_interlocker_data *interlocker_instance) { + if (interlocker_instance == NULL) { + syslog_server(LOG_ERR, + "Free interlocker instance: invalid (NULL) interlocker_instance"); + return; + } else if (dyn_containers_interface == NULL) { return; } + const int inst_index = interlocker_instance->dyn_containers_interlocker_instance; + struct t_interlocker_instance_io *interlocker_instance_io = + &dyn_containers_interface->interlocker_instances_io[inst_index]; - struct t_interlocker_instance_io * const interlocker_instance_io = - &dyn_containers_interface->interlocker_instances_io[interlocker_instance->dyn_containers_interlocker_instance]; - pthread_mutex_lock(&dyn_containers_mutex); interlocker_instance_io->input_release = true; pthread_mutex_unlock(&dyn_containers_mutex); - + do { usleep(let_period_us); } while (interlocker_instance_io->output_in_use); @@ -618,28 +672,48 @@ void dyn_containers_free_interlocker_instance(t_interlocker_data * const interlo pthread_mutex_lock(&dyn_containers_mutex); interlocker_instance_io->input_release = false; pthread_mutex_unlock(&dyn_containers_mutex); - - syslog_server(LOG_NOTICE, "Interlocker instance %d released", + + syslog_server(LOG_NOTICE, + "Interlocker instance %d released", interlocker_instance->dyn_containers_interlocker_instance); } -void dyn_containers_set_interlocker_instance_reset(t_interlocker_data * const interlocker_instance, - const bool reset) { - struct t_interlocker_instance_io * const interlocker_instance_io = - &dyn_containers_interface->interlocker_instances_io[interlocker_instance->dyn_containers_interlocker_instance]; - +void dyn_containers_set_interlocker_instance_reset(t_interlocker_data *interlocker_instance, + bool reset) { + if (interlocker_instance == NULL) { + syslog_server(LOG_ERR, + "Set interlocker instance reset: invalid (NULL) interlocker_instance"); + return; + } else if (dyn_containers_interface == NULL) { + return; + } + const int inst_index = interlocker_instance->dyn_containers_interlocker_instance; + struct t_interlocker_instance_io *interlocker_instance_io = + &dyn_containers_interface->interlocker_instances_io[inst_index]; + pthread_mutex_lock(&dyn_containers_mutex); interlocker_instance_io->input_reset = reset; pthread_mutex_unlock(&dyn_containers_mutex); } -void dyn_containers_set_interlocker_instance_inputs(t_interlocker_data * const interlocker_instance, +void dyn_containers_set_interlocker_instance_inputs(t_interlocker_data *interlocker_instance, const char *src_signal_id, const char *dst_signal_id, const char *train_id) { - struct t_interlocker_instance_io * const interlocker_instance_io = - &dyn_containers_interface->interlocker_instances_io[interlocker_instance->dyn_containers_interlocker_instance]; - + if (interlocker_instance == NULL + || src_signal_id == NULL + || dst_signal_id == NULL + || train_id == NULL) { + + syslog_server(LOG_ERR, "Set interlocker instance outputs: invalid (NULL) parameters"); + return; + } else if (dyn_containers_interface == NULL) { + return; + } + const int inst_index = interlocker_instance->dyn_containers_interlocker_instance; + struct t_interlocker_instance_io *interlocker_instance_io = + &dyn_containers_interface->interlocker_instances_io[inst_index]; + pthread_mutex_lock(&dyn_containers_mutex); interlocker_instance_io->input_reset = true; strncpy(interlocker_instance_io->input_src_signal_id, src_signal_id, NAME_MAX); @@ -648,10 +722,17 @@ void dyn_containers_set_interlocker_instance_inputs(t_interlocker_data * const i pthread_mutex_unlock(&dyn_containers_mutex); } -void dyn_containers_get_interlocker_instance_outputs(t_interlocker_data * const interlocker_instance, +void dyn_containers_get_interlocker_instance_outputs(t_interlocker_data *interlocker_instance, struct t_interlocker_instance_io *interlocker_instance_io_copy) { - struct t_interlocker_instance_io * const interlocker_instance_io = - &dyn_containers_interface->interlocker_instances_io[interlocker_instance->dyn_containers_interlocker_instance]; + if (interlocker_instance == NULL || interlocker_instance_io_copy == NULL) { + syslog_server(LOG_ERR, "Get interlocker instance outputs: invalid (NULL) parameters"); + return; + } else if (dyn_containers_interface == NULL) { + return; + } + const int inst_index = interlocker_instance->dyn_containers_interlocker_instance; + struct t_interlocker_instance_io *interlocker_instance_io = + &dyn_containers_interface->interlocker_instances_io[inst_index]; pthread_mutex_lock(&dyn_containers_mutex); interlocker_instance_io_copy->output_in_use = interlocker_instance_io->output_in_use; diff --git a/server/src/dyn_containers_interface.h b/server/src/dyn_containers_interface.h index 064eb0f6..9df8d5e8 100644 --- a/server/src/dyn_containers_interface.h +++ b/server/src/dyn_containers_interface.h @@ -116,84 +116,88 @@ int dyn_containers_start(void); void dyn_containers_stop(void); -const bool dyn_containers_is_running(void); +bool dyn_containers_is_running(void); // Obtains a shared memory segment based on a given key -void dyn_containers_shm_create(t_dyn_shm_config * const shm_config, - const int shm_permissions, const key_t shm_key, - t_dyn_containers_interface ** const shm_payload); +void dyn_containers_shm_create(t_dyn_shm_config *shm_config, + int shm_permissions, key_t shm_key, + t_dyn_containers_interface **shm_payload); // Detaches the shared memory segment from our data space -void dyn_containers_shm_detach(t_dyn_containers_interface ** const shm_payload); +void dyn_containers_shm_detach(t_dyn_containers_interface **shm_payload); // Deletes the shared memory segment from our data space -void dyn_containers_shm_delete(t_dyn_shm_config * const shm_config); +void dyn_containers_shm_delete(t_dyn_shm_config *shm_config); // Finds the first available slot for a train engine // Can only be called while the dyn_containers_mutex is locked -const int dyn_containers_get_free_engine_slot(void); +int dyn_containers_get_free_engine_slot(void); // Finds the slot of a train engine // Can only be called while the dyn_containers_mutex is locked -const int dyn_containers_get_engine_slot(const char name[]); +int dyn_containers_get_engine_slot(const char name[]); // Loads train engine into specified slot // Can only be called while the dyn_containers_mutex is locked -void dyn_containers_set_engine(const int engine_slot, const char filepath[]); +void dyn_containers_set_engine(int engine_slot, const char filepath[]); // Unloads train engine at specified slot // Can only be called while the dyn_containers_mutex is locked -bool dyn_containers_free_engine(const int engine_slot); +bool dyn_containers_free_engine(int engine_slot); -// Gets a comma-separated string of train engines that have been loaded -GString *dyn_containers_get_train_engines(void); +// Gets a char*-GArray with the names of train engines that have been loaded. +GArray *dyn_containers_get_train_engines_arr(void); // Finds the requested train engine, and finds an available train engine instance to use -int dyn_containers_set_train_engine_instance(t_train_data * const grabbed_train, +int dyn_containers_set_train_engine_instance(t_train_data *grabbed_train, const char *train, const char *engine); -void dyn_containers_free_train_engine_instance(const int dyn_containers_engine_instance); +void dyn_containers_free_train_engine_instance(int dyn_containers_engine_instance); -void dyn_containers_set_train_engine_instance_inputs(const int dyn_containers_engine_instance, - const int requested_speed, - const char requested_forwards); +void dyn_containers_set_train_engine_instance_inputs(int dyn_containers_engine_instance, + int requested_speed, + bool requested_forwards); // Finds the first available slot for a interlocker // Can only be called while the dyn_containers_mutex is locked -const int dyn_containers_get_free_interlocker_slot(void); +int dyn_containers_get_free_interlocker_slot(void); // Finds the slot of a interlocker // Can only be called while the dyn_containers_mutex is locked -const int dyn_containers_get_interlocker_slot(const char name[]); +int dyn_containers_get_interlocker_slot(const char name[]); // Loads interlocker into specified slot // Can only be called while the dyn_containers_mutex is locked -void dyn_containers_set_interlocker(const int interlocker_slot, const char filepath[]); +void dyn_containers_set_interlocker(int interlocker_slot, const char filepath[]); // Unloads interlocker at specified slot // Can only be called while the dyn_containers_mutex is locked -bool dyn_containers_free_interlocker(const int interlocker_slot); +bool dyn_containers_free_interlocker(int interlocker_slot); + +// Gets a char*-GArray with the names of interlockers that have been loaded +GArray *dyn_containers_get_interlockers_arr(void); // Gets a comma-separated string of interlockers that have been loaded GString *dyn_containers_get_interlockers(void); -// Finds the requested interlocker, and finds an available interlocker instance to use -int dyn_containers_set_interlocker_instance(t_interlocker_data * const interlocker_instance, +// Finds the requested interlocker, and finds an available interlocker instance to use; +// returns 1 on error/failure to set interlocker instance, and 0 on success. +int dyn_containers_set_interlocker_instance(t_interlocker_data *interlocker_instance, const char *interlocker); -void dyn_containers_free_interlocker_instance(t_interlocker_data * const interlocker_instance); +void dyn_containers_free_interlocker_instance(t_interlocker_data *interlocker_instance); -void dyn_containers_set_interlocker_instance_reset(t_interlocker_data * const interlocker_instance, - const bool reset); +void dyn_containers_set_interlocker_instance_reset(t_interlocker_data *interlocker_instance, + bool reset); -void dyn_containers_set_interlocker_instance_inputs(t_interlocker_data * const interlocker_instance, +void dyn_containers_set_interlocker_instance_inputs(t_interlocker_data *interlocker_instance, const char *src_signal_id, const char *dst_signal_id, const char *train_id); -void dyn_containers_get_interlocker_instance_outputs(t_interlocker_data * const interlocker_instance, +void dyn_containers_get_interlocker_instance_outputs(t_interlocker_data *interlocker_instance, struct t_interlocker_instance_io *interlocker_instance_io_copy); #endif // DYN_CONTAINERS_INTERFACE_H diff --git a/server/src/dynlib.c b/server/src/dynlib.c index 39612d4a..bce89687 100644 --- a/server/src/dynlib.c +++ b/server/src/dynlib.c @@ -43,10 +43,10 @@ static const char dynlib_symbol_interlocker_tick[] = "request_route_tick"; static const char dynlib_symbol_drive_route_reset[] = "drive_route_reset"; static const char dynlib_symbol_drive_route_tick[] = "drive_route_tick"; -static const char sccharts_compiler_c_command[] = "java -jar \"$KIELER_PATH\"/kico.jar -s de.cau.cs.kieler.sccharts.priority"; +static const char sccharts_compiler_c_command[] = "java -jar \"$KIELER_PATH\"/kico.jar -s de.cau.cs.kieler.sccharts.netlist"; static const char c_compiler_command[] = "clang -shared -fpic -Wall -Wextra"; -static const char bahndsl_compiler_command[] = "\"$BAHNDSL_PATH\"/bahnc -o %s/bahnc -m library %s/%s.bahn"; +static const char bahndsl_compiler_command[] = "\"$BAHNC_PATH\"/bahnc -o %s/bahnc -m library %s/%s.bahn"; static const char bahndsl_move_command[] = "mv %s/bahnc/libinterlocker_%s.%s %s/libinterlocker_%s.%s"; dynlib_status dynlib_load_train_engine_funcs(dynlib_data *library); @@ -64,17 +64,15 @@ dynlib_status dynlib_compile_scchart(const char filepath[], const char output_di // Compile the SCCharts model to a C file char command[MAX_INPUT + 2 * (PATH_MAX + NAME_MAX)]; sprintf(command, "%s -o %s %s.sctx", sccharts_compiler_c_command, output_dir, filepath); - + int ret = system(command); if (ret == -1 || WEXITSTATUS(ret) != 0) { return DYNLIB_COMPILE_SCCHARTS_C_ERR; } - + // Compile the C file into a shared library sprintf(command, "%s -o %s/lib%s.so %s/%s.c", - c_compiler_command, - output_dir, filename, - output_dir, filename); + c_compiler_command, output_dir, filename, output_dir, filename); ret = system(command); if (ret == -1 || WEXITSTATUS(ret) != 0) { @@ -93,7 +91,7 @@ dynlib_status dynlib_compile_bahndsl(const char filepath[], const char output_di // Compile the BahnDSL model to a shared library char command[MAX_INPUT + 2 * (PATH_MAX + NAME_MAX)]; - sprintf(command, bahndsl_compiler_command, output_dir, output_dir, filename); + sprintf(command, bahndsl_compiler_command, output_dir, output_dir, filename); int ret = system(command); if (ret == -1 || WEXITSTATUS(ret) != 0) { return DYNLIB_COMPILE_SHARED_BAHNDSL_ERR; @@ -104,7 +102,8 @@ dynlib_status dynlib_compile_bahndsl(const char filepath[], const char output_di ret = system(command); if (ret == -1 || WEXITSTATUS(ret) != 0) { // Try and move the shared library with *.dylib extension out of the bahnc folder - sprintf(command, bahndsl_move_command, output_dir, filename, "dylib", output_dir, filename, "dylib"); + sprintf(command, bahndsl_move_command, output_dir, + filename, "dylib", output_dir, filename, "dylib"); ret = system(command); if (ret == -1 || WEXITSTATUS(ret) != 0) { @@ -133,11 +132,13 @@ dynlib_status dynlib_load(dynlib_data *library, const char filepath[], dynlib_ty library->lib_handle = dlopen(library->filepath, RTLD_LAZY); if (library->lib_handle == NULL) { - syslog_server(LOG_ERR, "Could not load dynamic library %s.\n%s", library->filepath, dlerror()); + syslog_server(LOG_ERR, + "Could not load dynamic library %s. Error message: %s", + library->filepath, dlerror()); return DYNLIB_LOAD_ERR; } } - + library->type = type; // Try and locate the functions of the library interface @@ -161,7 +162,7 @@ dynlib_status dynlib_load(dynlib_data *library, const char filepath[], dynlib_ty } if (status == DYNLIB_LOAD_SUCCESS) { - syslog_server(LOG_NOTICE, "Loaded dynamic library %s\n", library->filepath); + syslog_server(LOG_NOTICE, "Loaded dynamic library %s", library->filepath); } return status; @@ -169,57 +170,75 @@ dynlib_status dynlib_load(dynlib_data *library, const char filepath[], dynlib_ty dynlib_status dynlib_load_train_engine_funcs(dynlib_data *library) { char *error; - - *(void **) (&library->train_engine_reset_func) = dlsym(library->lib_handle, dynlib_symbol_train_engine_reset); + + *(void **) (&library->train_engine_reset_func) = + dlsym(library->lib_handle, dynlib_symbol_train_engine_reset); if ((error = dlerror()) != NULL) { - syslog_server(LOG_ERR, "Could not find address of symbol %s.\n%s", dynlib_symbol_train_engine_reset, error); + syslog_server(LOG_ERR, + "Could not find address of symbol %s. Error message: %s", + dynlib_symbol_train_engine_reset, error); return DYNLIB_LOAD_RESET_ERR; } dlerror(); - *(void **) (&library->train_engine_tick_func) = dlsym(library->lib_handle, dynlib_symbol_train_engine_tick); + *(void **) (&library->train_engine_tick_func) = + dlsym(library->lib_handle, dynlib_symbol_train_engine_tick); if ((error = dlerror()) != NULL) { - syslog_server(LOG_ERR, "Could not find address of symbol %s.\n%s", dynlib_symbol_train_engine_tick, error); + syslog_server(LOG_ERR, + "Could not find address of symbol %s. Error message: %s", + dynlib_symbol_train_engine_tick, error); return DYNLIB_LOAD_TICK_ERR; } - + return DYNLIB_LOAD_SUCCESS; } dynlib_status dynlib_load_interlocker_funcs(dynlib_data *library) { char *error; - - *(void **) (&library->interlocker_reset_func) = dlsym(library->lib_handle, dynlib_symbol_interlocker_reset); + + *(void **) (&library->interlocker_reset_func) = + dlsym(library->lib_handle, dynlib_symbol_interlocker_reset); if ((error = dlerror()) != NULL) { - syslog_server(LOG_ERR, "Could not find address of symbol %s.\n%s", dynlib_symbol_interlocker_reset, error); + syslog_server(LOG_ERR, + "Could not find address of symbol %s. Error message: %s", + dynlib_symbol_interlocker_reset, error); return DYNLIB_LOAD_RESET_ERR; } - + dlerror(); - *(void **) (&library->interlocker_tick_func) = dlsym(library->lib_handle, dynlib_symbol_interlocker_tick); + *(void **) (&library->interlocker_tick_func) = + dlsym(library->lib_handle, dynlib_symbol_interlocker_tick); if ((error = dlerror()) != NULL) { - syslog_server(LOG_ERR, "Could not find address of symbol %s.\n%s", dynlib_symbol_interlocker_tick, error); + syslog_server(LOG_ERR, + "Could not find address of symbol %s. Error message: %s", + dynlib_symbol_interlocker_tick, error); return DYNLIB_LOAD_TICK_ERR; } - + return DYNLIB_LOAD_SUCCESS; } dynlib_status dynlib_load_drive_route_funcs(dynlib_data *library) { char *error; - - *(void **) (&library->drive_route_reset_func) = dlsym(library->lib_handle, dynlib_symbol_drive_route_reset); + + *(void **) (&library->drive_route_reset_func) = + dlsym(library->lib_handle, dynlib_symbol_drive_route_reset); if ((error = dlerror()) != NULL) { - syslog_server(LOG_ERR, "Could not find address of symbol %s.\n%s", dynlib_symbol_drive_route_reset, error); + syslog_server(LOG_ERR, + "Could not find address of symbol %s. Error message: %s", + dynlib_symbol_drive_route_reset, error); return DYNLIB_LOAD_RESET_ERR; } - - *(void **) (&library->drive_route_tick_func) = dlsym(library->lib_handle, dynlib_symbol_drive_route_tick); + + *(void **) (&library->drive_route_tick_func) = + dlsym(library->lib_handle, dynlib_symbol_drive_route_tick); if ((error = dlerror()) != NULL) { - syslog_server(LOG_ERR, "Could not find address of symbol %s.\n%s", dynlib_symbol_drive_route_tick, error); + syslog_server(LOG_ERR, + "Could not find address of symbol %s. Error message: %s", + dynlib_symbol_drive_route_tick, error); return DYNLIB_LOAD_TICK_ERR; } - + return DYNLIB_LOAD_SUCCESS; } diff --git a/server/src/engines/libtrain_engine_default (unremovable).so b/server/src/engines/libtrain_engine_default (unremovable).so index d6be5082..308399c8 100755 Binary files a/server/src/engines/libtrain_engine_default (unremovable).so and b/server/src/engines/libtrain_engine_default (unremovable).so differ diff --git a/server/src/engines/libtrain_engine_linear (unremovable).so b/server/src/engines/libtrain_engine_linear (unremovable).so index be1aecce..321af2cd 100755 Binary files a/server/src/engines/libtrain_engine_linear (unremovable).so and b/server/src/engines/libtrain_engine_linear (unremovable).so differ diff --git a/server/src/engines/train_engine_default.sctx b/server/src/engines/train_engine_default.sctx new file mode 100644 index 00000000..722cdd31 --- /dev/null +++ b/server/src/engines/train_engine_default.sctx @@ -0,0 +1,42 @@ +//#code.naming prefix + +// Invariant properties +@Invariant + "nominal_speed < 127", + "Nominal speed shall never exceed the train's maximum speed step of 126." + +@Invariant + "nominal_speed >= 0", + "Nominal speed shall never be negative." + + +scchart train_engine_default { + @AssumeRange -130, 130 + input int requested_speed + input bool requested_forwards + + @AssumeRange -130, 130 + output int nominal_speed = 0 + output bool nominal_forwards = true + + const int nominal_speed_min = 0 + const int nominal_speed_max = 126 + + initial state UpdateSpeed "Update Speed" + if (requested_forwards != nominal_forwards && nominal_speed != 0) + do nominal_speed = 0 + go to UpdateSpeed + if (requested_speed < nominal_speed_min) + do nominal_speed = nominal_speed_min + go to UpdateDirection + if (requested_speed > nominal_speed_max) + do nominal_speed = nominal_speed_max + go to UpdateDirection + do nominal_speed = requested_speed + go to UpdateDirection + + + state UpdateDirection "Update Direction" + immediate do nominal_forwards = requested_forwards + go to UpdateSpeed +} \ No newline at end of file diff --git a/server/src/engines/train_engine_linear.sctx b/server/src/engines/train_engine_linear.sctx new file mode 100644 index 00000000..4a279658 --- /dev/null +++ b/server/src/engines/train_engine_linear.sctx @@ -0,0 +1,78 @@ +//#code.naming prefix + +// Invariant properties +@Invariant + "nominal_speed < 127", + "Nominal speed shall never exceed the train's maximum speed step of 126." + +@Invariant + "nominal_speed >= 0", + "Nominal speed shall never be negative." + + +scchart train_engine_linear { + @AssumeRange -130, 130 + input int requested_speed + input bool requested_forwards + + @AssumeRange -130, 130 + output int nominal_speed = 0 + output bool nominal_forwards = true + + signal pure new_request + + const int speed_increment = 1 + const int nominal_speed_min = 0 + const int nominal_speed_max = 126 + + region Detect "Detect request changes" { + initial state Check + if (pre(requested_speed) != requested_speed) || (pre(requested_forwards) != requested_forwards) + do new_request + go to Check + } + + region Controller { + initial state Handle "Handle new request" { + initial state Difference "Determine speed difference" + immediate if !nominal_forwards && requested_forwards + go to DriveForward + immediate if nominal_forwards && !requested_forwards + go to DriveBackward + go to SameDirection + + + state DriveForward "Change direction to drive forward" + if nominal_speed > 0 + do nominal_speed -= speed_increment + go to DriveForward + immediate if nominal_speed == 0 + do nominal_forwards = true + go to SameDirection + + + state DriveBackward "Change direction to drive backward" + if nominal_speed > 0 + do nominal_speed -= speed_increment + go to DriveBackward + immediate if nominal_speed == 0 + do nominal_forwards = false + go to SameDirection + + + state SameDirection "Same direction" + if nominal_speed < requested_speed && nominal_speed < nominal_speed_max + do nominal_speed += speed_increment + go to SameDirection + if nominal_speed > requested_speed && nominal_speed > nominal_speed_min + do nominal_speed -= speed_increment + go to SameDirection + go to Wait + + + state Wait + + } if new_request abort to Handle + } + +} \ No newline at end of file diff --git a/server/src/handler_admin.c b/server/src/handler_admin.c index 2d73e078..77d5cb84 100644 --- a/server/src/handler_admin.c +++ b/server/src/handler_admin.c @@ -22,6 +22,7 @@ * present swtbahn-cli (in alphabetic order by surname): * * - Nicolas Gross + * - Bernhard Luedtke * */ @@ -39,13 +40,32 @@ #include "param_verification.h" #include "bahn_data_util.h" #include "websocket_uploader/engine_uploader.h" +#include "communication_utils.h" +// Mutex to lock when performing startup or shutdown static pthread_mutex_t start_stop_mutex = PTHREAD_MUTEX_INITIALIZER; +// Thread that polls bidib messages periodically static pthread_t poll_bidib_messages_thread; +typedef onion_connection_status o_con_status; +typedef enum { + STARTUP_SUCCESS, + ERR_BIDIB_START_FAIL, + ERR_DIRECTORIES_CLEARING_FAIL, + ERR_CONFIG_LOAD_FAIL, + ERR_DYN_CONTAINERS_START_FAIL, + ERR_LOAD_DEFAULT_INTERLOCKER_FAIL +} e_startup_result_code; -void build_message_hex_string(unsigned char *message, char *dest) { - for (size_t i = 0; i <= message[0]; i++) { +/** + * @brief Constructs a hex-string from a bidib message into `dest`. + * + * @param message bidib msg string, first byte shall indicate length + * @param dest (in-out), filled with resulting hex string; caller responsible for ensuring + * that this string is long enough to hold the hex string + */ +static void build_message_hex_string(unsigned char *message, char *dest) { + for (unsigned int i = 0; i <= message[0]; i++) { if (i != 0) { dest += sprintf(dest, " "); } @@ -53,10 +73,18 @@ void build_message_hex_string(unsigned char *message, char *dest) { } } +/** + * @brief While the system is running, polls bidib messages and logs them. + * + * @param _ unused + * @return void* + */ static void *poll_bidib_messages(void *_) { while (running) { unsigned char *message; while ((message = bidib_read_message()) != NULL) { + // message[0] holds length of msg; +1 for null-term(?), + // and max. 5 chars per byte will be needed in hex representation char hex_string[(message[0] + 1) * 5]; build_message_hex_string(message, hex_string); syslog_server(LOG_NOTICE, "SWTbahn message queue: %s", hex_string); @@ -68,50 +96,65 @@ static void *poll_bidib_messages(void *_) { syslog_server(LOG_ERR, "SWTbahn error message queue: %s", hex_string); free(message); } - usleep(500000); // 0.5 seconds + usleep(250000); // 0.25 seconds } pthread_exit(NULL); } -// Must be called with start_stop_mutex already acquired -static bool startup_server(void) { +/** + * @brief Starts the server/system. I.e., establishes BiDiB connection, + * clears temporary directories, loads the config, starts the dynamic containers + * along with the default interlocker, and launches the thread that polls + * bidib messages. + * Shall only be called with start_stop_mutex acquired. + * + * @return true if startup succeeded, otherwise returns false + */ +static e_startup_result_code startup_server(void) { const int err_serial = bidib_start_serial(serial_device, config_directory, 0); if (err_serial) { syslog_server(LOG_ERR, "Startup server - Could not start BiDiB serial connection"); - return false; + return ERR_BIDIB_START_FAIL; } const int succ_clear_dir = clear_engine_dir() + clear_interlocker_dir(); if (!succ_clear_dir) { syslog_server(LOG_ERR, "Startup server - Could not clear the engine and interlocker directories"); - return false; + return ERR_DIRECTORIES_CLEARING_FAIL; } const int succ_config = bahn_data_util_initialise_config(config_directory); if (!succ_config) { syslog_server(LOG_ERR, "Startup server - Could not initialise interlocking tables"); - return false; + return ERR_CONFIG_LOAD_FAIL; } const int err_dyn_containers = dyn_containers_start(); if (err_dyn_containers) { syslog_server(LOG_ERR, "Startup server - Could not start shared library containers"); - return false; + return ERR_DYN_CONTAINERS_START_FAIL; } - const int err_interlocker = load_default_interlocker_instance(); + const bool err_interlocker = load_default_interlocker_instance(); if (err_interlocker) { syslog_server(LOG_ERR, "Startup server - Could not load default interlocker instance"); - return false; + return ERR_LOAD_DEFAULT_INTERLOCKER_FAIL; } running = true; - pthread_create(&poll_bidib_messages_thread, NULL, poll_bidib_messages, NULL); - return true; + pthread_create(&poll_bidib_messages_thread, NULL, poll_bidib_messages, NULL); + return STARTUP_SUCCESS; } +/** + * @brief Stops the server/system. I.e., releases all grabbed trains, releases all interlockers, + * stops the dynamic containers, frees the loaded config memory, joins with the thread + * polling bidib messages, and stops bidib. + * + * Shall only be called with start_stop_mutex acquired. + */ void shutdown_server(void) { session_id = 0; syslog_server(LOG_NOTICE, "Shutdown server"); @@ -126,153 +169,190 @@ void shutdown_server(void) { syslog_server(LOG_INFO, "Shutdown server - Released interlocking config and table data"); pthread_join(poll_bidib_messages_thread, NULL); syslog_server(LOG_NOTICE, - "Shutdown server - BiDiB message poll thread joined, now stopping BiDiB and closing log"); + "Shutdown server - BiDiB message poll thread joined, " + "now stopping BiDiB and closing log"); bidib_stop(); } -onion_connection_status handler_startup(void *_, onion_request *req, onion_response *res) { +o_con_status handler_startup(void *_, onion_request *req, onion_response *res) { build_response_header(res); - int retval = OCS_NOT_IMPLEMENTED; - pthread_mutex_lock(&start_stop_mutex); if (!running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { // Necessary when restarting the server because libbidib closes syslog on exit openlog("swtbahn", 0, LOG_LOCAL0); - session_id = time(NULL); - syslog_server(LOG_NOTICE, "Request: Startup server - session id: %ld - start", session_id); - - if (startup_server()) { - retval = OCS_PROCESSED; - syslog_server(LOG_NOTICE, - "Request: Startup server - session id: %ld - finish", - session_id); - } else { + syslog_server(LOG_NOTICE, "Request: Startup server - session-id: %ld - start", session_id); + + e_startup_result_code startup_code = startup_server(); + pthread_mutex_unlock(&start_stop_mutex); + char *reason_str; + switch (startup_code) { + case STARTUP_SUCCESS: + reason_str = ""; + onion_response_set_code(res, HTTP_OK); + syslog_server(LOG_NOTICE, + "Request: Startup server - session-id: %ld - finish", + session_id); + break; + case ERR_BIDIB_START_FAIL: + reason_str = "unable to start the server, failed to start (lib)bidib"; + break; + case ERR_DIRECTORIES_CLEARING_FAIL: + reason_str = "unable to start the server, " + "failed to clear engine/interlocker directories"; + break; + case ERR_CONFIG_LOAD_FAIL: + reason_str = "unable to start the server, failed to load config " + "and/or failed to initialize the interlocking table"; + break; + case ERR_DYN_CONTAINERS_START_FAIL: + reason_str = "unable to start the server, failed to start dynamic containers"; + break; + case ERR_LOAD_DEFAULT_INTERLOCKER_FAIL: + reason_str = "unable to start the server, " + "failed to load default interlocker instance"; + break; + default: + reason_str = "unable to start the server"; + break; + } + if (startup_code != STARTUP_SUCCESS) { + send_common_feedback(res, HTTP_INTERNAL_ERROR, reason_str); syslog_server(LOG_ERR, - "Request: Startup server - session id: %ld - unable to start BiDiB - abort", + "Request: Startup server - session-id: %ld - " + "unable to start the server - abort", session_id); } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Startup server - BiDiB system is already running or wrong request type"); + // Cannot use the usual handler for this case, as startup requires server NOT to be running. + o_con_status ret = OCS_PROCESSED; + if (running) { + syslog_server(LOG_WARNING, "Request: Startup server - system already running"); + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "server already running"); + } else { + syslog_server(LOG_WARNING, "Request: Startup server - wrong request type"); + onion_response_set_code(res, HTTP_METHOD_NOT_ALLOWED); + ret = OCS_NOT_IMPLEMENTED; + } + pthread_mutex_unlock(&start_stop_mutex); + return ret; } - pthread_mutex_unlock(&start_stop_mutex); - - return retval; } -onion_connection_status handler_shutdown(void *_, onion_request *req, onion_response *res) { +o_con_status handler_shutdown(void *_, onion_request *req, onion_response *res) { build_response_header(res); - int retval = OCS_NOT_IMPLEMENTED; - pthread_mutex_lock(&start_stop_mutex); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { syslog_server(LOG_NOTICE, "Request: Shutdown server - start"); shutdown_server(); + pthread_mutex_unlock(&start_stop_mutex); + onion_response_set_code(res, HTTP_OK); // Can't log "finished" here since bidib closes the syslog when stopping - retval = OCS_PROCESSED; + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Shutdown server - BiDiB system is not running or wrong request type"); - retval = OCS_NOT_IMPLEMENTED; + o_con_status ret = handle_req_run_or_method_fail(res, running, "Shutdown server"); + pthread_mutex_unlock(&start_stop_mutex); + return ret; } - pthread_mutex_unlock(&start_stop_mutex); - - return retval; } -onion_connection_status handler_set_track_output(void *_, onion_request *req, onion_response *res) { +o_con_status handler_set_track_output(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - char *end; const char *data_state = onion_request_get_post(req, "state"); - long int state = strtol(data_state, &end, 10); - if (data_state == NULL || (state == LONG_MAX || state == LONG_MIN) || *end != '\0') { - syslog_server(LOG_ERR, "Request: Set track output - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Set track output", "state", data_state)) { + return OCS_PROCESSED; + } + char *end; + const long int state = strtol(data_state, &end, 10); + + if ((state == LONG_MAX || state == LONG_MIN) || *end != '\0') { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid state"); + // log the original input (data_state) for debugging + syslog_server(LOG_ERR, "Request: Set track output - invalid state (%s)", data_state); } else { syslog_server(LOG_NOTICE, "Request: Set track output - state: 0x%02x - start", state); bidib_set_track_output_state_all(state); bidib_flush(); + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, "Request: Set track output - state: 0x%02x - finish", state); - return OCS_PROCESSED; } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Set track output - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set track output"); } } -onion_connection_status handler_set_verification_option(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_set_verification_option(void *_, onion_request *req, onion_response *res) { build_response_header(res); if ((onion_request_get_flags(req) & OR_METHODS) == OR_POST) { const char *data_verification_option = onion_request_get_post(req, "verification-option"); - if (!params_check_is_bool_string(data_verification_option)) { - syslog_server(LOG_ERR, "Request: Set verification option - invalid parameters"); - return OCS_NOT_IMPLEMENTED; - } - if (strcmp("true", data_verification_option) == 0 - || strcmp("True", data_verification_option) == 0 - || strcmp("TRUE", data_verification_option) == 0) { - verification_enabled = true; + if (handle_param_miss_check(res, "Set verification option", "verification-option", + data_verification_option)) { + ; + } else if (!params_check_is_bool_string(data_verification_option)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid verification-option"); + syslog_server(LOG_ERR, + "Request: Set verification option - invalid verification-option (%s)", + data_verification_option); } else { - verification_enabled = false; + verification_enabled = strcasecmp("true", data_verification_option) == 0; + onion_response_set_code(res, HTTP_OK); + syslog_server(LOG_NOTICE, + "Request: Set verification option - new state: %s - done", + verification_enabled ? "enabled" : "disabled"); } - syslog_server(LOG_NOTICE, - "Request: Set verification option - new state: %s - done", - verification_enabled ? "enabled" : "disabled"); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Set verification option - wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set verification option"); } } -onion_connection_status handler_set_verification_url(void *_, onion_request *req, - onion_response *res) { - build_response_header(res); +o_con_status handler_set_verification_url(void *_, onion_request *req, onion_response *res) { + build_response_header(res); if ((onion_request_get_flags(req) & OR_METHODS) == OR_POST) { const char *data_verification_url = onion_request_get_post(req, "verification-url"); - if (data_verification_url == NULL) { - syslog_server(LOG_ERR, "Request: Set verification URL - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Set verification URL", "verification-url", + data_verification_url)) { + ; + } else { + set_verifier_url(data_verification_url); + onion_response_set_code(res, HTTP_OK); + syslog_server(LOG_NOTICE, + "Request: Set verification URL - new URL: %s - done", + data_verification_url); } - set_verifier_url(data_verification_url); - syslog_server(LOG_NOTICE, - "Request: Set verification URL - new URL: %s - done", - data_verification_url); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Set verification URL - wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set verification URL"); } } -onion_connection_status handler_admin_release_train(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_admin_release_train(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_train = onion_request_get_post(req, "train"); + if (handle_param_miss_check(res, "Admin release train", "train", data_train)) { + return OCS_PROCESSED; + } const int grab_id = train_get_grab_id(data_train); - - pthread_mutex_lock(&grabbed_trains_mutex); - if (grab_id == -1 || !grabbed_trains[grab_id].is_valid) { - pthread_mutex_unlock(&grabbed_trains_mutex); + if (grab_id == -1) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid train or train not grabbed"); syslog_server(LOG_ERR, - "Request: Admin release train - invalid train id or train %s not grabbed", + "Request: Admin release train - invalid train or train %s not grabbed", data_train); - return OCS_NOT_IMPLEMENTED; + return OCS_PROCESSED; } syslog_server(LOG_NOTICE, "Request: Admin release train - train: %s - start", data_train); // Ensure that the train has stopped moving + pthread_mutex_lock(&grabbed_trains_mutex); const int engine_instance = grabbed_trains[grab_id].dyn_containers_engine_instance; dyn_containers_set_train_engine_instance_inputs(engine_instance, 0, true); pthread_mutex_unlock(&grabbed_trains_mutex); - t_bidib_train_state_query train_state_query = bidib_get_train_state(data_train); while (train_state_query.data.set_speed_step != 0) { bidib_free_train_state_query(train_state_query); @@ -281,66 +361,67 @@ onion_connection_status handler_admin_release_train(void *_, onion_request *req, } bidib_free_train_state_query(train_state_query); - - if (!release_train(grab_id)) { - syslog_server(LOG_ERR, - "Request: Admin release train - train: %s - invalid grab id - abort", - data_train); - return OCS_NOT_IMPLEMENTED; - } else { - syslog_server(LOG_NOTICE, - "Request: Admin release train - train: %s - finish", - data_train); - return OCS_PROCESSED; - } + // We can ignore the return of release_train here, as it would only fail if someone else + // released the train with this grab_id in the meantime -> that is okay, objective achieved. + // (Due to how the dyn-containers work and how grabbed_trains_mutex is used, we can't + // easily avoid such a race condition being possible - have to release the mutex whilst + // waiting for the train to stop; thus someone else could do smth with it in the meantime) + release_train(grab_id); + onion_response_set_code(res, HTTP_OK); + syslog_server(LOG_NOTICE, + "Request: Admin release train - train: %s - finish", + data_train); + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Admin release train - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Admin release train"); } } -onion_connection_status handler_admin_set_dcc_train_speed(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_admin_set_dcc_train_speed(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_train = onion_request_get_post(req, "train"); const char *data_speed = onion_request_get_post(req, "speed"); const char *data_track_output = onion_request_get_post(req, "track-output"); - int speed = params_check_speed(data_speed); - - if (speed == 999) { + const int speed = params_check_speed(data_speed); + const char *l_name = "Admin set dcc train speed"; + if (handle_param_miss_check(res, l_name, "train", data_train) + || handle_param_miss_check(res, l_name, "speed", data_speed) + || handle_param_miss_check(res, l_name, "track-output", data_track_output)) { + return OCS_PROCESSED; + } else if (speed == 999) { + send_common_feedback(res, HTTP_BAD_REQUEST, "bad speed"); syslog_server(LOG_ERR, - "Request: Admin set dcc train speed - train: %s speed: %d - invalid speed", - data_train, speed); - return OCS_NOT_IMPLEMENTED; - } else if (data_track_output == NULL) { + "Request: Admin set dcc train speed - train: %s speed: %d - bad speed (%s)", + data_train, speed, data_speed); + return OCS_PROCESSED; + } + + syslog_server(LOG_NOTICE, + "Request: Admin set dcc train speed - train: %s speed: %d - start", + data_train, speed); + // Does not require lock on grabbed trains mutex as this bidib function is threadsafe. + ///TODO: Directly setting the speed via bidib - i.e., not setting it via + /// the dynamic containers - will cause an inconsistent state: + /// the train in the real world will stop, but the speed set in the dynamic container + /// is not necessarily 0. + /// Todo for the future: set speed via dyn. container, check that speed is set to 0 + // with a timeout after which it is set to 0 directly via bidib as currently done. + if (bidib_set_train_speed(data_train, speed, data_track_output)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid parameter values"); syslog_server(LOG_ERR, - "Request: Admin set dcc train speed - train: %s speed: %d - invalid track output", + "Request: Admin set dcc train speed - train: %s speed: %d - " + "invalid parameter values - abort", data_train, speed); - return OCS_NOT_IMPLEMENTED; } else { + bidib_flush(); + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, - "Request: Admin set dcc train speed - train: %s speed: %d - start", + "Request: Admin set dcc train speed - train: %s speed: %d - finish", data_train, speed); - pthread_mutex_lock(&grabbed_trains_mutex); - if (bidib_set_train_speed(data_train, speed, data_track_output)) { - syslog_server(LOG_ERR, - "Request: Admin set dcc train speed - train: %s speed: %d - " - "invalid parameters - abort", - data_train, speed); - } else { - bidib_flush(); - syslog_server(LOG_NOTICE, - "Request: Admin set dcc train speed - train: %s speed: %d - finish", - data_train, speed); - } - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_PROCESSED; } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Admin set dcc train speed - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Admin set dcc train speed"); } } diff --git a/server/src/handler_admin.h b/server/src/handler_admin.h index 8209df52..4476e316 100644 --- a/server/src/handler_admin.h +++ b/server/src/handler_admin.h @@ -30,28 +30,23 @@ #include +typedef onion_connection_status o_con_status; + void shutdown_server(void); -onion_connection_status handler_startup(void *_, onion_request *req, - onion_response *res); +o_con_status handler_startup(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_shutdown(void *_, onion_request *req, - onion_response *res); +o_con_status handler_shutdown(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_track_output(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_track_output(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_verification_option(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_verification_option(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_verification_url(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_verification_url(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_admin_release_train(void *_, onion_request *req, - onion_response *res); +o_con_status handler_admin_release_train(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_admin_set_dcc_train_speed(void *_, onion_request *req, - onion_response *res); +o_con_status handler_admin_set_dcc_train_speed(void *_, onion_request *req, onion_response *res); #endif // HANDLER_ADMIN_H \ No newline at end of file diff --git a/server/src/handler_controller.c b/server/src/handler_controller.c index 89749b7f..37670a54 100644 --- a/server/src/handler_controller.c +++ b/server/src/handler_controller.c @@ -40,6 +40,8 @@ #include "interlocking.h" #include "bahn_data_util.h" #include "check_route_sectional/check_route_sectional_direct.h" +#include "json_response_builder.h" +#include "communication_utils.h" pthread_mutex_t interlocker_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -50,39 +52,69 @@ static t_interlocker_data interlocker_instances[INTERLOCKER_INSTANCE_COUNT_MAX] { .is_valid = false, .dyn_containers_interlocker_instance = -1 } }; -static GString *selected_interlocker_name; +static GString *selected_interlocker_name = NULL; static int selected_interlocker_instance = -1; - -const int set_interlocker(const char *interlocker_name) { - if (selected_interlocker_instance != -1) { - return selected_interlocker_instance; +static const size_t max_signals_in_route_assmptn = 1024; +static const size_t max_items_in_route_assmptn = 1024; + + +/** + * @brief Set the currently selected interlocker to be the interlocker with + * name given by `interlocker_name interlocker_name`, if an interlocker with this name exists + * and no interlocker is currently set. + * Shall only be called with interlocker_mutex locked. + * + * @param interlocker_name name of the interlocker to set, shall not be NULL + * @return int >= 0 if interlocker was set successfully, otherwise -1. + */ +static int set_interlocker(const char *interlocker_name) { + if (interlocker_name == NULL) { + syslog_server(LOG_ERR, "Set interlocker - invalid (NULL) interlocker_name"); + return -1; + } else if (selected_interlocker_instance != -1) { + // Another interlocker is already set, return -1 + syslog_server(LOG_ERR, + "Set interlocker - interlocker: %s - another interlocker is already set!", + interlocker_name); + return -1; } - - pthread_mutex_lock(&interlocker_mutex); - for (size_t i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { + + for (int i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { + // Look for not already used interlocker instance slot (indicated by is_valid being false). if (!interlocker_instances[i].is_valid) { if (dyn_containers_set_interlocker_instance(&interlocker_instances[i], interlocker_name)) { syslog_server(LOG_ERR, "Set interlocker - interlocker: %s - could not be used in instance %d", interlocker_name, i); + return -1; } else { selected_interlocker_name = g_string_new(interlocker_name); selected_interlocker_instance = i; interlocker_instances[selected_interlocker_instance].is_valid = true; + return selected_interlocker_instance; } - break; } } - pthread_mutex_unlock(&interlocker_mutex); - return selected_interlocker_instance; + syslog_server(LOG_ERR, + "Set interlocker - interlocker: %s - no interlocker instance/slot available", + interlocker_name); + return -1; } -const int unset_interlocker(const char *interlocker_name) { +/** + * @brief Un-set the interlocker given the name of the currently set interlocker. + * Shall only be called with interlocker_mutex locked. + * + * @param interlocker_name name of the interlocker to unset + * @return int -1 if no interlocker is set after unsetting, >= 0 if an interlocker remains set, + * i.e., unsetting failed if the returned value is >= 0. + */ +static int unset_interlocker(const char *interlocker_name) { if (selected_interlocker_instance == -1) { - return selected_interlocker_instance; + // Selected interlocker instance is -1 -> no interlocker to unset. + return -1; } - - pthread_mutex_lock(&interlocker_mutex); + if (strcmp(selected_interlocker_name->str, interlocker_name) == 0 && interlocker_instances[selected_interlocker_instance].is_valid) { dyn_containers_free_interlocker_instance(&interlocker_instances[selected_interlocker_instance]); @@ -91,152 +123,169 @@ const int unset_interlocker(const char *interlocker_name) { selected_interlocker_name = NULL; selected_interlocker_instance = -1; } - pthread_mutex_unlock(&interlocker_mutex); return selected_interlocker_instance; } -const int load_default_interlocker_instance() { +bool load_default_interlocker_instance() { while (!dyn_containers_is_running()) { - // Empty + usleep(let_period_us); } - - selected_interlocker_name = g_string_new("libinterlocker_default (unremovable)"); - const int result = set_interlocker(selected_interlocker_name->str); + pthread_mutex_lock(&interlocker_mutex); + const int result = set_interlocker("libinterlocker_default (unremovable)"); + pthread_mutex_unlock(&interlocker_mutex); + // return true if loading failed, otherwise false return (result == -1); } void release_all_interlockers(void) { + pthread_mutex_lock(&interlocker_mutex); if (selected_interlocker_name != NULL) { g_string_free(selected_interlocker_name, true); selected_interlocker_name = NULL; } - - for (size_t i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { - pthread_mutex_lock(&interlocker_mutex); + for (int i = 0; i < INTERLOCKER_INSTANCE_COUNT_MAX; i++) { if (interlocker_instances[i].is_valid) { dyn_containers_free_interlocker_instance(&interlocker_instances[i]); interlocker_instances[i].is_valid = false; } - pthread_mutex_unlock(&interlocker_mutex); } - selected_interlocker_instance = -1; + pthread_mutex_unlock(&interlocker_mutex); } +/** + * Check if a sectional interlocker is in use. + * Shall only be called with interlocker_mutex locked. + * + * @return true if the currently used interlocker name contains "sectional", i.e., + * a sectional interlocker is in use. + * @return false otherwise + */ +static bool is_sectional_interlocker_in_use() { + return selected_interlocker_name != NULL + && (g_strrstr(selected_interlocker_name->str, "sectional") != NULL); +} -// get_granted_route_conflicts, but using direct implementation of sectional-style checker -GArray *get_granted_route_conflicts_sectional(const char *route_id) { +bool get_route_has_granted_conflicts(const char *route_id) { if (route_id == NULL) { - return NULL; + return false; } - GArray* conflict_route_ids = g_array_new(FALSE, FALSE, sizeof(char *)); + // When a sectional interlocker is in use, use the sectional checker to check for conflicts. + const bool sectional_in_use = is_sectional_interlocker_in_use(); - const unsigned int route_count = MAX(interlocking_table_get_size(), 1024); + // Amount of conflicting routes can't be larger than overall amount of routes known. + const unsigned int route_count = interlocking_table_get_size(); char *conflict_routes[route_count]; - const size_t conflict_routes_len = + const int conflict_routes_len = config_get_array_string_value("route", route_id, "conflicts", conflict_routes); - for (size_t i = 0; i < conflict_routes_len; i++) { - t_interlocking_route *conflict_route = get_route(conflict_routes[i]); - if (conflict_route->train != NULL) { - if (!is_route_conflict_safe_sectional(conflict_routes[i],route_id)) { - const size_t conflict_route_id_string_len = strlen(conflict_route->id) - + strlen(conflict_route->train) + 3 + 1; - char *conflict_route_id_string = malloc(sizeof(char) * conflict_route_id_string_len); - if (conflict_route_id_string == NULL) { - syslog_server(LOG_ERR, - "get_granted_route_conflicts_sectional - failed to allocate memory" - " for conflict_route_id_string"); - g_array_free(conflict_route_ids, true); - return NULL; - } - snprintf(conflict_route_id_string, conflict_route_id_string_len, "%s (%s)", - conflict_route->id, conflict_route->train); - g_array_append_val(conflict_route_ids, conflict_route_id_string); + for (int i = 0; i < conflict_routes_len; i++) { + const t_interlocking_route *conflict_route = get_route(conflict_routes[i]); + if (conflict_route != NULL && conflict_route->train != NULL) { + if (sectional_in_use && is_route_conflict_safe_sectional(conflict_routes[i], route_id)) { + // If a sectional checker is in use and it says that this conflict is + // actually "safe", skip this conflict. + continue; + } else { + return true; } } } - return conflict_route_ids; + return false; } -GArray *get_granted_route_conflicts(const char *route_id) { +GArray *get_granted_route_conflicts(const char *route_id, bool include_conflict_train_info) { if (route_id == NULL) { return NULL; } GArray* conflict_route_ids = g_array_new(FALSE, FALSE, sizeof(char *)); - + // When a sectional interlocker is in use, use the sectional checker to // check for route availability. - if (g_strrstr(selected_interlocker_name->str, "sectional") != NULL) { - // Use native implementation of sectional checker - return get_granted_route_conflicts_sectional(route_id); - } + const bool sectional_in_use = is_sectional_interlocker_in_use(); + + // For the route with id=route_id, get all conflicting routes and add them to the + // GArray that will be returned if they are granted. - const unsigned int route_count = MAX(interlocking_table_get_size(), 1024); + // Amount of conflicting routes can't be larger than overall amount of routes known. + const unsigned int route_count = interlocking_table_get_size(); char *conflict_routes[route_count]; - const size_t conflict_routes_len = + const int conflict_routes_len = config_get_array_string_value("route", route_id, "conflicts", conflict_routes); - for (size_t i = 0; i < conflict_routes_len; i++) { - t_interlocking_route *conflict_route = get_route(conflict_routes[i]); - if (conflict_route->train != NULL) { - const size_t conflict_route_id_string_len = strlen(conflict_route->id) - + strlen(conflict_route->train) + 3 + 1; - char *conflict_route_id_string = malloc(sizeof(char) * conflict_route_id_string_len); + for (int i = 0; i < conflict_routes_len; i++) { + const t_interlocking_route *conflict_route = get_route(conflict_routes[i]); + if (conflict_route != NULL && conflict_route->train != NULL) { + if (sectional_in_use && is_route_conflict_safe_sectional(conflict_routes[i], route_id)) { + // If a sectional checker is in use and it says that this conflict is + // actually "safe" to grant, skip this conflict/dont add it to the list. + continue; + } + size_t conflict_entry_len = strlen(conflict_route->id) + 1; + if (!include_conflict_train_info) { + conflict_entry_len += strlen(conflict_route->train) + 3; + } + char *conflict_route_id_string = malloc(sizeof(char) * conflict_entry_len); if (conflict_route_id_string == NULL) { syslog_server(LOG_ERR, "get_granted_route_conflicts - failed to allocate memory" " for conflict_route_id_string"); + for (unsigned int n = 0; n < conflict_route_ids->len; ++n) { + char *elem = g_array_index(conflict_route_ids, char *, n); + free(elem); + } g_array_free(conflict_route_ids, true); return NULL; } - snprintf(conflict_route_id_string, conflict_route_id_string_len, "%s (%s)", - conflict_route->id, conflict_route->train); + if (include_conflict_train_info) { + snprintf(conflict_route_id_string, conflict_entry_len, "%s (%s)", + conflict_route->id, conflict_route->train); + } else { + snprintf(conflict_route_id_string, conflict_entry_len, "%s", conflict_route->id); + } g_array_append_val(conflict_route_ids, conflict_route_id_string); } } - return conflict_route_ids; } -const bool get_route_is_clear(const char *route_id) { +bool get_route_is_clear(const char *route_id) { if (route_id == NULL) { return false; } bahn_data_util_init_cached_track_state(); - + // Check that all route signals are in the Stop aspect - char *signal_ids[1024]; - const size_t signal_ids_len = config_get_array_string_value("route", route_id, - "route_signals", signal_ids); - for (size_t i = 0; i < signal_ids_len; i++) { - char *signal_state = track_state_get_value(signal_ids[i]); + char *signal_ids[max_signals_in_route_assmptn]; + const int signal_ids_len = + config_get_array_string_value("route", route_id, "route_signals", signal_ids); + for (int i = 0; i < signal_ids_len; i++) { + const char *signal_state = track_state_get_value(signal_ids[i]); if (strcmp(signal_state, "stop")) { bahn_data_util_free_cached_track_state(); return false; } } - - // Check that all blocks are unoccupied - char *item_ids[1024]; - const size_t item_ids_len = config_get_array_string_value("route", route_id, "path", item_ids); - for (size_t i = 0; i < item_ids_len; i++) { + + // Check that all segments are unoccupied + char *item_ids[max_items_in_route_assmptn]; + const int item_ids_len = config_get_array_string_value("route", route_id, "path", item_ids); + for (int i = 0; i < item_ids_len; i++) { if (is_type_segment(item_ids[i]) && is_segment_occupied(item_ids[i])) { bahn_data_util_free_cached_track_state(); return false; } } - - bahn_data_util_free_cached_track_state(); + + bahn_data_util_free_cached_track_state(); return true; } GString *grant_route(const char *train_id, const char *source_id, const char *destination_id) { if (train_id == NULL || source_id == NULL || destination_id == NULL) { - syslog_server(LOG_ERR, "Grant route - invalid (NULL) parameter(s)"); + syslog_server(LOG_ERR, "Grant route - invalid (NULL) parameters"); return g_string_new("not_grantable"); } - pthread_mutex_lock(&interlocker_mutex); if (selected_interlocker_instance == -1) { pthread_mutex_unlock(&interlocker_mutex); @@ -245,31 +294,31 @@ GString *grant_route(const char *train_id, const char *source_id, const char *de train_id, source_id, destination_id); return g_string_new("no_interlocker"); } - + bahn_data_util_init_cached_track_state(); - + // Ask the interlocker to grant requested route. // May take multiple ticks to process the request. dyn_containers_set_interlocker_instance_inputs(&interlocker_instances[selected_interlocker_instance], source_id, destination_id, train_id); - + struct t_interlocker_instance_io interlocker_instance_io; do { usleep(let_period_us); dyn_containers_get_interlocker_instance_outputs(&interlocker_instances[selected_interlocker_instance], &interlocker_instance_io); } while (!interlocker_instance_io.output_has_reset); - + dyn_containers_set_interlocker_instance_reset(&interlocker_instances[selected_interlocker_instance], false); - + do { usleep(let_period_us); dyn_containers_get_interlocker_instance_outputs(&interlocker_instances[selected_interlocker_instance], &interlocker_instance_io); } while (!interlocker_instance_io.output_terminated); - + // Return the result GString *g_route_id_copy = g_string_new(interlocker_instance_io.output_route_id); bahn_data_util_free_cached_track_state(); @@ -301,90 +350,94 @@ GString *grant_route(const char *train_id, const char *source_id, const char *de const char *grant_route_id(const char *train_id, const char *route_id) { if (train_id == NULL || route_id == NULL) { - syslog_server(LOG_ERR, "Grant route id - invalid (NULL) parameter(s)"); + syslog_server(LOG_ERR, "Grant route id - invalid (NULL) parameters"); return "not_grantable"; } pthread_mutex_lock(&interlocker_mutex); // Check whether the route can be granted - t_interlocking_route * const route = get_route(route_id); - GArray * const granted_conflicts = get_granted_route_conflicts(route_id); - if (granted_conflicts == NULL) { + t_interlocking_route *route = get_route(route_id); + if (route == NULL) { + pthread_mutex_unlock(&interlocker_mutex); + syslog_server(LOG_ERR, "Grant route id - unknown route id %s", route_id); + return "not_known"; + } else if (route->train != NULL) { pthread_mutex_unlock(&interlocker_mutex); syslog_server(LOG_WARNING, - "Grant route id - train: %s route: %s - search for conflicting routes failed", - train_id, route_id); - return "not_grantable"; - } - const bool hasGrantedConflicts = (granted_conflicts->len > 0); - g_array_free(granted_conflicts, true); - if (route->train != NULL || hasGrantedConflicts) { + "Grant route id - route: %s train: %s - route already granted", + route_id, train_id); + return "already_granted"; + } else if (get_route_has_granted_conflicts(route_id)) { pthread_mutex_unlock(&interlocker_mutex); syslog_server(LOG_WARNING, - "Grant route id - train: %s route: %s - route already granted " - "or conflicting routes are in use", - train_id, route_id); + "Grant route id - route: %s train: %s - conflicting routes are in use", + route_id, train_id); return "not_grantable"; - } - - // Check whether the route is physically available - if (!get_route_is_clear(route_id)) { + } else if (!get_route_is_clear(route_id)) { pthread_mutex_unlock(&interlocker_mutex); syslog_server(LOG_WARNING, - "Grant route id - train: %s route: %s - route is not clear", - train_id, route_id); + "Grant route id - route: %s train: %s - route is not clear", + route_id, train_id); return "not_clear"; } - + // Grant the route to the train - + syslog_server(LOG_INFO, - "Grant route id - train: %s route: %s - checks passed, now grant route", - train_id, route_id); + "Grant route id - route: %s train: %s - checks passed, now grant route", + route_id, train_id); route->train = strdup(train_id); if (route->train == NULL) { pthread_mutex_unlock(&interlocker_mutex); syslog_server(LOG_ERR, - "Grant route id - train: %s route: %s - unable to allocate memory for route->train", - train_id, route_id); - return "not_grantable"; + "Grant route id - route: %s train: %s - " + "unable to allocate memory for route->train", + route_id, train_id); + return "internal_error"; } - + // Set the points to their required positions - for (size_t i = 0; i < route->points->len; i++) { + for (unsigned int i = 0; i < route->points->len; i++) { const t_interlocking_point point = g_array_index(route->points, t_interlocking_point, i); const char *position = (point.position == NORMAL) ? "normal" : "reverse"; bidib_switch_point(point.id, position); bidib_flush(); } - + // Set the signals to their required aspects - for (size_t i = 0; i < route->signals->len - 1; i++) { + for (unsigned int i = 0; i < route->signals->len - 1; i++) { const char *signal = g_array_index(route->signals, char *, i); const char *signal_type = config_get_scalar_string_value("signal", signal, "type"); - const char *signal_aspect = strcmp(signal_type, "shunting") == 0 ? "aspect_shunt" : "aspect_go"; + const char *signal_aspect = + strcmp(signal_type, "shunting") == 0 ? "aspect_shunt" : "aspect_go"; bidib_set_signal(signal, signal_aspect); bidib_flush(); } - syslog_server(LOG_NOTICE, - "Grant route id - train: %s route: %s - route granted", - train_id, route_id); - + /// TODO: Discuss - at this point we could add a wait of ~2s and then check if the points are + /// in their required position. + /// Benefit: Detect hardware failures, thus preventing a potential short circuit later. + /// Drawback: Latency increases. + pthread_mutex_unlock(&interlocker_mutex); + + syslog_server(LOG_NOTICE, + "Grant route id - route: %s train: %s - route granted", + route_id, train_id); return "granted"; } ///TODO: This should not unconditionally set all route signals to stop, because that would -// prevent sectional route release from working correctly! -void release_route(const char *route_id) { +// prevent sectional route release from working correctly +bool release_route(const char *route_id) { if (route_id == NULL) { - syslog_server(LOG_ERR, "Release route - invalid parameter, route_id is null"); - return; + syslog_server(LOG_ERR, "Release route - invalid (NULL) route_id"); + return false; } pthread_mutex_lock(&interlocker_mutex); t_interlocking_route *route = get_route(route_id); + bool ret = false; if (route != NULL && route->train != NULL) { syslog_server(LOG_INFO, "Release route - route: %s - currently granted to train %s, " @@ -394,31 +447,33 @@ void release_route(const char *route_id) { const char *signal_aspect = "aspect_stop"; for (int signal_index = 0; signal_index < route->signals->len; signal_index++) { const char *signal_id = g_array_index(route->signals, char *, signal_index); - + if (bidib_set_signal(signal_id, signal_aspect)) { syslog_server(LOG_ERR, "Release route - route: %s - unable to set signal to aspect %s", route_id, signal_aspect); + } else { + bidib_flush(); } - bidib_flush(); } - + free(route->train); route->train = NULL; syslog_server(LOG_NOTICE, "Release route - route: %s - released", route_id); + ret = true; } else if (route == NULL) { syslog_server(LOG_ERR, "Release route - route: %s - does not exist", route_id); } else { syslog_server(LOG_ERR, "Release route - route: %s - is not granted to any train", route_id); } - pthread_mutex_unlock(&interlocker_mutex); + return ret; } -const bool reversers_state_update(void) { +bool reversers_state_update(void) { const int max_retries = 5; bool error = false; - + t_bidib_id_list_query rev_query = bidib_get_connected_reversers(); for (size_t i = 0; i < rev_query.length; i++) { const char *reverser_id = rev_query.ids[i]; @@ -426,7 +481,7 @@ const bool reversers_state_update(void) { config_get_scalar_string_value("reverser", reverser_id, "board"); error |= bidib_request_reverser_state(reverser_id, reverser_board); bidib_flush(); - + bool state_unknown = true; for (int retry = 0; retry < max_retries && state_unknown; retry++) { t_bidib_reverser_state_query rev_state_query = bidib_get_reverser_state(reverser_id); @@ -437,223 +492,257 @@ const bool reversers_state_update(void) { if (!state_unknown) { break; } - + usleep(50000); // 0.05s } - + error |= state_unknown; } - + bidib_free_id_list_query(rev_query); return !error; } -onion_connection_status handler_release_route(void *_, onion_request *req, onion_response *res) { +o_con_status handler_release_route(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_route_id = onion_request_get_post(req, "route-id"); const char *route_id = params_check_route_id(data_route_id); - if (strcmp(route_id, "") == 0) { - syslog_server(LOG_ERR, "Request: Release route - invalid parameters"); - return OCS_NOT_IMPLEMENTED; - } else { - syslog_server(LOG_NOTICE, "Request: Release route - route: %s - start", route_id); - release_route(route_id); - syslog_server(LOG_NOTICE, "Request: Release route - route: %s - finish", route_id); + + if (handle_param_miss_check(res, "Release route", "route-id", data_route_id)) { + return OCS_PROCESSED; + } else if (strcmp(route_id, "") == 0) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid route-id"); + // log the original input (data_route_id), not the parsed (route_id), for debugging + syslog_server(LOG_ERR, "Request: Release route - invalid route-id (%s)", data_route_id); return OCS_PROCESSED; } + + syslog_server(LOG_NOTICE, "Request: Release route - route: %s - start", route_id); + bool release_success = release_route(route_id); + if (release_success) { + send_common_feedback(res, HTTP_OK, ""); + syslog_server(LOG_NOTICE, "Request: Release route - route: %s - finish", route_id); + } else { + send_common_feedback(res, HTTP_BAD_REQUEST, + "invalid route-id, route does not exist or is not granted"); + syslog_server(LOG_ERR, "Request: Release route - route: %s - abort", route_id); + } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Release route - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Release route"); } } -onion_connection_status handler_set_point(void *_, onion_request *req, onion_response *res) { +o_con_status handler_set_point(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_point = onion_request_get_post(req, "point"); const char *data_state = onion_request_get_post(req, "state"); - if (data_point == NULL || data_state == NULL) { - syslog_server(LOG_ERR, "Request: Set point - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Set point", "point", data_point) + || handle_param_miss_check(res, "Set point", "state", data_state)) { + return OCS_PROCESSED; + } else if (!is_type_point(data_point)) { + send_common_feedback(res, HTTP_NOT_FOUND, "unknown point"); + syslog_server(LOG_ERR, "Request: Set point - unknown point (%s)", data_point); + return OCS_PROCESSED; + } + + syslog_server(LOG_NOTICE, + "Request: Set point - point: %s state: %s - start", + data_point, data_state); + if (bidib_switch_point(data_point, data_state)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid parameter values"); + syslog_server(LOG_ERR, + "Request: Set point - point: %s state: %s - " + "invalid parameter values - abort", + data_point, data_state); } else { + bidib_flush(); + send_common_feedback(res, HTTP_OK, ""); syslog_server(LOG_NOTICE, - "Request: Set point - point: %s state: %s - start", + "Request: Set point - point: %s state: %s - finish", data_point, data_state); - if (bidib_switch_point(data_point, data_state)) { - syslog_server(LOG_ERR, - "Request: Set point - point: %s state: %s - invalid parameters - abort", - data_point, data_state); - return OCS_NOT_IMPLEMENTED; - } else { - bidib_flush(); - syslog_server(LOG_NOTICE, - "Request: Set point - point: %s state: %s - finish", - data_point, data_state); - return OCS_PROCESSED; - } } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Set point - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set point"); } } -onion_connection_status handler_set_signal(void *_, onion_request *req, onion_response *res) { +o_con_status handler_set_signal(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_signal = onion_request_get_post(req, "signal"); const char *data_state = onion_request_get_post(req, "state"); - if (data_signal == NULL || data_state == NULL) { - syslog_server(LOG_ERR, "Request: Set signal - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Set signal", "signal", data_signal) + || handle_param_miss_check(res, "Set signal", "state", data_state)) { + return OCS_PROCESSED; + } else if (!is_type_signal(data_signal)) { + send_common_feedback(res, HTTP_NOT_FOUND, "unknown signal"); + syslog_server(LOG_ERR, "Request: Set point - unknown signal (%s)", data_signal); + return OCS_PROCESSED; + } + + syslog_server(LOG_NOTICE, + "Request: Set signal - signal: %s state: %s - start", + data_signal, data_state); + if (bidib_set_signal(data_signal, data_state)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid parameter values"); + syslog_server(LOG_ERR, + "Request: Set signal - signal: %s state: %s - " + "invalid parameter values - abort", + data_signal, data_state); } else { + bidib_flush(); + send_common_feedback(res, HTTP_OK, ""); syslog_server(LOG_NOTICE, - "Request: Set signal - signal: %s state: %s - start", + "Request: Set signal - signal: %s state: %s - finish", data_signal, data_state); - if (bidib_set_signal(data_signal, data_state)) { - syslog_server(LOG_ERR, - "Request: Set signal - signal: %s state: %s - " - "invalid parameters - abort", - data_signal, data_state); - return OCS_NOT_IMPLEMENTED; - } else { - bidib_flush(); - syslog_server(LOG_NOTICE, - "Request: Set signal - signal: %s state: %s - finish", - data_signal, data_state); - return OCS_PROCESSED; - } } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Set signal - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set signal"); } } -onion_connection_status handler_set_peripheral(void *_, onion_request *req, onion_response *res) { +o_con_status handler_set_peripheral(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_peripheral = onion_request_get_post(req, "peripheral"); const char *data_state = onion_request_get_post(req, "state"); - if (data_peripheral == NULL || data_state == NULL) { - syslog_server(LOG_ERR, "Request: Set peripheral - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Set peripheral", "peripheral", data_peripheral) + || handle_param_miss_check(res, "Set peripheral", "state", data_state)) { + return OCS_PROCESSED; + } + + syslog_server(LOG_NOTICE, + "Request: Set peripheral - peripheral: %s state: %s - start", + data_peripheral, data_state); + if (bidib_set_peripheral(data_peripheral, data_state)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid parameter values"); + syslog_server(LOG_ERR, + "Request: Set peripheral - peripheral: %s state: %s - " + "invalid parameter values - abort", + data_peripheral, data_state); } else { + bidib_flush(); + send_common_feedback(res, HTTP_OK, ""); syslog_server(LOG_NOTICE, - "Request: Set peripheral - peripheral: %s state: %s - start", + "Request: Set peripheral - peripheral: %s state: %s - finish", data_peripheral, data_state); - if (bidib_set_peripheral(data_peripheral, data_state)) { - syslog_server(LOG_ERR, - "Request: Set peripheral - peripheral: %s state: %s - " - "invalid parameters - abort", - data_peripheral, data_state); - return OCS_NOT_IMPLEMENTED; - } else { - bidib_flush(); - syslog_server(LOG_NOTICE, - "Request: Set peripheral - peripheral: %s state: %s - finish", - data_peripheral, data_state); - return OCS_PROCESSED; - } } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Set peripheral - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set peripheral"); } } -onion_connection_status handler_get_interlocker(void *_, onion_request *req, onion_response *res) { +o_con_status handler_get_interlocker(void *_, onion_request *req, onion_response *res) { build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + pthread_mutex_lock(&interlocker_mutex); if (selected_interlocker_instance != -1 && selected_interlocker_name != NULL) { - onion_response_printf(res, "%s", selected_interlocker_name->str); + GString *g_resstr = g_string_new("{\"interlocker\": \""); + g_string_append_printf(g_resstr, "%s\"}", + selected_interlocker_name->str); + pthread_mutex_unlock(&interlocker_mutex); + send_some_gstring_and_free(res, HTTP_OK, g_resstr); syslog_server(LOG_INFO, "Request: Get interlocker - done"); - return OCS_PROCESSED; } else { + pthread_mutex_unlock(&interlocker_mutex); + send_common_feedback(res, HTTP_NOT_FOUND, "No interlocker is currently selected"); syslog_server(LOG_ERR, "Request: Get interlocker - none selected"); - return OCS_NOT_IMPLEMENTED; } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get interlocker - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get interlocker"); } } -onion_connection_status handler_set_interlocker(void *_, onion_request *req, onion_response *res) { +o_con_status handler_set_interlocker(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_interlocker = onion_request_get_post(req, "interlocker"); - if (data_interlocker == NULL) { - syslog_server(LOG_ERR, "Request: Set interlocker - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Set interlocker", "interlocker", data_interlocker)) { + return OCS_PROCESSED; + } + + syslog_server(LOG_NOTICE, + "Request: Set interlocker - interlocker: %s - start", + data_interlocker); + pthread_mutex_lock(&interlocker_mutex); + if (selected_interlocker_instance != -1) { + pthread_mutex_unlock(&interlocker_mutex); + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, + "another interlocker instance is already set"); + syslog_server(LOG_ERR, + "Request: Set interlocker - interlocker: %s - another " + "interlocker instance is already set - abort", + data_interlocker); + } else if (set_interlocker(data_interlocker) == -1) { + pthread_mutex_unlock(&interlocker_mutex); + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid interlocker name " + "or no more interlocker instances can be loaded"); + syslog_server(LOG_ERR, + "Request: Set interlocker - interlocker: %s - invalid interlocker " + "name or no more interlocker instances can be loaded - abort", + data_interlocker); } else { + pthread_mutex_unlock(&interlocker_mutex); + send_common_feedback(res, HTTP_OK, ""); syslog_server(LOG_NOTICE, - "Request: Set interlocker - interlocker: %s - start", + "Request: Set interlocker - interlocker: %s - finish", data_interlocker); - if (selected_interlocker_instance != -1) { - syslog_server(LOG_ERR, - "Request: Set interlocker - interlocker: %s - another " - "interlocker instance is already set - abort", - data_interlocker); - return OCS_NOT_IMPLEMENTED; - } - - set_interlocker(data_interlocker); - if (selected_interlocker_instance == -1) { - syslog_server(LOG_ERR, - "Request: Set interlocker - interlocker: %s - invalid " - "parameters or no more interlocker instances can be loaded - abort", - data_interlocker); - return OCS_NOT_IMPLEMENTED; - } else { - onion_response_printf(res, "%s", selected_interlocker_name->str); - syslog_server(LOG_NOTICE, - "Request: Set interlocker - interlocker: %s - finish", - data_interlocker); - return OCS_PROCESSED; - } } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Set interlocker - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set interlocker"); } } -onion_connection_status handler_unset_interlocker(void *_, onion_request *req, onion_response *res) { +o_con_status handler_unset_interlocker(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_interlocker = onion_request_get_post(req, "interlocker"); - if (data_interlocker == NULL) { - syslog_server(LOG_ERR, "Request: Unset interlocker - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Unset interlocker", "interlocker", data_interlocker)) { + return OCS_PROCESSED; + } + + syslog_server(LOG_NOTICE, + "Request: Unset interlocker - interlocker: %s - start", + data_interlocker); + pthread_mutex_lock(&interlocker_mutex); + if (selected_interlocker_instance == -1) { + pthread_mutex_unlock(&interlocker_mutex); + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, + "no interlocker instance is set that can be unset"); + syslog_server(LOG_ERR, + "Request: Unset interlocker - interlocker: %s - " + "no interlocker instance to unset - abort", + data_interlocker); + } else if (unset_interlocker(data_interlocker) != -1) { + pthread_mutex_unlock(&interlocker_mutex); + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "invalid interlocker name (curr" + "ently set interlocker doesn't match provided interlocker name)"); + syslog_server(LOG_ERR, + "Request: Unset interlocker - interlocker: %s - " + "invalid interlocker name - abort", + data_interlocker); } else { - syslog_server(LOG_NOTICE, "Request: Unset interlocker - interlocker: %s - start", + pthread_mutex_unlock(&interlocker_mutex); + send_common_feedback(res, HTTP_OK, ""); + syslog_server(LOG_NOTICE, + "Request: Unset interlocker - interlocker: %s - finish", data_interlocker); - if (selected_interlocker_instance == -1) { - syslog_server(LOG_ERR, - "Request: Unset interlocker - interlocker: %s - " - "no interlocker instance to unset - abort", - data_interlocker); - return OCS_NOT_IMPLEMENTED; - } - - unset_interlocker(data_interlocker); - if (selected_interlocker_instance != -1) { - syslog_server(LOG_ERR, - "Request: Unset interlocker - interlocker: %s - " - "invalid parameters - abort", - data_interlocker); - return OCS_NOT_IMPLEMENTED; - } else { - syslog_server(LOG_NOTICE, - "Request: Unset interlocker - interlocker: %s - finish", - data_interlocker); - return OCS_PROCESSED; - } } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Unset interlocker - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Unset interlocker"); } } diff --git a/server/src/handler_controller.h b/server/src/handler_controller.h index fb7a65d2..59189dda 100644 --- a/server/src/handler_controller.h +++ b/server/src/handler_controller.h @@ -32,6 +32,8 @@ #include #include +typedef onion_connection_status o_con_status; + #define INTERLOCKER_COUNT_MAX 4 #define INTERLOCKER_INSTANCE_COUNT_MAX 4 @@ -48,27 +50,44 @@ void release_all_interlockers(void); /** * Loads the default interlocker - * @return 0 if successful, otherwise 1 + * @return false if successful, otherwise true + */ +bool load_default_interlocker_instance(); + +/** + * Checks if there exists at least one route that is currently granted + * and conflicts with the route specified via route_id. + * Shall only be called with interlocker_mutex locked. + * + * @param route_id id of route for which conflicts shall be checked + * @return true if at least one conflict with a granted route exists and route_id is a valid route + * @return false otherwise */ -const int load_default_interlocker_instance(); +bool get_route_has_granted_conflicts(const char *route_id); /** * Finds conflicting routes that have been granted. + * Shall only be called with interlocker_mutex locked. + * The caller is responsible for freeing the returned array and its contents. * - * @param ID of route for which conflicts should be checked - * @return GArray of granted route conflicts + * @param route_id id of route for which conflicts shall be checked + * @param include_conflict_train_info whether the train to which a conflicting route is granted + * shall be added for each element + * @return GArray of granted route conflicts, described by strings. + * returns NULL if inputs are invalid or internal error occured. */ -GArray *get_granted_route_conflicts(const char *route_id); +GArray *get_granted_route_conflicts(const char *route_id, bool include_conflict_train_info); /** * Determines whether a route is physically ready for use: * All route signals are in the Stop aspect and all blocks * are unoccupied. + * Shall only be called with interlocker_mutex locked. * * @param ID of route for which clearance should be checked * @return true if clear, otherwise false */ -const bool get_route_is_clear(const char *route_id); +bool get_route_is_clear(const char *route_id); /** * Determines whether conflicting routes that have @@ -84,6 +103,7 @@ bool route_has_no_sectional_conflicts(const char *route_id); /** * Finds and grants a requested train route using an external algorithm. * A requested route is defined by a pair of source and destination signals. + * The caller is responsible for freeing the returned string. * * @param name of requesting train * @param name of the source signal @@ -108,8 +128,10 @@ const char *grant_route_id(const char *train_id, * Releases the requested route id. * * @param ID of the requested route + * @return true if the release succeeded + * @return false if the release failed */ -void release_route(const char *route_id); +bool release_route(const char *route_id); /** * Requests the reverser state to be updated and waits @@ -118,28 +140,21 @@ void release_route(const char *route_id); * * @return true if the update was successful, otherwise false */ -const bool reversers_state_update(void); +bool reversers_state_update(void); -onion_connection_status handler_release_route(void *_, onion_request *req, - onion_response *res); +o_con_status handler_release_route(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_point(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_point(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_signal(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_signal(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_peripheral(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_peripheral(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_interlocker(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_interlocker(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_interlocker(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_interlocker(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_unset_interlocker(void *_, onion_request *req, - onion_response *res); +o_con_status handler_unset_interlocker(void *_, onion_request *req, onion_response *res); #endif // HANDLER_CONTROLLER_H diff --git a/server/src/handler_driver.c b/server/src/handler_driver.c index f94d59bb..2b7ea926 100644 --- a/server/src/handler_driver.c +++ b/server/src/handler_driver.c @@ -42,6 +42,8 @@ #include "param_verification.h" #include "interlocking.h" #include "bahn_data_util.h" +#include "json_response_builder.h" +#include "communication_utils.h" pthread_mutex_t grabbed_trains_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -55,12 +57,12 @@ typedef struct { bool has_been_set_to_stop; bool is_source_signal; bool is_destination_signal; - size_t index_in_route_path; + unsigned int index_in_route_path; } t_route_signal_info; typedef struct { t_route_signal_info **data_ptr; - size_t len; + unsigned int len; } t_route_signal_info_array; t_train_data grabbed_trains[TRAIN_ENGINE_INSTANCE_COUNT_MAX] = { @@ -69,7 +71,7 @@ t_train_data grabbed_trains[TRAIN_ENGINE_INSTANCE_COUNT_MAX] = { typedef struct { bool *arr; - size_t len; + unsigned int len; } t_route_repeated_segment_flags; typedef enum { @@ -80,7 +82,7 @@ typedef enum { } e_route_pos_error_code; typedef struct { - size_t pos_index; + unsigned int pos_index; e_route_pos_error_code err_code; } t_train_index_on_route_query; @@ -92,10 +94,14 @@ static void increment_next_grab_id(void) { } } -const int train_get_grab_id(const char *train) { - pthread_mutex_lock(&grabbed_trains_mutex); +int train_get_grab_id(const char *train) { + if (train == NULL) { + syslog_server(LOG_ERR, "Train get grab-id - invalid (NULL) train"); + return -1; + } int grab_id = -1; - for (size_t i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { + pthread_mutex_lock(&grabbed_trains_mutex); + for (int i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { if (grabbed_trains[i].is_valid && strcmp(grabbed_trains[i].name->str, train) == 0) { grab_id = i; break; @@ -106,9 +112,13 @@ const int train_get_grab_id(const char *train) { } bool train_grabbed(const char *train) { + if (train == NULL) { + syslog_server(LOG_ERR, "Train grabbed - invalid (NULL) train"); + return false; + } bool grabbed = false; pthread_mutex_lock(&grabbed_trains_mutex); - for (size_t i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { + for (int i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { if (grabbed_trains[i].is_valid && grabbed_trains[i].name != NULL && strcmp(grabbed_trains[i].name->str, train) == 0) { @@ -120,22 +130,11 @@ bool train_grabbed(const char *train) { return grabbed; } -struct timespec get_delta_timespec_(const struct timespec *time_a, const struct timespec *time_b) { - if (time_a != NULL && time_b != NULL) { - long delta_nanos = time_b->tv_nsec - time_a->tv_nsec; - long delta_seconds = time_b->tv_sec - time_a->tv_sec; - if (time_b->tv_nsec < time_a->tv_nsec) { - delta_nanos += 1000000000; - delta_seconds--; - } - struct timespec diff = {.tv_sec = delta_seconds, .tv_nsec = delta_nanos}; - return diff; - } - struct timespec empty_diff = {.tv_sec = 0, .tv_nsec = 0}; - return empty_diff; -} - static bool train_position_is_at(const char *train_id, const char *segment) { + if (train_id == NULL || segment == NULL) { + syslog_server(LOG_ERR, "Train position is at - invalid (NULL) parameters"); + return false; + } t_bidib_train_position_query train_position_query = bidib_get_train_position(train_id); for (size_t i = 0; i < train_position_query.length; i++) { @@ -149,8 +148,11 @@ static bool train_position_is_at(const char *train_id, const char *segment) { return false; } -static const bool is_forward_driving(const t_interlocking_route *route, const char *train_id) { - +static bool is_forward_driving(const t_interlocking_route *route, const char *train_id) { + if (route == NULL || train_id == NULL) { + syslog_server(LOG_ERR, "Is forward driving - invalid (NULL) parameters"); + return true; + } t_bidib_train_position_query train_position_query = bidib_get_train_position(train_id); const bool train_is_left = train_position_query.orientation_is_left; const bool route_is_clockwise = (strcmp(route->orientation, "clockwise") == 0); @@ -162,54 +164,52 @@ static const bool is_forward_driving(const t_interlocking_route *route, const ch char *block_id = NULL; for (size_t i = 0; i < train_position_query.length; i++) { block_id = config_get_block_id_of_segment(train_position_query.segments[i]); - if (block_id != NULL) { + if (block_id != NULL && strlen(block_id) > 0) { break; } } bidib_free_train_position_query(train_position_query); - if (block_id == NULL) { + if (block_id == NULL || strlen(block_id) == 0) { syslog_server(LOG_ERR, - "Is forward driving - train: %s driving: %s - current block of train is unknown", + "Is forward driving - train: %s driving: %s - current block of train unknown", train_id, is_forwards ? "forwards" : "backwards"); return is_forwards; } - + // 2. Check whether the train is on a block controlled by a reverser - bool electrically_reversed = false; + bool block_reversed = false; t_bidib_id_list_query rev_query = bidib_get_connected_reversers(); for (size_t i = 0; i < rev_query.length; i++) { const char *reverser_id = rev_query.ids[i]; const char *reverser_block = config_get_scalar_string_value("reverser", reverser_id, "block"); - + if (strcmp(block_id, reverser_block) == 0) { const bool succ = reversers_state_update(); t_bidib_reverser_state_query rev_state_query = bidib_get_reverser_state(reverser_id); // 3. Check the reverser's state if (succ && rev_state_query.available) { - electrically_reversed = (rev_state_query.data.state_value == BIDIB_REV_EXEC_STATE_ON); + block_reversed = (rev_state_query.data.state_value == BIDIB_REV_EXEC_STATE_ON); } + bidib_free_reverser_state_query(rev_state_query); break; } } bidib_free_id_list_query(rev_query); - const bool requested_forwards = electrically_reversed - ? !is_forwards - : is_forwards; + const bool requested_forwards = block_reversed ? !is_forwards : is_forwards; syslog_server(LOG_NOTICE, "Is forward driving - train: %s driving: %s", - train_id, is_forwards ? "forwards" : "backwards"); + train_id, requested_forwards ? "forwards" : "backwards"); return requested_forwards; } static bool drive_route_params_valid(const char *train_id, t_interlocking_route *route) { if (train_id == NULL || route == NULL) { - syslog_server(LOG_ERR, "Check drive route params - invalid (NULL) parameter(s)"); + syslog_server(LOG_ERR, "Check drive route params - invalid (NULL) parameters"); return false; - } - if ((route->train == NULL) || strcmp(train_id, route->train) != 0) { + } else if (route->train == NULL || strcmp(train_id, route->train) != 0) { syslog_server(LOG_WARNING, "Check drive route params - route %s not granted to train %s", route->id, train_id); @@ -228,28 +228,6 @@ static bool validate_interlocking_route_members_not_null(const t_interlocking_ro && route->train != NULL); } -static void log_signal_info(int priority, const t_route_signal_info *sig_info) { - if (sig_info != NULL) { - syslog_server(priority, - "Drive route signal info - id: %s, source: %s, destination: %s, path index: %d", - sig_info->id != NULL ? sig_info->id : "NULL", - sig_info->is_source_signal ? "is" : "not", - sig_info->is_destination_signal ? "is" : "not", - sig_info->index_in_route_path); - } -} - -__attribute__ ((unused)) -static void log_signal_info_array(int priority, const t_route_signal_info_array *sig_info_array) { - syslog_server(priority, "log route signal info array - start"); - if (sig_info_array != NULL) { - for (size_t i = 0; i < sig_info_array->len; ++i) { - log_signal_info(priority, sig_info_array->data_ptr[i]); - } - } - syslog_server(priority, "log route signal info array - end"); -} - static void free_route_signal_info_array(t_route_signal_info_array *route_signal_info_array) { if (route_signal_info_array == NULL) { return; @@ -258,7 +236,7 @@ static void free_route_signal_info_array(t_route_signal_info_array *route_signal route_signal_info_array->len = 0; return; } - for (size_t i = 0; i < route_signal_info_array->len; ++i) { + for (unsigned int i = 0; i < route_signal_info_array->len; ++i) { t_route_signal_info *elem = route_signal_info_array->data_ptr[i]; if (elem != NULL) { if (elem->id != NULL) { @@ -279,20 +257,20 @@ static void free_route_signal_info_array(t_route_signal_info_array *route_signal // continuing. Otherwise returns true. static bool add_signal_info_for_signal(t_route_signal_info_array *signal_info_array, const char *signal_id_item, - size_t index_in_info_array, - size_t number_of_signal_infos) { + unsigned int index_in_info_array, + unsigned int number_of_signal_infos) { if (signal_info_array == NULL || signal_info_array->data_ptr == NULL) { syslog_server(LOG_ERR, "Add signal-info to signal_info_array - " "signal info array or signal info array data pointer is NULL"); return false; } - const size_t i = index_in_info_array; + const unsigned int i = index_in_info_array; // A. Return without adding signal-info if signal from route->signals is NULL if (signal_id_item == NULL) { syslog_server(LOG_WARNING, "Add signal-info to signal_info_array - " - "skipping NULL signal at index %d of route->signals", + "skipping NULL signal at index %u of route->signals", i); signal_info_array->data_ptr[i] = NULL; signal_info_array->len = i + 1; @@ -304,7 +282,8 @@ static bool add_signal_info_for_signal(t_route_signal_info_array *signal_info_ar signal_info_array->len = i + 1; if (signal_info_array->data_ptr[i] == NULL) { syslog_server(LOG_ERR, - "Add signal-info to signal_info_array - unable to allocate memory for array index %d", + "Add signal-info to signal_info_array - " + "unable to allocate memory for array index %u", i); return false; } @@ -318,7 +297,8 @@ static bool add_signal_info_for_signal(t_route_signal_info_array *signal_info_ar signal_info_array->data_ptr[i]->id = strdup(signal_id_item); if (signal_info_array->data_ptr[i]->id == NULL) { syslog_server(LOG_ERR, - "Add signal-info to signal_info_array - unable to allocate memory for signal id %s", + "Add signal-info to signal_info_array - " + "unable to allocate memory for signal id %s", signal_id_item); return false; } @@ -332,13 +312,11 @@ static t_route_signal_info_array get_route_signal_info_array(const t_interlockin "Get route signal info array - route is NULL or some route details are NULL"); return info_arr; } - syslog_server(LOG_DEBUG, - "Get route signal info array - route: %s - start building", - route->id); // 1. Allocate memory for array of pointers to t_route_signal_info type entities - const size_t number_of_signal_infos = route->signals->len; - info_arr.data_ptr = (t_route_signal_info**) malloc(sizeof(t_route_signal_info*) * number_of_signal_infos); + const unsigned int number_of_signal_infos = route->signals->len; + info_arr.data_ptr = + (t_route_signal_info**) malloc(sizeof(t_route_signal_info*) * number_of_signal_infos); if (info_arr.data_ptr == NULL) { syslog_server(LOG_ERR, @@ -347,23 +325,27 @@ static t_route_signal_info_array get_route_signal_info_array(const t_interlockin return info_arr; } // 2. For every signal in route->signals... - for (size_t i = 0; i < number_of_signal_infos; ++i) { + for (unsigned int i = 0; i < number_of_signal_infos; ++i) { const char *signal_id_item = g_array_index(route->signals, char *, i); - // 3. Call function to add a signal-info to info_arr for signal_id_item + // ... Call function to add a signal-info to info_arr for signal_id_item if (!add_signal_info_for_signal(&info_arr, signal_id_item, i, number_of_signal_infos)) { + syslog_server(LOG_ERR, + "Get route signal info array - route: %s - " + "failed to add signal-info, abort", + route->id); free_route_signal_info_array(&info_arr); - return info_arr; + return (t_route_signal_info_array){.data_ptr = NULL, .len = 0}; } } // 3. For every signal_info, determine index of its signal-id in route->path and set member // for index_in_route_path of signal_info accordingly - size_t path_index = 0; - for (size_t i = 0; i < info_arr.len; ++i) { + unsigned int path_index = 0; + for (unsigned int i = 0; i < info_arr.len; ++i) { if (info_arr.data_ptr[i] == NULL) { syslog_server(LOG_WARNING, "Get route signal info array - route: %s - " - "skipped NULL signal_info at pos %d of info_arr", - i); + "skipped NULL signal_info at pos %u of info_arr", + route->id, i); continue; } @@ -382,9 +364,6 @@ static t_route_signal_info_array get_route_signal_info_array(const t_interlockin } } } - syslog_server(LOG_DEBUG, - "Get route signal info array - route: %s - finished building", - route->id); return info_arr; } @@ -403,7 +382,7 @@ static t_route_repeated_segment_flags get_route_repeated_segment_flags(const t_i if (route == NULL || route->path == NULL) { return r_seg_flags; } - const size_t route_path_len = (size_t) route->path->len; + const unsigned int route_path_len = route->path->len; r_seg_flags.arr = malloc(sizeof(bool) * route_path_len); if (r_seg_flags.arr == NULL) { syslog_server(LOG_ERR, @@ -413,17 +392,17 @@ static t_route_repeated_segment_flags get_route_repeated_segment_flags(const t_i } r_seg_flags.len = route_path_len; - for (size_t i = 0; i < r_seg_flags.len; ++i) { + for (unsigned int i = 0; i < r_seg_flags.len; ++i) { r_seg_flags.arr[i] = false; } // For every segment in route->path, check if it occurs again at a different position. // If yes, set the respective flags in r_seg_flags to true. - for (size_t path_index_i = 0; path_index_i < route_path_len; ++path_index_i) { + for (unsigned int path_index_i = 0; path_index_i < route_path_len; ++path_index_i) { const char *path_item_i = g_array_index(route->path, char *, path_index_i); if (path_item_i != NULL && is_type_segment(path_item_i) && (path_index_i + 1) < route_path_len) { - for (size_t path_index_n = path_index_i + 1; path_index_n < route_path_len; ++path_index_n) { + for (unsigned int path_index_n = path_index_i + 1; path_index_n < route_path_len; ++path_index_n) { const char *path_item_n = g_array_index(route->path, char *, path_index_n); if (path_item_n != NULL && strcmp(path_item_i, path_item_n) == 0) { r_seg_flags.arr[path_index_i] = true; @@ -459,14 +438,14 @@ static t_train_index_on_route_query get_train_pos_index_in_route_ignore_repeated } ret_query.err_code = ERR_TRAIN_NOT_ON_ROUTE; - const size_t path_count = route->path->len; - for (size_t i = 0; i < train_pos_query.length; ++i) { + const unsigned int path_count = route->path->len; + for (unsigned int i = 0; i < train_pos_query.length; ++i) { // Search starting at most recent pos_index to skip unnecessary comparisons if ((ret_query.pos_index + 1) >= path_count) { // pos_index at max, stop. break; } - for (size_t n = ret_query.pos_index + 1; n < path_count; ++n) { + for (unsigned int n = ret_query.pos_index + 1; n < path_count; ++n) { bool ignore = repeated_segment_flags->arr[n]; if (!ignore) { const char *path_item = g_array_index(route->path, char *, n); @@ -482,58 +461,21 @@ static t_train_index_on_route_query get_train_pos_index_in_route_ignore_repeated return ret_query; } -// Queries the position of the train. Checks which segments that the train occupies exist in the route path. -// If train occupies at least one segment in route path, returned t_train_index_on_route_query -// member .pos_index holds the index of the occupied segment that is the furthest along the route, -// and .err_code holds OKAY_TRAIN_ON_ROUTE -// If parameters are invalid, t_train_index_on_route_query .err_code holds ERR_INVALID_PARAM. -// If train is not on tracks, t_train_index_on_route_query .err_code holds ERR_TRAIN_NOT_ON_TRACKS. -// If train is not on route, t_train_index_on_route_query .err_code holds ERR_TRAIN_NOT_ON_ROUTE. -__attribute__ ((unused)) -static t_train_index_on_route_query get_train_pos_index_in_route_path(const char *train_id, - const t_interlocking_route *route) { - t_train_index_on_route_query ret_query = {.pos_index = 0, .err_code = ERR_INVALID_PARAM}; - if (train_id == NULL || route == NULL || route->path == NULL) { - return ret_query; - } - t_bidib_train_position_query train_position_query = bidib_get_train_position(train_id); - if (train_position_query.segments == NULL || train_position_query.length == 0) { - bidib_free_train_position_query(train_position_query); - ret_query.err_code = ERR_TRAIN_NOT_ON_TRACKS; - return ret_query; - } - ret_query.err_code = ERR_TRAIN_NOT_ON_ROUTE; - const size_t path_count = route->path->len; - for (size_t i = 0; i < train_position_query.length; ++i) { - // Search starting at most recent pos_index to skip unnecessary comparisons - for (size_t n = ret_query.pos_index; n < path_count; ++n) { - const char *path_item = g_array_index(route->path, char *, n); - if (n > ret_query.pos_index && strcmp(path_item, train_position_query.segments[i]) == 0) { - ret_query.pos_index = n; - ret_query.err_code = OKAY_TRAIN_ON_ROUTE; - break; - } - } - } - bidib_free_train_position_query(train_position_query); - return ret_query; -} - // For a train at position train_pos_index in route->path, set all signals to stop that // the train has passed and have not yet been set to stop. // Returns the count of how many signals have been set to stop in this function -static size_t update_route_signals_for_train_pos(t_route_signal_info_array *signal_info_array, - t_interlocking_route *route, - size_t train_pos_index) { - size_t signals_set_to_stop = 0; +static unsigned int update_route_signals_for_train_pos(t_route_signal_info_array *signal_info_array, + t_interlocking_route *route, + unsigned int train_pos_index) { + unsigned int signals_set_to_stop = 0; const char *signal_stop_aspect = "aspect_stop"; // 1. For every signal on the route, represented by an entry in signal_info_array - for (size_t sig_info_index = 0; sig_info_index < signal_info_array->len; ++sig_info_index) { + for (unsigned int sig_info_index = 0; sig_info_index < signal_info_array->len; ++sig_info_index) { t_route_signal_info *sig_info = signal_info_array->data_ptr[sig_info_index]; if (sig_info == NULL) { syslog_server(LOG_WARNING, - "Update route signals - route: %s - signal_info_array[%d] is NULL", + "Update route signals - route: %s - signal_info_array[%u] is NULL", route->id, sig_info_index); continue; } @@ -544,13 +486,14 @@ static size_t update_route_signals_for_train_pos(t_route_signal_info_array *sign // 4. Try to set the signal to signal_stop_aspect. if (bidib_set_signal(sig_info->id, signal_stop_aspect)) { syslog_server(LOG_WARNING, - "Update route signals - route: %s signal: %s - unable to set signal to %s", + "Update route signals - route: %s signal: %s - " + "unable to set signal to %s", route->id, sig_info->id, signal_stop_aspect); } else { bidib_flush(); syslog_server(LOG_NOTICE, "Update route signals - route: %s signal: %s - signal set to %s", - sig_info->id, signal_stop_aspect, route->id); + route->id, sig_info->id, signal_stop_aspect); sig_info->has_been_set_to_stop = true; signals_set_to_stop++; } @@ -560,25 +503,23 @@ static size_t update_route_signals_for_train_pos(t_route_signal_info_array *sign return signals_set_to_stop; } -static bool drive_route_decoupled_signal_info_array_valid(const char *route_id, - t_route_signal_info_array *signal_info_array) { +static bool validate_route_signal_info_array(const char *route_id, + t_route_signal_info_array *signal_info_array) { if (signal_info_array == NULL || route_id == NULL) { - syslog_server(LOG_ERR, - "Drive route decoupled signal info array validation - " - "route id or signal info array is NULL"); + syslog_server(LOG_ERR, "Route signal info array validation - invalid (NULL) parameters"); return false; } - // Check that signal_info_array array is not empty and has at least 2 entries (source, destination) + // Check that signal_info_array array is not empty and has >= 2 entries (source, destination) if (signal_info_array->data_ptr == NULL) { syslog_server(LOG_ERR, - "Drive route decoupled signal info array validation - route: %s - " + "Route signal info array validation - route: %s - " "signal info array is NULL", route_id); return false; } else if (signal_info_array->len < 2) { syslog_server(LOG_ERR, - "Drive route decoupled signal info array validation - route: %s - " - "signal info array has only %d elements (at least two elements needed)", + "Route signal info array validation - route: %s - " + "signal info array has only %u elements (at least two elements needed)", route_id, signal_info_array->len); return false; } @@ -586,21 +527,23 @@ static bool drive_route_decoupled_signal_info_array_valid(const char *route_id, } /** - * @brief For a train driving a route, set the signals to stop that the train passes + * @brief For a train driving a route, set the signals to stop that the train passes. * * @param train_id The train driving the route * @param route The route to be driven - * @return true signal updating successful (all signals were passed and set to stop) - * @return false signal updating failed + * @return true if signal updating successful (all passed signals were set to stop, + * and all signals have been passed or the route has been released or the system is stopping), + * otherwise returns false. */ -static bool drive_route_progressive_stop_signals_decoupled(const char *train_id, - t_interlocking_route *route) { +static bool monitor_train_on_route(const char *train_id, t_interlocking_route *route) { if (route == NULL || route->id == NULL) { - syslog_server(LOG_ERR, "Drive route decoupled - route or route id is NULL"); + syslog_server(LOG_ERR, "Monitor train on route - invalid (NULL) route or route->id"); return false; } if (train_id == NULL) { - syslog_server(LOG_ERR, "Drive route decoupled - route: %s - train id is NULL", route->id); + syslog_server(LOG_ERR, + "Monitor train on route - route: %s - invalid (NULL) train_id", + route->id); return false; } @@ -608,21 +551,19 @@ static bool drive_route_progressive_stop_signals_decoupled(const char *train_id, t_route_signal_info_array signal_info_array = get_route_signal_info_array(route); t_route_repeated_segment_flags repeated_segment_flags = get_route_repeated_segment_flags(route); - if (!drive_route_decoupled_signal_info_array_valid(route->id, &signal_info_array)) { + if (!validate_route_signal_info_array(route->id, &signal_info_array)) { free_route_signal_info_array(&signal_info_array); free_route_repeated_segment_flags(&repeated_segment_flags); return false; } - syslog_server(LOG_DEBUG, - "Drive route decoupled - route: %s train: %s - signal_info_array has %d elements", - route->id, train_id, signal_info_array.len); - // Signals in a route shall be set to stop once the train has driven passed them. // Destination signal is already in STOP aspect, thus signal_info_array.len - 1 signals. - const size_t signals_to_set_to_stop_count = signal_info_array.len - 1; - size_t signals_set_to_stop = 0; - size_t train_pos_index_previous = 0; + // No underflow/wraparound to be concerned about because the call to + // `drive_route_decoupled_signal_info_array_valid` above checks that signal_info_array.len >= 2. + const unsigned int signals_to_set_to_stop_count = signal_info_array.len - 1; + unsigned int signals_set_to_stop = 0; + unsigned int train_pos_index_previous = 0; bool first_okay_position = true; while (running && drive_route_params_valid(train_id, route) @@ -632,9 +573,6 @@ static bool drive_route_progressive_stop_signals_decoupled(const char *train_id, get_train_pos_index_in_route_ignore_repeated_segments(train_id, route, &repeated_segment_flags); if (train_pos_query.err_code != OKAY_TRAIN_ON_ROUTE) { - syslog_server(LOG_DEBUG, - "Drive route decoupled - route: %s train: %s - train position unknown", - route->id, train_id); // Train position unknown, perhaps temporarily lost -> skip this iteration. usleep(TRAIN_DRIVE_TIME_STEP); continue; @@ -645,109 +583,49 @@ static bool drive_route_progressive_stop_signals_decoupled(const char *train_id, if (train_pos_index_previous != train_pos_query.pos_index || first_okay_position) { const char *path_item = g_array_index(route->path, char *, train_pos_query.pos_index); syslog_server(LOG_DEBUG, - "Drive route decoupled - route: %s train: %s - train is at index %d (%s)", + "Monitor train on route - route: %s train: %s - train is at index %u (%s)", route->id, train_id, train_pos_query.pos_index, - path_item != NULL ? path_item : "NULL"); - - if (train_pos_index_previous > train_pos_query.pos_index) { - syslog_server(LOG_DEBUG, - "Drive route decoupled - route: %s train: %s - " - "new train path index %d is lower than previous path index %d", - route->id, train_id, - train_pos_query.pos_index, train_pos_index_previous); - } - - signals_set_to_stop += update_route_signals_for_train_pos(&signal_info_array, route, train_pos_query.pos_index); + path_item != NULL ? path_item : "PATH-ITEM-IS-NULL"); + signals_set_to_stop += + update_route_signals_for_train_pos(&signal_info_array, route, + train_pos_query.pos_index); first_okay_position = false; } train_pos_index_previous = train_pos_query.pos_index; usleep(TRAIN_DRIVE_TIME_STEP); } - syslog_server(LOG_NOTICE, - "Drive route decoupled - Finished setting %d signals to stop for route id %s", - signals_set_to_stop, route->id); + syslog_server(LOG_INFO, + "Monitor train on route - route: %s train: %s - Finished, set %u signals to stop", + route->id, train_id, signals_set_to_stop); free_route_signal_info_array(&signal_info_array); free_route_repeated_segment_flags(&repeated_segment_flags); return true; } -__attribute__ ((unused)) -static bool drive_route_progressive_stop_signals(const char *train_id, t_interlocking_route *route) { - if (train_id == NULL || route == NULL || route->id == NULL) { - return false; - } - const char *signal_stop_aspect = "aspect_stop"; - const char *next_signal = route->source; - bool set_signal_stop = true; - const int path_count = route->path->len; - for (size_t path_item_index = 0; path_item_index < path_count; path_item_index++) { - // Get path item (segment or signal) - const char *path_item = g_array_index(route->path, char *, path_item_index); - - if (is_type_signal(path_item)) { - // Train will encounter a signal when it exits the current segment - next_signal = path_item; - set_signal_stop = true; - } - - if (set_signal_stop && is_type_segment(path_item)) { - // Signal that the train has just passed will be set to the Stop aspect - // when it enters the next segment - - // Wait until the next segment is entered - while (running && !train_position_is_at(train_id, path_item)) { - usleep(TRAIN_DRIVE_TIME_STEP); - route = get_route(route->id); - if (!drive_route_params_valid(train_id, route)) { - return false; - } - } - - // Set signal to the Stop aspect - set_signal_stop = false; - if (bidib_set_signal(next_signal, signal_stop_aspect)) { - syslog_server(LOG_ERR, - "Drive route progressive stop signals - " - "unable to set route signal %s to aspect %s", - next_signal, signal_stop_aspect); - } else { - bidib_flush(); - syslog_server(LOG_NOTICE, - "Drive route progressive stop signals - set signal: %s to aspect: %s", - next_signal, signal_stop_aspect); - } - } - } - - return true; -} - -static bool drive_route(const int grab_id, const char *route_id, const bool is_automatic) { - pthread_mutex_lock(&grabbed_trains_mutex); - char *train_id = strdup(grabbed_trains[grab_id].name->str); - pthread_mutex_unlock(&grabbed_trains_mutex); - - if (train_id == NULL) { - syslog_server(LOG_ERR, "Drive route - unable to allocate memory for train_id"); +static bool drive_route(const int grab_id, const char* train_id, const char *route_id, bool is_automatic) { + if (train_id == NULL || route_id == NULL) { + syslog_server(LOG_ERR, "Drive route - invalid (NULL) parameters"); return false; } t_interlocking_route *route = get_route(route_id); if (route == NULL || !drive_route_params_valid(train_id, route)) { - syslog_server(LOG_ERR, "Drive route - unable to start driving because route is invalid"); - free(train_id); + syslog_server(LOG_ERR, + "Drive route - route: %s train: %s - " + "unable to start driving because route is invalid", + route_id, train_id); return false; } - + // Driving starts: Driving direction is computed from the route orientation syslog_server(LOG_NOTICE, "Drive route - route: %s train: %s - %s driving starts", - route->id, train_id, is_automatic ? "automatic" : "manual"); + route_id, train_id, is_automatic ? "automatic" : "manual"); - pthread_mutex_lock(&grabbed_trains_mutex); + pthread_mutex_lock(&grabbed_trains_mutex); const int engine_instance = grabbed_trains[grab_id].dyn_containers_engine_instance; - const char requested_forwards = is_forward_driving(route, train_id); + const bool requested_forwards = is_forward_driving(route, train_id); if (is_automatic) { dyn_containers_set_train_engine_instance_inputs(engine_instance, DRIVING_SPEED_SLOW, @@ -757,24 +635,27 @@ static bool drive_route(const int grab_id, const char *route_id, const bool is_a // Set the signals along the route to Stop as the train drives past them // This will return as soon as the train has passed all but the destination signal - const bool result = drive_route_progressive_stop_signals_decoupled(train_id, route); + const bool result = monitor_train_on_route(train_id, route); // If driving is automatic, slow train down at the end of the route if (is_automatic && result) { + // Assumes that all routes have a path with a length of at least 2. const char *pre_dest_segment = g_array_index(route->path, char *, route->path->len - 2); while (running && !train_position_is_at(train_id, pre_dest_segment) && drive_route_params_valid(train_id, route)) { usleep(TRAIN_DRIVE_TIME_STEP); } - syslog_server(LOG_NOTICE, - "Drive route - route: %s train: %s - slowing down for end of route", - train_id, route_id); - pthread_mutex_lock(&grabbed_trains_mutex); - dyn_containers_set_train_engine_instance_inputs(engine_instance, - DRIVING_SPEED_STOPPING, - requested_forwards); - pthread_mutex_unlock(&grabbed_trains_mutex); + if (train_get_grab_id(train_id) == grab_id) { + syslog_server(LOG_NOTICE, + "Drive route - route: %s train: %s - slowing down for end of route", + route_id, train_id); + pthread_mutex_lock(&grabbed_trains_mutex); + dyn_containers_set_train_engine_instance_inputs(engine_instance, + DRIVING_SPEED_STOPPING, + requested_forwards); + pthread_mutex_unlock(&grabbed_trains_mutex); + } } // Wait for train to reach the end of the route @@ -789,60 +670,78 @@ static bool drive_route(const int grab_id, const char *route_id, const bool is_a struct timespec tva, tvb; clock_gettime(CLOCK_MONOTONIC, &tva); syslog_server(LOG_INFO, - "Drive route - route: %s train: %s - end of route (%s) reached detected at %d.%.9ld", - route->id, train_id, dest_segment, tva.tv_sec, tva.tv_nsec); + "Drive route - route: %s train: %s - end of route (%s) reached detected at %ld.%06ld", + route->id, train_id, dest_segment, tva.tv_sec, tva.tv_nsec/1000); // Driving stops - pthread_mutex_lock(&grabbed_trains_mutex); - clock_gettime(CLOCK_MONOTONIC, &tvb); - dyn_containers_set_train_engine_instance_inputs(engine_instance, 0, requested_forwards); - pthread_mutex_unlock(&grabbed_trains_mutex); - syslog_server(LOG_NOTICE, - "Drive route - route: %s train: %s - driving stops (commanded at %d.%.9ld)", - route->id, train_id, tvb.tv_sec, tvb.tv_nsec); + if (train_get_grab_id(train_id) == grab_id) { + pthread_mutex_lock(&grabbed_trains_mutex); + clock_gettime(CLOCK_MONOTONIC, &tvb); + dyn_containers_set_train_engine_instance_inputs(engine_instance, 0, requested_forwards); + pthread_mutex_unlock(&grabbed_trains_mutex); + syslog_server(LOG_NOTICE, + "Drive route - route: %s train: %s - driving stops (commanded at %ld.%06ld)", + route_id, train_id, tvb.tv_sec, tvb.tv_nsec/1000); + // Give train engine container some time to actuate before moving on to releasing + // -> cleaner log, i.e., for log analysis tools it doesn't look like we are releasing + // the route while the train is still driving if we wait a little here. + usleep(TRAIN_DRIVE_TIME_STEP*5); + } else { + bidib_set_train_speed(train_id, 0, "master"); + clock_gettime(CLOCK_MONOTONIC, &tvb); + bidib_flush(); + syslog_server(LOG_WARNING, + "Drive route - route: %s train: %s - driving stops (commanded at %ld.%06ld) " + "directly via bidib, train was released during route driving!", + route_id, train_id, tvb.tv_sec, tvb.tv_nsec/1000); + } // Release the route if (drive_route_params_valid(train_id, route)) { release_route(route_id); } - free(train_id); return true; } static int grab_train(const char *train, const char *engine) { if (train == NULL || engine == NULL) { - syslog_server(LOG_ERR, "Grab train - invalid (NULL) parameter(s)"); - return -1; + syslog_server(LOG_ERR, "Grab train - invalid (NULL) parameters"); + return -4; } pthread_mutex_lock(&grabbed_trains_mutex); - for (size_t i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { + // Check if train is already grabbed + for (int i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { if (grabbed_trains[i].is_valid && strcmp(grabbed_trains[i].name->str, train) == 0) { pthread_mutex_unlock(&grabbed_trains_mutex); syslog_server(LOG_ERR, "Grab train - train: %s engine: %s - train already grabbed", train, engine); - return -1; + return -3; } } - int start = next_grab_id; + // Check if there is an unused grab-id + const int start = next_grab_id; if (grabbed_trains[next_grab_id].is_valid) { increment_next_grab_id(); while (grabbed_trains[next_grab_id].is_valid) { if (next_grab_id == start) { pthread_mutex_unlock(&grabbed_trains_mutex); syslog_server(LOG_ERR, - "Grab train - train: %s engine: %s - all grab ids in use", + "Grab train - train: %s engine: %s - all grab-ids in use", train, engine); - return -1; + return -2; } increment_next_grab_id(); } } - int grab_id = next_grab_id; - increment_next_grab_id(); + // Assign grab-id, set track output to master, set engine instance + const int grab_id = next_grab_id; + increment_next_grab_id(); // increment for next "grab" action grabbed_trains[grab_id].name = g_string_new(train); + strcpy(grabbed_trains[grab_id].track_output, "master"); + if (dyn_containers_set_train_engine_instance(&grabbed_trains[grab_id], train, engine)) { pthread_mutex_unlock(&grabbed_trains_mutex); syslog_server(LOG_ERR, @@ -865,9 +764,9 @@ bool release_train(int grab_id) { grabbed_trains[grab_id].is_valid = false; dyn_containers_free_train_engine_instance(grabbed_trains[grab_id].dyn_containers_engine_instance); syslog_server(LOG_NOTICE, - "Release train - grab id: %d train: %s - released", + "Release train - grab-id: %d train: %s - released", grab_id, grabbed_trains[grab_id].name->str); - g_string_free(grabbed_trains[grab_id].name, TRUE); + g_string_free(grabbed_trains[grab_id].name, true); grabbed_trains[grab_id].name = NULL; success = true; } @@ -876,7 +775,7 @@ bool release_train(int grab_id) { } void release_all_grabbed_trains(void) { - for (size_t i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { + for (int i = 0; i < TRAIN_ENGINE_INSTANCE_COUNT_MAX; i++) { release_train(i); } } @@ -887,61 +786,86 @@ char *train_id_from_grab_id(int grab_id) { pthread_mutex_unlock(&grabbed_trains_mutex); return NULL; } + if (grabbed_trains[grab_id].name == NULL) { + pthread_mutex_unlock(&grabbed_trains_mutex); + syslog_server(LOG_ERR, + "Train id from grab-id - train with id %d marked valid but name is NULL", + grab_id); + return NULL; + } char *train_id = strdup(grabbed_trains[grab_id].name->str); pthread_mutex_unlock(&grabbed_trains_mutex); if (train_id == NULL) { - syslog_server(LOG_ERR, "Train id from grab id - unable to allocate memory for train_id"); - return NULL; + syslog_server(LOG_ERR, "Train id from grab-id - unable to allocate memory for train_id"); } return train_id; } +static GString *build_grab_fdbk_json(int l_session_id, int grab_id) { + GString *g_feedback = g_string_sized_new(96); + g_string_assign(g_feedback, ""); + append_start_of_obj(g_feedback, false); + append_field_int_value(g_feedback, "session-id", l_session_id, true); + append_field_int_value(g_feedback, "grab-id", grab_id, false); + append_end_of_obj(g_feedback, false); + return g_feedback; +} -onion_connection_status handler_grab_train(void *_, onion_request *req, onion_response *res) { +o_con_status handler_grab_train(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_train = onion_request_get_post(req, "train"); const char *data_engine = onion_request_get_post(req, "engine"); - if (data_train == NULL || data_engine == NULL) { - syslog_server(LOG_ERR, "Request: Grab train - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Grab train", "train", data_train) + || handle_param_miss_check(res, "Grab train", "engine", data_engine)) { + return OCS_PROCESSED; } + syslog_server(LOG_NOTICE, "Request: Grab train - train: %s engine: %s - start", data_train, data_engine); t_bidib_train_state_query train_state_query = bidib_get_train_state(data_train); - if (!train_state_query.known) { - bidib_free_train_state_query(train_state_query); + bool trainstate_known = train_state_query.known; + bidib_free_train_state_query(train_state_query); + if (!trainstate_known) { + send_common_feedback(res, HTTP_NOT_FOUND, "unknown train or invalid train state"); syslog_server(LOG_ERR, "Request: Grab train - train: %s engine: %s - " - "unknown train or train state - abort", + "unknown train or invalid train state - abort", data_train, data_engine); - return OCS_NOT_IMPLEMENTED; + return OCS_PROCESSED; } - bidib_free_train_state_query(train_state_query); int grab_id = grab_train(data_train, data_engine); - if (grab_id == -1) { - syslog_server(LOG_ERR, - "Request: Grab train - train: %s engine: %s - train could not be grabbed - abort", - data_train, data_engine); - return OCS_NOT_IMPLEMENTED; + // No extra syslog per case as grab_train logs extensively. + if (grab_id >= 0) { + send_some_gstring_and_free(res, HTTP_OK, build_grab_fdbk_json(session_id, grab_id)); + } else if (grab_id == -4) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid parameters"); + } else if (grab_id == -3) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "train has already been grabbed"); + } else if (grab_id == -2) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, + "all grab-IDs are in use (max no. of grabbed trains reached)"); + } else if (grab_id == -1) { + send_common_feedback(res, HTTP_INTERNAL_ERROR, + "internal err - failed to start train engine container"); } else { - syslog_server(LOG_NOTICE, - "Request: Grab train - train: %s engine: %s - finish", - data_train, data_engine); - onion_response_printf(res, "%ld,%d", session_id, grab_id); - return OCS_PROCESSED; + send_common_feedback(res, HTTP_INTERNAL_ERROR, + "internal err - unexpected error case in grab_train"); } + syslog_server(LOG_NOTICE, + "Request: Grab train - train: %s engine: %s - finish", + data_train, data_engine); + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Grab train - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Grab train"); } } -onion_connection_status handler_release_train(void *_, onion_request *req, onion_response *res) { +o_con_status handler_release_train(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_session_id = onion_request_get_post(req, "session-id"); @@ -949,22 +873,27 @@ onion_connection_status handler_release_train(void *_, onion_request *req, onion const int client_session_id = params_check_session_id(data_session_id); const int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); - if (client_session_id != session_id) { + if (handle_param_miss_check(res, "Release train", "session-id", data_session_id) + || handle_param_miss_check(res, "Release train", "grab-id", data_grab_id)) { + return OCS_PROCESSED; + } else if (client_session_id != session_id) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid session-id"); syslog_server(LOG_ERR, - "Request: Release train - grab id: %d - invalid session id: %s", + "Request: Release train - grab-id: %d - invalid session-id: %s", grab_id, data_session_id); - return OCS_NOT_IMPLEMENTED; + return OCS_PROCESSED; } // If grab_id is valid, train_id will be, too. char *train_id = train_id_from_grab_id(grab_id); if (train_id == NULL) { - syslog_server(LOG_ERR, "Request: Release train - grab id: %d - invalid grab id", grab_id); - return OCS_NOT_IMPLEMENTED; + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid grab-id"); + syslog_server(LOG_ERR, "Request: Release train - grab-id: %d - invalid grab-id", grab_id); + return OCS_PROCESSED; } syslog_server(LOG_NOTICE, - "Request: Release train - grab id: %d train: %s - start", + "Request: Release train - grab-id: %d train: %s - start", grab_id, train_id); // Set train speed to 0 @@ -973,8 +902,11 @@ onion_connection_status handler_release_train(void *_, onion_request *req, onion dyn_containers_set_train_engine_instance_inputs(engine_instance, 0, true); pthread_mutex_unlock(&grabbed_trains_mutex); - // Wait until the train has stopped moving + ///NOTE: There is a potential race condition with set-dcc-speed: + /// If release is requested and a non-zero speed is set just barely after/during that, + /// the effective speed will not become 0 (which we wait for in the loop below). + ///TODO: Consider adding a timeout after which the train is directly set to stop via bidib. t_bidib_train_state_query train_state_query = bidib_get_train_state(train_id); while (train_state_query.data.set_speed_step != 0) { bidib_free_train_state_query(train_state_query); @@ -984,25 +916,56 @@ onion_connection_status handler_release_train(void *_, onion_request *req, onion bidib_free_train_state_query(train_state_query); if (!release_train(grab_id)) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "train is not currently grabbed"); syslog_server(LOG_ERR, - "Request: Release train - grab id: %d train: %s - invalid grab id - abort", + "Request: Release train - grab-id: %d train: %s - " + "train is not currently grabbed - abort", grab_id, train_id); - free(train_id); - return OCS_NOT_IMPLEMENTED; } else { + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, - "Request: Release train - grab id: %d train: %s - finish", + "Request: Release train - grab-id: %d train: %s - finish", grab_id, train_id); - free(train_id); - return OCS_PROCESSED; } + free(train_id); + return OCS_PROCESSED; + } else { + return handle_req_run_or_method_fail(res, running, "Release train"); + } +} + +static void process_request_route_and_reply(onion_response *res, const char *train_id, + const char *source, const char *destination) { + syslog_server(LOG_NOTICE, + "Request: Request route - train: %s from: %s to: %s - start", + train_id, source, destination); + // Use interlocker to find and grant a route + GString *route_id = grant_route(train_id, source, destination); + // No extra syslog in both branches as grant_route logs extensively + if (route_id != NULL && route_id->str != NULL && params_check_is_number(route_id->str)) { + send_single_str_field_feedback(res, HTTP_OK, "granted-route-id", route_id->str); + } else if (route_id == NULL || route_id->str == NULL) { + send_common_feedback(res, HTTP_BAD_REQUEST, "Route could not be granted"); + } else if (strcmp(route_id->str, "no_interlocker") == 0) { + send_common_feedback(res, HTTP_BAD_REQUEST, "No interlocker selected for use"); + } else if (strcmp(route_id->str, "no_routes") == 0) { + send_common_feedback(res, HTTP_BAD_REQUEST, "No routes possible"); + } else if (strcmp(route_id->str, "not_grantable") == 0) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, + "Route conflicts with granted route(s)"); + } else if (strcmp(route_id->str, "not_clear") == 0) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, + "Route found has occupied tracks or source signal is not stop"); } else { - syslog_server(LOG_ERR, "Request: Release train - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + send_common_feedback(res, HTTP_BAD_REQUEST, "Route could not be granted"); } + syslog_server(LOG_NOTICE, + "Request: Request route - train: %s from: %s to: %s - finish", + train_id, source, destination); + g_string_free(route_id, true); } -onion_connection_status handler_request_route(void *_, onion_request *req, onion_response *res) { +o_con_status handler_request_route(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_session_id = onion_request_get_post(req, "session-id"); @@ -1012,73 +975,37 @@ onion_connection_status handler_request_route(void *_, onion_request *req, onion const int client_session_id = params_check_session_id(data_session_id); const int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); - if (data_source_name == NULL || data_destination_name == NULL) { - syslog_server(LOG_ERR, "Request: Request train route - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Request route", "session-id", data_session_id) + || handle_param_miss_check(res, "Request route", "grab-id", data_grab_id) + || handle_param_miss_check(res, "Request route", "destination", data_destination_name) + || handle_param_miss_check(res, "Request route", "source", data_source_name)) { + return OCS_PROCESSED; } else if (client_session_id != session_id) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid session-id"); syslog_server(LOG_ERR, - "Request: Request train route - from: %s to: %s - invalid session id", - data_source_name, data_destination_name); - return OCS_NOT_IMPLEMENTED; - } - + "Request: Request route - from: %s to: %s - invalid session-id (%s)", + data_source_name, data_destination_name, data_session_id); + return OCS_PROCESSED; + } // If grab_id is valid, train_id will be, too. char *train_id = train_id_from_grab_id(grab_id); if (train_id == NULL) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid grab-id"); syslog_server(LOG_ERR, - "Request: Request train route - from: %s to: %s - invalid grab id", - data_source_name, data_destination_name); - return OCS_NOT_IMPLEMENTED; + "Request: Request route - from: %s to: %s - invalid grab-id (%s)", + data_source_name, data_destination_name, data_grab_id); + return OCS_PROCESSED; } - syslog_server(LOG_NOTICE, - "Request: Request train route - train: %s from: %s to: %s - start", - train_id, data_source_name, data_destination_name); - - // Use interlocker to find and grant a route - GString *route_id = grant_route(train_id, data_source_name, data_destination_name); - if (route_id->str != NULL && params_check_is_number(route_id->str)) { - syslog_server(LOG_NOTICE, - "Request: Request train route - train: %s from: %s to: %s - route %s granted", - train_id, data_source_name, data_destination_name, route_id->str); - onion_response_printf(res, "%s", route_id->str); - } else { - onion_response_set_code(res, HTTP_BAD_REQUEST); - syslog_server(LOG_WARNING, - "Request: Request train route - train: %s from: %s to: %s - route %s not granted", - train_id, data_source_name, data_destination_name, route_id->str); - - if (strcmp(route_id->str, "no_interlocker") == 0) { - onion_response_printf(res, "No interlocker has been selected for use"); - } else if (strcmp(route_id->str, "no_routes") == 0) { - onion_response_printf(res, - "No routes possible from %s to %s", - data_source_name, data_destination_name); - } else if (strcmp(route_id->str, "not_grantable") == 0) { - onion_response_printf(res, "Route found conflicts with others"); - } else if (strcmp(route_id->str, "not_clear") == 0) { - onion_response_printf(res, - "Route found has occupied tracks or source signal is not stop"); - } else { - onion_response_printf(res, "Route could not be granted (%s)", route_id->str); - } - syslog_server(LOG_NOTICE, - "Request: Request train route - train: %s from: %s to: %s - finish", - train_id, data_source_name, data_destination_name); - } - g_string_free(route_id, true); + process_request_route_and_reply(res, train_id, data_source_name, data_destination_name); free(train_id); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Request train route - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Request route"); } } -///TODO: Discuss - maybe this should be called "request_route_by_id", to distinguish it from -// just getting a route id of/for something (similar to a monitor endpoint). -onion_connection_status handler_request_route_id(void *_, onion_request *req, onion_response *res) { +o_con_status handler_request_route_by_id(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_session_id = onion_request_get_post(req, "session-id"); @@ -1088,102 +1015,121 @@ onion_connection_status handler_request_route_id(void *_, onion_request *req, on const int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); const char *route_id = params_check_route_id(data_route_id); - if (strcmp(route_id, "") == 0) { - syslog_server(LOG_ERR, "Request: Request train route id - invalid route id"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Request route by id", "session-id", data_session_id) + || handle_param_miss_check(res, "Request route by id", "grab-id", data_grab_id) + || handle_param_miss_check(res, "Request route by id", "route-id", data_route_id)) { + return OCS_PROCESSED; + } else if (strcmp(route_id, "") == 0) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid route-id"); + syslog_server(LOG_ERR, + "Request: Request route by id - invalid route-id (%s)", + data_route_id); + return OCS_PROCESSED; } else if (client_session_id != session_id) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid session-id"); syslog_server(LOG_ERR, - "Request: Request train route id - route: %s - invalid session id", - route_id); - return OCS_NOT_IMPLEMENTED; + "Request: Request route by id - route: %s - invalid session-id (%s)", + route_id, data_session_id); + return OCS_PROCESSED; } - // If grab_id is valid, train_id will be, too. char *train_id = train_id_from_grab_id(grab_id); if (train_id == NULL) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid grab-id"); syslog_server(LOG_ERR, - "Request: Request train route id - route: %s - invalid grab id", - route_id); - return OCS_NOT_IMPLEMENTED; + "Request: Request route by id - route: %s - invalid grab-id (%s)", + route_id, data_grab_id); + return OCS_PROCESSED; } syslog_server(LOG_NOTICE, - "Request: Request train route id - train: %s route: %s - start", - train_id, route_id); + "Request: Request route by id - route: %s train: %s - start", + route_id, train_id); // Grant the route ID using an internal algorithm const char *result = grant_route_id(train_id, route_id); - + // No extra syslog as grant_route_id logs extensively if (strcmp(result, "granted") == 0) { - onion_response_printf(res, "%s", result); - syslog_server(LOG_NOTICE, - "Request: Request train route id - train: %s route: %s - route granted", - train_id, route_id, result); + onion_response_set_code(res, HTTP_OK); + } else if (strcmp(result, "not_known") == 0) { + send_common_feedback(res, HTTP_NOT_FOUND, "Route is not known"); + } else if (strcmp(result, "already_granted") == 0) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, + "Route is already granted to another train"); + } else if (strcmp(result, "not_grantable") == 0) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, + "Route not available or has conflicts with granted route(s)"); + } else if (strcmp(result, "not_clear") == 0) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, + "Route has occupied tracks or source signal is not stop"); + } else if (strcmp(result, "internal_error") == 0) { + send_common_feedback(res, HTTP_INTERNAL_ERROR, + "Route granting failed due to internal error"); } else { - onion_response_set_code(res, HTTP_BAD_REQUEST); - syslog_server(LOG_WARNING, - "Request: Request train route id - train: %s route: %s - route not granted (%s)", - train_id, route_id, result); - if (strcmp(result, "not_grantable") == 0) { - onion_response_printf(res, - "Route %s is not available or has conflicts with others", - route_id); - } else if (strcmp(result, "not_clear") == 0) { - onion_response_printf(res, - "Route %s has occupied tracks or source signal is not stop", - route_id); - } else { - onion_response_printf(res, - "Route %s could not be granted", - route_id); - } + send_common_feedback(res, HTTP_BAD_REQUEST, "Route could not be granted"); } + syslog_server(LOG_NOTICE, - "Request: Request train route id - train: %s route: %s - finish", - train_id, route_id); + "Request: Request route by id - route: %s train: %s - finish", + route_id, train_id); free(train_id); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Request train route id - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Request route by id"); } } -onion_connection_status handler_driving_direction(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_driving_direction(void *_, onion_request *req, onion_response *res) { + // Notes regarding documentation: + // The driving direction is determined based on the route specified *and* the trains position + // and the trains orientation. The position of the train is relevant as a Kehrschleife/ + // reverser can influence the expected/correct result. + // I.e., given where the train currently is located, and which route is to be driven, + // what direction would the train have to drive (forwards/backwards) to reach the destination? + build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_train = onion_request_get_post(req, "train"); const char *data_route_id = onion_request_get_post(req, "route-id"); const char *route_id = params_check_route_id(data_route_id); - if (data_train == NULL) { - syslog_server(LOG_ERR, "Request: Driving direction - train id is NULL"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Driving direction", "train", data_train) + || handle_param_miss_check(res, "Driving direction", "route-id", data_route_id)) { + return OCS_PROCESSED; } else if (strcmp(route_id, "") == 0) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid route-id"); syslog_server(LOG_ERR, - "Request: Driving direction - train: %s - invalid route id", - data_train); - return OCS_NOT_IMPLEMENTED; + "Request: Driving direction - train: %s - invalid route-id (%s)", + data_train, data_route_id); + return OCS_PROCESSED; } - syslog_server(LOG_INFO, "Request: Driving direction - train: %s - start", data_train); + syslog_server(LOG_INFO, + "Request: Driving direction - train: %s route: %s - start", + data_train, route_id); pthread_mutex_lock(&interlocker_mutex); const t_interlocking_route *route = get_route(route_id); - onion_response_printf(res, "%s", - is_forward_driving(route, data_train) ? "forwards" : "backwards"); + if (route == NULL) { + send_common_feedback(res, HTTP_NOT_FOUND, "no route with given route-id known"); + syslog_server(LOG_INFO, + "Request: Driving direction - train: %s route: %s - unknown route", + data_train, route_id); + } else if (is_forward_driving(route, data_train)) { + send_some_cstring(res, HTTP_OK, "{\"direction\": \"forwards\"}"); + } else { + send_some_cstring(res, HTTP_OK, "{\"direction\": \"backwards\"}"); + } pthread_mutex_unlock(&interlocker_mutex); - syslog_server(LOG_INFO, "Request: Driving direction - train: %s - finish", data_train); + syslog_server(LOG_INFO, + "Request: Driving direction - train: %s route: %s - finish", + data_train, route_id); return OCS_PROCESSED; - } else { - syslog_server(LOG_ERR, - "Request: Driving direction - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Driving direction"); } } -onion_connection_status handler_drive_route(void *_, onion_request *req, onion_response *res) { +o_con_status handler_drive_route(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_session_id = onion_request_get_post(req, "session-id"); @@ -1195,22 +1141,33 @@ onion_connection_status handler_drive_route(void *_, onion_request *req, onion_r const char *route_id = params_check_route_id(data_route_id); const char *mode = params_check_mode(data_mode); - if (client_session_id != session_id) { - syslog_server(LOG_ERR, "Request: Drive route - invalid session id"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Drive route", "session-id", data_session_id) + || handle_param_miss_check(res, "Drive route", "grab-id", data_grab_id) + || handle_param_miss_check(res, "Drive route", "route-id", data_route_id) + || handle_param_miss_check(res, "Drive route", "mode", data_mode)) { + return OCS_PROCESSED; + } else if (client_session_id != session_id) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid session-id"); + syslog_server(LOG_ERR, "Request: Drive route - invalid session-id (%s)", data_session_id); + return OCS_PROCESSED; } else if (strcmp(mode, "") == 0) { - syslog_server(LOG_ERR, "Request: Drive route - invalid driving mode"); - return OCS_NOT_IMPLEMENTED; + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid driving mode"); + syslog_server(LOG_ERR, "Request: Drive route - invalid driving mode (%s)", data_mode); + return OCS_PROCESSED; } else if (strcmp(route_id, "") == 0) { - syslog_server(LOG_ERR, "Request: Drive route - invalid route id"); - return OCS_NOT_IMPLEMENTED; + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid route-id"); + syslog_server(LOG_ERR, "Request: Drive route - invalid route-id (%s)", data_route_id); + return OCS_PROCESSED; } // If grab_id is valid, train_id will be, too. char *train_id = train_id_from_grab_id(grab_id); if (train_id == NULL) { - syslog_server(LOG_ERR, "Request: Drive route - route: %s - invalid grab id", route_id); - return OCS_NOT_IMPLEMENTED; + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid grab-id"); + syslog_server(LOG_ERR, + "Request: Drive route - route: %s - invalid grab-id (%s)", + route_id, data_grab_id); + return OCS_PROCESSED; } syslog_server(LOG_NOTICE, @@ -1218,62 +1175,78 @@ onion_connection_status handler_drive_route(void *_, onion_request *req, onion_r route_id, train_id, mode); const bool is_automatic = (strcmp(mode, "automatic") == 0); - if (drive_route(grab_id, route_id, is_automatic)) { - onion_response_printf(res, "Route %s driving completed", route_id); + if (drive_route(grab_id, train_id, route_id, is_automatic)) { + send_common_feedback(res, HTTP_OK, "Route driving completed"); syslog_server(LOG_NOTICE, "Request: Drive route - route: %s train: %s drive mode: %s - finish", route_id, train_id, mode); - free(train_id); - return OCS_PROCESSED; } else { + send_common_feedback(res, HTTP_BAD_REQUEST, + "Route driving failed; please ensure that the route is granted " + "to the train corresponding to the grab-id"); syslog_server(LOG_ERR, "Request: Drive route - route: %s train: %s drive mode: %s - " "driving failed - abort", route_id, train_id, mode); - ///TODO: Automatic countermeasures? e.g. set train speed to 0 - free(train_id); - return OCS_NOT_IMPLEMENTED; } + free(train_id); + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Drive route - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Drive route"); } } -onion_connection_status handler_set_dcc_train_speed(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_set_dcc_train_speed(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_session_id = onion_request_get_post(req, "session-id"); const char *data_grab_id = onion_request_get_post(req, "grab-id"); const char *data_speed = onion_request_get_post(req, "speed"); const char *data_track_output = onion_request_get_post(req, "track-output"); - int client_session_id = params_check_session_id(data_session_id); - int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); - int speed = params_check_speed(data_speed); + const int client_session_id = params_check_session_id(data_session_id); + const int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); + const int speed = params_check_speed(data_speed); - if (client_session_id != session_id) { - syslog_server(LOG_ERR, "Request: Set dcc train speed - invalid session id"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Set dcc train speed", "session-id", data_session_id) + || handle_param_miss_check(res, "Set dcc train speed", "grab-id", data_grab_id) + || handle_param_miss_check(res, "Set dcc train speed", "track-output", data_track_output) + || handle_param_miss_check(res, "Set dcc train speed", "speed", data_speed)) { + return OCS_PROCESSED; + } else if (client_session_id != session_id) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid session-id"); + syslog_server(LOG_ERR, + "Request: Set dcc train speed - invalid session-id (%s)", + data_session_id); + return OCS_PROCESSED; } pthread_mutex_lock(&grabbed_trains_mutex); if (grab_id == -1 || !grabbed_trains[grab_id].is_valid) { pthread_mutex_unlock(&grabbed_trains_mutex); - syslog_server(LOG_ERR, "Request: Set dcc train speed - invalid grab id"); - return OCS_NOT_IMPLEMENTED; + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid grab-id"); + syslog_server(LOG_ERR, + "Request: Set dcc train speed - invalid grab-id (%s)", + data_grab_id); + return OCS_PROCESSED; } else if (speed == 999) { + send_common_feedback(res, HTTP_BAD_REQUEST, "bad speed"); syslog_server(LOG_ERR, - "Request: Set dcc train speed - train: %s speed: %d - bad speed", - grabbed_trains[grab_id].name->str, speed); + "Request: Set dcc train speed - train: %s speed: %d - bad speed (%s)", + grabbed_trains[grab_id].name->str, speed, data_speed); pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; - } else if (data_track_output == NULL) { + return OCS_PROCESSED; + } else if (strlen(data_track_output) > 32) { + // strlen check here as the value is copied to grabbed_trains[grab_id].track_output, + // which has a length of 32. + send_common_feedback(res, HTTP_BAD_REQUEST, + "invalid track output (currently no track output longer than " + "32 chars, incl. nul terminator, is supported for this endpoint)"); syslog_server(LOG_ERR, - "Request: Set dcc train speed - train: %s speed: %d - invalid track output", - grabbed_trains[grab_id].name->str, speed); + "Request: Set dcc train speed - train: %s speed: %d - " + "invalid track output (%s)", + grabbed_trains[grab_id].name->str, speed, data_track_output); pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; + return OCS_PROCESSED; } syslog_server(LOG_NOTICE, @@ -1287,50 +1260,53 @@ onion_connection_status handler_set_dcc_train_speed(void *_, onion_request *req, "Request: Set dcc train speed - train: %s speed: %d - finish", grabbed_trains[grab_id].name->str, speed); pthread_mutex_unlock(&grabbed_trains_mutex); + onion_response_set_code(res, HTTP_OK); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Set dcc train speed - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set dcc train speed"); } } -onion_connection_status handler_set_calibrated_train_speed(void *_, - onion_request *req, - onion_response *res) { +o_con_status handler_set_calibrated_train_speed(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_session_id = onion_request_get_post(req, "session-id"); const char *data_grab_id = onion_request_get_post(req, "grab-id"); const char *data_speed = onion_request_get_post(req, "speed"); const char *data_track_output = onion_request_get_post(req, "track-output"); - int client_session_id = params_check_session_id(data_session_id); - int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); - int speed = params_check_calibrated_speed(data_speed); + const int client_session_id = params_check_session_id(data_session_id); + const int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); + const int speed = params_check_calibrated_speed(data_speed); - if (client_session_id != session_id) { - syslog_server(LOG_ERR, "Request: Set calibrated train speed - invalid session id"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Set calibrated train speed", "session-id", data_session_id) + || handle_param_miss_check(res, "Set calibrated train speed", "grab-id", data_grab_id) + || handle_param_miss_check(res, "Set calibrated train speed", "track-output", + data_track_output) + || handle_param_miss_check(res, "Set calibrated train speed", "speed", data_speed)) { + return OCS_PROCESSED; + } else if (client_session_id != session_id) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid session-id"); + syslog_server(LOG_ERR, + "Request: Set calibrated train speed - invalid session-id (%s)", + data_session_id); + return OCS_PROCESSED; } pthread_mutex_lock(&grabbed_trains_mutex); if (grab_id == -1 || !grabbed_trains[grab_id].is_valid) { pthread_mutex_unlock(&grabbed_trains_mutex); - syslog_server(LOG_ERR, "Request: Set calibrated train speed - invalid grab id"); - return OCS_NOT_IMPLEMENTED; - } else if (speed == 999) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid grab-id"); syslog_server(LOG_ERR, - "Request: Set calibrated train speed - train: %s speed: %d - bad speed", - grabbed_trains[grab_id].name->str, speed); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; - } else if (data_track_output == NULL) { + "Request: Set calibrated train speed - invalid grab-id (%s)", + data_grab_id); + return OCS_PROCESSED; + } else if (speed == 999) { + send_common_feedback(res, HTTP_BAD_REQUEST, "bad speed"); syslog_server(LOG_ERR, - "Request: Set calibrated train speed - train: %s speed: %d - " - "invalid track output", - grabbed_trains[grab_id].name->str, speed); + "Request: Set calibrated train speed - train: %s speed: %d - bad speed (%s)", + grabbed_trains[grab_id].name->str, speed, data_speed); pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; + return OCS_PROCESSED; } syslog_server(LOG_NOTICE, @@ -1338,85 +1314,82 @@ onion_connection_status handler_set_calibrated_train_speed(void *_, grabbed_trains[grab_id].name->str, speed); if (bidib_set_calibrated_train_speed(grabbed_trains[grab_id].name->str, speed, data_track_output)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid parameter values"); syslog_server(LOG_ERR, "Request: Set calibrated train speed - train: %s speed: %d - " - "invalid parameters - abort", + "invalid parameter values - abort", grabbed_trains[grab_id].name->str, speed); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; } else { bidib_flush(); + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, "Request: Set calibrated train speed - train: %s speed: %d - finish", grabbed_trains[grab_id].name->str, speed); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_PROCESSED; } - + pthread_mutex_unlock(&grabbed_trains_mutex); + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Set calibrated train speed - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set calibrated train speed"); } } -onion_connection_status handler_set_train_emergency_stop(void *_, - onion_request *req, - onion_response *res) { +o_con_status handler_set_train_emergency_stop(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_session_id = onion_request_get_post(req, "session-id"); const char *data_grab_id = onion_request_get_post(req, "grab-id"); const char *data_track_output = onion_request_get_post(req, "track-output"); - int client_session_id = params_check_session_id(data_session_id); - int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); + const int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); + const int client_session_id = params_check_session_id(data_session_id); - if (client_session_id != session_id) { - syslog_server(LOG_ERR, "Request: Set train emergency stop - invalid session id"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Set train emergency stop", "session-id", data_session_id) + || handle_param_miss_check(res, "Set train emergency stop", "grab-id", data_grab_id) + || handle_param_miss_check(res, "Set train emergency stop", "track-output", + data_track_output)) { + return OCS_PROCESSED; + } else if (client_session_id != session_id) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid session-id"); + syslog_server(LOG_ERR, + "Request: Set train emergency stop - invalid session-id (%s)", + data_session_id); + return OCS_PROCESSED; } pthread_mutex_lock(&grabbed_trains_mutex); if (grab_id == -1 || !grabbed_trains[grab_id].is_valid) { pthread_mutex_unlock(&grabbed_trains_mutex); - syslog_server(LOG_ERR, "Request: Set train emergency stop - invalid grab id"); - return OCS_NOT_IMPLEMENTED; - } else if (data_track_output == NULL) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid grab-id"); syslog_server(LOG_ERR, - "Request: Set train emergency stop - train: %s - invalid track output", - grabbed_trains[grab_id].name->str); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; + "Request: Set train emergency stop - invalid grab-id (%s)", + data_grab_id); + return OCS_PROCESSED; } + syslog_server(LOG_NOTICE, "Request: Set train emergency stop - train: %s - start", grabbed_trains[grab_id].name->str); if (bidib_emergency_stop_train(grabbed_trains[grab_id].name->str, data_track_output)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid parameter values"); syslog_server(LOG_ERR, - "Request: Set train emergency stop - train: %s - invalid parameters - abort", + "Request: Set train emergency stop - train: %s - " + "invalid parameter values - abort", grabbed_trains[grab_id].name->str); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; } else { bidib_flush(); + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, "Request: Set train emergency stop - train: %s - finish", grabbed_trains[grab_id].name->str); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_PROCESSED; } - + pthread_mutex_unlock(&grabbed_trains_mutex); + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Set train emergency stop - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set train emergency stop"); } } -onion_connection_status handler_set_train_peripheral(void *_, - onion_request *req, - onion_response *res) { +o_con_status handler_set_train_peripheral(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_session_id = onion_request_get_post(req, "session-id"); @@ -1424,67 +1397,64 @@ onion_connection_status handler_set_train_peripheral(void *_, const char *data_peripheral = onion_request_get_post(req, "peripheral"); const char *data_state = onion_request_get_post(req, "state"); const char *data_track_output = onion_request_get_post(req, "track-output"); - int client_session_id = params_check_session_id(data_session_id); - int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); - int state = params_check_state(data_state); + const int client_session_id = params_check_session_id(data_session_id); + const int grab_id = params_check_grab_id(data_grab_id, TRAIN_ENGINE_INSTANCE_COUNT_MAX); + const int state = params_check_state(data_state); - if (client_session_id != session_id) { - syslog_server(LOG_ERR, "Request: Set train peripheral - invalid session id"); - return OCS_NOT_IMPLEMENTED; + if (handle_param_miss_check(res, "Set train peripheral", "session-id", data_session_id) + || handle_param_miss_check(res, "Set train peripheral", "grab-id", data_grab_id) + || handle_param_miss_check(res, "Set train peripheral", "peripheral", data_peripheral) + || handle_param_miss_check(res, "Set train peripheral", "track-output", data_track_output) + || handle_param_miss_check(res, "Set train peripheral", "state", data_state)) { + return OCS_PROCESSED; + } else if (client_session_id != session_id) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid session-id"); + syslog_server(LOG_ERR, + "Request: Set train peripheral - invalid session-id (%s)", + data_session_id); + return OCS_PROCESSED; } pthread_mutex_lock(&grabbed_trains_mutex); if (grab_id == -1 || !grabbed_trains[grab_id].is_valid) { pthread_mutex_unlock(&grabbed_trains_mutex); - syslog_server(LOG_ERR, "Request: Set train peripheral - invalid grab id"); - return OCS_NOT_IMPLEMENTED; - } else if (state == -1) { - syslog_server(LOG_ERR, - "Request: Set train peripheral - train: %s - invalid state", - grabbed_trains[grab_id].name->str); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; - } else if (data_peripheral == NULL) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid grab-id"); syslog_server(LOG_ERR, - "Request: Set train peripheral - train: %s - invalid peripheral", - grabbed_trains[grab_id].name->str); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; - } else if (data_track_output == NULL) { + "Request: Set train peripheral - invalid grab-id (%s)", + data_grab_id); + return OCS_PROCESSED; + } else if (state == -1) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid state"); syslog_server(LOG_ERR, - "Request: Set train peripheral - train: %s peripheral: %s - " - "invalid track output", - grabbed_trains[grab_id].name->str, data_peripheral); + "Request: Set train peripheral - train: %s - invalid state (%s)", + grabbed_trains[grab_id].name->str, data_state); pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; + return OCS_PROCESSED; } syslog_server(LOG_NOTICE, - "Request: Set train peripheral - train: %s peripheral: %s state: 0x%02x - start", + "Request: Set train peripheral - train: %s peripheral: %s state: %d - start", grabbed_trains[grab_id].name->str, data_peripheral, state); if (bidib_set_train_peripheral(grabbed_trains[grab_id].name->str, data_peripheral, state, data_track_output)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "invalid parameter values"); syslog_server(LOG_ERR, "Request: Set train peripheral - train: %s " - "peripheral: %s state: 0x%02x - invalid parameters - abort", + "peripheral: %s state: %d - invalid parameter values - abort", grabbed_trains[grab_id].name->str, data_peripheral, state); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_NOT_IMPLEMENTED; } else { bidib_flush(); + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, - "Request: Set train peripheral - train: %s peripheral: %s state: 0x%02x" + "Request: Set train peripheral - train: %s peripheral: %s state: %d" " - finish", grabbed_trains[grab_id].name->str, data_peripheral, state); - pthread_mutex_unlock(&grabbed_trains_mutex); - return OCS_PROCESSED; } - + pthread_mutex_unlock(&grabbed_trains_mutex); + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Set train peripheral - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Set train peripheral"); } } diff --git a/server/src/handler_driver.h b/server/src/handler_driver.h index 0ca471b3..43ff855e 100644 --- a/server/src/handler_driver.h +++ b/server/src/handler_driver.h @@ -37,6 +37,8 @@ #define MICROSECOND 1 #define TRAIN_DRIVE_TIME_STEP 10000 * MICROSECOND // 0.01 seconds +typedef onion_connection_status o_con_status; + extern pthread_mutex_t grabbed_trains_mutex; typedef struct { @@ -49,7 +51,7 @@ typedef struct { extern t_train_data grabbed_trains[TRAIN_ENGINE_INSTANCE_COUNT_MAX]; -const int train_get_grab_id(const char *train); +int train_get_grab_id(const char *train); bool train_grabbed(const char *train); @@ -65,36 +67,25 @@ void release_all_grabbed_trains(void); */ char *train_id_from_grab_id(int grab_id); -onion_connection_status handler_grab_train(void *_, onion_request *req, - onion_response *res); +o_con_status handler_grab_train(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_release_train(void *_, onion_request *req, - onion_response *res); +o_con_status handler_release_train(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_request_route(void *_, onion_request *req, - onion_response *res); +o_con_status handler_request_route(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_request_route_id(void *_, onion_request *req, - onion_response *res); +o_con_status handler_request_route_by_id(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_driving_direction(void *_, onion_request *req, - onion_response *res); +o_con_status handler_driving_direction(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_drive_route(void *_, onion_request *req, - onion_response *res); +o_con_status handler_drive_route(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_dcc_train_speed(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_dcc_train_speed(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_calibrated_train_speed(void *_, - onion_request *req, - onion_response *res); +o_con_status handler_set_calibrated_train_speed(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_train_peripheral(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_train_peripheral(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_set_train_emergency_stop(void *_, onion_request *req, - onion_response *res); +o_con_status handler_set_train_emergency_stop(void *_, onion_request *req, onion_response *res); #endif // HANDLER_DRIVER_H \ No newline at end of file diff --git a/server/src/handler_monitor.c b/server/src/handler_monitor.c index 33b4b817..25aa8f2d 100644 --- a/server/src/handler_monitor.c +++ b/server/src/handler_monitor.c @@ -40,609 +40,1303 @@ #include "interlocking.h" #include "bahn_data_util.h" #include "websocket_uploader/engine_uploader.h" +#include "json_response_builder.h" +#include "communication_utils.h" -onion_connection_status handler_get_platform_name(void *_, onion_request *req, onion_response *res) { +///NOTE: Handlers/endpoints that do NOT require parameters/args passed from clients +// now use HTTP method GET. All other stick with POST. Need to adjust clients accordingly. + +static GArray* garray_points_to_garray_str_ids(GArray *points) { + if (points == NULL) { + return NULL; + } + GArray *g_point_ids = g_array_new(FALSE, FALSE, sizeof(char *)); + + for (int i = 0; i < points->len; ++i) { + t_interlocking_point *point = &g_array_index(points, t_interlocking_point, i); + if (point != NULL) { + char *point_id_dcopy = strdup(point->id); + if (point_id_dcopy != NULL) { + g_array_append_val(g_point_ids, point_id_dcopy); + } + } + } + return g_point_ids; +} + +static void free_g_strarray_and_contents(GArray *g_strarray) { + if (g_strarray == NULL) { + return; + } + for (int i = 0; i < g_strarray->len; ++i) { + if (g_array_index(g_strarray, char *, i) != NULL) { + free(g_array_index(g_strarray, char *, i)); + } + } + g_array_free(g_strarray, true); + g_strarray = NULL; +} + +o_con_status handler_get_platform_name(void *_, onion_request *req, onion_response *res) { build_response_header(res); // "platform name" is a synonym for "module-name" (def. in extras-config.yml). // module name is only loaded when parsing config, which is done at startup. - // -> when system is not running, name is not available yet, thus return error. + // -> when system is not running, name is not available yet. if (running && (onion_request_get_flags(req) & OR_METHODS) == OR_GET) { const char *platform_module_name = config_get_module_name(); - onion_response_printf(res, "%s", platform_module_name); + onion_response_printf(res, "{\"platform-name\": \"%s\"}", platform_module_name); syslog_server(LOG_INFO, "Request: Get platform name (%s) - done", platform_module_name); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Get platform name - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, ""); + } +} + +/** + * @brief Get information on trains. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_trains.json + * + * @return GString* containing info on trains in json format. + * Returns NULL on failure to allocate the string. + */ +static GString *get_trains_json() { + t_bidib_id_list_query query = bidib_get_trains(); + GString *g_trains = g_string_sized_new(60 * (query.length + 1)); + if (g_trains == NULL) { + bidib_free_id_list_query(query); + syslog_server(LOG_ERR, "Get trains json - can't allocate g_trains"); + return NULL; + } + g_string_assign(g_trains, ""); + + append_start_of_obj(g_trains, false); + append_field_start_of_list(g_trains, "trains"); + + for (size_t i = 0; i < query.length; i++) { + append_start_of_obj(g_trains, true); + + append_field_str_value(g_trains, "id", query.ids[i], true); + append_field_bool_value(g_trains, "grabbed", train_grabbed(query.ids[i]), true); + + t_bidib_train_position_query train_position_query = bidib_get_train_position(query.ids[i]); + append_field_bool_value(g_trains, "on_track", train_position_query.length > 0, false); + bidib_free_train_position_query(train_position_query); + + append_end_of_obj(g_trains, i+1 < query.length); } + append_end_of_list(g_trains, false, query.length > 0); + append_end_of_obj(g_trains, false); + bidib_free_id_list_query(query); + return g_trains; } -onion_connection_status handler_get_trains(void *_, onion_request *req, onion_response *res) { +o_con_status handler_get_trains(void *_, onion_request *req, onion_response *res) { build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - GString *trains = g_string_new(""); - t_bidib_id_list_query query = bidib_get_trains(); - for (size_t i = 0; i < query.length; i++) { - g_string_append_printf(trains, "%s%s - grabbed: %s", - i != 0 ? "\n" : "", query.ids[i], - train_grabbed(query.ids[i]) ? "yes" : "no"); + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + GString *g_trains = get_trains_json(); + if (g_trains != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_trains); + syslog_server(LOG_INFO, "Request: Get trains - done"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: Get trains - unable to build reply message"); } - bidib_free_id_list_query(query); - onion_response_printf(res, "%s", trains->str); - syslog_server(LOG_INFO, "Request: Get available trains - done"); - g_string_free(trains, true); + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Get available trains - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, ""); } } -onion_connection_status handler_get_train_state(void *_, onion_request *req, onion_response *res) { +/** + * @brief Get information on the state of a train, given a train state query and the trains ID. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_train-state.json + * + * @param train_id string with id of the train + * @param tr_state_query train state query (from libbidib) for the train with the id specified + * in train_id. "tr_state_query.known" shall be true, otherwise the behaviour is undefined. + * @return GString* containing info on train state in json format. + * Returns NULL on failure to allocate the string. + */ +static GString *get_train_state_json_given_statequery(const char *train_id, + t_bidib_train_state_query tr_state_query) { + const char *f_logname = "Get train state json given statequery"; + GString *g_train_state = g_string_sized_new(256); + if (g_train_state == NULL) { + syslog_server(LOG_ERR, "%s - can't allocate g_train_state", f_logname); + return NULL; + } + g_string_assign(g_train_state, ""); + + append_start_of_obj(g_train_state, false); + append_field_str_value(g_train_state, "id", train_id, true); + append_field_bool_value(g_train_state, "grabbed", train_grabbed(train_id), true); + const char *orientation_str = + (tr_state_query.data.orientation == BIDIB_TRAIN_ORIENTATION_LEFT) ? "left" : "right"; + append_field_str_value(g_train_state, "orientation", orientation_str, true); + const char *direction_str = tr_state_query.data.set_is_forwards ? "forwards" : "backwards"; + append_field_str_value(g_train_state, "direction", direction_str, true); + + append_field_int_value(g_train_state, "speed_step", tr_state_query.data.set_speed_step, true); + append_field_int_value(g_train_state, "detected_kmh_speed", tr_state_query.data.detected_kmh_speed, true); + + pthread_mutex_lock(&interlocker_mutex); + const char *route_id = interlocking_table_get_route_id_of_train(train_id); + pthread_mutex_unlock(&interlocker_mutex); + append_field_str_value(g_train_state, "route_id", route_id == NULL ? "" : route_id, true); + + t_bidib_train_position_query train_position_query = bidib_get_train_position(train_id); + bool is_on_track = train_position_query.length > 0; + // is_on_track intentionally used for both value and add_trailing_comma parameters. + append_field_bool_value(g_train_state, "on_track", is_on_track, is_on_track); + if (is_on_track) { + append_field_strlist_value(g_train_state, "occupied_segments", + (const char **) train_position_query.segments, + train_position_query.length, true); + + append_field_start_of_list(g_train_state, "occupied_blocks"); + int added_blocks = 0; + for (size_t i = 0; i < train_position_query.length; i++) { + const char *block_id = config_get_block_id_of_segment(train_position_query.segments[i]); + GString *search_str = g_string_new(""); + g_string_append_printf(search_str, "\"%s\"", block_id); + // only add block if it does not already exist in the list (and thus in g_train_state) + // Naively, if e.g., "block12" was added, then block1 will be found. + // -> fix is to include the '"'s in the search. + if (block_id != NULL && strlen(block_id) > 0 && strstr(g_train_state->str, search_str->str) == NULL) { + g_string_append_printf(g_train_state, "%s\"%s\"", added_blocks > 0 ? ", " : "", block_id); + ++added_blocks; + } + g_string_free(search_str, true); + } + append_end_of_list(g_train_state, false, false); + } + + bidib_free_train_position_query(train_position_query); + append_end_of_obj(g_train_state, false); + return g_train_state; +} + +o_con_status handler_get_train_state(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_train = onion_request_get_post(req, "train"); - if (data_train == NULL) { - syslog_server(LOG_ERR, "Request: Get train state - invalid parameters"); - return OCS_NOT_IMPLEMENTED; - } - t_bidib_train_state_query train_state_query = bidib_get_train_state(data_train); - t_bidib_train_position_query train_position_query = bidib_get_train_position(data_train); + if (handle_param_miss_check(res, "Get train state", "train", data_train)) { + return OCS_PROCESSED; + } - if (train_state_query.known) { - GString *seg_string = g_string_new("no"); - GString *block_string = g_string_new("no"); - if (train_position_query.length > 0) { - g_string_printf(seg_string, "%s", train_position_query.segments[0]); - for (size_t i = 1; i < train_position_query.length; i++) { - g_string_append_printf(seg_string, ", %s", train_position_query.segments[i]); - } - for (size_t i = 0; i < train_position_query.length; i++) { - const char *block_id = - config_get_block_id_of_segment(train_position_query.segments[i]); - if (block_id != NULL) { - g_string_printf(block_string, "%s", block_id); - break; - } - } - } - bidib_free_train_position_query(train_position_query); - - GString *ret_string = g_string_new(""); - g_string_append_printf(ret_string, "grabbed: %s - on segment: %s - on block: %s" - " - orientation: %s" - " - speed step: %d - detected speed: %d km/h - direction: %s", - train_grabbed(data_train) ? "yes" : "no", - seg_string->str, - block_string->str, - (train_state_query.data.orientation == - BIDIB_TRAIN_ORIENTATION_LEFT) ? - "left" : "right", - train_state_query.data.set_speed_step, - train_state_query.data.detected_kmh_speed, - train_state_query.data.set_is_forwards - ? "forwards" : "backwards"); + t_bidib_train_state_query train_state_query = bidib_get_train_state(data_train); + if (!train_state_query.known) { bidib_free_train_state_query(train_state_query); - onion_response_printf(res, "%s", ret_string->str); - syslog_server(LOG_INFO, "Request: Get train state - train: %s - done", data_train); - g_string_free(seg_string, true); - g_string_free(ret_string, true); + onion_response_set_code(res, HTTP_NOT_FOUND); + syslog_server(LOG_WARNING, + "Request: Get train state - train: %s - unknown train/train state", + data_train); return OCS_PROCESSED; + } + + GString *ret_string = get_train_state_json_given_statequery(data_train, train_state_query); + bidib_free_train_state_query(train_state_query); + if (ret_string != NULL) { + send_some_gstring_and_free(res, HTTP_OK, ret_string); + syslog_server(LOG_INFO, "Request: Get train state - train: %s - done", data_train); } else { - bidib_free_train_position_query(train_position_query); - bidib_free_train_state_query(train_state_query); + onion_response_set_code(res, HTTP_INTERNAL_ERROR); syslog_server(LOG_ERR, - "Request: Get train state - train: %s - invalid train", + "Request: Get train state - train: %s - unable to build reply message", data_train); - return OCS_NOT_IMPLEMENTED; } + return OCS_PROCESSED; + } else { + return handle_req_run_or_method_fail(res, running, "Get train state"); + } +} + +/** + * @brief Get information on the state of all known trains. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_train-states.json + * + * @return GString* containing info on train states in json format. + * Returns NULL on failure to allocate the string. + */ +static GString *get_train_states_json() { + t_bidib_id_list_query query = bidib_get_trains(); + GString *g_train_states = g_string_sized_new(256 * (query.length + 1)); + if (g_train_states == NULL) { + bidib_free_id_list_query(query); + syslog_server(LOG_ERR, "Get train states json - can't allocate g_train_states"); + return NULL; + } + g_string_assign(g_train_states, ""); + + append_start_of_obj(g_train_states, false); + append_field_start_of_list(g_train_states, "train-states"); + int added_trains = 0; + for (size_t i = 0; i < query.length; i++) { + t_bidib_train_state_query tr_state_q = bidib_get_train_state(query.ids[i]); + if (tr_state_q.known) { + GString *g_train_state = get_train_state_json_given_statequery(query.ids[i], tr_state_q); + if (g_train_state != NULL) { + if (added_trains > 0) { + g_string_append_printf(g_train_states, "%s", ",\n"); + } + added_trains++; + g_string_append_printf(g_train_states, "%s", g_train_state->str); + g_string_free(g_train_state, true); + } + } + bidib_free_train_state_query(tr_state_q); + } + append_end_of_list(g_train_states, false, query.length > 0); + append_end_of_obj(g_train_states, false); + + bidib_free_id_list_query(query); + return g_train_states; +} + +o_con_status handler_get_train_states(void *_, onion_request *req, onion_response *res) { + build_response_header(res); + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + GString *g_train_states = get_train_states_json(); + if (g_train_states != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_train_states); + syslog_server(LOG_INFO, "Request: Get train states - done"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: Get train states - unable to build reply message"); + } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get train state - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get train states"); } } -onion_connection_status handler_get_train_peripherals(void *_, onion_request *req, - onion_response *res) { +/** + * @brief Get information on a train's peripherals. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_train-peripherals.json + * + * @param train_id id of the train whose peripherals to get information about. + * @return GString* containing info on train's peripherals in json format. + * Returns NULL on failure to allocate the string, and/or if train_id is NULL. + */ +static GString *get_train_peripherals_json(const char *train_id) { + if (train_id == NULL) { + return NULL; + } + + t_bidib_id_list_query query = bidib_get_train_peripherals(train_id); + GString *g_train_peripherals = g_string_sized_new(24 + 42 * query.length); + if (g_train_peripherals == NULL) { + bidib_free_id_list_query(query); + syslog_server(LOG_ERR, "Get train peripherals json - can't allocate g_train_peripherals"); + return NULL; + } + g_string_assign(g_train_peripherals, ""); + + append_start_of_obj(g_train_peripherals, false); + append_field_start_of_list(g_train_peripherals, "train-peripherals"); + + for (size_t i = 0; i < query.length; i++) { + t_bidib_train_peripheral_state_query train_peripheral_state = + bidib_get_train_peripheral_state(train_id, query.ids[i]); + char *state_string; + if (train_peripheral_state.available) { + state_string = train_peripheral_state.state == 1 ? "on" : "off"; + } else { + state_string = "unknown"; + } + + append_start_of_obj(g_train_peripherals, true); + append_field_str_value(g_train_peripherals, "id", query.ids[i], true); + append_field_str_value(g_train_peripherals, "state", state_string, false); + append_end_of_obj(g_train_peripherals, i+1 < query.length); + } + + append_end_of_list(g_train_peripherals, false, query.length > 0); + append_end_of_obj(g_train_peripherals, false); + + bidib_free_id_list_query(query); + return g_train_peripherals; +} + +o_con_status handler_get_train_peripherals(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_train = onion_request_get_post(req, "train"); - if (data_train == NULL) { - syslog_server(LOG_ERR, "Request: Get train peripherals - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Get train peripherals", "train", data_train)) { + return OCS_PROCESSED; + } else if (!train_known(data_train)) { + onion_response_set_code(res, HTTP_NOT_FOUND); + syslog_server(LOG_WARNING, + "Request: Get train peripherals - train: %s - unknown train", + data_train); + return OCS_PROCESSED; } - t_bidib_id_list_query query = bidib_get_train_peripherals(data_train); - if (query.length > 0) { - GString *train_peripherals = g_string_new(""); - for (size_t i = 0; i < query.length; i++) { - t_bidib_train_peripheral_state_query per_state = - bidib_get_train_peripheral_state(data_train, query.ids[i]); - g_string_append_printf(train_peripherals, "%s%s - state: %s", - i != 0 ? "\n" : "", query.ids[i], - per_state.state == 1 ? "on" : "off"); - } - bidib_free_id_list_query(query); - - onion_response_printf(res, "%s", train_peripherals->str); + GString *g_train_peripherals = get_train_peripherals_json(data_train); + if (g_train_peripherals != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_train_peripherals); syslog_server(LOG_INFO, "Request: Get train peripherals - train: %s - done", data_train); - g_string_free(train_peripherals, true); - return OCS_PROCESSED; } else { - bidib_free_id_list_query(query); + onion_response_set_code(res, HTTP_INTERNAL_ERROR); syslog_server(LOG_ERR, - "Request: Get train train peripherals - train: %s - invalid train", + "Request: Get train peripherals - train: %s - unable to build reply message", data_train); - return OCS_NOT_IMPLEMENTED; } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Get train peripherals - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get train peripherals"); + } +} + +/** + * @brief Get information on available engines or available interlockers. + * If the value of the parameter "engines" is true, it will contain info on engines, + * otherwise info on interlockers. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_engines.json (if engines is true), otherwise + * /server/doc/api-formats/monitor/json-schema-monitor_interlockers.json + * + * + * @param engines pass true if information about available engines is desired, if information on + * available interlockers is desired then pass false. + * @return GString* containing info on available engines or interlockers in json format. + * Returns NULL on failure to allocate the string. + */ +static GString *get_engines_or_interlockers_json(bool engines) { + GString *g_json_ret = g_string_new(""); + if (g_json_ret == NULL) { + syslog_server(LOG_ERR, "Get engines or interlockers json - can't allocate g_engines"); + return NULL; + } + append_start_of_obj(g_json_ret, false); + GArray *names_list = NULL; + if (engines) { + names_list = dyn_containers_get_train_engines_arr(); + } else { + names_list = dyn_containers_get_interlockers_arr(); + } + if (names_list == NULL) { + syslog_server(LOG_ERR, "Get engines or interlockers json - list from dyncontainers is NULL"); + g_string_free(g_json_ret, true); + return NULL; } + const char *fieldname = engines ? "engines" : "interlockers"; + append_field_strlist_value_from_garray_strs(g_json_ret, fieldname, names_list, false); + append_end_of_obj(g_json_ret, false); + free_g_strarray_and_contents(names_list); + return g_json_ret; } -onion_connection_status handler_get_track_outputs(void *_, onion_request *req, - onion_response *res) { +static o_con_status get_engines_interlockers_common(onion_request *req, onion_response *res, + bool engines) { build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - GString *track_outputs = g_string_new(""); - t_bidib_id_list_query query = bidib_get_track_outputs(); - for (size_t i = 0; i < query.length; i++) { - t_bidib_track_output_state_query track_output_state = - bidib_get_track_output_state(query.ids[i]); - if (track_output_state.known) { - char *state_string; - switch (track_output_state.cs_state) { - case 0x00: - state_string = "off"; - break; - case 0x01: - state_string = "stop"; - break; - case 0x02: - state_string = "soft stop"; - break; - case 0x03: - state_string = "go"; - break; - case 0x04: - state_string = "go + ignore watchdog"; - break; - case 0x08: - state_string = "prog"; - break; - case 0x09: - state_string = "prog busy"; - break; - case 0x0D: - state_string = "busy"; - break; - case 0xFF: - state_string = "query"; - break; - default: - state_string = "off"; - break; - } - g_string_append_printf(track_outputs, "%s%s - state: %s", - i != 0 ? "\n" : "", query.ids[i], - state_string); - } + const char *l_name = engines ? "Get engines" : "Get interlockers"; + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + GString *g_ret = get_engines_or_interlockers_json(engines); + if (g_ret != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_ret); + syslog_server(LOG_INFO, "Request: %s - done", l_name); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: %s - unable to build reply message", l_name); } - bidib_free_id_list_query(query); - - onion_response_printf(res, "%s", track_outputs->str); - syslog_server(LOG_INFO, "Request: Get track outputs - done"); - g_string_free(track_outputs, true); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Get track outputs - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, l_name); } } -onion_connection_status handler_get_points(void *_, onion_request *req, onion_response *res) { - build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - GString *points = g_string_new(""); - t_bidib_id_list_query query = bidib_get_connected_points(); - for (size_t i = 0; i < query.length; i++) { - t_bidib_unified_accessory_state_query point_state = bidib_get_point_state(query.ids[i]); - - GString *execution_state = g_string_new(""); - if (point_state.type == BIDIB_ACCESSORY_BOARD) { - g_string_printf(execution_state, "(target state%s reached)", - point_state.board_accessory_state.execution_state ? " not" : ""); +o_con_status handler_get_engines(void *_, onion_request *req, onion_response *res) { + return get_engines_interlockers_common(req, res, true); +} + +o_con_status handler_get_interlockers(void *_, onion_request *req, onion_response *res) { + return get_engines_interlockers_common(req, res, false); +} + +/** + * @brief Get information on known track outputs. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_track-outputs.json + * + * @return GString* containing info on track outputs in json format. + * Returns NULL on failure to allocate the string. + */ +static GString *get_track_outputs_json() { + t_bidib_id_list_query query = bidib_get_track_outputs(); + GString *g_track_outputs = g_string_sized_new(24 + 48 * query.length); + if (g_track_outputs == NULL) { + bidib_free_id_list_query(query); + syslog_server(LOG_ERR, "Get track outputs json - can't allocate g_track_outputs"); + return NULL; + } + + g_string_assign(g_track_outputs, ""); + append_start_of_obj(g_track_outputs, false); + append_field_start_of_list(g_track_outputs, "track-outputs"); + + int track_outputs_added = 0; + for (size_t i = 0; i < query.length; i++) { + t_bidib_track_output_state_query track_output_state_query = + bidib_get_track_output_state(query.ids[i]); + if (track_output_state_query.known) { + char *state_string; + switch (track_output_state_query.cs_state) { + case 0x00: state_string = "off"; break; + case 0x01: state_string = "stop"; break; + case 0x02: state_string = "soft stop"; break; + case 0x03: state_string = "go"; break; + case 0x04: state_string = "go + ignore watchdog"; break; + case 0x08: state_string = "prog"; break; + case 0x09: state_string = "prog busy"; break; + case 0x0D: state_string = "busy"; break; + case 0xFF: state_string = "query"; break; + default: state_string = "off"; break; } - - g_string_append_printf(points, "%s%s - state: %s %s", - i != 0 ? "\n" : "", query.ids[i], - point_state.type == BIDIB_ACCESSORY_BOARD ? - point_state.board_accessory_state.state_id : - point_state.dcc_accessory_state.state_id, - execution_state->str); - g_string_free(execution_state, true); - bidib_free_unified_accessory_state_query(point_state); + if (track_outputs_added > 0) { + g_string_append_c(g_track_outputs, ','); + } + track_outputs_added++; + append_start_of_obj(g_track_outputs, true); + append_field_str_value(g_track_outputs, "id", query.ids[i], true); + append_field_str_value(g_track_outputs, "state", state_string, false); + append_end_of_obj(g_track_outputs, false); } - bidib_free_id_list_query(query); - - onion_response_printf(res, "%s", points->str); - syslog_server(LOG_INFO, "Request: Get points - done"); - g_string_free(points, true); - return OCS_PROCESSED; - } else { - syslog_server(LOG_ERR, "Request: Get points - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; } + append_end_of_list(g_track_outputs, false, track_outputs_added > 0); + append_end_of_obj(g_track_outputs, false); + bidib_free_id_list_query(query); + return g_track_outputs; } -onion_connection_status handler_get_signals(void *_, onion_request *req, onion_response *res) { +o_con_status handler_get_track_outputs(void *_, onion_request *req, onion_response *res) { build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - GString *signals = g_string_new(""); - t_bidib_id_list_query query = bidib_get_connected_signals(); - for (size_t i = 0; i < query.length; i++) { - t_bidib_unified_accessory_state_query signal_state = - bidib_get_signal_state(query.ids[i]); - g_string_append_printf(signals, "%s%s - state: %s", - i != 0 ? "\n" : "", query.ids[i], - signal_state.type == BIDIB_ACCESSORY_BOARD ? - signal_state.board_accessory_state.state_id : - signal_state.dcc_accessory_state.state_id); - bidib_free_unified_accessory_state_query(signal_state); + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + GString *g_track_outputs = get_track_outputs_json(); + if (g_track_outputs != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_track_outputs); + syslog_server(LOG_INFO, "Request: Get track outputs - done"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: Get track outputs - unable to build reply message"); } + return OCS_PROCESSED; + } else { + return handle_req_run_or_method_fail(res, running, "Get track outputs"); + } +} + +/** + * @brief Get a string containing a json list field of aspects that the accessory + * identified by acc_id supports. Accessory must be a signal or a point (indicate which one it + * is through parameter is_point). + * Example returned string: `"aspects": ["normal", "reverse"]` + * + * @param acc_id id of the accessory whose aspects to get. + * @param is_point pass true if the accessory in question is a point, pass false if it is a signal. + * @return GString* containing a json field called "aspects" with a list of aspects the specified + * accessory supports. Returns NULL on failure to allocate the string, and/or on failure to query + * the accessories aspects. + */ +static GString *get_accessory_aspects_json_listonly(const char *acc_id, bool is_point) { + if (acc_id == NULL) { + syslog_server(LOG_ERR, "Get accessory aspects json listonly - invalid (NULL) acc_id"); + return NULL; + } + t_bidib_id_list_query query; + if (is_point) { + query = bidib_get_point_aspects(acc_id); + } else { + query = bidib_get_signal_aspects(acc_id); + } + if (query.ids == NULL) { + syslog_server(LOG_ERR, + "Get accessory aspects json listonly - accessory: %s - " + "bidib query id list is NULL", + acc_id); + return NULL; + } + // size 16 general plus 16 per aspect (heuristic/estimate) + GString *g_aspects_list = g_string_sized_new(16 + 16 * query.length); + if (g_aspects_list == NULL) { bidib_free_id_list_query(query); + syslog_server(LOG_ERR, + "Get accessory aspects json listonly - accessory: %s - " + "can't allocate g_aspects_list", + acc_id); + return NULL; + } + + g_string_assign(g_aspects_list, ""); + append_field_start_of_list(g_aspects_list, "aspects"); + + for (size_t i = 0; i < query.length; i++) { + g_string_append_printf(g_aspects_list, "%s\"%s\"", i != 0 ? ", " : "", query.ids[i]); + } + append_end_of_list(g_aspects_list, false, false); + bidib_free_id_list_query(query); + return g_aspects_list; +} + +/** + * @brief Get information on either all point accessories or signal accessories (choose by value + * passed in parameter point_accessories). + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_points.json if point_accessories is true, else: + * /server/doc/api-formats/monitor/json-schema-monitor_signals.json + * + * @param point_accessories pass true if info on points is desired, pass false if info on signals + * is desired. + * @return GString* containing info on either points or signals in json format. + * Returns NULL on failure to allocate the string, and/or on failure to query + * the accessories. + */ +static GString *get_accessories_json(bool point_accessories) { + t_bidib_id_list_query query; + if (point_accessories) { + query = bidib_get_connected_points(); + } else { + query = bidib_get_connected_signals(); + } + if (query.ids == NULL) { + syslog_server(LOG_ERR, "Get accessories json - bidib returned NULL id list"); + return NULL; + } + + GString *g_accs = g_string_sized_new(24 + 72 * query.length); + if (g_accs == NULL) { + bidib_free_id_list_query(query); + syslog_server(LOG_ERR, "Get accessories json - can't allocate g_accs"); + return NULL; + } + g_string_assign(g_accs, ""); + append_start_of_obj(g_accs, false); + append_field_start_of_list(g_accs, point_accessories ? "points" : "signals"); + + for (size_t i = 0; i < query.length; i++) { + t_bidib_unified_accessory_state_query acc_state = + point_accessories ? bidib_get_point_state(query.ids[i]) + : bidib_get_signal_state(query.ids[i]); + + append_start_of_obj(g_accs, true); + append_field_str_value(g_accs, "id", query.ids[i], true); + // target_state_reached field is only present if its a point, its state is known, and + // its accessory state type is BIDIB_ACCESSORY_BOARD. + bool field_target_reached_present = + point_accessories && acc_state.known && acc_state.type == BIDIB_ACCESSORY_BOARD; + if (acc_state.known) { + append_field_str_value(g_accs, "state", + acc_state.type == BIDIB_ACCESSORY_BOARD ? + acc_state.board_accessory_state.state_id : + acc_state.dcc_accessory_state.state_id, + field_target_reached_present); + } else { + append_field_str_value(g_accs, "state", "unknown", + field_target_reached_present); + } - onion_response_printf(res, "%s", signals->str); - syslog_server(LOG_INFO, "Request: Get signals - done"); - g_string_free(signals, true); + // Decided not to have the signal type in this "get all signals" monitor endpoint. + // But will have it in a "get signal details" kind of monitor endpoint. + if (field_target_reached_present) { + bool target_state_reached_val = + acc_state.board_accessory_state.execution_state == BIDIB_EXEC_STATE_REACHED + || acc_state.board_accessory_state.execution_state == BIDIB_EXEC_STATE_REACHED_VERIFIED; + append_field_bool_value(g_accs, "target_state_reached", + target_state_reached_val, false); + } + append_end_of_obj(g_accs, i+1 < query.length); + bidib_free_unified_accessory_state_query(acc_state); + } + append_end_of_list(g_accs, false, query.length > 0); + append_end_of_obj(g_accs, false); + bidib_free_id_list_query(query); + return g_accs; +} + +static o_con_status get_points_signals_common(onion_request *req, onion_response *res, bool points) { + build_response_header(res); + const char *l_name = points ? "Get points" : "Get signals"; + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + GString *g_ret = get_accessories_json(points); + if (g_ret != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_ret); + syslog_server(LOG_INFO, "Request: %s - done", l_name); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, + "Request: %s - unable to build reply message", + l_name); + } return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get signals - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, l_name); } } +o_con_status handler_get_points(void *_, onion_request *req, onion_response *res) { + return get_points_signals_common(req, res, true); +} + +o_con_status handler_get_signals(void *_, onion_request *req, onion_response *res) { + return get_points_signals_common(req, res, false); +} + +/** + * @brief Get detail information on a point specified by its id in point_id. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_point-details.json + * + * @param point_id id of the point to get detail info on. + * @return GString* containing info on the point in json format. + * Returns NULL on failure to allocate the string, and/or if point_id is NULL. + */ +static GString *get_point_details_json(const char *point_id) { + if (point_id == NULL) { + syslog_server(LOG_WARNING, "Get point details json - invalid (NULL) point_id"); + return NULL; + } + + GString *g_details = g_string_sized_new(156); + if (g_details == NULL) { + syslog_server(LOG_ERR, + "Get point details json - point: %s - can't allocate g_details", + point_id); + return NULL; + } + g_string_assign(g_details, ""); + append_start_of_obj(g_details, false); + // field: id + append_field_str_value(g_details, "id", point_id, true); + + // field: aspects + GString *g_aspects_list = get_accessory_aspects_json_listonly(point_id, true); + if (g_aspects_list == NULL) { + syslog_server(LOG_ERR, + "Get point details json - point: %s - failed to get aspects list", + point_id); + g_string_free(g_details, true); + return NULL; + } + + g_string_append_printf(g_details, "\n%s,\n", g_aspects_list->str); + g_string_free(g_aspects_list, true); + + // field: state + t_bidib_unified_accessory_state_query acc_state = bidib_get_point_state(point_id); + // Accessories with type "BIDIB_ACCESSORY_BOARD" have the field "target_state_reached" + bool has_target_reached_field = acc_state.type == BIDIB_ACCESSORY_BOARD; + if (!acc_state.known) { + // If point state is unknown, no additional field, and "unknown" state + has_target_reached_field = false; + syslog_server(LOG_WARNING, + "Get point details json - point: %s - bidib get point state query is empty" + "/point (state) is not known", point_id); + append_field_str_value(g_details, "state", "unknown", true); + } else { + append_field_str_value(g_details, "state", + acc_state.type == BIDIB_ACCESSORY_BOARD ? + acc_state.board_accessory_state.state_id : + acc_state.dcc_accessory_state.state_id, + true); + } + + // field: segment + const char *point_segment = config_get_scalar_string_value("point", point_id, "segment"); + append_field_str_value(g_details, "segment", point_segment, true); + + append_field_bool_value(g_details, "occupied", + is_segment_occupied(point_segment), + has_target_reached_field); + + // field: target_state_reached + if (has_target_reached_field) { + t_bidib_accessory_execution_state ex_state = acc_state.board_accessory_state.execution_state; + /// NOTE: this means also "BIDIB_EXEC_STATE_ERROR" is regarded as "false". -> but extra log. + bool target_state_reached_val = + ex_state == BIDIB_EXEC_STATE_REACHED || ex_state == BIDIB_EXEC_STATE_REACHED_VERIFIED; + if (ex_state == BIDIB_EXEC_STATE_ERROR) { + syslog_server(LOG_NOTICE, + "Get point details json - point: %s - target_state_reached reported as false, " + "execution state is BIDIB_EXEC_STATE_ERROR", point_id); + } + + append_field_bool_value(g_details, "target_state_reached", target_state_reached_val, false); + + } + append_end_of_obj(g_details, false); + bidib_free_unified_accessory_state_query(acc_state); + return g_details; +} -onion_connection_status handler_get_point_aspects(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_get_point_details(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_point = onion_request_get_post(req, "point"); - if (data_point == NULL) { - syslog_server(LOG_ERR, "Request: Get point aspects - invalid parameters"); - return OCS_NOT_IMPLEMENTED; - } - t_bidib_id_list_query query = bidib_get_point_aspects(data_point); - if (query.length > 0) { - GString *aspects = g_string_new(""); - for (size_t i = 0; i < query.length; i++) { - g_string_append_printf(aspects, "%s%s", i != 0 ? ", " : "", query.ids[i]); - } - bidib_free_id_list_query(query); - - onion_response_printf(res, "%s", aspects->str); - syslog_server(LOG_INFO, "Request: Get point aspects - point: %s - done", data_point); - g_string_free(aspects, true); + if (handle_param_miss_check(res, "Get point details", "point", data_point)) { return OCS_PROCESSED; + } else if (!is_type_point(data_point)) { + onion_response_set_code(res, HTTP_NOT_FOUND); + return OCS_PROCESSED; + } + + GString *g_details = get_point_details_json(data_point); + if (g_details != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_details); + syslog_server(LOG_INFO, "Request: Get point details - point: %s - done", data_point); } else { - bidib_free_id_list_query(query); + onion_response_set_code(res, HTTP_INTERNAL_ERROR); syslog_server(LOG_ERR, - "Request: Get point aspects - point: %s - invalid point", + "Request: Get point details - point: %s - unable to build reply message", data_point); - return OCS_NOT_IMPLEMENTED; } + return OCS_PROCESSED; } else { + return handle_req_run_or_method_fail(res, running, "Get point details"); + } +} + +o_con_status handler_get_signal_details(void *_, onion_request *req, onion_response *res) { + syslog_server(LOG_ERR, "Request: Get signal details - not implemented yet!"); + ///TODO: Implement and add to API documentation + return OCS_NOT_IMPLEMENTED; +} + +/** + * @brief Get information on the aspects an accessory supports, either for a point or a signal + * (choose by value passed for is_point parameter). See also get_accessory_aspects_json_listonly. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_point-aspects.json if is_point is true, else + * /server/doc/api-formats/monitor/json-schema-monitor_signal-aspects.json + * + * @param acc_id id of the accessory whose aspects to get info on + * @param is_point pass true if the accessory is a point, pass false if it is a signal. + * @return GString* containing info on the aspects the accessory supports, in json format. + * Returns NULL on failure to allocate the string, and/or if acc_id is NULL. + */ +static GString *get_accessory_aspects_json(const char *acc_id, bool is_point) { + if (acc_id == NULL) { + syslog_server(LOG_ERR, "Get accessory aspects json - invalid (NULL) acc_id"); + return NULL; + } + GString *g_aspects = get_accessory_aspects_json_listonly(acc_id, is_point); + if (g_aspects == NULL) { syslog_server(LOG_ERR, - "Request: Get point aspects - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + "Get accessory aspects json - accessory: %s - getting aspects json list failed", + acc_id); + return NULL; } + // g_aspects now has the list field, need to enclose it in json obj. + g_string_prepend(g_aspects, "{\n"); + g_string_append(g_aspects, "\n}"); + return g_aspects; } -onion_connection_status handler_get_signal_aspects(void *_, onion_request *req, - onion_response *res) { +static o_con_status get_acc_aspects_common(onion_request *req, onion_response *res, bool point) { build_response_header(res); + const char *l_name = point ? "Get point aspects" : "Get signal aspects"; + ///NOTE: uses POST instead of GET to allow parameter passing via POST dict/data if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - const char *data_signal = onion_request_get_post(req, "signal"); - if (data_signal == NULL) { - syslog_server(LOG_ERR, "Request: Get signal aspects - invalid parameters"); - return OCS_NOT_IMPLEMENTED; - } + const char *acc_type_name = point ? "point" : "signal"; + const char *data_acc = onion_request_get_post(req, acc_type_name); - t_bidib_id_list_query query = bidib_get_signal_aspects(data_signal); - if (query.length > 0) { - GString *aspects = g_string_new(""); - for (size_t i = 0; i < query.length; i++) { - g_string_append_printf(aspects, "%s%s", i != 0 ? ", " : "", query.ids[i]); - } - bidib_free_id_list_query(query); - - onion_response_printf(res, "%s", aspects->str); - syslog_server(LOG_INFO, - "Request: Get signal aspects - signal: %s - done", - data_signal); - g_string_free(aspects, true); + if (handle_param_miss_check(res, l_name, acc_type_name, data_acc)) { + return OCS_PROCESSED; + } else if ((point && !is_type_point(data_acc)) || (!point && !is_type_signal(data_acc))) { + onion_response_set_code(res, HTTP_NOT_FOUND); return OCS_PROCESSED; + } + + GString *g_aspects = get_accessory_aspects_json(data_acc, point); + if (g_aspects != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_aspects); + syslog_server(LOG_INFO, "Request: %s - %s: %s - done", l_name, acc_type_name, data_acc); } else { - bidib_free_id_list_query(query); + ///NOTE: get_accessory_aspects_json also returns NULL if the input data_acc is NULL, + // and then HTTP_BAD_REQUEST would be the appropriate code. But this case is + // checked already in the 'if' before the mentioned function is called, + // and for all other cases where it returns NULL, INTERNAL_ERROR is appropriate. + onion_response_set_code(res, HTTP_INTERNAL_ERROR); syslog_server(LOG_ERR, - "Request: Get signal aspects - signal: %s - invalid signal", - data_signal); - return OCS_NOT_IMPLEMENTED; + "Request: %s - %s: %s - unable to build reply message", + l_name, acc_type_name, data_acc); } + return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Get signal aspects - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, l_name); } } -onion_connection_status handler_get_segments(void *_, onion_request *req, onion_response *res) { - build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - GString *segments = g_string_new(""); - t_bidib_id_list_query seg_query = bidib_get_connected_segments(); - for (size_t i = 0; i < seg_query.length; i++) { - t_bidib_segment_state_query seg_state_query = bidib_get_segment_state(seg_query.ids[i]); - g_string_append_printf(segments, "%s%s - occupied: %s", - i != 0 ? "\n" : "", seg_query.ids[i], - seg_state_query.data.occupied ? "yes" : "no"); - if (seg_state_query.data.dcc_address_cnt > 0) { - g_string_append_printf(segments, " trains: "); - t_bidib_id_query id_query; - for (size_t j = 0; j < seg_state_query.data.dcc_address_cnt; j++) { - id_query = bidib_get_train_id(seg_state_query.data.dcc_addresses[j]); - g_string_append_printf(segments, "%s%s", - j != 0 ? ", " : "", - id_query.known ? id_query.id : "unknown"); - bidib_free_id_query(id_query); - } +o_con_status handler_get_point_aspects(void *_, onion_request *req, onion_response *res) { + return get_acc_aspects_common(req, res, true); +} + +// Probably want to have a signal-details endpoint too, once we +// have more information about signals we can return; at the moment it +// is quite limited. One interesting property may be, for example, +// to know what segment->segment travel means the signal has been driven past. +o_con_status handler_get_signal_aspects(void *_, onion_request *req, onion_response *res) { + return get_acc_aspects_common(req, res, false); +} + +/** + * @brief Get information on all known segments, i.e., the identifier and occupancy information each. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_segments.json + * + * @return GString* containing info on segments in json format. + * Returns NULL on failure to allocate the string. + */ +static GString *get_segments_json() { + t_bidib_id_list_query seg_query = bidib_get_connected_segments(); + // empty query result will lead to reply with empty list, this is intended. + + // Size estimate based on examples. non-occupied segment adds ca. 36 chars, occupied one ca. 50 + GString *g_segments = g_string_sized_new(20 + 44 * seg_query.length); + if (g_segments == NULL) { + bidib_free_id_list_query(seg_query); + syslog_server(LOG_ERR, "Get segments json - can't allocate g_segments"); + return NULL; + } + g_string_assign(g_segments, ""); + append_start_of_obj(g_segments, false); + append_field_start_of_list(g_segments, "segments"); + + int added_segments = 0; + for (size_t i = 0; i < seg_query.length; i++) { + t_bidib_segment_state_query seg_state_query = bidib_get_segment_state(seg_query.ids[i]); + if (added_segments > 0) { + g_string_append_c(g_segments, ','); + } + added_segments++; + const bool segment_is_occupied = seg_state_query.known && seg_state_query.data.occupied; + + append_start_of_obj(g_segments, true); + append_field_str_value(g_segments, "id", seg_query.ids[i], segment_is_occupied); + + if (segment_is_occupied) { + append_field_start_of_list(g_segments, "occupied-by"); + for (size_t j = 0; j < seg_state_query.data.dcc_address_cnt; j++) { + t_bidib_id_query id_query = bidib_get_train_id(seg_state_query.data.dcc_addresses[j]); + g_string_append_printf(g_segments, "%s\"%s\"", + j != 0 ? ", " : "", + id_query.known ? id_query.id : "unknown"); + bidib_free_id_query(id_query); + } + // In case we know the segment is occupied, but no addresses are present, the + // loop above won't add "unknown", so deal with this case separately + if (seg_state_query.data.dcc_address_cnt == 0) { + g_string_append_printf(g_segments, "\"unknown\""); } - bidib_free_segment_state_query(seg_state_query); + append_end_of_list(g_segments, false, false); } - bidib_free_id_list_query(seg_query); + append_end_of_obj(g_segments, false); - onion_response_printf(res, "%s", segments->str); - syslog_server(LOG_INFO, "Request: Get segments - done"); - g_string_free(segments, true); + bidib_free_segment_state_query(seg_state_query); + } + append_end_of_list(g_segments, false, added_segments > 0); + append_end_of_obj(g_segments, false); + bidib_free_id_list_query(seg_query); + return g_segments; +} + +o_con_status handler_get_segments(void *_, onion_request *req, onion_response *res) { + build_response_header(res); + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + GString *g_segments = get_segments_json(); + if (g_segments != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_segments); + syslog_server(LOG_INFO, "Request: Get segments - done"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, + "Request: Get segments - unable to build reply message"); + } return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get segments - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get segments"); } } -onion_connection_status handler_get_reversers(void *_, onion_request *req, onion_response *res) { +/** + * @brief Get information on the known reversers, i.e., the identifier and the reverser state each. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_reversers.json + * + * @return GString* containing info on reversers in json format. + * Returns NULL on failure to allocate the string. + */ +static GString *get_reversers_json() { + t_bidib_id_list_query rev_query = bidib_get_connected_reversers(); + // Size heuristic/guess from examples. Will be auto-resized if too small. + GString *g_reversers = g_string_sized_new(24 + 36 * rev_query.length); + if (g_reversers == NULL) { + syslog_server(LOG_ERR, "Get reversers json - can't allocate g_reversers"); + return NULL; + } + g_string_assign(g_reversers, ""); + + append_start_of_obj(g_reversers, false); + append_field_start_of_list(g_reversers, "reversers"); + + int added_reversers = 0; + for (size_t i = 0; i < rev_query.length; i++) { + const char *reverser_id = rev_query.ids[i]; + t_bidib_reverser_state_query rev_state_query = bidib_get_reverser_state(reverser_id); + if (!rev_state_query.available) { + // "unavailable" reversers are not included in the response. + bidib_free_reverser_state_query(rev_state_query); + continue; + } + + char *state_value_str = "unknown"; + switch (rev_state_query.data.state_value) { + case BIDIB_REV_EXEC_STATE_OFF: + state_value_str = "off"; + break; + case BIDIB_REV_EXEC_STATE_ON: + state_value_str = "on"; + break; + default: + state_value_str = "unknown"; + break; + } + bidib_free_reverser_state_query(rev_state_query); + if (added_reversers > 0) { + g_string_append_c(g_reversers, ','); + } + added_reversers++; + append_start_of_obj(g_reversers, true); + append_field_str_value(g_reversers, "id", reverser_id, true); + append_field_str_value(g_reversers, "state", state_value_str, false); + append_end_of_obj(g_reversers, false); + + } + append_end_of_list(g_reversers, false, added_reversers > 0); + append_end_of_obj(g_reversers, false); + bidib_free_id_list_query(rev_query); + return g_reversers; +} + +o_con_status handler_get_reversers(void *_, onion_request *req, onion_response *res) { build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { if (!reversers_state_update()) { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); syslog_server(LOG_ERR, "Request: Get reversers - unable to request state update"); - return OCS_NOT_IMPLEMENTED; + return OCS_PROCESSED; } - - GString *reversers = g_string_new(""); - t_bidib_id_list_query rev_query = bidib_get_connected_reversers(); - for (size_t i = 0; i < rev_query.length; i++) { - const char *reverser_id = rev_query.ids[i]; - t_bidib_reverser_state_query rev_state_query = bidib_get_reverser_state(reverser_id); - if (!rev_state_query.available) { - continue; - } - - char *state_value_str = "unknown"; - switch (rev_state_query.data.state_value) { - case BIDIB_REV_EXEC_STATE_OFF: - state_value_str = "off"; - break; - case BIDIB_REV_EXEC_STATE_ON: - state_value_str = "on"; - break; - default: - state_value_str = "unknown"; - break; - } - - g_string_append_printf(reversers, "%s%s - state: %s", - i != 0 ? "\n" : "", - reverser_id, state_value_str); - bidib_free_reverser_state_query(rev_state_query); + GString *g_reversers = get_reversers_json(); + if (g_reversers != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_reversers); + syslog_server(LOG_INFO, "Request: Get reversers - done"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: Get reversers - unable to build reply message"); } - bidib_free_id_list_query(rev_query); - - onion_response_printf(res, "%s", reversers->str); - syslog_server(LOG_INFO, "Request: Get reversers - done"); - g_string_free(reversers, true); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get reversers - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get reversers"); } } -onion_connection_status handler_get_peripherals(void *_, onion_request *req, onion_response *res) { - build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - GString *peripherals = g_string_new(""); - t_bidib_id_list_query per_query = bidib_get_connected_peripherals(); - for (size_t i = 0; i < per_query.length; i++) { - t_bidib_peripheral_state_query per_state_query = - bidib_get_peripheral_state(per_query.ids[i]); - g_string_append_printf(peripherals, "%s%s - %s: %d", - i != 0 ? "\n" : "", per_query.ids[i], - per_state_query.data.state_id, - per_state_query.data.state_value); +/** + * @brief Get information on all known peripherals (this does NOT include train peripherals!), + * i.e., the identifier, the peripheral's state ID and its state value. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_peripherals.json + * + * @return GString* containing info on peripherals in json format. + * Returns NULL on failure to allocate the string. + */ +static GString *get_peripherals_json() { + // Trying out ways of estimating size. The +1 is there to avoid size 0 if query length is 0. + t_bidib_id_list_query per_query = bidib_get_connected_peripherals(); + GString *g_peripherals = g_string_sized_new(64 * (per_query.length + 1)); + if (g_peripherals == NULL) { + bidib_free_id_list_query(per_query); + syslog_server(LOG_ERR, "Get peripherals json - can't allocate g_granted_routes"); + return NULL; + } + + g_string_assign(g_peripherals, ""); + append_start_of_obj(g_peripherals, false); + append_field_start_of_list(g_peripherals, "peripherals"); + + int added_peripherals = 0; + for (size_t i = 0; i < per_query.length; i++) { + t_bidib_peripheral_state_query per_state_query = bidib_get_peripheral_state(per_query.ids[i]); + if (!per_state_query.available) { bidib_free_peripheral_state_query(per_state_query); + syslog_server(LOG_WARNING, + "Get pheripherals json - peripheral %s not available", + per_query.ids[i]); + continue; + } + // intentional post-increment, so only add comma before obj from second loop iter onwards. + if (added_peripherals++ > 0) { + g_string_append_c(g_peripherals, ','); } - bidib_free_id_list_query(per_query); - onion_response_printf(res, "%s", peripherals->str); - syslog_server(LOG_INFO, "Request: Get peripherals - done"); - g_string_free(peripherals, true); + append_start_of_obj(g_peripherals, true); + append_field_str_value(g_peripherals, "id", per_query.ids[i], true); + append_field_str_value(g_peripherals, "state-id", per_state_query.data.state_id, true); + append_field_uint_value(g_peripherals, "state-value", per_state_query.data.state_value, false); + append_end_of_obj(g_peripherals, false); + bidib_free_peripheral_state_query(per_state_query); + } + + append_end_of_list(g_peripherals, false, added_peripherals > 0); + append_end_of_obj(g_peripherals, false); + bidib_free_id_list_query(per_query); + return g_peripherals; +} + +o_con_status handler_get_peripherals(void *_, onion_request *req, onion_response *res) { + build_response_header(res); + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + GString *g_peripherals = get_peripherals_json(); + if (g_peripherals != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_peripherals); + syslog_server(LOG_INFO, "Request: Get peripherals - done"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: Get peripherals - unable to build reply message"); + } return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Get peripherals - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get peripherals"); } } -onion_connection_status handler_get_verification_option(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_get_verification_option(void *_, onion_request *req, onion_response *res) { build_response_header(res); if ((onion_request_get_flags(req) & OR_METHODS) == OR_GET) { - onion_response_printf(res, "verification-enabled: %s", + onion_response_set_code(res, HTTP_OK); + onion_response_printf(res, + "{\"verification-enabled\": %s }", verification_enabled ? "true" : "false"); syslog_server(LOG_INFO, "Request: Get verification option - done"); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get verification option - wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get verification option"); } } -onion_connection_status handler_get_verification_url(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_get_verification_url(void *_, onion_request *req, onion_response *res) { build_response_header(res); if ((onion_request_get_flags(req) & OR_METHODS) == OR_GET) { const char *verif_url = get_verifier_url(); - onion_response_printf(res, "verification-url: %s", verif_url == NULL ? "null" : verif_url); + send_single_str_field_feedback(res, HTTP_OK, "verification-url", + verif_url == NULL ? "null" : verif_url); syslog_server(LOG_INFO, "Request: Get verification url - done"); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get verification url - wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get verification url"); } } -onion_connection_status handler_get_granted_routes(void *_, onion_request *req, - onion_response *res) { - build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - bool needNewLine = false; - GString *granted_routes = g_string_new(""); - GArray *route_ids = interlocking_table_get_all_route_ids(); - if (route_ids != NULL) { - for (size_t i = 0; i < route_ids->len; i++) { - const char *route_id = g_array_index(route_ids, char *, i); - if (route_id != NULL) { - t_interlocking_route *route = get_route(route_id); - if (route != NULL && route->train != NULL) { - g_string_append_printf(granted_routes, "%sroute id: %s train: %s", - needNewLine ? "\n" : "", route->id, route->train); - needNewLine = true; +/** + * @brief Get information on granted routes, i.e., the route identifier and the train that the + * route is granted to for each granted route. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_granted-routes.json + * + * @return GString* containing info on granted routes in json format. + * Returns NULL on failure to allocate the string. + */ +static GString* get_granted_routes_json() { + // Size based on examples, will be auto-resized if not enough. + GString *g_granted_routes = g_string_sized_new(128); + if (g_granted_routes == NULL) { + syslog_server(LOG_ERR, "Get granted routes json - can't allocate g_granted_routes"); + return NULL; + } + g_string_assign(g_granted_routes, ""); + + append_start_of_obj(g_granted_routes, false); + append_field_start_of_list(g_granted_routes, "granted-routes"); + + pthread_mutex_lock(&interlocker_mutex); + GArray *route_ids = interlocking_table_get_all_route_ids_shallowcpy(); + + int routes_added = 0; + if (route_ids != NULL) { + for (unsigned int i = 0; i < route_ids->len; i++) { + const char *route_id = g_array_index(route_ids, char *, i); + if (route_id != NULL) { + t_interlocking_route *route = get_route(route_id); + if (route != NULL && route->train != NULL) { + if (routes_added > 0) { + g_string_append_c(g_granted_routes, ','); } + routes_added++; + append_start_of_obj(g_granted_routes, true); + append_field_str_value(g_granted_routes, "id", route->id, true); + append_field_str_value(g_granted_routes, "train", route->train, false); + append_end_of_obj(g_granted_routes, false); } } - g_array_free(route_ids, true); } - - if (strcmp(granted_routes->str, "") == 0) { - g_string_append_printf(granted_routes, "No granted routes"); + pthread_mutex_unlock(&interlocker_mutex); + // free the GArray but not the contained strings, as it was created by shallow copy. + g_array_free(route_ids, true); + } else { + pthread_mutex_unlock(&interlocker_mutex); + syslog_server(LOG_WARNING, + "Get granted routes json - " + "route ID array copied from interlocking table is NULL"); + } + + append_end_of_list(g_granted_routes, false, routes_added > 0); + append_end_of_obj(g_granted_routes, false); + return g_granted_routes; +} + +o_con_status handler_get_granted_routes(void *_, onion_request *req, onion_response *res) { + build_response_header(res); + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { + GString *g_granted_routes = get_granted_routes_json(); + if (g_granted_routes != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_granted_routes); + syslog_server(LOG_INFO, "Request: Get granted routes - done"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: Get granted routes - unable to build reply message"); } - - onion_response_printf(res, "%s", granted_routes->str); - syslog_server(LOG_INFO, "Request: Get granted routes - done"); - g_string_free(granted_routes, true); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Get granted routes - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get granted routes"); } } -void sprintf_garray_char(GString *output, GArray *garray) { - if (garray->len == 0) { - g_string_append_printf(output, "none"); - return; +/** + * @brief Get information on a particular route, specified by the parameter route_id. + * The returned string is formatted to comply with the json-schema: + * /server/doc/api-formats/monitor/json-schema-monitor_route.json + * + * @param route_id id of the route to get info on. + * @return GString* containing info on the route in json format. + * Returns NULL on failure to allocate the string. + */ +static GString* get_route_json(const char *route_id) { + ///NOTE: Think about a possible endpoint with less details; e.g., no length, + /// no conflicting-route-ids, no sections; to reduce bandwidth load where those fields + /// are not needed by the client anyway. + /// Also, this does not return info on the required position of points, is that not needed + /// by any client? + + if (route_id == NULL) { + return NULL; } - for (size_t i = 0; i < garray->len; i++) { - g_string_append_printf(output, "%s%s", - g_array_index(garray, char *, i), - i != (garray->len - 1) ? ", " : ""); + pthread_mutex_lock(&interlocker_mutex); + const t_interlocking_route *route = get_route(route_id); + if (route == NULL) { + pthread_mutex_unlock(&interlocker_mutex); + syslog_server(LOG_ERR, "Get route json - route: %s - no route with this ID found", route_id); + return NULL; } -} - -void sprintf_garray_interlocking_point(GString *output, GArray *garray) { - if (garray->len == 0) { - g_string_append_printf(output, "none"); - return; + // Size estimated from examples with some extra margins. + GString *g_route = g_string_sized_new(1024); + if (g_route == NULL) { + pthread_mutex_unlock(&interlocker_mutex); + syslog_server(LOG_ERR, "Get route json - route: %s - can't allocate g_route", route_id); + return NULL; } - for (size_t i = 0; i < garray->len; i++) { - g_string_append_printf(output, "%s%s", - g_array_index(garray, t_interlocking_point, i).id, - i != (garray->len - 1) ? ", " : ""); + g_string_assign(g_route, ""); + + append_start_of_obj(g_route, false); + append_field_literal_value_from_str(g_route, "id", route->id, true); + append_field_str_value(g_route, "source_signal", route->source, true); + append_field_str_value(g_route, "destination_signal", route->destination, true); + append_field_str_value(g_route, "orientation", route->orientation, true); + append_field_float_value(g_route, "length", route->length, true); + append_field_strlist_value_from_garray_strs(g_route, "path", route->path, true); + append_field_strlist_value_from_garray_strs(g_route, "sections", route->sections, true); + append_field_strlist_value_from_garray_strs(g_route, "signals", route->signals, true); + + // if g_point_ids is null, nothing will be appended. (same for g_conflicts down below). + GArray *g_point_ids = garray_points_to_garray_str_ids(route->points); + append_field_strlist_value_from_garray_strs(g_route, "points", g_point_ids, true); + if (g_point_ids == NULL) { + syslog_server(LOG_WARNING, + "Get route json - route: %s - g_point_ids null, no points will be included", + route_id); + } else { + free_g_strarray_and_contents(g_point_ids); } + + // string array in json, because everywhere else route_ids are given as strings. + // integer representation would be more memory/bandwidth efficient though, + // as the list of conflicts is often very long for larger platforms. + append_field_strlist_value_from_garray_strs(g_route, "conflicting_route_ids", + route->conflicts, true); + + GArray *g_conflicts = get_granted_route_conflicts(route_id, false); + append_field_literallist_value_from_garray_strs(g_route, "granted_conflicting_route_ids", + g_conflicts, true); + if (g_conflicts == NULL) { + syslog_server(LOG_WARNING, + "Get route json - route: %s - g_conflicts null, conflicts will be empty", + route_id); + } else { + free_g_strarray_and_contents(g_conflicts); + } + + append_field_bool_value(g_route, "clear", get_route_is_clear(route_id), true); + append_field_str_value(g_route, "granted_to_train", + route->train == NULL ? "" : route->train, false); + append_end_of_obj(g_route, false); + pthread_mutex_unlock(&interlocker_mutex); + return g_route; } -onion_connection_status handler_get_route(void *_, onion_request *req, onion_response *res) { +o_con_status handler_get_route(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *data_route_id = onion_request_get_post(req, "route-id"); const char *route_id = params_check_route_id(data_route_id); - if (route_id == NULL || strcmp(route_id, "") == 0 || get_route(route_id) == NULL) { - syslog_server(LOG_ERR, "Request: Get route - invalid parameters"); - return OCS_NOT_IMPLEMENTED; + + if (handle_param_miss_check(res, "Get route", "route-id", data_route_id)) { + return OCS_PROCESSED; + } else if (strcmp(route_id, "") == 0 || get_route(route_id) == NULL) { + onion_response_set_code(res, HTTP_NOT_FOUND); + syslog_server(LOG_ERR, "Request: Get route - unknown route-id"); + return OCS_PROCESSED; } syslog_server(LOG_INFO, "Request: Get route - route: %s - start", route_id); - GString *route_str = g_string_new(""); - - pthread_mutex_lock(&interlocker_mutex); - t_interlocking_route *route = get_route(route_id); - g_string_append_printf(route_str, "route id: %s\n", route->id); - g_string_append_printf(route_str, " source signal: %s\n", route->source); - g_string_append_printf(route_str, " destination signal: %s\n", route->destination); - g_string_append_printf(route_str, " orientation: %s\n", route->orientation); - g_string_append_printf(route_str, " length: %f\n", route->length); - g_string_append_printf(route_str, " path: "); - sprintf_garray_char(route_str, route->path); - g_string_append_printf(route_str, "\n sections: "); - sprintf_garray_char(route_str, route->sections); - g_string_append_printf(route_str, "\n points: "); - sprintf_garray_interlocking_point(route_str, route->points); - g_string_append_printf(route_str, "\n signals: "); - sprintf_garray_char(route_str, route->signals); - g_string_append_printf(route_str, "\n conflicting route ids: "); - sprintf_garray_char(route_str, route->conflicts); - - g_string_append_printf(route_str, "\nstatus:"); - g_string_append_printf(route_str, "\n granted conflicting route ids: "); - GArray *granted_route_conflicts = get_granted_route_conflicts(route_id); - sprintf_garray_char(route_str, granted_route_conflicts); - g_array_free(granted_route_conflicts, true); - - g_string_append_printf(route_str, "\n route clear: %s", - get_route_is_clear(route_id) ? "yes": "no"); - - g_string_append_printf(route_str, "\n granted train: %s", - route->train == NULL ? "none" : route->train); - pthread_mutex_unlock(&interlocker_mutex); - - onion_response_printf(res, "%s", route_str->str); - syslog_server(LOG_INFO, "Request: Get route - route: %s - finish", route_id); - g_string_free(route_str, true); + GString* g_route = get_route_json(route_id); + if (g_route != NULL) { + send_some_gstring_and_free(res, HTTP_OK, g_route); + syslog_server(LOG_INFO, "Request: Get route - route: %s - finished", route_id); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, + "Request: Get route - route: %s - invalid route-id or internal error", + route_id); + } return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get route - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get route"); } } // Returns debugging information related to the ForeC dynamic containers. // Provides data values seen by the environment (dyn_containers_interface.c) // and those set by the containers (dyn_containers.forec). -GString *debug_info(void) { - GString *info_str = g_string_new(""); - +static GString *debug_info(void) { + GString *info_str = g_string_new(""); + const char info_template0[] = "Debug info: \n" "* dyn_containers_reaction_counter: %lld \n" @@ -654,7 +1348,7 @@ GString *debug_info(void) { dyn_containers_reaction_counter__global_0_0, dyn_containers_actuate_reaction_counter ); - + const char info_template1[] = "dyn_containers_interface: (external value, internal value) \n" " running: %d \n" @@ -780,35 +1474,35 @@ GString *debug_info(void) { return info_str; } -onion_connection_status handler_get_debug_info(void *_, onion_request *req, onion_response *res) { +o_con_status handler_get_debug_info(void *_, onion_request *req, onion_response *res) { build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { GString *debug_info_str = debug_info(); - char response[strlen(debug_info_str->str) + 1]; - strcpy(response, debug_info_str->str); - g_string_free(debug_info_str, true); - - onion_response_printf(res, "%s", response); - syslog_server(LOG_NOTICE, "Request: Get debug info"); + if (debug_info_str != NULL && debug_info_str->str != NULL) { + send_some_gstring_and_free(res, HTTP_OK, debug_info_str); + syslog_server(LOG_NOTICE, "Request: Get debug info - done"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: Get debug info - internal error"); + } return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Get debug info - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get debug info"); } } // Returns extra debugging information related to the ForeC thread scheduler // in dyn_containers.forec. -GString *debug_info_extra(void) { - GString *info_str = g_string_new(""); - +static GString *debug_info_extra(void) { + GString *info_str = g_string_new(""); + const char info_template[] = "Debug info extra: \n" "* mainParReactionCounter: %d \n" "* mainParCore1.reactionCounter: %d \n" "* mainParCore2.reactionCounter: %d \n" "\n"; - + g_string_append_printf( info_str, info_template, mainParReactionCounter, @@ -819,21 +1513,19 @@ GString *debug_info_extra(void) { return info_str; } -onion_connection_status handler_get_debug_info_extra(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_get_debug_info_extra(void *_, onion_request *req, onion_response *res) { build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { + if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_GET)) { GString *debug_info_extra_str = debug_info_extra(); - char response[strlen(debug_info_extra_str->str) + 1]; - strcpy(response, debug_info_extra_str->str); - g_string_free(debug_info_extra_str, true); - - onion_response_printf(res, "%s", response); - syslog_server(LOG_NOTICE, "Request: Get debug info extra"); + if (debug_info_extra_str != NULL && debug_info_extra_str->str != NULL) { + send_some_gstring_and_free(res, HTTP_OK, debug_info_extra_str); + syslog_server(LOG_NOTICE, "Request: Get debug info extra"); + } else { + onion_response_set_code(res, HTTP_INTERNAL_ERROR); + syslog_server(LOG_ERR, "Request: Get debug info extra - internal error"); + } return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Get debug info extra - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Get debug info extra"); } } diff --git a/server/src/handler_monitor.h b/server/src/handler_monitor.h index 16a5d80e..fa878da7 100644 --- a/server/src/handler_monitor.h +++ b/server/src/handler_monitor.h @@ -32,64 +32,57 @@ #include "dyn_containers_interface.h" #include "dyn_containers.h" +typedef onion_connection_status o_con_status; extern t_dyn_containers_interface *dyn_containers_interface; extern long long dyn_containers_reaction_counter__global_0_0; extern long long dyn_containers_actuate_reaction_counter; -onion_connection_status handler_get_platform_name(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_platform_name(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_trains(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_trains(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_train_state(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_train_state(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_train_peripherals(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_train_states(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_track_outputs(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_train_peripherals(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_points(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_engines(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_signals(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_interlockers(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_point_aspects(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_track_outputs(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_signal_aspects(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_points(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_segments(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_signals(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_reversers(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_point_details(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_peripherals(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_signal_details(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_verification_option(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_point_aspects(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_verification_url(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_signal_aspects(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_granted_routes(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_segments(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_route(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_reversers(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_debug_info(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_peripherals(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_debug_info_extra(void *_, onion_request *req, - onion_response *res); +o_con_status handler_get_verification_option(void *_, onion_request *req, onion_response *res); + +o_con_status handler_get_verification_url(void *_, onion_request *req, onion_response *res); + +o_con_status handler_get_granted_routes(void *_, onion_request *req, onion_response *res); + +o_con_status handler_get_route(void *_, onion_request *req, onion_response *res); + +o_con_status handler_get_debug_info(void *_, onion_request *req, onion_response *res); + +o_con_status handler_get_debug_info_extra(void *_, onion_request *req, onion_response *res); #endif // HANDLER_MONITOR_H diff --git a/server/src/handler_upload.c b/server/src/handler_upload.c index a9041746..06dd879b 100644 --- a/server/src/handler_upload.c +++ b/server/src/handler_upload.c @@ -40,6 +40,9 @@ #include "dynlib.h" #include "dyn_containers_interface.h" #include "websocket_uploader/engine_uploader.h" +#include "communication_utils.h" + +typedef onion_connection_status o_con_status; static const char engine_dir[] = "engines"; static const char engine_extensions[][5] = { "c", "h", "sctx" }; @@ -51,8 +54,7 @@ static const int interlocker_extensions_count = 1; extern pthread_mutex_t dyn_containers_mutex; - -bool clear_dir(const char dir[]) { +static bool clear_dir(const char dir[]) { int result = 0; DIR *dir_handle = opendir(dir); @@ -85,15 +87,17 @@ bool clear_interlocker_dir(void) { return clear_dir(interlocker_dir); } -void remove_file_extension(char filepath_destination[], - const char filepath_source[], const char extension[]) { +static void remove_file_extension(char filepath_destination[], const char filepath_source[], + const char extension[]) { strcpy(filepath_destination, filepath_source); size_t filepath_len = strlen(filepath_source); size_t extension_len = strlen(extension); - filepath_destination[filepath_len - extension_len] = '\0'; + if (filepath_len > extension_len) { + filepath_destination[filepath_len - extension_len] = '\0'; + } } -bool engine_file_exists(const char filename[]) { +static bool engine_file_exists(const char filename[]) { DIR *dir_handle = opendir(engine_dir); if (dir_handle == NULL) { closedir(dir_handle); @@ -116,7 +120,7 @@ bool engine_file_exists(const char filename[]) { return false; } -bool remove_engine_files(const char library_name[]) { +static bool remove_engine_files(const char library_name[]) { // Remove the prefix "lib" char name[PATH_MAX + NAME_MAX]; strcpy(name, library_name + 3); @@ -133,33 +137,38 @@ bool remove_engine_files(const char library_name[]) { return (result == 0); } -bool plugin_is_unremovable(const char name[]) { +static bool plugin_is_unremovable(const char name[]) { return (strstr(name, "(unremovable)") != NULL); } -onion_connection_status handler_upload_engine(void *_, onion_request *req, onion_response *res) { +o_con_status handler_upload_engine(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { + // The client has to attach the file via form data. They do NOT have to + // also send the filename on its own as a string - this is just how onion + // allows us to get the filename of an attached file, even though it looks + // like the client has to provide both a file and the filename. const char *filename = onion_request_get_post(req, "file"); const char *temp_filepath = onion_request_get_file(req, "file"); - if (filename == NULL || temp_filepath == NULL) { - syslog_server(LOG_ERR, "Request: Upload engine - engine file is invalid"); - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Engine file is invalid"); + if (handle_param_miss_check(res, "Upload engine", "file", filename)) { + return OCS_PROCESSED; + } else if (temp_filepath == NULL) { + // Either something went wrong with the fs(?), or the file was not attached at all. + send_common_feedback(res, HTTP_BAD_REQUEST, "engine file is invalid or missing"); + syslog_server(LOG_ERR, "Request: Upload engine - engine file is invalid or missing"); return OCS_PROCESSED; } syslog_server(LOG_NOTICE, "Request: Upload engine - engine file: %s - start", filename); - - if (engine_file_exists(filename)) { + + if (engine_file_exists(filename)) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "engine file already exists"); syslog_server(LOG_ERR, "Request: Upload engine - engine file: %s - " "engine file already exists - abort", filename); - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Engine file already exists"); return OCS_PROCESSED; } @@ -167,26 +176,34 @@ onion_connection_status handler_upload_engine(void *_, onion_request *req, onion remove_file_extension(filename_noextension, filename, ".sctx"); char libname[sizeof(filename_noextension)]; snprintf(libname, sizeof(libname), "lib%s", filename_noextension); - + char final_filepath[PATH_MAX + NAME_MAX]; snprintf(final_filepath, sizeof(final_filepath), "%s/%s", engine_dir, filename); onion_shortcut_rename(temp_filepath, final_filepath); syslog_server(LOG_DEBUG, "Request: Upload engine - engine file: %s - copied engine file from %s to %s", filename, temp_filepath, final_filepath); - + if (verification_enabled) { verif_result engine_verif_result = verify_engine_model(final_filepath); if (!engine_verif_result.success) { // Stop upload if verification did not succeed syslog_server(LOG_NOTICE, "Request: Upload Engine - engine verification failed - abort"); remove_engine_files(libname); - onion_response_set_code(res, HTTP_BAD_REQUEST); - if (engine_verif_result.message != NULL) { - onion_response_printf(res, "%s", engine_verif_result.message->str); + // If the reply message (from the verification server) is NOT in json format, + // send it via common_feedback; if it IS in JSON format, send it directly. + // -> the OpenAPI spec conformance thus relies on the verification server + // adhering to the spec, which is fragile, but all other options would be + // quite complicated. + // If there's no message, reason for verification failure is unknown. + if (engine_verif_result.message != NULL && !engine_verif_result.message_is_json_str) { + send_common_feedback(res, HTTP_BAD_REQUEST, engine_verif_result.message->str); g_string_free(engine_verif_result.message, true); + } else if (engine_verif_result.message != NULL && engine_verif_result.message_is_json_str) { + send_some_gstring_and_free(res, HTTP_BAD_REQUEST, engine_verif_result.message); } else { - onion_response_printf(res, "Engine Verification failed due to unknown reason."); + send_common_feedback(res, HTTP_BAD_REQUEST, + "verification failed due to unknown reason"); } return OCS_PROCESSED; } @@ -197,17 +214,12 @@ onion_connection_status handler_upload_engine(void *_, onion_request *req, onion const dynlib_status status = dynlib_compile_scchart(filepath, engine_dir); if (status == DYNLIB_COMPILE_SCCHARTS_C_ERR || status == DYNLIB_COMPILE_SHARED_SCCHARTS_ERR) { remove_engine_files(libname); - + + send_common_feedback(res, HTTP_INTERNAL_ERROR, "engine file could not be compiled"); syslog_server(LOG_ERR, "Request: Upload engine - engine file: %s - could not be " "compiled into a C file and then to a shared library - abort", filepath); - ///TODO: Discuss which code to return - onion_response_set_code(res, HTTP_INTERNAL_ERROR); - onion_response_printf(res, - "Engine file %s could not be compiled into a C file " - "and then a shared library", - filepath); return OCS_PROCESSED; } syslog_server(LOG_DEBUG, @@ -219,56 +231,38 @@ onion_connection_status handler_upload_engine(void *_, onion_request *req, onion if (engine_slot < 0) { pthread_mutex_unlock(&dyn_containers_mutex); remove_engine_files(libname); - + + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "No available engine slot"); syslog_server(LOG_WARNING, "Request: Upload engine - engine file: %s - " "no available engine slot - abort", filename); - ///TODO: Discuss which code to return - onion_response_set_code(res, HTTP_INTERNAL_ERROR); - onion_response_printf(res, "No available engine slot"); return OCS_PROCESSED; } snprintf(filepath, sizeof(filepath), "%s/%s", engine_dir, libname); dyn_containers_set_engine(engine_slot, filepath); pthread_mutex_unlock(&dyn_containers_mutex); + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, "Request: Upload engine - engine file: %s - finish", filename); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Upload engine - system not running or wrong request type"); - ///TODO: Discuss which code to return - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "System not running or wrong request type"); - return OCS_PROCESSED; + return handle_req_run_or_method_fail(res, running, "Upload engine"); } } -onion_connection_status handler_get_engines(void *_, onion_request *req, onion_response *res) { - build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - GString *train_engines = dyn_containers_get_train_engines(); - onion_response_printf(res, "%s", train_engines->str); - g_string_free(train_engines, true); - syslog_server(LOG_INFO, "Request: Get engines - done"); - return OCS_PROCESSED; - } else { - syslog_server(LOG_ERR, "Request: Get engines - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; - } -} - -onion_connection_status handler_remove_engine(void *_, onion_request *req, onion_response *res) { +o_con_status handler_remove_engine(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *name = onion_request_get_post(req, "engine-name"); - if (name == NULL || plugin_is_unremovable(name)) { + + if (handle_param_miss_check(res, "Remove engine", "engine-name", name)) { + return OCS_PROCESSED; + } else if (plugin_is_unremovable(name)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "engine to remove is unremovable"); syslog_server(LOG_ERR, - "Request: Remove engine - engine name \"%s\" is " - "invalid or engine is unremovable", - (name == NULL) ? "null" : name); - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Engine name is invalid or engine is unremovable"); + "Request: Remove engine - engine to remove (%s) is unremovable", + name); return OCS_PROCESSED; } @@ -278,47 +272,37 @@ onion_connection_status handler_remove_engine(void *_, onion_request *req, onion const int engine_slot = dyn_containers_get_engine_slot(name); if (engine_slot < 0) { pthread_mutex_unlock(&dyn_containers_mutex); + send_common_feedback(res, HTTP_NOT_FOUND, "engine could not be found"); syslog_server(LOG_WARNING, "Request: Remove engine - engine: %s - engine could not be found - abort", name); - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Engine %s could not be found", name); return OCS_PROCESSED; } const bool engine_freed_successfully = dyn_containers_free_engine(engine_slot); pthread_mutex_unlock(&dyn_containers_mutex); if (!engine_freed_successfully) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "engine still in use"); syslog_server(LOG_WARNING, "Request: Remove engine - engine: %s - engine is still in use - abort", name); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Engine %s is still in use", name); return OCS_PROCESSED; } if (!remove_engine_files(name)) { - syslog_server(LOG_ERR, - "Request: Remove engine - engine: %s - files could not be removed - abort", + syslog_server(LOG_WARNING, + "Request: Remove engine - engine: %s - files could not be removed", name); - ///TODO: This is an internal error, should return different response code IMO - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Engine %s files could not be removed", name); - return OCS_PROCESSED; } + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, "Request: Remove engine - engine: %s - finish", name); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, "Request: Remove engine - system not running or wrong request type"); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "System not running or wrong request type"); - return OCS_PROCESSED; + return handle_req_run_or_method_fail(res, running, "Remove engine"); } } -bool interlocker_file_exists(const char filename[]) { +static bool interlocker_file_exists(const char filename[]) { DIR *dir_handle = opendir(interlocker_dir); if (dir_handle == NULL) { closedir(dir_handle); @@ -338,11 +322,11 @@ bool interlocker_file_exists(const char filename[]) { return false; } -bool remove_interlocker_files(const char library_name[]) { +static bool remove_interlocker_files(const char library_name[]) { // Remove the prefix "libinterlocker_" char name[PATH_MAX + NAME_MAX]; strcpy(name, library_name + 15); - + int result = 0; char filepath[PATH_MAX + NAME_MAX]; for (int i = 0; i < interlocker_extensions_count; i++) { @@ -355,39 +339,40 @@ bool remove_interlocker_files(const char library_name[]) { return (result == 0); } -onion_connection_status handler_upload_interlocker(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_upload_interlocker(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *filename = onion_request_get_post(req, "file"); const char *temp_filepath = onion_request_get_file(req, "file"); - if (filename == NULL || temp_filepath == NULL) { - syslog_server(LOG_ERR, "Request: Upload - interlocker file is invalid"); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Interlocker file is invalid"); + + if (handle_param_miss_check(res, "Upload interlocker", "file", filename)) { + return OCS_PROCESSED; + } else if (temp_filepath == NULL) { + // Either something went wrong with the fs(?), or the file was not attached at all. + send_common_feedback(res, HTTP_BAD_REQUEST, "interlocker file is invalid or missing"); + syslog_server(LOG_ERR, + "Request: Upload interlocker - interlocker file is invalid or missing"); return OCS_PROCESSED; } + syslog_server(LOG_NOTICE, "Request: Upload interlocker - interlocker file: %s - start", filename); - + if (interlocker_file_exists(filename)) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "interlocker file already exists"); syslog_server(LOG_ERR, "Request: Upload interlocker - interlocker file: %s - " "file already exists - abort", filename); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Interlocker file already exists"); return OCS_PROCESSED; } - + char filename_noextension[NAME_MAX]; remove_file_extension(filename_noextension, filename, ".bahn"); char libname[sizeof(filename_noextension)]; snprintf(libname, sizeof(libname), "libinterlocker_%s", filename_noextension); - + char final_filepath[PATH_MAX + NAME_MAX]; snprintf(final_filepath, sizeof(final_filepath), "%s/%s", interlocker_dir, filename); onion_shortcut_rename(temp_filepath, final_filepath); @@ -395,137 +380,102 @@ onion_connection_status handler_upload_interlocker(void *_, onion_request *req, "Request: Upload interlocker - interlocker file: %s - " "copied interlocker BahnDSL file from %s to %s", filename, temp_filepath, final_filepath); - + char filepath[sizeof(final_filepath)]; remove_file_extension(filepath, final_filepath, ".bahn"); const dynlib_status status = dynlib_compile_bahndsl(filepath, interlocker_dir); if (status == DYNLIB_COMPILE_SHARED_BAHNDSL_ERR) { + send_common_feedback(res, HTTP_INTERNAL_ERROR, "interlocker file could not be compiled"); syslog_server(LOG_ERR, "Request: Upload interlocker - interlocker file: %s - " "interlocker could not be compiled - abort", filename); remove_interlocker_files(libname); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Interlocker file %s could not be compiled", filepath); return OCS_PROCESSED; } syslog_server(LOG_DEBUG, "Request: Upload interlocker - interlocker file: %s - interlocker compiled", filename); - + pthread_mutex_lock(&dyn_containers_mutex); const int interlocker_slot = dyn_containers_get_free_interlocker_slot(); if (interlocker_slot < 0) { pthread_mutex_unlock(&dyn_containers_mutex); remove_interlocker_files(libname); - + + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "no interlocker slot available"); syslog_server(LOG_WARNING, "Request: Upload interlocker - interlocker file: %s - " "no available interlocker slot - abort", filename); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "No available interlocker slot"); return OCS_PROCESSED; } - + snprintf(filepath, sizeof(filepath), "%s/%s", interlocker_dir, libname); dyn_containers_set_interlocker(interlocker_slot, filepath); pthread_mutex_unlock(&dyn_containers_mutex); + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, "Request: Upload interlocker - interlocker file: %s - finish", filename); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Upload interlocker - system not running or wrong request type"); - - onion_response_printf(res, "System not running or wrong request type"); - onion_response_set_code(res, HTTP_BAD_REQUEST); - return OCS_PROCESSED; - } -} - -onion_connection_status handler_get_interlockers(void *_, onion_request *req, - onion_response *res) { - build_response_header(res); - if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { - GString *interlockers = dyn_containers_get_interlockers(); - onion_response_printf(res, "%s", interlockers->str); - g_string_free(interlockers, true); - syslog_server(LOG_INFO, "Request: Get interlockers - done"); - return OCS_PROCESSED; - } else { - syslog_server(LOG_ERR, - "Request: Get interlockers - system not running or wrong request type"); - return OCS_NOT_IMPLEMENTED; + return handle_req_run_or_method_fail(res, running, "Upload interlocker"); } } -onion_connection_status handler_remove_interlocker(void *_, onion_request *req, - onion_response *res) { +o_con_status handler_remove_interlocker(void *_, onion_request *req, onion_response *res) { build_response_header(res); if (running && ((onion_request_get_flags(req) & OR_METHODS) == OR_POST)) { const char *name = onion_request_get_post(req, "interlocker-name"); - if (name == NULL || plugin_is_unremovable(name)) { + + if (handle_param_miss_check(res, "Remove interlocker", "interlocker-name", name)) { + return OCS_PROCESSED; + } else if (plugin_is_unremovable(name)) { + send_common_feedback(res, HTTP_BAD_REQUEST, "interlocker to remove is unremovable"); syslog_server(LOG_ERR, - "Request: Remove interlocker - interlocker name is invalid " - "or interlocker is unremovable"); - - onion_response_printf(res, "Interlocker name is invalid or interlocker is unremovable"); - onion_response_set_code(res, HTTP_BAD_REQUEST); + "Request: Remove interlocker - interlocker to remove (%s) is unremovable", + name); return OCS_PROCESSED; } + syslog_server(LOG_NOTICE, "Request: Remove interlocker - interlocker: %s - start", name); - + pthread_mutex_lock(&dyn_containers_mutex); const int interlocker_slot = dyn_containers_get_interlocker_slot(name); if (interlocker_slot < 0) { pthread_mutex_unlock(&dyn_containers_mutex); + send_common_feedback(res, HTTP_NOT_FOUND, "Interlocker to remove could not be found"); syslog_server(LOG_ERR, "Request: Remove interlocker - interlocker: %s - " "interlocker could not be found - abort", name); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Interlocker %s could not be found", name); return OCS_PROCESSED; } - - const bool interlocker_freed_successfully = dyn_containers_free_interlocker(interlocker_slot); + + const bool free_success = dyn_containers_free_interlocker(interlocker_slot); pthread_mutex_unlock(&dyn_containers_mutex); - if (!interlocker_freed_successfully) { + if (!free_success) { + send_common_feedback(res, CUSTOM_HTTP_CODE_CONFLICT, "Interlocker is still in use"); syslog_server(LOG_WARNING, "Request: Remove interlocker - interlocker: %s - " "interlocker is still in use - abort", name); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Interlocker %s is still in use", name); return OCS_PROCESSED; } - + if (!remove_interlocker_files(name)) { - syslog_server(LOG_ERR, + syslog_server(LOG_WARNING, "Request: Remove interlocker - interlocker: %s - " - "files could not be removed - abort", + "files could not be removed", name); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "Interlocker %s files could not be removed", name); - return OCS_PROCESSED; } + onion_response_set_code(res, HTTP_OK); syslog_server(LOG_NOTICE, "Request: Remove interlocker - interlocker: %s - finish", name); return OCS_PROCESSED; } else { - syslog_server(LOG_ERR, - "Request: Remove interlocker - system not running or wrong request type"); - - onion_response_set_code(res, HTTP_BAD_REQUEST); - onion_response_printf(res, "System not running or wrong request type"); - return OCS_PROCESSED; + return handle_req_run_or_method_fail(res, running, "Remove interlocker"); } } diff --git a/server/src/handler_upload.h b/server/src/handler_upload.h index a3bcc28e..3df5d5c8 100644 --- a/server/src/handler_upload.h +++ b/server/src/handler_upload.h @@ -32,28 +32,20 @@ #include +typedef onion_connection_status o_con_status; + bool clear_engine_dir(void); bool clear_interlocker_dir(void); -onion_connection_status handler_upload_engine(void *_, onion_request *req, - onion_response *res); - -onion_connection_status handler_get_engines(void *_, onion_request *req, - onion_response *res); - -onion_connection_status handler_remove_engine(void *_, onion_request *req, - onion_response *res); +o_con_status handler_upload_engine(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_upload_interlocker(void *_, onion_request *req, - onion_response *res); +o_con_status handler_remove_engine(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_get_interlockers(void *_, onion_request *req, - onion_response *res); +o_con_status handler_upload_interlocker(void *_, onion_request *req, onion_response *res); -onion_connection_status handler_remove_interlocker(void *_, onion_request *req, - onion_response *res); +o_con_status handler_remove_interlocker(void *_, onion_request *req, onion_response *res); #endif // HANDLER_UPLOAD_H diff --git a/server/src/interlockers/libinterlocker_default (unremovable).so b/server/src/interlockers/libinterlocker_default (unremovable).so index 7476a062..96791bd6 100755 Binary files a/server/src/interlockers/libinterlocker_default (unremovable).so and b/server/src/interlockers/libinterlocker_default (unremovable).so differ diff --git a/server/src/interlockers/libinterlocker_simple_sectional (unremovable).so b/server/src/interlockers/libinterlocker_simple_sectional (unremovable).so index dcca0eac..6442ed38 100755 Binary files a/server/src/interlockers/libinterlocker_simple_sectional (unremovable).so and b/server/src/interlockers/libinterlocker_simple_sectional (unremovable).so differ diff --git a/server/src/interlockers/smart.bahn b/server/src/interlockers/smart.bahn index ec9e3a38..1e2cb7df 100644 --- a/server/src/interlockers/smart.bahn +++ b/server/src/interlockers/smart.bahn @@ -8,38 +8,38 @@ def request_route(string src_signal_id, string dst_signal_id, string train_id): return "no_routes" end - string route_id = "" - bool grantable = false - bool cleared = false - float min_length = 999999999 - for string route in route_ids - # 2. Check whether the route can be granted - if route_is_grantable(route, train_id) - grantable = true - - # 3a. Check whether the route is physically available - if route_is_clear(route) - - # 3b. Check whether the route is the shortest available - float length = get config route.length route - if length < min_length - min_length = length - cleared = true - route_id = route - end - end - end - - # Check the next route - end - - if !grantable - return "not_grantable" - end - - if !cleared - return "not_clear" - end + string route_id = "" + bool grantable = false + bool cleared = false + float min_length = 999999999 + for string route in route_ids + # 2. Check whether the route can be granted + if route_is_grantable(route, train_id) + grantable = true + + # 3a. Check whether the route is physically available + if route_is_clear(route) + + # 3b. Check whether the route is the shortest available + float length = get config route.length route + if length < min_length + min_length = length + cleared = true + route_id = route + end + end + end + + # Check the next route + end + + if !grantable + return "not_grantable" + end + + if !cleared + return "not_clear" + end # 4. Grant the route to the train and mark it unavailable grant route_id to train_id diff --git a/server/src/interlocking.c b/server/src/interlocking.c index 66b0e0ab..e539a54b 100644 --- a/server/src/interlocking.c +++ b/server/src/interlocking.c @@ -34,140 +34,157 @@ #include "server.h" #include "parsers/interlocking_parser.h" - GHashTable *route_hash_table = NULL; GHashTable *route_string_to_ids_hashtable = NULL; -void free_interlocking_hashtable_key(void *pointer) { - char *key = (char *)pointer; - free(key); +static void free_interlocking_hashtable_key(void *pointer) { + if (pointer != NULL) { + free(pointer); + pointer = NULL; + } } -void free_interlocking_hashtable_value(void *pointer) { - GArray *value = (GArray *)pointer; - g_array_free(value, true); +static void free_interlocking_hashtable_value(void *pointer) { + // For use with route_string_to_ids_hashtable: + // Not necessary to free elements individually, as they are not allocated/owned here, + // they are owned by the route_hash_table instead. + GArray *value = (GArray *)pointer; + g_array_free(value, true); } -void create_interlocking_hashtable(void) { - route_string_to_ids_hashtable = g_hash_table_new_full(g_str_hash, g_str_equal, free_interlocking_hashtable_key, free_interlocking_hashtable_value); - - GHashTableIter iter; - gpointer key, value; - g_hash_table_iter_init (&iter, route_hash_table); - while (g_hash_table_iter_next (&iter, &key, &value)) { - t_interlocking_route *route = (t_interlocking_route *) value; - - // Build key, example: signal3signal6 - size_t len = strlen(route->source) + strlen(route->destination) + 1; - char *route_string = malloc(sizeof(char) * len); - if (route_string == NULL) { - syslog_server(LOG_ERR, - "Interlocking create interlocking hash table: " - "unable to allocate memory for route_string"); - return; - } - snprintf(route_string, len, "%s%s", route->source, route->destination); - - if (g_hash_table_contains(route_string_to_ids_hashtable, route_string)) { - void *route_ids_ptr = g_hash_table_lookup(route_string_to_ids_hashtable, route_string); - free(route_string); - GArray *route_ids = (GArray *) route_ids_ptr; - g_array_append_val(route_ids, route->id); - } else { - GArray *route_ids = g_array_sized_new(FALSE, FALSE, sizeof(size_t), 8); - g_array_append_val(route_ids, route->id); - g_hash_table_insert(route_string_to_ids_hashtable, route_string, route_ids); - } - } - - syslog_server(LOG_NOTICE, "Interlocking create hash table - done"); +// initialises the route_string_to_ids_hashtable hashtable. +static void create_route_str_to_ids_hashtable() { + route_string_to_ids_hashtable = + g_hash_table_new_full(g_str_hash, g_str_equal, + free_interlocking_hashtable_key, + free_interlocking_hashtable_value); + + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, route_hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + t_interlocking_route *route = (t_interlocking_route *) value; + + // Build search key, example: signal3signal6 + size_t len = strlen(route->source) + strlen(route->destination) + 1; + char *route_string = malloc(sizeof(char) * len); + if (route_string == NULL) { + syslog_server(LOG_ERR, + "Interlocking create interlocking hash table: " + "unable to allocate memory for route_string"); + return; + } + snprintf(route_string, len, "%s%s", route->source, route->destination); + + if (g_hash_table_contains(route_string_to_ids_hashtable, route_string)) { + // entry with route_string as key exists in hashtable + void *route_ids_ptr = g_hash_table_lookup(route_string_to_ids_hashtable, route_string); + free(route_string); + // Add the route to the hashtable entry's value/list of routes + GArray *route_ids = (GArray *) route_ids_ptr; + g_array_append_val(route_ids, route->id); + } else { + // entry with route_string as key does NOT exist in hashtable -> create new + GArray *route_ids = g_array_sized_new(FALSE, FALSE, sizeof(char *), 8); + g_array_append_val(route_ids, route->id); + // Ownership of key (route_string) is transferred to hashtable + g_hash_table_insert(route_string_to_ids_hashtable, route_string, route_ids); + } + } + + syslog_server(LOG_NOTICE, "Interlocking create hash table - done"); } bool interlocking_table_initialise(const char *config_dir) { - // Parse the interlocking table from the YAML file - route_hash_table = parse_interlocking_table(config_dir); - if (route_hash_table != NULL) { - create_interlocking_hashtable(); - return true; - } - - return false; + // Parse the interlocking table from the YAML file + route_hash_table = parse_interlocking_table(config_dir); + if (route_hash_table != NULL) { + create_route_str_to_ids_hashtable(); + return true; + } + + return false; } void free_interlocking_table(void) { - // free hash table - if (route_string_to_ids_hashtable != NULL) { - g_hash_table_destroy(route_string_to_ids_hashtable); - route_string_to_ids_hashtable = NULL; - } - - // free array - if (route_hash_table != NULL) { - g_hash_table_destroy(route_hash_table); - route_hash_table = NULL; - } - - syslog_server(LOG_NOTICE, "Interlocking table freed"); + // free route-string to route ids hash table + if (route_string_to_ids_hashtable != NULL) { + g_hash_table_destroy(route_string_to_ids_hashtable); + route_string_to_ids_hashtable = NULL; + } + syslog_server(LOG_INFO, "Interlocking extra table route str to route ids freed"); + + // free general route hash table + if (route_hash_table != NULL) { + g_hash_table_destroy(route_hash_table); + route_hash_table = NULL; + } + + syslog_server(LOG_NOTICE, "Interlocking table freed"); } -GArray *interlocking_table_get_all_route_ids(void) { - GArray* route_ids = g_array_new(FALSE, FALSE, sizeof(char *)); - - GHashTableIter iter; - gpointer key, value; - g_hash_table_iter_init (&iter, route_hash_table); - while (g_hash_table_iter_next (&iter, &key, &value)) { - t_interlocking_route *route = (t_interlocking_route *) value; - char *route_id_string = strdup(route->id); - if (route_id_string == NULL) { - syslog_server(LOG_ERR, - "Interlocking table get all route ids: " - "unable to allocate memory for route_id_string"); - g_array_free(route_ids, true); - return NULL; - } - g_array_append_val(route_ids, route_id_string); - } - - return route_ids; +GArray *interlocking_table_get_all_route_ids_shallowcpy(void) { + GArray* route_ids = g_array_new(FALSE, FALSE, sizeof(char *)); + + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, route_hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + t_interlocking_route *route = (t_interlocking_route *) value; + g_array_append_val(route_ids, route->id); + } + return route_ids; } -GArray *interlocking_table_get_route_ids(const char *source_id, const char *destination_id) { - size_t len = strlen(source_id) + strlen(destination_id) + 1; - char route_string[len]; - snprintf(route_string, len, "%s%s", source_id, destination_id); - - if (g_hash_table_contains(route_string_to_ids_hashtable, route_string)) { - return (GArray *)g_hash_table_lookup(route_string_to_ids_hashtable, route_string); - } - - return NULL; +const char *interlocking_table_get_route_id_of_train(const char *train_id) { + if (train_id == NULL) { + syslog_server(LOG_ERR, "Get route id of train: invalid (NULL) train_id"); + return NULL; + } + GHashTableIter iter; + gpointer key, value; + g_hash_table_iter_init (&iter, route_hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + const t_interlocking_route *route = (t_interlocking_route *) value; + if (route != NULL && route->train != NULL && strcmp(route->train, train_id) == 0) { + return route->id; + } + } + return NULL; } -int interlocking_table_get_route_id(const char *source_id, const char *destination_id) { - GArray *route_ids = interlocking_table_get_route_ids(source_id, destination_id); - - // Return first route - if (route_ids != NULL && route_ids->len > 0) { - char *id = g_array_index(route_ids, char *, 0); - return atoi(id); - } - - return -1; +GArray *interlocking_table_get_route_ids(const char *source_id, const char *destination_id) { + if (source_id == NULL || destination_id == NULL) { + syslog_server(LOG_ERR, + "Get route ids for source-dest combination: invalid (NULL) parameters"); + return NULL; + } + const size_t len = strlen(source_id) + strlen(destination_id) + 1; + char route_string[len]; + snprintf(route_string, len, "%s%s", source_id, destination_id); + + if (g_hash_table_contains(route_string_to_ids_hashtable, route_string)) { + return (GArray *)g_hash_table_lookup(route_string_to_ids_hashtable, route_string); + } + + return NULL; } t_interlocking_route *get_route(const char *route_id) { - if (g_hash_table_contains(route_hash_table, route_id)) { - return g_hash_table_lookup(route_hash_table, route_id); - } - - return NULL; + if (route_id == NULL) { + return NULL; + } + if (g_hash_table_contains(route_hash_table, route_id)) { + return g_hash_table_lookup(route_hash_table, route_id); + } + + return NULL; } unsigned int interlocking_table_get_size() { - if (route_hash_table != NULL) { - return g_hash_table_size(route_hash_table); - } - - return 0; + if (route_hash_table != NULL) { + return g_hash_table_size(route_hash_table); + } + + return 0; } diff --git a/server/src/interlocking.h b/server/src/interlocking.h index 355ad00c..204b7742 100644 --- a/server/src/interlocking.h +++ b/server/src/interlocking.h @@ -85,30 +85,36 @@ bool interlocking_table_initialise(const char *config_dir); void free_interlocking_table(void); /** - * Return all the route IDs in the interlocking table. + * Return all the route IDs in the interlocking table. + * The strings containing the route IDs are shallow copies of the ones in the interlocking table. + * That means, the caller has to free the GArray but not the contained strings! * - * @return array of route IDs. Caller is responsible for freeing the GArray + * @return array of route IDs. Caller is responsible for freeing the GArray, + * but not the contained strings(!) */ -GArray *interlocking_table_get_all_route_ids(void); +GArray *interlocking_table_get_all_route_ids_shallowcpy(void); /** - * Return the array of route ID for a given source and destination signal. + * Search for the first route granted to the specified train and return its id. + * The caller is NOT responsible for freeing the returned string. * - * @return array if it exists, otherwise NULL + * @param train_id + * @return int id of the first route found that is granted to train. + * NULL if no routes are granted to this train or the train is unknown/invalid. */ -GArray *interlocking_table_get_route_ids(const char *source_id, const char *destination_id); +const char *interlocking_table_get_route_id_of_train(const char *train_id); /** - * Return the first route between the source and destination signals. - * - * @param source_id - * @param destination_id - * @return + * Return the array of route IDs for a given source and destination signal. + * The caller is NOT responsible for freeing the array or its contents. + * + * @return array if it exists, otherwise NULL */ -int interlocking_table_get_route_id(const char *source_id, const char *destination_id); +GArray *interlocking_table_get_route_ids(const char *source_id, const char *destination_id); /** - * Return the route (pointer to a struct) for a given route_id + * Return (pointer to) the route for a given route_id. + * The caller is NOT responsible for freeing the memory pointed to by the route pointer. * * @param route_id route * @return the route pointer if it exists, otherwise NULL diff --git a/server/src/json_response_builder.c b/server/src/json_response_builder.c new file mode 100644 index 00000000..23d002e4 --- /dev/null +++ b/server/src/json_response_builder.c @@ -0,0 +1,204 @@ +/* + * + * Copyright (C) 2025 University of Bamberg, Software Technologies Research Group + * , + * + * This file is part of the SWTbahn command line interface (swtbahn-cli), which is + * a client-server application to interactively control a BiDiB model railway. + * + * swtbahn-cli is licensed under the GNU GENERAL PUBLIC LICENSE (Version 3), see + * the LICENSE file at the project's top-level directory for details or consult + * . + * + * swtbahn-cli is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or any later version. + * + * swtbahn-cli is a RESEARCH PROTOTYPE and distributed WITHOUT ANY WARRANTY, without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * The following people contributed to the conception and realization of the + * present swtbahn-cli (in alphabetic order by surname): + * + * - Bernhard Luedtke + * + */ + +#include "json_response_builder.h" + + +GString* append_field_str_value(GString *dest, const char *field, + const char *value_str, bool add_trailing_comma) { + if (dest == NULL || field == NULL || value_str == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": \"%s\"%s", + field, value_str, add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_str_value_from_int(GString *dest, const char *field, + int value_int, bool add_trailing_comma) { + if (dest == NULL || field == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": \"%d\"%s", + field, value_int, add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_literal_value_from_str(GString *dest, const char *field, + const char *value_str, bool add_trailing_comma) { + if (dest == NULL || field == NULL || value_str == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": %s%s", + field, value_str, add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_strlist_value(GString *dest, const char *field, + const char **value_liststr, unsigned int list_len, + bool add_trailing_comma) { + if (dest == NULL || field == NULL || value_liststr == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": [", field); + + for(unsigned int i = 0; i < list_len; ++i) { + const char * list_elem = value_liststr[i]; + if (list_elem != NULL) { + // append to json list and add "," if not last element + g_string_append_printf(dest, "\"%s\"%s", list_elem, (i+1 < list_len) ? ", " : ""); + } + } + g_string_append_printf(dest, "]%s", add_trailing_comma ? "," : ""); + + return dest; +} + +static GString* append_field_value_garray_strs_base(GString *dest, const char *field, + const GArray* g_strarray, + bool add_value_quote_marks, + bool add_trailing_comma) { + if (dest == NULL || field == NULL) { + return NULL; + } + if (g_strarray == NULL || g_strarray->len == 0) { + g_string_append_printf(dest, "\n\"%s\": []%s", field, add_trailing_comma ? "," : ""); + return dest; + } + const char *format_str_value_elem = add_value_quote_marks ? "\"%s\"%s" : "%s%s"; + + g_string_append_printf(dest, "\n\"%s\": [", field); + for (unsigned int i = 0; i < g_strarray->len; ++i) { + g_string_append_printf(dest, format_str_value_elem, + g_array_index(g_strarray, char *, i), + (i+1 < g_strarray->len) ? ", " : ""); + } + g_string_append_printf(dest, "]%s", add_trailing_comma ? "," : ""); + + return dest; +} + +GString* append_field_strlist_value_from_garray_strs(GString *dest, const char *field, + const GArray* g_strarray, + bool add_trailing_comma) { + return append_field_value_garray_strs_base(dest, field, g_strarray, true, add_trailing_comma); +} + +GString* append_field_literallist_value_from_garray_strs(GString *dest, const char *field, + const GArray* g_strarray, + bool add_trailing_comma) { + return append_field_value_garray_strs_base(dest, field, g_strarray, false, add_trailing_comma); +} + +GString* append_field_emptylist_value(GString *dest, const char *field, bool add_trailing_comma) { + if (dest == NULL || field == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": []%s", field, add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_bool_value(GString *dest, const char *field, bool value_bool, + bool add_trailing_comma) { + if (dest == NULL || field == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": %s%s", + field, value_bool ? "true" : "false", add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_int_value(GString *dest, const char *field, int value_int, + bool add_trailing_comma) { + if (dest == NULL || field == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": %d%s", + field, value_int, add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_uint_value(GString *dest, const char *field, unsigned int value_uint, + bool add_trailing_comma) { + if (dest == NULL || field == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": %u%s", + field, value_uint, add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_float_value(GString *dest, const char *field, + float value_float, bool add_trailing_comma) { + if (dest == NULL || field == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": %f%s", + field, value_float, add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_start_of_list(GString *dest, const char *field) { + if (dest == NULL || field == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": [", field); + return dest; +} + +GString* append_end_of_list(GString *dest, bool add_trailing_comma, bool with_prepend_newline) { + if (dest == NULL) { + return NULL; + } + g_string_append_printf(dest, "%s]%s", + with_prepend_newline ? "\n" : "", add_trailing_comma ? "," : ""); + return dest; +} + +GString* append_field_start_of_obj(GString *dest, const char *field) { + if (dest == NULL || field == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n\"%s\": {", field); + return dest; +} + +GString* append_start_of_obj(GString *dest, bool with_prepend_newline) { + if (dest == NULL) { + return NULL; + } + g_string_append_printf(dest, "%s{", with_prepend_newline ? "\n" : ""); + return dest; +} + +GString* append_end_of_obj(GString *dest, bool add_trailing_comma) { + if (dest == NULL) { + return NULL; + } + g_string_append_printf(dest, "\n}%s", add_trailing_comma ? "," : ""); + return dest; +} diff --git a/server/src/json_response_builder.h b/server/src/json_response_builder.h new file mode 100644 index 00000000..f8f65876 --- /dev/null +++ b/server/src/json_response_builder.h @@ -0,0 +1,248 @@ +/* + * + * Copyright (C) 2025 University of Bamberg, Software Technologies Research Group + * , + * + * This file is part of the SWTbahn command line interface (swtbahn-cli), which is + * a client-server application to interactively control a BiDiB model railway. + * + * swtbahn-cli is licensed under the GNU GENERAL PUBLIC LICENSE (Version 3), see + * the LICENSE file at the project's top-level directory for details or consult + * . + * + * swtbahn-cli is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or any later version. + * + * swtbahn-cli is a RESEARCH PROTOTYPE and distributed WITHOUT ANY WARRANTY, without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * The following people contributed to the conception and realization of the + * present swtbahn-cli (in alphabetic order by surname): + * + * - Bernhard Luedtke + * + */ + +#ifndef JSON_RESPONSE_BUILDER_H +#define JSON_RESPONSE_BUILDER_H + +#include +#include + +/** + * @brief Adds a json field with an identifier and a value, + * where the value is represented in json as a string. + * Example. Input: field=`mystr`, value_str=`helloworld`. + * Resulting addition to `dest`: `\n"mystr": "helloworld"`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param value_str string value of the json field + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_str_value(GString *dest, const char *field, const char *value_str, + bool add_trailing_comma); +/** + * @brief Adds a json field with an identifier and a string value (from int). + * Example. Input: field=`mystr`, value_int=`5`. + * Resulting addition to `dest`: `\n"mystr": "5"`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param value_int int value of the json field (will be added as string, i.e., enclosed by "") + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_str_value_from_int(GString *dest, const char *field, int value_int, + bool add_trailing_comma); + +/** + * @brief Adds a json field with an identifier and a value. + * Example. Input: field=`myval`, value_str=`somevaluehello`. + * Resulting addition to `dest`: `\n"myval": somevaluehello`. + * Note that the value is NOT surrounded by `"`s. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param value_str value of the json field (will not be enclosed in, e.g., quote marks) + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_literal_value_from_str(GString *dest, const char *field, const char *value_str, + bool add_trailing_comma); + +/** + * @brief Adds a json field with a list of strings as the value of the field. + * Example. Input: field=`mystrlist`, value_liststr=list with strings `hello`, `world`. + * Resulting addition to `dest`: `\n"mystrlist": ["hello", "world"]`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param value_liststr list of strings to add as the field value + * @param list_len length of the list of strings (i.e., length of value_liststr) + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_strlist_value(GString *dest, const char *field, const char **value_liststr, + unsigned int list_len, bool add_trailing_comma); + +/** + * @brief Adds a json field with a list of strings as the value of the field. + * Like append_field_strlist_value, but here with GArray* instead of raw array + length. + * See append_field_strlist_value for an example. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param g_strarray list of strings to add as the field value + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_strlist_value_from_garray_strs(GString *dest, const char *field, + const GArray* g_strarray, + bool add_trailing_comma); + +/** + * @brief Adds a json field with a list as the value of the field. + * Like append_field_strlist_value and append_field_strlist_value_from_garray_strs, + * but here the strings in the passed list are not enclosed in quote marks + * in the `dest` string. + * Example. Input: field=`mylist`, g_strarray=list with strings `hello`, `world`. + * Resulting addition to `dest`: `\n"mylist": [hello, world]`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param g_strarray list of strings to add as the field value (as literal values) + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_literallist_value_from_garray_strs(GString *dest, const char *field, + const GArray* g_strarray, + bool add_trailing_comma); + +/** + * @brief Adds a json field with an empty list as the value of the field. + * Example. Input: field=`mylist`. + * Resulting addition to `dest`: `\n"mylist": []`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_emptylist_value(GString *dest, const char *field, bool add_trailing_comma); + +/** + * @brief Adds a json field with a boolean value (true or false). + * Example. Input: field=`mybool`, value_bool=`true`. + * Resulting addition to `dest`: `\n"mybool": true`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param value_bool field value + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_bool_value(GString *dest, const char *field, bool value_bool, + bool add_trailing_comma); + +/** + * @brief Adds a json field with a number value (from int). + * Example. Input: field=`mynumber`, value_int=`5`. + * Resulting addition to `dest`: `\n"mynumber": 5`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param value_int field value + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_int_value(GString *dest, const char *field, int value_int, + bool add_trailing_comma); + +/** + * @brief Adds a json field with a number value (from unsigned int). + * Example. Input: field=`mynumber`, value_uint=`-5`. + * Resulting addition to `dest`: `\n"mynumber": -5`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param value_uint field value + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_uint_value(GString *dest, const char *field, unsigned int value_uint, + bool add_trailing_comma); + +/** + * @brief Adds a json field with a (real) number value (from float). + * Example. Input: field=`myreal`, value_float=`15.124`. + * Resulting addition to `dest`: `\n"myreal": 15.124`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @param value_float field value + * @param add_trailing_comma if true, adds comma after the field and value + * @return GString* modified "dest" string + */ +GString* append_field_float_value(GString *dest, const char *field, float value_float, + bool add_trailing_comma); + +/** + * @brief Adds a json field and starts a list for the value + * (i.e., value is incomplete in json terms, list needs to be closed later). + * Example. Input: field=`mylist`. + * Resulting addition to `dest`: `\n"mylist": [`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @return GString* modified "dest" string + */ +GString* append_field_start_of_list(GString *dest, const char *field); + +/** + * @brief Adds the end of a list marker (`]`). + * Example. Resulting Addition to `dest`: `]`. + * + * @param dest String to be added to + * @param add_trailing_comma if true, adds comma after the added `]` + * @param with_prepend_newline if true, adds a newline in front of the added `]` + * @return GString* modified "dest" string + */ +GString* append_end_of_list(GString *dest, bool add_trailing_comma, bool with_prepend_newline); + +/** + * @brief Adds a json field and starts an object for the value + * (i.e., value is incomplete in json terms, object needs to be closed later). + * Example. Input: field=`myobject`. + * Resulting addition to `dest`: `\n"myobject": {`. + * + * @param dest String to be added to + * @param field name of the json field to add + * @return GString* modified "dest" string + */ +GString* append_field_start_of_obj(GString *dest, const char *field); + +/** + * @brief Adds the start of an object marker (`{`). + * Example. Resulting Addition to `dest`: `{`. + * + * @param dest String to be added to + * @param with_prepend_newline if true, adds a newline in front of the added `{` + * @return GString* modified "dest" string + */ +GString* append_start_of_obj(GString *dest, bool with_prepend_newline); + +/** + * @brief Adds the end of an object marker (`}`). + * Example. Resulting Addition to `dest`: `}`. + * + * @param dest String to be added to + * @param add_trailing_comma if true, adds a comma after the added `}` + * @return GString* modified "dest" string + */ +GString* append_end_of_obj(GString *dest, bool add_trailing_comma); + +#endif // JSON_RESPONSE_BUILDER_H \ No newline at end of file diff --git a/server/src/param_verification.c b/server/src/param_verification.c index c0968ece..0262bfaf 100644 --- a/server/src/param_verification.c +++ b/server/src/param_verification.c @@ -35,7 +35,7 @@ int params_check_session_id(const char *data_session_id) { int client_session_id; char *end_client_session_id; if (data_session_id != NULL) { - client_session_id = strtol(data_session_id, &end_client_session_id, 10); + client_session_id = strtol(data_session_id, &end_client_session_id, 10); if (*end_client_session_id == '\0') { return client_session_id; } @@ -47,8 +47,8 @@ int params_check_grab_id(const char *data_grab_id, int max_trains) { int grab_id; char *end_grab_id; if (data_grab_id == NULL || - (grab_id = strtol(data_grab_id, &end_grab_id, 10)) < 0 || - grab_id >= max_trains || *end_grab_id != '\0') { + (grab_id = strtol(data_grab_id, &end_grab_id, 10)) < 0 || + grab_id >= max_trains || *end_grab_id != '\0') { return -1; } return grab_id; @@ -58,7 +58,7 @@ int params_check_speed(const char *data_speed) { int speed; char *end_speed; if (data_speed == NULL || (speed = strtol(data_speed, &end_speed, 10)) < -126 || - speed > 126 || *end_speed != '\0') { + speed > 126 || *end_speed != '\0') { return 999; } return speed; @@ -68,7 +68,7 @@ int params_check_calibrated_speed(const char *data_speed) { int speed; char *end_speed; if (data_speed == NULL || (speed = strtol(data_speed, &end_speed, 10)) < -9 || - speed > 9 || *end_speed != '\0') { + speed > 9 || *end_speed != '\0') { return 999; } return speed; @@ -78,7 +78,7 @@ int params_check_state(const char *data_state) { int state; char *end_state; if (data_state == NULL || (state = strtol(data_state, &end_state, 10)) < 0 || - state > 1 || *end_state != '\0') { + state > 1 || *end_state != '\0') { return -1; } return state; @@ -88,7 +88,7 @@ const char *params_check_route_id(const char *data_route_id) { int route_id; char *end_route_id; if (data_route_id == NULL || (route_id = strtol(data_route_id, &end_route_id, 10)) < 0 || - *end_route_id != '\0') { + *end_route_id != '\0') { return ""; } return data_route_id; @@ -116,7 +116,7 @@ bool params_check_is_bool_string(const char *string) { if (string == NULL || *string == '\0' || isspace(*string)) { return false; } else if (strcmp("false", string) == 0 || strcmp("False", string) == 0 || strcmp("FALSE", string) == 0 - || strcmp("true", string) == 0 || strcmp("True", string) == 0 || strcmp("TRUE", string) == 0) { + || strcmp("true", string) == 0 || strcmp("True", string) == 0 || strcmp("TRUE", string) == 0) { return true; } else { return false; diff --git a/server/src/parsers/config_data_parser.c b/server/src/parsers/config_data_parser.c index 753607c4..959b6e26 100644 --- a/server/src/parsers/config_data_parser.c +++ b/server/src/parsers/config_data_parser.c @@ -72,6 +72,8 @@ bool parse_config_data(const char *config_dir, t_config_data *config_data) { } void free_config_data(t_config_data config_data) { + syslog_server(LOG_NOTICE, "Config data freeing start"); + if (config_data.module_name != NULL) { free(config_data.module_name); config_data.module_name = NULL; diff --git a/server/src/parsers/extras_config_parser.c b/server/src/parsers/extras_config_parser.c index b4bd5c71..37239816 100644 --- a/server/src/parsers/extras_config_parser.c +++ b/server/src/parsers/extras_config_parser.c @@ -76,8 +76,8 @@ e_extras_sequence_level extras_sequence = EXTRAS_SEQ_NONE; void free_extras_id_key(void *pointer) { if (pointer != NULL) { - log_debug("free key: %s", (char *) pointer); free(pointer); + pointer = NULL; } } void free_block(void *pointer) { @@ -86,18 +86,15 @@ void free_block(void *pointer) { return; } if (block->id != NULL) { - log_debug("free block: %s", block->id); free(block->id); block->id = NULL; } if (block->direction != NULL) { - log_debug("\tfree block direction"); free(block->direction); block->direction = NULL; } if (block->main_segments != NULL) { - log_debug("\tfree block main segments"); for (int i = 0; i < block->main_segments->len; ++i) { free(g_array_index(block->main_segments, char *, i)); } @@ -105,7 +102,6 @@ void free_block(void *pointer) { } if (block->overlaps != NULL) { - log_debug("\tfree block overlaps"); for (int i = 0; i < block->overlaps->len; ++i) { free(g_array_index(block->overlaps, char *, i)); } @@ -113,7 +109,6 @@ void free_block(void *pointer) { } if (block->signals != NULL) { - log_debug("\tfree block signals"); for (int i = 0; i < block->signals->len; ++i) { free(g_array_index(block->signals, char *, i)); } @@ -121,7 +116,6 @@ void free_block(void *pointer) { } if (block->train_types != NULL) { - log_debug("\tfree block train types"); for (int i = 0; i < block->train_types->len; ++i) { free(g_array_index(block->train_types, char *, i)); } @@ -137,17 +131,14 @@ void free_reverser(void *pointer) { return; } if (reverser->id != NULL) { - log_debug("free reverser: %s", reverser->id); free(reverser->id); reverser->id = NULL; } if (reverser->board != NULL) { - log_debug("\tfree reverser board"); free(reverser->board); reverser->board = NULL; } if (reverser->block != NULL) { - log_debug("\tfree reverser block"); free(reverser->block); reverser->block = NULL; } @@ -160,12 +151,10 @@ void free_crossing(void *pointer) { return; } if (crossing->id != NULL) { - log_debug("free crossing: %s", crossing->id); free(crossing->id); crossing->id = NULL; } if (crossing->main_segment != NULL) { - log_debug("\tfree crossing main segment"); free(crossing->main_segment); crossing->main_segment = NULL; } @@ -178,17 +167,14 @@ void free_signal_type(void *pointer) { return; } if (signal_type->id != NULL) { - log_debug("free signal type: %s", signal_type->id); free(signal_type->id); signal_type->id = NULL; } if (signal_type->initial != NULL) { - log_debug("\tfree signal type initial"); free(signal_type->initial); signal_type->initial = NULL; } if (signal_type->aspects != NULL) { - log_debug("\tfree signal type aspects"); for (int i = 0; i < signal_type->aspects->len; ++i) { free(g_array_index(signal_type->aspects, char *, i)); } @@ -203,27 +189,22 @@ void free_composite_signal(void *pointer) { return; } if (composite_signal->id != NULL) { - log_debug("free composite signal: %s", composite_signal->id); free(composite_signal->id); composite_signal->id = NULL; } if (composite_signal->distant != NULL) { - log_debug("\tfree composite signal distant"); free(composite_signal->distant); composite_signal->distant = NULL; } if (composite_signal->entry != NULL) { - log_debug("\tfree composite signal entry"); free(composite_signal->entry); composite_signal->entry = NULL; } if (composite_signal->exit != NULL) { - log_debug("\tfree composite signal exit"); free(composite_signal->exit); composite_signal->exit = NULL; } if (composite_signal->block != NULL) { - log_debug("\tfree composite signal block"); free(composite_signal->block); composite_signal->block = NULL; } @@ -236,17 +217,14 @@ void free_peripheral_type(void *pointer) { return; } if (peripheral_type->id != NULL) { - log_debug("free peripheral type: %s", peripheral_type->id); free(peripheral_type->id); peripheral_type->id = NULL; } if (peripheral_type->initial != NULL) { - log_debug("\tfree peripheral type initial"); free(peripheral_type->initial); peripheral_type->initial = NULL; } if (peripheral_type->aspects != NULL) { - log_debug("\tfree peripheral type aspects"); for (int i = 0; i < peripheral_type->aspects->len; ++i) { free(g_array_index(peripheral_type->aspects, char *, i)); } @@ -265,7 +243,6 @@ void nullify_extras_config_tables(void) { } void extras_yaml_sequence_start(char *scalar) { - log_debug("extras_yaml_sequence_start: %s", scalar); switch (extras_mapping) { case EXTRAS_ROOT: if (str_equal(scalar, "blocks") || str_equal(scalar, "platforms")) { @@ -327,7 +304,6 @@ void extras_yaml_sequence_start(char *scalar) { } void extras_yaml_sequence_end(char *scalar) { - log_debug("extras_yaml_sequence_end: %s", scalar); // decrease sequence level switch (extras_sequence) { case BLOCKS: @@ -356,7 +332,6 @@ void extras_yaml_sequence_end(char *scalar) { } void extras_yaml_mapping_start(char *scalar) { - log_debug("extras_yaml_mapping_start: %s", scalar); switch (extras_sequence) { case BLOCKS: extras_mapping = BLOCK; @@ -435,8 +410,6 @@ void extras_yaml_mapping_start(char *scalar) { } void extras_yaml_mapping_end(char *scalar) { - log_debug("extras_yaml_mapping_end: %s", scalar); - // insert mapping to hash table switch (extras_mapping) { case BLOCK: @@ -496,11 +469,9 @@ void extras_yaml_scalar(char *last_scalar, char *cur_scalar) { g_array_append_val(cur_block->train_types, cur_scalar); return; } else if (extras_sequence == SIGNAL_TYPE_ASPECTS) { - log_debug("insert aspect to signal type: %s, %s", cur_signal_type->id, cur_scalar); g_array_append_val(cur_signal_type->aspects, cur_scalar); return; } else if (extras_sequence == PERIPHERAL_TYPE_ASPECTS) { - log_debug("insert aspect to peripheral type: %s, %s", cur_peripheral_type->id, cur_scalar); g_array_append_val(cur_peripheral_type->aspects, cur_scalar); return; } diff --git a/server/src/parsers/interlocking_parser.c b/server/src/parsers/interlocking_parser.c index ad6544b2..7aec8025 100644 --- a/server/src/parsers/interlocking_parser.c +++ b/server/src/parsers/interlocking_parser.c @@ -52,7 +52,7 @@ bool init_parser(const char *config_dir, const char *table_file, *fh = fopen(full_path, "r"); if (*fh == NULL) { - syslog_server(LOG_ERR, "%Interlocking parser: Failed to open %s", full_path); + syslog_server(LOG_ERR, "Interlocking parser: Failed to open %s", full_path); return false; } @@ -117,7 +117,10 @@ e_sequence_level decrease_sequence_level(e_sequence_level level) { } void free_route_key(void *pointer) { - free(pointer); + if (pointer != NULL) { + free(pointer); + pointer = NULL; + } } void free_route(void *item) { @@ -126,33 +129,27 @@ void free_route(void *item) { return; } if (route->id != NULL) { - log_debug("free route: %s", route->id); free(route->id); route->id = NULL; } if (route->source != NULL) { - log_debug("\tfree route source"); free(route->source); route->source = NULL; } if (route->destination != NULL) { - log_debug("\tfree route destination"); free(route->destination); route->destination = NULL; } if (route->orientation != NULL) { - log_debug("\tfree route orientation"); free(route->orientation); route->orientation = NULL; } if (route->train != NULL) { - log_debug("\tfree route train"); free(route->train); route->train = NULL; } if (route->path != NULL) { - log_debug("\tfree route path"); for (int i = 0; i < route->path->len; ++i) { free(g_array_index(route->path, char *, i)); } @@ -160,7 +157,6 @@ void free_route(void *item) { } if (route->sections != NULL) { - log_debug("\tfree route sections"); for (int i = 0; i < route->sections->len; ++i) { free(g_array_index(route->sections, char *, i)); } @@ -168,13 +164,11 @@ void free_route(void *item) { } if (route->points != NULL) { - log_debug("\tfree route points"); - //differently allocated than other g_arrays. + // differently allocated than other g_arrays. g_array_free(route->points, true); } if (route->signals != NULL) { - log_debug("\tfree route signals"); for (int i = 0; i < route->signals->len; ++i) { free(g_array_index(route->signals, char *, i)); } @@ -182,7 +176,6 @@ void free_route(void *item) { } if (route->conflicts != NULL) { - log_debug("\tfree route conflicts"); for (int i = 0; i < route->conflicts->len; ++i) { free(g_array_index(route->conflicts, char *, i)); } @@ -197,7 +190,6 @@ void free_interlocking_point(void *item) { return; } if (point->id != NULL) { - log_debug("free interlocking point: %s", point->id); free(point->id); point->id = NULL; } @@ -218,7 +210,7 @@ GHashTable *parse(yaml_parser_t *parser) { char *last_scalar = NULL; do { if (!yaml_parser_parse(parser, &event)) { - syslog_server(LOG_ERR, "Parser error %d\n", (*parser).error); + syslog_server(LOG_ERR, "Parser error %d", (*parser).error); break; } @@ -426,7 +418,7 @@ GHashTable *parse(yaml_parser_t *parser) { GHashTable *parse_interlocking_table(const char *config_dir) { if (config_dir == NULL) { syslog_server(LOG_ERR, "Interlocking parser: config directory is missing"); - return false; + return NULL; } // init @@ -434,7 +426,7 @@ GHashTable *parse_interlocking_table(const char *config_dir) { yaml_parser_t parser; if (!init_parser(config_dir, INTERLOCKING_TABLE_FILENAME, &fh, &parser)) { syslog_server(LOG_ERR, "Interlocking parser: Interlocking table file is missing"); - return false; + return NULL; } // parse @@ -446,7 +438,7 @@ GHashTable *parse_interlocking_table(const char *config_dir) { // success if (routes != NULL) { - syslog_server(LOG_INFO, "Interlocking parser: Interlocking table loaded successfully: %d routes", + syslog_server(LOG_INFO, "Interlocking parser: Interlocking table loaded successfully: %u routes", g_hash_table_size(routes)); return routes; } diff --git a/server/src/parsers/parser_util.c b/server/src/parsers/parser_util.c index 684b03e7..c64651d5 100644 --- a/server/src/parsers/parser_util.c +++ b/server/src/parsers/parser_util.c @@ -82,7 +82,7 @@ void parse_yaml_content(yaml_parser_t *parser, do { if (!yaml_parser_parse(parser, &event)) { - syslog_server(LOG_ERR, "Error parsing: %d\n", (*parser).error); + syslog_server(LOG_ERR, "Error parsing: %d", (*parser).error); break; } diff --git a/server/src/parsers/track_config_parser.c b/server/src/parsers/track_config_parser.c index 827a9305..20d6797e 100644 --- a/server/src/parsers/track_config_parser.c +++ b/server/src/parsers/track_config_parser.c @@ -66,8 +66,10 @@ e_track_mapping_level track_mapping = TRACK_ROOT; e_track_sequence_level track_sequence = TRACK_SEQ_NONE; void free_track_id_key(void *pointer) { - log_debug("free key: %s", (char *) pointer); - free(pointer); + if (pointer != NULL) { + free(pointer); + pointer = NULL; + } } void free_segment(void *pointer) { @@ -76,7 +78,6 @@ void free_segment(void *pointer) { return; } if (segment->id != NULL) { - log_debug("free segment: %s", segment->id); free(segment->id); segment->id = NULL; } @@ -89,24 +90,20 @@ void free_signal(void *pointer) { return; } if (signal->id != NULL) { - log_debug("free signal: %s", signal->id); free(signal->id); signal->id = NULL; } if (signal->aspects != NULL) { - log_debug("\tfree signal aspects"); for (int i = 0; i < signal->aspects->len; ++i) { free(g_array_index(signal->aspects, char *, i)); } g_array_free(signal->aspects, true); } if (signal->initial != NULL) { - log_debug("\tfree signal initial"); free(signal->initial); signal->initial = NULL; } if (signal->type != NULL) { - log_debug("\tfree signal type"); free(signal->type); signal->type = NULL; } @@ -120,27 +117,22 @@ void free_point(void *pointer) { return; } if (point->id != NULL) { - log_debug("free point: %s", point->id); free(point->id); point->id = NULL; } if (point->initial != NULL) { - log_debug("\tfree point initial"); free(point->initial); point->initial = NULL; } if (point->normal_aspect != NULL) { - log_debug("\tfree point normal aspect"); free(point->normal_aspect); point->normal_aspect = NULL; } if (point->reverse_aspect != NULL) { - log_debug("\tfree point reverse aspect"); free(point->reverse_aspect); point->reverse_aspect = NULL; } if (point->segment != NULL) { - log_debug("\tfree point segment"); free(point->segment); point->segment = NULL; } @@ -153,25 +145,21 @@ void free_peripheral(void *pointer) { return; } if (peripheral->id != NULL) { - log_debug("free peripheral: %s", peripheral->id); free(peripheral->id); peripheral->id = NULL; } if (peripheral->aspects != NULL) { - log_debug("\tfree peripheral aspects"); for (int i = 0; i < peripheral->aspects->len; ++i) { free(g_array_index(peripheral->aspects, char *, i)); } g_array_free(peripheral->aspects, true); } if (peripheral->initial != NULL) { - log_debug("\tfree peripheral initial"); free(peripheral->initial); peripheral->initial = NULL; } if (peripheral->type != NULL) { - log_debug("\tfree peripheral type"); free(peripheral->type); peripheral->type = NULL; } diff --git a/server/src/parsers/train_config_parser.c b/server/src/parsers/train_config_parser.c index 706bf099..ada6c0b0 100644 --- a/server/src/parsers/train_config_parser.c +++ b/server/src/parsers/train_config_parser.c @@ -47,8 +47,10 @@ e_train_mapping_level train_mapping = TRAIN_ROOT; e_train_sequence_level train_sequence = TRAIN_SEQ_NONE; void free_train_id_key(void *pointer) { - log_debug("free key: %s", (char *) pointer); - free(pointer); + if (pointer != NULL) { + free(pointer); + pointer = NULL; + } } void free_train(void *pointer) { @@ -57,24 +59,20 @@ void free_train(void *pointer) { return; } if (train->id != NULL) { - log_debug("free train: %s", train->id); free(train->id); train->id = NULL; } if (train->type != NULL) { - log_debug("\tfree train type"); free(train->type); train->type = NULL; } if (train->peripherals != NULL) { - log_debug("\tfree train peripherals"); for (int i = 0; i < train->peripherals->len; ++i) { free(g_array_index(train->peripherals, char *, i)); } g_array_free(train->peripherals, true); } if (train->calibration != NULL) { - log_debug("\tfree train calibration"); g_array_free(train->calibration, true); } free(train); @@ -84,8 +82,7 @@ void nullify_train_config_table(void) { tb_trains = NULL; } -void train_yaml_sequence_start(char *scalar) { - log_debug("train_yaml_sequence_start: %s", scalar); +void train_yaml_sequence_start(char *scalar) { if (train_mapping == TRAIN_ROOT && str_equal(scalar, "trains")) { train_sequence = TRAINS; if (tb_trains == NULL) { @@ -104,7 +101,6 @@ void train_yaml_sequence_start(char *scalar) { } void train_yaml_sequence_end(char *scalar) { - log_debug("train_yaml_sequence_end: %s", scalar); switch (train_sequence) { case PERIPHERALS: case CALIBRATIONS: @@ -115,7 +111,6 @@ void train_yaml_sequence_end(char *scalar) { } } void train_yaml_mapping_start(char *scalar) { - log_debug("train_yaml_mapping_start: %s", scalar); switch (train_sequence) { case TRAINS: train_mapping = TRAIN; @@ -138,8 +133,6 @@ void train_yaml_mapping_start(char *scalar) { } void train_yaml_mapping_end(char *scalar) { - log_debug("train_yaml_mapping_end: %s", scalar); - // insert mapping to hash table if (train_mapping == TRAIN) { log_debug("train_yaml_mapping_end: insert train: %s", cur_train->id); @@ -160,7 +153,6 @@ void train_yaml_mapping_end(char *scalar) { } void train_yaml_scalar(char *last_scalar, char *cur_scalar) { - if (train_sequence == CALIBRATIONS) { int cal = (int)strtol(cur_scalar, NULL, 10); g_array_append_val(cur_train->calibration, cal); diff --git a/server/src/server.c b/server/src/server.c index 47dc233a..3d12b056 100644 --- a/server/src/server.c +++ b/server/src/server.c @@ -101,17 +101,17 @@ static onion_connection_status handler_assets(void *_, onion_request *req, onion GString *full_filename = g_string_new(global_path); onion_low_free(global_path); g_string_append(full_filename, filename); - + onion_connection_status status = - onion_shortcut_response_file(full_filename->str, req, res); - g_string_free(full_filename, TRUE); + onion_shortcut_response_file(full_filename->str, req, res); + g_string_free(full_filename, true); return status; } static int eval_args(int argc, char **argv) { if (argc == 5) { if (strnlen(argv[1], INPUT_MAX_LEN + 1) == INPUT_MAX_LEN + 1 || - strnlen(argv[2], INPUT_MAX_LEN + 1) == INPUT_MAX_LEN + 1) { + strnlen(argv[2], INPUT_MAX_LEN + 1) == INPUT_MAX_LEN + 1) { printf("Serial device and config directory must not exceed %d characters\n", INPUT_MAX_LEN); return 1; @@ -128,7 +128,7 @@ static int eval_args(int argc, char **argv) { } } else { printf("Four arguments expected: " - " \n"); + " \n"); return 1; } } @@ -137,7 +137,7 @@ int main(int argc, char **argv) { if (eval_args(argc, argv)) { return 1; } - + openlog("swtbahn", 0, LOG_LOCAL0); syslog_server(LOG_NOTICE, "SWTbahn server started"); ///TODO: Consider making configurable a max_thread count to limit @@ -152,7 +152,7 @@ int main(int argc, char **argv) { // --- home page --- onion_url_add_with_data(urls, "", onion_shortcut_internal_redirect, "assets/index.html", NULL); - + // --- admin functions --- onion_url_add(urls, "admin/startup", handler_startup); onion_url_add(urls, "admin/shutdown", handler_shutdown); @@ -173,32 +173,36 @@ int main(int argc, char **argv) { // --- train driver functions --- onion_url_add(urls, "driver/grab-train", handler_grab_train); - onion_url_add(urls, "driver/release-train", handler_release_train); + onion_url_add(urls, "driver/release-train", handler_release_train); onion_url_add(urls, "driver/request-route", handler_request_route); - onion_url_add(urls, "driver/request-route-id", handler_request_route_id); + /// NOTE: Changed path from request-route-id to request-route-by-id + onion_url_add(urls, "driver/request-route-by-id", handler_request_route_by_id); onion_url_add(urls, "driver/direction", handler_driving_direction); onion_url_add(urls, "driver/drive-route", handler_drive_route); onion_url_add(urls, "driver/set-dcc-train-speed", handler_set_dcc_train_speed); onion_url_add(urls, "driver/set-calibrated-train-speed", handler_set_calibrated_train_speed); onion_url_add(urls, "driver/set-train-emergency-stop", handler_set_train_emergency_stop); onion_url_add(urls, "driver/set-train-peripheral", handler_set_train_peripheral); - + // --- upload functions --- onion_url_add(urls, "upload/engine", handler_upload_engine); - onion_url_add(urls, "upload/refresh-engines", handler_get_engines); onion_url_add(urls, "upload/remove-engine", handler_remove_engine); onion_url_add(urls, "upload/interlocker", handler_upload_interlocker); - onion_url_add(urls, "upload/refresh-interlockers", handler_get_interlockers); onion_url_add(urls, "upload/remove-interlocker", handler_remove_interlocker); - + // --- monitor functions --- onion_url_add(urls, "monitor/platform-name", handler_get_platform_name); onion_url_add(urls, "monitor/trains", handler_get_trains); onion_url_add(urls, "monitor/train-state", handler_get_train_state); + onion_url_add(urls, "monitor/train-states", handler_get_train_states); onion_url_add(urls, "monitor/train-peripherals", handler_get_train_peripherals); + onion_url_add(urls, "monitor/engines", handler_get_engines); + onion_url_add(urls, "monitor/interlockers", handler_get_interlockers); onion_url_add(urls, "monitor/track-outputs", handler_get_track_outputs); onion_url_add(urls, "monitor/points", handler_get_points); onion_url_add(urls, "monitor/signals", handler_get_signals); + onion_url_add(urls, "monitor/point-details", handler_get_point_details); + onion_url_add(urls, "monitor/signal-details", handler_get_signal_details); onion_url_add(urls, "monitor/point-aspects", handler_get_point_aspects); onion_url_add(urls, "monitor/signal-aspects", handler_get_signal_aspects); onion_url_add(urls, "monitor/segments", handler_get_segments); @@ -209,10 +213,11 @@ int main(int argc, char **argv) { onion_url_add(urls, "monitor/granted-routes", handler_get_granted_routes); onion_url_add(urls, "monitor/route", handler_get_route); onion_url_add(urls, "monitor/debug", handler_get_debug_info); - onion_url_add(urls, "monitor/debug_extra", handler_get_debug_info_extra); + /// NOTE: Changed path from debug_extra to debug-extra + onion_url_add(urls, "monitor/debug-extra", handler_get_debug_info_extra); load_cached_verifier_url(); - + onion_listen(o); onion_free(o); if (running) { @@ -221,9 +226,9 @@ int main(int argc, char **argv) { cache_verifier_url(); free_verifier_url(); - syslog_server(LOG_NOTICE, "%s", "SWTbahn server stopped"); + syslog_server(LOG_NOTICE, "SWTbahn server stopped"); closelog(); - + return 0; } diff --git a/server/src/websocket_uploader/engine_uploader.c b/server/src/websocket_uploader/engine_uploader.c index d9a4708b..a10ca7f7 100644 --- a/server/src/websocket_uploader/engine_uploader.c +++ b/server/src/websocket_uploader/engine_uploader.c @@ -35,11 +35,12 @@ typedef struct { bool started; bool finished; bool success; + bool message_is_json_str; GString* file_path; GString* message; } ws_verif_data; - +///TODO: Protect against concur access with a mutex char *verifier_url = NULL; static const char cache_file_verifier_url[] = "verifier_url_cache.txt"; @@ -197,6 +198,7 @@ void process_verification_result_msg(struct mg_ws_message *ws_msg, ws_verif_data "engine does not satisfy all its properties"); ws_data_ptr->message = g_string_new(""); g_string_append_printf(ws_data_ptr->message,"%s", ws_msg->data.ptr); + ws_data_ptr->message_is_json_str = true; } ws_data_ptr->success = false; ws_data_ptr->finished = true; @@ -303,7 +305,7 @@ void websocket_verification_callback(struct mg_connection *ws_connection, verif_result verify_engine_model(const char* f_filepath) { struct mg_mgr event_manager; - ws_verif_data ws_verif_data = {false, false, false, g_string_new(f_filepath), NULL}; + ws_verif_data ws_verif_data = {false, false, false, false, g_string_new(f_filepath), NULL}; if (verifier_url == NULL) { syslog_server(LOG_ERR, @@ -311,6 +313,7 @@ verif_result verify_engine_model(const char* f_filepath) { "no verifier URL has been set, abort"); verif_result result_data; result_data.success = false; + result_data.message_is_json_str = false; result_data.message = g_string_new("No verifier server URL has been set, " "thus no verification was possible"); return result_data; @@ -337,7 +340,7 @@ verif_result verify_engine_model(const char* f_filepath) { ws_verif_data.success = false; syslog_server(LOG_WARNING, "Websocket engine uploader: Verify engine model - " - "verification did not start within %d ms, abort", + "verification did not start within %u ms, abort", (poll_counter * websocket_single_poll_length_ms)); } } @@ -353,6 +356,7 @@ verif_result verify_engine_model(const char* f_filepath) { mg_mgr_free(&event_manager); verif_result result_data; result_data.success = ws_verif_data.success; + result_data.message_is_json_str = ws_verif_data.message_is_json_str; result_data.message = ws_verif_data.message; // Free string allocated for filepath of model file @@ -371,12 +375,12 @@ void set_verifier_url(const char *upd_verifier_url) { verifier_url = NULL; } verifier_url = strdup(upd_verifier_url); - syslog_server(LOG_NOTICE, "Set verifier URL - verifier URL set to: %s", verifier_url); + syslog_server(LOG_NOTICE, "Set verifier URL - verifier URL set to: %s", verifier_url); } const char * get_verifier_url() { - return verifier_url; + return verifier_url; } @@ -412,7 +416,7 @@ void load_cached_verifier_url() { "Load cached verifier URL - loaded URL %s from cache", verifier_url); } else { - syslog_server(LOG_NOTICE, "Load cached verifier URL - no content in cache file"); + syslog_server(LOG_NOTICE, "Load cached verifier URL - no content in cache file"); } } else { syslog_server(LOG_NOTICE, @@ -434,7 +438,7 @@ void cache_verifier_url() { syslog_server(LOG_ERR, "Cache verifier URL - cache file opening failed"); return; } - + // Write the content to the file fputs(verifier_url, file); syslog_server(LOG_INFO, "Cache verifier URL - cached URL %s", verifier_url); diff --git a/server/src/websocket_uploader/engine_uploader.h b/server/src/websocket_uploader/engine_uploader.h index 94c4642c..9f91c6f8 100644 --- a/server/src/websocket_uploader/engine_uploader.h +++ b/server/src/websocket_uploader/engine_uploader.h @@ -33,6 +33,7 @@ typedef struct { bool success; + bool message_is_json_str; GString* message; } verif_result; diff --git a/server/test/unit/server_bahn_util_tests.c b/server/test/unit/server_bahn_util_tests.c index 60fb93fe..962cf719 100644 --- a/server/test/unit/server_bahn_util_tests.c +++ b/server/test/unit/server_bahn_util_tests.c @@ -37,10 +37,12 @@ static const char *config_directory = "../../configurations/swtbahn-full/"; static void test_setup(void) { + // Probably not necessary as no test calls track_state_get_value -> investigate bahn_data_util_init_cached_track_state(); } static void test_teardown(void) { + // Probably not necessary as no test calls track_state_get_value -> investigate bahn_data_util_free_cached_track_state(); } diff --git a/server/test/unit/server_parser_tests.c b/server/test/unit/server_parser_tests.c index 33c4569b..80df076f 100644 --- a/server/test/unit/server_parser_tests.c +++ b/server/test/unit/server_parser_tests.c @@ -37,10 +37,12 @@ static const char *config_directory = "../../configurations/swtbahn-full/"; static void test_setup(void) { + // Probably not necessary as no test calls track_state_get_value -> investigate bahn_data_util_init_cached_track_state(); } static void test_teardown(void) { + // Probably not necessary as no test calls track_state_get_value -> investigate bahn_data_util_free_cached_track_state(); }