🌐 CZ / EN

bMyRing - inteligentní asistenční prsten

Zařízení ve formě prstenu slouží jako bezdrátový ovladač aplikace bMyVision, se kterou komunikuje prostřednictvím technologie Bluetooth. Uživatel má prsten na ukazováčku a palcem může stisknout tlačítko, tím lze ovládat jednotlivé funkce aplikace, přičemž krátký stisk přepíná mezi režimy a dvojklik slouží k jejich potvrzení. V aktivním režimu umožňuje krátké stisknutí pořízení snímku a následné rozpoznání obsahu obrazu pomocí funkcí aplikace. Dlouhé stisknutí pak zajišťuje návrat do základního menu, čímž je ovládání intuitivní a snadno zapamatovatelné. Řešení je navrženo tak, aby maximálně zjednodušilo interakci zrakově postižených uživatelů s aplikací a zvýšilo jejich samostatnost při každodenních činnostech.

bMyRing - inteligentní asistenční prsten

Elektronika inteligentního asistenčního prstenu je založena na číslicovém obvodu EFR32BG22C222F352GN32. Schéma zapojení:

bMyRing - inteligentní asistenční prsten

Program pro číslicový obvod:

/***************************************************************************//**
 * @file
 * @brief Core application logic.
 *******************************************************************************
 * # License
 * <b>Copyright 2024 Silicon Laboratories Inc. www.silabs.com</b>
 *******************************************************************************
 *
 * SPDX-License-Identifier: Zlib
 *
 * The licensor of this software is Silicon Laboratories Inc.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 ******************************************************************************/
#include "sl_bt_api.h"
#include "sl_main_init.h"
#include "app_assert.h"
#include "app.h"

#include "em_gpio.h"
#include "sl_sleeptimer.h"
#include "sl_gpio.h"
#include "sl_power_manager.h"
#include "sl_bt_api.h"
#include "em_emu.h"
#include "em_cmu.h"
#include "sl_simple_button_instances.h"


/*IDLE
 └─ press → PRESSED
PRESSED
 ├─ release → WAIT_MULTI
 └─ timeout → LONG_PRESS
WAIT_MULTI
 ├─ press → PRESSED
 └─ timeout → EVALUATE_CLICKS*/

#define DEBOUNCE_MS             30
#define LONG_PRESS_MS           600 //800
#define MULTI_CLICK_TIMEOUT_MS  350 //400
#define IDLE_CLICK_TIMEOUT_MS  1500   //zadne kliknuti
#define INACTIVITY_MS          20000  // spadne do hlubokeho spanku - reset

// The advertising set handle allocated from Bluetooth stack.
static uint8_t advertising_set_handle = 0xff;

static uint8_t adv_data[] = { //data pro advertising
  2, 0x01, 0x06,            // flags
  10,  0x16,                  // length + type (service data 0x16)
  0xD2, 0xFC,               // service UUID 0x180F
  64,  0, 175, 1, 61,  58,  0  //adv_data[13]= button_STATUS - 1;
};

typedef enum
{
  BUTTON_IDLE,
  BUTTON_PRESSED,
  BUTTON_WAIT_MULTI,
  BUTTON_LONG_PRESS
} button_state_t;

static volatile button_state_t button_state = BUTTON_IDLE;
static volatile int sleep_state_ = 0;

static sl_sleeptimer_timer_handle_t debounce_timer;
static sl_sleeptimer_timer_handle_t long_timer;
static sl_sleeptimer_timer_handle_t multi_timer;
static sl_sleeptimer_timer_handle_t idle_timer;
static sl_sleeptimer_timer_handle_t inactivity_timer;

static uint8_t click_count = 0;
static volatile bool need_change_advertising = false;
static uint8_t new_click_type = 0;   // 1=short, 2=double, 3=triple, 4=long atd.


static void inactivity_timeout_cb(sl_sleeptimer_timer_handle_t *handle, void *data)
{ (void)data; (void)handle;
    sleep_state_=1; // prechod do deep sleep
}

static void evaluate_clicks(void)
{
  switch(click_count)
  {
    case 1:  //    button_event_short();   adv_data[13]= 1;
        new_click_type = 1;
        need_change_advertising = true;
        sl_bt_external_signal(1);   // signál pro změnu reklamy
      break;

    case 2:  //    button_event_double();        adv_data[13]= 2;
        new_click_type = 2; need_change_advertising = true;
        sl_bt_external_signal(1);   // signál pro změnu reklamy
      break;

    case 3:  //    button_event_triple(); adv_data[13]= 3;
        new_click_type = 3; need_change_advertising = true;
        sl_bt_external_signal(1);   // signál pro změnu reklamy
      break;

    default:
      break;
  }

  click_count = 0;
}

static void long_press_timeout(sl_sleeptimer_timer_handle_t *handle, void *data)
{ (void) handle; (void) data;
  if(button_state == BUTTON_PRESSED)
  {
    button_state = BUTTON_LONG_PRESS;
    click_count = 0;
    //button_event_long();
    new_click_type = 4; need_change_advertising = true;
    sl_bt_external_signal(1);   // signál pro změnu reklamy
  }
}

static void multi_click_timeout(sl_sleeptimer_timer_handle_t *handle, void *data)
{ (void) handle; (void) data;
  if(button_state == BUTTON_WAIT_MULTI)
  {
    evaluate_clicks();
    button_state = BUTTON_IDLE;
  }
}

static void idle_press_timeout(sl_sleeptimer_timer_handle_t *handle, void *data)
{ (void) handle; (void) data;
  if(button_state == BUTTON_IDLE)
  {
    new_click_type = 0; need_change_advertising = true;
    sl_bt_external_signal(1);   // signál pro změnu reklamy
  }
}

// test na zakmity
static void debounce_timeout(sl_sleeptimer_timer_handle_t *handle, void *data)
{ (void) handle; (void) data;
  //bool pressed = !GPIO_PinInGet(BUTTON_PORT, BUTTON_PIN);// aktivni v nule

  bool pressed = sl_button_get_state(SL_SIMPLE_BUTTON_INSTANCE(0));

  switch(button_state)
  {
    case BUTTON_IDLE:
      if(pressed)
      {
        button_state = BUTTON_PRESSED;
        sl_sleeptimer_start_timer_ms( &long_timer, LONG_PRESS_MS, long_press_timeout,NULL,0,0);
        sl_sleeptimer_restart_timer_ms(&inactivity_timer, INACTIVITY_MS, inactivity_timeout_cb, NULL, 0, 0);
      }
      break;

    case BUTTON_PRESSED:
      if(!pressed)
      {
        sl_sleeptimer_stop_timer(&long_timer);
        click_count++;
        button_state = BUTTON_WAIT_MULTI;
        sl_sleeptimer_start_timer_ms( &multi_timer, MULTI_CLICK_TIMEOUT_MS, multi_click_timeout,NULL,0,0);
      }
      break;

    case BUTTON_WAIT_MULTI:
      if(pressed)
      {
        button_state = BUTTON_PRESSED;
        sl_sleeptimer_start_timer_ms(&long_timer, LONG_PRESS_MS, long_press_timeout,NULL,0,0);
      }
      break;

    case BUTTON_LONG_PRESS:

      if(!pressed)
      {
        button_state = BUTTON_IDLE;
      }
      break;
  }
}

/***************************************************************************//**
 * Simple Button
 * Button state changed callback
 * @param[in] handle Button event handle
 ******************************************************************************/
void sl_button_on_change(const sl_button_t *handle)
{
  if (SL_SIMPLE_BUTTON_INSTANCE(0) == handle) {
      if(sl_button_get_state(handle)) {
            app_proceed();//vzestupna hrana
        }
      else {
            app_proceed();// sestupna hrana
        }
  }
}


void button_init(void)
{
     sl_sleeptimer_start_timer_ms(&inactivity_timer, INACTIVITY_MS, inactivity_timeout_cb, NULL, 0, 0);
     GPIO_EM4EnablePinWakeup(GPIO_IEN_EM4WUIEN5, 0);
     GPIO_EM4SetPinRetention(true);
     GPIO_PinModeSet(gpioPortC, 0, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortC, 1, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortC, 2, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortC, 3, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortC, 4, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortC, 5, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortA, 0, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortA, 4, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortA, 6, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortB, 0, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortB, 1, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortB, 2, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortD, 0, gpioModePushPull, 0);
     GPIO_PinModeSet(gpioPortD, 1, gpioModePushPull, 0);
}

// Application Init.
void app_init(void)
{
    EMU_UnlatchPinRetention();
    button_init();
    sl_sleeptimer_start_timer_ms( &debounce_timer, DEBOUNCE_MS, debounce_timeout, NULL,  0, 0);
}

// Application Process Action.
void app_process_action(void)
{
  if (app_is_process_required()) {
      sl_sleeptimer_start_timer_ms( &debounce_timer, DEBOUNCE_MS, debounce_timeout, NULL,  0, 0);
  }
  if(sleep_state_)
    {
      sleep_state_=0;
      sl_bt_advertiser_stop(advertising_set_handle);
      sl_bt_system_halt(1);
      sl_power_manager_enter_em4();
    }
}

/**************************************************************************//**
 * Bluetooth stack event handler.
 *
 * @param[in] evt Event coming from the Bluetooth stack.
 *****************************************************************************/
void sl_bt_on_event(sl_bt_msg_t *evt)
{
  sl_status_t sc;

  switch (SL_BT_MSG_ID(evt->header)) {
    case sl_bt_evt_system_boot_id:
      sc = sl_bt_advertiser_create_set(&advertising_set_handle);
      app_assert_status(sc);

      sc = sl_bt_legacy_advertiser_generate_data(advertising_set_handle,
                                                 sl_bt_advertiser_general_discoverable);
      app_assert_status(sc);

      sc = sl_bt_advertiser_set_timing(
        advertising_set_handle,
        160,
        160,
        0,
        0);
      app_assert_status(sc);

      sc = sl_bt_legacy_advertiser_set_data(advertising_set_handle, sl_bt_advertiser_general_discoverable, sizeof(adv_data), adv_data);
      sc = sl_bt_legacy_advertiser_start(advertising_set_handle,
                                         sl_bt_legacy_advertiser_connectable);
      sl_sleeptimer_start_periodic_timer_ms(&idle_timer, IDLE_CLICK_TIMEOUT_MS , idle_press_timeout, NULL, 0, 0);
      app_assert_status(sc);
      break;

    case sl_bt_evt_connection_opened_id:
      break;

    case sl_bt_evt_connection_closed_id:
      sc = sl_bt_legacy_advertiser_generate_data(advertising_set_handle,
                                                 sl_bt_advertiser_general_discoverable);
      app_assert_status(sc);

      sc = sl_bt_legacy_advertiser_start(advertising_set_handle,
                                         sl_bt_legacy_advertiser_scannable);
      app_assert_status(sc);
      break;

    case sl_bt_evt_system_external_signal_id:
         if (evt->data.evt_system_external_signal.extsignals & 1) {
             if (need_change_advertising) {
                 need_change_advertising = false;
                 adv_data[13]=new_click_type;
                 sl_bt_legacy_advertiser_set_data(advertising_set_handle,
                      sl_bt_advertiser_general_discoverable, sizeof(adv_data), adv_data);
                 sl_bt_legacy_advertiser_start(advertising_set_handle, sl_bt_legacy_advertiser_scannable);
                 if(new_click_type)
                    sl_sleeptimer_restart_periodic_timer_ms(&idle_timer, IDLE_CLICK_TIMEOUT_MS , idle_press_timeout, NULL, 0, 0);
                 else
                    sl_sleeptimer_stop_timer(&idle_timer);
              }
           }
           break;

    default:
      break;
  }
}

Prototyp inteligentního asistenčního prstenu byl vyvinut s využitím moderního 3D tisku plošných spojů na tiskárně Dragonfly od firmy Nanodimension.