Архив рубрики: Программирование

FTDI борется за копирайт, но не видит, что творится под носом

FTDI, крупный производитель полупроводников, недавно выпустил обновление драйверов для популярных устройств, которые «отключают» устройства изготовленные с использованием поддельных микросхем. Это вызвало волну негодования среди пользователей, потому как они практически не имеют возможности установить, используются ли в купленных ими устройствах настоящие или поддельные микросхемы. К счастью, FTDI убрал это обновление на данный момент. Кроме того имеются способы восстановления работоспособности устройств с поддельными микросхемами. Тем не менее компания по прежнему не оставляет своих планов по написанию таких драйверов, которые бы мешали пользоваться поддельными микросхемами. По подробнее об этом можно почитать, например, здесь: FTDI наносит ответный удар

Но взглянем на микросхему, о которой речь.
FT232RL — чрезвычайно удобна. Она служит мостом между виртуальным последовательным портом на кампьютере и UART на устройстве. Помимо этого, она поддерживает режим bit-bang, который позволяет реализовать программную поддержку многих протоколов (например SPI, I2C, и т.д.). В довесок идёт внутренняя схема, которая позволяет подключаться как линиям данных как при напряжении 5В, так и при 3,3В.
С точки зрения программирования, необходимо лишь подключить устройство с FT232 к порту USB, установить драйвер и вот уже есть доступ к виртуальному последовательному порту. Нужен режим bit-bang? Тогда скачиваете библиотеку с описанием на сайте FTDI и пользуетесь.

Идеальная железка для любых проектов, не смотря на заявленые цены.

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

Подключаем и Бам! Ничего не работает.
После некоторого времени проведённого в экспериментах и выдирании волос с головы, обнаруживаем в Диспетчере Устройств следующую картину:

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

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

Подключаем 16-клавишный тачпад на базе TTP229 к микроконтроллеру

Недавно получил, заказанные с eBay, тачпад с 16 клавишами, на базе чипа TTP229.

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

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

Всего с двумя дополнительными перемычками заработали все 16 клавиш, и кроме того поменялся режим выхода с активного низкого, на активный высокий. Для быстрого тестирования и демонстрации принципов работы с модулем, набросал простенький скетч для Arduino, который можно найти в моём репозитории на GitHub https://github.com/dmmedia/TTP229B_16keypad. Загрузив скетч и подключившись к Arduino через SerialMonitor, можно наблюдать события нажатия и отпускания или смены кнопок.

Далее в планах переписать этот скетч в полноценную библиотеку, и начать использовать модули в моих экспериментах и проектах. Модули довольно хорошо для этого подходят: они тонкие, особенно если заменить разьём-гребёнку на паянные перемычки и провода, там где это надо. Клавиатуру модуля можно закрыть плёночной или бумажной мембраной с собственным дизайном. А цена на эти модули позволяет покупать их пачкой, вместо того, что бы тратить своё время на изготовление.

И снова эскейпим SQL

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

Однако снова и снова открываешь какой-нибудь энтерпрайз проект и видишь что то наподобие этого:
CString sql = "SELECT * FROM categories WHERE category = '" + category + "'";
где category никем не проверяемая и неэкранированная переменная, которую создают прямиком из введённых пользователем данных.

Ведь практически каждая современная СУБД позволяет через свой API либо автоматически, во время подготовки запроса(prepared statements), либо в ручную, вызвав спец. метод экранировать такие строки. Но нет, кто-то продолжает писать код на коленке, когда надо по быстрому сдать проект. По моему надо учить обработке пользовательских данных буквально на любом курсе программирования, что бы потом это было на уровне рефлексов. А не так как сейчас, где некоторые темы на курсах совсем не затрагиваются или обращается мало внимания на качество кода.

Частая ошибка при использовании std::erase в связке со std::remove_if в C++

Для того, что бы удалить эелемент из контейнера типа vector, string, list или deque мы пользуемся методом erase, которому параметром передаём итератор на ненужный элемент.

В какое то время, происходит оптимизация кода или же изначально закладывается возможность поиска ненужного элемента с помощью алгоритма STL remove_if.

Далее код обрастает использованием boost::bind для удобства и прочими артефактами.

И тут в какой то момент становится нужно удалять не один элемент, а несколько. Неважно, в новом коде, или же расширяя возможности старого. И здесь многие новички в C++ делают ошибку, не зная и не посмотрев в документацию метода erase. Они копируют по примеру код, написанный ранее и переделывают условие поиска элементов для remove_if. Но они забывают, что если методу erase передаётся один аргумент, то этот метод удаляет именно тот элемент, итератор на который был передан аргументом.

