| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| #include <plc4c/driver_simulated.h> |
| #include <plc4c/plc4c.h> |
| #include <plc4c/spi/types_private.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| enum plc4c_driver_simulated_field_type_t { RANDOM, STATE, STDOUT }; |
| typedef enum plc4c_driver_simulated_field_type_t |
| plc4c_driver_simulated_field_type; |
| |
| typedef enum plc4c_driver_simulated_field_datatype_t |
| plc4c_driver_simulated_field_datatype; |
| |
| // State definitions |
| enum plc4c_driver_simulated_disconnect_states { |
| PLC4C_DRIVER_SIMULATED_DISCONNECT_INIT, |
| PLC4C_DRIVER_SIMULATED_DISCONNECT_WAIT_TASKS_FINISHED, |
| PLC4C_DRIVER_SIMULATED_DISCONNECT_FINISHED |
| }; |
| |
| enum read_states { |
| PLC4C_DRIVER_SIMULATED_READ_INIT, |
| PLC4C_DRIVER_SIMULATED_READ_FINISHED |
| }; |
| |
| enum write_states { |
| PLC4C_DRIVER_SIMULATED_WRITE_INIT, |
| PLC4C_DRIVER_SIMULATED_WRITE_FINISHED |
| }; |
| |
| struct plc4c_driver_simulated_item_t { |
| char *name; |
| plc4c_driver_simulated_field_type type; |
| plc4c_data_type data_type; |
| int num_elements; |
| }; |
| typedef struct plc4c_driver_simulated_item_t plc4c_driver_simulated_item; |
| |
| plc4c_return_code plc4c_driver_simulated_connect_machine_function( |
| plc4c_system_task *task) { |
| plc4c_connection *connection = task->context; |
| if (connection == NULL) { |
| return INTERNAL_ERROR; |
| } |
| if (plc4c_connection_get_connected(connection)) { |
| return ALREADY_CONNECTED; |
| } |
| plc4c_connection_set_connected(connection, true); |
| task->completed = true; |
| return OK; |
| } |
| |
| plc4c_return_code plc4c_driver_simulated_disconnect_machine_function( |
| plc4c_system_task *task) { |
| plc4c_connection *connection = task->context; |
| if (connection == NULL) { |
| return INTERNAL_ERROR; |
| } |
| |
| switch (task->state_id) { |
| case PLC4C_DRIVER_SIMULATED_DISCONNECT_INIT: { |
| plc4c_connection_set_disconnect(connection, true); |
| task->state_id = PLC4C_DRIVER_SIMULATED_DISCONNECT_WAIT_TASKS_FINISHED; |
| break; |
| } |
| case PLC4C_DRIVER_SIMULATED_DISCONNECT_WAIT_TASKS_FINISHED: { |
| // The disconnect system-task also counts. |
| if (plc4c_connection_get_running_tasks_count(connection) == 1) { |
| plc4c_connection_set_connected(connection, false); |
| task->completed = true; |
| task->state_id = PLC4C_DRIVER_SIMULATED_DISCONNECT_FINISHED; |
| } |
| break; |
| } |
| case PLC4C_DRIVER_SIMULATED_DISCONNECT_FINISHED: { |
| // Do nothing |
| break; |
| } |
| default: { |
| return INTERNAL_ERROR; |
| } |
| } |
| return OK; |
| } |
| |
| plc4c_return_code plc4c_driver_simulated_read_machine_function( |
| plc4c_system_task *task) { |
| if (task->context == NULL) { |
| return INTERNAL_ERROR; |
| } |
| |
| plc4c_read_request_execution *read_request_execution = task->context; |
| plc4c_read_request *read_request = read_request_execution->read_request; |
| switch (task->state_id) { |
| case PLC4C_DRIVER_SIMULATED_READ_INIT: { |
| // Create a response. |
| plc4c_read_response *read_response = malloc(sizeof(plc4c_read_response)); |
| read_response->read_request = read_request; |
| plc4c_utils_list_create(&(read_response->items)); |
| |
| // Process every field in the request. |
| plc4c_list_element *cur_element = |
| plc4c_utils_list_head(read_request->items); |
| while (cur_element != NULL) { |
| plc4c_driver_simulated_item *cur_item = cur_element->value; |
| plc4c_response_value_item *value_item = |
| malloc(sizeof(plc4c_response_value_item)); |
| value_item->item = (plc4c_item *)cur_item; |
| value_item->response_code = PLC4C_RESPONSE_CODE_OK; |
| /* |
| * create the plc4c_data |
| * if this were a custom type we could set a custom destroy method |
| * we can also set a custom printf method |
| * right , now just create a new random value |
| */ |
| value_item->value = plc4c_data_create_uint32_t_data(rand()); |
| |
| // Add the value to the response. |
| plc4c_utils_list_insert_tail_value(read_response->items, value_item); |
| cur_element = cur_element->next; |
| } |
| |
| read_request_execution->read_response = read_response; |
| task->state_id = PLC4C_DRIVER_SIMULATED_READ_FINISHED; |
| task->completed = true; |
| break; |
| } |
| case PLC4C_DRIVER_SIMULATED_READ_FINISHED: { |
| // Do nothing |
| break; |
| } |
| default: { |
| return INTERNAL_ERROR; |
| } |
| } |
| return OK; |
| } |
| |
| plc4c_return_code plc4c_driver_simulated_write_machine_function( |
| plc4c_system_task *task) { |
| if (task->context == NULL) { |
| return INTERNAL_ERROR; |
| } |
| |
| plc4c_write_request_execution *write_request_execution = task->context; |
| plc4c_write_request *write_request = write_request_execution->write_request; |
| switch (task->state_id) { |
| case PLC4C_DRIVER_SIMULATED_WRITE_INIT: { |
| // Create a response. |
| plc4c_write_response *write_response = |
| malloc(sizeof(plc4c_write_response)); |
| write_response->write_request = write_request; |
| plc4c_utils_list_create(&(write_response->response_items)); |
| |
| // Process every field in the request. |
| plc4c_list_element *cur_element = |
| plc4c_utils_list_head(write_request->items); |
| while (cur_element != NULL) { |
| plc4c_request_value_item *cur_value_item = cur_element->value; |
| plc4c_driver_simulated_item *cur_item = |
| (plc4c_driver_simulated_item *)cur_value_item->item; |
| plc4c_data *write_data = cur_value_item->value; |
| plc4c_response_code response_code = -1; |
| switch (cur_item->type) { |
| case STDOUT: { |
| printf(""); |
| if (cur_item->data_type != write_data->data_type) { |
| printf( |
| "--> Simulated Driver Write: Value is %s but Item type is %s", |
| plc4c_data_type_name(write_data->data_type), |
| plc4c_data_type_name(cur_item->data_type)); |
| response_code = PLC4C_RESPONSE_CODE_INVALID_DATATYPE; |
| break; |
| } |
| printf("--> Simulated Driver Write: Value (%s) %s: ", |
| plc4c_data_type_name(write_data->data_type), cur_item->name); |
| plc4c_data_printf(write_data); |
| printf("\n"); |
| response_code = PLC4C_RESPONSE_CODE_OK; |
| break; |
| } |
| default: { |
| response_code = PLC4C_RESPONSE_CODE_INVALID_ADDRESS; |
| break; |
| } |
| } |
| |
| // Create a response element and add that to the response ... |
| plc4c_response_item *response_item = |
| malloc(sizeof(plc4c_response_item)); |
| response_item->item = (plc4c_item *)cur_item; |
| response_item->response_code = response_code; |
| plc4c_utils_list_insert_tail_value(write_response->response_items, |
| response_item); |
| |
| cur_element = cur_element->next; |
| } |
| |
| write_request_execution->write_response = write_response; |
| task->state_id = PLC4C_DRIVER_SIMULATED_WRITE_FINISHED; |
| task->completed = true; |
| break; |
| } |
| case PLC4C_DRIVER_SIMULATED_WRITE_FINISHED: { |
| // Do nothing |
| break; |
| } |
| default: { |
| return INTERNAL_ERROR; |
| } |
| } |
| return OK; |
| } |
| |
| plc4c_item *plc4c_driver_simulated_parse_address(char *address_string) { |
| plc4c_driver_simulated_field_type type = RANDOM; |
| char *name = NULL; |
| plc4c_data_type data_type = -1; |
| int num_elements = 0; |
| int start_segment_index = 0; |
| char *start_segment = address_string; |
| for (int i = 0; i <= strlen(address_string); i++) { |
| // This marks the end of the type part. |
| if (*(address_string + i) == '/') { |
| char *type_str = malloc(sizeof(char) * (i + 1)); |
| strncpy(type_str, start_segment, i); |
| if (strcmp(type_str, "RANDOM") == 0) { |
| type = RANDOM; |
| } else if (strcmp(type_str, "STATE") == 0) { |
| type = STATE; |
| } else if (strcmp(type_str, "STDOUT") == 0) { |
| type = STDOUT; |
| } else { |
| return NULL; |
| } |
| start_segment = address_string + i + 1; |
| start_segment_index = i + 1; |
| } |
| // This marks the end of the name part. |
| if (*(address_string + i) == ':') { |
| name = malloc(sizeof(char) * ((i - start_segment_index) + 1)); |
| strncpy(name, start_segment, (i - start_segment_index)); |
| start_segment = address_string + i + 1; |
| start_segment_index = i + 1; |
| } |
| // This marks the end of the data-type part if there is a size coming in |
| // addition. |
| if ((i == strlen(address_string)) || (*(address_string + i) == '[')) { |
| char *datatype_name = |
| malloc(sizeof(char) * ((i - start_segment_index) + 1)); |
| strncpy(datatype_name, start_segment, (i - start_segment_index)); |
| |
| // Translate the string into a constant. |
| if (strcmp(datatype_name, "INTEGER") == 0) { |
| data_type = PLC4C_INT; |
| } else if (strcmp(datatype_name, "STRING") == 0) { |
| data_type = PLC4C_CONSTANT_STRING; |
| } else { |
| return NULL; |
| } |
| |
| start_segment = address_string + i + 1; |
| start_segment_index = i + 1; |
| if (i == strlen(address_string)) { |
| num_elements = 1; |
| break; |
| } |
| } |
| // This marks the end of the size part. |
| if (*(address_string + i) == ']') { |
| char *num_elements_str = |
| malloc(sizeof(char) * ((i - start_segment_index) + 1)); |
| strncpy(num_elements_str, start_segment, (i - start_segment_index)); |
| char *success = NULL; |
| num_elements = (int)strtol(num_elements_str, &success, 10); |
| break; |
| } |
| } |
| |
| // Create a new driver specific item. |
| plc4c_driver_simulated_item *item = (plc4c_driver_simulated_item *)malloc( |
| sizeof(plc4c_driver_simulated_item)); |
| item->type = type; |
| item->name = name; |
| item->data_type = data_type; |
| item->num_elements = num_elements; |
| |
| return (plc4c_item *)item; |
| } |
| |
| plc4c_return_code plc4c_driver_simulated_connect_function( |
| plc4c_connection *connection, plc4c_system_task **task) { |
| plc4c_system_task *new_task = malloc(sizeof(plc4c_system_task)); |
| // There's nothing to do here, so no need for a state-machine. |
| new_task->state_id = -1; |
| new_task->state_machine_function = |
| &plc4c_driver_simulated_connect_machine_function; |
| new_task->completed = false; |
| new_task->context = connection; |
| new_task->connection = connection; |
| *task = new_task; |
| return OK; |
| } |
| |
| plc4c_return_code plc4c_driver_simulated_disconnect_function( |
| plc4c_connection *connection, plc4c_system_task **task) { |
| plc4c_system_task *new_task = malloc(sizeof(plc4c_system_task)); |
| new_task->state_id = PLC4C_DRIVER_SIMULATED_DISCONNECT_INIT; |
| new_task->state_machine_function = |
| &plc4c_driver_simulated_disconnect_machine_function; |
| new_task->completed = false; |
| new_task->context = connection; |
| new_task->connection = connection; |
| *task = new_task; |
| return OK; |
| } |
| |
| plc4c_return_code plc4c_driver_simulated_read_function( |
| plc4c_read_request_execution *read_request_execution, |
| plc4c_system_task **task) { |
| plc4c_system_task *new_task = malloc(sizeof(plc4c_system_task)); |
| new_task->state_id = PLC4C_DRIVER_SIMULATED_READ_INIT; |
| new_task->state_machine_function = |
| &plc4c_driver_simulated_read_machine_function; |
| new_task->completed = false; |
| new_task->context = read_request_execution; |
| new_task->connection = read_request_execution->read_request->connection; |
| |
| read_request_execution->system_task = new_task; |
| |
| *task = new_task; |
| return OK; |
| } |
| |
| plc4c_return_code plc4c_driver_simulated_write_function( |
| plc4c_write_request_execution *write_request_execution, |
| plc4c_system_task **task) { |
| plc4c_system_task *new_task = malloc(sizeof(plc4c_system_task)); |
| new_task->state_id = PLC4C_DRIVER_SIMULATED_WRITE_INIT; |
| new_task->state_machine_function = |
| &plc4c_driver_simulated_write_machine_function; |
| new_task->completed = false; |
| new_task->context = write_request_execution; |
| new_task->connection = write_request_execution->write_request->connection; |
| |
| write_request_execution->system_task = new_task; |
| |
| *task = new_task; |
| return OK; |
| } |
| |
| void plc4c_driver_simulated_free_read_response_item( |
| plc4c_list_element *read_item_element) { |
| plc4c_response_value_item *value_item = |
| (plc4c_response_value_item *)read_item_element->value; |
| plc4c_data_destroy(value_item->value); |
| value_item->value = NULL; |
| } |
| |
| void plc4c_driver_simulated_free_read_response(plc4c_read_response *response) { |
| // the request will be cleaned up elsewhere |
| plc4c_utils_list_delete_elements(response->items, |
| &plc4c_driver_simulated_free_read_response_item); |
| } |
| |
| void plc4c_driver_simulated_free_write_response_item( |
| plc4c_list_element *write_item_element) { |
| plc4c_response_value_item *value_item = |
| (plc4c_response_value_item *)write_item_element->value; |
| // do not delete the plc4c_item |
| // we also, in THIS case don't delete the random value which isn't really |
| // a pointer |
| // free(value_item->value); |
| value_item->value = NULL; |
| } |
| |
| void plc4c_driver_simulated_free_write_response( |
| plc4c_write_response *response) { |
| // the request will be cleaned up elsewhere |
| plc4c_utils_list_delete_elements(response->response_items, |
| &plc4c_driver_simulated_free_write_response_item); |
| } |
| |
| plc4c_driver *plc4c_driver_simulated_create() { |
| plc4c_driver *driver = (plc4c_driver *)malloc(sizeof(plc4c_driver)); |
| driver->protocol_code = "simulated"; |
| driver->protocol_name = "Simulated PLC4X Datasource"; |
| driver->default_transport_code = "dummy"; |
| driver->parse_address_function = &plc4c_driver_simulated_parse_address; |
| driver->connect_function = &plc4c_driver_simulated_connect_function; |
| driver->disconnect_function = &plc4c_driver_simulated_disconnect_function; |
| driver->read_function = &plc4c_driver_simulated_read_function; |
| driver->write_function = &plc4c_driver_simulated_write_function; |
| // TODO: Implement ... |
| driver->subscribe_function = NULL; |
| // TODO: Implement ... |
| driver->unsubscribe_function = NULL; |
| driver->free_read_response_function = |
| &plc4c_driver_simulated_free_read_response; |
| driver->free_write_response_function = |
| &plc4c_driver_simulated_free_write_response; |
| // TODO: Implement ... |
| driver->free_subscription_response_function = NULL; |
| // TODO: Implement ... |
| driver->free_unsubscription_response_function = NULL; |
| return driver; |
| } |