Попытка использовать современный C++ и паттерны проектирования для программирования микроконтроллеров

Всем привет!

Проблема использования С++ в микроконтроллерах терзала меня довольно долгое время. Дело было в том, что я искренне не понимал, как этот объектно ориентированный язык может быть применим к встраиваем системам. Я имею ввиду, как выделять классы и на базе чего составлять объекты, то есть как именно применять этот язык правильно. Спустя некоторое время и прочтения n-ого количества литературы, я пришёл к кое каким результатам, о чем и хочу поведать в этой статье. Имеют ли какую либо ценность эти результаты или нет — остается на суд читателя. Мне будет очень интересно почитать критику к моему подходу, чтобы наконец ответить себе на вопрос: «Как же правильно использовать C++ при программировании микроконтроллеров?».

Предупреждаю, в статье будет много исходного кода.

В этой статье, я, на примере использования USART в МК stm32 для связи с esp8266 постараюсь изложить свой подход и его основные преимущества. Начнем с того, что главное преимущество использование C++ для меня — это возможность сделать аппаратную развязку, т.е. сделать использование модулей верхнего уровня независимым от аппаратной платформы. Это будет вытекать в то, что система станет легко модифицирована при каких либо изменениях. Для этого я выделил три уровня абстракции системы:

  1. HW_USART — аппаратный уровень, зависит от платформы
  2. MW_USART — средний уровень, служит для развязки первого и третьего уровней
  3. APP_ESP8266 — уровень приложения, ничего не знает о МК

HW_USART

Самый примитивный уровень. Я использовал камень stm32f411, USART №2, также выполнил поддержку DMA. Интерфейс реализован в виде всего трех функций: инициализировать, отправить, получить.

Функция инициализации выглядит следующим образом:

bool usart2_init(uint32_t baud_rate) {   bool res = false;      /*-------------GPIOA Enable, PA2-TX/PA3-RX ------------*/   BIT_BAND_PER(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN) = true;      /*----------GPIOA set-------------*/   GPIOA->MODER |= (GPIO_MODER_MODER2_1 | GPIO_MODER_MODER3_1);   GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR2 | GPIO_OSPEEDER_OSPEEDR3);   constexpr uint32_t USART_AF_TX = (7 << 8);   constexpr uint32_t USART_AF_RX = (7 << 12);   GPIOA->AFR[0] |= (USART_AF_TX | USART_AF_RX);              /*!---------------USART2 Enable------------>!*/   BIT_BAND_PER(RCC->APB1ENR, RCC_APB1ENR_USART2EN) = true;      /*-------------USART CONFIG------------*/   USART2->CR3 |= (USART_CR3_DMAT | USART_CR3_DMAR);   USART2->CR1 |= (USART_CR1_TE | USART_CR1_RE | USART_CR1_UE);   USART2->BRR = (24000000UL + (baud_rate >> 1))/baud_rate;      //Current clocking for APB1      /*-------------DMA for USART Enable------------*/      BIT_BAND_PER(RCC->AHB1ENR, RCC_AHB1ENR_DMA1EN) = true;      /*-----------------Transmit DMA--------------------*/   DMA1_Stream6->PAR = reinterpret_cast<uint32_t>(&(USART2->DR));   DMA1_Stream6->M0AR = reinterpret_cast<uint32_t>(&(usart2_buf.tx));   DMA1_Stream6->CR = (DMA_SxCR_CHSEL_2| DMA_SxCR_MBURST_0 | DMA_SxCR_PL | DMA_SxCR_MINC | DMA_SxCR_DIR_0);         /*-----------------Receive DMA--------------------*/   DMA1_Stream5->PAR = reinterpret_cast<uint32_t>(&(USART2->DR));   DMA1_Stream5->M0AR = reinterpret_cast<uint32_t>(&(usart2_buf.rx));   DMA1_Stream5->CR = (DMA_SxCR_CHSEL_2 | DMA_SxCR_MBURST_0 | DMA_SxCR_PL | DMA_SxCR_MINC);      DMA1_Stream5->NDTR = MAX_UINT16_T;   BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = true;   return res; } 

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

Тогда функция отправки выглядит следующим образом:

bool usart2_write(const uint8_t* buf, uint16_t len) {    bool res = false;    static bool first_attempt = true;        /*!<-----Copy data to DMA USART TX buffer----->!*/    memcpy(usart2_buf.tx, buf, len);        if(!first_attempt)    {      /*!<-----Checking copmletion of previous transfer------->!*/      while(!(DMA1->HISR & DMA_HISR_TCIF6)) continue;      BIT_BAND_PER(DMA1->HIFCR, DMA_HIFCR_CTCIF6) = true;    }        first_attempt = false;        /*!<------Sending data to DMA------->!*/    BIT_BAND_PER(DMA1_Stream6->CR, DMA_SxCR_EN) = false;    DMA1_Stream6->NDTR = len;    BIT_BAND_PER(DMA1_Stream6->CR, DMA_SxCR_EN) = true;        return res; } 

