Счетчики. Вариант 2 - Счетчик запусков или защита своего проекта от недобросовестного заказчика (Обновлено)

14.08.2017 20:32

genaonyx



Введение

Всем привет, рано или поздно каждый Ардуинщик сталкивается с тем, что его просят сделать тот или иной проект естественно не безвозмездно. И все бы было хорошо, но находиться люди, которых хлебом не корми да дай кого-нибудь развести на деньги и просто кинуть человека, который вложил свое время и знания в его реализацию. Ситуация усугубляется если заказчик и исполнитель находятся в разных городах / странах. Тут надо иметь дело с почтой и, как правило, основное условие заказчика сначала товар потом деньги. И я всецело с ними согласен, мало кто захочет платить тому, кто может и не сделать задуманное и просто взять деньги и пропасть. И тут естественно надо себя как то защитить. Одой из таких защит является счетчик включения / выключения или счетчик запусков (Кому как удобней, так и называйте). Конечно, даже с ним вы можете пролететь на деньги, а именно на стоимость Arduino и комплектующих модулей использующихся в проекте, но зато сам проект станет неработоспособным в случае обмана ну или заставит злоумышленника передумать и все же оплатить заработанные деньги.


Принцип работы / описание

Принцип работы счетчика довольно прост, каждый раз, когда Arduino запускается, в обработчике setup() мы производим чтение двух переменных из энергонезависимой памяти Arduino (EEPROM). Одна из переменных используется для определения, работает ли наш счетчик или он уже был деактивирован вводом пароля. Вторая же переменная будет тем самым счетчиком, увеличивающимся каждый раз при включении Arduino, то есть мы этот счетчик прочтем, увеличим на единицу и запишем обратно в энергонезависимую память. В основной же секции loop() мы будем определять, работает ли счетчик, достиг ли он лимита и собственно, если все хорошо выполнять ваш скетч, иначе ожидать ввода пароля. В данной статье я опишу ввод пароля путем передачи его посредством серийного монитора, то есть через USB, вы же можете изменить скетч и добавить ввод пароля, например нажатием некой комбинации кнопок, если таковые есть в вашем проекте. Ну а теперь приступим.


Реализация простейшего счетчика запусков

#include "EEPROM.h" // Подключаем библиотеку EEPROM.

#define scAddress 0 // Адрес, где хранятся показания счетчика.

byte scValue = 0; // Переменная с показаниями счетчика.

void setup()
{
  scValue = EEPROM.read(scAddress); // Чтение данных счетчика из EEPROM.
  scValue++;                        // Увеличиваем счетчик на единицу.
  EEPROM.write(scAddress, scValue); // Записываем данные счетчика обратно в EEPROM.
  Serial.begin(9600);               // Выводим в монитор порта для проверки.
  Serial.println(scValue);
}

void loop()
{
  //...
}

Теперь вкратце, что мы сделали.

  • Подключили библиотеку EEPROM для работы с энергонезависимой памятью.
  • Создали константу scAddress для того чтобы указать где находиться показания нашего счетчика запусков (число может быть разное но не более 1023 для Arduino UNO).
  • Далее мы объявили переменную scValue с которой будем в дальнейшем работать.
  • Прочитали из EEPROM в переменную scValue показания счетчика запусков.
  • Увеличили переменную scValue на единицу.
  • Записали обратно показания счетчика в указанный в scAddress адрес в нашей энергонезависимой памяти.
  • Вывели показания scValue в монитор порта для отладки.


Ограничение на количество запусков

Теперь давайте добавим ограничение на количество запусков.

#include "EEPROM.h" // Подключаем библиотеку EEPROM.

#define scAddress 1 // Адрес, где хранятся показания счетчика.
#define scCount 5   // Количество разрешенных запусков.
#define ledPin 13   // Порт Ардуино со светодиодом.

byte scValue = 0; // Переменная с показаниями счетчика.

void setup()
{
  scValue = EEPROM.read(scAddress); // Чтение данных счетчика из EEPROM.
  if (scValue == 255) scValue = 0;  // Сбрасываем счетчик в 0.
  if (scValue <= scCount)
  {
    scValue++;                        // Увеличиваем счетчик на единицу.
    EEPROM.write(scAddress, scValue); // Записываем данные счетчика обратно в EEPROM.
  }
  pinMode(ledPin, OUTPUT);   // Настраиваем порт светодиода.
  digitalWrite(ledPin, LOW); // Выключаем светодиод.
}

