Во время удаленной работы бывает тяжело сохранить прежнюю производительность. Мне в этом деле помогло отслеживание своего потраченного времени. Для этой цели я сделал себе тайм трекер куб. Каждая сторона куба отвечает за тот или иной вид деятельности. Затем, я понял, что шести сторон куба для меня недостаточно и решил сделать больше. Таким образом, я сделал Тайм трекер додекаэдр с 12 сторонами. В данной статье расскажу, как собрать и запрограммировать свой тайм трекер интегрированный с уже существующей системой, используя его API.
Использование существующего тайм трекера дает нам множество преимуществ, можно использовать веб, десктопную или мобильную версию трекера в любой удобный момент. Нам не требуется задумываться над методом и системой хранения данных про наши записи времени и мы можем использовать уже имеющиеся отчеты данной системы. А наш трекер додекаэдр служит дополнением ко всему этому. Ссылка на github страницу моего проекта, где лежит весь нужный код. Ниже я подробно расскажу, как работать с датчиком наклона, распознать на какой стороне лежит додекаэдр, что такое Toggl Track и как работать с его API для отслеживания времени.

В данную статью включена часть из моей статьи тайм трекер куб для общего понимания. Несколько особых функций, которые были добавлены в данный проект:

  • Распознавание стороны на которой лежит додекаэдр. 

  • Умное подключение к Wi-Fi.

  • Остановка таймера отслеживания времени.

Нам понадобятся датчик MPU6050, Ардуино и Wi-Fi модуль. Я использую микроконтроллер NodeMCU, в котором встроен Wi-Fi модуль. Но вся логика применима при использовании Ардуино с ESP8266. 

Распознать на какой стороне лежит додекаэдр

MPU6050

В проекте используется датчик MPU6050. Он представляет собой 3-х осевой гироскоп и 3-х осевой акселерометр в одном корпусе. Он имеет интерфейс I2C, который мы будем использовать для подключения. Для Ардуино: A5 — SCL, A4 — SDA, 5V—VCC и GND — GND; для NodeMCU: D1— SCL, D2 — SDA, 3.3V— VCC и GND — GND.

Получение данных с датчика

Загрузив данный код на микроконтроллер, открыв serial monitor и выставив baud rate на 9600, мы получим сырые данные с гироскопа и акселерометра.

#include "Wire.h"
#include "I2Cdev.h"
#include "MPU6050.h"
MPU6050 mpu;
int16_t ax, ay, az;
int16_t gx, gy, gz;
void setup() {
  Wire.begin();
  Serial.begin(9600);
  mpu.initialize();
  //connection status
  Serial.println(mpu.testConnection() ? "MPU6050 OK" : "MPU6050 FAIL");
  delay(1000);
}
void loop() {
  mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  Serial.print(ax); Serial.print('\t');
  Serial.print(ay); Serial.print('\t');
  Serial.print(az); Serial.print('\t');
  Serial.print(gx); Serial.print('\t');
  Serial.print(gy); Serial.print('\t');
  Serial.println(gz);
  delay(50);
}

Довольно интересно покрутить, поиграться с датчиком и понаблюдать за показателями.

Данные будут в диапазоне от -32768 до 32767. Для более удобной работы с данными, давайте сделаем диапазон короче (-100 … 100) разделив значение на 327. Также, для определения на какой стороне лежит додекаэдр, нам будет достаточно использовать данные с акселерометра. Мы будем использовать ускорение свободного падения - гравитацию Земли.

Serial.print(ax/327); Serial.print('\t');
Serial.print(ay/327); Serial.print('\t');
Serial.print(az/327); Serial.print('\t');
Serial.println("");
delay(50);

Додекаэдр

Чтобы распознать, на какой стороне лежит додекаэдр, нам нужен сам додекаэдр. Мы можем распечатать на картоне и собрать данное оригами

Теперь перейдем к распознаванию на какой стороне лежит додекаэдр.
Общая логика: у нас будет список с показаниями датчика по трем осям акселерометра на каждой стороне додекаэдра, мы будем сверять показания датчика в данный момент с показаниями из списка и после проверять стабильно ли лежит додекаэдр в данном положении.

Epsilon - погрешность используемая при сверке.

Список с показаниями датчика по трем осям акселерометра (x, y, z) на каждой стороне додекаэдра:

int16_t epsilon = 6;
const int side_values[12][3] = {
  {-2,    0,  46},
  {21,  -38,  21},
  {-29, -32,  20},
  {-40,  18,  17},
  {5,    44,  19},
  {43,    7,  21},
  {2,     0, -54},
  {-42,  -8, -29},
  {-4,  -44, -27},
  {40,  -19, -25},
  {32,   31, -27},
  {-19,  38, -27}
};