В функции есть костыль, в виде переменной first_attempt, которая помогает определить самая ли первая это отправка по DMA или нет. Зачем это нужно? Дело в том, что проверку о том, успешна ли предыдущая отправка в DMA или нет я сделал ДО отправки, а не ПОСЛЕ. Сделал я так, чтобы после отправки данных не тупо ждать её завершения, а выполнять полезный код в это время.

Тогда функция приема выглядит следующим образом:

uint16_t usart2_read(uint8_t* buf) {    uint16_t len = 0;    constexpr uint16_t BYTES_MAX = MAX_UINT16_T; //MAX Bytes in DMA buffer        /*!<---------Waiting until line become IDLE----------->!*/    if(!(USART2->SR & USART_SR_IDLE)) return len;    /*!<--------Clean the IDLE status bit------->!*/    USART2->DR;        /*!<------Refresh the receive DMA buffer------->!*/    BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = false;    len = BYTES_MAX - (DMA1_Stream5->NDTR);    memcpy(buf, usart2_buf.rx, len);    DMA1_Stream5->NDTR = BYTES_MAX;    BIT_BAND_PER(DMA1->HIFCR, DMA_HIFCR_CTCIF5) = true;    BIT_BAND_PER(DMA1_Stream5->CR, DMA_SxCR_EN) = true;        return len; } 

Особенностью этой функции является то, что мне заранее не известно сколько байт я должен получить. Для индикации полученных данных я проверяю флаг IDLE, затем, если состояние IDLE зафиксировано, чищу флаг и читаю данные из буфера. Если же состояние IDLE не зафиксировано, то функция просто возвращает нуль, то есть отсутствие данных.

На этом предлагаю закончить с низким уровнем и перейти непосредственно к C++ и паттернам.

MW_USART

Здесь я реализовал базовый абстрактный класс USART и применил паттерн «прототип» для создания наследников (конкретных классов USART1 и USART2). Я не буду описывать реализацию паттерна прототип, так как его можно найти по первой ссылке в гугле, а сразу приведу исходный код, и пояснения приведу ниже.

#pragma once #include <stdint.h> #include <vector> #include <map>  /*!<========Enumeration of USART=======>!*/ enum class USART_NUMBER : uint8_t {   _1,   _2 };   class USART; //declaration of basic USART class  using usart_registry = std::map<USART_NUMBER, USART*>;    /*!<=========Registry of prototypes=========>!*/ extern usart_registry _instance; //Global variable - IAR Crutch #pragma inline=forced  static usart_registry& get_registry(void) { return _instance; }  /*!<=======Should be rewritten as========>!*/ /* static usart_registry& get_registry(void)  {    usart_registry _instance;   return _instance;  } */  /*!<=========Basic USART classes==========>!*/ class USART { private: protected:      static void add_prototype(USART_NUMBER num, USART* prot)   {     usart_registry& r = get_registry();     r[num] = prot;   }      static void remove_prototype(USART_NUMBER num)   {     usart_registry& r = get_registry();     r.erase(r.find(num));   } public:   static USART* create_USART(USART_NUMBER num)   {     usart_registry& r = get_registry();     if(r.find(num) != r.end())     {       return r[num]->clone();     }     return nullptr;   }   virtual USART* clone(void) const = 0;   virtual ~USART(){}      virtual bool init(uint32_t baudrate) const = 0;   virtual bool send(const uint8_t* buf, uint16_t len) const = 0;   virtual uint16_t receive(uint8_t* buf) const = 0; };  /*!<=======Specific class USART 1==========>!*/ class USART_1 : public USART { private:   static USART_1 _prototype;      USART_1()    {       add_prototype( USART_NUMBER::_1, this);   } public:    virtual USART* clone(void) const override final   {    return new USART_1;  }    virtual bool init(uint32_t baudrate) const override final;  virtual bool send(const uint8_t* buf, uint16_t len) const override final;  virtual uint16_t receive(uint8_t* buf) const override final; };  /*!<=======Specific class USART 2==========>!*/ class USART_2 : public USART { private:   static USART_2 _prototype;      USART_2()    {       add_prototype( USART_NUMBER::_2, this);   } public:    virtual USART* clone(void) const override final   {    return new USART_2;  }    virtual bool init(uint32_t baudrate) const override final;  virtual bool send(const uint8_t* buf, uint16_t len) const override final;  virtual uint16_t receive(uint8_t* buf) const override final; };  

Сначала файла идёт перечисление enum class USART_NUMBER со всеми доступными USART, для моего камня их всего два. Затем идёт опережающее объявление базового класса class USART. Далее идёт объявление контейнер а всех прототипов std::map<USART_NUMBER, USART*> и его реестра, который реализован в виде синглтона Мэйерса.

Тут я напоролся на особенность IAR ARM, а именно то, что он инициализирует статические переменные два раза, в начале программы и непосредственно при входе в main. Поэтому я несколько переписал синглтон, заменив статическую переменную _instance на глобальную. То, как это выглядит в идеале, описано в комментарии.

Далее объявлен базовый класс USART, где определены методы добавления прототипа, удаления прототипа, а также создания объекта(так как конструктор классов наследников объявлен как приватный, для ограничения доступа).

Также объявлен чисто виртуальный метод clone, и чисто виртуальные методы инициализации, отправки и получения.

После всего лишь, мы наследуем конкретные классы, где определяем чисто виртуальные методы, описанные выше.

Код определения методов привожу ниже:

#include "MW_USART.h" #include "HW_USART.h"  usart_registry _instance; //Crutch for IAR  /*!<========Initialization of global static USART value==========>!*/ USART_1 USART_1::_prototype = USART_1(); USART_2 USART_2::_prototype = USART_2();  /*!<======================UART1 functions========================>!*/ bool USART_1::init(uint32_t baudrate) const {  bool res = false;  //res = usart_init(USART1, baudrate);  //Platform depending function  return res; }  bool USART_1::send(const uint8_t* buf, uint16_t len) const {   bool res = false;      return res; }  uint16_t USART_1::receive(uint8_t* buf) const {   uint16_t len = 0;      return len; }   /*!<======================UART2 functions========================>!*/ bool USART_2::init(uint32_t baudrate) const {  bool res = false;  res = usart2_init(baudrate);   //Platform depending function  return res; }  bool USART_2::send(const uint8_t* buf, const uint16_t len) const {   bool res = false;   res = usart2_write(buf, len); //Platform depending function   return res; }  uint16_t USART_2::receive(uint8_t* buf) const {   uint16_t len = 0;   len = usart2_read(buf);       //Platform depending function   return len; } 

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

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

APP_ESP8266

Определяю базовый класс для ESP8266 по паттерну «одиночка». В нем определяю указатель на базовый класс USART*.

class ESP8266 { private:   ESP8266(){}   ESP8266(const ESP8266& root) = delete;   ESP8266& operator=(const ESP8266&) = delete;      /*!<---------USART settings for ESP8266------->!*/   static constexpr auto USART_BAUDRATE = ESP8266_USART_BAUDRATE;   static constexpr USART_NUMBER ESP8266_USART_NUMBER = USART_NUMBER::_2;   USART* usart;      static constexpr uint8_t LAST_COMMAND_SIZE = 32;   char last_command[LAST_COMMAND_SIZE] = {0};   bool send(uint8_t const *buf, const uint16_t len = 0);      static constexpr uint8_t ANSWER_BUF_SIZE = 32;   uint8_t answer_buf[ANSWER_BUF_SIZE] = {0};      bool receive(uint8_t* buf);   bool waiting_answer(bool (ESP8266::*scan_line)(uint8_t *));      bool scan_ok(uint8_t * buf);   bool if_str_start_with(const char* str, uint8_t *buf); public:     bool init(void);      static ESP8266& Instance()   {     static ESP8266 esp8266;     return esp8266;   } }; 

Здесь же есть constexpr переменная, в которой и хранится номер используемого USART. Теперь для изменения номера USART нам достаточно только лишь поменять её значение! Связывание же происходит в функции инициализации:

bool ESP8266::init(void) {   bool res = false;      usart = USART::create_USART(ESP8266_USART_NUMBER);   usart->init(USART_BAUDRATE);      const uint8_t* init_commands[] =    {     "AT",     "ATE0",     "AT+CWMODE=2",     "AT+CIPMUX=0",     "AT+CWSAP=\"Tortoise_assistant\",\"00000000\",5,0",     "AT+CIPMUX=1",     "AT+CIPSERVER=1,8888"   };      for(const auto &command: init_commands)   {     this->send(command);     while(this->waiting_answer(&ESP8266::scan_ok)) continue;   }        return res; } 

Строка usart = USART::create_USART(ESP8266_USART_NUMBER); связывает наш уровень приложения с конкретным USART модулем.

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

ссылка на оригинал статьи https://habr.com/ru/post/540148/

Удобное отображение пустого списка

Иногда пользователю необходимо показать подсказу при отсутствии необходимых данных. Например, пустой список, ошибка сервера или отсутствие соединения с сетью интернет. Как быть в случае, когда подсказка должна быть сложной и отличаться для каждого из случаев? Реализация, представленная ниже, должна решить  эти проблемы.

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

Каждая подсказка представляет из себя отдельный фрагмент, который может содержать все угодно. Чтобы управлять ими, объекту нужен FragmentManager и Id контейнера, в котором должны находится фрагменты. Больше зависимостей нет.

Каждая ситуация представляет из себя элемент перечисления (все станет очень понятно с кодом), вложенного в этот объект. Когда нужно изменить подсказку, будет вызываться метод, отвечающий за замену, в который будет передаваться новая ситуация. Это вся логика.

Теперь это надо сделать в реалиях Андроида. Кроме самой активности (или фрагмента), которой нужен объект, на него никто больше ссылаться не будет. Есть начальное, «Дефолтное», состояние. Оно будет отображаться при первом появлении, пока не появится новое значение. Сами значения хранятся в LiveData (которая во ViewModel), на которую подписывается активность и передает каждое новое объекту. Это позволяет переживать пересоздание активности и сохранять состояние.

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

Нюансы

Если, например, не очистить список от данных, то подсказка может быть отображена поверх (или под) этим списком, что будет некрасиво. Для этого нужно предварительно очищать список (или скрывать его видимость, что лучше?).

Реализация

CodeSwitcher.

CodeSwitcher. Я не смог придумать адекватного названия.

//код самого объекта public class CodeSwitcher {     //перечисление всех ситуаций     public enum Code {         DEFAULT,          HTTP_OK,         HTTP_CREATED,         HTTP_BAD_REQUEST,         HTTP_NOT_FOUND,          NO_DATA     }      //зависимости     private FragmentManager fragmentManager;     private int fragmentHostId;      public CodeSwitcher(FragmentManager fragmentManager, int fragmentHostId) {         this.fragmentManager = fragmentManager;         this.fragmentHostId = fragmentHostId;     }      //метод, который будет заменять фрагменты     public void switchFragments(Code code) {         FragmentTransaction transaction = fragmentManager.beginTransaction();          switch (code) {             case HTTP_OK:                 transaction.replace(fragmentHostId, CodeFragment.newInstance("HTTP_OK"));                 break;              case HTTP_CREATED:                 transaction.replace(fragmentHostId, CodeFragment.newInstance("HTTP_CREATED"));                 break;              case HTTP_BAD_REQUEST:                 transaction.replace(fragmentHostId, CodeFragment.newInstance("HTTP_BAD_REQUEST"));                 break;              case HTTP_NOT_FOUND:                 transaction.replace(fragmentHostId, CodeFragment.newInstance("HTTP_NOT_FOUND"));                 break;              case NO_DATA:                 transaction.replace(fragmentHostId, CodeFragment.newInstance("NO_DATA"));                 break;              default:                 transaction.replace(fragmentHostId, CodeFragment.newInstance("Default"));                 break;         }          transaction.commit();     } }
//код ViewModel  //в конструкторе происходит установка дефолтного состояния public CodeShowActivityViewModel() {     listCode = new MutableLiveData<>();     listCode.setValue(CodeSwitcher.Code.DEFAULT); }  //методы только для примера, в реальности таких нет, нужны для изменения состояния public void httpOk() {   listCode.setValue(CodeSwitcher.Code.HTTP_OK);   clearList(); }  public void httpBadRequest() {   listCode.setValue(CodeSwitcher.Code.HTTP_BAD_REQUEST);   clearList(); }
//код активности, необходимый для использования private CodeSwitcher switcher;  //в onCreate() switcher = new CodeSwitcher(getSupportFragmentManager(), R.id.айди_контейнера);  //подписка на LiveData, передача значения на обработку codeActVM.getListCode().observe(this, code -> {   switcher.switchFragments(code); });
Гифка (4мб) с описанием

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

Что думаете об этом способе?

P.S.

Беды с названиями…

ссылка на оригинал статьи https://habr.com/ru/post/540150/

Школы для разработчиков, корпоративные тренинги и онбординг-сервисы — EdTech и T&D-стартапы, которые следует знать

Мы запустили «Актион Акселератор» и ждем всех, кто делает MOOC-курсы, решения в духе «Education 4.0», сервисы для обмена знаниями, онбординга и управления персоналом. Помогаем ресурсами и экспертизой независимым командам и тем, кто развивает что-то похожее in-house.

Участникам акселератора мы рекомендуем изучать сторонние EdTech и T&D-проекты — смотреть, кто привлекает инвестиции и аудиторию. Сегодня делимся подборкой таких компаний.

Фотография: Max Duzij. Источник: Unsplash.com
Фотография: Max Duzij. Источник: Unsplash.com

Galvanize — организует онлайн-курсы и очные тренинги для частных лиц и фирм, занимающихся разработкой программного обеспечения и data science. Управляет несколькими кампусами в США, где находится не только образовательная инфраструктура, но и коворкинги. Более восьми тысяч «выпускников» учебных программ Galvanize зарабатывают от 90 тыс. долларов в год — в Amazon, Facebook, Google, Apple и нескольких сотнях других ИТ-компаний.

Команде удается поддерживать контакт с клиентами и оставаться в курсе того, какие им нужны навыки и специалисты. Отсюда высокая актуальность тренингов и «конверсия» студентов — для выхода на работу 80% из них требуется менее шести месяцев, а рост заработной платы в среднем составляет 30 тыс. долларов. Эти моменты стали ключевыми в сделке с K12 — крупным игроком в образовательной сфере, который выложил 165 млн долларов за проект в 2020 году.

Помимо Galvanize K12 выкупили еще одного организатора буткампов для разработчиков — Tech Elevator, а потом переименовали себя в Stride, чтобы обозначить выход за рамки школьного образования и фокус на «lifelong learning» и персонализированном развитии карьеры собственных учеников. Кстати, за несколько лет до сделки Galvanize и сами приобрели двух производителей образовательного контента для аналитиков, дата-сайентистов и разработчиков, а глава последней компании возглавил Galvanize и довел его до сотрудничества с K12/Stride.

Из других примечательных особенностей — запуск курса по машинному обучению вместе с IBM в 2017 году, когда компания решила познакомить аудиторию со своими облачными решениями и Watson API. Так появился IBM Cognitive Course.


Code Institute — менее масштабный EdTech-проект из Европы. Он уступает Galvanize по численности сотрудников, но показатели трудоустройства учеников у CI даже выше — до 94% выпускников выходят на работу менее чем за три месяца с момента завершения учебы.

Взаимодействие с бизнесом выстроено чуть иначе. Его представители контролируют качество контента напрямую. В экспертный совет входит и бывший CTO Red Hat, который сейчас работает в Google, ведущие инженеры PayPal, Mastercard, Dell и других технологических компаний. Еще такой подход позволяет студентам получать дипломы Эдинбургского университета Нейпира — там засчитывают CI-курсы в рамках классической программы по разработке ПО [Кстати, похожая система действует и в России — в ИТМО экспертный совет оценивает стартапы магистров].

Фотография: Luca Bravo. Источник: Unsplash.com
Фотография: Luca Bravo. Источник: Unsplash.com

Команда работает над проектом шесть лет. Им управляет основатель и спикер Digital Marketing Institute. На старте Code Institute они провели seed-раунд на 500 тыс. евро, а в прошлом году привлекли 1,2 млн от двух венчурных фондов. Средства направят на развитие курсов и B2B-услуг.


Coassemble — это SaaS-конструктор онлайн-курсов. С его помощью старшие специалисты компаний могут делиться опытом с коллегами и управлять процессом обучения: добавлять и редактировать контент, отслеживать завершаемость модулей и получать обратную связь. Как выглядят точечные разделы админки можно увидеть на YouTube-канале и в блоге проекта.

Основатели Coassemble из Австралии. Ранее они занимались разработкой приложений и корпоративных образовательных программ. С 2016 года они привлекали клиентов — в основном из США — и развивали стартап на свои деньги. За это время им удалось заработать ряд индустриальных наград. В 2020-м команда решила обратиться к венчурному финансированию и в рамках первого раунда «подняла» 4,4 млн долларов. На эти средства Coassemble планирует расширить штат своего представительства в Денвере и сделать продукт доступным для небольших компаний — ранее команда сотрудничала только с крупным и средним бизнесом.


Pathlight — предоставляет инструментарий для онбординга и обучения сотрудников в связке с бизнес-аналитикой. Для всего этого здесь предусмотрены дешборды, интеграции с продуктами вроде Salesforce, Zendesk и Slack. Сервис делает ставку на отслеживание бизнес-показателей компании в режиме «реального времени» и помогает соотносить их с тем, как развивается команда. Для коучинга и управления персоналом есть шаблоны и мониторинг KPI.

Фотография: Ant Rozetsky. Источник: Unsplash.com
Фотография: Ant Rozetsky. Источник: Unsplash.com

Один из основателей проекта «сделал exit» из маркетплейса для автомобилей Shift и компании, разработавшей помощника для водителей Automatic. Для Pathlight — команда привлекла 1,1 млн в рамках seed-раунда в 2016-м и еще 7 млн долларов от венчурного фонда в прошлом году. Сегодня она может похвастаться несколькими сотнями корпоративных клиентов из сферы финансов, электронной коммерции и менеджмента с многочисленным штатом сотрудников. Сервис помогает им управлять проектами на дистанционке, автоматизировать формирование отчетов с помощью NLP-инструментария и проводить точечные образовательные сессии.


EmployStream/Able — помогает управлять процессом онбординга сотрудников, которые только присоединяются к новому коллективу или переходят из одного подразделения в другое. Руководству и HR-департаменту компаний-клиентов Able позволяет кастомизировать бизнес-процессы отбора и оформления персонала, их наполнение и отправку уведомлений.

Одна из «фишек» проекта — электронный документооборот и шаблоны, соответствующие требованиям законодательства США для трудовых договоров, в том числе с «удаленщиками». Еще — есть интеграция с Salesforce. Все это подходит для компаний, которые нанимают постоянно и в большом количестве. Able помогает им быстрее справляться со скринингом кандидатов, проверкой документов и сократить количество ошибок при их оформлении.

С 2014 года команда работала под названием EmployStream и привлекла 4,5 млн долларов инвестиций, а в 2020-м — провела еще один раунд на 7 млн и ребрендинг продукта. Кстати, он годится не только для крупных компаний — Able используют десятки хантинговых агентств.


P.S. В одном из следующих материалов мы расскажем о компаниях, предоставляющих инструментарий для VR-тренингов медицинского персонала, плюс — посмотрим на eLearning системы для развития других навыков, прокачки эмоционального интеллекта и «soft skills».

«Актион Акселератор» подходит для команд, разрабатывающих решения в области корпоративного образования и управления персоналом. Он включает пять направлений: от «Обучения 4.0» до организации информационного взаимодействия внутри коллективов предприятий. Мы делимся с участниками инфраструктурой и экспертизой «Актион-МЦФЭР», а победителям каждого трека — выделяем по 500 тысяч рублей.

Заявки можно подавать и тем, кто находится на этапе идеи, начинает прорабатывать концепцию собственного сервиса или только собирается выводить его на рынок.


Что еще почитать у нас на Хабре:


ссылка на оригинал статьи https://habr.com/ru/company/action360/blog/539390/

Дайджест интересных материалов для мобильного разработчика #379 (25 — 31 января)

В этом выпуске выпиливание Realm и создание виджетов, секреты приготовления BLE и уменьшения ANR в шесть раз, вопросы навигации и развития в Android-разработке, подготовка к собеседованию и работа мобильной розницы во время карантина. Все это и многое другое в новом дайджесте!

Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

Быстрый, простой, сложный: как мы выпилили Realm
 HexThrees — моя первая законченная игра
 Как создать виджет для iOS 14 (и не удалить его у пользователей при обновлении)
 Погружение в автотестирование на iOS. Часть 2. Как взаимодействовать с ui-элементами iOS приложения в тестах
 MFS — паттерн построения UI в iOS приложениях
  Паттерн MFS для табличных представлений в iOS приложениях
Apple внедрит защиту конфиденциальности «ранней весной»
Apple приглашает на онлайн-конференцию «Создание отличных виджетов»
Twitter открыл Text Editor API для iOS-разработчиков
Приложение-песочница: как iOS-разработчики автоматизируют рутинные задачи
Введение в Core Graphics
7 расширений Swift, которые должен использовать каждый iOS-разработчик
Профилирование SwiftUI приложений с помощью Инструментов Xcode
Как символизировать логи сбоев в iOS
ToastUI: тосты для SwiftUI
XCMetrics: анализ логов Xcode

Android

 Приложение отвечает: как мы уменьшили количество ANRs в шесть раз. Часть 2, про исправление ошибок + Часть 1
Как обойти проверку на Рутинг устройства, обхитрив библиотеку RootBeer?
 Android Bluetooth Low Energy (BLE) – готовим правильно, часть #4 (bonding)
 Ликбез по Navigation Component: тем, кто пропустил все туториалы
Navigation Component и multi backstack navigation
Автоматизация публикации приложения в Google Play при помощи Jenkins
 Safe Args?—? верный помощник Navigation Component
Как развиваться в Android-разработке и где брать новые знания
Android Broadcast: превращаем Android приложение в Kotlin Multiplatform
Обновление FragmentViewBindingDelegate: ошибка, унаследованная от AutoClearedValue)
Использование Hilt ViewModelComponent
Обработка ответов из сети для Android-проектов с помощью Sandwich
Epoxy – создание декларативных и повторно используемых компонентов пользовательского интерфейса
Unity как библиотека: добавьте функции Unity в ваше Android-приложение
Решение архитектурных проблем в мобильных приложениях с Bluetooth Low Energy
Android TopSheet – реализация
Машинное обучение в Android с помощью TensorFlow Lite
Поиск ошибок в приложении для Android
9 распространенных ловушек при Android-собеседовании
ComposeSlackDesktop: Slack на Jetpack Compose