void loop()
{
  // Если количество запусков превысило необходимое нам количество раз, то прекращаем выполнение скетча.
  if (scValue > scCount) return;
  // Иначе выполняем скетч дальше.
  digitalWrite(ledPin, HIGH); // Включаем светодиод.
}

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

  • Мы добавили константу scCount для ограничения количества запусков. В нашем случае это 5 запусков, но вы можете поменять и на любое другое количество до 255.
  • Для наглядности добавили управление 13-м светодиодом Arduino, первые 5-ть включений он будет работать, а потом работать откажется, так как мы превысим разрешенный лимит запусков.
  • Изменили принцип работы счетчика, если раньше он у нас был циклическим, то теперь он отсчитывает ровно столько, сколько разрешено, после чего запрещает всякое выполнение программы.
  • Так же прошу обратить внимание на сброс счетчика в ноль. По умолчанию память EEPROM полностью заполнена единичными битами. Собственно считывая байт состоящий из восьми единиц мы получим число 255. Это число надо сбросить в 0.
  • Ну и в конце в обработчике loop() мы проверяем достиг ли наш счетчик заданного порога, и если он достиг то запрещаем выполнение скетча.

Вот так вот просто мы можем ограничить количество запусков.

На этом этапе мы уже можем отправлять проект заказчику но придется долго и нудно объяснять как скачать Arduino IDE ну и собственно как прошить Arduino.

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


Снятие защиты вводом пароля.

#include "EEPROM.h"  // Подключаем библиотеку EEPROM.

#define scAddress1 2 // Адрес, где хранятся показания счетчика.
#define scAddress2 3 // Адрес, где хранятся статус счетчика (вкл/выкл).
#define scCount 5    // Количество разрешенных запусков.
#define ledPin 13    // Порт Ардуино со светодиодом.

char scPassword[] = "www.arduino.on.kg"; // Пароль.
String scInput = ""; // Содержит пароль, полученный от пользователя.

byte scValue = 0;  // Переменная с показаниями счетчика.
byte scEnable = 0; // Переменная со статусом счетчика.

void setup()
{
  scValue = EEPROM.read(scAddress1);  // Чтение данных счетчика из EEPROM.
  scEnable = EEPROM.read(scAddress2); // Чтение статуса счетчика из EEPROM.
  if (scValue == 255) scValue = 0;    // Сбрасываем счетчик в 0.
  if (scEnable == 255) scEnable = 1;  // Сбрасываем статус на включён.
  if (scValue <= scCount && scEnable)
  {
    scValue++;                         // Увеличиваем счетчик на единицу.
    EEPROM.write(scAddress1, scValue); // Записываем данные счетчика обратно в EEPROM.
  }
  Serial.begin(9600);        // Настраиваем COM-порт.
  pinMode(ledPin, OUTPUT);   // Настраиваем порт светодиода.
  digitalWrite(ledPin, LOW); // Выключаем светодиод.
}

void loop()
{
  // Если количество запусков превысило необходимое нам количество раз, то ждем ввода пароля.
  if (scValue > scCount && scEnable)
  {
    delay(1000);                   // Ждем секунду, чтобы получить весь пароль целиком.
    scInput = Serial.readString(); // Считываем пароль.
    if (scInput != "")             // Если что-то прочли.
     if (scInput == scPassword)    // Сравниваем пароли.
     {
       scEnable = 0;                               // Сбрасываем запрет. 
       EEPROM.write(scAddress2, scEnable);         // Записываем в память EEPROM ноль чтобы выключить счетчик.
       Serial.println("Password successfully.");   // Сообщаем, что пароль подошел.
     } else Serial.println("Password incorrect."); // Сообщаем, что пароль не подошел.
    return;                                        // Запрещаем выполнение скетча далее.
  }
  // Иначе выполняем скетч дальше.
  digitalWrite(ledPin, HIGH); // Включаем светодиод.
}