Сверка показателей датчика со списком side_values и выявление на какой стороне лежит додекаэдр:

int dodecahedron_side = get_dodecahedron_side(ax/327, ay/327, az/327);

int get_dodecahedron_side(int16_t ax, int16_t ay, int16_t az){ 
  //return the dedocahedron side 0-11, else -2
  for(int i=0; i<12; i++){
    if((side_values[i][0]-epsilon < ax && ax < side_values[i][0]+epsilon) &&
       (side_values[i][1]-epsilon < ay && ay < side_values[i][1]+epsilon) &&
       (side_values[i][2]-epsilon < az && az < side_values[i][2]+epsilon)){//44 < 50 < 66
        return i;
    } 
  }
  return -2;
};

Для проверки того, стабильно ли додекаэдр лежит в данном положении, мы будем проверять, лежит ли он три секунды в одном положении. Перерыв между считыванием 50 миллисекунд. Разделив 3 секунды (3000 миллисекунд) на 50 миллисекунд, получим 60. 

Создаем список, где будем хранить последние 60 показателей сторон и заполняем его значением -1 в начале:

int last_60_measurements[60] = {};
for(int i=0; i<60; i++){ last_60_measurements[i]=-1;}

Сдвигаем и добавляем последнее значение (dodecahedron_side):

for (int i=0;i<59;i++){
    last_60_measurements[i]=last_60_measurements[i+1];//0=1,1=2..
  }
last_60_measurements[59] = dodecahedron_side;

Проверяем одинаковые ли все 60 значений из списка:

if(checkIfCubeStable(last_60_measurements)){
  Serial.print("Stable position"); Serial.print('\t');
}

bool checkIfCubeStable(int measurements[]){
  for (int i=0;i<60;i++){
    if(i==59){return true;};
    if(measurements[i] != measurements[i+1]){return false;};
  }
}

Также, проверяем на какой стороне уже было начато отслеживание времени. Это нужно для того, чтобы после начала отслеживания времени таймер заново не перезапускался до тех пор, пока не сменится положение додекаэдра.

int sent_dodecahedron_side = -1;

if(sent_dodecahedron_side!=dodecahedron_side){
  Serial.print("SEND DATA!"); Serial.print('\t');
  sent_dodecahedron_side = dodecahedron_side;
} 

После всех этих проверок, мы можем начать отслеживание времени (запустить трекер).

Кратко про Toggl Track

Если вы заинтересованы в отслеживании времени, вы уже должны были слышать о  Toggl Track. Это невероятно простое решение для отслеживания времени с приложениями для iOS, Android, macOS, Windows, Linux и веб-версией. Но самое главное для нас - у него есть открытая документация по его API.

Toggl track API

Для работы с Toggl track API требуется иметь аккаунт в данной системе, знать e-mail и пароль от него. Все остальное будет рассказано ниже.

В Toggl track используются проекты для более удобной организации своего времени.

Запуск отслеживания времени будет происходить через HTTPS POST запросы.
Для начала, будет удобно заранее проверить все запросы. Мы это можем сделать разными способами: через терминал с Сurl (в macOS и Windows 10 (версия 1803 и выше)) или использовать один из сайтов для запуска Curl команд онлайн.

Первая команда, с помощью которого мы сможем узнать workspaces id, нам нужен для следующей команды:

curl -v -u email:password -X GET
https://api.track.toggl.com/api/v8/me
Получим такой ответ:
{
  "since":1361780172,
  "data": {
    "id":123,
    "api_token":"1971800d4d82861d8f2c1651fea4d212",
    "default_wid":777,
    "email":"john.doe@gmail.com",
    "fullname":"John Doe",
    "jquery_timeofday_format":"h:i A",
    "jquery_date_format":"m/d/Y",
    "timeofday_format":"h:mm A",
    "date_format":"MM/DD/YYYY",
    "store_start_and_stop_time":true,
    "beginning_of_week":1,
    "language":"en_US",
    "duration_format": "improved",
    "image_url":"https://www.toggl.com/images/profile.png",
    "at": "2015-02-17T16:58:53+00:00",
    "created_at": "2014-07-31T07:51:17+00:00",
    "timezone": "Europe/London",
    "retention": 9,
    "new_blog_post":{},
    "projects": [
      {
        "id":90123,
        "wid":777,
        "name":"Our best project",
        "billable":true,
        "active":true,
        "at":"2013-02-12T09:47:57+00:00",
        "color":"5"
      }
    ],
    "tags": [
      {
        "id":238526,
        "wid":777,
        "name":"billed"
      }
    ],
    "tasks": [],
    "workspaces": [
      {
        "id":777,
        "name":"John's WS",
        "at":"2012-11-28T11:56:49+00:00",
        "default_hourly_rate": 0,
        "default_currency": "USD",
        "projects_billable_by_default": true,
        "rounding": 1,
        "rounding_minutes": 0,
        "api_token": "ea897..."
      }
    ],
    "clients": []
}

С помощью данного запроса мы сможем узнать идентификаторы всех проектов (если они есть), которые мы можем использовать для запуска отслеживания времени. Также, хотелось бы упомянуть то, что трекер можно запустить и без проекта.

curl -v -u email:password
-X GET
https://api.track.toggl.com/api/v8/workspaces/WORKSPACE_ID/projects

и наконец запуск отслеживания времени:

curl -v -u email:password 
-H "Content-Type: application/json" 
-d '{"time_entry":{"description":"Description goes here","tags":[],"pid":PROJECT_ID,"created_with":"curl"}}' 
-X POST https://api.track.toggl.com/api/v8/time_entries/start

На Ардуино мы будем запускать HTTPS команды.  На сайте для запуска Curl команд можно нажать на HTTP и увидеть тот код, который нам нужно будет запускать на Ардуино.

Создание записи времени / Запуск трекера

Подключение нужных библиотек:

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>

Здесь у нас умное подключение к Wi-Fi. Есть возможность написать несколько названий сетей и паролей от них. Удобно, если вы, как в моем случае, носите с собой тайм трекер.

String ssid_list[] =      {"Network_Name_1",  "Network_Name_2", "Network_Name_3"}; //Wifi Network Name
String password_list[] =  {"Network_Key_1",   "Network_Key_2",  "Network_Key_3"};  //Wifi Network Key

void setup() {
Wire.begin();
Serial.begin(9600);
//smart wifi connection
WiFi.mode(WIFI_OFF);
delay(1000);
WiFi.mode(WIFI_STA);
String ssid = "";
while (WiFi.status() != WL_CONNECTED) {
  for (byte x = 0; x < (sizeof(ssid_list) / sizeof(ssid_list[0])); x++) {
  ssid = ssid_list[x];
  String password = password_list[x];
    WiFi.begin(ssid, password);
    Serial.println("");
    Serial.print("Connecting to wifi: ");
    Serial.print(ssid);
    int i = 0;
    while (WiFi.status() != WL_CONNECTED &amp;&amp; i&lt;14) {
      //on average, it connects in 7 attempts
      delay(500);
      i++;
      Serial.print(".");
    }

    if(WiFi.status() == WL_CONNECTED){
      break;
    }
  }

  }
  Serial.print("Connected to wifi: ");
  Serial.println(ssid);
}

Для удобства код авторизации положим в отдельную переменную:

const char *authorization = "Basic Y2WG...lGsA==";

В списке trackers будет описание и идентификатор проекта записи времени для запуска на каждую сторону додекаэдра.

const char *trackers[12][2] = {{"Email check",             "172635855"},//0
                              {"Meeting",                  "172635927"},//1
                              {"Programming",              "172635927"},//2
                              {"Reading",                  "163047428"},//3
                              {"Online training courses",  "163047428"},//4
                              {"STOP",                     "\"\""     },//5
                              {"Bug fix",                  "\"\""     },//6
                              {"UI",                       "\"\""     },//7
                              {"Call",                     "\"\""     },//8
                              {"Music",                    "\"\""     },//9
                              {"Exercise",                 "\"\""     },//10
                              {"Relax",                    "\"\""     }};//11

Дополнительные нужные переменные:

const char *host = "api.track.toggl.com";
const int httpsPort = 443;
String datarx; //Received data as string
int httpsClientTimeout = 5000; //in millis

Запуск отслеживания времени. Мы подключаемся к серверу, отправляем HTTPS POST запрос и получаем ответ. Я использую очень много “Serial.print” для того, чтобы мы могли идеально видеть, что было отправлено и какой ответ мы получили.

callhttps_start_time_entry(trackers[sent_dodecahedron_side][0], trackers[sent_dodecahedron_side][1]);

void callhttps_start_time_entry(const char* description, const char* pid){
  WiFiClientSecure httpsClient;
  httpsClient.setInsecure();
  
  httpsClient.setTimeout(httpsClientTimeout);
  delay(1000);
  int retry = 0;
  while ((!httpsClient.connect(host, httpsPort)) && (retry < 15)) {
    delay(100);
    Serial.print(".");
    retry++;
  }
  if (retry == 15) {Serial.println("Connection failed");}
  else {Serial.println("Connected to Server");}
  
  Serial.println("Request_start{");
  String req = String("POST /api/v8/time_entries/start HTTP/1.1\r\n")
        + "Host: api.track.toggl.com\r\n"
        +"Content-Type: application/json\r\n"
        +"Authorization: " + authorization + "\r\n"
        +"Content-Length: " + (77 + strlen(description) + strlen(pid)) + "\r\n\r\n"
        
        +"{\"time_entry\":{\"description\":\"" + description + "\",\"tags\":[],\"pid\":" + pid + ",\"created_with\":\"time_cube\"}}" + "\r\n\r\n";
  
  Serial.println(req);
  httpsClient.print(req);
  Serial.println("}Request_end");
  
  Serial.println("line{");
  while (httpsClient.connected()) {
    String line = httpsClient.readStringUntil('\n');
    Serial.print(line);
    if (line == "\r") {
      break;
    }
  }
  Serial.println("}line");
  
  Serial.println("datarx_start{");
  while (httpsClient.available()) {
    datarx += httpsClient.readStringUntil('\n');
  }
  Serial.println(datarx);
  Serial.println("}datarx_end");
  datarx = "";
}

Остановка трекера осуществляется в два шага. Первый, получение идентификатора записи времени. Второй, остановка трекера, посредством использования его идентификатора:

Код:
String timeEntry_id = callhttps_stop_time_entry_p1();
callhttps_stop_time_entry_p2(timeEntry_id);

String callhttps_stop_time_entry_p1(){
  WiFiClientSecure httpsClient;
  httpsClient.setInsecure(); // this is the magical line that makes everything work
  
  httpsClient.setTimeout(httpsClientTimeout);
  delay(1000);
  int retry = 0;
  while ((!httpsClient.connect(host, httpsPort)) && (retry < 15)) {
    delay(100);
    Serial.print(".");
    retry++;
  }
  if (retry == 15) {Serial.println("Connection failed");}
  else {Serial.println("Connected to Server");}
  
  Serial.println("Request_start{");
  
  //Get the information about running time entry. Especcially we need time entry id.
  String req = String("GET /api/v8/time_entries/current HTTP/1.1\r\n")
        +"Host: api.track.toggl.com\r\n"
        +"Authorization: " + authorization +"\r\n\r\n";
  
  Serial.println(req);
  httpsClient.print(req);
  Serial.println("}Request_end");
  
  Serial.println("line{");
  while (httpsClient.connected()) {
    String line = httpsClient.readStringUntil('\n');
    Serial.print(line);
    if (line == "\r") {
      break;
    }
  }
  Serial.println("}line");
  
  Serial.println("datarx_start{");
  while (httpsClient.available()) {
    datarx += httpsClient.readStringUntil('\n');
  }
  Serial.println(datarx);
  Serial.println("}datarx_end");
  String timeEntry_id = datarx.substring(14,24);
  datarx = "";
  Serial.println("timeEntry_id:"+timeEntry_id);
  
  return timeEntry_id;
}

void callhttps_stop_time_entry_p2(String timeEntry_id){
  WiFiClientSecure httpsClient;
  httpsClient.setInsecure(); // this is the magical line that makes everything work
  
  httpsClient.setTimeout(15000);
  delay(1000);
  int retry = 0;
  while ((!httpsClient.connect(host, httpsPort)) && (retry < 15)) {
    delay(100);
    Serial.print(".");
    retry++;
  }
  if (retry == 15) {Serial.println("Connection failed");}
  else {Serial.println("Connected to Server");}
  
  Serial.println("Request_start{");
  //Here we will stop the running time entry by using its id:
  String req = String("PUT /api/v8/time_entries/"+timeEntry_id+"/stop HTTP/1.1\r\n")
        +"Host: api.track.toggl.com\r\n"
        +"Authorization: " + authorization +"\r\n"
         +"Content-Length: " + "0" + "\r\n\r\n";

  Serial.println(req);
  httpsClient.print(req);
  Serial.println("}Request_end");
  
  Serial.println("line{");
  while (httpsClient.connected()) {
    String line = httpsClient.readStringUntil('\n');
    Serial.print(line);
    if (line == "\r") {
      break;
    }
  }
  Serial.println("}line");
  
  Serial.println("datarx_start{");
  while (httpsClient.available()) {
    datarx += httpsClient.readStringUntil('\n');
  }
  Serial.println(datarx);
  Serial.println("}datarx_end");
  datarx = "";
}

В loop вызов функции запуска и остановки трекера выглядит так:

if(trackers[sent_dodecahedron_side][0] != "STOP"){
  Serial.print("callhttps_start_time_entry"); Serial.print('\t');
  callhttps_start_time_entry(trackers[sent_dodecahedron_side][0], trackers[sent_dodecahedron_side][1]);
}else{
  Serial.println("callhttps_stop_time_entry");
  String timeEntry_id = callhttps_stop_time_entry_p1();
  callhttps_stop_time_entry_p2(timeEntry_id);
}

Заключение

Данное решение подойдет не только для определения на какой стороне лежит додекаэдр, оно также легко может быть использовано для любых других фигур. Нужно только уменьшить или увеличить размеры списков side_values и trackers в зависимости от фигуры. 

https://www.pinterest.ru/pin/538391330439375238/
https://www.pinterest.ru/pin/538391330439375238/

Идеи по развитию проекта:

  • Веб интерфейс, через который можно было бы обновить описания и проекты трекеров.

  • Добавить аккумулятор.

  • Сделать 3д модель кейса, который можно было бы распечатать.

Под конец дня, увидев свои невыполненные задачи, я обычно расстраивался и корил себя за то, что не был достаточно продуктивным и слишком много времени уделял отдыху. После того как начал отслеживать время, посмотрел на что оно было потрачено и понимал, что какая-то определенная задача заняла больше времени, чем требовалось или была назначена встреча, после которой мне нужен был перерыв на чашку чая.

В заключение, я хотел бы поделиться с вами тем, какие изменения внес в мою жизнь учет времени.

С тех пор как я начал отслеживать свое время, я стал более бдительным. Я трачу время с умом и поддерживаю здоровый баланс между работой и личной жизнью. Многим идея отслеживания времени не является привлекательной из-за того, что учет времени — это тоже работа. Додекаэдр для отслеживания времени помог мне легко и просто отслеживать свое время.

Кроме того, отчет легко редактировать или просматривать в Toggl track. Было интересно, но самое главное, что это действительно актуально для ежедневного использования. Закончив проект, я не был уверен, стоит ли писать и публиковать эту статью, но увидев, что потратил на него более 17 часов своего свободного времени (и более 50 на тайм трекер куб) то решил, что эта работа будет полезна кому-то, хотя бы для экономии данного времени. Этот проект поможет вам грамотно распределить и отслеживать свое время.

Спасибо за ваше уделенное время на прочтение данной статьи, и я надеюсь, что вы тратите время только на важные для вас вещи. В будущем я планирую внедрить новые функции, которые еще больше облегчат процесс учета рабочего времени.

Комментарии (9)


  1. NAI
    27.02.2022 20:19
    +3

    А не проще ли взять nfc-ридер и на каждую грань наклеить по карте?

    В качестве ридера может выступать как телефон, так и отдельное устройство. Сразу решаются проблемы с автономностью, "дребезгом" и пр.


    1. ChingizNazar Автор
      27.02.2022 20:33

      Интересная мысль. Приму во внимание. Спасибо.


    1. vmkazakoff
      27.02.2022 23:40
      +2

      Когда мне нужно было понять на что и сколько уходит времени я взял 10 билетиков от метро и какое-то бесплатное приложение на телефоне, которое добавляло строку в гуглдок мне. Но разве это спортивно?) Да и на статью не потянуло бы, а тут и интересно, и поучительно!))


  1. Samid777
    27.02.2022 21:21
    +1

    А как справится код с проблемой, если додекаэдр будет лежать на не ровной поверхности, либо находится на движущемся объекте?


    1. ChingizNazar Автор
      28.02.2022 06:19

      На счет неровной поверхности, если в рамках значения epsilon (погрешность используемая при сверке) то, это будет приемлемо и все будет работать.

      Если речь идет о поезде, где ускорение пропадает через короткое время и объект движется со стабильной скоростью, то проблем не возникнет.


  1. Spaceoddity
    27.02.2022 22:56
    +2

    М. Гарднер "Крестики-нолики", гл.10 "Складывание многогранников" - рассказывается как складывать 5 правильных многогранников из одинаковых полосок бумаги. При некотором "скилле" можно обойтись и без клея.


  1. laminar
    28.02.2022 09:46

    NIH, очередная самостоятельная реализация, хотя возможно кому-нибудь действительно будет интересно.


  1. CoolCmd
    28.02.2022 10:58

    Сдвигаем и добавляем последнее значение (dodecahedron_side):

    лучше использовать кольцевой буфер


  1. srg27y
    01.03.2022 11:29

    когда прочітал заголовок, то сразу подумал что в ітоге будет кубік на котором отщитываеца время, типо такого https://habr.com/ru/company/ruvds/blog/595273/