Разработка

 Менеджер приложений для Windows Mobile
Работа с асинхронностью в Dart
 Кроссплатформенный мультиплеер на Godot без боли
 Онбординг нового разработчика с помощью Ansible
 Все, что вам нужно знать о маршрутизации между страницами в Flutter
Podlodka #200: как учить языки программирования
make sense podcast: О процессах в продуктовых командах
Моя подготовка к собеседованию в Google
Платформа Ludo помогает придумывать идеи игр с помощью ИИ
Дизайн приложений: примеры для вдохновения #29
Задачи с собеседований: ветер
Яндекс открывает набор в летние школы разработки и дизайна
Почему красивое кажется удобным: разбираем интерфейсы с точки зрения науки. Часть 1
5 мощных IDE, о которых никто не говорит
Не просто пишите код, решайте проблемы
Разбираем блестящий и простой дизайн Tinder
Создание приложения для криптовалюты с помощью Flutter
10 непростительных фраз, которые не надо говорить на собеседованиях
Мобильные приложения больше не являются хорошей идеей для стартапов
Как разместить Docker сервер многопользовательской игры Unity в облаке Google
5 простых способов улучшить навыки отладки
3 простых метода для улучшения навыков программирования
Где лучше работать продуктовому дизайнеру? Дизайн-агентство vs. продуктовая компания
Цепочка ответчиков iOS: UIResponder, UIEvent, UIControl и как их совместить
10 лучших бесплатных инструментов для разработки игр в 2021 году
5 шаблонов проектирования, которые должен знать каждый программист
Худшая ошибка, которую вы можете сделать во время технического интервью
Ray: трассировка лучей в ASCII