Ну и давайте продолжим в том же духе, как и ранее в статье.

  • И так, мы добавили статус нашего счетчика, включен он или выключен. Для этого мы зарезервировали в памяти еще один байт по адресу scAddress2. Данные самого счетчика у нас теперь по адресу scAddress1. Итого для работы нашего счетчика нам требуется уже два байта в энергонезависимой памяти.
  • Так же были добавлены две переменные. Это scPassword – содержащая пароль выключения счетчика и scInput – содержащая данные полученные от монитора порта или терминала. Собственно сравнение этих двух переменных на равенство и деактивирует наш счетчик.
  • За показания счетчика по прежнему отвечает переменная scValue, а вот статус счетчика лежит в новой переменной scEnable, где 0 – выключен, а 1 – включен.
  • Как и счетчик при первом запуске мы сбрасываем статус в единицу, то есть включаем его.
  • Далее в секции setup() практически то же самое за исключением того, что мы теперь проверяем включен ли счетчик или нет, если считчик выключен то мы ничего не делаем.
  • А вот в секции loop() кода значительно прибавилось. Если счетчик выключен или не достиг своего лимита, то мы сразу выполняем скетч, что следует за проверкой, а вот если счетчик достиг лимита и он собственно включен, то мы не даем выполнять скетч до тех пор, пока не получим из монитора порта или терминала пароль. Естественно информирую пользователя о том, что пароль верен или не верен.


Подытожим

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


P.S.

Люди, пожалуйста, обратите внимание на то, что адреса в приведенных мною скетчах разные. Как вы должны понимать, энергонезависимая память не стирается после перезагрузки или включения / выключения Arduino. Поэтому данные скетчи будут работать только один раз, после чего в адреса используем в скетчах надо обратно записать байт = 255. Пожалуйста, разберитесь со скетчем и его работой.


Сброс EEPROM до заводского состояния

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

#include "EEPROM.h"

void setup()
{
  for (int i = 0; i < EEPROM.length(); i++) EEPROM.write(i, 255);
}

void loop()
{
  // ...
}


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


Обновлено (14.08.2017)

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

#define scAddress1 4 // Адрес, где хранятся показания счетчика.
#define scAddress2 6 // Адрес, где хранятся статус счетчика (вкл/выкл).
#define scCount 5    // Количество разрешенных запусков.
#define ledPin 13    // Порт Ардуино со светодиодом.

char scPassword[] = "www.arduino.on.kg"; // Пароль.
String scInput = ""; // Содержит пароль, полученный от пользователя.

uint16_t scValue = 0; // Переменная с показаниями счетчика.
uint8_t scEnable = 0; // Переменная со статусом счетчика.

void setup()
{
  scValue = eeprom_read_word((uint16_t*)scAddress1); // Чтение данных счетчика из EEPROM.
  scEnable = eeprom_read_byte((uint8_t*)scAddress2); // Чтение статуса счетчика из EEPROM.
  if (scValue == 65535) scValue = 0;   // Сбрасываем счетчик в 0.
  if (scEnable == 65535) scEnable = 1; // Сбрасываем статус на включён.
  if (scValue <= scCount && scEnable)
  {
    scValue++; // Увеличиваем счетчик на единицу.
    eeprom_write_word((uint16_t*)scAddress1, scValue); // Записываем данные счетчика обратно в EEPROM.
  }
  Serial.begin(9600);        // Настраиваем COM-порт.
  pinMode(ledPin, OUTPUT);   // Настраиваем порт светодиода.
  digitalWrite(ledPin, LOW); // Выключаем светодиод.
}

void loop()
{
  // Если количество запусков превысило необходимое нам количество раз, то ждем ввода пароля.
  if (scValue > scCount && scEnable)
  {
    delay(1000);                   // Ждем секунду, чтобы получить весь пароль целиком.
    scInput = Serial.readString(); // Считываем пароль.
    if (scInput != "")             // Если что то прочли.
     if (scInput == scPassword)    // Сравниваем пароли.
     {
       scEnable = 0; // Сбрасываем запрет. 
       eeprom_write_byte((uint8_t*)scAddress2, scEnable); // Записываем в память EEPROM ноль чтобы выключить счетчик.
       Serial.println("Password successfully.");   // Сообщаем что пароль подошел.
     } else Serial.println("Password incorrect."); // Сообщаем что пароль не подошел.
    return;  // Запрещаем выполнение скетча далее.
  }
  // Иначе выполняем скетч дальше.
  digitalWrite(ledPin, HIGH); // Включаем светодиод.
}

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



Расскажи о нас

Сообщение

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