From f5a3f5e6e99215d53f36484030aba64efcda5ea9 Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Mon, 21 Oct 2019 17:08:00 +0200 Subject: [PATCH 1/6] added elm327 plugin - plugin for communicating with an ELM327 - supports event channels for monitoring engine rpm, engine load, coolant temp, vehicle speed & throttle position - not tested right now, may not work at all - the serial device used is /dev/rfcomm0 --- src/platformchannel.c | 6 + src/pluginregistry.c | 3 + src/plugins/elm327plugin.c | 552 ++++++++++++++++++++++++++++++++++ src/plugins/elm327plugin.h | 142 +++++++++ src/plugins/services-plugin.c | 2 +- src/plugins/testplugin.c | 2 +- 6 files changed, 705 insertions(+), 2 deletions(-) create mode 100644 src/plugins/elm327plugin.c create mode 100644 src/plugins/elm327plugin.h diff --git a/src/platformchannel.c b/src/platformchannel.c index e7fefb81..ccfb4a3b 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -1082,6 +1082,9 @@ int PlatformChannel_respondNotImplemented(FlutterPlatformMessageResponseHandle * } int PlatformChannel_respondError(FlutterPlatformMessageResponseHandle *handle, enum ChannelCodec codec, char *errorcode, char *errormessage, void *errordetails) { if ((codec == kStandardMessageCodec) || (codec == kStandardMethodCall) || (codec == kStandardMethodCallResponse)) { + if (errordetails == NULL) + errordetails = &(struct StdMsgCodecValue) {.type = kNull}; + return PlatformChannel_respond(handle, &(struct ChannelObject) { .codec = kStandardMethodCallResponse, .success = false, @@ -1090,6 +1093,9 @@ int PlatformChannel_respondError(FlutterPlatformMessageResponseHandle *handle, e .stderrordetails = *((struct StdMsgCodecValue *) errordetails) }); } else if ((codec == kJSONMessageCodec) || (codec == kJSONMethodCall) || (codec == kJSONMethodCallResponse)) { + if (errordetails == NULL) + errordetails = &(struct JSONMsgCodecValue) {.type = kJSNull}; + return PlatformChannel_respond(handle, &(struct ChannelObject) { .codec = kJSONMethodCallResponse, .success = false, diff --git a/src/pluginregistry.c b/src/pluginregistry.c index a0643add..f7810d04 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -7,6 +7,7 @@ // hardcoded plugin headers #include "plugins/services-plugin.h" #include "plugins/testplugin.h" +#include "plugins/elm327plugin.h" struct ChannelObjectReceiverData { @@ -28,6 +29,8 @@ struct FlutterPiPlugin hardcoded_plugins[] = { #ifdef INCLUDE_TESTPLUGIN {.name = "testplugin", .init = TestPlugin_init, .deinit = TestPlugin_deinit} #endif + + {.name = "elm327", .init = ELM327Plugin_init, .deinit = ELM327Plugin_deinit} }; size_t hardcoded_plugins_count; diff --git a/src/plugins/elm327plugin.c b/src/plugins/elm327plugin.c new file mode 100644 index 00000000..bcb9154c --- /dev/null +++ b/src/plugins/elm327plugin.c @@ -0,0 +1,552 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../pluginregistry.h" +#include "elm327plugin.h" + + +struct elm327 elm; +pthread_mutex_t pidqq_mutex; +struct pidqq_element *pidqq = NULL; +size_t pidqq_size = 0; +volatile bool pidqq_processor_shouldrun = false; +pthread_t pidqq_processor_thread; + +/***************************** + * Communication with ELM327 * + *****************************/ + +/// send a raw command to the ELM327. +/// the contents of the cmd string and an end-of-command marker are +/// send to the ELM. +/// if response is not NULL and length is larger than 0, the ELM327's response +/// is stored in "response". If the buffer given by response and length is to small +/// to fit the ELMs response, elm_command will start writing at the start of the +/// buffer again. +/// if no response buffer is given (the response parameter is NULL or length is 0) +/// elm_command will still wait for a response, it just won't store anything. +int elm_command(char *cmd, char *response, size_t length) { + int ok = 0, count = 0, i = 0, ntransmit; + char charbuff = 0; + + elm.elm_errno = ELM_ERRNO_OK; + + assert(elm.is_online && "elm_command: ELM327 must be online"); + assert(cmd && "elm_command: command can't be NULL"); + + ntransmit = strlen(cmd) + strlen(ELM327_EOC); + + // write cmd to line + ok = pselect(elm.fd+1, NULL, &elm.fdset, NULL, &elm.timeout, NULL); + if (ok > 0) { + tcflush(elm.fd, TCIOFLUSH); + + // why do we write byte per byte here? + for (i=0; i < strlen(cmd); i++) + count += write(elm.fd, (const void *) cmd+i, sizeof(char)); + + for (i=0; i < strlen(ELM327_EOC); i++) + count += write(elm.fd, (const void *) ELM327_EOC+i, sizeof(char)); + + if (count != ntransmit) { + fprintf(stderr, "could not write command to serial, written %d bytes, should be %ld\n", count, ntransmit); + return EIO; + } + } else { + fprintf(stderr, "elm connection timed out while writing, after %lus, %09luns\n", elm.timeout.tv_sec, elm.timeout.tv_nsec); + elm.elm_errno = ELM_ERRNO_NOCONN; + return EIO; + } + + // wait for bytes to send + tcdrain(elm.fd); + + // read response + while (1) { + ok = pselect(elm.fd+1, &elm.fdset, NULL, NULL, &elm.timeout, NULL); + + if (ok > 0) { + ok = read(elm.fd, &charbuff, sizeof(char)); + + if (isprint(charbuff)) printf("%c", charbuff); + else printf("\\x%02x", charbuff); + + if (charbuff == '>') { + if (response) response[i] = '\0'; + printf("\n"); + break; + } else if (response) { + response[i] = charbuff; + i = (i+1) % length; + } + } else { + printf("\n"); + fprintf(stderr, "ELM327 connection timed out while reading, after %lus, %09luns\n", elm.timeout.tv_sec, elm.timeout.tv_nsec); + elm.elm_errno = ELM_ERRNO_NOCONN; + return EIO; + } + } + + return 0; +} + +/// queries the value of a pid +/// (uses elm_command for execution) +int elm_query(uint8_t pid, uint32_t* response) { + char txt_response[32], command[5]; + size_t response_length = 0; + uint8_t bytes[6] = {0}; + int ok; + + sprintf(command, "01%02X", pid); + + ok = elm_command(command, txt_response, 32); + if (ok != 0) return ok; + + // scan reply for errors + elm.elm_errno = ELM_ERRNO_OK; + if (strstr(txt_response, "ERROR")) { + if (strstr(txt_response, ELM327_BUS_ERROR)) + elm.elm_errno = ELM_ERRNO_BUS_ERROR; + else if (strstr(txt_response, ELM327_CAN_ERROR)) + elm.elm_errno = ELM_ERRNO_CAN_ERROR; + else if (strstr(txt_response, ELM327_LINE_DATA_ERROR)) + elm.elm_errno = ELM_ERRNO_LINE_DATA_ERROR; + else if (strstr(txt_response, ELM327_DATA_ERROR)) + elm.elm_errno = ELM_ERRNO_DATA_ERROR; + else if (strstr(txt_response, ELM327_FEEDBACK_ERROR)) + elm.elm_errno = ELM_ERRNO_FEEDBACK_ERROR; + else if (strstr(txt_response, ELM327_LINE_RX_ERROR)) + elm.elm_errno = ELM_ERRNO_LINE_RX_ERROR; + } else if (strstr(txt_response, ELM327_OK)) + elm.elm_errno = ELM_ERRNO_OK; + else if (strstr(txt_response, ELM327_INVALID)) + elm.elm_errno = ELM_ERRNO_INVALID; + else if (strstr(txt_response, ELM327_ACT_ALERT)) + elm.elm_errno = ELM_ERRNO_ACT_ALERT; + else if (strstr(txt_response, ELM327_BUFFER_FULL)) + elm.elm_errno = ELM_ERRNO_BUFFER_FULL; + else if (strstr(txt_response, ELM327_BUS_BUSY)) + elm.elm_errno = ELM_ERRNO_BUS_BUSY; + else if (strstr(txt_response, ELM327_LOW_POWER_ALERT)) + elm.elm_errno = ELM_ERRNO_LOW_POWER_ALERT; + else if (strstr(txt_response, ELM327_LOW_VOLTAGE_RESET)) + elm.elm_errno = ELM_ERRNO_LOW_VOLTAGE_RESET; + else if (strstr(txt_response, ELM327_NO_DATA)) + elm.elm_errno = ELM_ERRNO_NO_DATA; + else if (strstr(txt_response, ELM327_STOPPED)) + elm.elm_errno = ELM_ERRNO_STOPPED; + else if (strstr(txt_response, ELM327_NOCONN)) + elm.elm_errno = ELM_ERRNO_NOCONN; + else if (strstr(txt_response, ELM327_SEARCHING)) + elm.elm_errno = ELM_ERRNO_SEARCHING; + + if (elm.elm_errno != ELM_ERRNO_OK) { + fprintf(stderr, "elm_query: query was not successful. ELM_ERRNO: %d\n", elm.elm_errno); + return EIO; + } + + response_length = strlen(txt_response); + + // we need to remove all carriage returns so we can print out the response + // remove all trailing carriage-returns + for (int i = response_length-1; i>=0; i--) { + if (txt_response[i] == 0x0D) { + txt_response[i] = 0x00; + response_length--; + } else { + break; + } + } + + // remove all remaining carriage-returns + int n_move = 0; + for (int i = 0; i <= response_length; i++) + if (txt_response[i] == 0x0D) n_move++; + else if (n_move) txt_response[i-n_move] = txt_response[i]; + response_length -= n_move; + + printf("asked for \"%s\", response: \"%s\"\n", command, txt_response); + + // parse the response + int res = sscanf(txt_response, "%2hhX%2hhX%2hhX%2hhX%2hhX%2hhX", + bytes, bytes+1, bytes+2, bytes+3, bytes+4, bytes+5); + + if (res == EOF) { + fprintf(stderr, "elm_query: string matching error ocurred\n"); + return EIO; + } else if ((res >= 0) && (res <= 2)) { + fprintf(stderr, "elm_query: unexpected ELM327 reply\n"); + return EIO; + } + + // put the values returned from the ELM327 into the response + *response = 0; + for (int i = 2; i < res; i++) + *response = (*response << 8) | bytes[i]; + + return 0; +} + +/// if the given pid is supported by the vehicle, returns true. +/// otherwise, returns false. +/// the supported PIDs are tested in elm_open, so this won't send anything +/// to the ELM327. +bool elm_pid_supported(uint8_t pid) { + if (pid == 0x00) return true; + + uint8_t pid_bank = (pid-1) >> 5; + uint8_t pid_index = (pid-1) & 0x1F; + pid_index = 0x1f - pid_index; + + return (elm.supported_pids[pid_bank] & (1 << pid_index)) && 1; +} + +/// closes the underlying serial device +void elm_destroy() { + cfsetispeed(&elm.tty, B0); + cfsetospeed(&elm.tty, B0); + + close(elm.fd); +} + +/// Opens the serial device given through "serial_path" with the given baudrate, +/// and sets up the ELM327 at the other end for communication. +/// elm_command, elm_query, elm_pid_supported and elm_destroy can't be used +/// before elm_open was called. +int elm_open(char *serial_path, int baudrate) { + int ok; + + printf("Opening ELM327 at \"%s\"\n", serial_path); + + elm.timeout.tv_sec = 5; + elm.timeout.tv_nsec = 0; + elm.is_online = false; + + ok = access(serial_path, R_OK | W_OK); + if (ok != 0) { + fprintf(stderr, "Process doesn't have access to serial device \"%s\": %s\n", serial_path, strerror(errno)); + return errno; + } + + // Open serial device at given path + elm.fd = open(serial_path, O_RDWR | O_NOCTTY | O_NDELAY); + if (elm.fd < 0) { + fprintf(stderr, "Could not open serial device \"%s\": %s\n", serial_path, strerror(errno)); + return errno; + } + + // set the serial line attributes + ok = tcgetattr(elm.fd, &elm.tty); + + elm.tty.c_cflag |= (CLOCAL|CREAD); + elm.tty.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG|IEXTEN); + elm.tty.c_iflag &= ~(INLCR|IGNCR|ICRNL|IGNBRK|IUCLC|PARMRK| + INPCK|ISTRIP|IXON|IXOFF|IXANY); + elm.tty.c_oflag &= ~(OPOST); + + elm.tty.c_cc[VMIN] = 0; + elm.tty.c_cc[VTIME]= 0; + + + // set the baudrate + speed_t serial_speed; + switch (baudrate) { + case 0: serial_speed = B0; break; + case 50: serial_speed = B50; break; + case 75: serial_speed = B75; break; + case 110: serial_speed = B110; break; + case 134: serial_speed = B134; break; + case 150: serial_speed = B150; break; + case 200: serial_speed = B200; break; + case 300: serial_speed = B300; break; + case 600: serial_speed = B600; break; + case 1200: serial_speed = B1200; break; + case 1800: serial_speed = B1800; break; + case 2400: serial_speed = B2400; break; + case 4800: serial_speed = B4800; break; + case 9600: serial_speed = B9600; break; + case 19200: serial_speed = B19200; break; + case 38400: serial_speed = B38400; break; + case 57600: serial_speed = B57600; break; + case 115200: serial_speed = B115200; break; + case 230400: serial_speed = B230400; break; + default: serial_speed = B0; break; + } + if (serial_speed == B0) { + fprintf(stderr, "Not a valid baudrate: %d\n", baudrate); + return EINVAL; + } + + cfsetispeed(&(elm.tty), serial_speed); + cfsetospeed(&(elm.tty), serial_speed); + + tcsetattr(elm.fd, TCSANOW, &(elm.tty)); + + // create an fdset containing the serial device fd + FD_ZERO(&elm.fdset); + FD_SET(elm.fd, &elm.fdset); + + // completely reset the ELM327 + printf("elm ATWS\n"); + elm.is_online = true; + ok = elm_command(ELM327_RESET, NULL, 0); + if (ok != 0) return ok; + + printf("elm ATI\n"); + ok = elm_command(ELM327_VERSION, elm.version, sizeof(elm.version)); + if (ok != 0) return ok; + + printf("elm AT E0\n"); + ok = elm_command(ELM327_ECHO_OFF, NULL, 0); + if (ok != 0) return ok; + + printf("elm AT L0\n"); + ok = elm_command(ELM327_LINEFEEDS_OFF, NULL, 0); + if (ok != 0) return ok; + + // send a dummy message so the ELM can determine the cars protocol + ok = elm_query(0x00, elm.supported_pids); + if ((ok != 0) && (elm.elm_errno != ELM_ERRNO_SEARCHING)) return EIO; + + // query supported pids + for (int i = 0; i < 8; i++) { + printf("is PID 0x%02X supported? %s\n", i*0x20, elm_pid_supported(i*0x20) ? "yes" : "no"); + ok = elm_query(i*0x20, &elm.supported_pids[i]); + if (ok != 0) return ok; + } + + // output a list of supported PIDs + printf("list of supported PIDs: "); + for (uint8_t pid = 0; pid <= 0xFF; pid++) + if (elm_pid_supported(pid)) + printf("0x%02X, ", pid); + + printf("\n"); +} + +/* + * pid-query priority queue + * + * when the ELM327 plugin wants to know about a pid, + * it has to queue this query in the pid-query queue (pidqq) + * the queries are processed by the pidqq_processor, which runs on it's own thread. + * pid queries with higher priorities are executed first. + */ +int pidqq_lastIndex(void) { + int i = 0; + while ((i > 0) && (pidqq[i].priority == 0)) + i--; + + return i; +} +void pidqq_add(struct pidqq_element *element) { + int lastIndex = -1; + int i = 0; + + while ((i < pidqq_size) && (pidqq[i].priority != 0) && (pidqq[i].priority > element->priority)) + i++; + + if ((i == pidqq_size) || ((lastIndex = pidqq_lastIndex()) == pidqq_size-1)) { + // make queue larger + size_t before = pidqq_size*sizeof(struct pidqq_element); + pidqq_size = pidqq_size*2; + size_t after = pidqq_size*sizeof(struct pidqq_element); + pidqq = realloc(pidqq, after); + memset(pidqq + before, 0, after-before); + } + + if (pidqq[i].priority == 0) { + // insert at last element + pidqq[i] = *element; + } else { + if (lastIndex == -1) lastIndex = pidqq_lastIndex(); + + // shift all elements from i to lastIndex one up + for (int j = lastIndex+1; j>i; j--) + pidqq[j] = pidqq[j-1]; + + // insert element at i + pidqq[i] = *element; + } +} +void pidqq_remove(int index) { + int lastIndex = pidqq_lastIndex(); + pidqq[index].priority = 0; + + int i; + for (i = index+1; (i < pidqq_size) && (pidqq[i].priority != 0); i++) { + pidqq[i-1] = pidqq[i]; + } + pidqq[i-1].priority = 0; +} +int pidqq_findWithPid(uint8_t pid) { + for (int i = 0; (i < pidqq_size) && (pidqq[i].priority != 0); i++) + if (pidqq[i].pid == pid) + return i; + + return -1; +} +void *run_pidqq_processor(void* arg) { + uint8_t pid; + uint32_t response; + + while (pidqq_processor_shouldrun) { + pthread_mutex_lock(&pidqq_mutex); + if (pidqq[0].priority) { + pid = pidqq[0].pid; + + pthread_mutex_unlock(&pidqq_mutex); + response = 0; + elm_query(pid, &response); + + pthread_mutex_lock(&pidqq_mutex); + if ((pidqq[0].priority) && (pidqq[0].pid == pid)) { + struct pidqq_element e = pidqq[0]; + if (pidqq[0].repeat) pidqq_add(&e); + + pidqq_remove(0); + pthread_mutex_unlock(&pidqq_mutex); + + e.completionCallback(e, response); + } else { + pidqq_remove(0); + pthread_mutex_unlock(&pidqq_mutex); + } + } else { + pthread_mutex_unlock(&pidqq_mutex); + } + } + + return NULL; +} + +/***************** + * ELM327 plugin * + *****************/ +void ELM327Plugin_onPidQueryCompletion(struct pidqq_element query, uint32_t result) { + struct ChannelObject obj = { + .codec = kStandardMessageCodec, + .stdmsgcodec_value = { + .type = kFloat64, + .float64_value = 0.0 + } + }; + uint8_t pid = query.pid; + + switch (pid) { + case OBDII_PID_ENGINE_RPM: + obj.stdmsgcodec_value.float64_value = result / 4.0; + break; + case OBDII_PID_ENGINE_LOAD: + case OBDII_PID_THROTTLE_POSITION: + obj.stdmsgcodec_value.float64_value = result * 100.0 / 255.0; + break; + case OBDII_PID_ENGINE_COOLANT_TEMP: + obj.stdmsgcodec_value.type = kInt32; + obj.stdmsgcodec_value.int32_value = (int32_t) result - 40; + break; + case OBDII_PID_VEHICLE_SPEED: + obj.stdmsgcodec_value.type = kInt32; + obj.stdmsgcodec_value.int32_value = (int32_t) result; + break; + default: + break; + } + + PlatformChannel_send(query.channel, &obj, kStandardMessageCodec, NULL, NULL); +} +int ELM327Plugin_onEventChannelListen(char *channel, uint8_t pid, FlutterPlatformMessageResponseHandle *handle) { + pthread_mutex_lock(&pidqq_mutex); + + pidqq_add(&(struct pidqq_element) { + .priority = 1, + .pid = pid, + .repeat = true, + .completionCallback = ELM327Plugin_onPidQueryCompletion + }); + + pthread_mutex_unlock(&pidqq_mutex); + + if (elm_pid_supported(pid)) { + PlatformChannel_respond(handle, &(struct ChannelObject) { + .codec = kStandardMethodCallResponse, + .success = true, + .stdresult = {.type = kNull} + }); + } else { + PlatformChannel_respondError( + handle, kStandardMethodCallResponse, + "notsupported", + "The vehicle doesn't support the PID used for this channel.", + NULL + ); + } +} +int ELM327Plugin_onEventChannelCancel(char *channel, uint8_t pid, FlutterPlatformMessageResponseHandle *handle) { + pthread_mutex_lock(&pidqq_mutex); + + pidqq_remove(pidqq_findWithPid(OBDII_PID_ENGINE_RPM)); + + pthread_mutex_unlock(&pidqq_mutex); + + PlatformChannel_respond(handle, &(struct ChannelObject) { + .codec = kStandardMethodCallResponse, + .success = true, + .stdresult = {.type = kNull} + }); +} +int ELM327Plugin_onReceive(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *handle) { + if (object->codec == kStandardMethodCall) { + uint8_t pid = (strcmp(channel, ELM327PLUGIN_RPM_CHANNEL) == 0) ? OBDII_PID_ENGINE_RPM : + (strcmp(channel, ELM327PLUGIN_ENGINELOAD_CHANNEL) == 0) ? OBDII_PID_ENGINE_LOAD : + (strcmp(channel, ELM327PLUGIN_COOLANTTEMP_CHANNEL) == 0) ? OBDII_PID_ENGINE_COOLANT_TEMP : + (strcmp(channel, ELM327PLUGIN_SPEED_CHANNEL) == 0) ? OBDII_PID_VEHICLE_SPEED : + (strcmp(channel, ELM327PLUGIN_THROTTLE_CHANNEL) == 0) ? OBDII_PID_THROTTLE_POSITION : 0; + assert((pid != 0) && "unexpected event channel"); + + if (strcmp(object->method, "listen") == 0) ELM327Plugin_onEventChannelListen(channel, pid, handle); + else if (strcmp(object->method, "cancel") == 0) ELM327Plugin_onEventChannelCancel(channel, pid, handle); + } + + return PlatformChannel_respondNotImplemented(handle); +} + +int ELM327Plugin_init(void) { + int r = 0; + + // init the elm327 + r = elm_open(ELM327PLUGIN_DEVICE_PATH, 9600); + + // init pidquery queue + pthread_mutex_init(&pidqq_mutex, NULL); + pidqq_size = 50; + pidqq = calloc(pidqq_size, sizeof(struct pidqq_element)); + + pthread_create(&pidqq_processor_thread, NULL, run_pidqq_processor, NULL); + + PluginRegistry_setReceiver(ELM327PLUGIN_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); +} +int ELM327Plugin_deinit(void) { + elm_destroy(); + + pidqq_processor_shouldrun = false; + pthread_join(pidqq_processor_thread, NULL); + + free(pidqq); +} \ No newline at end of file diff --git a/src/plugins/elm327plugin.h b/src/plugins/elm327plugin.h new file mode 100644 index 00000000..0cf7aeab --- /dev/null +++ b/src/plugins/elm327plugin.h @@ -0,0 +1,142 @@ +#ifndef ELM327PLUGIN_H +#define ELM327PLUGIN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ELM327_RESET "ATWS" +#define ELM327_VERSION "ATI" +#define ELM327_ECHO_OFF "AT E0" +#define ELM327_LINEFEEDS_OFF "AT L0" +#define ELM327_EOC "\r\n" + +#define ELM327_OK "OK" // command successfully executed +#define ELM327_INVALID "?" // ELM could not understand the command +#define ELM327_ACT_ALERT "ACT ALERT" // no activity for the last 19 minutes, going into low power mode soon +#define ELM327_BUFFER_FULL "BUFFER FULL" // ELM TX Buffer full +#define ELM327_BUS_BUSY "BUS BUSY" // CAN bus was to busy to request PID +#define ELM327_BUS_ERROR "BUS ERROR" // Generic bus error. +#define ELM327_CAN_ERROR "CAN ERROR" // Generic CAN error. (Incorrect CAN baudrate, etc) +#define ELM327_DATA_ERROR "DATA ERROR" // Vehicle replied with invalid data (maybe checksum error) +#define ELM327_LINE_DATA_ERROR " #include -#include "pluginregistry.h" +#include "../pluginregistry.h" #include "services-plugin.h" struct { diff --git a/src/plugins/testplugin.c b/src/plugins/testplugin.c index f3ade9eb..bc1e909d 100644 --- a/src/plugins/testplugin.c +++ b/src/plugins/testplugin.c @@ -2,7 +2,7 @@ #include #include -#include "pluginregistry.h" +#include "../pluginregistry.h" #define TESTPLUGIN_CHANNEL_JSON "plugins.flutter-pi.io/testjson" #define TESTPLUGIN_CHANNEL_STD "plugins.flutter-pi.io/teststd" From 1ce2e35ebe38ac94aac7985b1fd685dcc1d55f60 Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Mon, 21 Oct 2019 17:17:13 +0200 Subject: [PATCH 2/6] patches for seperate include dir --- src/flutter-pi.c | 6 +++--- src/platformchannel.c | 7 +++---- src/pluginregistry.c | 4 ++-- src/plugins/elm327plugin.c | 2 +- src/plugins/services-plugin.c | 2 +- src/plugins/testplugin.c | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 5f885114..e1be8181 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -27,9 +27,9 @@ #include -#include "flutter-pi.h" -#include "platformchannel.h" -#include "pluginregistry.h" +#include +#include +#include char* usage ="\ diff --git a/src/platformchannel.c b/src/platformchannel.c index 99bf28eb..95a72760 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -8,10 +8,9 @@ #include #include -#include "platformchannel.h" -#include "flutter-pi.h" -#include "jsmn.h" - +#include +#include +#include struct ResponseHandlerData { diff --git a/src/pluginregistry.c b/src/pluginregistry.c index f7810d04..0c4e6f27 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -1,8 +1,8 @@ #include #include -#include "platformchannel.h" -#include "pluginregistry.h" +#include +#include // hardcoded plugin headers #include "plugins/services-plugin.h" diff --git a/src/plugins/elm327plugin.c b/src/plugins/elm327plugin.c index bcb9154c..b0559b32 100644 --- a/src/plugins/elm327plugin.c +++ b/src/plugins/elm327plugin.c @@ -16,7 +16,7 @@ #include #include -#include "../pluginregistry.h" +#include #include "elm327plugin.h" diff --git a/src/plugins/services-plugin.c b/src/plugins/services-plugin.c index 00d1935d..8c3af3e5 100644 --- a/src/plugins/services-plugin.c +++ b/src/plugins/services-plugin.c @@ -1,7 +1,7 @@ #include #include -#include "../pluginregistry.h" +#include #include "services-plugin.h" struct { diff --git a/src/plugins/testplugin.c b/src/plugins/testplugin.c index bc1e909d..712aeed4 100644 --- a/src/plugins/testplugin.c +++ b/src/plugins/testplugin.c @@ -2,7 +2,7 @@ #include #include -#include "../pluginregistry.h" +#include #define TESTPLUGIN_CHANNEL_JSON "plugins.flutter-pi.io/testjson" #define TESTPLUGIN_CHANNEL_STD "plugins.flutter-pi.io/teststd" From 79ee6ac6080f61abf0f63ba15663635b750771fd Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Mon, 21 Oct 2019 23:11:53 +0200 Subject: [PATCH 3/6] fix busy waiting & event channel --- src/plugins/elm327plugin.c | 41 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/plugins/elm327plugin.c b/src/plugins/elm327plugin.c index b0559b32..87795f59 100644 --- a/src/plugins/elm327plugin.c +++ b/src/plugins/elm327plugin.c @@ -430,6 +430,9 @@ void *run_pidqq_processor(void* arg) { } } else { pthread_mutex_unlock(&pidqq_mutex); + sleep(1); // in the future, we should use signals to notify the pidqq_processor + // when a query has arrived in the queue, but this at least stops + // busy waiting for now. } } @@ -441,8 +444,9 @@ void *run_pidqq_processor(void* arg) { *****************/ void ELM327Plugin_onPidQueryCompletion(struct pidqq_element query, uint32_t result) { struct ChannelObject obj = { - .codec = kStandardMessageCodec, - .stdmsgcodec_value = { + .codec = kStandardMethodCallResponse, + .success = true, + .stdresult = { .type = kFloat64, .float64_value = 0.0 } @@ -465,38 +469,37 @@ void ELM327Plugin_onPidQueryCompletion(struct pidqq_element query, uint32_t resu obj.stdmsgcodec_value.type = kInt32; obj.stdmsgcodec_value.int32_value = (int32_t) result; break; - default: + default: break; } PlatformChannel_send(query.channel, &obj, kStandardMessageCodec, NULL, NULL); } int ELM327Plugin_onEventChannelListen(char *channel, uint8_t pid, FlutterPlatformMessageResponseHandle *handle) { - pthread_mutex_lock(&pidqq_mutex); + if (!elm_pid_supported(pid)) { + PlatformChannel_respondError( + handle, kStandardMethodCallResponse, + "notsupported", + "The vehicle doesn't support the PID used for this channel.", + NULL + ); + } + // insert a new query into pidqq + pthread_mutex_lock(&pidqq_mutex); pidqq_add(&(struct pidqq_element) { .priority = 1, .pid = pid, .repeat = true, .completionCallback = ELM327Plugin_onPidQueryCompletion }); - pthread_mutex_unlock(&pidqq_mutex); - if (elm_pid_supported(pid)) { - PlatformChannel_respond(handle, &(struct ChannelObject) { - .codec = kStandardMethodCallResponse, - .success = true, - .stdresult = {.type = kNull} - }); - } else { - PlatformChannel_respondError( - handle, kStandardMethodCallResponse, - "notsupported", - "The vehicle doesn't support the PID used for this channel.", - NULL - ); - } + PlatformChannel_respond(handle, &(struct ChannelObject) { + .codec = kStandardMethodCallResponse, + .success = true, + .stdresult = {.type = kNull} + }); } int ELM327Plugin_onEventChannelCancel(char *channel, uint8_t pid, FlutterPlatformMessageResponseHandle *handle) { pthread_mutex_lock(&pidqq_mutex); From b015741a927797c6252295e2e72773e3c9a65d37 Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Tue, 22 Oct 2019 11:18:04 +0200 Subject: [PATCH 4/6] verbose drm modeset --- src/flutter-pi.c | 62 ++++++++++++++++++++++++++++++++------------ src/pluginregistry.c | 4 ++- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/flutter-pi.c b/src/flutter-pi.c index e1be8181..8bbbcb54 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -545,41 +545,67 @@ bool init_display(void) { } - printf("Finding a connected connector...\n"); + printf("Finding a connected connector from %d available connectors...\n", resources->count_connectors); + connector = NULL; for (i = 0; i < resources->count_connectors; i++) { - connector = drmModeGetConnector(drm.fd, resources->connectors[i]); - if (connector->connection == DRM_MODE_CONNECTED) { - width_mm = connector->mmWidth; - height_mm = connector->mmHeight; - break; + drmModeConnector *conn = drmModeGetConnector(drm.fd, resources->connectors[i]); + + printf(" connectors[%d]: connected? %s, type: 0x%02X%s, %umm x %umm\n", + i, + (conn->connection == DRM_MODE_CONNECTED) ? "yes" : + (conn->connection == DRM_MODE_DISCONNECTED) ? "no" : "unknown", + conn->connector_type, + (conn->connector_type == DRM_MODE_CONNECTOR_HDMIA) ? " (HDMI-A)" : + (conn->connector_type == DRM_MODE_CONNECTOR_HDMIB) ? " (HDMI-B)" : + (conn->connector_type == DRM_MODE_CONNECTOR_DSI) ? " (DSI)" : + (conn->connector_type == DRM_MODE_CONNECTOR_DisplayPort) ? " (DisplayPort)" : "", + conn->mmWidth, conn->mmHeight + ); + + if ((connector == NULL) && (conn->connection == DRM_MODE_CONNECTED)) { + connector = conn; + width_mm = conn->mmWidth; + height_mm = conn->mmHeight; + } else { + drmModeFreeConnector(conn); } - drmModeFreeConnector(connector); - connector = NULL; } if (!connector) { fprintf(stderr, "could not find a connected connector!\n"); return false; } - printf("Choosing DRM mode...\n"); + printf("Choosing DRM mode from %d available modes...\n", connector->count_modes); for (i = 0, area = 0; i < connector->count_modes; i++) { drmModeModeInfo *current_mode = &connector->modes[i]; - if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { + printf(" modes[%d]: name: \"%s\", %ux%u%s, %uHz, type: %u, flags: %u\n", + i, current_mode->name, current_mode->hdisplay, current_mode->vdisplay, + (current_mode->flags & DRM_MODE_FLAG_INTERLACE) ? "i" : "p", + current_mode->vrefresh, current_mode->type, current_mode->flags + ); + + // we choose the highest resolution with the highest refresh rate, preferably non-interlaced (= progressive) here. + int current_area = current_mode->hdisplay * current_mode->vdisplay; + if (( current_area > area) || + ((current_area == area) && (drm.mode->vrefresh > refresh_rate)) || + ((current_area == area) && (drm.mode->vrefresh == refresh_rate) && ((drm.mode->flags & DRM_MODE_FLAG_INTERLACE) == 0)) || + ( current_mode->type & DRM_MODE_TYPE_PREFERRED)) { + drm.mode = current_mode; width = drm.mode->hdisplay; height = drm.mode->vdisplay; refresh_rate = drm.mode->vrefresh; + area = current_area; if (width_mm) pixel_ratio = (10.0 * width) / (width_mm * 38.0); + if (pixel_ratio < 1.0) pixel_ratio = 1.0; - break; - } - - int current_area = current_mode->hdisplay * current_mode->vdisplay; - if (current_area > area) { - drm.mode = current_mode; - area = current_area; + // if the preferred DRM mode is bogus, we're screwed. + if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { + printf("the chosen DRM mode is preferred by DRM. (DRM_MODE_TYPE_PREFERRED)\n"); + break; + } } } if (!drm.mode) { @@ -868,6 +894,8 @@ bool init_application(void) { if (_result != kSuccess) { fprintf(stderr, "Could not run the flutter engine\n"); return false; + } else { + printf("flutter engine successfully started up.\n"); } // update window size diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 0c4e6f27..781064bd 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -30,9 +30,11 @@ struct FlutterPiPlugin hardcoded_plugins[] = { {.name = "testplugin", .init = TestPlugin_init, .deinit = TestPlugin_deinit} #endif +#ifdef INCLUDE_ELM327PLUGIN {.name = "elm327", .init = ELM327Plugin_init, .deinit = ELM327Plugin_deinit} +#endif }; -size_t hardcoded_plugins_count; +//size_t hardcoded_plugins_count; int PluginRegistry_init() { From 7a9f32616b3fdcf58c07e30e189a37b9cc0483cb Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Tue, 22 Oct 2019 12:29:44 +0200 Subject: [PATCH 5/6] remove hardcoded flutter_embedder header fix bug with input handling --- include/flutter_embedder.h | 1303 ------------------------------------ src/flutter-pi.c | 129 ++-- 2 files changed, 84 insertions(+), 1348 deletions(-) delete mode 100644 include/flutter_embedder.h diff --git a/include/flutter_embedder.h b/include/flutter_embedder.h deleted file mode 100644 index 7490198b..00000000 --- a/include/flutter_embedder.h +++ /dev/null @@ -1,1303 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_EMBEDDER_H_ -#define FLUTTER_EMBEDDER_H_ - -#include -#include -#include - -#if defined(__cplusplus) -extern "C" { -#endif - -#ifndef FLUTTER_EXPORT -#define FLUTTER_EXPORT -#endif // FLUTTER_EXPORT - -#ifdef FLUTTER_API_SYMBOL_PREFIX -#define FLUTTER_EMBEDDING_CONCAT(a, b) a##b -#define FLUTTER_EMBEDDING_ADD_PREFIX(symbol, prefix) \ - FLUTTER_EMBEDDING_CONCAT(prefix, symbol) -#define FLUTTER_API_SYMBOL(symbol) \ - FLUTTER_EMBEDDING_ADD_PREFIX(symbol, FLUTTER_API_SYMBOL_PREFIX) -#else -#define FLUTTER_API_SYMBOL(symbol) symbol -#endif - -#define FLUTTER_ENGINE_VERSION 1 - -typedef enum { - kSuccess = 0, - kInvalidLibraryVersion, - kInvalidArguments, - kInternalInconsistency, -} FlutterEngineResult; - -typedef enum { - kOpenGL, - kSoftware, -} FlutterRendererType; - -/// Additional accessibility features that may be enabled by the platform. -/// Must match the `AccessibilityFeatures` enum in window.dart. -typedef enum { - /// Indicate there is a running accessibility service which is changing the - /// interaction model of the device. - kFlutterAccessibilityFeatureAccessibleNavigation = 1 << 0, - /// Indicate the platform is inverting the colors of the application. - kFlutterAccessibilityFeatureInvertColors = 1 << 1, - /// Request that animations be disabled or simplified. - kFlutterAccessibilityFeatureDisableAnimations = 1 << 2, - /// Request that text be rendered at a bold font weight. - kFlutterAccessibilityFeatureBoldText = 1 << 3, - /// Request that certain animations be simplified and parallax effects - // removed. - kFlutterAccessibilityFeatureReduceMotion = 1 << 4, -} FlutterAccessibilityFeature; - -/// The set of possible actions that can be conveyed to a semantics node. -/// -/// Must match the `SemanticsAction` enum in semantics.dart. -typedef enum { - /// The equivalent of a user briefly tapping the screen with the finger - /// without - /// moving it. - kFlutterSemanticsActionTap = 1 << 0, - /// The equivalent of a user pressing and holding the screen with the finger - /// for a few seconds without moving it. - kFlutterSemanticsActionLongPress = 1 << 1, - /// The equivalent of a user moving their finger across the screen from right - /// to left. - kFlutterSemanticsActionScrollLeft = 1 << 2, - /// The equivalent of a user moving their finger across the screen from left - /// to - /// right. - kFlutterSemanticsActionScrollRight = 1 << 3, - /// The equivalent of a user moving their finger across the screen from bottom - /// to top. - kFlutterSemanticsActionScrollUp = 1 << 4, - /// The equivalent of a user moving their finger across the screen from top to - /// bottom. - kFlutterSemanticsActionScrollDown = 1 << 5, - /// Increase the value represented by the semantics node. - kFlutterSemanticsActionIncrease = 1 << 6, - /// Decrease the value represented by the semantics node. - kFlutterSemanticsActionDecrease = 1 << 7, - /// A request to fully show the semantics node on screen. - kFlutterSemanticsActionShowOnScreen = 1 << 8, - /// Move the cursor forward by one character. - kFlutterSemanticsActionMoveCursorForwardByCharacter = 1 << 9, - /// Move the cursor backward by one character. - kFlutterSemanticsActionMoveCursorBackwardByCharacter = 1 << 10, - /// Set the text selection to the given range. - kFlutterSemanticsActionSetSelection = 1 << 11, - /// Copy the current selection to the clipboard. - kFlutterSemanticsActionCopy = 1 << 12, - /// Cut the current selection and place it in the clipboard. - kFlutterSemanticsActionCut = 1 << 13, - /// Paste the current content of the clipboard. - kFlutterSemanticsActionPaste = 1 << 14, - /// Indicate that the node has gained accessibility focus. - kFlutterSemanticsActionDidGainAccessibilityFocus = 1 << 15, - /// Indicate that the node has lost accessibility focus. - kFlutterSemanticsActionDidLoseAccessibilityFocus = 1 << 16, - /// Indicate that the user has invoked a custom accessibility action. - kFlutterSemanticsActionCustomAction = 1 << 17, - /// A request that the node should be dismissed. - kFlutterSemanticsActionDismiss = 1 << 18, - /// Move the cursor forward by one word. - kFlutterSemanticsActionMoveCursorForwardByWord = 1 << 19, - /// Move the cursor backward by one word. - kFlutterSemanticsActionMoveCursorBackwardByWord = 1 << 20, -} FlutterSemanticsAction; - -/// The set of properties that may be associated with a semantics node. -/// -/// Must match the `SemanticsFlag` enum in semantics.dart. -typedef enum { - /// The semantics node has the quality of either being "checked" or - /// "unchecked". - kFlutterSemanticsFlagHasCheckedState = 1 << 0, - /// Whether a semantics node is checked. - kFlutterSemanticsFlagIsChecked = 1 << 1, - /// Whether a semantics node is selected. - kFlutterSemanticsFlagIsSelected = 1 << 2, - /// Whether the semantic node represents a button. - kFlutterSemanticsFlagIsButton = 1 << 3, - /// Whether the semantic node represents a text field. - kFlutterSemanticsFlagIsTextField = 1 << 4, - /// Whether the semantic node currently holds the user's focus. - kFlutterSemanticsFlagIsFocused = 1 << 5, - /// The semantics node has the quality of either being "enabled" or - /// "disabled". - kFlutterSemanticsFlagHasEnabledState = 1 << 6, - /// Whether a semantic node that hasEnabledState is currently enabled. - kFlutterSemanticsFlagIsEnabled = 1 << 7, - /// Whether a semantic node is in a mutually exclusive group. - kFlutterSemanticsFlagIsInMutuallyExclusiveGroup = 1 << 8, - /// Whether a semantic node is a header that divides content into sections. - kFlutterSemanticsFlagIsHeader = 1 << 9, - /// Whether the value of the semantics node is obscured. - kFlutterSemanticsFlagIsObscured = 1 << 10, - /// Whether the semantics node is the root of a subtree for which a route name - /// should be announced. - kFlutterSemanticsFlagScopesRoute = 1 << 11, - /// Whether the semantics node label is the name of a visually distinct route. - kFlutterSemanticsFlagNamesRoute = 1 << 12, - /// Whether the semantics node is considered hidden. - kFlutterSemanticsFlagIsHidden = 1 << 13, - /// Whether the semantics node represents an image. - kFlutterSemanticsFlagIsImage = 1 << 14, - /// Whether the semantics node is a live region. - kFlutterSemanticsFlagIsLiveRegion = 1 << 15, - /// The semantics node has the quality of either being "on" or "off". - kFlutterSemanticsFlagHasToggledState = 1 << 16, - /// If true, the semantics node is "on". If false, the semantics node is - /// "off". - kFlutterSemanticsFlagIsToggled = 1 << 17, - /// Whether the platform can scroll the semantics node when the user attempts - /// to move the accessibility focus to an offscreen child. - /// - /// For example, a `ListView` widget has implicit scrolling so that users can - /// easily move the accessibility focus to the next set of children. A - /// `PageView` widget does not have implicit scrolling, so that users don't - /// navigate to the next page when reaching the end of the current one. - kFlutterSemanticsFlagHasImplicitScrolling = 1 << 18, - /// Whether the semantic node is read only. - /// - /// Only applicable when kFlutterSemanticsFlagIsTextField flag is on. - kFlutterSemanticsFlagIsReadOnly = 1 << 20, - /// Whether the semantic node can hold the user's focus. - kFlutterSemanticsFlagIsFocusable = 1 << 21, -} FlutterSemanticsFlag; - -typedef enum { - /// Text has unknown text direction. - kFlutterTextDirectionUnknown = 0, - /// Text is read from right to left. - kFlutterTextDirectionRTL = 1, - /// Text is read from left to right. - kFlutterTextDirectionLTR = 2, -} FlutterTextDirection; - -typedef struct _FlutterEngine* FLUTTER_API_SYMBOL(FlutterEngine); - -typedef struct { - /// horizontal scale factor - double scaleX; - /// horizontal skew factor - double skewX; - /// horizontal translation - double transX; - /// vertical skew factor - double skewY; - /// vertical scale factor - double scaleY; - /// vertical translation - double transY; - /// input x-axis perspective factor - double pers0; - /// input y-axis perspective factor - double pers1; - /// perspective scale factor - double pers2; -} FlutterTransformation; - -typedef void (*VoidCallback)(void* /* user data */); - -typedef enum { - /// Specifies an OpenGL texture target type. Textures are specified using - /// the FlutterOpenGLTexture struct. - kFlutterOpenGLTargetTypeTexture, - /// Specifies an OpenGL frame-buffer target type. Framebuffers are specified - /// using the FlutterOpenGLFramebuffer struct. - kFlutterOpenGLTargetTypeFramebuffer, -} FlutterOpenGLTargetType; - -typedef struct { - /// Target texture of the active texture unit (example GL_TEXTURE_2D). - uint32_t target; - /// The name of the texture. - uint32_t name; - /// The texture format (example GL_RGBA8). - uint32_t format; - /// User data to be returned on the invocation of the destruction callback. - void* user_data; - /// Callback invoked (on an engine managed thread) that asks the embedder to - /// collect the texture. - VoidCallback destruction_callback; -} FlutterOpenGLTexture; - -typedef struct { - /// The target of the color attachment of the frame-buffer. For example, - /// GL_TEXTURE_2D or GL_RENDERBUFFER. In case of ambiguity when dealing with - /// Window bound frame-buffers, 0 may be used. - uint32_t target; - - /// The name of the framebuffer. - uint32_t name; - - /// User data to be returned on the invocation of the destruction callback. - void* user_data; - - /// Callback invoked (on an engine managed thread) that asks the embedder to - /// collect the framebuffer. - VoidCallback destruction_callback; -} FlutterOpenGLFramebuffer; - -typedef bool (*BoolCallback)(void* /* user data */); -typedef FlutterTransformation (*TransformationCallback)(void* /* user data */); -typedef uint32_t (*UIntCallback)(void* /* user data */); -typedef bool (*SoftwareSurfacePresentCallback)(void* /* user data */, - const void* /* allocation */, - size_t /* row bytes */, - size_t /* height */); -typedef void* (*ProcResolver)(void* /* user data */, const char* /* name */); -typedef bool (*TextureFrameCallback)(void* /* user data */, - int64_t /* texture identifier */, - size_t /* width */, - size_t /* height */, - FlutterOpenGLTexture* /* texture out */); -typedef void (*VsyncCallback)(void* /* user data */, intptr_t /* baton */); - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterOpenGLRendererConfig). - size_t struct_size; - BoolCallback make_current; - BoolCallback clear_current; - BoolCallback present; - UIntCallback fbo_callback; - /// This is an optional callback. Flutter will ask the emebdder to create a GL - /// context current on a background thread. If the embedder is able to do so, - /// Flutter will assume that this context is in the same sharegroup as the - /// main rendering context and use this context for asynchronous texture - /// uploads. Though optional, it is recommended that all embedders set this - /// callback as it will lead to better performance in texture handling. - BoolCallback make_resource_current; - /// By default, the renderer config assumes that the FBO does not change for - /// the duration of the engine run. If this argument is true, the - /// engine will ask the embedder for an updated FBO target (via an - /// fbo_callback invocation) after a present call. - bool fbo_reset_after_present; - /// The transformation to apply to the render target before any rendering - /// operations. This callback is optional. - /// @attention When using a custom compositor, the layer offset and sizes - /// will be affected by this transformation. It will be - /// embedder responsibility to render contents at the - /// transformed offset and size. This is useful for embedders - /// that want to render transformed contents directly into - /// hardware overlay planes without having to apply extra - /// transformations to layer contents (which may necessitate - /// an expensive off-screen render pass). - TransformationCallback surface_transformation; - ProcResolver gl_proc_resolver; - /// When the embedder specifies that a texture has a frame available, the - /// engine will call this method (on an internal engine managed thread) so - /// that external texture details can be supplied to the engine for subsequent - /// composition. - TextureFrameCallback gl_external_texture_frame_callback; -} FlutterOpenGLRendererConfig; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterSoftwareRendererConfig). - size_t struct_size; - /// The callback presented to the embedder to present a fully populated buffer - /// to the user. The pixel format of the buffer is the native 32-bit RGBA - /// format. The buffer is owned by the Flutter engine and must be copied in - /// this callback if needed. - SoftwareSurfacePresentCallback surface_present_callback; -} FlutterSoftwareRendererConfig; - -typedef struct { - FlutterRendererType type; - union { - FlutterOpenGLRendererConfig open_gl; - FlutterSoftwareRendererConfig software; - }; -} FlutterRendererConfig; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterWindowMetricsEvent). - size_t struct_size; - /// Physical width of the window. - size_t width; - /// Physical height of the window. - size_t height; - /// Scale factor for the physical screen. - double pixel_ratio; -} FlutterWindowMetricsEvent; - -/// The phase of the pointer event. -typedef enum { - kCancel, - /// The pointer, which must have been down (see kDown), is now up. - /// - /// For touch, this means that the pointer is no longer in contact with the - /// screen. For a mouse, it means the last button was released. Note that if - /// any other buttons are still pressed when one button is released, that - /// should be sent as a kMove rather than a kUp. - kUp, - /// The pointer, which must have been been up, is now down. - /// - /// For touch, this means that the pointer has come into contact with the - /// screen. For a mouse, it means a button is now pressed. Note that if any - /// other buttons are already pressed when a new button is pressed, that - /// should be sent as a kMove rather than a kDown. - kDown, - /// The pointer moved while down. - /// - /// This is also used for changes in button state that don't cause a kDown or - /// kUp, such as releasing one of two pressed buttons. - kMove, - /// The pointer is now sending input to Flutter. For instance, a mouse has - /// entered the area where the Flutter content is displayed. - /// - /// A pointer should always be added before sending any other events. - kAdd, - /// The pointer is no longer sending input to Flutter. For instance, a mouse - /// has left the area where the Flutter content is displayed. - /// - /// A removed pointer should no longer send events until sending a new kAdd. - kRemove, - /// The pointer moved while up. - kHover, -} FlutterPointerPhase; - -/// The device type that created a pointer event. -typedef enum { - kFlutterPointerDeviceKindMouse = 1, - kFlutterPointerDeviceKindTouch, -} FlutterPointerDeviceKind; - -/// Flags for the `buttons` field of `FlutterPointerEvent` when `device_kind` -/// is `kFlutterPointerDeviceKindMouse`. -typedef enum { - kFlutterPointerButtonMousePrimary = 1 << 0, - kFlutterPointerButtonMouseSecondary = 1 << 1, - kFlutterPointerButtonMouseMiddle = 1 << 2, - kFlutterPointerButtonMouseBack = 1 << 3, - kFlutterPointerButtonMouseForward = 1 << 4, - /// If a mouse has more than five buttons, send higher bit shifted values - /// corresponding to the button number: 1 << 5 for the 6th, etc. -} FlutterPointerMouseButtons; - -/// The type of a pointer signal. -typedef enum { - kFlutterPointerSignalKindNone, - kFlutterPointerSignalKindScroll, -} FlutterPointerSignalKind; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterPointerEvent). - size_t struct_size; - FlutterPointerPhase phase; - /// @attention The timestamp must be specified in microseconds. - size_t timestamp; - double x; - double y; - /// An optional device identifier. If this is not specified, it is assumed - /// that the embedder has no multi-touch capability. - int32_t device; - FlutterPointerSignalKind signal_kind; - double scroll_delta_x; - double scroll_delta_y; - /// The type of the device generating this event. - /// Backwards compatibility note: If this is not set, the device will be - /// treated as a mouse, with the primary button set for `kDown` and `kMove`. - /// If set explicitly to `kFlutterPointerDeviceKindMouse`, you must set the - /// correct buttons. - FlutterPointerDeviceKind device_kind; - /// The buttons currently pressed, if any. - int64_t buttons; -} FlutterPointerEvent; - -struct _FlutterPlatformMessageResponseHandle; -typedef struct _FlutterPlatformMessageResponseHandle - FlutterPlatformMessageResponseHandle; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterPlatformMessage). - size_t struct_size; - const char* channel; - const uint8_t* message; - size_t message_size; - /// The response handle on which to invoke - /// `FlutterEngineSendPlatformMessageResponse` when the response is ready. - /// `FlutterEngineSendPlatformMessageResponse` must be called for all messages - /// received by the embedder. Failure to call - /// `FlutterEngineSendPlatformMessageResponse` will cause a memory leak. It is - /// not safe to send multiple responses on a single response object. - const FlutterPlatformMessageResponseHandle* response_handle; -} FlutterPlatformMessage; - -typedef void (*FlutterPlatformMessageCallback)( - const FlutterPlatformMessage* /* message*/, - void* /* user data */); - -typedef void (*FlutterDataCallback)(const uint8_t* /* data */, - size_t /* size */, - void* /* user data */); - -typedef struct { - double left; - double top; - double right; - double bottom; -} FlutterRect; - -/// `FlutterSemanticsNode` ID used as a sentinel to signal the end of a batch of -/// semantics node updates. -FLUTTER_EXPORT -extern const int32_t kFlutterSemanticsNodeIdBatchEnd; - -/// A node that represents some semantic data. -/// -/// The semantics tree is maintained during the semantics phase of the pipeline -/// (i.e., during PipelineOwner.flushSemantics), which happens after -/// compositing. Updates are then pushed to embedders via the registered -/// `FlutterUpdateSemanticsNodeCallback`. -typedef struct { - /// The size of this struct. Must be sizeof(FlutterSemanticsNode). - size_t struct_size; - /// The unique identifier for this node. - int32_t id; - /// The set of semantics flags associated with this node. - FlutterSemanticsFlag flags; - /// The set of semantics actions applicable to this node. - FlutterSemanticsAction actions; - /// The position at which the text selection originates. - int32_t text_selection_base; - /// The position at which the text selection terminates. - int32_t text_selection_extent; - /// The total number of scrollable children that contribute to semantics. - int32_t scroll_child_count; - /// The index of the first visible semantic child of a scroll node. - int32_t scroll_index; - /// The current scrolling position in logical pixels if the node is - /// scrollable. - double scroll_position; - /// The maximum in-range value for `scrollPosition` if the node is scrollable. - double scroll_extent_max; - /// The minimum in-range value for `scrollPosition` if the node is scrollable. - double scroll_extent_min; - /// The elevation along the z-axis at which the rect of this semantics node is - /// located above its parent. - double elevation; - /// Describes how much space the semantics node takes up along the z-axis. - double thickness; - /// A textual description of the node. - const char* label; - /// A brief description of the result of performing an action on the node. - const char* hint; - /// A textual description of the current value of the node. - const char* value; - /// A value that `value` will have after a kFlutterSemanticsActionIncrease` - /// action has been performed. - const char* increased_value; - /// A value that `value` will have after a kFlutterSemanticsActionDecrease` - /// action has been performed. - const char* decreased_value; - /// The reading direction for `label`, `value`, `hint`, `increasedValue`, and - /// `decreasedValue`. - FlutterTextDirection text_direction; - /// The bounding box for this node in its coordinate system. - FlutterRect rect; - /// The transform from this node's coordinate system to its parent's - /// coordinate system. - FlutterTransformation transform; - /// The number of children this node has. - size_t child_count; - /// Array of child node IDs in traversal order. Has length `child_count`. - const int32_t* children_in_traversal_order; - /// Array of child node IDs in hit test order. Has length `child_count`. - const int32_t* children_in_hit_test_order; - /// The number of custom accessibility action associated with this node. - size_t custom_accessibility_actions_count; - /// Array of `FlutterSemanticsCustomAction` IDs associated with this node. - /// Has length `custom_accessibility_actions_count`. - const int32_t* custom_accessibility_actions; -} FlutterSemanticsNode; - -/// `FlutterSemanticsCustomAction` ID used as a sentinel to signal the end of a -/// batch of semantics custom action updates. -FLUTTER_EXPORT -extern const int32_t kFlutterSemanticsCustomActionIdBatchEnd; - -/// A custom semantics action, or action override. -/// -/// Custom actions can be registered by applications in order to provide -/// semantic actions other than the standard actions available through the -/// `FlutterSemanticsAction` enum. -/// -/// Action overrides are custom actions that the application developer requests -/// to be used in place of the standard actions in the `FlutterSemanticsAction` -/// enum. -typedef struct { - /// The size of the struct. Must be sizeof(FlutterSemanticsCustomAction). - size_t struct_size; - /// The unique custom action or action override ID. - int32_t id; - /// For overridden standard actions, corresponds to the - /// `FlutterSemanticsAction` to override. - FlutterSemanticsAction override_action; - /// The user-readable name of this custom semantics action. - const char* label; - /// The hint description of this custom semantics action. - const char* hint; -} FlutterSemanticsCustomAction; - -typedef void (*FlutterUpdateSemanticsNodeCallback)( - const FlutterSemanticsNode* /* semantics node */, - void* /* user data */); - -typedef void (*FlutterUpdateSemanticsCustomActionCallback)( - const FlutterSemanticsCustomAction* /* semantics custom action */, - void* /* user data */); - -typedef struct _FlutterTaskRunner* FlutterTaskRunner; - -typedef struct { - FlutterTaskRunner runner; - uint64_t task; -} FlutterTask; - -typedef void (*FlutterTaskRunnerPostTaskCallback)( - FlutterTask /* task */, - uint64_t /* target time nanos */, - void* /* user data */); - -/// An interface used by the Flutter engine to execute tasks at the target time -/// on a specified thread. There should be a 1-1 relationship between a thread -/// and a task runner. It is undefined behavior to run a task on a thread that -/// is not associated with its task runner. -typedef struct { - /// The size of this struct. Must be sizeof(FlutterTaskRunnerDescription). - size_t struct_size; - void* user_data; - /// May be called from any thread. Should return true if tasks posted on the - /// calling thread will be run on that same thread. - /// - /// @attention This field is required. - BoolCallback runs_task_on_current_thread_callback; - /// May be called from any thread. The given task should be executed by the - /// embedder on the thread associated with that task runner by calling - /// `FlutterEngineRunTask` at the given target time. The system monotonic - /// clock should be used for the target time. The target time is the absolute - /// time from epoch (NOT a delta) at which the task must be returned back to - /// the engine on the correct thread. If the embedder needs to calculate a - /// delta, `FlutterEngineGetCurrentTime` may be called and the difference used - /// as the delta. - /// - /// @attention This field is required. - FlutterTaskRunnerPostTaskCallback post_task_callback; -} FlutterTaskRunnerDescription; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterCustomTaskRunners). - size_t struct_size; - /// Specify the task runner for the thread on which the `FlutterEngineRun` - /// call is made. - const FlutterTaskRunnerDescription* platform_task_runner; -} FlutterCustomTaskRunners; - -typedef struct { - /// The type of the OpenGL backing store. Currently, it can either be a - /// texture or a framebuffer. - FlutterOpenGLTargetType type; - union { - /// A texture for Flutter to render into. - FlutterOpenGLTexture texture; - /// A framebuffer for Flutter to render into. The embedder must ensure that - /// the framebuffer is complete. - FlutterOpenGLFramebuffer framebuffer; - }; -} FlutterOpenGLBackingStore; - -typedef struct { - /// A pointer to the raw bytes of the allocation described by this software - /// backing store. - const void* allocation; - /// The number of bytes in a single row of the allocation. - size_t row_bytes; - /// The number of rows in the allocation. - size_t height; - /// A baton that is not interpreted by the engine in any way. It will be given - /// back to the embedder in the destruction callback below. Embedder resources - /// may be associated with this baton. - void* user_data; - /// The callback invoked by the engine when it no longer needs this backing - /// store. - VoidCallback destruction_callback; -} FlutterSoftwareBackingStore; - -/// The identifier of the platform view. This identifier is specified by the -/// application when a platform view is added to the scene via the -/// `SceneBuilder.addPlatformView` call. -typedef int64_t FlutterPlatformViewIdentifier; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterPlatformView). - size_t struct_size; - /// The identifier of this platform view. This identifier is specified by the - /// application when a platform view is added to the scene via the - /// `SceneBuilder.addPlatformView` call. - FlutterPlatformViewIdentifier identifier; -} FlutterPlatformView; - -typedef enum { - /// Specifies an OpenGL backing store. Can either be an OpenGL texture or - /// framebuffer. - kFlutterBackingStoreTypeOpenGL, - /// Specified an software allocation for Flutter to render into using the CPU. - kFlutterBackingStoreTypeSoftware, -} FlutterBackingStoreType; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterBackingStore). - size_t struct_size; - /// A baton that is not interpreted by the engine in any way. The embedder may - /// use this to associate resources that are tied to the lifecycle of the - /// `FlutterBackingStore`. - void* user_data; - /// Specifies the type of backing store. - FlutterBackingStoreType type; - /// Indicates if this backing store was updated since the last time it was - /// associated with a presented layer. - bool did_update; - union { - /// The description of the OpenGL backing store. - FlutterOpenGLBackingStore open_gl; - /// The description of the software backing store. - FlutterSoftwareBackingStore software; - }; -} FlutterBackingStore; - -typedef struct { - double x; - double y; -} FlutterPoint; - -typedef struct { - double width; - double height; -} FlutterSize; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterBackingStoreConfig). - size_t struct_size; - /// The size of the render target the engine expects to render into. - FlutterSize size; -} FlutterBackingStoreConfig; - -typedef enum { - /// Indicates that the contents of this layer are rendered by Flutter into a - /// backing store. - kFlutterLayerContentTypeBackingStore, - /// Indicates that the contents of this layer are determined by the embedder. - kFlutterLayerContentTypePlatformView, -} FlutterLayerContentType; - -typedef struct { - /// This size of this struct. Must be sizeof(FlutterLayer). - size_t struct_size; - /// Each layer displays contents in one way or another. The type indicates - /// whether those contents are specified by Flutter or the embedder. - FlutterLayerContentType type; - union { - /// Indicates that the contents of this layer are rendered by Flutter into a - /// backing store. - const FlutterBackingStore* backing_store; - /// Indicates that the contents of this layer are determined by the - /// embedder. - const FlutterPlatformView* platform_view; - }; - /// The offset of this layer (in physical pixels) relative to the top left of - /// the root surface used by the engine. - FlutterPoint offset; - /// The size of the layer (in physical pixels). - FlutterSize size; -} FlutterLayer; - -typedef bool (*FlutterBackingStoreCreateCallback)( - const FlutterBackingStoreConfig* config, - FlutterBackingStore* backing_store_out, - void* user_data); - -typedef bool (*FlutterBackingStoreCollectCallback)( - const FlutterBackingStore* renderer, - void* user_data); - -typedef bool (*FlutterLayersPresentCallback)(const FlutterLayer** layers, - size_t layers_count, - void* user_data); - -typedef struct { - /// This size of this struct. Must be sizeof(FlutterCompositor). - size_t struct_size; - /// A baton that in not interpreted by the engine in any way. If it passed - /// back to the embedder in `FlutterCompositor.create_backing_store_callback`, - /// `FlutterCompositor.collect_backing_store_callback` and - /// `FlutterCompositor.present_layers_callback` - void* user_data; - /// A callback invoked by the engine to obtain a backing store for a specific - /// `FlutterLayer`. - /// - /// On ABI stability: Callers must take care to restrict access within - /// `FlutterBackingStore::struct_size` when specifying a new backing store to - /// the engine. This only matters if the embedder expects to be used with - /// engines older than the version whose headers it used during compilation. - FlutterBackingStoreCreateCallback create_backing_store_callback; - /// A callback invoked by the engine to release the backing store. The - /// embedder may collect any resources associated with the backing store. - FlutterBackingStoreCollectCallback collect_backing_store_callback; - /// Callback invoked by the engine to composite the contents of each layer - /// onto the screen. - FlutterLayersPresentCallback present_layers_callback; -} FlutterCompositor; - -typedef struct { - /// The size of this struct. Must be sizeof(FlutterProjectArgs). - size_t struct_size; - /// The path to the Flutter assets directory containing project assets. The - /// string can be collected after the call to `FlutterEngineRun` returns. The - /// string must be NULL terminated. - const char* assets_path; - /// The path to the Dart file containing the `main` entry point. - /// The string can be collected after the call to `FlutterEngineRun` returns. - /// The string must be NULL terminated. - /// - /// @deprecated As of Dart 2, running from Dart source is no longer - /// supported. Dart code should now be compiled to kernel form - /// and will be loaded by from `kernel_blob.bin` in the assets - /// directory. This struct member is retained for ABI - /// stability. - const char* main_path__unused__; - /// The path to the `.packages` file for the project. The string can be - /// collected after the call to `FlutterEngineRun` returns. The string must be - /// NULL terminated. - /// - /// @deprecated As of Dart 2, running from Dart source is no longer - /// supported. Dart code should now be compiled to kernel form - /// and will be loaded by from `kernel_blob.bin` in the assets - /// directory. This struct member is retained for ABI - /// stability. - const char* packages_path__unused__; - /// The path to the `icudtl.dat` file for the project. The string can be - /// collected after the call to `FlutterEngineRun` returns. The string must - /// be NULL terminated. - const char* icu_data_path; - /// The command line argument count used to initialize the project. - int command_line_argc; - /// The command line arguments used to initialize the project. The strings can - /// be collected after the call to `FlutterEngineRun` returns. The strings - /// must be `NULL` terminated. - /// - /// @attention The first item in the command line (if specified at all) is - /// interpreted as the executable name. So if an engine flag - /// needs to be passed into the same, it needs to not be the - /// very first item in the list. - /// - /// The set of engine flags are only meant to control - /// unstable features in the engine. Deployed applications should not pass any - /// command line arguments at all as they may affect engine stability at - /// runtime in the presence of un-sanitized input. The list of currently - /// recognized engine flags and their descriptions can be retrieved from the - /// `switches.h` engine source file. - const char* const* command_line_argv; - /// The callback invoked by the engine in order to give the embedder the - /// chance to respond to platform messages from the Dart application. The - /// callback will be invoked on the thread on which the `FlutterEngineRun` - /// call is made. - FlutterPlatformMessageCallback platform_message_callback; - /// The VM snapshot data buffer used in AOT operation. This buffer must be - /// mapped in as read-only. For more information refer to the documentation on - /// the Wiki at - /// https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode - const uint8_t* vm_snapshot_data; - /// The size of the VM snapshot data buffer. If vm_snapshot_data is a symbol - /// reference, 0 may be passed here. - size_t vm_snapshot_data_size; - /// The VM snapshot instructions buffer used in AOT operation. This buffer - /// must be mapped in as read-execute. For more information refer to the - /// documentation on the Wiki at - /// https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode - const uint8_t* vm_snapshot_instructions; - /// The size of the VM snapshot instructions buffer. If - /// vm_snapshot_instructions is a symbol reference, 0 may be passed here. - size_t vm_snapshot_instructions_size; - /// The isolate snapshot data buffer used in AOT operation. This buffer must - /// be mapped in as read-only. For more information refer to the documentation - /// on the Wiki at - /// https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode - const uint8_t* isolate_snapshot_data; - /// The size of the isolate snapshot data buffer. If isolate_snapshot_data is - /// a symbol reference, 0 may be passed here. - size_t isolate_snapshot_data_size; - /// The isolate snapshot instructions buffer used in AOT operation. This - /// buffer must be mapped in as read-execute. For more information refer to - /// the documentation on the Wiki at - /// https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode - const uint8_t* isolate_snapshot_instructions; - /// The size of the isolate snapshot instructions buffer. If - /// isolate_snapshot_instructions is a symbol reference, 0 may be passed here. - size_t isolate_snapshot_instructions_size; - /// The callback invoked by the engine in root isolate scope. Called - /// immediately after the root isolate has been created and marked runnable. - VoidCallback root_isolate_create_callback; - /// The callback invoked by the engine in order to give the embedder the - /// chance to respond to semantics node updates from the Dart application. - /// Semantics node updates are sent in batches terminated by a 'batch end' - /// callback that is passed a sentinel `FlutterSemanticsNode` whose `id` field - /// has the value `kFlutterSemanticsNodeIdBatchEnd`. - /// - /// The callback will be invoked on the thread on which the `FlutterEngineRun` - /// call is made. - FlutterUpdateSemanticsNodeCallback update_semantics_node_callback; - /// The callback invoked by the engine in order to give the embedder the - /// chance to respond to updates to semantics custom actions from the Dart - /// application. Custom action updates are sent in batches terminated by a - /// 'batch end' callback that is passed a sentinel - /// `FlutterSemanticsCustomAction` whose `id` field has the value - /// `kFlutterSemanticsCustomActionIdBatchEnd`. - /// - /// The callback will be invoked on the thread on which the `FlutterEngineRun` - /// call is made. - FlutterUpdateSemanticsCustomActionCallback - update_semantics_custom_action_callback; - /// Path to a directory used to store data that is cached across runs of a - /// Flutter application (such as compiled shader programs used by Skia). - /// This is optional. The string must be NULL terminated. - /// - // This is different from the cache-path-dir argument defined in switches.h, - // which is used in `flutter::Settings` as `temp_directory_path`. - const char* persistent_cache_path; - - /// If true, we'll only read the existing cache, but not write new ones. - bool is_persistent_cache_read_only; - - /// A callback that gets invoked by the engine when it attempts to wait for a - /// platform vsync event. The engine will give the platform a baton that needs - /// to be returned back to the engine via `FlutterEngineOnVsync`. All batons - /// must be retured to the engine before initializing a - /// `FlutterEngineShutdown`. Not doing the same will result in a memory leak. - /// While the call to `FlutterEngineOnVsync` must occur on the thread that - /// made the call to `FlutterEngineRun`, the engine will make this callback on - /// an internal engine-managed thread. If the components accessed on the - /// embedder are not thread safe, the appropriate re-threading must be done. - VsyncCallback vsync_callback; - - /// The name of a custom Dart entrypoint. This is optional and specifying a - /// null or empty entrypoint makes the engine look for a method named "main" - /// in the root library of the application. - /// - /// Care must be taken to ensure that the custom entrypoint is not tree-shaken - /// away. Usually, this is done using the `@pragma('vm:entry-point')` - /// decoration. - const char* custom_dart_entrypoint; - - /// Typically the Flutter engine create and manages its internal threads. This - /// optional argument allows for the specification of task runner interfaces - /// to event loops managed by the embedder on threads it creates. - const FlutterCustomTaskRunners* custom_task_runners; - - /// All `FlutterEngine` instances in the process share the same Dart VM. When - /// the first engine is launched, it starts the Dart VM as well. It used to be - /// the case that it was not possible to shutdown the Dart VM cleanly and - /// start it back up in the process in a safe manner. This issue has since - /// been patched. Unfortunately, applications already began to make use of the - /// fact that shutting down the Flutter engine instance left a running VM in - /// the process. Since a Flutter engine could be launched on any thread, - /// applications would "warm up" the VM on another thread by launching - /// an engine with no isolates and then shutting it down immediately. The main - /// Flutter application could then be started on the main thread without - /// having to incur the Dart VM startup costs at that time. With the new - /// behavior, this "optimization" immediately becomes massive performance - /// pessimization as the VM would be started up in the "warm up" phase, shut - /// down there and then started again on the main thread. Changing this - /// behavior was deemed to be an unacceptable breaking change. Embedders that - /// wish to shutdown the Dart VM when the last engine is terminated in the - /// process should opt into this behavior by setting this flag to true. - bool shutdown_dart_vm_when_done; - - /// Typically, Flutter renders the layer hierarchy into a single root surface. - /// However, when embedders need to interleave their own contents within the - /// Flutter layer hierarchy, their applications can push platform views within - /// the Flutter scene. This is done using the `SceneBuilder.addPlatformView` - /// call. When this happens, the Flutter rasterizer divides the effective view - /// hierarchy into multiple layers. Each layer gets its own backing store and - /// Flutter renders into the same. Once the layers contents have been - /// fulfilled, the embedder is asked to composite these layers on-screen. At - /// this point, it can interleave its own contents within the effective - /// hierarchy. The interface for the specification of these layer backing - /// stores and the hooks to listen for the composition of layers on-screen can - /// be controlled using this field. This field is completely optional. In its - /// absence, platforms views in the scene are ignored and Flutter renders to - /// the root surface as normal. - const FlutterCompositor* compositor; -} FlutterProjectArgs; - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineRun(size_t version, - const FlutterRendererConfig* config, - const FlutterProjectArgs* args, - void* user_data, - FLUTTER_API_SYMBOL(FlutterEngine) * - engine_out); - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine) - engine); - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineSendWindowMetricsEvent( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterWindowMetricsEvent* event); - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineSendPointerEvent( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterPointerEvent* events, - size_t events_count); - -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineSendPlatformMessage( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterPlatformMessage* message); - -//------------------------------------------------------------------------------ -/// @brief Creates a platform message response handle that allows the -/// embedder to set a native callback for a response to a message. -/// This handle may be set on the `response_handle` field of any -/// `FlutterPlatformMessage` sent to the engine. -/// -/// The handle must be collected via a call to -/// `FlutterPlatformMessageReleaseResponseHandle`. This may be done -/// immediately after a call to `FlutterEngineSendPlatformMessage` -/// with a platform message whose response handle contains the handle -/// created using this call. In case a handle is created but never -/// sent in a message, the release call must still be made. Not -/// calling release on the handle results in a small memory leak. -/// -/// The user data baton passed to the data callback is the one -/// specified in this call as the third argument. -/// -/// @see FlutterPlatformMessageReleaseResponseHandle() -/// -/// @param[in] engine A running engine instance. -/// @param[in] data_callback The callback invoked by the engine when the -/// Flutter application send a response on the -/// handle. -/// @param[in] user_data The user data associated with the data callback. -/// @param[out] response_out The response handle created when this call is -/// successful. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterPlatformMessageCreateResponseHandle( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - FlutterDataCallback data_callback, - void* user_data, - FlutterPlatformMessageResponseHandle** response_out); - -//------------------------------------------------------------------------------ -/// @brief Collects the handle created using -/// `FlutterPlatformMessageCreateResponseHandle`. -/// -/// @see FlutterPlatformMessageCreateResponseHandle() -/// -/// @param[in] engine A running engine instance. -/// @param[in] response The platform message response handle to collect. -/// These handles are created using -/// `FlutterPlatformMessageCreateResponseHandle()`. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - FlutterPlatformMessageResponseHandle* response); - -//------------------------------------------------------------------------------ -/// @brief Send a response from the native side to a platform message from -/// the Dart Flutter application. -/// -/// @param[in] engine The running engine instance. -/// @param[in] handle The platform message response handle. -/// @param[in] data The data to associate with the platform message -/// response. -/// @param[in] data_length The length of the platform message response data. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineSendPlatformMessageResponse( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterPlatformMessageResponseHandle* handle, - const uint8_t* data, - size_t data_length); - -//------------------------------------------------------------------------------ -/// @brief This API is only meant to be used by platforms that need to -/// flush tasks on a message loop not controlled by the Flutter -/// engine. -/// -/// @deprecated This API will be deprecated and is not part of the stable API. -/// Please use the custom task runners API by setting an -/// appropriate `FlutterProjectArgs::custom_task_runners` -/// interface. This will yield better performance and the -/// interface is stable. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult __FlutterEngineFlushPendingTasksNow(); - -//------------------------------------------------------------------------------ -/// @brief Register an external texture with a unique (per engine) -/// identifier. Only rendering backends that support external -/// textures accept external texture registrations. After the -/// external texture is registered, the application can mark that a -/// frame is available by calling -/// `FlutterEngineMarkExternalTextureFrameAvailable`. -/// -/// @see FlutterEngineUnregisterExternalTexture() -/// @see FlutterEngineMarkExternalTextureFrameAvailable() -/// -/// @param[in] engine A running engine instance. -/// @param[in] texture_identifier The identifier of the texture to register -/// with the engine. The embedder may supply new -/// frames to this texture using the same -/// identifier. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineRegisterExternalTexture( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - int64_t texture_identifier); - -//------------------------------------------------------------------------------ -/// @brief Unregister a previous texture registration. -/// -/// @see FlutterEngineRegisterExternalTexture() -/// @see FlutterEngineMarkExternalTextureFrameAvailable() -/// -/// @param[in] engine A running engine instance. -/// @param[in] texture_identifier The identifier of the texture for which new -/// frame will not be available. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineUnregisterExternalTexture( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - int64_t texture_identifier); - -//------------------------------------------------------------------------------ -/// @brief Mark that a new texture frame is available for a given texture -/// identifier. -/// -/// @see FlutterEngineRegisterExternalTexture() -/// @see FlutterEngineUnregisterExternalTexture() -/// -/// @param[in] engine A running engine instance. -/// @param[in] texture_identifier The identifier of the texture whose frame -/// has been updated. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineMarkExternalTextureFrameAvailable( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - int64_t texture_identifier); - -//------------------------------------------------------------------------------ -/// @brief Enable or disable accessibility semantics. -/// -/// @param[in] engine A running engine instance. -/// @param[in] enabled When enabled, changes to the semantic contents of the -/// window are sent via the -/// `FlutterUpdateSemanticsNodeCallback` registered to -/// `update_semantics_node_callback` in -/// `FlutterProjectArgs`. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineUpdateSemanticsEnabled( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - bool enabled); - -//------------------------------------------------------------------------------ -/// @brief Sets additional accessibility features. -/// -/// @param[in] engine A running engine instance -/// @param[in] features The accessibility features to set. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - FlutterAccessibilityFeature features); - -//------------------------------------------------------------------------------ -/// @brief Dispatch a semantics action to the specified semantics node. -/// -/// @param[in] engine A running engine instance. -/// @param[in] identifier The semantics action identifier. -/// @param[in] action The semantics action. -/// @param[in] data Data associated with the action. -/// @param[in] data_length The data length. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineDispatchSemanticsAction( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - uint64_t id, - FlutterSemanticsAction action, - const uint8_t* data, - size_t data_length); - -//------------------------------------------------------------------------------ -/// @brief Notify the engine that a vsync event occurred. A baton passed to -/// the platform via the vsync callback must be returned. This call -/// must be made on the thread on which the call to -/// `FlutterEngineRun` was made. -/// -/// @see FlutterEngineGetCurrentTime() -/// -/// @attention That frame timepoints are in nanoseconds. -/// -/// @attention The system monotonic clock is used as the timebase. -/// -/// @param[in] engine. A running engine instance. -/// @param[in] baton The baton supplied by the engine. -/// @param[in] frame_start_time_nanos The point at which the vsync event -/// occurred or will occur. If the time -/// point is in the future, the engine will -/// wait till that point to begin its frame -/// workload. -/// @param[in] frame_target_time_nanos The point at which the embedder -/// anticipates the next vsync to occur. -/// This is a hint the engine uses to -/// schedule Dart VM garbage collection in -/// periods in which the various threads -/// are most likely to be idle. For -/// example, for a 60Hz display, embedders -/// should add 16.6 * 1e6 to the frame time -/// field. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineOnVsync(FLUTTER_API_SYMBOL(FlutterEngine) - engine, - intptr_t baton, - uint64_t frame_start_time_nanos, - uint64_t frame_target_time_nanos); - -//------------------------------------------------------------------------------ -/// @brief Reloads the system fonts in engine. -/// -/// @param[in] engine. A running engine instance. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineReloadSystemFonts( - FLUTTER_API_SYMBOL(FlutterEngine) engine); - -//------------------------------------------------------------------------------ -/// @brief A profiling utility. Logs a trace duration begin event to the -/// timeline. If the timeline is unavailable or disabled, this has -/// no effect. Must be balanced with an duration end event (via -/// `FlutterEngineTraceEventDurationEnd`) with the same name on the -/// same thread. Can be called on any thread. Strings passed into -/// the function will NOT be copied when added to the timeline. Only -/// string literals may be passed in. -/// -/// @param[in] name The name of the trace event. -/// -FLUTTER_EXPORT -void FlutterEngineTraceEventDurationBegin(const char* name); - -//----------------------------------------------------------------------------- -/// @brief A profiling utility. Logs a trace duration end event to the -/// timeline. If the timeline is unavailable or disabled, this has -/// no effect. This call must be preceded by a trace duration begin -/// call (via `FlutterEngineTraceEventDurationBegin`) with the same -/// name on the same thread. Can be called on any thread. Strings -/// passed into the function will NOT be copied when added to the -/// timeline. Only string literals may be passed in. -/// -/// @param[in] name The name of the trace event. -/// -FLUTTER_EXPORT -void FlutterEngineTraceEventDurationEnd(const char* name); - -//----------------------------------------------------------------------------- -/// @brief A profiling utility. Logs a trace duration instant event to the -/// timeline. If the timeline is unavailable or disabled, this has -/// no effect. Can be called on any thread. Strings passed into the -/// function will NOT be copied when added to the timeline. Only -/// string literals may be passed in. -/// -/// @param[in] name The name of the trace event. -/// -FLUTTER_EXPORT -void FlutterEngineTraceEventInstant(const char* name); - -//------------------------------------------------------------------------------ -/// @brief Posts a task onto the Flutter render thread. Typically, this may -/// be called from any thread as long as a `FlutterEngineShutdown` -/// on the specific engine has not already been initiated. -/// -/// @param[in] engine A running engine instance. -/// @param[in] callback The callback to execute on the render thread. -/// @param callback_data The callback context. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEnginePostRenderThreadTask( - FLUTTER_API_SYMBOL(FlutterEngine) engine, - VoidCallback callback, - void* callback_data); - -//------------------------------------------------------------------------------ -/// @brief Get the current time in nanoseconds from the clock used by the -/// flutter engine. This is the system monotonic clock. -/// -/// @return The current time in nanoseconds. -/// -FLUTTER_EXPORT -uint64_t FlutterEngineGetCurrentTime(); - -//------------------------------------------------------------------------------ -/// @brief Inform the engine to run the specified task. This task has been -/// given to the engine via the -/// `FlutterTaskRunnerDescription.post_task_callback`. This call -/// must only be made at the target time specified in that callback. -/// Running the task before that time is undefined behavior. -/// -/// @param[in] engine a running instance. -/// @param[in] task the task handle. -/// -/// @return The result of the call. -/// -FLUTTER_EXPORT -FlutterEngineResult FlutterEngineRunTask(FLUTTER_API_SYMBOL(FlutterEngine) - engine, - const FlutterTask* task); - -#if defined(__cplusplus) -} // extern "C" -#endif - -#endif // FLUTTER_EMBEDDER_H_ diff --git a/src/flutter-pi.c b/src/flutter-pi.c index 8bbbcb54..cef7eb4c 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -41,19 +41,24 @@ Options:\n\ -t Path to the touchscreen device file. Typically /dev/input/touchscreenX or /dev/input/eventX\n\ "; -// width & height of the display in pixels +/// width & height of the display in pixels uint32_t width, height; -// physical width & height of the display in millimeters -uint32_t width_mm, height_mm; +/// physical width & height of the display in millimeters +/// the physical size can only be queried for HDMI displays (and even then, most displays will +/// probably return bogus values like 160mm x 90mm). +/// for DSI displays, the physical size of the official 7-inch display will be set in init_display. +/// init_display will only update width_mm and height_mm if they are set to zero, allowing you +/// to hardcode values for you individual display. +uint32_t width_mm = 0, height_mm = 0; uint32_t refresh_rate; -// this is the pixel ratio (used by flutter) for the Official Raspberry Pi 7inch display. -// if a HDMI screen is connected and being used by this application, the pixel ratio will be -// computed inside init_display. -// for DSI the pixel ratio can not be calculated, because there's no (general) way to query the physical -// size for DSI displays. -double pixel_ratio = 1.3671; +/// The pixel ratio used by flutter. +/// This is computed inside init_display using width_mm and height_mm. +/// flutter only accepts pixel ratios >= 1.0 +/// init_display will only update this value if it is equal to zero, +/// allowing you to hardcode values. +double pixel_ratio = 0.0; struct { char device[128]; @@ -104,7 +109,7 @@ struct { char device_path[128]; int fd; double x, y; - uint8_t button; + uint8_t buttons; struct TouchscreenSlot ts_slots[10]; struct TouchscreenSlot* ts_slot; bool is_mouse; @@ -564,8 +569,24 @@ bool init_display(void) { if ((connector == NULL) && (conn->connection == DRM_MODE_CONNECTED)) { connector = conn; - width_mm = conn->mmWidth; - height_mm = conn->mmHeight; + + // only update the physical size of the display if the values + // are not yet initialized / not set with a commandline option + if (!((width_mm == 0) && (height_mm == 0))) { + if ((conn->mmWidth == 160) && (conn->mmHeight == 90)) { + // if width and height is exactly 160mm x 90mm, the values are probably bogus. + width_mm = 0; + height_mm = 0; + } else if ((conn->connector_type == DRM_MODE_CONNECTOR_DSI) && (conn->mmWidth == 0) && (conn->mmHeight == 0)) { + // if it's connected via DSI, and the width & height are 0, + // it's probably the official 7 inch touchscreen. + width_mm = 155; + height_mm = 86; + } else { + width_mm = conn->mmWidth; + height_mm = conn->mmHeight; + } + } } else { drmModeFreeConnector(conn); } @@ -588,19 +609,16 @@ bool init_display(void) { // we choose the highest resolution with the highest refresh rate, preferably non-interlaced (= progressive) here. int current_area = current_mode->hdisplay * current_mode->vdisplay; if (( current_area > area) || - ((current_area == area) && (drm.mode->vrefresh > refresh_rate)) || - ((current_area == area) && (drm.mode->vrefresh == refresh_rate) && ((drm.mode->flags & DRM_MODE_FLAG_INTERLACE) == 0)) || + ((current_area == area) && (current_mode->vrefresh > refresh_rate)) || + ((current_area == area) && (current_mode->vrefresh == refresh_rate) && ((current_mode->flags & DRM_MODE_FLAG_INTERLACE) == 0)) || ( current_mode->type & DRM_MODE_TYPE_PREFERRED)) { drm.mode = current_mode; - width = drm.mode->hdisplay; - height = drm.mode->vdisplay; - refresh_rate = drm.mode->vrefresh; + width = current_mode->hdisplay; + height = current_mode->vdisplay; + refresh_rate = current_mode->vrefresh; area = current_area; - if (width_mm) pixel_ratio = (10.0 * width) / (width_mm * 38.0); - if (pixel_ratio < 1.0) pixel_ratio = 1.0; - // if the preferred DRM mode is bogus, we're screwed. if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { printf("the chosen DRM mode is preferred by DRM. (DRM_MODE_TYPE_PREFERRED)\n"); @@ -612,6 +630,16 @@ bool init_display(void) { fprintf(stderr, "could not find a suitable DRM mode!\n"); return false; } + + // calculate the pixel ratio + if (pixel_ratio == 0.0) { + if ((width_mm == 0) || (height_mm == 0)) { + pixel_ratio = 1.0; + } else { + pixel_ratio = (10.0 * width) / (width_mm * 38.0); + if (pixel_ratio < 1.0) pixel_ratio = 1.0; + } + } printf("Display properties:\n %u x %u, %uHz\n %umm x %umm\n pixel_ratio = %f\n", width, height, refresh_rate, width_mm, height_mm, pixel_ratio); @@ -946,8 +974,11 @@ void* io_loop(void* userdata) { int n_pointerevents = 0; bool ok; - // first, tell flutter that the mouse is inside the engine window + if (input.is_mouse) { + + // first, tell flutter that the mouse is inside the engine window + printf("sending initial mouse pointer event to flutter\n"); ok = FlutterEngineSendPointerEvent( engine, & (FlutterPointerEvent) { @@ -956,13 +987,14 @@ void* io_loop(void* userdata) { .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), .x = input.x, .y = input.y, - .signal_kind = kFlutterPointerSignalKindNone + .signal_kind = kFlutterPointerSignalKindNone, + .device_kind = kFlutterPointerDeviceKindMouse, + .buttons = 0 }, 1 ) == kSuccess; if (!ok) return false; - // mouse while (1) { // read up to 64 input events int rd = read(input.fd, &event, sizeof(struct input_event)*64); @@ -981,25 +1013,25 @@ void* io_loop(void* userdata) { if (ev->type == EV_REL) { if (ev->code == REL_X) { // mouse moved in the x-direction input.x += ev->value; - phase = input.button ? kMove : kHover; + phase = input.buttons ? kMove : kHover; } else if (ev->code == REL_Y) { // mouse moved in the y-direction input.y += ev->value; - phase = input.button ? kMove : kHover; + phase = input.buttons ? kMove : kHover; } } else if (ev->type == EV_ABS) { if (ev->code == ABS_X) { input.x = ev->value; - phase = input.button ? kMove : kHover; + phase = input.buttons ? kMove : kHover; } else if (ev->code == ABS_Y) { input.y = ev->value; - phase = input.button ? kMove : kHover; + phase = input.buttons ? kMove : kHover; } } else if ((ev->type == EV_KEY) && ((ev->code == BTN_LEFT) || (ev->code == BTN_RIGHT))) { // either the left or the right mouse button was pressed - // the 1st bit in "button" is set when BTN_LEFT is down. the 2nd bit when BTN_RIGHT is down. - uint8_t mask = ev->code == BTN_LEFT ? 1 : 2; - if (ev->value == 1) input.button |= mask; - else input.button &= ~mask; + // the 1st bit in "buttons" is set when BTN_LEFT is down. the 2nd bit when BTN_RIGHT is down. + uint8_t mask = ev->code == BTN_LEFT ? kFlutterPointerButtonMousePrimary : kFlutterPointerButtonMouseSecondary; + if (ev->value == 1) input.buttons |= mask; + else input.buttons &= ~mask; phase = ev->value == 1 ? kDown : kUp; } @@ -1010,9 +1042,12 @@ void* io_loop(void* userdata) { engine, & (FlutterPointerEvent) { .struct_size = sizeof(FlutterPointerEvent), + .phase=phase, .timestamp = (size_t) (ev->time.tv_sec * 1000000ul) + ev->time.tv_usec, - .phase=phase, .x=input.x, .y=input.y, - .signal_kind = kFlutterPointerSignalKindNone + .x=input.x, .y=input.y, + .signal_kind = kFlutterPointerSignalKindNone, + .device_kind = kFlutterPointerDeviceKindMouse, + .buttons = input.buttons }, 1 ) == kSuccess; @@ -1035,7 +1070,8 @@ void* io_loop(void* userdata) { .device = j, .x = 0, .y = 0, - .signal_kind = kFlutterPointerSignalKindNone + .signal_kind = kFlutterPointerSignalKindNone, + .device_kind = kFlutterPointerDeviceKindTouch }, 1 ) == kSuccess; @@ -1085,23 +1121,26 @@ void* io_loop(void* userdata) { .device = j, .x = input.ts_slots[j].x, .y = input.ts_slots[j].y, - .signal_kind = kFlutterPointerSignalKindNone + .signal_kind = kFlutterPointerSignalKindNone, + .device_kind = kFlutterPointerDeviceKindTouch }; input.ts_slots[j].phase = kCancel; } } } } - - ok = FlutterEngineSendPointerEvent( - engine, - pointer_event, - n_pointerevents - ) == kSuccess; - - if (!ok) { - fprintf(stderr, "Error sending pointer events to flutter\n"); - return false; + + if (n_pointerevents > 0) { + ok = FlutterEngineSendPointerEvent( + engine, + pointer_event, + n_pointerevents + ) == kSuccess; + + if (!ok) { + fprintf(stderr, "Error sending pointer events to flutter\n"); + return false; + } } } } From 19f153ae057294101802a571b14355ba0f39d9aa Mon Sep 17 00:00:00 2001 From: ardera <2488440+ardera@users.noreply.github.com> Date: Wed, 6 Nov 2019 16:52:16 +0100 Subject: [PATCH 6/6] Updated ELM327-plugin, added Makefile - ELM327-Plugin now works properly and with higher polling-speed - added makefile for easier compilation (no cross-compilation for now) - fixed incorrect dpi calculation --- .gitignore | 2 +- Makefile | 21 ++ include/flutter-pi.h | 4 +- include/platformchannel.h | 8 +- src/flutter-pi.c | 118 ++++----- src/platformchannel.c | 14 +- src/pluginregistry.c | 6 +- src/plugins/elm327plugin.c | 501 ++++++++++++++++++++++--------------- src/plugins/elm327plugin.h | 26 +- 9 files changed, 396 insertions(+), 304 deletions(-) create mode 100644 Makefile diff --git a/.gitignore b/.gitignore index 0b9da9c5..b755a331 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ out .vscode -Makefile \ No newline at end of file +build.sh \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..74ff9bff --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +CC = cc +LD = cc +REAL_CFLAGS = -I./include $(shell pkg-config --cflags dri gbm libdrm glesv2 egl) -D_GNU_SOURCE -DBUILD_ELM327PLUGIN $(CFLAGS) +REAL_LDFLAGS = $(shell pkg-config --libs dri gbm libdrm glesv2 egl) -lrt -lflutter_engine -lpthread -ldl $(LDFLAGS) + +SOURCES = src/flutter-pi.c src/platformchannel.c src/pluginregistry.c src/plugins/elm327plugin.c src/plugins/services-plugin.c src/plugins/testplugin.c +OBJECTS = $(patsubst src/%.c,out/obj/%.o,$(SOURCES)) + +all: out/flutter-pi + +out/obj/%.o: src/%.c + @mkdir -p $(@D) + $(CC) -c $(REAL_CFLAGS) $(REAL_LDFLAGS) $< -o $@ + +out/flutter-pi: $(OBJECTS) + @mkdir -p $(@D) + $(CC) $(REAL_CFLAGS) $(REAL_LDFLAGS) $(OBJECTS) -o out/flutter-pi + +clean: + @mkdir -p out + rm -rf $(OBJECTS) out/flutter-pi out/obj/* \ No newline at end of file diff --git a/include/flutter-pi.h b/include/flutter-pi.h index 2ff689f5..94fa3c22 100644 --- a/include/flutter-pi.h +++ b/include/flutter-pi.h @@ -9,8 +9,8 @@ #define EGL_PLATFORM_GBM_KHR 0x31D7 -struct LinkedTaskListElement { - struct LinkedTaskListElement* next; +struct FlutterPiTask { + struct FlutterPiTask* next; bool is_vblank_event; union { FlutterTask task; diff --git a/include/platformchannel.h b/include/platformchannel.h index e5658984..f454dd59 100644 --- a/include/platformchannel.h +++ b/include/platformchannel.h @@ -201,10 +201,10 @@ int PlatformChannel_encode(struct ChannelObject *object, uint8_t **buffer_out, s /// Encodes a generic ChannelObject (anything, string/binary codec or Standard/JSON Method Calls and responses) as a platform message /// and sends it to flutter on channel `channel` -/// When flutter responds to this message, it is automatically decoded using the codec given in `response_codec`. -/// Then, on_response is called with the decoded ChannelObject and the userdata as an argument. -/// Flutter _should_ always respond to platform messages, so it's okay if not calling your handler would cause a memory leak -/// (since that should never happen) +/// If you supply a response callback (i.e. on_response is != NULL): +/// when flutter responds to this message, it is automatically decoded using the codec given in `response_codec`. +/// Then, on_response is called with the decoded ChannelObject and the userdata as an argument. +/// It's possible that flutter won't respond to your platform message, like when sending events via an EventChannel. /// userdata can be NULL. int PlatformChannel_send(char *channel, struct ChannelObject *object, enum ChannelCodec response_codec, PlatformMessageResponseCallback on_response, void *userdata); diff --git a/src/flutter-pi.c b/src/flutter-pi.c index cef7eb4c..6c05be41 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -118,16 +119,14 @@ struct { pthread_t io_thread_id; pthread_t platform_thread_id; -struct LinkedTaskListElement task_list_head_sentinel = { +struct FlutterPiTask tasklist = { .next = NULL, .is_vblank_event = false, .target_time = 0, .task = {.runner = NULL, .task = 0} }; -pthread_mutex_t task_list_lock; -bool should_notify_platform_thread = false; -sigset_t sigusr1_set; - +pthread_mutex_t tasklist_lock = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t task_added = PTHREAD_COND_INITIALIZER; /********************* @@ -135,7 +134,7 @@ sigset_t sigusr1_set; *********************/ bool make_current(void* userdata) { if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context) != EGL_TRUE) { - fprintf(stderr, "Could not make the context current.\n"); + fprintf(stderr, "make_current: could not make the context current.\n"); return false; } @@ -143,7 +142,7 @@ bool make_current(void* userdata) { } bool clear_current(void* userdata) { if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { - fprintf(stderr, "Could not clear the current context.\n"); + fprintf(stderr, "clear_current: could not clear the current context.\n"); return false; } @@ -165,10 +164,12 @@ struct drm_fb* drm_fb_get_from_bo(struct gbm_bo *bo) { uint32_t width, height, format, strides[4] = {0}, handles[4] = {0}, offsets[4] = {0}, flags = 0; int ok = -1; + // if the buffer object already has some userdata associated with it, + // it's the framebuffer we allocated. struct drm_fb *fb = gbm_bo_get_user_data(bo); - if (fb) return fb; + // if there's no framebuffer for the bo, we need to create one. fb = calloc(1, sizeof(struct drm_fb)); fb->bo = bo; @@ -197,7 +198,7 @@ struct drm_fb* drm_fb_get_from_bo(struct gbm_bo *bo) { if (ok) { if (flags) - fprintf(stderr, "Modifiers failed!\n"); + fprintf(stderr, "drm_fb_get_from_bo: modifiers failed!\n"); memcpy(handles, (uint32_t [4]){gbm_bo_get_handle(bo).u32,0,0,0}, 16); memcpy(strides, (uint32_t [4]){gbm_bo_get_stride(bo),0,0,0}, 16); @@ -207,7 +208,7 @@ struct drm_fb* drm_fb_get_from_bo(struct gbm_bo *bo) { } if (ok) { - fprintf(stderr, "failed to create fb: %s\n", strerror(errno)); + fprintf(stderr, "drm_fb_get_from_bo: failed to create fb: %s\n", strerror(errno)); free(fb); return NULL; } @@ -286,7 +287,7 @@ void cut_word_from_string(char* string, char* word) { } while (word_in_str[i++ + word_length] != 0); } } -const GLubyte* hacked_glGetString(GLenum name) { +const GLubyte *hacked_glGetString(GLenum name) { if (name == GL_EXTENSIONS) { static GLubyte* extensions; @@ -365,7 +366,7 @@ const GLubyte* hacked_glGetString(GLenum name) { return glGetString(name); } } -void* proc_resolver(void* userdata, const char* name) { +void *proc_resolver(void* userdata, const char* name) { if (name == NULL) return NULL; /* @@ -405,58 +406,36 @@ void handle_sigusr1(int _) {} bool init_message_loop() { platform_thread_id = pthread_self(); - // first, initialize the task heap mutex - if (pthread_mutex_init(&task_list_lock, NULL) != 0) { - fprintf(stderr, "Could not initialize task list mutex\n"); - return false; - } - - sigemptyset(&sigusr1_set); - sigaddset(&sigusr1_set, SIGUSR1); - sigaction(SIGUSR1, &(struct sigaction) {.sa_handler = &handle_sigusr1}, NULL); - pthread_sigmask(SIG_UNBLOCK, &sigusr1_set, NULL); - return true; } bool message_loop(void) { - should_notify_platform_thread = true; + struct timespec abstargetspec; + uint64_t currenttime, abstarget; while (true) { - pthread_mutex_lock(&task_list_lock); - if (task_list_head_sentinel.next == NULL) { - pthread_mutex_unlock(&task_list_lock); - sigwaitinfo(&sigusr1_set, NULL); - continue; - } else { - uint64_t target_time = task_list_head_sentinel.next->target_time; - uint64_t current_time = FlutterEngineGetCurrentTime(); - - if (target_time > current_time) { - uint64_t diff = target_time - current_time; - - struct timespec target_timespec = { - .tv_sec = (uint64_t) (diff / 1000000000ull), - .tv_nsec = (uint64_t) (diff % 1000000000ull) - }; + pthread_mutex_lock(&tasklist_lock); - pthread_mutex_unlock(&task_list_lock); - sigtimedwait(&sigusr1_set, NULL, &target_timespec); - continue; - } + // wait for a task to be inserted into the list + while ((tasklist.next == NULL)) + pthread_cond_wait(&task_added, &tasklist_lock); + + // wait for a task to be ready to be run + while (tasklist.target_time > (currenttime = FlutterEngineGetCurrentTime())) { + clock_gettime(CLOCK_REALTIME, &abstargetspec); + abstarget = abstargetspec.tv_nsec + abstargetspec.tv_sec*1000000000ull - currenttime; + abstargetspec.tv_nsec = abstarget % 1000000000; + abstargetspec.tv_sec = abstarget / 1000000000; + + pthread_cond_timedwait(&task_added, &tasklist_lock, &abstargetspec); } - FlutterTask task = task_list_head_sentinel.next->task; - bool is_vblank_event = task_list_head_sentinel.next->is_vblank_event; - drmVBlankReply vbl = task_list_head_sentinel.next->vbl; + FlutterTask task = tasklist.next->task; + struct FlutterPiTask* new_first = tasklist.next->next; + free(tasklist.next); + tasklist.next = new_first; - struct LinkedTaskListElement* new_first = task_list_head_sentinel.next->next; - free(task_list_head_sentinel.next); - task_list_head_sentinel.next = new_first; - pthread_mutex_unlock(&task_list_lock); - - if (is_vblank_event) { - - } else if (FlutterEngineRunTask(engine, &task) != kSuccess) { + pthread_mutex_unlock(&tasklist_lock); + if (FlutterEngineRunTask(engine, &task) != kSuccess) { fprintf(stderr, "Error running platform task\n"); return false; }; @@ -465,24 +444,22 @@ bool message_loop(void) { return true; } void post_platform_task(FlutterTask task, uint64_t target_time, void* userdata) { - struct LinkedTaskListElement* to_insert = malloc(sizeof(struct LinkedTaskListElement)); + // prepare the task to be inserted into the tasklist. + struct FlutterPiTask* to_insert = malloc(sizeof(struct FlutterPiTask)); to_insert->next = NULL; - to_insert->is_vblank_event = false; to_insert->task = task; to_insert->target_time = target_time; - pthread_mutex_lock(&task_list_lock); - struct LinkedTaskListElement* this = &task_list_head_sentinel; - while ((this->next) != NULL && (target_time > this->next->target_time)) - this = this->next; - - to_insert->next = this->next; - this->next = to_insert; - pthread_mutex_unlock(&task_list_lock); - - if (should_notify_platform_thread) { - pthread_kill(platform_thread_id, SIGUSR1); - } + // insert the task at a fitting position. (the tasks are ordered by target time) + pthread_mutex_lock(&tasklist_lock); + struct FlutterPiTask* this = &tasklist; + while ((this->next) != NULL && (target_time > this->next->target_time)) + this = this->next; + + to_insert->next = this->next; + this->next = to_insert; + pthread_mutex_unlock(&tasklist_lock); + pthread_cond_signal(&task_added); } bool runs_platform_tasks_on_current_thread(void* userdata) { return pthread_equal(pthread_self(), platform_thread_id) != 0; @@ -572,7 +549,7 @@ bool init_display(void) { // only update the physical size of the display if the values // are not yet initialized / not set with a commandline option - if (!((width_mm == 0) && (height_mm == 0))) { + if ((width_mm == 0) && (height_mm == 0)) { if ((conn->mmWidth == 160) && (conn->mmHeight == 90)) { // if width and height is exactly 160mm x 90mm, the values are probably bogus. width_mm = 0; @@ -939,6 +916,7 @@ bool init_application(void) { return true; } + void destroy_application(void) { int ok; diff --git a/src/platformchannel.c b/src/platformchannel.c index 95a72760..dc899c79 100644 --- a/src/platformchannel.c +++ b/src/platformchannel.c @@ -204,7 +204,7 @@ int PlatformChannel_writeStdMsgCodecValueToBuffer(struct StdMsgCodecValue* value break; case kFloat64: align8(pbuffer); - write64(pbuffer, (uint64_t) value->float64_value); + write64(pbuffer, *((uint64_t*) &(value->float64_value))); advance(pbuffer, 8); break; case kLargeInt: @@ -871,7 +871,6 @@ int PlatformChannel_encode(struct ChannelObject *object, uint8_t **buffer_out, s *size_out = size; return 0; - free_buffer_and_return_ok: free(buffer); return ok; @@ -917,6 +916,13 @@ int PlatformChannel_send(char *channel, struct ChannelObject *object, enum Chann if (result != kSuccess) return EINVAL; } + //printf("[platformchannel] sending platform message to flutter on channel \"%s\". message_size: %d, has response_handle? %s\n", channel, size, response_handle ? "yes" : "no"); + //printf(" message buffer: \""); + //for (int i = 0; i < size; i++) + // if (isprint(buffer[i])) printf("%c", buffer[i]); + // else printf("\\x%02X", buffer[i]); + //printf("\"\n"); + result = FlutterEngineSendPlatformMessage( engine, & (const FlutterPlatformMessage) { @@ -960,8 +966,8 @@ int PlatformChannel_jsoncall(char *channel, char *method, struct JSONMsgCodecVal } int PlatformChannel_respond(FlutterPlatformMessageResponseHandle *handle, struct ChannelObject *response) { FlutterEngineResult result; - uint8_t *buffer; - size_t size; + uint8_t *buffer = NULL; + size_t size = 0; int ok; ok = PlatformChannel_encode(response, &buffer, &size); diff --git a/src/pluginregistry.c b/src/pluginregistry.c index 781064bd..f1178117 100644 --- a/src/pluginregistry.c +++ b/src/pluginregistry.c @@ -26,12 +26,12 @@ struct { struct FlutterPiPlugin hardcoded_plugins[] = { {.name = "services", .init = Services_init, .deinit = Services_deinit}, -#ifdef INCLUDE_TESTPLUGIN +#ifdef BUILD_TESTPLUGIN {.name = "testplugin", .init = TestPlugin_init, .deinit = TestPlugin_deinit} #endif -#ifdef INCLUDE_ELM327PLUGIN - {.name = "elm327", .init = ELM327Plugin_init, .deinit = ELM327Plugin_deinit} +#ifdef BUILD_ELM327PLUGIN + {.name = "elm327plugin", .init = ELM327Plugin_init, .deinit = ELM327Plugin_deinit} #endif }; //size_t hardcoded_plugins_count; diff --git a/src/plugins/elm327plugin.c b/src/plugins/elm327plugin.c index 87795f59..a58b0301 100644 --- a/src/plugins/elm327plugin.c +++ b/src/plugins/elm327plugin.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -20,12 +21,14 @@ #include "elm327plugin.h" -struct elm327 elm; -pthread_mutex_t pidqq_mutex; +struct elm327 elm; +pthread_mutex_t pidqq_lock = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t pidqq_added = PTHREAD_COND_INITIALIZER; struct pidqq_element *pidqq = NULL; -size_t pidqq_size = 0; -volatile bool pidqq_processor_shouldrun = false; -pthread_t pidqq_processor_thread; +size_t pidqq_size = 0; +size_t pidqq_nelements = 0; +atomic_bool pidqq_processor_shouldrun = true; +pthread_t pidqq_processor_thread; /***************************** * Communication with ELM327 * @@ -43,13 +46,19 @@ pthread_t pidqq_processor_thread; int elm_command(char *cmd, char *response, size_t length) { int ok = 0, count = 0, i = 0, ntransmit; char charbuff = 0; - elm.elm_errno = ELM_ERRNO_OK; - assert(elm.is_online && "elm_command: ELM327 must be online"); - assert(cmd && "elm_command: command can't be NULL"); - - ntransmit = strlen(cmd) + strlen(ELM327_EOC); + if (!elm.is_online) { + fprintf(stderr, "[elm327plugin] elm_command: ELM327 must be online\n"); + return EINVAL; + } + if (!cmd) { + fprintf(stderr, "[elm327plugin] elm_command: command can't be NULL\n"); + return EINVAL; + } + + FlutterEngineTraceEventDurationBegin("elm_command"); + FlutterEngineTraceEventDurationBegin("elm_command write"); // write cmd to line ok = pselect(elm.fd+1, NULL, &elm.fdset, NULL, &elm.timeout, NULL); @@ -57,63 +66,65 @@ int elm_command(char *cmd, char *response, size_t length) { tcflush(elm.fd, TCIOFLUSH); // why do we write byte per byte here? - for (i=0; i < strlen(cmd); i++) + count += write(elm.fd, (const void *) cmd, sizeof(char)*strlen(cmd)); + count += write(elm.fd, (const void *) ELM327_EOC, sizeof(char)*strlen(ELM327_EOC)); + /*for (i=0; i < strlen(cmd); i++) count += write(elm.fd, (const void *) cmd+i, sizeof(char)); for (i=0; i < strlen(ELM327_EOC); i++) count += write(elm.fd, (const void *) ELM327_EOC+i, sizeof(char)); + */ - if (count != ntransmit) { - fprintf(stderr, "could not write command to serial, written %d bytes, should be %ld\n", count, ntransmit); + if (count != (strlen(cmd) + strlen(ELM327_EOC))) { + fprintf(stderr, "[elm327plugin] elm_command: could not write command to serial, written %d bytes, should be %ld\n", count, (strlen(cmd) + strlen(ELM327_EOC))); + elm.elm_errno = ELM_ERRNO_NOCONN; return EIO; } } else { - fprintf(stderr, "elm connection timed out while writing, after %lus, %09luns\n", elm.timeout.tv_sec, elm.timeout.tv_nsec); + fprintf(stderr, "[elm327plugin] elm_command: elm connection timed out while writing, after %lus, %09luns\n", elm.timeout.tv_sec, elm.timeout.tv_nsec); elm.elm_errno = ELM_ERRNO_NOCONN; return EIO; } - // wait for bytes to send - tcdrain(elm.fd); + FlutterEngineTraceEventDurationEnd("elm_command write"); + FlutterEngineTraceEventDurationBegin("elm_command read"); // read response + i = 0; while (1) { - ok = pselect(elm.fd+1, &elm.fdset, NULL, NULL, &elm.timeout, NULL); + ok = read(elm.fd, &charbuff, sizeof(char)); - if (ok > 0) { - ok = read(elm.fd, &charbuff, sizeof(char)); - - if (isprint(charbuff)) printf("%c", charbuff); - else printf("\\x%02x", charbuff); - - if (charbuff == '>') { - if (response) response[i] = '\0'; - printf("\n"); - break; - } else if (response) { - response[i] = charbuff; - i = (i+1) % length; - } - } else { - printf("\n"); - fprintf(stderr, "ELM327 connection timed out while reading, after %lus, %09luns\n", elm.timeout.tv_sec, elm.timeout.tv_nsec); + if (ok == 0) { + fprintf(stderr, "[elm327plugin] elm_command: ELM327 connection timed out while reading, after %lus, %09luns\n", elm.timeout.tv_sec, elm.timeout.tv_nsec); elm.elm_errno = ELM_ERRNO_NOCONN; return EIO; + } if (charbuff == '>') { + if (response) response[i] = '\0'; + break; + } else if (response && isprint(charbuff)) { + response[i] = charbuff; + i = (i+1) % length; } } + FlutterEngineTraceEventDurationEnd("elm_command read"); + FlutterEngineTraceEventDurationEnd("elm_command"); + return 0; } /// queries the value of a pid /// (uses elm_command for execution) int elm_query(uint8_t pid, uint32_t* response) { - char txt_response[32], command[5]; + char txt_response[32], command[6]; size_t response_length = 0; uint8_t bytes[6] = {0}; int ok; - sprintf(command, "01%02X", pid); + elm.elm_errno = ELM_ERRNO_OK; + + sprintf(command, "01%02X1", pid); + printf("[elm327plugin] query string: %s\n", command); ok = elm_command(command, txt_response, 32); if (ok != 0) return ok; @@ -157,41 +168,31 @@ int elm_query(uint8_t pid, uint32_t* response) { elm.elm_errno = ELM_ERRNO_SEARCHING; if (elm.elm_errno != ELM_ERRNO_OK) { - fprintf(stderr, "elm_query: query was not successful. ELM_ERRNO: %d\n", elm.elm_errno); + fprintf(stderr, "[elm327plugin] elm_query: query was not successful. ELM_ERRNO: %d\n", elm.elm_errno); return EIO; } + /* response_length = strlen(txt_response); - - // we need to remove all carriage returns so we can print out the response - // remove all trailing carriage-returns - for (int i = response_length-1; i>=0; i--) { - if (txt_response[i] == 0x0D) { - txt_response[i] = 0x00; - response_length--; - } else { - break; - } + printf("asked for \"%s\", response: \"", command); + for (int i=0; i < response_length; i++) { + if (isprint(txt_response[i])) printf("%c", txt_response[i]); + else printf("\\x%02X", txt_response[i]); } - - // remove all remaining carriage-returns - int n_move = 0; - for (int i = 0; i <= response_length; i++) - if (txt_response[i] == 0x0D) n_move++; - else if (n_move) txt_response[i-n_move] = txt_response[i]; - response_length -= n_move; - - printf("asked for \"%s\", response: \"%s\"\n", command, txt_response); + printf("\"\n"); + */ // parse the response - int res = sscanf(txt_response, "%2hhX%2hhX%2hhX%2hhX%2hhX%2hhX", + int res = sscanf(txt_response, "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", bytes, bytes+1, bytes+2, bytes+3, bytes+4, bytes+5); - + if (res == EOF) { - fprintf(stderr, "elm_query: string matching error ocurred\n"); + fprintf(stderr, "[elm327plugin] elm_query: string matching error ocurred\n"); + elm.elm_errno = ELM_ERRNO_INVALID; return EIO; } else if ((res >= 0) && (res <= 2)) { - fprintf(stderr, "elm_query: unexpected ELM327 reply\n"); + fprintf(stderr, "[elm327plugin] elm_query: unexpected ELM327 reply\n"); + elm.elm_errno = ELM_ERRNO_INVALID; return EIO; } @@ -219,10 +220,12 @@ bool elm_pid_supported(uint8_t pid) { /// closes the underlying serial device void elm_destroy() { - cfsetispeed(&elm.tty, B0); - cfsetospeed(&elm.tty, B0); - - close(elm.fd); + if (elm.is_online) { + cfsetispeed(&elm.tty, B0); + cfsetospeed(&elm.tty, B0); + + close(elm.fd); + } } /// Opens the serial device given through "serial_path" with the given baudrate, @@ -232,28 +235,28 @@ void elm_destroy() { int elm_open(char *serial_path, int baudrate) { int ok; - printf("Opening ELM327 at \"%s\"\n", serial_path); - - elm.timeout.tv_sec = 5; + elm.timeout.tv_sec = 10; elm.timeout.tv_nsec = 0; elm.is_online = false; ok = access(serial_path, R_OK | W_OK); if (ok != 0) { - fprintf(stderr, "Process doesn't have access to serial device \"%s\": %s\n", serial_path, strerror(errno)); + fprintf(stderr, "[elm327plugin] elm_open: process doesn't have access to serial device \"%s\": %s\n", serial_path, strerror(errno)); return errno; } // Open serial device at given path - elm.fd = open(serial_path, O_RDWR | O_NOCTTY | O_NDELAY); + elm.fd = open(serial_path, O_RDWR | O_NOCTTY | O_SYNC); if (elm.fd < 0) { - fprintf(stderr, "Could not open serial device \"%s\": %s\n", serial_path, strerror(errno)); + fprintf(stderr, "[elm327plugin] elm_open: could not open serial device at \"%s\": %s\n", serial_path, strerror(errno)); return errno; } // set the serial line attributes ok = tcgetattr(elm.fd, &elm.tty); + if (ok != 0) goto error; + /* elm.tty.c_cflag |= (CLOCAL|CREAD); elm.tty.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG|IEXTEN); elm.tty.c_iflag &= ~(INLCR|IGNCR|ICRNL|IGNBRK|IUCLC|PARMRK| @@ -262,6 +265,17 @@ int elm_open(char *serial_path, int baudrate) { elm.tty.c_cc[VMIN] = 0; elm.tty.c_cc[VTIME]= 0; + */ + + // make raw terminal + elm.tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + elm.tty.c_oflag &= ~OPOST; + elm.tty.c_cflag &= ~(CSIZE | PARENB); + elm.tty.c_cflag |= CS8; + elm.tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + + elm.tty.c_cc[VMIN] = 1; + elm.tty.c_cc[VTIME] = 100; // set the baudrate @@ -289,55 +303,94 @@ int elm_open(char *serial_path, int baudrate) { default: serial_speed = B0; break; } if (serial_speed == B0) { - fprintf(stderr, "Not a valid baudrate: %d\n", baudrate); + fprintf(stderr, "[elm327plugin] elm_open: not a valid baudrate: %d\n", baudrate); return EINVAL; } - cfsetispeed(&(elm.tty), serial_speed); - cfsetospeed(&(elm.tty), serial_speed); + ok = cfsetispeed(&(elm.tty), serial_speed); + if (ok != 0) { + fprintf(stderr, "[elm327plugin] elm_open: could not set serial input speed: %s\n", strerror(ok)); + goto error; + } - tcsetattr(elm.fd, TCSANOW, &(elm.tty)); + ok = cfsetospeed(&(elm.tty), serial_speed); + if (ok != 0) { + fprintf(stderr, "[elm327plugin] elm_open: could not set serial output speed: %s\n", strerror(ok)); + goto error; + } + + ok = tcsetattr(elm.fd, TCSANOW, &(elm.tty)); + if (ok != 0) { + fprintf(stderr, "[elm327plugin] elm_open: could not set serial tty-config: %s\n", strerror(ok)); + goto error; + } // create an fdset containing the serial device fd FD_ZERO(&elm.fdset); FD_SET(elm.fd, &elm.fdset); - // completely reset the ELM327 - printf("elm ATWS\n"); + memset(elm.supported_pids, 0, sizeof(elm.supported_pids)); + + // ELM327 is now connected. elm.is_online = true; - ok = elm_command(ELM327_RESET, NULL, 0); - if (ok != 0) return ok; - printf("elm ATI\n"); - ok = elm_command(ELM327_VERSION, elm.version, sizeof(elm.version)); - if (ok != 0) return ok; + // completely reset the ELM327 + ok = elm_command(ELM327_RESET, NULL, 0); + if (ok != 0) { + fprintf(stderr, "[elm327plugin] elm_open: error resetting ELM327 using AT WS: %d\n", ok); + goto error; + } - printf("elm AT E0\n"); ok = elm_command(ELM327_ECHO_OFF, NULL, 0); - if (ok != 0) return ok; + if (ok != 0) { + fprintf(stderr, "[elm327plugin] elm_open: error setting ELM327 echo off using AT E0: %d\n", ok); + goto error; + } - printf("elm AT L0\n"); ok = elm_command(ELM327_LINEFEEDS_OFF, NULL, 0); - if (ok != 0) return ok; + if (ok != 0) { + fprintf(stderr, "[elm327plugin] elm_open: error setting ELM327 linefeeds off using AT L0\n", ok); + goto error; + } + + ok = elm_command(ELM327_VERSION, elm.version, sizeof(elm.version)); + if (ok != 0) { + fprintf(stderr, "[elm327plugin] elm_open: error fetching ELM327 version using AT I: %d\n", ok); + goto error; + } // send a dummy message so the ELM can determine the cars protocol ok = elm_query(0x00, elm.supported_pids); - if ((ok != 0) && (elm.elm_errno != ELM_ERRNO_SEARCHING)) return EIO; + if ((ok != 0) && (elm.elm_errno != ELM_ERRNO_SEARCHING)) { + fprintf(stderr, "[elm327plugin] elm_open: error while querying PID 00 / searching for OBDII bus signal: %d\n", ok); + ok = EIO; + goto error; + } // query supported pids for (int i = 0; i < 8; i++) { - printf("is PID 0x%02X supported? %s\n", i*0x20, elm_pid_supported(i*0x20) ? "yes" : "no"); + if (!elm_pid_supported(i*0x20)) break; ok = elm_query(i*0x20, &elm.supported_pids[i]); - if (ok != 0) return ok; + if (ok != 0) { + fprintf(stderr, "[elm327plugin] elm_open: error while querying PID %02X: %d\n", i*0x20, ok); + goto error; + } } // output a list of supported PIDs - printf("list of supported PIDs: "); - for (uint8_t pid = 0; pid <= 0xFF; pid++) - if (elm_pid_supported(pid)) - printf("0x%02X, ", pid); - + printf("[elm327plugin] list of supported PIDs: "); + for (uint8_t pid = 0;; pid++) { + if (elm_pid_supported(pid)) printf("0x%02X, ", pid); + if (pid == 0xFF) break; + } printf("\n"); + + return 0; + + error: + elm.is_online = false; + if (elm.fd >= 0) close(elm.fd); + return ok; } /* @@ -348,92 +401,80 @@ int elm_open(char *serial_path, int baudrate) { * the queries are processed by the pidqq_processor, which runs on it's own thread. * pid queries with higher priorities are executed first. */ -int pidqq_lastIndex(void) { - int i = 0; - while ((i > 0) && (pidqq[i].priority == 0)) - i--; - - return i; -} void pidqq_add(struct pidqq_element *element) { - int lastIndex = -1; - int i = 0; + int index; - while ((i < pidqq_size) && (pidqq[i].priority != 0) && (pidqq[i].priority > element->priority)) - i++; - - if ((i == pidqq_size) || ((lastIndex = pidqq_lastIndex()) == pidqq_size-1)) { - // make queue larger + // make queue larger if necessary + if (pidqq_nelements == pidqq_size) { size_t before = pidqq_size*sizeof(struct pidqq_element); pidqq_size = pidqq_size*2; size_t after = pidqq_size*sizeof(struct pidqq_element); pidqq = realloc(pidqq, after); memset(pidqq + before, 0, after-before); - } + } - if (pidqq[i].priority == 0) { - // insert at last element - pidqq[i] = *element; - } else { - if (lastIndex == -1) lastIndex = pidqq_lastIndex(); - - // shift all elements from i to lastIndex one up - for (int j = lastIndex+1; j>i; j--) - pidqq[j] = pidqq[j-1]; - - // insert element at i - pidqq[i] = *element; - } + // find a nice place to insert the element + for (index = 0; (pidqq[index].priority >= element->priority) && (index < pidqq_nelements); index++); + + // shift all elements above index one up + for (int i = pidqq_nelements+1; i > index; i--) + pidqq[i] = pidqq[i-1]; + + pidqq[index] = *element; + + pidqq_nelements++; } void pidqq_remove(int index) { - int lastIndex = pidqq_lastIndex(); - pidqq[index].priority = 0; + for (int i = index+1; (i < pidqq_nelements); i++) + pidqq[i-1] = pidqq[i]; + pidqq[pidqq_nelements-1].priority = 0; - int i; - for (i = index+1; (i < pidqq_size) && (pidqq[i].priority != 0); i++) { - pidqq[i-1] = pidqq[i]; - } - pidqq[i-1].priority = 0; + pidqq_nelements--; } int pidqq_findWithPid(uint8_t pid) { - for (int i = 0; (i < pidqq_size) && (pidqq[i].priority != 0); i++) + for (int i = 0; i < pidqq_nelements; i++) if (pidqq[i].pid == pid) return i; return -1; } void *run_pidqq_processor(void* arg) { + ELM327PluginPIDQueryCompletionCallback completionCallback = NULL; + struct pidqq_element element; uint8_t pid; - uint32_t response; + uint32_t result; + + printf("[elm327plugin] running pid query queue processor\n"); while (pidqq_processor_shouldrun) { - pthread_mutex_lock(&pidqq_mutex); - if (pidqq[0].priority) { - pid = pidqq[0].pid; - - pthread_mutex_unlock(&pidqq_mutex); - response = 0; - elm_query(pid, &response); - - pthread_mutex_lock(&pidqq_mutex); - if ((pidqq[0].priority) && (pidqq[0].pid == pid)) { - struct pidqq_element e = pidqq[0]; - if (pidqq[0].repeat) pidqq_add(&e); - - pidqq_remove(0); - pthread_mutex_unlock(&pidqq_mutex); - - e.completionCallback(e, response); - } else { - pidqq_remove(0); - pthread_mutex_unlock(&pidqq_mutex); - } - } else { - pthread_mutex_unlock(&pidqq_mutex); - sleep(1); // in the future, we should use signals to notify the pidqq_processor - // when a query has arrived in the queue, but this at least stops - // busy waiting for now. - } + result = 0; + + pthread_mutex_lock(&pidqq_lock); + while (!(pidqq[0].priority)) + pthread_cond_wait(&pidqq_added, &pidqq_lock); + + FlutterEngineTraceEventDurationBegin("pidqq_process"); + + pid = pidqq[0].pid; + pthread_mutex_unlock(&pidqq_lock); + + int ok = elm_query(pid, &result); + + pthread_mutex_lock(&pidqq_lock); + element = pidqq[0]; + if ((element.priority) && (element.pid == pid)) { + pidqq_remove(0); + if (element.repeat) pidqq_add(&element); + } + pthread_mutex_unlock(&pidqq_lock); + + if ((element.priority) && (element.pid == pid) && (element.completionCallback)) { + FlutterEngineTraceEventDurationBegin("pidqq completionCallback"); + element.completionCallback(element, result, elm.elm_errno); + FlutterEngineTraceEventDurationEnd("pidqq completionCallback"); + } + + FlutterEngineTraceEventDurationEnd("pidqq_process"); } return NULL; @@ -442,7 +483,8 @@ void *run_pidqq_processor(void* arg) { /***************** * ELM327 plugin * *****************/ -void ELM327Plugin_onPidQueryCompletion(struct pidqq_element query, uint32_t result) { +void ELM327Plugin_onPidQueryCompletion(struct pidqq_element query, uint32_t result, enum elm327plugin_errno elm_errno) { + // channel object that will be returned to flutter. struct ChannelObject obj = { .codec = kStandardMethodCallResponse, .success = true, @@ -451,33 +493,45 @@ void ELM327Plugin_onPidQueryCompletion(struct pidqq_element query, uint32_t resu .float64_value = 0.0 } }; - uint8_t pid = query.pid; + + if (elm_errno == ELM_ERRNO_OK) { + uint8_t pid = query.pid; - switch (pid) { - case OBDII_PID_ENGINE_RPM: - obj.stdmsgcodec_value.float64_value = result / 4.0; - break; - case OBDII_PID_ENGINE_LOAD: - case OBDII_PID_THROTTLE_POSITION: - obj.stdmsgcodec_value.float64_value = result * 100.0 / 255.0; - break; - case OBDII_PID_ENGINE_COOLANT_TEMP: - obj.stdmsgcodec_value.type = kInt32; - obj.stdmsgcodec_value.int32_value = (int32_t) result - 40; - break; - case OBDII_PID_VEHICLE_SPEED: - obj.stdmsgcodec_value.type = kInt32; - obj.stdmsgcodec_value.int32_value = (int32_t) result; - break; - default: - break; + switch (pid) { + case OBDII_PID_ENGINE_RPM: + obj.stdresult.float64_value = (double) result / (double) 4.0; + break; + case OBDII_PID_ENGINE_LOAD: + case OBDII_PID_THROTTLE_POSITION: + obj.stdresult.float64_value = result * 100.0 / 255.0; + break; + case OBDII_PID_ENGINE_COOLANT_TEMP: + case OBDII_PID_INTAKE_AIR_TEMP: + obj.stdresult.type = kInt32; + obj.stdresult.int32_value = (int32_t) result - 40; + break; + case OBDII_PID_VEHICLE_SPEED: + obj.stdresult.type = kInt32; + obj.stdresult.int32_value = (int32_t) result; + break; + default: + break; + } + } else { + obj.success = false; + obj.errorcode = "queryfailed"; + obj.errormessage = "ELM327 PID query failed. Reason could be a timeout on the connection between Pi and ELM or between ELM and ECU, or something else."; + obj.stderrordetails.type = kNull; } - PlatformChannel_send(query.channel, &obj, kStandardMessageCodec, NULL, NULL); + PlatformChannel_send(query.channel, &obj, kBinaryCodec, NULL, NULL); } int ELM327Plugin_onEventChannelListen(char *channel, uint8_t pid, FlutterPlatformMessageResponseHandle *handle) { + printf("[elm327plugin] listener registered on event channel %s with pid 0x%02X\n", channel, pid); + + // check if pid is supported, if not, respond with an error envelope if (!elm_pid_supported(pid)) { - PlatformChannel_respondError( + return PlatformChannel_respondError( handle, kStandardMethodCallResponse, "notsupported", "The vehicle doesn't support the PID used for this channel.", @@ -485,71 +539,104 @@ int ELM327Plugin_onEventChannelListen(char *channel, uint8_t pid, FlutterPlatfor ); } - // insert a new query into pidqq - pthread_mutex_lock(&pidqq_mutex); - pidqq_add(&(struct pidqq_element) { - .priority = 1, - .pid = pid, - .repeat = true, - .completionCallback = ELM327Plugin_onPidQueryCompletion - }); - pthread_mutex_unlock(&pidqq_mutex); + // copy the channel string. + char *channel_copy = malloc(strlen(channel)+1); + if (channel_copy == NULL) { + fprintf(stderr, "[elm327plugin] could not allocate memory.\n"); + return ENOMEM; + } + strcpy(channel_copy, channel); - PlatformChannel_respond(handle, &(struct ChannelObject) { + // insert a new query into pidqq + pthread_mutex_lock(&pidqq_lock); + pidqq_add(&(struct pidqq_element) { + .channel = channel_copy, + .priority = 1, + .pid = pid, + .repeat = true, + .completionCallback = ELM327Plugin_onPidQueryCompletion + }); + pthread_mutex_unlock(&pidqq_lock); + pthread_cond_signal(&pidqq_added); + + // respond with null + return PlatformChannel_respond(handle, &(struct ChannelObject) { .codec = kStandardMethodCallResponse, .success = true, .stdresult = {.type = kNull} }); } int ELM327Plugin_onEventChannelCancel(char *channel, uint8_t pid, FlutterPlatformMessageResponseHandle *handle) { - pthread_mutex_lock(&pidqq_mutex); - - pidqq_remove(pidqq_findWithPid(OBDII_PID_ENGINE_RPM)); - - pthread_mutex_unlock(&pidqq_mutex); - - PlatformChannel_respond(handle, &(struct ChannelObject) { + // remove query from pidqq + pthread_mutex_lock(&pidqq_lock); + int index = pidqq_findWithPid(OBDII_PID_ENGINE_RPM); + free(pidqq[index].channel); + pidqq_remove(index); + pthread_mutex_unlock(&pidqq_lock); + + // respond with null. + return PlatformChannel_respond(handle, &(struct ChannelObject) { .codec = kStandardMethodCallResponse, .success = true, .stdresult = {.type = kNull} }); } int ELM327Plugin_onReceive(char *channel, struct ChannelObject *object, FlutterPlatformMessageResponseHandle *handle) { - if (object->codec == kStandardMethodCall) { + bool isListen = false; + if ((object->codec == kStandardMethodCall) && ((isListen = (strcmp(object->method, "listen") == 0)) || (strcmp(object->method, "cancel") == 0))) { uint8_t pid = (strcmp(channel, ELM327PLUGIN_RPM_CHANNEL) == 0) ? OBDII_PID_ENGINE_RPM : (strcmp(channel, ELM327PLUGIN_ENGINELOAD_CHANNEL) == 0) ? OBDII_PID_ENGINE_LOAD : (strcmp(channel, ELM327PLUGIN_COOLANTTEMP_CHANNEL) == 0) ? OBDII_PID_ENGINE_COOLANT_TEMP : (strcmp(channel, ELM327PLUGIN_SPEED_CHANNEL) == 0) ? OBDII_PID_VEHICLE_SPEED : (strcmp(channel, ELM327PLUGIN_THROTTLE_CHANNEL) == 0) ? OBDII_PID_THROTTLE_POSITION : 0; - assert((pid != 0) && "unexpected event channel"); - - if (strcmp(object->method, "listen") == 0) ELM327Plugin_onEventChannelListen(channel, pid, handle); - else if (strcmp(object->method, "cancel") == 0) ELM327Plugin_onEventChannelCancel(channel, pid, handle); - } + if (pid == 0) { + fprintf(stderr, "[elm327plugin] ELM327Plugin_onReceive: unexpected event channel: \"%s\"\n", channel); + return EINVAL; + } - return PlatformChannel_respondNotImplemented(handle); + if (elm.is_online) { + if (isListen) ELM327Plugin_onEventChannelListen(channel, pid, handle); + else ELM327Plugin_onEventChannelCancel(channel, pid, handle); + } else { + return PlatformChannel_respondError( + handle, kStandardMethodCallResponse, + "noelm", "elm.is_online == false. No communication to ELM327 possible, or initialization failed.", NULL + ); + } + } else { + return PlatformChannel_respondNotImplemented(handle); + } } int ELM327Plugin_init(void) { int r = 0; // init the elm327 - r = elm_open(ELM327PLUGIN_DEVICE_PATH, 9600); + r = elm_open(ELM327PLUGIN_DEVICE_PATH, ELM327PLUGIN_BAUDRATE); + if (r != 0) { + fprintf(stderr, "[elm327plugin] ELM327Plugin_init: ELM327 communication was not initialized successfully. elm327plugin won't supply any OBDII data. error code: %s\n", strerror(r)); + } // init pidquery queue - pthread_mutex_init(&pidqq_mutex, NULL); + pthread_mutex_init(&pidqq_lock, NULL); pidqq_size = 50; pidqq = calloc(pidqq_size, sizeof(struct pidqq_element)); + pidqq_processor_shouldrun = true; pthread_create(&pidqq_processor_thread, NULL, run_pidqq_processor, NULL); PluginRegistry_setReceiver(ELM327PLUGIN_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); + PluginRegistry_setReceiver(ELM327PLUGIN_RPM_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); + PluginRegistry_setReceiver(ELM327PLUGIN_ENGINELOAD_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); + PluginRegistry_setReceiver(ELM327PLUGIN_COOLANTTEMP_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); + PluginRegistry_setReceiver(ELM327PLUGIN_SPEED_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); + PluginRegistry_setReceiver(ELM327PLUGIN_THROTTLE_CHANNEL, kStandardMethodCall, ELM327Plugin_onReceive); } int ELM327Plugin_deinit(void) { - elm_destroy(); - pidqq_processor_shouldrun = false; pthread_join(pidqq_processor_thread, NULL); + elm_destroy(); + free(pidqq); } \ No newline at end of file diff --git a/src/plugins/elm327plugin.h b/src/plugins/elm327plugin.h index 0cf7aeab..2f008c76 100644 --- a/src/plugins/elm327plugin.h +++ b/src/plugins/elm327plugin.h @@ -22,7 +22,7 @@ #define ELM327_VERSION "ATI" #define ELM327_ECHO_OFF "AT E0" #define ELM327_LINEFEEDS_OFF "AT L0" -#define ELM327_EOC "\r\n" +#define ELM327_EOC "\r" #define ELM327_OK "OK" // command successfully executed #define ELM327_INVALID "?" // ELM could not understand the command @@ -77,7 +77,7 @@ #define OBDII_PID_ACTUAL_PERCENT_TORQUE 0x62 #define OBDII_PID_REFERENCE_TORQUE 0x63 -enum elm_errno { +enum elm327plugin_errno { ELM_ERRNO_OK, ELM_ERRNO_INVALID, ELM_ERRNO_ACT_ALERT, @@ -106,12 +106,12 @@ struct elm327 { int fd; int baudrate; bool is_online; - enum elm_errno elm_errno; + enum elm327plugin_errno elm_errno; }; -extern struct elm327 elm; struct pidqq_element; -typedef void (*ELM327PluginPIDQueryCompletionCallback)(struct pidqq_element query, uint32_t result); +typedef void (*ELM327PluginPIDQueryCompletionCallback)(struct pidqq_element query, uint32_t result, enum elm327plugin_errno elm_errno); + // pid queries take time to execute, so we queue all queries. // every query has priority (so it's actually a priority queue) struct pidqq_element { @@ -124,17 +124,17 @@ struct pidqq_element { // after it was executed. ELM327PluginPIDQueryCompletionCallback completionCallback; }; -extern struct pidqq_element *pidqq; -extern size_t pidqq_size; -#define ELM327PLUGIN_CHANNEL "plugins.flutter.io/elmdev" -#define ELM327PLUGIN_RPM_CHANNEL "plugins.flutter.io/elmdev/rpm" -#define ELM327PLUGIN_ENGINELOAD_CHANNEL "plugins.flutter.io/elmdev/engineload" -#define ELM327PLUGIN_COOLANTTEMP_CHANNEL "plugins.flutter.io/elmdev/coolanttemp" -#define ELM327PLUGIN_SPEED_CHANNEL "plugins.flutter.io/elmdev/speed" -#define ELM327PLUGIN_THROTTLE_CHANNEL "plugins.flutter.io/elmdev/throttle" + +#define ELM327PLUGIN_CHANNEL "plugins.flutter-pi.io/elm327" +#define ELM327PLUGIN_RPM_CHANNEL "plugins.flutter-pi.io/elm327/rpm" +#define ELM327PLUGIN_ENGINELOAD_CHANNEL "plugins.flutter-pi.io/elm327/engineload" +#define ELM327PLUGIN_COOLANTTEMP_CHANNEL "plugins.flutter-pi.io/elm327/coolanttemp" +#define ELM327PLUGIN_SPEED_CHANNEL "plugins.flutter-pi.io/elm327/speed" +#define ELM327PLUGIN_THROTTLE_CHANNEL "plugins.flutter-pi.io/elm327/throttle" #define ELM327PLUGIN_DEVICE_PATH "/dev/rfcomm0" +#define ELM327PLUGIN_BAUDRATE 9600 int ELM327Plugin_init(void); int ELM327Plugin_deinit(void);