Аналитика, маркетинг и монетизация

Разумный женский календарь: как делают приложение № 1 в категории «Здоровье и фитнес»
Google Play разрешает «игры на деньги» еще в 15 странах
Charlie: игровое избавление от долгов
Почти все российские государственные приложения передают данные сторонним компаниям
Руководство маркетолога по новостному приложению № 1 в Китае: Toutiao
Literati получил $40 млн на развитие книжного клуба
Симуляторы показали наибольший рост доходов в США
В какие игры еще играют пользователи: исследование AppsFlyer
Маркетологи в мобайле: Виталий Шахматов (Hoff)
Bodyguard: автоматическое удаление негатива
Голосовой чат Clubhouse получает инвестиции и начинает монетизацию
Персонализация предложений в мобильном приложении и интернет-магазине: кейс ВсеИнструменты.ру

AI, Устройства, IoT

 Системы контроля управления доступом в IoT — умеем, знаем, практикуем
OpenCV проводит конкурс пространственного ИИ
Google открывает Tilt Brush
Как сделать IoT-устройство

< Предыдущий дайджест. Если у вас есть другие интересные материалы или вы нашли ошибку — пришлите, пожалуйста, в почту.

ссылка на оригинал статьи https://habr.com/ru/company/productivity_inside/blog/540152/