Для удаления нескольких элементов существует перегруженный метод erase, который принимает два аргумента — на начало и конец итеративной последовательности, подлежащей удалению. В большинстве случаев, когда используется алгоритм remove_if, он реорганизует последовательность так, что все элементы не подлежащие удалению переставляются в начало контейнера. Например, у нас есть vector , содержащий следующие значения.
1 2 3 1 2 3 1 2 3

Если вызвать
bool IsTwo (int i) { return (i == 2); }

tstart = remove_if(v.begin(), v.end(), isTwo)
//удалить элемент со значением 2 из контейнера v

получится
1 3 1 3 1 3 ? ? ?
где ? — некий мусор. Итератор tstart, который возвратит remove_if, будет указывать на первый мусорный элемент, в этом случае на третий с конца. Мусорные элементы могут иметь те же значения, что и до вызова remove_if, то есть 1 2 3, а могут иметь и любые другие, на это полагаться не стоит. Размер контейнера остается неизменным.

Таким образом, если мы запишем полный кусок кода, с которым работает новичок, то получится следующее:
v.erase(remove_if(v.begin(), v.end(), isTwo));

Соответственно erase удаляет первый мусорный элемент и изменяет размер контейнера на единицу. В результате содержимое контейнера начинает выглядеть так:
1 3 1 3 1 3 ? ?
что является неправильным и соответственно приводит к непредсказуемым последствиям при дальнейшей работе с этим контейнером.

Как же не допускать такого? Когда, разработчик не уверен в своих знаниях, то хорошим тоном будет проверить их по документации и узнать, что если вызвать erase таким образом:
v.erase(remove_if(v.begin(), v.end(), isTwo), v.end());
то все мусорные элементы будут удалены.

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

Опасность использования значений по умолчанию в обьявлениях

Недавно работал на одним большим проектом написанным на C++, и во время отладки встретился с довольно опасной вещью. Посмотрим на примерах. В начале был некий метод с парой параметров один из которых задаётся по умолчанию в обьявлении:

Class1::SetEditBox(CString const& value, bool isUppercase = true);

И гдето в коде этот метод по разному вызывался:

obj1.SetEditBox(_T("Volume"), false);
 ...
 obj2.SetEditBox(_T("SerialNo"));

Прошло время и обьявление метода немного дополнили:

Class1::SetEditBox(CString const& value, bool isEditable = false, bool isUppercase = true);

В новых местах использование, всё было хорошо, да вот старый код толком никто так и не посмотрел, таким образом после вызова

obj1.SetEditBox(_T("Volume"), false);

isUppercase становился true. Однако, так как обьём обычно задаётся в числах, то без толкого тестирования различных вариантов, такой случай прошёл незамеченным.
Время шло, система росла и становилась сложнее. С каждой сменой команды разработчиков её брались переписывать и оптимизировать и в какой то момент бросали. Метод задействовался в других классах:

Class2::SetListEdit(CString const& value, bool isEditable = false, bool isUppercase = true)
{
    // ...
    obj4.SetEditBox(value, isEditable, isUppercase);
    // ...
}

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

Class1::SetEditBox(CString const& value, bool isNumeric = true, bool isEditable = false, bool isUppercase = true);

На этот раз уже гораздо больше кода следовало просмотреть и изменить, но как то изменение забылось и прошло незамеченным. И далее код ещё более расширился:

Class3::AddListEdit(CString const& value, bool isUppercase = true)
{
    Class2 obj5;
    obj5.SetListEdit(value, isUppercase);
}

В результате мы имеем ошибку, которую я назвал «Shifting Boolean».
Таким образом вызов Class3::AddListEdit() и передача аргументом только строки, приводит к тому, что значениее isUppercase становится значением для isEditable в Class2::SetListEdit() и затем становится значением для isNumeric в Class1::SetEditBox().

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

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

Рефакторинг этого кода занял у меня 6 часов. Зато я избавился от переменных типа bool, заменил их единственным аргументом enum, так как в данном случае комбинации изначальных аргументов были между собой связаны логически. И теперь следующий разработчик сможет спокойно воспользоваться единственным нужным значением, вместо того, что бы смотреть где что установлено по умолчанию и что нужно прописать руками. Да и перебрать один аргумент через switch() гораздо легче, чем продираться через лес if()-ов для проверки всех возможных комбинаций для bool.

Округление дробей в стиле Omniva

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

Округление в стиле Omniva