├── android
├── .gitignore
├── project
│ └── plugins.sbt
├── build.sbt
└── src
│ └── main
│ ├── res
│ └── values
│ │ └── strings.xml
│ ├── scala
│ └── us
│ │ └── insolit
│ │ └── mazdaconnector
│ │ ├── StartVRReceiver.scala
│ │ ├── AutoStartReceiver.scala
│ │ ├── MainActivity.scala
│ │ ├── WakeUpActivity.scala
│ │ └── BluetoothService.scala
│ └── AndroidManifest.xml
├── .gitignore
├── shared
├── stringify.h
├── prevent_brick.hpp
└── dbus_helpers.hpp
├── src
├── connector
│ ├── dbus.hpp
│ ├── bluetooth.hpp
│ ├── navigation.hpp
│ ├── gesture_recognizer.hpp
│ ├── dbus.cpp
│ ├── connector.cpp
│ ├── navigation.cpp
│ ├── gesture_recognizer.cpp
│ └── bluetooth.cpp
└── input_filter
│ └── input_filter.cpp
├── README.md
└── COPYING
/android/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /connector
3 | /input_filter
4 |
--------------------------------------------------------------------------------
/android/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.3.16")
2 |
--------------------------------------------------------------------------------
/shared/stringify.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #define _STRINGIFY(x) #x
4 | #define STRINGIFY(x) _STRINGIFY(x)
5 |
--------------------------------------------------------------------------------
/android/build.sbt:
--------------------------------------------------------------------------------
1 | import android.Keys._
2 |
3 | android.Plugin.androidBuild
4 |
5 | platformTarget in Android := "android-17"
6 |
7 | name := "mazda_connector"
8 |
9 | javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6")
10 |
--------------------------------------------------------------------------------
/android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 62306c74-5706-4375-bb48-212331070361
4 | Mazda Connector
5 |
6 |
--------------------------------------------------------------------------------
/src/connector/dbus.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #define SERVICE_BUS_ADDRESS "unix:path=/tmp/dbus_service_socket"
6 | #define HMI_BUS_ADDRESS "unix:path=/tmp/dbus_hmi_socket"
7 |
8 | extern DBusConnection *service_bus;
9 | extern DBusConnection *hmi_bus;
10 |
11 | void initialize_dbus(void);
12 |
--------------------------------------------------------------------------------
/android/src/main/scala/us/insolit/mazdaconnector/StartVRReceiver.scala:
--------------------------------------------------------------------------------
1 | package us.insolit.mazdaconnector
2 |
3 | import android.content.{BroadcastReceiver, Context, Intent}
4 |
5 | class StartVRReceiver extends BroadcastReceiver {
6 | override def onReceive(ctx: Context, intent: Intent) {
7 | WakeUpActivity.start(ctx)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/android/src/main/scala/us/insolit/mazdaconnector/AutoStartReceiver.scala:
--------------------------------------------------------------------------------
1 | package us.insolit.mazdaconnector
2 |
3 | import android.content.{BroadcastReceiver, Context, Intent}
4 |
5 | class AutoStartReceiver extends BroadcastReceiver {
6 | override def onReceive(ctx: Context, intent: Intent) {
7 | ctx.startService(new Intent(ctx, classOf[BluetoothService]))
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/android/src/main/scala/us/insolit/mazdaconnector/MainActivity.scala:
--------------------------------------------------------------------------------
1 | package us.insolit.mazdaconnector
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.os.Bundle
6 |
7 | class MainActivity extends Activity {
8 | override def onCreate(bundle: Bundle) {
9 | super.onCreate(bundle)
10 | startService(new Intent(this, classOf[BluetoothService]))
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/shared/prevent_brick.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | inline void prevent_brick(const char *checkfile)
8 | {
9 | int checkfd = ::open(checkfile, O_RDWR, 0755);
10 |
11 | if (checkfd < 0) {
12 | printf("failed to open checkfile %s\n", checkfile);
13 | for (;;);
14 | }
15 |
16 | char check = '\0';
17 | ssize_t count = ::pread(checkfd, &check, 1, 0);
18 | if (count != 1 || (check != '1' && check != '2')) {
19 | printf("%s is disabled\n", checkfile);
20 | printf("count = %d, check = %c\n", count, check);
21 | for (;;);
22 | }
23 |
24 | if (check != '2') {
25 | ::pwrite(checkfd, "0", 1, 0);
26 | }
27 |
28 | ::close(checkfd);
29 | }
30 |
--------------------------------------------------------------------------------
/src/connector/bluetooth.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include
7 |
8 | BOOST_STRONG_TYPEDEF(uint32_t, bluetooth_device_id_t);
9 |
10 | // Service specified in BdsConfiguration.xml
11 | BOOST_STRONG_TYPEDEF(uint32_t, bluetooth_service_id_t);
12 |
13 | enum class bluetooth_socket_state {
14 | disconnected,
15 | connecting,
16 | connected,
17 | };
18 |
19 | struct bluetooth_socket {
20 | bluetooth_socket_state state = bluetooth_socket_state::disconnected;
21 | std::promise fd_promise;
22 | std::shared_future fd_future = fd_promise.get_future();
23 | };
24 |
25 |
26 | // Returns a future that will be populated with the FD of the socket.
27 | // Immediately dup this fd when received, the original will be closed when the socket is closed.
28 | std::shared_future bluetooth_connect(bluetooth_device_id_t device, bluetooth_service_id_t service);
29 |
30 | void bluetooth_disconnect(bluetooth_device_id_t device_id, bluetooth_service_id_t service_id);
31 |
32 | void handle_bluetooth_connection_response(DBusMessage *message);
33 |
--------------------------------------------------------------------------------
/src/connector/navigation.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | void GuidanceChanged(
6 | int32_t manueverIcon,
7 | int32_t manueverDistance,
8 | int32_t manueverDistanceUnit,
9 | int32_t speedLimit,
10 | int32_t speedLimitUnit,
11 | int32_t laneIcon1,
12 | int32_t laneIcon2,
13 | int32_t laneIcon3,
14 | int32_t laneIcon4,
15 | int32_t laneIcon5,
16 | int32_t laneIcon6,
17 | int32_t laneIcon7,
18 | int32_t laneIcon8
19 | );
20 |
21 | uint8_t SetHUDDisplayMsgReq(
22 | uint32_t manueverIcon,
23 | uint16_t manueverDistance,
24 | uint8_t manueverDistanceUnit,
25 | uint16_t speedLimit,
26 | uint8_t speedLimitUnit
27 | );
28 |
29 | void NotificationBar_Notify(
30 | int32_t manueverIcon,
31 | int32_t manueverDistance,
32 | int32_t manueverDistanceUnit,
33 | const char *streetName,
34 | int32_t priority
35 | );
36 |
37 | void updateHUD(
38 | int32_t manueverIcon,
39 | int32_t manueverDistance,
40 | int32_t manueverDistanceUnit,
41 | int32_t speedLimit,
42 | int32_t speedLimitUnit,
43 | const char *streetName,
44 | int32_t priority
45 | );
46 |
--------------------------------------------------------------------------------
/src/connector/gesture_recognizer.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | struct gesture {
7 | int keycode;
8 | bool long_press;
9 | int tap_count;
10 | };
11 |
12 | class gesture_recognizer {
13 | public:
14 | using cb_receive_input = std::function;
15 |
16 | gesture_recognizer(const gesture_recognizer ©) = delete;
17 | gesture_recognizer(gesture_recognizer &&move) = default;
18 | gesture_recognizer(cb_receive_input input_callback, int long_press_ms = 500, int tap_interval_ms = 250);
19 | ~gesture_recognizer(void);
20 |
21 | private:
22 | bool started = false;
23 | int input_pipe[2];
24 | cb_receive_input input_callback;
25 |
26 | // Time to wait to trigger a long press
27 | int long_press_ms;
28 |
29 | // Time to wait for additional taps
30 | int tap_interval_ms;
31 |
32 | struct recognizer_state {
33 | int tap_count = 0;
34 | struct timeval last_timestamp = { .tv_sec = 0, .tv_usec = 0 };
35 | int last_value = 0;
36 | int timer = -1;
37 | };
38 |
39 | // map from keycode to last event
40 | std::unordered_map key_state;
41 |
42 | void spawn_thread(void);
43 |
44 | public:
45 | void handle_input(const struct input_event *input);
46 | };
47 |
--------------------------------------------------------------------------------
/src/connector/dbus.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "dbus.hpp"
6 |
7 | DBusConnection *service_bus;
8 | DBusConnection *hmi_bus;
9 |
10 | void initialize_dbus(void)
11 | {
12 | dbus_threads_init_default();
13 |
14 | DBusError error;
15 | dbus_error_init(&error);
16 |
17 | // Initialize service bus
18 | service_bus = dbus_connection_open(SERVICE_BUS_ADDRESS, &error);
19 | if (!service_bus) {
20 | errx(1, "failed to connect to service bus: %s: %s\n", error.name, error.message);
21 | }
22 |
23 | if (!dbus_bus_register(service_bus, &error)) {
24 | errx(1, "failed to register with service bus: %s: %s\n", error.name, error.message);
25 | }
26 |
27 | // Initialize HMI bus
28 | hmi_bus = dbus_connection_open(HMI_BUS_ADDRESS, &error);
29 | if (!hmi_bus) {
30 | errx(1, "failed to connect to HMI bus: %s: %s\n", error.name, error.message);
31 | }
32 |
33 | if (!dbus_bus_register(hmi_bus, &error)) {
34 | errx(1, "failed to register with HMI bus: %s: %s\n", error.name, error.message);
35 | }
36 |
37 | std::thread service_thread([]() {
38 | while (dbus_connection_read_write_dispatch(service_bus, -1));
39 | });
40 |
41 | std::thread hmi_thread([]() {
42 | while (dbus_connection_read_write_dispatch(hmi_bus, -1));
43 | });
44 |
45 | service_thread.detach();
46 | hmi_thread.detach();
47 | }
48 |
--------------------------------------------------------------------------------
/android/src/main/scala/us/insolit/mazdaconnector/WakeUpActivity.scala:
--------------------------------------------------------------------------------
1 | package us.insolit.mazdaconnector
2 |
3 | import android.app.Activity
4 | import android.content.{ComponentName, Context, Intent}
5 | import android.os.Bundle
6 | import android.view.WindowManager.LayoutParams
7 |
8 | class WakeUpActivity extends Activity {
9 | var started: Boolean = _
10 |
11 | override def onCreate(bundle: Bundle) {
12 | super.onCreate(bundle)
13 |
14 | started = false
15 | }
16 |
17 | override def onStart() {
18 | super.onStart()
19 |
20 | val window = getWindow()
21 | window.addFlags(
22 | LayoutParams.FLAG_DISMISS_KEYGUARD |
23 | LayoutParams.FLAG_SHOW_WHEN_LOCKED |
24 | LayoutParams.FLAG_TURN_SCREEN_ON |
25 | LayoutParams.FLAG_KEEP_SCREEN_ON
26 | )
27 | }
28 |
29 | override def onStop() {
30 | super.onStop();
31 | finish()
32 | }
33 |
34 | override def onWindowFocusChanged(focus: Boolean) {
35 | if (focus && !started) {
36 | started = true
37 | this.startGoogleNow()
38 | }
39 | }
40 |
41 | def startGoogleNow() {
42 | val googleNowComponent = new ComponentName("com.google.android.googlequicksearchbox", "com.google.android.googlequicksearchbox.VoiceSearchActivity")
43 | val googleNowIntent = new Intent(Intent.ACTION_MAIN)
44 | .addCategory(Intent.CATEGORY_DEFAULT)
45 | .addCategory(Intent.CATEGORY_LAUNCHER)
46 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
47 | .setComponent(googleNowComponent)
48 |
49 | startActivity(googleNowIntent)
50 | }
51 | }
52 |
53 | object WakeUpActivity {
54 | def start(ctx: Context) {
55 | ctx.startActivity(new Intent(ctx, classOf[WakeUpActivity]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP))
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
11 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/connector/connector.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 | #include
15 |
16 | #include "shared/dbus_helpers.hpp"
17 | #include "shared/prevent_brick.hpp"
18 |
19 | #include "bluetooth.hpp"
20 | #include "dbus.hpp"
21 | #include "gesture_recognizer.hpp"
22 | #include "navigation.hpp"
23 |
24 | static void handle_input(gesture input);
25 |
26 | static std::atomic btfd { -1 };
27 | static gesture_recognizer recognizer(handle_input);
28 |
29 | static DBusHandlerResult handle_service_message(DBusConnection *, DBusMessage *message, void *)
30 | {
31 | return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
32 | }
33 |
34 | static DBusHandlerResult handle_hmi_message(DBusConnection *, DBusMessage *message, void *)
35 | {
36 | if (strcmp("ConnectionStatusResp", dbus_message_get_member(message)) == 0) {
37 | handle_bluetooth_connection_response(message);
38 | return DBUS_HANDLER_RESULT_HANDLED;
39 | } else if (strcmp("KeyEvent", dbus_message_get_member(message)) == 0) {
40 | DBusMessageIter iter;
41 | if (!dbus_message_iter_init(message, &iter)) {
42 | errx(1, "failed to open message iterator for reading KeyEvent message");
43 | }
44 |
45 | struct input_event event;
46 | if (!dbus_message_decode_input_event(&iter, &event)) {
47 | errx(1, "failed to get input event from KeyEvent message");
48 | }
49 |
50 | recognizer.handle_input(&event);
51 |
52 | return DBUS_HANDLER_RESULT_HANDLED;
53 | }
54 |
55 | printf("failed to handle message with member: %s\n", dbus_message_get_member(message));
56 |
57 | return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
58 | }
59 |
60 | static void handle_input(gesture input) {
61 | int fd = btfd.load();
62 | if (fd < 0) {
63 | printf("Can't send input to device, not yet connected\n");
64 | } else {
65 | std::stringstream ss;
66 | ss << "Input:" << input.keycode << ";" << int(input.long_press) << ";" << input.tap_count;
67 |
68 | std::string command = ss.str();
69 |
70 | printf("Sending: %s\n", command.c_str());
71 |
72 | std::vector buf(command.length() + sizeof(uint32_t));
73 | *(uint32_t *)&buf[0] = command.length();
74 | memcpy(&buf[4], command.c_str(), command.length());
75 | ::write(fd, &buf[0], buf.size());
76 | }
77 | }
78 |
79 | static void register_signals(void)
80 | {
81 | DBusError error;
82 | dbus_error_init(&error);
83 |
84 | dbus_connection_add_filter(service_bus, handle_service_message, nullptr, nullptr);
85 | dbus_connection_add_filter(hmi_bus, handle_hmi_message, nullptr, nullptr);
86 |
87 | // Bluetooth ConnectionStatusResp
88 | dbus_bus_add_match(hmi_bus, "type='signal',interface='com.jci.bca',member='ConnectionStatusResp'", &error);
89 | if (dbus_error_is_set(&error)) {
90 | errx(1, "failed to add ConnectionStatusResp match: %s: %s\n", error.name, error.message);
91 | }
92 |
93 | dbus_bus_add_match(hmi_bus, "type='signal',interface='us.insolit.mazda.connector',member='KeyEvent'", &error);
94 | if (dbus_error_is_set(&error)) {
95 | errx(1, "failed to add KeyEvent match: %s: %s\n", error.name, error.message);
96 | }
97 | }
98 |
99 | int main(void)
100 | {
101 | // Try not to brick the car
102 | prevent_brick("/tmp/mnt/data/enable_connector");
103 |
104 | initialize_dbus();
105 | register_signals();
106 |
107 | setbuf(stdout, NULL);
108 | setbuf(stderr, NULL);
109 |
110 | bluetooth_device_id_t device_id = bluetooth_device_id_t(1);
111 | bluetooth_service_id_t service_id = bluetooth_service_id_t(8017);
112 | while (true) {
113 | std::shared_future socket = bluetooth_connect(device_id, service_id);
114 | int fd = socket.get();
115 | if (fd < 0) {
116 | printf("Failed to connect to socket, retrying in 1 seconds\n");
117 | sleep(1);
118 | continue;
119 | }
120 |
121 | fd = dup(fd);
122 | btfd.store(fd);
123 |
124 | struct pollfd pfd {
125 | .fd = fd,
126 | .events = POLLHUP | POLLERR,
127 | .revents = 0
128 | };
129 |
130 | poll(&pfd, 1, -1);
131 | bluetooth_disconnect(device_id, service_id);
132 | btfd.store(-1);
133 | ::close(fd);
134 | }
135 |
136 | return 0;
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/shared/dbus_helpers.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include "stringify.h"
8 |
9 | inline bool dbus_message_encode_timeval(DBusMessageIter *iter, const struct timeval *time)
10 | {
11 | DBusMessageIter sub;
12 | if (!dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, nullptr, &sub)) {
13 | printf("ERROR: failed to open struct container\n");
14 | return false;
15 | }
16 |
17 | dbus_bool_t result = TRUE;
18 | uint64_t tv_sec = time->tv_sec;
19 | uint64_t tv_usec = time->tv_usec;
20 |
21 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT64, &tv_sec);
22 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT64, &tv_usec);
23 |
24 | if (!result) {
25 | printf("ERROR: failed to append time data");
26 | return false;
27 | }
28 |
29 | if (!dbus_message_iter_close_container(iter, &sub)) {
30 | errx(1, "ERROR: failed to close struct container\n");
31 | }
32 |
33 | return true;
34 | }
35 |
36 | inline bool dbus_message_encode_input_event(DBusMessageIter *iter, const struct input_event *event)
37 | {
38 | DBusMessageIter sub;
39 | if (!dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, nullptr, &sub)) {
40 | printf("ERROR: failed to open struct container\n");
41 | return false;
42 | }
43 |
44 | if (!dbus_message_encode_timeval(&sub, &event->time)) {
45 | printf("ERROR: failed to append time\n");
46 | return false;
47 | }
48 |
49 | dbus_bool_t result = TRUE;
50 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT16, &event->type);
51 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT16, &event->code);
52 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_INT32, &event->value);
53 |
54 | if (!result) {
55 | printf("ERROR: failed to append event data");
56 | return false;
57 | }
58 |
59 | if (!dbus_message_iter_close_container(iter, &sub)) {
60 | errx(1, "ERROR: failed to close struct container\n");
61 | }
62 |
63 | return true;
64 |
65 | }
66 |
67 | inline bool dbus_message_decode_timeval(DBusMessageIter *iter, struct timeval *time)
68 | {
69 | DBusMessageIter sub;
70 | uint64_t tv_sec = 0;
71 | uint64_t tv_usec = 0;
72 |
73 | if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) {
74 | printf("[" STRINGIFY(__LINE__) "]: received unexpected type: %c\n", dbus_message_iter_get_arg_type(iter));
75 | return false;
76 | }
77 |
78 | dbus_message_iter_recurse(iter, &sub);
79 |
80 | if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
81 | printf("[" STRINGIFY(__LINE__) "]: received unexpected type: %c\n", dbus_message_iter_get_arg_type(iter));
82 | return false;
83 | }
84 | dbus_message_iter_get_basic(&sub, &tv_sec);
85 | dbus_message_iter_next(&sub);
86 |
87 | if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
88 | printf("[" STRINGIFY(__LINE__) "]: received unexpected type: %c\n", dbus_message_iter_get_arg_type(iter));
89 | return false;
90 | }
91 | dbus_message_iter_get_basic(&sub, &tv_usec);
92 |
93 | dbus_message_iter_next(iter);
94 |
95 | time->tv_sec = tv_sec;
96 | time->tv_usec = tv_usec;
97 |
98 | return true;
99 | }
100 |
101 | inline bool dbus_message_decode_input_event(DBusMessageIter *iter, struct input_event *event)
102 | {
103 | DBusMessageIter sub;
104 |
105 | if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRUCT) {
106 | printf("[" STRINGIFY(__LINE__) "]: received unexpected type: %c\n", dbus_message_iter_get_arg_type(iter));
107 | return false;
108 | }
109 |
110 | dbus_message_iter_recurse(iter, &sub);
111 |
112 | if (!dbus_message_decode_timeval(&sub, &event->time)) {
113 | printf("ERROR: failed to decode timeval");
114 | return false;
115 | }
116 |
117 | if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16) {
118 | printf("[" STRINGIFY(__LINE__) "]: received unexpected type: %c\n", dbus_message_iter_get_arg_type(&sub));
119 | return false;
120 | }
121 | dbus_message_iter_get_basic(&sub, &event->type);
122 | dbus_message_iter_next(&sub);
123 |
124 | if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT16) {
125 | printf("[" STRINGIFY(__LINE__) "]: received unexpected type: %c\n", dbus_message_iter_get_arg_type(&sub));
126 | return false;
127 | }
128 | dbus_message_iter_get_basic(&sub, &event->code);
129 | dbus_message_iter_next(&sub);
130 |
131 | if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INT32) {
132 | printf("[" STRINGIFY(__LINE__) "]: received unexpected type: %c\n", dbus_message_iter_get_arg_type(&sub));
133 | return false;
134 | }
135 | dbus_message_iter_get_basic(&sub, &event->value);
136 |
137 | dbus_message_iter_next(iter);
138 |
139 | return true;
140 | }
141 |
--------------------------------------------------------------------------------
/android/src/main/scala/us/insolit/mazdaconnector/BluetoothService.scala:
--------------------------------------------------------------------------------
1 | package us.insolit.mazdaconnector
2 |
3 | import java.io.IOException
4 | import java.nio.ByteBuffer
5 | import java.nio.ByteOrder
6 | import java.nio.charset.Charset
7 | import java.util.UUID
8 |
9 | import scala.concurrent.{ExecutionContext, Future}
10 | import scala.concurrent.ExecutionContext.Implicits.global
11 |
12 | import android.app.Service
13 | import android.bluetooth.BluetoothAdapter
14 | import android.content.Intent
15 | import android.os.{Handler, IBinder, Looper, SystemClock}
16 | import android.speech.tts.TextToSpeech
17 | import android.util.Log
18 | import android.view.KeyEvent
19 |
20 | class BluetoothService extends Service {
21 | // FIXME: Move this somewhere else
22 | def runOnMainThread(f: => Unit) {
23 | val handler = new Handler(Looper.getMainLooper())
24 | handler.post(new Runnable {
25 | override def run() = f
26 | })
27 | }
28 |
29 | override def onCreate() {
30 | super.onCreate()
31 | Log.v("MazdaConnector", "Service started")
32 | Future {
33 | val adapter = BluetoothAdapter.getDefaultAdapter();
34 | val uuid = UUID.fromString(getString(R.string.connector_uuid))
35 | val serverSocket = adapter.listenUsingRfcommWithServiceRecord("connector", uuid);
36 |
37 | val tts: TextToSpeech = new TextToSpeech(this,
38 | new TextToSpeech.OnInitListener() {
39 | override def onInit(status: Int) {
40 | }
41 | }
42 | )
43 |
44 | while (true) {
45 | val socket = serverSocket.accept()
46 | Future {
47 | try {
48 | val is = socket.getInputStream()
49 | val os = socket.getOutputStream()
50 |
51 | while (true) {
52 | // length of the packet
53 | val header = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN)
54 | if (is.read(header.array) != header.array.length) {
55 | throw new IOException("BluetoothSocket returned short when reading header (wanted " +
56 | header.array.length + "bytes)")
57 | }
58 |
59 | val length = header.getInt
60 | if (length < 0) {
61 | throw new IOException("BluetoothSocket received length > 2GB, wtf")
62 | }
63 |
64 | val packet = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN)
65 | if (is.read(packet.array) != packet.array.length) {
66 | throw new IOException("BluetoothSocket returned short when reading data")
67 | }
68 |
69 | val packetString = new String(packet.array(), Charset.forName("UTF-8"))
70 | // Expected format: :
71 | val fields = packetString.split(":", 2)
72 | if (fields.length != 2) {
73 | throw new IOException("Received malformed message: " + packet)
74 | }
75 |
76 | fields(0) match {
77 | case "Input" => {
78 | val input = fields(1).split(";")
79 | if (input.length != 3) {
80 | throw new IOException("Received malformed input message: " + fields(1))
81 | }
82 |
83 | val keycode = input(0).toInt
84 | val longPress = input(1) == "1"
85 | val tapCount = input(2).toInt
86 |
87 | if (tapCount == 1) {
88 | if (longPress) {
89 | WakeUpActivity.start(this)
90 | } else {
91 | val time = SystemClock.uptimeMillis();
92 | val downEvent = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0);
93 | val downIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null).putExtra(Intent.EXTRA_KEY_EVENT, downEvent)
94 | sendOrderedBroadcast(downIntent, null);
95 |
96 | val upEvent = new KeyEvent(time, time, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0);
97 | val upIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null).putExtra(Intent.EXTRA_KEY_EVENT, upEvent)
98 | sendOrderedBroadcast(upIntent, null);
99 | }
100 | } else {
101 | var message = "Received keycode " + input(0) + ", "
102 | if (longPress) {
103 | message += "long press "
104 | }
105 | message += "count " + input(2)
106 | tts.speak(message, TextToSpeech.QUEUE_ADD, null)
107 | }
108 | }
109 |
110 | case tag => {
111 | throw new IOException("Unhandled tag: " + tag)
112 | }
113 | }
114 | }
115 | } catch {
116 | case ex : Throwable => Log.e("MazdaConnector", "Received exception", ex)
117 | }
118 |
119 | socket.close()
120 | }
121 | }
122 | }
123 | }
124 |
125 | override def onBind(intent: Intent): IBinder = ???
126 | }
127 |
128 |
--------------------------------------------------------------------------------
/src/connector/navigation.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #include
7 |
8 | #include "dbus.hpp"
9 | #include "navigation.hpp"
10 |
11 | void GuidanceChanged(
12 | int32_t manueverIcon,
13 | int32_t manueverDistance,
14 | int32_t manueverDistanceUnit,
15 | int32_t speedLimit,
16 | int32_t speedLimitUnit,
17 | int32_t laneIcon1,
18 | int32_t laneIcon2,
19 | int32_t laneIcon3,
20 | int32_t laneIcon4,
21 | int32_t laneIcon5,
22 | int32_t laneIcon6,
23 | int32_t laneIcon7,
24 | int32_t laneIcon8)
25 | {
26 | dbus_bool_t result;
27 | DBusMessage *msg = dbus_message_new_signal("/com/NNG/Api/Server", "com.NNG.Api.Server.Guidance", "GuidanceChanged");
28 | if (!msg) {
29 | assert(false && "failed to create message");
30 | }
31 |
32 | result =
33 | dbus_message_append_args(
34 | msg,
35 | DBUS_TYPE_INT32, &manueverIcon,
36 | DBUS_TYPE_INT32, &manueverDistance,
37 | DBUS_TYPE_INT32, &manueverDistanceUnit,
38 | DBUS_TYPE_INT32, &speedLimit,
39 | DBUS_TYPE_INT32, &speedLimitUnit,
40 | DBUS_TYPE_INT32, &laneIcon1,
41 | DBUS_TYPE_INT32, &laneIcon2,
42 | DBUS_TYPE_INT32, &laneIcon3,
43 | DBUS_TYPE_INT32, &laneIcon4,
44 | DBUS_TYPE_INT32, &laneIcon5,
45 | DBUS_TYPE_INT32, &laneIcon6,
46 | DBUS_TYPE_INT32, &laneIcon7,
47 | DBUS_TYPE_INT32, &laneIcon8,
48 | DBUS_TYPE_INVALID);
49 |
50 | if (!result) {
51 | assert(false && "failed to append arguments to message");
52 | exit(1);
53 | }
54 |
55 | if (!dbus_connection_send(service_bus, msg, nullptr)) {
56 | assert(false && "failed to send message");
57 | }
58 |
59 | dbus_message_unref(msg);
60 | }
61 |
62 | // Synchronous, should probably be fixed
63 | uint8_t SetHUDDisplayMsgReq(
64 | uint32_t manueverIcon,
65 | uint16_t manueverDistance,
66 | uint8_t manueverDistanceUnit,
67 | uint16_t speedLimit,
68 | uint8_t speedLimitUnit)
69 | {
70 | DBusMessage *msg = dbus_message_new_method_call("com.jci.vbs.navi", "/com/jci/vbs/navi",
71 | "com.jci.vbs.navi","SetHUDDisplayMsgReq");
72 | DBusPendingCall *pending = nullptr;
73 |
74 | if (!msg) {
75 | assert(false && "failed to create message");
76 | }
77 |
78 | DBusMessageIter iter;
79 | dbus_message_iter_init_append(msg, &iter);
80 |
81 | DBusMessageIter sub;
82 | if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, nullptr, &sub)) {
83 | assert(false && "failed to initialize sub-iterator");
84 | }
85 |
86 | {
87 | dbus_bool_t result = TRUE;
88 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &manueverIcon);
89 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT16, &manueverDistance);
90 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_BYTE, &manueverDistanceUnit);
91 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT16, &speedLimit);
92 | result &= dbus_message_iter_append_basic(&sub, DBUS_TYPE_BYTE, &speedLimitUnit);
93 |
94 | if (!result) {
95 | assert(false && "failed to append arguments to struct");
96 | }
97 | }
98 |
99 | if (!dbus_message_iter_close_container(&iter, &sub)) {
100 | assert(false && "failed to close container");
101 | }
102 |
103 | if (!dbus_connection_send_with_reply(service_bus, msg, &pending, -1)) {
104 | assert(false && "failed to send message");
105 | }
106 |
107 | dbus_connection_flush(service_bus);
108 | dbus_message_unref(msg);
109 |
110 | dbus_pending_call_block(pending);
111 | msg = dbus_pending_call_steal_reply(pending);
112 | if (!msg) {
113 | assert(false && "received null reply");
114 | }
115 |
116 | uint8_t result;
117 | if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_BYTE, &result,
118 | DBUS_TYPE_INVALID)) {
119 | assert(false && "failed to get result");
120 | }
121 |
122 | dbus_message_unref(msg);
123 |
124 | return result;
125 | }
126 |
127 | void NotificationBar_Notify(
128 | int32_t manueverIcon,
129 | int32_t manueverDistance,
130 | int32_t manueverDistanceUnit,
131 | const char *streetName,
132 | int32_t priority)
133 | {
134 | dbus_bool_t result;
135 | DBusMessage *msg = dbus_message_new_signal("/com/NNG/Api/Server", "com.NNG.Api.Server.NotificationBar", "Notify");
136 | if (!msg) {
137 | assert(false && "failed to create message");
138 | }
139 |
140 | result =
141 | dbus_message_append_args(
142 | msg,
143 | DBUS_TYPE_INT32, &manueverIcon,
144 | DBUS_TYPE_INT32, &manueverDistance,
145 | DBUS_TYPE_INT32, &manueverDistanceUnit,
146 | DBUS_TYPE_STRING, &streetName,
147 | DBUS_TYPE_INT32, &priority,
148 | DBUS_TYPE_INVALID);
149 |
150 | if (!result) {
151 | assert(false && "failed to append arguments to message");
152 | }
153 |
154 | if (!dbus_connection_send(service_bus, msg, nullptr)) {
155 | assert(false && "failed to send message");
156 | }
157 |
158 | dbus_message_unref(msg);
159 | }
160 |
161 | void updateHUD(
162 | int32_t manueverIcon,
163 | int32_t manueverDistance,
164 | int32_t manueverDistanceUnit,
165 | int32_t speedLimit,
166 | int32_t speedLimitUnit,
167 | const char *streetName,
168 | int32_t priority)
169 | {
170 | GuidanceChanged(manueverIcon, manueverDistance, manueverDistanceUnit, speedLimit, speedLimitUnit, 0, 0, 0, 0, 0, 0, 0, 0);
171 | NotificationBar_Notify(manueverIcon, manueverDistance, manueverDistanceUnit, streetName, priority);
172 | SetHUDDisplayMsgReq(manueverIcon, manueverDistance, manueverDistanceUnit, speedLimit, speedLimitUnit);
173 | dbus_connection_flush(service_bus);
174 | }
175 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Mazda Connector[[1]](#1)
2 | Mazda Connector is a collection of binaries to augment the functionality of the MZD Connect infotainment system on Mazda's current line of cars.[[2]](#2) Currently, there are three components: an Android application (``android``), a server binary which runs on the infotainment system (``connector``), and an input capturing binary (``input_filter``) that does a bunch of gross hacks to capture input from the steering wheel/commander knob before it hits the system.
3 | ### Components
4 | ##### Android
5 | An Android application which provides a bluetooth server socket to which the car can connect for bidirectional communication. Currently, this is only used to capture the steering wheel talk button's input events (single press to play/pause music, long press to trigger Google Now, and other inputs (e.g. quadruple press followed by a long press) to trigger text to speech saying what you pressed), but it can be easily extended to do more interesting things.
6 |
7 | ##### Connector
8 | A daemon that handles connecting to the Android application through the system's native bluetooth stack, and listens to incoming events via D-BUS to tell the phone to do stuff.
9 |
10 | ##### Input Filter
11 | A daemon that runs before any of the system's input reading services start, which filters out specific button presses (currently only the steering wheel talk button) and sends the events to connector for handling.
12 |
13 | ### Building
14 | Building the connector and input filter components requires an ARM cross-compilation toolchain with a relatively new version of gcc (C++11 support is required), glibc 2.11, libdbus, boost headers. A toolchain built for Ubuntu 14.10 is available [here](https://github.com/jmgao/m3-toolchain). [ninja](http://martine.github.io/ninja/) is also required, because I'm too lazy to write a makefile. The android app is built with [sbt](http://www.scala-sbt.org/download.html).
15 |
16 | ### Installation
17 | **WARNING**: DO NOT DO THIS UNLESS YOU KNOW WHAT YOU ARE DOING.
18 | _**THIS CAN BRICK YOUR INFOTAINMENT SYSTEM IF YOU ARE UNLUCKY OR UNCAREFUL.**_
19 | Here is a checklist of things you should verify (and know how to verify) before even thinking of installing this:
20 |
21 | 1. ``/dev/input/event1`` exists and is the virtual keyboard device.
22 | 2. ``/dev/input/event5`` is the last event device in ``/dev/input``.
23 | 3. KEY_G is the voice recognition button on your steering wheel.
24 |
25 | ##### Bootloop prevention
26 | Several of the services on the infotainment system will cause a reboot of the system if they fail. If this happens during the boot sequence because one of the daemons added by us has broken some invariant expected by the services, it will probably enter an endless bootloop which you won't be able to fix with ssh. To prevent this, both `connector` and `input_filter` are controlled by the `enable_connector` and `enable_input_filter` files in /tmp/mnt/data. The daemons will check these files on startup for '1' to decide whether to do stuff, and set them to '0' before continuing. If they happen to break things and cause a reboot, they'll do nothing on next boot, preventing Bad Things from happening. When you have everything working after a successful boot, setting the files to contain '2' will prevent them from disabling themselves on future boots.
27 |
28 | ##### Install the android app
29 | Sideload it onto your android device through adb or google drive or emailing the APK to yourself or whatever. Make sure that your phone is the only device paired with the car, and start the application (it's currently just a blank screen right now). The application needs to be started at least once after installing, upgrading, or force stopping it, because Android prevents it from automatically starting while it is in the stopped state. After it's been launched once, it'll be automatically started in the background when the bluetooth state changes.
30 |
31 | ##### Install connector
32 | Installation of ``connector`` and verifying that it works is relatively safe. Add an entry for the service into ``/jci/bds/BdsConfiguration.xml``:
33 | ```
34 |
35 | ```
36 | Then, copy the binary to somewhere like ``/tmp/mnt/data``, and add it to one of the later stage start scripts specificed in ``/jci/sm/sm.conf``, such as ``/jci/scripts/stage_gap2.sh``. Changing the file to contain the following should work:
37 | ```
38 | #!/bin/sh
39 | /tmp/mnt/data/connector > /tmp/mnt/data/connector.log 2>&1 &
40 | ```
41 | Currently, connector is pretty much completely safe, so you can set ``enable_connector`` to '2' from the start. This may not be true in the future, so on upgrades, you should probably always be setting the values for both daemons to '1')
42 |
43 | ##### Add input_filter to the startup manifest
44 | This is the scary part: if you mess up here, you've bricked your car. Edit the ``/jci/sm/sm.conf`` file to add ``input_filter`` to the startup sequence.
45 | ```
46 |
47 |
48 |
49 | ```
50 | We want ``input_filter`` to run immediately before the first process which consumes input, which is ``devices``. Therefore, we need to add a dependency for it on the ``input_filter`` service. (FIXME: Is there a race here?)
51 | ```
52 |
53 |
54 |
55 | >>>
56 |
57 | ```
58 | After modifying sm.conf and triple checking that it's correct and no mistaken changes have been made, reboot with '1' in ``/tmp/mnt/data/enable_input_filter`` and verify that the voice recognition button no longer triggers the stock voice prompt.
59 |
60 | At this point, everything is probably working, so you should be able to enable both services permanently.
61 |
62 | ### License
63 | AGPLv3
64 |
65 | --
66 | 1. Placeholder name until I can think of something better; Mazda please don't sue me
67 | 2. Currently only tested with the 2014 Mazda 3
68 |
69 |
--------------------------------------------------------------------------------
/src/connector/gesture_recognizer.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | #include
13 |
14 | #include "gesture_recognizer.hpp"
15 |
16 | #define USE_THREAD_FOR_CALLBACK 1
17 | constexpr long MS_TO_NS = 1000000;
18 |
19 | gesture_recognizer::gesture_recognizer(cb_receive_input input_callback, int long_press_ms, int tap_interval_ms) :
20 | input_callback(input_callback), long_press_ms(long_press_ms), tap_interval_ms(tap_interval_ms)
21 | {
22 | if (::pipe(input_pipe) != 0) {
23 | err(1, "failed to create input pipe in gesture_recognizer");
24 | }
25 |
26 | this->spawn_thread();
27 | }
28 |
29 | gesture_recognizer::~gesture_recognizer(void)
30 | {
31 | ::close(input_pipe[0]);
32 | ::close(input_pipe[1]);
33 | }
34 |
35 | void gesture_recognizer::spawn_thread(void)
36 | {
37 | assert(!started && "gesture_recognizer started multiple times");
38 | started = true;
39 |
40 | std::thread([this]() {
41 | int epfd = ::epoll_create(1);
42 | struct epoll_event epoll_event = {
43 | .events = EPOLLIN,
44 | .data = {
45 | .u64 = 0
46 | }
47 | };
48 |
49 | epoll_ctl(epfd, EPOLL_CTL_ADD, input_pipe[0], &epoll_event);
50 |
51 | while (true) {
52 | int count = ::epoll_wait(epfd, &epoll_event, 1, -1);
53 | if (count <= 0) {
54 | err(1, "epoll_wait failed");
55 | }
56 |
57 | if (epoll_event.data.u64 == 0) {
58 | // Input event
59 | struct input_event event;
60 | if (::read(input_pipe[0], &event, sizeof event) != sizeof event) {
61 | err(1, "failed to receive input event");
62 | }
63 |
64 | if (event.type != EV_KEY) {
65 | printf("received unhandled input, ignoring (type = %u, code = %u, value = %d)\n", event.type, event.code, event.value);
66 | continue;
67 | }
68 |
69 | auto &state = this->key_state[event.code];
70 | if (event.value == 1) {
71 | // KEY_DOWN
72 | assert(state.last_value == 0);
73 |
74 | ++state.tap_count;
75 |
76 | struct itimerspec long_press_timerspec = {
77 | .it_interval = {0, 0},
78 | .it_value = { long_press_ms / 1000, (long_press_ms % 1000) * MS_TO_NS }
79 | };
80 |
81 | if (state.tap_count == 1) {
82 | // Create the timer if it doesn't already exist
83 | if (state.timer < 0) {
84 | state.timer = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
85 | if (state.timer < 0) {
86 | err(1, "failed to create timer");
87 | }
88 |
89 | struct epoll_event timer_event = {
90 | .events = EPOLLIN,
91 | .data = {
92 | .u64 = event.code
93 | }
94 | };
95 |
96 | if (::epoll_ctl(epfd, EPOLL_CTL_ADD, state.timer, &timer_event) != 0) {
97 | err(1, "failed to add timer to epoll");
98 | }
99 | }
100 |
101 | // Start the timer
102 | if (::timerfd_settime(state.timer, 0, &long_press_timerspec, nullptr) != 0) {
103 | err(1, "failed to set timer for key down");
104 | }
105 | } else {
106 | // Update the timer to the long press interval
107 | assert(state.timer >= 0);
108 | if (::timerfd_settime(state.timer, 0, &long_press_timerspec, nullptr) != 0) {
109 | err(1, "failed to set timer for key down");
110 | }
111 | }
112 | } else {
113 | // KEY_UP
114 | assert(event.value == 0);
115 |
116 | if (state.tap_count == 0) {
117 | printf("received key up for key with no current event, ignoring\n");
118 | assert(state.tap_count == 0);
119 | assert(state.last_value == 0);
120 | } else {
121 | assert(state.last_value == 1);
122 | assert(state.timer >= 0);
123 |
124 | // Start the tap interval timer
125 | struct itimerspec tap_interval_timerspec = {
126 | .it_interval = {0, 0},
127 | .it_value = { tap_interval_ms / 1000, (tap_interval_ms % 1000) * MS_TO_NS }
128 | };
129 |
130 | if (::timerfd_settime(state.timer, 0, &tap_interval_timerspec, nullptr) != 0) {
131 | err(1, "failed to set timer for key up");
132 | }
133 | }
134 | }
135 |
136 | state.last_value = event.value;
137 | } else {
138 | // A timer expired
139 | int keycode = epoll_event.data.u64;
140 | auto it = this->key_state.find(keycode);
141 | assert(it != this->key_state.end());
142 | auto &state = it->second;
143 |
144 | assert(state.tap_count > 0);
145 | assert(state.timer >= 0);
146 |
147 | // Turn off the timer
148 | struct itimerspec disabled_timerspec = {
149 | .it_interval = {0, 0},
150 | .it_value = {0, 0}
151 | };
152 |
153 | if (::timerfd_settime(state.timer, 0, &disabled_timerspec, nullptr) != 0) {
154 | err(1, "failed to disable timer");
155 | }
156 |
157 | gesture result;
158 | result.keycode = keycode;
159 | if (state.last_value == 0) {
160 | // Tap interval timer went off
161 | result.long_press = false;
162 | } else {
163 | // Long press timer went off
164 | result.long_press = true;
165 | }
166 |
167 | result.tap_count = state.tap_count;
168 |
169 | #if USE_THREAD_FOR_CALLBACK
170 | std::thread([this, result]() {
171 | #endif
172 | input_callback(result);
173 | #if USE_THREAD_FOR_CALLBACK
174 | }).detach();
175 | #endif
176 |
177 | state.tap_count = 0;
178 | state.last_value = 0;
179 | }
180 | }
181 |
182 | }).detach();
183 | }
184 |
185 | void gesture_recognizer::handle_input(const struct input_event *input)
186 | {
187 | if (::write(input_pipe[1], input, sizeof *input) != sizeof *input) {
188 | err(1, "failed to write input into pipe");
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/input_filter/input_filter.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 | #include
15 |
16 | #include
17 | #include
18 | #include
19 |
20 | #include
21 |
22 | #include "shared/dbus_helpers.hpp"
23 | #include "shared/prevent_brick.hpp"
24 |
25 | #define HMI_BUS_ADDRESS "unix:path=/tmp/dbus_hmi_socket"
26 |
27 | constexpr char source_device[] = "/dev/input/event1";
28 | constexpr char filtered_name[] = "Virtual Keyboard";
29 |
30 | // HACK: There doesn't seem to be a good way to get the path of the device
31 | // The UI_GET_SYSNAME ioctl requires kernel 3.15+
32 | constexpr char new_device_path[] = "/dev/input/event6";
33 |
34 | static int infd = -1;
35 | static int outfd = -1;
36 |
37 | using matcher_fn_t = std::function;
38 | static std::vector matchers;
39 |
40 | static void destroy_device(int fd)
41 | {
42 | ::ioctl(fd, UI_DEV_DESTROY);
43 | ::close(fd);
44 | }
45 |
46 | static void __attribute__((destructor)) cleanup(void)
47 | {
48 | if (outfd >= 0) {
49 | destroy_device(outfd);
50 | }
51 | }
52 |
53 | static void signal_handler(int)
54 | {
55 | exit(1);
56 | }
57 |
58 | // SUPER HACKY: Create a bunch of dummy input devices to force our device to get created at event6
59 | static int make_dummy(void)
60 | {
61 | static int counter = 0;
62 | int dummy = ::open("/dev/uinput", O_WRONLY);
63 | ::ioctl(dummy, UI_SET_EVBIT, EV_SYN);
64 |
65 | struct uinput_user_dev uidev;
66 | memset(&uidev, 0, sizeof uidev);
67 | snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "dummy%d", counter++);
68 | uidev.id.bustype = BUS_USB;
69 | uidev.id.vendor = 0x1234;
70 | uidev.id.product = 0xfedc;
71 | uidev.id.version = 1;
72 |
73 | if (::write(dummy, &uidev, sizeof uidev) != sizeof uidev) {
74 | err(1, "failed to write uidev struct");
75 | }
76 |
77 | if (::ioctl(dummy, UI_DEV_CREATE) != 0) {
78 | err(1, "failed to create device");
79 | }
80 |
81 | return dummy;
82 | }
83 |
84 | static void initialize(void)
85 | {
86 | signal(SIGINT, signal_handler);
87 |
88 | int dummies[3];
89 | dummies[0] = make_dummy();
90 | dummies[1] = make_dummy();
91 | dummies[2] = make_dummy();
92 |
93 | outfd = ::open("/dev/uinput", O_WRONLY);
94 | if (outfd < 0) {
95 | err(1, "failed to open uinput device");
96 | }
97 |
98 | infd = ::open(source_device, O_RDONLY);
99 | if (infd < 0) {
100 | err(1, "failed to open input device %s", source_device);
101 | }
102 |
103 | if (::ioctl(outfd, UI_SET_EVBIT, EV_SYN) != 0) {
104 | err(1, "failed to enable EV_SYN");
105 | }
106 |
107 | if (::ioctl(outfd, UI_SET_EVBIT, EV_KEY) != 0) {
108 | err(1, "failed to enable EV_SYN");
109 | }
110 |
111 | if (::ioctl(outfd, UI_SET_EVBIT, EV_LED) != 0) {
112 | err(1, "failed to enable EV_SYN");
113 | }
114 |
115 | for (int i = 0; i < 199; ++i) {
116 | if (::ioctl(outfd, UI_SET_KEYBIT, i) != 0) {
117 | err(1, "failed to enable key bit %d", i);
118 | }
119 | }
120 |
121 | struct uinput_user_dev uidev;
122 | memset(&uidev, 0, sizeof uidev);
123 | snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, filtered_name);
124 | uidev.id.bustype = BUS_USB;
125 | uidev.id.vendor = 0x1234;
126 | uidev.id.product = 0xfedc;
127 | uidev.id.version = 1;
128 |
129 | if (::write(outfd, &uidev, sizeof uidev) != sizeof uidev) {
130 | err(1, "failed to write uidev struct");
131 | }
132 |
133 | // Replace the old device
134 | struct stat st;
135 | if (::stat(new_device_path, &st) == 0) {
136 | errx(1, "new_device_path '%s' exists before creating input device", new_device_path);
137 | }
138 |
139 | if (::ioctl(outfd, UI_DEV_CREATE) != 0) {
140 | err(1, "failed to create device");
141 | }
142 |
143 | if (::stat(new_device_path, &st) != 0) {
144 | err(1, "couldn't stat created device");
145 | }
146 |
147 | // HACK: Wait for udev to populate /dev/input/
148 | sleep(1);
149 |
150 | if (::unlink(source_device) != 0) {
151 | err(1, "failed to unlink source device '%s'", source_device);
152 | }
153 |
154 | if (::rename(new_device_path, source_device) != 0) {
155 | err(1, "failed to move new device");
156 | }
157 |
158 | destroy_device(dummies[0]);
159 | destroy_device(dummies[1]);
160 | destroy_device(dummies[2]);
161 | }
162 |
163 | static void __attribute__((noreturn)) loop(void)
164 | {
165 | enum class events {
166 | input,
167 | };
168 |
169 | int epfd = ::epoll_create(1);
170 | if (epfd < 0) {
171 | err(1, "failed to create epoll fd");
172 | }
173 |
174 | struct epoll_event event {
175 | .events = EPOLLIN,
176 | .data = {
177 | .u64 = uint64_t(events::input)
178 | }
179 | };
180 |
181 | if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &event) != 0) {
182 | err(1, "failed to add input fd to epoll set");
183 | }
184 |
185 | constexpr size_t buffer_size = 64;
186 | std::vector iovecs;
187 | iovecs.reserve(buffer_size);
188 | while (epoll_wait(epfd, &event, 1, -1) >= 0) {
189 | switch (events(event.data.u64)) {
190 | case events::input:
191 | {
192 | iovecs.clear();
193 | struct input_event ev_buf[buffer_size];
194 | ssize_t bytes_read = ::read(infd, &ev_buf, sizeof ev_buf);
195 | if (bytes_read <= 0 || bytes_read % sizeof(struct input_event) != 0) {
196 | err(1, "failed to read event");
197 | }
198 |
199 | ssize_t events_read = bytes_read / sizeof (struct input_event);
200 | for (int i = 0; i < events_read; ++i) {
201 | struct input_event *ev = &ev_buf[i];
202 | bool matched = false;
203 | for (const auto &matcher : matchers) {
204 | if (matcher(ev)) {
205 | matched = true;
206 | break;
207 | }
208 | }
209 |
210 | if (!matched) {
211 | iovecs.push_back({
212 | .iov_base = ev,
213 | .iov_len = sizeof (struct input_event)
214 | });
215 | }
216 | }
217 |
218 | ::writev(outfd, iovecs.data(), iovecs.size());
219 | }
220 | }
221 | }
222 |
223 | err(1, "epoll_wait failed");
224 | }
225 |
226 | int main(int argc, const char *argv[])
227 | {
228 | // Try not to brick the car
229 | prevent_brick("/tmp/mnt/data/enable_input_filter");
230 |
231 | // BEGIN SCARY STUFF:
232 | initialize();
233 |
234 | DBusError error;
235 | dbus_error_init(&error);
236 |
237 | DBusConnection *hmi_bus = dbus_connection_open(HMI_BUS_ADDRESS, &error);
238 | if (!hmi_bus) {
239 | errx(1, "failed to connect to HMI bus: %s: %s\n", error.name, error.message);
240 | }
241 |
242 | if (!dbus_bus_register(hmi_bus, &error)) {
243 | errx(1, "failed to register with HMI bus: %s: %s\n", error.name, error.message);
244 | }
245 |
246 | const std::unordered_set captured_keys = {
247 | // Steering wheel talk
248 | KEY_G,
249 |
250 | // Console navigation
251 | // KEY_R,
252 | };
253 |
254 | matchers.push_back(
255 | [hmi_bus, &captured_keys] (const struct input_event *ev) {
256 | if (ev->type == EV_KEY && captured_keys.count(ev->code)) {
257 | printf("received talk button, status = %d\n", ev->value);
258 |
259 | DBusMessage *msg = dbus_message_new_signal(
260 | "/us/insolit/mazda/connector", "us.insolit.mazda.connector", "KeyEvent");
261 |
262 | if (!msg) {
263 | errx(1, "failed to create dbus message");
264 | }
265 |
266 | DBusMessageIter iter;
267 | dbus_message_iter_init_append(msg, &iter);
268 |
269 | if (!dbus_message_encode_input_event(&iter, ev)) {
270 | errx(1, "failed to append input event");
271 | }
272 |
273 | if (!dbus_connection_send(hmi_bus, msg, nullptr)) {
274 | errx(1, "failed to send dbus message");
275 | }
276 |
277 | dbus_connection_flush(hmi_bus);
278 | dbus_message_unref(msg);
279 |
280 | return true;
281 | }
282 |
283 | return false;
284 | });
285 |
286 | loop();
287 | }
288 |
--------------------------------------------------------------------------------
/src/connector/bluetooth.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include