Новые данные о движении звёзд усложнили жизнь астрономам

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


Кликабельно

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

«Вводите название звезды или её местоположение, и через секунду получаете ответ», — сказал Бэрри Мэдор, космолог из Чикагского университета и Обсерваторий Карнеги, во время видеозвонка. «В общем…» – он умолк.

«Мы просто завалены этими данными», — сказала Венди Фридман, космолог из тех же университетов, жена и коллега Мэдора.

«Невозможно преувеличить мой восторг по этому поводу», — сказал по телефону Адам Рисс из университета Джонса Хопкинса, получивший в 2011 году нобелевскую премию за участие в открытии тёмной энергии. «Давайте я переключусь на видео, чтобы показать вам, что меня так восхитило?» Мы перешли в Zoom, чтобы он смог поделиться своим экраном, где расположились красивые графики, описывающие новые данные по местоположениям звёзд.

Эти данные собрал космический аппарат «Гайя» европейского космического агентства. Последние шесть лет он с насеста высотой в полтора миллиона километров без перерыва глазел на звёзды. Телескоп измерил параллаксы 1,3 млрд звёзд – крохотные изменения видимых положений звёзд, выдающие расстояние до них. «Параллаксы с Гайи – самые точные измерения расстояний за всю историю», — сказал Джо Бови, астрофизик из Торонтского университета.

