diff --git a/examples/device/nrf52840/src/main.c b/examples/device/nrf52840/src/main.c
index 286db9e39..b8f1674a7 100644
--- a/examples/device/nrf52840/src/main.c
+++ b/examples/device/nrf52840/src/main.c
@@ -102,51 +102,53 @@ void virtual_com_task(void)
 //--------------------------------------------------------------------+
 void usb_hid_task(void)
 {
-  if ( tud_mounted() )
+  /*------------- Keyboard -------------*/
+  if ( tud_hid_keyboard_ready() )
   {
+    // Poll every 10ms
+    static tu_timeout_t tm = { .start = 0, .interval = 10 };
 
+    if ( !tu_timeout_expired(&tm) ) return; // not enough time
+    tu_timeout_reset(&tm);
 
-    /*------------- Keyboard -------------*/
-/*
-    if ( !tud_hid_keyboard_busy() )
+    uint32_t const btn = board_buttons();
+
+    if ( btn )
     {
-      if ( btn )
-      {
-        uint8_t keycode[6] = { 0 };
+      uint8_t keycode[6] = { 0 };
 
-        for(uint8_t i=0; i < 6; i++)
-        {
-          if ( btn & (1 << i) ) keycode[i] = HID_KEY_A + i;
-        }
-
-        tud_hid_keyboard_send_keycode(0, keycode);
-      }else
+      for(uint8_t i=0; i < 6; i++)
       {
-        // Null means empty (all zeroes) report
-        tud_hid_keyboard_send_report(NULL);
+        if ( btn & (1 << i) ) keycode[i] = HID_KEY_A + i;
       }
-    }
-*/
 
-    /*------------- Mouse -------------*/
-    if ( !tud_hid_mouse_busy() )
+      tud_hid_keyboard_keycode(0, keycode);
+    }else
     {
-      // Poll every 10ms
-      static tu_timeout_t tm = { .start = 0, .interval = 10 };
-
-      if ( !tu_timeout_expired(&tm) ) return; // not enough time
-      tu_timeout_reset(&tm);
-
-      uint32_t const btn = board_buttons();
-
-      if ( btn )
-      {
-        hid_mouse_report_t report = { .buttons = 0, .x = 10, .y = 0, .wheel = 0 };
-        tud_hid_mouse_report(&report);
-      }
+      // Null means all zeroes keycodes
+      tud_hid_keyboard_keycode(0, NULL);
     }
-
   }
+
+
+  /*------------- Mouse -------------*/
+  //    if ( !tud_hid_mouse_busy() )
+  //    {
+  //      // Poll every 10ms
+  //      static tu_timeout_t tm = { .start = 0, .interval = 10 };
+  //
+  //      if ( !tu_timeout_expired(&tm) ) return; // not enough time
+  //      tu_timeout_reset(&tm);
+  //
+  //      uint32_t const btn = board_buttons();
+  //
+  //      if ( btn )
+  //      {
+  //        hid_mouse_report_t report = { .buttons = 0, .x = 10, .y = 0, .wheel = 0 };
+  //        tud_hid_mouse_report(&report);
+  //      }
+  //    }
+
 }
 
 
diff --git a/examples/obsolete/device/src/keyboard_device_app.c b/examples/obsolete/device/src/keyboard_device_app.c
index 4e4de8996..4bb25b9c0 100644
--- a/examples/obsolete/device/src/keyboard_device_app.c
+++ b/examples/obsolete/device/src/keyboard_device_app.c
@@ -128,7 +128,7 @@ tusb_error_t keyboard_device_subtask(void)
 
   osal_task_delay(50);
 
