Недавно я более внимательно отслеживал свое время на проектах в течение дня. Полезно видеть, какие проекты занимают больше времени, чем другие, и помогает мне определить, какие дни я наиболее продуктивен (а что отвлекает меня!). Мой сервис выбора для этого Toggl . Это просто, чисто и синхронизирует между устройствами. Лучше всего — у него есть API, к которому вы можете подключать свои собственные приложения и устройства. Я решил установить кнопку, подключенную к моему Particle Photon, которая запускает и останавливает мой таймер Toggl для меня. Я использовал простой сервер Node для управления связью между моим устройством Particle и Toggl.
Нажав на физическую кнопку, вы чувствуете, что это немного больше возможностей, чем нажатие на программную кнопку, и мне не нужно вынимать свой смартфон или нажимать на мой Mac, чтобы найти таймер!
Что вам нужно
- Particle Core или Photon — я буду использовать Particle Photon, но оба должны быть совместимы с демо
- Физическая кнопка какой-то
- Макет, резисторы и перемычки. Если вы новичок в микроконтроллерах, у SparkFun есть отличный набор Inventors Kit для Photon.
- Аккаунт Toggl — если у вас его нет, зайдите на сайт Toggl, чтобы зарегистрироваться!
- Знание того, как получить код на ваше устройство Particle — если вы новичок в этом, я опубликовал статью SitePoint несколько недель назад о подключении к Photon . Частица ядра похожа.
- Основное понимание работы сервера Node и использования npm — Питер Диркс из SitePoint написал довольно подробное руководство по запуску с npm .
Примечание: частицы также продают большие физические кнопки . Скорее всего, вы могли бы адаптировать эту концепцию к большой кнопке для большого удовольствия, у меня просто нет такой… пока.
Поиск ключей API
Чтобы получить ключ API Toggl, посетите страницу «Мой профиль» Toggl . Если вы прокрутите вниз до нижней части этой страницы, вы найдете уникальный токен API, который вы можете использовать следующим образом:
Скопируйте этот токен в безопасное место. Вам это понадобится!
Вы также можете сбросить его, используя крошечную ссылку «Сброс» с правой стороны (полезно в такие моменты, как тогда, когда я открыл всем свой ключ API).
Если со времени вашего последнего сеанса сборки Particle прошло некоторое время, и вам нужно освежить в поиске ключа API для Particle, перейдите в онлайн-редактор Particle Build и щелкните значок шестеренки в самом низу, чтобы перейти на страницу настроек. Оттуда вы увидите экран, на котором показан ваш токен доступа.
Скопируйте это тоже.
Наш Частичный Эскиз
Наш эскиз с макетом макета, устройства Particle (на этом снимке показано ядро, но будет работать и этот, и фотон), светодиод и кнопка выглядят так:
Скачать код
Весь код для этого примера можно найти на GitHub .
Наш Кодекс Частиц
Наш код частиц будет отслеживать, нажата кнопка или нет, и хотим ли мы, чтобы наш светодиод горел или нет. Все остальные функции будут выполняться нашим Node-сервером.
Код частиц выглядит так:
int ledPin = D0; int buttonPin = D5; bool ready = true; int last; void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT); last = millis(); digitalWrite(ledPin, LOW); Spark.function("ledTrigger", ledTrigger); } void loop() { if (millis() - last > 200) { if (digitalRead(buttonPin)) { if (ready) { ready = false; Spark.publish("buttonPressed"); last = millis(); } } else { ready = true; // button ready to be pressed again } } } int ledTrigger(String value) { if (value == "ON") { digitalWrite(ledPin, HIGH); } else { digitalWrite(ledPin, LOW); } return 0; }
Я объясню, что означает каждый бит этого кода. Для начала мы определим наши два компонента и контакты, к которым они прикреплены. Наша кнопка подключена к D5, а наш светодиод подключен к контакту D0.
int ledPin = D0; int buttonPin = D5;
Следующие две переменные предназначены для отслеживания времени в нашем цикле. ready
отслеживает, ready
ли наша кнопка к повторному нажатию. Мы хотим убедиться, что существует период между первым щелчком по нему и повторным нажатием на него. last
— это переменная, которая помогает отслеживать этот период времени, она отслеживает последний раз, когда цикл был запущен. Это может иметь больше смысла, когда вы скоро увидите это в действии.
bool ready = true; int last;
В нашей функции setup()
мы начинаем с установки режима вывода для вывода нашего светодиода и установки его для ввода для нашей кнопки.
void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT); // more code explained next! }
После этого мы используем last
переменную, которую я объяснил ранее. Мы изначально установили его в millis()
. Функция millis()
возвращает количество миллисекунд с момента запуска этой программы на нашем устройстве Particle. Таким образом, мы знаем, когда наша функция setup()
начала работать.
last = millis();
Первоначально мы установили для нашего светодиода значение LOW
, что отключает его.
digitalWrite(ledPin, LOW);
Наконец, в нашей функции setup()
мы определяем публичную функцию, к которой наш код Node сможет получить доступ. Эта функция является нашей ledTrigger()
, так как мы хотим, чтобы наш код узла мог включать и выключать светодиод.
Spark.function("ledTrigger", ledTrigger);
В нашей функции loop()
мы запускаем код каждые 200 миллисекунд. Мы работаем с этим, читая текущее значение millis()
и беря его из нашего last
значения. Существуют и другие альтернативные методы проверки нажатия кнопок, но этот метод был показан командой Particle, а также единственный, который мне подходит!
void loop() { if (millis() - last > 200) { // Our checks for button presses } }
Каждые 200 миллисекунд мы проверяем, есть ли сигнал от нашей кнопки. Если мы видим сигнал от нашей кнопки и наша переменная ready
имеет значение true, то мы публикуем событие "buttonPressed"
чтобы наш сервер Node мог его услышать. Мы также установили для false
значение false
чтобы кнопка не millis()
несколько раз и last
до текущего millis()
(так что теперь мы можем подождать еще 200 миллисекунд, прежде чем запускать снова).
if (digitalRead(buttonPin)) { if (ready) { ready = false; Spark.publish("buttonPressed"); last = millis(); } }
Если по истечении 200 миллисекунд сигнал от нашей кнопки не поступает, мы устанавливаем ready
к true
, поскольку наша кнопка была отпущена, и, таким образом, мы можем начать наблюдение за следующим 200-миллисекундным интервалом при повторном нажатии.
else { ready = true; // button ready to be pressed again }
Последний ledTrigger()
кода — наша публичная ledTrigger()
мы упоминали ранее. Это то, что наш код узла будет использовать для включения и выключения нашего светодиода. Каждому вызову функции также передается строка "ON"
или "OFF"
. Если мы видим, что в функцию передано "ON"
, мы устанавливаем наш светодиод на HIGH
. В противном случае мы устанавливаем его на LOW
выключая.
int ledTrigger(String value) { if (value == "ON") { digitalWrite(ledPin, HIGH); } else { digitalWrite(ledPin, LOW); } return 0; }
Наш Узловой Сервер
В рамках нашего Node-сервера мы заботимся обо всей интеграции между нашей учетной записью Toggl и нашим устройством Particle. Наш Node-сервер выглядит так:
var spark = require("spark"), TogglClient = require("toggl-api"), toggl = new TogglClient({apiToken: "YOURAPITOKEN"}), _ = require("underscore"), currentParticle; initParticle(); function initParticle() { spark.on("login", function(err, body) { console.log("Particle Core login successful: ", body); var deviceList = spark.listDevices(); deviceList.then(function(devices) { currentParticle = _.find(devices, function(device) { return device.name == "Timon"; }); console.log("Timon was found: ", currentParticle); currentParticle.onEvent("buttonPressed", function() { console.log("Button was pressed!"); toggl.getCurrentTimeEntry(function(err, currentEntry) { if (currentEntry) { console.log(currentEntry.description + " is running"); toggl.stopTimeEntry(currentEntry.id, function(err, stoppedEntry) { console.log(stoppedEntry.description + " was stopped"); currentParticle.callFunction("ledTrigger", "OFF", function(result) { console.log("LED should be off"); }); }); } else { var currentDate = new Date(), yesterday = new Date(); yesterday.setDate(currentDate.getDate() - 1); toggl.getTimeEntries(yesterday.toISOString(), currentDate.toISOString(), function(err, data) { if (!err) { var lastEntry = data[data.length - 1]; console.log(lastEntry); toggl.startTimeEntry({ description: lastEntry.description, pid: lastEntry.pid, wid: lastEntry.wid }, function(err, timeEntry) { console.log("Entry started"); currentParticle.callFunction("ledTrigger", "ON", function(result) { console.log("LED should be on"); }); }); } }); } }); }); }); }); spark.login({ accessToken: "YOURACCESSTOKEN" }, function(err, body) { if (!err) console.log("API login complete!"); }); }
Все начинается с того, что нам требуются библиотеки spark
, toggl-api
и underscore
npm. spark
— это библиотека, которую мы используем для доступа к Particle Core (раньше она называлась «Spark Core»), toggl-api
— это toggl-api
и простая библиотека, которая позволяет нам получить доступ к нашему API Toggl, нам просто нужно передать наш токен API как вы видите ниже. underscore
будет использоваться для упрощения просмотра данных, возвращаемых из API Toggl. currentParticle
— это место, где мы будем хранить детали ядра частиц, которые мы используем.
var spark = require("spark"), TogglClient = require("toggl-api"), toggl = new TogglClient({apiToken: "YOURAPITOKEN"}), _ = require("underscore"), currentParticle;
Все происходит внутри функции, которую мы назвали initParticle()
. Сначала мы заходим в сервис Particle с помощью нашего токена доступа, а затем используем наш доступ в spark.on("login")
:
function initParticle() { spark.on("login", function(err, body) { console.log("Particle device login successful: ", body); // We'll be accessing our Particle here }); spark.login({ accessToken: "YOURACCESSTOKEN" }, function(err, body) { if (!err) console.log("API login complete!"); }); }
В нашем состоянии входа в систему мы затем используем spark.listDevices()
чтобы получить список всех устройств, подключенных к этой учетной записи. Как только это будет возвращено, мы используем библиотеку underscore
для поиска результатов и нахождения конкретного устройства Particle, к которому мы прикрепили нашу кнопку. Я назвал свое устройство частиц «Timon», поэтому я ищу это имя ниже. Как только мы находим устройство, мы присоединяем его к currentParticle
.
var deviceList = spark.listDevices(); deviceList.then(function(devices) { currentParticle = _.find(devices, function(device) { return device.name == "Timon"; }); console.log("Timon was found: ", currentParticle);
Когда у нас есть устройство Particle, мы наблюдаем событие "buttonPressed"
которое мы устанавливаем, чтобы наше устройство Particle "buttonPressed"
всякий раз, когда оно обнаруживает, что наша кнопка нажата. Если мы видим это событие, мы отвечаем:
currentParticle.onEvent("buttonPressed", function() { console.log("Button was pressed!"); // We'll talk to Toggl here next! });
Мы достигли точки в нашем коде, когда нам нужно поговорить с Toggl, чтобы сообщить им, что мы хотим что-то сделать с помощью нашего отслеживания времени. Сначала мы хотим узнать, отслеживается ли уже проект. Мы можем сделать это с помощью toggl.getCurrentTimeEntry()
. Если есть текущая запись времени, она возвращается в переменной currentEntry
. Мы проверяем эту переменную и, если запись уже запущена, мы хотим, чтобы нажатие нашей кнопки остановило этот таймер. Мы делаем это с помощью функции toggl.stopTimeEntry()
. Когда эта функция будет успешной, мы сообщаем нашему устройству Particle выключить наш светодиод с помощью вызова currentParticle.callFunction("ledTrigger")
вы можете увидеть ниже.
toggl.getCurrentTimeEntry(function(err, currentEntry) { if (currentEntry) { console.log(currentEntry.description + " is running"); toggl.stopTimeEntry(currentEntry.id, function(err, stoppedEntry) { console.log(stoppedEntry.description + " was stopped"); currentParticle.callFunction("ledTrigger", "OFF", function(result) { console.log("LED should be off"); }); }); } // We will have an else statement next! }
Если текущее событие не запущено, мы вместо этого хотим найти последнее запущенное событие и возобновить его с помощью нажатия кнопки (поскольку мы не можем реально определить событие с помощью нажатия кнопки, мы предполагаем, что перезапускаем наше последнее событие, которое мы определили в настольных или мобильных приложениях).
Чтобы найти прошлые события, мы используем toggl.getTimeEntries()
. Эта функция принимает две переменные: дату начала и дату окончания, в которой мы хотим искать события. Мы действительно хотим только самого последнего события, поэтому мы настроили его на просмотр прошедшего дня. Для этого мы установили две переменные: currentDate
которая является типичной new Date()
в JavaScript, и yesterday
которая является нашей currentDate
минус одна. Они должны быть в формате даты и времени ISO 8601, чтобы они могли работать с Toggl, поэтому мы конвертируем их оба с помощью toISOString()
.
else { var currentDate = new Date(), yesterday = new Date(); yesterday.setDate(currentDate.getDate() - 1); toggl.getTimeEntries(yesterday.toISOString(), currentDate.toISOString(), function(err, data) { if (!err) { // We have a time entry to begin! } }); }
Если у нас нет возвращенных ошибок, у нас будет массив с именем data
который содержит записи времени Toggl за последний день. Чтобы получить последний зарегистрированный элемент, мы получаем последний элемент в массиве через data[data.length - 1]
и присваиваем его lastEntry
.
var lastEntry = data[data.length - 1]; console.log(lastEntry);
Теперь мы знаем нашу последнюю запись и можем начать новую запись того же проекта и задачи. Чтобы начать новую запись времени, мы запускаем toggl.startTimeEntry()
. Мы передаем description
(имя вашей записи в Toggl), pid
(идентификатор проекта) и wid
(идентификатор рабочей области) нашего lastEntry
, чтобы он запускал ту же задачу и назначал ее для того же проекта. Когда он успешно запускает наше отслеживание времени, мы снова вызываем нашу функцию "ledTrigger"
, на этот раз включив наш светодиод, чтобы показать, что мы отслеживаем проект через устройство Particle.
toggl.startTimeEntry({ description: lastEntry.description, pid: lastEntry.pid, wid: lastEntry.wid }, function(err, timeEntry) { console.log("Entry started"); currentParticle.callFunction("ledTrigger", "ON", function(result) { console.log("LED should be on"); }); });
В бою
Поместите этот код на ваше устройство Particle и запустите обычные npm install
и node index.js
чтобы запустить ваш Node-сервер.
Теперь вы сможете нажать кнопку, чтобы начать и остановить отслеживание Toggl! Одна вещь, которую я заметил, заключается в том, что мое приложение Mac Toggl не получает данные о времени сразу, если они изначально не запускались из приложения. Тем не менее, мое приложение Android Toggl синхронизируется намного быстрее и почти в режиме реального времени реагирует на нажатия кнопок, показывая новую запись времени при запуске и остановке.
Вот мое видео в действии:
Для тех, кто не хочет смотреть видео, вот картинка за мгновение до того, как нажата кнопка, мое отслеживание Toggl готово и ждет:
Как только он регистрирует щелчок, Toggl начинает отслеживать последнюю задачу и проект, над которым я ранее работал. Светодиод также включается, чтобы показать, что устройство Particle успешно начало отслеживать что-то:
Нажмите кнопку еще раз, когда вы будете готовы остановить отслеживание проекта!
Вывод
Обладая этими знаниями, вы можете создавать и создавать свои собственные приложения Toggl, связанные с IoT. Подключите его абсолютно ко всему, чтобы отслеживать свою продуктивность, использовать свое воображение и видеть, какие блестящие идеи приходят в голову! С этой демонстрацией можно сделать гораздо больше. Одна область, которая определенно требует дополнительной работы — это синхронизация светодиода с Toggl, когда отслеживание начинается на других устройствах.
Если вы делаете действительно интересное и уникальное приложение для отслеживания времени Toggl на основе этого кода, пожалуйста, поделитесь им в комментариях или свяжитесь со мной в Twitter ( @thatpatrickguy ). Я хотел бы видеть это!
Если вы ищете больше ссылок и примеров проектов, которые помогут вам в разработке Particle, у меня есть набор ссылок, которые могут помочь! Отправляйтесь в Dev Diner и ознакомьтесь с моим Руководством для разработчиков частиц Dev Diner .