А что самое приятное для космологов, в новый каталог «Гайи» входят особые звёзды, расстояния до которых служат мерилом для всех остальных, более далёких расстояний. Поэтому новые данные мгновенно обострили крупнейшую проблему современной космологии: неожиданно быстрое расширение Вселенной, «хаббловскую напряжённость» [Hubble tension].

Напряжённость состоит в следующем: на основе известных составляющих Вселенной и управляющих ею уравнений получается, что она должна расширяться со скоростью в 67 км в секунду на мегапарсек – то есть, с каждым дополнительным мегапарсеком между нами и галактикой она должна разлетаться от нас на 67 км быстрее. Однако реальные измерения постоянно превосходят это значение. Галактики разлетаются слишком быстро. Это расхождение наводит на волнующую мысль о том, что в космосе должен быть какой-то неизвестный нам ускоряющий фактор.

«Было бы невероятно здорово обнаружить новую физику, — сказала Фридман. – Я втайне надеюсь, что на этом основании можно сделать открытие. Но нам нужно убедиться, что мы правы. Перед тем, как недвусмысленно заявить об этом, нужно проделать много работы».

В эту работу входит уменьшение возможных источников ошибок в измерениях скорости расширения. Крупнейшим из таких источников было расстояние до ближайших к нам звёзд – и это расстояние уточнили новые данные о параллаксе.