-  if ( tud_mounted() && !tud_hid_keyboard_busy(0) )
+  if ( tud_mounted() && tud_hid_keyboard_ready(0) )
   {
     static uint32_t button_mask = 0;
     uint32_t new_button_mask = board_buttons();
diff --git a/src/class/hid/hid_device.c b/src/class/hid/hid_device.c
index a49479000..9f54da34f 100644
--- a/src/class/hid/hid_device.c
+++ b/src/class/hid/hid_device.c
@@ -86,14 +86,15 @@ CFG_TUSB_ATTR_USBRAM static hidd_interface_t _composite_itf;
 // KEYBOARD APPLICATION API
 //--------------------------------------------------------------------+
 #if CFG_TUD_HID_KEYBOARD
-bool tud_hid_keyboard_busy(void)
+bool tud_hid_keyboard_ready(void)
 {
-  return dcd_edpt_busy(TUD_OPT_RHPORT, _kbd_itf.ep_in);
+  VERIFY( _kbd_itf.ep_in != 0 );
+  return !dcd_edpt_busy(TUD_OPT_RHPORT, _kbd_itf.ep_in);
 }
 
-bool tud_hid_keyboard_report(hid_keyboard_report_t const *p_report)
+static bool hidd_kbd_report(hid_keyboard_report_t const *p_report)
 {
-  VERIFY( tud_mounted() && !tud_hid_keyboard_busy() );
+  VERIFY( tud_hid_keyboard_ready() );
 
   hidd_interface_t * p_hid = &_kbd_itf;
 
@@ -102,7 +103,10 @@ bool tud_hid_keyboard_report(hid_keyboard_report_t const *p_report)
     memcpy(p_hid->report_buf, p_report, sizeof(hid_keyboard_report_t));
   }else
   {
-    // empty report
+    // only send empty report if previous report is not empty
+    // TODO idle rate
+    if ( mem_all_zero(p_hid->report_buf, sizeof(hid_keyboard_report_t)) ) return true;
+
     arrclr_(p_hid->report_buf);
   }
 
@@ -112,27 +116,29 @@ bool tud_hid_keyboard_report(hid_keyboard_report_t const *p_report)
 bool tud_hid_keyboard_keycode(uint8_t modifier, uint8_t keycode[6])
 {
   hid_keyboard_report_t report = { .modifier = modifier };
-  memcpy(report.keycode, keycode, 6);
 
-  return tud_hid_keyboard_report(&report);
+  if ( keycode )
+  {
+    memcpy(report.keycode, keycode, 6);
+  }else
+  {
+    memclr_(report.keycode, 6);
+  }
+
+  return hidd_kbd_report(&report);
 }
 
 #if CFG_TUD_HID_ASCII_TO_KEYCODE_LOOKUP
 
 bool tud_hid_keyboard_key_press(char ch)
 {
-  hid_keyboard_report_t report;
-  varclr_(&report);
+  uint8_t keycode[6] = { 0 };
+  uint8_t modifier   = 0;
 
-  report.modifier   = ( HID_ASCII_TO_KEYCODE[(uint8_t)ch].shift ) ? KEYBOARD_MODIFIER_LEFTSHIFT : 0;
-  report.keycode[0] = HID_ASCII_TO_KEYCODE[(uint8_t)ch].keycode;
+  if ( HID_ASCII_TO_KEYCODE[(uint8_t)ch].shift ) modifier = KEYBOARD_MODIFIER_LEFTSHIFT;
+  keycode[0] = HID_ASCII_TO_KEYCODE[(uint8_t)ch].keycode;
 
-  return tud_hid_keyboard_report(&report);
-}
-
-bool tud_hid_keyboard_key_release(void)
-{
-  return tud_hid_keyboard_report(NULL);
+  return tud_hid_keyboard_keycode(modifier, keycode);
 }
 
 bool tud_hid_keyboard_key_sequence(const char* str, uint32_t interval_ms)
@@ -152,7 +158,7 @@ bool tud_hid_keyboard_key_sequence(const char* str, uint32_t interval_ms)
      * the current one, else no need to send */
     if ( lookahead == ch || lookahead == 0 )
     {
-      tud_hid_keyboard_report(NULL);
+      tud_hid_keyboard_key_release();
       tu_timeout_wait(interval_ms);
     }
   }
diff --git a/src/class/hid/hid_device.h b/src/class/hid/hid_device.h
index 842eb8fa7..76a59e13a 100644
--- a/src/class/hid/hid_device.h
+++ b/src/class/hid/hid_device.h
@@ -56,23 +56,18 @@
 /** \defgroup Keyboard_Device Device
  *  @{ */
 
-/** \brief      Check if the interface is currently busy or not
- * \retval      true if the interface is busy meaning the stack is still transferring/waiting data from/to host
- * \retval      false if the interface is not busy meaning the stack successfully transferred data from/to host
- * \note        This function is primarily used for polling/waiting result after \ref tusbd_hid_keyboard_send.
+/** Check if the interface is ready to use
+ * \returns true if ready, otherwise interface may not be mounted or still busy transferring data
+ * \note    Application must not perform any action if the interface is not ready
  */
-bool tud_hid_keyboard_busy(void);
+bool tud_hid_keyboard_ready(void);
 
-/** \brief        Send a keyboard report
- * \param[in,out] p_report Report data, if NULL, an empty report (all zeroes) is used
- * \returns       true on success, false otherwise (not mounted or busy)
- */
-bool tud_hid_keyboard_report(hid_keyboard_report_t const *p_report);
 bool tud_hid_keyboard_keycode(uint8_t modifier, uint8_t keycode[6]);
 
+static inline bool tud_hid_keyboard_key_release(void) { return tud_hid_keyboard_keycode(0, NULL); }
+
 #if CFG_TUD_HID_ASCII_TO_KEYCODE_LOOKUP
 bool tud_hid_keyboard_key_press(char ch);
-bool tud_hid_keyboard_key_release(void);
 bool tud_hid_keyboard_key_sequence(const char* str, uint32_t interval_ms);
 
 typedef struct{
@@ -131,6 +126,12 @@ bool tud_hid_mouse_busy(void);
  * \returns       true on success, false otherwise (not mounted or busy)
  */
 bool tud_hid_mouse_report(hid_mouse_report_t const *p_report);
+bool tud_hid_mouse_data(uint8_t buttons, int8_t x, int8_t y, int8_t scroll, int8_t pan);
+
+bool tud_hid_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan);
+
+bool tud_hid_mouse_button_press(uint8_t buttons);
+bool tud_hid_mouse_button_release(uint8_t buttons);
 
 /*------------- Callbacks -------------*/