Была поставлена задача сделать учет рабочего времени для одного работника, приходя отмечается по отпечатку пальца ,уходя отмечается по отпечатку пальца, сообщения о приходе и уходе должны приходить в телеграмм, так же через телеграмм должны быть доступны отчеты.Поскольку система считывания отпечатков пальцев стоит в одном городе, а руководитель живет в другом то локально систему не сделать, по этому я использую свой VPS сервер на котором уже стоит MajorDoMo, писать клиента для отправки сообщений было лениво и я взял прошивку WiFi-IoT c включенными опциями: mqtt клиент и UART bridge, в качестве сканера отпечатков пальцев у меня использован AS608 подключенный к Arduino nano. Прокладка из ардуино была поставлена из-за того что не хотелось писать клиента, прошивку от ардуино я взял в интернете и немного её модернизировал, она должна отправлять в uart порт и как следствие на сервер сообщение parametr и через пробел номер ячейки в которую записан отпечаток пальца. В общем в MajorDoMo по MQTT должнен приходить номер ячейки отпечатка пальца, как это сделать вариантов много.
Схему и скетч для ардуино я взял отсюда, скетч немного модернизировал, добавил отправку с сериал порт номер ячейки пальца, сделал в начале переменные для длительности зумера и открытия замка (отображение сообщения).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
// Подключаем библиотеки: #include <Wire.h> // подключаем библиотеку для работы с шиной I2C #include <SoftwareSerial.h> // подключаем библиотеку для работы с программным UART #include <LiquidCrystal_I2C.h> // подключаем библиотеку для работы с LCD дисплеем #include <Adafruit_Fingerprint.h> // подключаем библиотеку для работы с модулем отпечатков пальцев LiquidCrystal_I2C lcd(0x27,16,2); // объявляем объект lcd для работы с LCD дисплеем, указывая параметры дисплея (адрес I2C = 0x27, количество столбцов = 16, количество строк = 2) SoftwareSerial mySerial(2, 3); // объявляем объект mySerial для работы с библиотекой SoftwareSerial ИМЯ_ОБЪЕКТА( RX, TX ); // Можно указывать любые выводы, поддерживающие прерывание PCINTx Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial); // объявляем объект finger для работы с библиотекой Adafruit_Fingerprint ИМЯ_ОБЪЕКТА = Adafruit_Fingerprint(ПАРАМЕТР); // ПАРАМЕТР - ссылка на объект для работы с UART к которому подключен модуль, например: &Serial1 // Объявляем переменные и константы: const uint8_t PIN_led_OPEN = 4; // указываем номер вывода arduino, к которому подключен зелёный светодиод const uint8_t PIN_led_CLOSED = 5; // указываем номер вывода arduino, к которому подключен красный светодиод const uint8_t PIN_beep_OPEN = 6; // указываем номер вывода arduino, к которому подключен Trema зуммер const uint8_t PIN_key_OPEN = 7; // указываем номер вывода arduino, к которому подключен Trema ключ const uint8_t PIN_button_A = 8; // указываем номер вывода arduino, к которому подключена кнопка A const uint8_t PIN_button_B = 9; // указываем номер вывода arduino, к которому подключена кнопка B uint16_t TIM_Button_A = 0; // время удержания кнопки A (в сотых долях секунды) uint16_t TIM_Button_B = 0; // время удержания кнопки B (в сотых долях секунды) uint32_t TIM_mode_ACCESS = 0; // время установки флага FLG_mode_ACCESS указывающего о необходимости открытия замка int ACK_finger_FUN = 0; // результат выполнения функции библиотеки Adafruit_Fingerprint uint8_t VAR_mode_MENU = 0; // текущий режим вывода меню uint8_t VAR_count_ID = 0; // количество найденных ID в базе uint8_t VAR_first_ID = 0; // номер первого выведенного ID из всех найденных uint8_t VAR_this_ID = 0; // номер ID, шаблон которого требуется сохранить/удалить uint8_t VAR_array_ID[162]; // массив найденных ID в базе bool FLG_result_FUN = 0; // флаг указывающий на результат сохранения шаблона bool FLG_mode_ACCESS = 0; // флаг указывающий о необходимости открытия замка bool FLG_state_WORK = 1; // флаг указывающий о состоянии работы замка bool FLG_display_UPD = 1; // флаг указывающий о необходимости обновления информации на дисплее bool FLG_beep = 0; // флаг для зумера int LEN_open = 5000; // длительность открытия замка и сообщения int LEN_beep = 300; // длительность зумера void setup(){ pinMode(PIN_button_A, INPUT); // устанавливаем режим работы вывода PIN_button_A, как "вход" pinMode(PIN_button_B, INPUT); // устанавливаем режим работы вывода PIN_button_B, как "вход" pinMode(PIN_led_OPEN, OUTPUT); // устанавливаем режим работы вывода PIN_led_OPEN, как "выход" pinMode(PIN_led_CLOSED, OUTPUT); // устанавливаем режим работы вывода PIN_led_CLOSED, как "выход" pinMode(PIN_key_OPEN, OUTPUT); // устанавливаем режим работы вывода PIN_key_OPEN, как "выход" pinMode(PIN_beep_OPEN, OUTPUT); // устанавливаем режим работы вывода PIN_beep_OPEN, как "выход" lcd.init(); Serial.begin(9600); // инициируем LCD дисплей lcd.backlight(); // включаем подсветку LCD дисплея lcd.clear(); // стираем информацию с дисплея lcd.setCursor(0, 0); lcd.print(F("iArduino.ru")); // выводим текст "iArduino.ru" delay(500); // обязательная задержка перед инициализацией модуля отпечатков пальцев finger.begin(57600); // инициируем модуль отпечатков пальцев, с подключением через программный UART на скорости 57600 (скорость модуля по умолчанию) lcd.clear(); // стираем информацию с дисплея lcd.setCursor(0, 0); lcd.print(F("Scan sensor...")); // выводим текст "Scan sensor..." lcd.setCursor(0, 1); // устанавливаем курсор в позицию: 0 столбец, 1 строка if(finger.verifyPassword()){lcd.print(F("Found sensor"));} // если модуль отпечатков обнаружен, выводим сообщение "сенсор обнаружен" else {lcd.print(F("Sensor not found")); while(1);} // если модуль отпечатков не обнаружен, выводим сообщение "сенсор не обнаружен" и входим в бесконечный цикл: while(1); delay(1000); // необязательная задержка, чтоб можно было прочитать сообщение об обнаружении модуля } void loop(){ // Передаём управление кнопкам Func_buttons_control(); // вызываем функцию Func_buttons_control(); // Обновляем информацию на дисплее if(FLG_display_UPD){Func_display_show();} // если установлен флаг FLG_display_UPD (нужно обновить информацию на дисплее), то вызываем функцию Func_display_show(); // Общаемся с модулем отпечатков пальцев Func_sensor_communication(); // вызываем функцию Func_sensor_communication(); // Управляем замком, светодиодами и зуммером if(FLG_state_WORK){ // если установлен флаг FLG_state_WORK (замок работает, «State: ENABLE»), то ... digitalWrite(PIN_key_OPEN, FLG_mode_ACCESS); // если используется электромагнитный замок, где 0-открыто, а 1-закрыто, то второй параметр указывается с восклицательным знаком: digitalWrite(PIN_key_OPEN, !FLG_mode_ACCESS); digitalWrite(PIN_led_OPEN, FLG_mode_ACCESS); // включаем или выключаем светодиод подключённый к выводу PIN_led_OPEN digitalWrite(PIN_led_CLOSED, !FLG_mode_ACCESS); // включаем или выключаем светодиод подключённый к выводу PIN_led_CLOSED if(FLG_mode_ACCESS){ // если установлен флаг FLG_mode_ACCESS (замок открыт), то отправляем меандр на вывод PIN_beep_OPEN (к которому подключён Trema зуммер) длительностью 100 мс с частотой 2000 Гц if(FLG_beep){ tone(PIN_beep_OPEN, 2000, 300); FLG_beep = 0; } } } // Сбрасываем флаг FLG_mode_ACCESS, указывающий о необходимости открытия замка, через 5 секунд после его установки if(FLG_mode_ACCESS){ if( TIM_mode_ACCESS >millis()){FLG_mode_ACCESS=0; FLG_display_UPD = 1; if(VAR_mode_MENU==1){VAR_mode_MENU=0;}} if((TIM_mode_ACCESS+LEN_open)<millis()){FLG_mode_ACCESS=0; FLG_display_UPD = 1; if(VAR_mode_MENU==1){VAR_mode_MENU=0;}} } } // Функция управления кнопками: void Func_buttons_control(){ TIM_Button_A=0; // время удержания кнопки A (в сотых долях секунды) TIM_Button_B=0; // время удержания кнопки B (в сотых долях секунды) uint8_t f=0; // 1-зафиксировано нажатие кнопки, 2-зафиксировано нажатие кнопки и очищен дисплей, 3-обе кнопки удерживались дольше 2 сек if(digitalRead(PIN_button_A)){noTone(PIN_beep_OPEN);} // если нажата кнопка A то отключаем меандр на выводе PIN_beep_OPEN if(digitalRead(PIN_button_B)){noTone(PIN_beep_OPEN);} // если нажата кнопка B то отключаем меандр на выводе PIN_beep_OPEN while(digitalRead(PIN_button_A)||digitalRead(PIN_button_B)){ // если нажата кнопка A и/или кнопка B, то создаём цикл, пока кнопка(и) не будет(ут) отпущена(ы) if(digitalRead(PIN_button_A)&&TIM_Button_A<65535){TIM_Button_A++; if(f==0){f=1;}} // если удерживается кнопка A, то увеличиваем время её удержания if(digitalRead(PIN_button_B)&&TIM_Button_B<65535){TIM_Button_B++; if(f==0){f=1;}} // если удерживается кнопка B, то увеличиваем время её удержания if(f==1){lcd.clear(); f=2;} // если зафиксировано нажатие кнопки, то стираем информацию с дисплея if(f<3 && TIM_Button_A>200 && TIM_Button_B>200){VAR_mode_MENU=0; Func_display_show(); f=3;} // если обе кнопки удерживаются дольше 2 сек, то выходим из меню (VAR_mode_MENU=0) и обновляем информацию на дисплее (Func_display_show();) delay(10); // пропускаем 0,01с (подавляем дребезг кнопок) } if(f==2){ FLG_display_UPD = 1; // если зафиксировано нажатие на кнопку, то ... switch(VAR_mode_MENU){ case 0: /* вне меню */ if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 10; } // войти в меню if(TIM_Button_A >0 && TIM_Button_B==0){FLG_mode_ACCESS = 1; TIM_mode_ACCESS=millis(); FLG_beep = 1; } // открыть замок if(TIM_Button_A==0 && TIM_Button_B >0){FLG_mode_ACCESS = 1; TIM_mode_ACCESS=millis(); FLG_beep = 1; } // открыть замок break; case 10: /* 1 уровень меню - вкл/выкл замок */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_mode_MENU = 99; } // перейти к предыдущему пункту меню if(TIM_Button_A==0 && TIM_Button_B >0){VAR_mode_MENU = 20; } // перейти к следующему пункту меню if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 11; } // выбрать текущий пункт меню break; case 11: /* 2 уровень меню - вкл/выкл замок */ if(TIM_Button_A >0 && TIM_Button_B==0){FLG_state_WORK = FLG_state_WORK?0:1; } // вкл/выкл if(TIM_Button_A==0 && TIM_Button_B >0){FLG_state_WORK = FLG_state_WORK?0:1; } // вкл/выкл if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 10; } // выйти из текущего пункта меню break; case 20: /* 1 уровень меню - показать ID */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_mode_MENU = 10; } // перейти к предыдущему пункту меню if(TIM_Button_A==0 && TIM_Button_B >0){VAR_mode_MENU = 30; } // перейти к следующему пункту меню if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 21; } // выбрать текущий пункт меню break; // case 21: /* 2 уровень меню - показать ID */ это поиск ID с надписью Please wait ... // без реакции на нажатие кнопок // case 22: /* 3 уровень меню - показать ID */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_first_ID -= VAR_first_ID>0?3:0; } // уменьшить первый выводимый на дисплей ID if(TIM_Button_A==0 && TIM_Button_B >0){VAR_first_ID += ((VAR_first_ID+3)<VAR_count_ID)?3:0;} // увеличить первый выводимый на дисплей ID if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 20; } // выйти из текущего пункта меню break; case 30: /* 1 уровень меню - сохранить ID */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_mode_MENU = 20; } // перейти к предыдущему пункту меню if(TIM_Button_A==0 && TIM_Button_B >0){VAR_mode_MENU = 40; } // перейти к следующему пункту меню if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 31; } // выбрать текущий пункт меню break; // case 31: /* 2 уровень меню - сохранить ID */ это поиск ID с надписью Please wait ... // без реакции на нажатие кнопок // case 32: /* 3 уровень меню - сохранить ID */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_this_ID -= VAR_this_ID>0?1:0; } // уменьшить ID по которому будет сохранён отпечаток if(TIM_Button_A==0 && TIM_Button_B >0){VAR_this_ID += VAR_this_ID<162?1:0; } // увеличить ID по которому будет сохранён отпечаток if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 33; } // выбрать текущий пункт меню break; // case 33: /* 4 уровень меню - сохранить ID */ это ожидание прикладываемого пальца (в первый раз) с надписью Put finger ... // без реакции на нажатие кнопок // // case 34: /* 5 уровень меню - сохранить ID */ это ожидание конвертации изображения в шаблон (в первый раз) с надписью converting ... // без реакции на нажатие кнопок // // case 35: /* 6 уровень меню - сохранить ID */ это ожидание отсутствия пальца перед сканером с надписью remove finger ... // без реакции на нажатие кнопок // // case 36: /* 7 уровень меню - сохранить ID */ это ожидание прикладываемого пальца (во второй раз) с надписью Put finger ... // без реакции на нажатие кнопок // // case 37: /* 8 уровень меню - сохранить ID */ это ожидание конвертации изображения в шаблон (во второй раз) с надписью converting ... // без реакции на нажатие кнопок // // case 38: /* 9 уровень меню - сохранить ID */ это ожидание создания и сохранения шаблона с надписью Saved ... // без реакции на нажатие кнопок // case 39: /*10 уровень меню - сохранить ID */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_mode_MENU = 30; } // в начало текущего пункта меню if(TIM_Button_A==0 && TIM_Button_B >0){VAR_mode_MENU = 30; } // в начало текущего пункта меню if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 30; } // в начало текущего пункта меню break; case 40: /* 1 уровень меню - удалить ID */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_mode_MENU = 30; } // перейти к предыдущему пункту меню if(TIM_Button_A==0 && TIM_Button_B >0){VAR_mode_MENU = 99; } // перейти к следующему пункту меню if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 41; VAR_this_ID=0; } // выбрать текущий пункт меню break; case 41: /* 2 уровень меню - удалить ID */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_this_ID = VAR_this_ID==255?0:255; } // 255-all / 0-one if(TIM_Button_A==0 && TIM_Button_B >0){VAR_this_ID = VAR_this_ID==255?0:255; } // 255-all / 0-one if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = VAR_this_ID==255?42:43; } // выбрать текущий пункт меню break; // case 42: /* 3 уровень меню - удалить ID */ это удаление всех ID с надписью Please wait ... // без реакции на нажатие кнопок // // case 43: /* 4 уровень меню - удалить ID */ это поиск ID с надписью Please wait ... // без реакции на нажатие кнопок case 44: /* 5 уровень меню - удалить ID */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_this_ID -= VAR_this_ID>0?1:0; } // уменьшить ID по которому будет удалён отпечаток if(TIM_Button_A==0 && TIM_Button_B >0){VAR_this_ID += VAR_this_ID<162?1:0; } // увеличить ID по которому будет удалён отпечаток if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 45; } // выбрать текущий пункт меню break; // case 45: /* 6 уровень меню - удалить ID */ это удаление выбранного ID с надписью Please wait ... // без реакции на нажатие кнопок // case 99: /* 1 уровень меню - выйти из меню */ if(TIM_Button_A >0 && TIM_Button_B==0){VAR_mode_MENU = 40; } // перейти к предыдущему пункту меню if(TIM_Button_A==0 && TIM_Button_B >0){VAR_mode_MENU = 10; } // перейти к следующему пункту меню if(TIM_Button_A >0 && TIM_Button_B >0){VAR_mode_MENU = 0; } // выбрать текущий пункт меню break; } } } // функция вывода информации на дисплей void Func_display_show(){ FLG_display_UPD=0; lcd.clear(); switch(VAR_mode_MENU){ case 0: /* вне меню */ lcd.setCursor(0, 0); lcd.print(F("State: ")); lcd.print(FLG_state_WORK ? F("ENABLE") : F("DISABLE") ); lcd.setCursor(0, 1); lcd.print(F("Access: ")); lcd.print(FLG_mode_ACCESS ? F("OPENED") : F("CLOSED" ) ); break; case 1: /* замок открыт отпечатком */ lcd.setCursor(0, 0); lcd.print(F("Found ID: ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(F("Access: OPENED") ); Serial.print("parametr "); Serial.println(VAR_this_ID); break; case 10: /* 1 уровень меню - вкл/выкл замок */ lcd.setCursor(0, 0); lcd.print(F("Menu > Set state")); break; case 11: /* 2 уровень меню - вкл/выкл замок */ lcd.setCursor(0, 0); lcd.print(F("State:")); lcd.setCursor(9, 0); lcd.print(F("ENABLE")); lcd.setCursor(9, 1); lcd.print(F("DISABLE")); lcd.setCursor(7, FLG_state_WORK?0:1); lcd.print(F(">")); break; case 20: /* 1 уровень меню - показать ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > Show ID")); break; case 21: /* 2 уровень меню - показать ID */ lcd.setCursor(0, 0); lcd.print(F("Scan sensor ...")); lcd.setCursor(0, 1); lcd.print(F("Please wait ...")); break; case 22: /* 3 уровень меню - показать ID */ lcd.setCursor(0, 0); lcd.print(F("Found ")); lcd.print(VAR_count_ID); lcd.print(F(" ID:")); lcd.setCursor(0, 1); lcd.print(VAR_first_ID==0?F(" "):F("<")); for(int i=0; i<3; i++){if(VAR_count_ID>VAR_first_ID+i){lcd.print(i==0?F(" "):F(",")); if(VAR_array_ID[(VAR_first_ID+i)]<100){lcd.print(F("0"));} if(VAR_array_ID[(VAR_first_ID+i)]<10){lcd.print(F("0"));} lcd.print(VAR_array_ID[(VAR_first_ID+i)]);}} if(VAR_count_ID>(VAR_first_ID+3)){lcd.setCursor(15,1); lcd.print(F(">"));} break; case 30: /* 1 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > New ID")); break; case 31: /* 2 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Scan sensor ...")); lcd.setCursor(0, 1); lcd.print(F("Please wait ...")); break; case 32: /* 3 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu>NewID>SetID")); lcd.setCursor(6, 1); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(4, 1); if(VAR_this_ID>0 ){lcd.print(F("<"));} lcd.setCursor(10,1); if(VAR_this_ID<162){lcd.print(F(">"));} break; case 33: /* 4 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > NewID ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(F("Put finger ...")); break; case 34: /* 5 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > NewID ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(F("Converting ...")); break; case 35: /* 6 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > NewID ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(F("Remove finger ...")); break; case 36: /* 7 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > NewID ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(F("Put finger ...")); break; case 37: /* 8 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > NewID ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(F("Converting ...")); break; case 38: /* 9 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > NewID ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(F("Saved ...")); break; case 39: /*10 уровень меню - сохранить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > NewID ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(FLG_result_FUN?F("Finger saving!"):F("ERROR :(")); break; case 40: /* 1 уровень меню - удалить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu > Del ID")); break; case 41: /* 2 уровень меню - удалить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu>DelID")); lcd.setCursor(13,0); lcd.print(F("All")); lcd.setCursor(13,1); lcd.print(F("One")); lcd.setCursor(11, VAR_this_ID==255?0:1); lcd.print(F(">")); break; case 42: /* 3 уровень меню - удалить ID */ lcd.setCursor(0, 0); lcd.print(F("Deleted ID ...")); lcd.setCursor(0, 1); lcd.print(F("Please wait ...")); break; case 43: /* 4 уровень меню - удалить ID */ lcd.setCursor(0, 0); lcd.print(F("Scan sensor ...")); lcd.setCursor(0, 1); lcd.print(F("Please wait ...")); break; case 44: /* 5 уровень меню - удалить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu>Del One ID")); lcd.setCursor(6, 1); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(4, 1); if(VAR_this_ID>0 ){lcd.print(F("<"));} lcd.setCursor(10,1); if(VAR_this_ID<162){lcd.print(F(">"));} break; case 45: /* 6 уровень меню - удалить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu>Del ID ")); if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(F("Please wait ...")); break; case 49: /*10 уровень меню - удалить ID */ lcd.setCursor(0, 0); lcd.print(F("Menu> Del ")); if(VAR_this_ID==255){lcd.print(F("All ID"));}else{lcd.print(F("ID "));} if(VAR_this_ID<100){lcd.print(F("0"));} if(VAR_this_ID<10){lcd.print(F("0"));} lcd.print(VAR_this_ID); lcd.setCursor(0, 1); lcd.print(FLG_result_FUN?F("Finger deleted!"):F("ERROR :(")); break; case 99: /* 1 уровень меню - выйти из меню */ lcd.setCursor(0, 0); lcd.print(F("Menu > Exit")); break; } } // функция общения с модулем отпечатков пальцев void Func_sensor_communication(){ switch(VAR_mode_MENU){ // Если требуется сверять отпечатки пальцев case 0: if(!FLG_mode_ACCESS){ if(millis()%500 == 0){ // проверяем каждые 500 мс if(finger.getImage() == FINGERPRINT_OK){ // Захватываем изображение, если результат выполнения равен константе FINGERPRINT_OK (корректная загрузка изображения), то проходим дальше if(finger.image2Tz() == FINGERPRINT_OK){ // Конвертируем полученное изображение, если результат выполнения равен константе FINGERPRINT_OK (изображение сконвертировано), то проходим дальше if(finger.fingerFastSearch() == FINGERPRINT_OK){ // Находим соответствие в базе данных отпечатков пальцев, если результат выполнения равен константе FINGERPRINT_OK (найдено соответствие), то проходим дальше VAR_this_ID = finger.fingerID; VAR_mode_MENU = 1; FLG_display_UPD = 1; FLG_mode_ACCESS = 1; FLG_beep = 1; TIM_mode_ACCESS = millis(); }else{finger.fingerFastSearch();}}}}} // Эта строка не являеется обработчиком отсутствия соответствий в базе данных отпечатков пальцев! Просто без неё в некоторых версиях Arduino IDE компилятор не сможет корректно подставить результат функции fingerFastSearch() для сравнения с константой FINGERPRINT_OK в условии оператора if(). break; // Если требуется найти все ID сохранённые в базе отпечатков case 21: VAR_first_ID=0; VAR_count_ID=0; // сбрасываем количество найденных Id и номер первого среди отображенных for(uint8_t id = 0; id<162; id++){ // проход по 162 идентификаторам if(finger.loadModel(id) == FINGERPRINT_OK){ // если по указанному идентификатору найден шаблон отпечатка, то ... VAR_array_ID[VAR_count_ID]=id; VAR_count_ID++; // сохраняем номер идентификатора и увеличиваем количество найденных ID } } VAR_mode_MENU=22; FLG_display_UPD=1; // переходим в 3 уровень меню ( показать ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее break; // Если требуется найти первый свободный ID в базе отпечатков для его изменения на требуемый для сохранения case 31: VAR_this_ID=161; // устанавливаем номер 161 для первого свободного ID в базе отпечатков for(uint8_t id = 0; id<162; id++){ // проход по 162 идентификаторам if(VAR_this_ID==161){if(finger.loadModel(id)!=FINGERPRINT_OK){VAR_this_ID=id;}} // если по указанному идентификатору не найден шаблон отпечатка, то сохраняем его } VAR_mode_MENU=32; FLG_display_UPD=1; // переходим в 3 уровень меню ( сохранить ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее break; // Если требуется определить наличие пальца на сканере и сосканировать его case 33: ACK_finger_FUN=finger.getImage(); // захватываем изображение, с сохранением результата в переменную ACK_finger_FUN if(ACK_finger_FUN==FINGERPRINT_OK){ // изображение отпечатка пальца корректно загрузилось VAR_mode_MENU=34; FLG_display_UPD=1; // переходим в 5 уровень меню ( сохранить ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее }else if(ACK_finger_FUN==FINGERPRINT_NOFINGER){ // сканер не обнаружил отпечаток пальца, остаёмся в текущем уровне }else{FLG_result_FUN=0; VAR_mode_MENU=39; FLG_display_UPD=1;} // переходим в 10 уровень меню ( результат сохранения ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее break; // Если требуется сконвертировать первое полученное отсканированное изображение case 34: FLG_result_FUN=0; FLG_display_UPD=1; // устанавливаем флаг указывающий о необходимости обновить информацию на дисплее VAR_mode_MENU=finger.image2Tz(1)==FINGERPRINT_OK?35:39; // конвертируем первое изображение, если результат выполнения данной операции == FINGERPRINT_OK, значит изображение сконвертированно break; // Если требуется зафиксировать отсутствие пальца на сканере case 35: delay(500); VAR_mode_MENU=36; FLG_display_UPD=1; // ждём 0.5 сек и пытаемся перейти в 7 уровень меню ( сохранить ID ) while(finger.getImage() != FINGERPRINT_NOFINGER){;} // не выходим из цикла, пока палец не перестанет сканироваться break; // Если требуется определить наличие пальца на сканере и сосканировать его case 36: ACK_finger_FUN=finger.getImage(); // захватываем изображение, с сохранением результата в переменную ACK_finger_FUN if(ACK_finger_FUN==FINGERPRINT_OK){ // изображение отпечатка пальца корректно загрузилось VAR_mode_MENU=37; FLG_display_UPD=1; // переходим в 8 уровень меню ( сохранить ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее }else if(ACK_finger_FUN==FINGERPRINT_NOFINGER){ // сканер не обнаружил отпечаток пальца, остаёмся в текущем уровне }else{FLG_result_FUN=0; VAR_mode_MENU=39; FLG_display_UPD=1;} // переходим в 10 уровень меню ( результат сохранения ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее break; // Если требуется сконвертировать второе полученное отсканированное изображение case 37: FLG_result_FUN=0; FLG_display_UPD=1; // устанавливаем флаг указывающий о необходимости обновить информацию на дисплее VAR_mode_MENU=finger.image2Tz(2)==FINGERPRINT_OK?38:39; // Конвертируем второе изображение, если результат выполнения данной операции == FINGERPRINT_OK, значит изображение сконвертированно break; // Если требуется создать и сохранить шаблон из первых двух отсканированных изображений case 38: FLG_result_FUN=0; VAR_mode_MENU=39; FLG_display_UPD=1; // пытаемся перейти в 10 уровень меню ( результат сохранения ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее if(finger.createModel()==FINGERPRINT_OK){ // создаем модель (шаблон) отпечатка пальца, по двум изображениям if(finger.storeModel(VAR_this_ID)==FINGERPRINT_OK){ // сохраняем модель (шаблон) отпечатка пальца, по двум изображениям FLG_result_FUN=1; // если создание и сохранение модель (шаблона) выполнено успешно, то устанавливаем флаг указывающий об успешном сохранении }else{finger.storeModel(VAR_this_ID);} // Эта строка не является обработкой ошибки сохранения модели (шаблона) отпечатка пальца! Просто без неё в некоторых версиях Arduino IDE компилятор не сможет корректно подставить результат функции storeModel() для сравнения с константой FINGERPRINT_OK в условии оператора if(). }else{finger.createModel();} // Эта строка не является обработкой ошибки создания модели (шаблона) отпечатка пальца! Просто без неё в некоторых версиях Arduino IDE компилятор не сможет корректно подставить результат функции createModel() для сравнения с константой FINGERPRINT_OK в условии оператора if(). break; case 39: delay(2000); FLG_result_FUN=0; VAR_mode_MENU=30; FLG_display_UPD=1; // выходим из меню через 2 сек break; // Если требуется удалить все ID из базы отпечатков case 42: FLG_result_FUN=1; VAR_mode_MENU=49; FLG_display_UPD=1; // устанавливаем результат и пытаемся перейти в 10 уровень меню ( результат удаления ID ) установив флаг указывающий о необходимости обновить информацию на дисплее for(uint8_t id = 0; id<162; id++){ // проход по 162 идентификаторам if(finger.loadModel(id) == FINGERPRINT_OK){ // если по указанному идентификатору найден шаблон отпечатка, то ... if(finger.deleteModel(id)!=FINGERPRINT_OK){FLG_result_FUN=1;} // если шаблон не удаляется, сбрасываем результат } } break; // Если требуется найти последний занятый ID в базе отпечатков для его изменения на требуемый для сохранения case 43: VAR_this_ID=0; // сбрасываем номер последнего занятого ID в базе отпечатков for(uint8_t id = 0; id<162; id++){ // проход по 162 идентификаторам if(finger.loadModel(id)==FINGERPRINT_OK){VAR_this_ID=id;} // если по указанному идентификатору найден шаблон отпечатка, то сохраняем его } VAR_mode_MENU=44; FLG_display_UPD=1; // переходим в 5 уровень меню ( удалить ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее break; // Если требуется удалить выбранный ID из базы отпечатков case 45: FLG_result_FUN=0; VAR_mode_MENU=49; FLG_display_UPD=1; // пытаемся перейти в 10 уровень меню ( результат удаления ID ) и устанавливаем флаг указывающий о необходимости обновить информацию на дисплее if(finger.deleteModel(VAR_this_ID)==FINGERPRINT_OK){FLG_result_FUN=1;} // Удаляем шаблон отпечатка пальца VAR_this_ID из базы данных, при успешном выполнении, устанавливаем флаг FLG_result_FUN break; case 49: delay(2000); FLG_result_FUN=0; VAR_mode_MENU=40; FLG_display_UPD=1; // выходим из меню через 2 сек break; default: break; } } |
В MajorDoMo надо установить модуль MQTT и Telegramm и настроить их. Для учета пользователей я создал класс dostup со свойствами id и name
Создал объекты: setting для хранения настроек и объекты для работников rab1, rab2 ...
в setting добавил свойства:
эти настройки используются для отчетов в Telegramm, так же в setting добавляем метод write.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
$id_user = $params['VALUE']; // получаем значение от MQTT номер ячейки отпечатка пальца // сопоставляем номер ячейки с номером пользователя, у меня 3 пользователя, каждому пользователю задано 3 ячейки для отпечатков switch ($id_user) { case 1: $users = 1; break; case 2: $users = 1; break; case 3: $users = 1; break; case 4: $users = 2; break; case 5: $users = 2; break; case 6: $users = 2; break; case 7: $users = 3; break; case 8: $users = 3; break; case 9: $users = 3; break; } $userid = "rab".$users.".id"; $username = "rab".$users.".name"; $ret = gethistorycount($userid,strtotime("00:00")); $ret2 = gethistorycount($userid,strtotime("-2 minute")); // интервал при котором не фиксируется повторное нажатие $user = "202343323"; // ID пользователя в Telegramm include_once(DIR_MODULES . 'telegram/telegram.class.php'); $telegram_module = new telegram(); $text = "зафиксирован вход, ".gg($username); // сообщение о входе $text2 = "зафиксирован выход, ".gg($username); // сообщение о выходе if ($ret === false) { if ($ret2 === false) { sg($userid,1); $telegram_module->sendMessageToUser($user, $text); } } else { if ($ret2 === false) { sg($userid,0); $data = getHistory($userid, strtotime("00:00")); $data1 = array_column($data, 'ADDED'); $period = time() - strtotime($data1[0]) - 3600; $period1 = timeNow($period); $telegram_module->sendMessageToUser($user, $text2.", время работы: ".$period1); } } |
при входе в свойство id пользователя записывается единица, при выходе ноль, считается что пользователь не работает в полночь, а это значит первая отметка за сутки это всегда вход, то есть записывается единица, вторая и последующая отметка это всегда выход. При этом сообщения в телеграмм отсылаются при всех выходах, повторное нажатие не фиксируется в течении 2 минут.
Теперь нам надо в модуле MQTT найти свойство которое передает микроконтроллер и отредактировать его добавив выполнение метода
теперь будет выполняться наш метод и отсылать сообщения в телеграмм.
Теперь нам надо сделать отчеты через Telegramm. Делаем кнопку Отчеты и добавляем обработку messageHook следующего содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
// форматирование дат $format = 'Y-m-d'; $format2 = 'd.m.Y'; // ввод начальной даты отчетного периода if (gg('setting.udata1')){ sg('setting.data1',$text); sg('setting.udata1', 0); $status = "ОК"; $menu_6 = 1; } // ввод конечной даты отчетного периода if (gg('setting.udata2')){ sg('setting.data2',$text); sg('setting.udata2', 0); $status = "ОК"; $menu_6 = 1; } // ввод пользователя для отчета if (gg('setting.uid')){ sg('setting.idtek',$text); sg('setting.uid', 0); $status = "ОК"; $menu_6 = 1; } // получение свойств $userid = "rab".gg('setting.idtek').".id"; // формирование имени свойства текущий пользователь $username = "rab".gg('setting.idtek').".name"; // формирование имени свойства имя текущего пользователя $username2 = gg($username); // получение данных имя текущего пользователя $s_data1 = gg("setting.data1"); // дата начального периода $s_data2 = gg("setting.data2"); // дата конечного периода $s_text = "Отчет с ".$s_data1." по ".$s_data2." для ".$username2; // формирование название кнопки //меню отчеты $option4 = array(array($s_text),array("Неделя", "тек.месяц", "пред.месяц"),array("Начало", "Конец", "Работник"),array("Назад")); // обработка сообщений switch ($text) { case "Отчеты": $status = "Выберите"; $menu_6 = 1; break; case $s_text: $menu_7 = 1; $pp_start = $s_data1; $pp_stop = $s_data2; break; case "Начало": $status = "введите дату начала периода в формате ДД.ММ.ГГГГ (например 14.04.2017)"; sg('setting.udata1', 1); $menu_6 = 1; break; case "Конец": $status = "введите дату окончания периода в формате ДД.ММ.ГГГГ (например 14.04.2017)"; sg('setting.udata2', 1); $menu_6 = 1; break; case "Работник": $status = "выберите работника (введите номер работника):\r\n"; for ($i = 1; $i <= 100; $i++) { $username = "rab".$i.".name"; if (gg($username)) { $status.= $i.". ".gg($username)."\r\n"; } } sg('setting.uid', 1); $menu_6 = 1; break; case "Неделя": $menu_7 = 1; $pp_start1 = DateTime::createFromFormat('U', strtotime("-7 day")); $pp_stop1 = DateTime::createFromFormat('U', time()); $pp_start = $pp_start1->format($format2); $pp_stop = $pp_stop1->format($format2); break; case "тек.месяц": $menu_7 = 1; $pp_stop1 = DateTime::createFromFormat('U', time()); $pp_stop = $pp_stop1->format($format2); $pp_start = substr_replace($pp_stop,'01', 0, 2); break; case "пред.месяц": $menu_7 = 1; $pp_stop1 = DateTime::createFromFormat('U', time()); $pp_tek = $pp_stop1->format($format2); $tek_m = substr ($pp_tek, 3, 2); $tek_y = substr ($pp_tek, 6, 4); switch ($tek_m) { case "01": $mes = "12"; $tek_y = $tek_y - 1; break; case "02": $mes = "01"; break; case "03": $mes = "02"; break; case "04": $mes = "03"; break; case "05": $mes = "04"; break; case "06": $mes = "05"; break; case "07": $mes = "06"; break; case "08": $mes = "07"; break; case "09": $mes = "08"; break; case "10": $mes = "09"; break; case "11": $mes = "10"; break; case "12": $mes = "11"; break; } $pp_start = "01.".$mes.".".$tek_y; $pp_stop = cal_days_in_month(CAL_GREGORIAN, $mes, $tek_y).".".$mes.".".$tek_y; break; } // показать меню if ($menu_6 == 1) { $this->sendMessageToUser($chat_id,$status,$option4); $skip = true; } //Вывести отчет if ($menu_7 == 1) { // получение интервала дат $rabota = "Отчет с ".$pp_start." до ".$pp_stop." для ".$username2; $rabota2 = 0; $array_date = array(); $interval_date = new DateInterval('P1D'); $realEnd = new DateTime($pp_stop); $realEnd->add($interval_date); $period = new DatePeriod(new DateTime($pp_start), $interval_date, $realEnd); foreach($period as $date) { $date_1 = $date->format($format)." 00:00:00"; $date_2 = $date->format($format)." 23:59:59"; $ret = gethistorycount($userid,strtotime($date_1),strtotime($date_2)); $data = getHistory($userid, strtotime($date_1),strtotime($date_2)); $data1 = array_column($data, 'ADDED'); $start_tr = date_format(date_create($data1[0]), "H:i"); $end_tr = date_format(date_create($data1[$ret - 1]), "H:i"); $per_tri = strtotime($end_tr) - strtotime($start_tr); $per_tr = timeNow($per_tri-3600); if ($ret) {array_push ($array_date, ['date' => $date->format($format2), 'count' => $ret, 'start' => $start_tr, 'end' => $end_tr, 'period' => $per_tr, 'periodi' => $per_tri]);}; } foreach($array_date as $date) { $rabota.="\r\n".$date['date']." - ".$date['start']."-".$date['end']." - ".$date['period']; $rabota2 = $rabota2 + $date['periodi']; } $rabota.="\r\n"."Общее время работы: ".timeNow($rabota2-3600); $this->sendMessageToUser($chat_id, $rabota, $option4); $skip = true; } |
в результате мы получим вот такое меню:
периоды и работник меняются кнопками: Начало, Конец и Работник соответственно
Для отчета выбираем соответствующий отчет
немного модернизировав код можно делать округления времени, фиксировать опоздание или ранний уход с работы.