В опубликованной в журнале The Astrophysical Journal работе команда Рисса использовала новые данные, чтобы уточнить скорость расширения. Они получили 73,2 км в секунду на мегапарсек, что соответствует их предыдущим оценкам, только теперь погрешность уменьшилась до 1,8%. Это лишь укрепляет расхождение с предсказанной величиной скорости, 67.

Фридман и Мэдор скоро планируют опубликовать собственное новое и улучшенное измерение этой величины. Они тоже считают, что новые данные лишь укрепят, но не изменят их измерений, которые, хоть и были меньше, чем у Рисса и других групп, но всё равно превосходили предсказание.

С момента запуска «Гайи» в декабре 2013 года она выпустила два массивных набора данных, произведших революцию в понимании ближайших к нам частей космоса. Однако предыдущие измерения параллакса «Гайи» разочаровали всех. «Когда мы посмотрели на первый выпуск данных» в 2016 году, «нам хотелось плакать», сказала Фридман.

Непредвиденная проблема

Если бы параллаксы было легче измерять, революция Коперника могла бы случиться и ранее.

В XVI веке Коперник предположил, что Земля вращается вокруг Солнца [такие предположения высказывались и задолго до него, однако в Европе общепринятой считалась геоцентрическая система]. Однако уже тогда астрономы знали о параллаксе. Если Коперник был прав, и Земля движется, то они ожидали увидеть смещение позиций звёзд на небе – так же, как видимый вами фонарный столб смещается относительно далёких холмов сзади него, когда вы переходите улицу. Астроном Тихо Браге не обнаружил подобных сдвигов, и заключил, что Земля не движется.

И всё-таки, она движется, а звёзды – сдвигаются, хотя и очень мало, поскольку расположены они очень далеко от нас.

Только в 1838 году немецкий астроном Фридрих Вильгельм Бессель смог обнаружить звёздный параллакс. Измеряя угловой сдвиг звёздной системы 61 Лебедя по отношению к окружающим звёздам, Бессель заключил, что она расположена на расстоянии в 10,3 светового года от нас [по образному выражению его современников, «впервые лот, заброшенный в глубины мироздания, достиг дна» / прим. пер.]. И его измерения отличались от истины всего на 10% — новые измерения «Гайи» говорят, что две звезды этой системы располагаются на расстоянии в 11,4030 и 11,4026 световых года от нас, плюс-минус пару тысячных долей.

Система 61 Лебедя находится чрезвычайно близко к нам. Более типичные звёзды Млечного Пути сдвигаются лишь на сотые доли угловой секунды – это в сто раз меньше пикселя у современной камеры телескопа. Для определения их движения требуется специализированное сверхстабильное оборудование. «Гайю» специально разрабатывали для этой цели, но когда телескоп включили, то столкнулись с непредвиденной проблемой.

Телескоп работает благодаря тому, что смотрит сразу в двух направлениях, и отслеживает угловую разницу между звёздами в двух областях видимости, пояснил Леннарт Линдерген, один из авторов проекта «Гайя» в 1993 году, и руководитель команды, анализирующей новые данные по параллаксу. Для точного измерения параллакса требуется, чтобы угол между двумя областями зрения оставался постоянным. Но в начале работы миссии учёные обнаружили, что это не так. Телескоп слегка изгибался, вращаясь по отношению к Солнцу, из-за чего в его движения вкрадывались колебания, имитировавшие параллакс. Что ещё хуже, этот сдвиг сложным образом зависел от местоположения объектов, их цвета и яркости.

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

Взбираясь по лестнице

Вооружившись новыми данными, Рисс, Фридман и Мэдор с командами смогли пересчитать скорость расширения Вселенной. В общих чертах, чтобы измерить скорость расширения, нужно понять, насколько далеко от нас находятся удалённые галактики, и как быстро они от нас удаляются. Измерить скорость просто, а расстояния – сложно.

Самые точные измерения полагаются на сложные "лестницы космических расстояний". За первую ступень отвечают «стандартные свечи» – звёзды, внутри и снаружи нашей Галактики с хорошо определённой яркостью, находящиеся достаточно близко от нас, чтобы их параллакс можно было измерить – а это единственный способ измерить расстояние до объекта, не приближаясь к нему. Затем астрономы сравнивают яркость этих стандартных свечей с яркостью более тусклых, расположенных в близлежащих галактиках, чтобы рассчитать расстояние до них. Это вторая ступень лестницы. Зная расстояние до галактик, выбранных потому, что в них есть редкие и яркие взрывы сверхновых типа Iа, астрономы могут измерить относительные расстояния до расположенных ещё дальше галактик, где тоже есть, уже более тусклые для нас, сверхновые типа Ia. Отношение скорости этих далёких галактик к расстоянию до них даёт скорость расширения космоса.

Параллаксы, следовательно, критически важны для всей этой конструкции. «Измените первый шаг – параллаксы – и все последующие тоже поменяются», — сказал Рисс, один из лидеров подхода с лестницей расстояний. «Измените точность первого шага, изменится точность всего остального».

Команда Рисса использовала новый измеренный «Гайей» параллакс 75 цефеид – пульсирующих переменных звёзд, выбранных ими в качестве предпочтительных стандартных свечей – чтобы заново откалибровать своё измерение скорости расширения Вселенной.

Главные соперники Рисса в игре с лестницей расстояний, Фридман и Мэдор, в последние годы начали утверждать, что в цефеидах может скрываться погрешность, влияющая на верхние ступени лестницы. Поэтому, не полагаясь на них, их команда комбинирует измерения на основе различных стандартных свечей из набора данных «Гайи» – цефеиды, переменные типа RR Лиры, звёзды с верхней части ветки красных гигантов, и т.н. углеродные звёзды.

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

«Это говорит о том, что мы действительно получаем реальные данные», — сказал Мэдор.

ссылка на оригинал статьи https://habr.com/ru/post/540154/