Тайм-менеджмент для кинестетиков

    Время — самый ценный ресурс, который у нас есть. Чтобы использовать его максимально продуктивно, существуют всякого рода техники тайм-менеджмента. Если говорить о тайм-менеджменте в масштабах рабочего дня, то одна из самых популярных техник называется Pomodoro. Но эта статья не про GTD, а про код (и немного про железо ^^).


    Так вот, для техники Pomodoro есть инструмент Tomighty и у него открытый исходный код на C#, что побуждает к модификации этого самого кода с целью добавления новых возможностей и интеграции со всякими штуками.


    Сегодня мы будем интегрировать клиент Tomighty с устройстовм "Большая Красная Кнопка". Нам для этого понадобится:


    • Большая Красная Кнопка (со светодиодом). У меня оказалась не очень большая, но очень красная.
    • ESP8266 — один из наиболее оптимальных микроконтроллеров по соотношению удобство/цена. Это даже не микроконтроллер вовсе, но как микроконтроллер он абсолютно прекрасен!
    • MicroPython. Я не знаю языка удобнее чем Python, а вы? Разве что Ruby, но его вроде бы не портировали на ESP8266.
    • Протокол MQTT для связи между компом и девайсом.
    • Visual Studio.
    • Опционально, расширение CodeRush for Roslyn. Оно сильно упрощает работу с как со своим, так и с незнакомым кодом.


    Зачем? Чтобы получить опыт работы с чужим кодом. В связи с грядущим Hacktoberfest, этот скилл будет крайне актуален.


    Welcome!


    Большая Красная Кнопка


    С аппаратной стороны нет ничего сверхъестественного, посему подробно описывать каждый шаг не буду, всё должно работать, а код сам по себе довольно понятный.


    1. Берём ESP8266. У меня ESP-12-Q, но подойдет любой модуль с памятью 1МБ или более.
    2. Прошиваем MicroPython по инструкции.
    3. Качаем официальную реализацию MQTT и сохраняем её как mqtt.py. На ESP через WebREPL.
    4. Находим бесплатный MQTT-брокер в Интернетах, или поднимаем локальный Mosquitto. Я использовал CloudMQTT, но это вовсе не единственный вариант.
    5. Изучаем и улучшаем код для ESP8266, а потом заливаем на плату под имененм main.py.
    6. Подключаем светящуюся кнопочку из ближайшего магазина электроники.
    7. Reboot.
    8. Debug via Serial Port.

    Доработка клиента Tomighty


    Начнём с клонирования репозитория. Можно официальный, можно форк (где в соответствующем брэнче всё уже сделано). Открываем солюшн в студии.


    Для того, чтобы собрать проект Tomighty.Windows, необходимо устанвоить в него пакет UWPDesktop через NuGet. Это совершенно неочевидное действие, до которого мы с коллегами относительно долго пытались додуматься и чуть менее долго догуглиться. Возможно это тривиально для тех кто имел дело со старомодными WinForms приложениями, зовущими новомодный UWP API, но для тех кто таким не занимался — не очень.


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


    Задачи такие:


    1. Добавить в меню иконки трэя пункт "Connect to the Red Button"
    2. Запускать уведомление (их Toast с иконкой) при успешном соединении с MQTT-брокером или неудачной попытке
    3. Запускать период Pomodoro при получении сообщения по MQTT
    4. Отправлять сообщение по MQTT при фактическом старте периода Pomodoro и другое сообщение при старте прерывов

    Для начала выясним как запустить период Pomodoro, скорее всего этот путь приведет нас к основным архитектурным элементам приложения быстрее всего. Пробный запуск показал, что, похоже, основным источником управления тут является икнока в трэе, так что попробуем найти точку входа где-нибудь в папке Tomighty.Windows\Tray\. Действительно, в интерфейсе ITrayMenu есть похожий на правду метод, посмотрим где он используется.



    Нашёлся очень мясистый файлик TrayMenuController.cs, а в нём и нужный метод


    private void OnStartPomodoroClick(object sender, EventArgs e) => StartTimer(IntervalType.Pomodoro);
    
    // ...
    
    private void StartTimer(IntervalType intervalType) {
        Task.Run(() => pomodoroEngine.StartTimer(intervalType));
    }

    Окей, значит за основные операции типа запуска периодов отвечает объект pomodoroEngine. Он нам понадобится.


    Название (да и содержимое) этого класса TrayMenuController как бы намекают на то что он является одним из интерфейсов между программой и человеком. Скорее всего нам надо создать что-то похожее, чтобы добавить поддержку собственного интерфейса в виде красной кнопки. Воспользуемся той же Jump to менюшкой, чтобы найти где создается объект этого класса.



    Отлично, мы нашли точку входа. Она выглядит как-то так:


    internal class TomightyApplication : ApplicationContext {
        public TomightyApplication() {
            var eventHub = new SynchronousEventHub();
            var timer = new Tomighty.Timer(eventHub);
            var userPreferences = new UserPreferences();
            var pomodoroEngine = new PomodoroEngine(timer, userPreferences, eventHub);
    
            var trayMenu = new TrayMenu() as ITrayMenu;
            var trayIcon = CreateTrayIcon(trayMenu);
            var timerWindowPresenter = new TimerWindowPresenter(pomodoroEngine, timer, eventHub);
    
            new TrayIconController(trayIcon, timerWindowPresenter, eventHub);
            new TrayMenuController(trayMenu, this, pomodoroEngine, eventHub);
            // ...
    
            new StartupEvents(eventHub);
        }
        // ...
    }

    Время совершить небольшую интервенцию: создадим еще один объект несуществующего класса, а потом с помощью фичи Declare Class добавим сам класс.



    Я сразу передал еще и eventHub, потому что заметил что в TrayMenuController через него можно подписаться на ивенты старта и окончания таймера. Пригодится.


    Сразу можно сделать два филда из автоматически сгенерированных параметров конструктора фичей Declare Field with Initializer:



    Что ж, теперь мы можем подписываться на ивенты и управлять таймерами. Попробуем добавить пункт меню в трэй, который будет вызывать метод RedButtonController.Connect().


    Довольно быстро пришло осознание, что лучше всё-таки сохранить инстанс нашего контроллера и передать его в TrayMenuController, чтобы тот мог спокойно напрямую позвать Connect() без всяких ивентов и усложнений.


    var redButton = new RedButtonController(eventHub);
    // ...
    new TrayMenuController(trayMenu, this, pomodoroEngine, eventHub, redButton);

    Чтобы пункт меню появился в списке, надо создать TrayMenu.redButtonConnectItem и везде его прокинуть по аналогии с теми что рядом. В поиске таких мест хорошо поможет Tab to Next Reference: Можно просто поставить курсор на любой референс, нажать Tab и перейти к следующему, при этом все референсы в поле зрения подсвечиваются.



    Никаких подводных камней замечено не было, всё заработало довольно быстро. redButtonConnectItem вызывает RedButtonController.Connect() через хэндлер TrayMenuController.OnRedButtonConnect()



    (таскбар слева экономит вертикальное пространство и круче чем таскбар снизу)


    А теперь, попробуем вызвать Toast (это такие новомодные нотификации). Когда я впервые запустил приложение, один такой прилетал с предложением настроиться после первого запуска. Попробуем его отыскать. Думаю, надо начать со строчки new StartupEvents(eventHub) в конце конструктора TomightyApplication. Пара нажатий на F12 (перейти к декларации) приводят в файл Tomighty.Windows\Events.cs с двумя пустыми ивентами:


    namespace Tomighty.Windows.Events {
        public class FirstRun { }
        public class AppUpdated { }
    }

    Что ж, ни один из этих нам не подходит, при чём даже формат пустого ивента не совсем подходит, хотелось бы передавать туда степень успешности попытки подключиться. Создаём новый ивент, объявляем в нём филд и используем Smart Constructor для добавления конструктора с автоматической инициализацией филда.



    Далее, пришлось пройтись по всем местам, где что-то происходило с ивентом FirstRun и добавить подобные действия для нашего ивента RedButtonConnectionChanged.



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



    Соединение с MQTT


    Окей, у нас есть pomodoroEngine, eventHub, пункт меню и нотификации, вроде бы всё что нужно, можно соединяться с MQTT и пробовать общаться с кнопкой. Для MQTT будем использовать самый гуглящийся клиент M2Mqtt:


    PM> Install-Package M2Mqtt

    У меня уже был простенький класс, для M2Mqtt, так что я его просто подключил и наслаждался ну-совсем-простым API:


    public void Connect() {
        mqtt = new MQTTClient("m10.cloudmqtt.com", 13633);
        mqtt.Connect("%LOGIN%", "%PASSWORD%");
        if (!mqtt.client.IsConnected) {
            eventHub.Publish(new RedButtonConnectionChanged(false));
            return;
        }
        eventHub.Publish(new RedButtonConnectionChanged(true));
    
        mqtt.client.MqttMsgPublishReceived += onMsgReceived;
        mqtt.Subscribe("esp");
    }

    Добавить хэндлер можно с помощью Declare Method:



    Осталось подписаться на TimerStarted и TimerStopped, и можно писать логику. А логика у меня в первом приближении получилась такая:



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



    UPD:



    Питание от LM1117.

    • +20
    • 10,4k
    • 7
    Developer Soft
    75,95
    Компания
    Поделиться публикацией

    Комментарии 7

      0
      Спасибо за статью.
      … Для того чтобы собрать проект Tomighty.Windows, необходимо устанвоить в него пакет UWPDesktop через NuGet. Это совершенно не очевидное действие...

      Можно просто в Visual Studio меню «Сборка» -> «Собрать решение». Она сама восстановит зависимости и подгрузит пакеты через NuGet.
        0

        Не заработало (( там этот пакет не числится явно в зависимостях почему-то, Студия просто ругается что нет трёх неймспейсов типа Windows.UI

          0
          Видимо у нас с вами разные настройки Visual Studio.

          Чтобы включить восстановление пакетов NuGet во время выполнения сборки, в Visual Studio откройте диалоговое окно «Параметры», щелкните узел «Диспетчер пакетов» и поставьте флажок «Разрешить NuGet скачивать отсутствующие пакеты при выполнении сборки».
            0

            Я не трогал эту настройку, она по дефолту. но предполагаю что в исходном проекте не было никакого упомянания UWPDesktop и студия совсем не знала что делать с непонятными референсами. Или вы попробывали и у вас заработало?)

              0
              Попробовал и у меня заработало без UwpDesktop)
                0

                Хм, ну у меня не заработало. Теперь мы знаем больше, спасибо )

        +1

        Upadte! Запилил (в прямом смысле) корпус. Можно позалипать на гифку

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое