Счётчик человеко-часов

AC520

✩✩✩✩✩✩✩
16 Янв 2023
11
5
Понадобился мне счётчик человеко-часов.
Для чего? Вот есть сложное техническое устройство. В один момент над ним могут трудиться до 4х специалистов. Как посчитать стоимость работы? Возле каждого человека с секундомером не приставишь. А им тоже есть чем заняться, чтобы какой-то журнал вести. Итак:
1. Ардуино нано.
2. Модуль реального времени.
3. Модуль SD карты памяти.
4. TFT дисплей 240х320.
5. Модуль питания.
5. АКБ от старого бесперебойника.
Идея простая.
Рядом с объектом стоит коробка с четырьмя обычными выключателями. Специалист подходит - включает любой свободный (если не нужно считать отдельно время каждого) или только "свой номер". Какой свободный - видно на экране, горит зелёным. На фото роль выключателей играют 4 чёрных проводка на макетной плате, обведённых жёлтым.
Специалист отвлекается на другую работу, уходит на обед или в любой другой момент, когда перестаёт работать над этим объектом - выключает любой включенный (на экране отмеченный красным), или "свой номер". В этот момент на карту памяти записывается событие: время начала, время окончания, количество секунд сессии.
Данные с карты памяти легко импортируются в любой табличный редактор и анализируются.
Надо понимать, что данное изделие не обладает контрольно-надзорным функционалом и никак не защищено от злоупотреблений. Его задача избавить инженерный персонал от учётных процедур, а так же помогать в пересмотре норм на технические операции.

LabourLogger:
/*
   Устройство для фиксации времени работы над объектом для 4х сотрудников
   Состоит: нано 328, tft 240x320, модуль реального времени, кардридер, 4 тумблера, модуль питания
   При включении любого тумблера начинается отсчёт времени работы для сотрудника. Время начала выводится на экран.
   При выключении тумблера время окончания фиксируется на экране. Выводится количество секунд сессии.
   На карту памяти записывается лог: сотрудник, дата, время начала сессии, время окончания сессии, количество секунд сессии.
   Дальнейший анализ данных проводится путём импорта текстового файла в табличный редактор (Microsoft Excel, например).
*/

#include <microDS3231.h>
MicroDS3231 rtc;

#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#include <SD.h>

#define TFT_CS        10 //D10
#define TFT_RST        9 //D9
#define TFT_DC         8 //D8
#define TFT_MOSI 11  // D11
#define TFT_SCLK 13  // D13

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

#define black    0x0000
#define blue     0x001F
#define red      0xF800
#define green    0x07E0
#define cyan     0x07FF
#define magenta  0xF81F
#define yellow   0xFFE0
#define white    0xFFFF
#define maroon   0x7800

const int chipSelect = 7; //номер ноги для кардридера
int rab01[2][3]; //Массив данных о начале работы и окончании на каждого сотрудника 2х3 : начало-конец, час-минута-секунда
int rab02[2][3];
int rab03[2][3];
int rab04[2][3];
long sec01; // количество секунд работы каждой сессии для каждого сотрудника
long sec02;
long sec03;
long sec04;
bool fl01 = true; // флаги для однократного выполнения операций
bool fl02 = true;
bool fl03 = true;
bool fl04 = true;
String dataString = "";

void setup() {
  tft.init(240, 320);           // Init ST7789 320x240
  tft.setRotation(1);

  tft.fillScreen(0);
  tft.setTextSize(2);
  if (!SD.begin(chipSelect)) {
    tft.println("Card failed, or not present");
    while (1);
  }
  tft.println("Card initialized.");
  delay(2000);
  tft.fillScreen(0);

  pinMode(2, INPUT_PULLUP); // ноги для тумблеров
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);

  tft.drawLine(0, 0, 319, 0, yellow); //сетка на экране
  tft.drawLine(0, 59, 319, 59, yellow);
  tft.drawLine(0, 119, 319, 119, yellow);
  tft.drawLine(0, 179, 319, 179, yellow);
  tft.drawLine(0, 239, 319, 239, yellow);

}
void loop() {

  DateTime now = rtc.getTime();

  /*
     Замыкаем тумблеры на землю, поэтому невключенный тумблер выдаёт на ногу МК 1 (true).
     Если тумблер включен и флаг выполнения true, тогда выполняем условие:
     заполняем первую строку двустрочного массива данных значениями часов, минут и секунд из модуля реального времени;
     синий фон (служит для "очистки" секции экрана); выводим время начала сессии; рисуем красный прямоугольник - как сигнал
     идущего процесса. Переводим флаг в false, чтобы не выполнять больше это условие до окончания сессии.
  */

  if (!digitalRead(2) and fl01) {
    rab01[0][0] = now.hour;
    rab01[0][1] = now.minute;
    rab01[0][2] = now.second;
    fl01 = false;
    tft.fillRect(1, 1, 319, 58, blue);
    tft.setTextSize(3);
    tft.setCursor(5, 3);
    tft.print(rtc.getTimeString());
    tft.fillRect(2, 39, 72, 18, red);
    tft.setTextSize(2);
    tft.setCursor(80, 39);
    tft.print("A&P #1:(sec)  ");
  }
  if (!digitalRead(3) and fl02) {
    rab02[0][0] = now.hour;
    rab02[0][1] = now.minute;
    rab02[0][2] = now.second;
    fl02 = false;
    tft.fillRect(0, 60, 319, 58, blue);
    tft.setTextSize(3);
    tft.setCursor(5, 62);
    tft.print(rtc.getTimeString());
    tft.fillRect(2, 98, 72, 18, red);
    tft.setTextSize(2);
    tft.setCursor(80, 98);
    tft.print("A&P #2:(sec)  ");
  }
  if (!digitalRead(4) and fl03) {
    rab03[0][0] = now.hour;
    rab03[0][1] = now.minute;
    rab03[0][2] = now.second;
    fl03 = false;
    tft.fillRect(0, 120, 319, 58, blue);
    tft.setTextSize(3);
    tft.setCursor(5, 121);
    tft.print(rtc.getTimeString());
    tft.fillRect(2, 157, 72, 18, red);
    tft.setTextSize(2);
    tft.setCursor(80, 157);
    tft.print("A&P #3:(sec)  ");
  }
  if (!digitalRead(5) and fl04) {
    rab04[0][0] = now.hour;
    rab04[0][1] = now.minute;
    rab04[0][2] = now.second;
    fl04 = false;
    tft.fillRect(0, 180, 319, 58, blue);
    tft.setTextSize(3);
    tft.setCursor(5, 180);
    tft.print(rtc.getTimeString());
    tft.fillRect(2, 216, 72, 18, red);
    tft.setTextSize(2);
    tft.setCursor(80, 216);
    tft.print("A&P #4:(sec)  ");
  }

  /*
     При выключении тумблера (появления true на ноге МК) и флага в состоянии false выполняем условие:
     заполняем вторую строку массива данных значениями часов, минут, секунд из модуля реального времени;
     вычисляем количество секунд сессии; переводим флаг в состояние true, для возможности повторного запуска следующей сессии;
     выводим на экран время окончания сессии, количество секунд сессии, рисуем зелёный прямоугольник - как сигнал завершённой сессии;
     на карту памяти записываем сведения: сотрудник, время начала сессии, время окончания, количество секунд всех сессий, дата.
  */
  if (digitalRead(2) and !fl01) {
    rab01[1][0] = now.hour;
    rab01[1][1] = now.minute;
    rab01[1][2] = now.second;
    sec01 = sec01 + (rab01[1][0] - rab01[0][0]) * 3600 + (rab01[1][1] - rab01[0][1]) * 60 + (rab01[1][2] - rab01[0][2]);
    fl01 = true;
    tft.setTextSize(3);
    tft.setCursor(170, 3);
    tft.print(rtc.getTimeString());
    tft.fillRect(2, 39, 72, 18, green);
    tft.setTextSize(2);
    tft.setCursor(80, 39);
    tft.print("A&P #1:(sec)  ");
    tft.print(sec01);
    File dataFile = SD.open("datalog.txt", FILE_WRITE);
    dataString = "A&P #1, " + String(rab01[0][0]) + ":" + String(rab01[0][1]) + ":" + String(rab01[0][2]) + "," + String(rab01[1][0]) + ":" + String(rab01[1][1]) + ":" + String(rab01[1][2]) + "," + String(sec01);
    dataFile.println(dataString);
    dataFile.close();
    sec01 = 0;
  }
  if (digitalRead(3) and !fl02) {
    rab02[1][0] = now.hour;
    rab02[1][1] = now.minute;
    rab02[1][2] = now.second;
    sec02 = sec02 + (rab02[1][0] - rab02[0][0]) * 3600 + (rab02[1][1] - rab02[0][1]) * 60 + (rab02[1][2] - rab02[0][2]);
    fl02 = true;
    tft.setTextSize(3);
    tft.setCursor(170, 62);
    tft.print(rtc.getTimeString());
    tft.fillRect(2, 98, 72, 18, green);
    tft.setTextSize(2);
    tft.setCursor(80, 98);
    tft.print("A&P #2:(sec)  ");
    tft.print(sec02);
    File dataFile = SD.open("datalog.txt", FILE_WRITE);
    dataString = "A&P #2, " + String(rab02[0][0]) + ":" + String(rab02[0][1]) + ":" + String(rab02[0][2]) + "," + String(rab02[1][0]) + ":" + String(rab02[1][1]) + ":" + String(rab02[1][2]) + "," + String(sec02);
    dataFile.println(dataString);
    dataFile.close();
    sec02 = 0;
  }
  if (digitalRead(4) and !fl03) {
    rab03[1][0] = now.hour;
    rab03[1][1] = now.minute;
    rab03[1][2] = now.second;
    sec03 = sec03 + (rab03[1][0] - rab03[0][0]) * 3600 + (rab03[1][1] - rab03[0][1]) * 60 + (rab03[1][2] - rab03[0][2]);
    fl03 = true;
    tft.setTextSize(3);
    tft.setCursor(170, 121);
    tft.print(rtc.getTimeString());
    tft.fillRect(2, 157, 72, 18, green);
    tft.setTextSize(2);
    tft.setCursor(80, 157);
    tft.print("A&P #3:(sec)  ");
    tft.print(sec03);
    File dataFile = SD.open("datalog.txt", FILE_WRITE);
    dataString = "A&P #3, " + String(rab03[0][0]) + ":" + String(rab03[0][1]) + ":" + String(rab03[0][2]) + "," + String(rab03[1][0]) + ":" + String(rab03[1][1]) + ":" + String(rab03[1][2]) + "," + String(sec03);
    dataFile.println(dataString);
    dataFile.close();
    sec03 = 0;
  }
  if (digitalRead(5) and !fl04) {
    rab04[1][0] = now.hour;
    rab04[1][1] = now.minute;
    rab04[1][2] = now.second;
    sec04 = sec04 + (rab04[1][0] - rab04[0][0]) * 3600 + (rab04[1][1] - rab04[0][1]) * 60 + (rab04[1][2] - rab04[0][2]);
    fl04 = true;
    tft.setTextSize(3);
    tft.setCursor(170, 180);
    tft.print(rtc.getTimeString());
    tft.fillRect(2, 216, 72, 18, green);
    tft.setTextSize(2);
    tft.setCursor(80, 216);
    tft.print("A&P #4:(sec)  ");
    tft.print(sec04);
    File dataFile = SD.open("datalog.txt", FILE_WRITE);
    dataString = "A&P #4, " + String(rab04[0][0]) + ":" + String(rab04[0][1]) + ":" + String(rab04[0][2]) + "," + String(rab04[1][0]) + ":" + String(rab04[1][1]) + ":" + String(rab04[1][2]) + "," + String(sec04);
    dataFile.println(dataString);
    dataFile.close();
    sec04 = 0;
  }
  delay(1000);
}
 

Вложения

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,265
944
58
Марий-Эл
@AC520, Я же говорил снесут.
А по теме.
Не нужно никаких кнопок.
Нужно RFID метки в виде брелков и карточек.
Человек подходит перед работой, регистрируется.
Уходит с работы, регистрируется.
По первичной и вторичной регистрации вычисляется время работы.
Мне кажется, так удобнее.
 
  • Лойс +1
Реакции: Stamp и Divin

AC520

✩✩✩✩✩✩✩
16 Янв 2023
11
5
Нужно RFID метки в виде брелков и карточек.
Человек подходит перед работой, регистрируется.
Уходит с работы, регистрируется.
чем, в моём случае, отличается rfid от тумблера? в этой задаче мне не важна персонализация рабочего времени. мне важен общий итог. давай назовём его "калькулятор человеко-часов".
 

Эдуард Анисимов

★★★★★★✩
23 Сен 2019
2,265
944
58
Марий-Эл
чем, в моём случае, отличается rfid от тумблера?
Он имеет уникальный номер. Поэтому мы знаем кому принадлежит.
Не нужно кучи тумблеров. Система становится масштабируемой. От одного до 100500.
А уже полученные данные можно обрабатывать как угодно.
Хоть отдельные человеко часы. Хоть общие.
 
Изменено:
  • Лойс +1
Реакции: Divin

Divin

★★★✩✩✩✩
30 Янв 2021
409
182
@Эдуард Анисимов, и называется это - СКУД , можно конечно и свое сделать из говна и палок на ардуино, а можно за относительно небольшие деньги использовать готовое решение - там и учет времени и контроль доступа и масштабируемость. причем на элементарном уровне - человек зашел в помещение - открыл меткой - реистрация прихода - вышел - открыл меткой - регистрация ухода.
 
  • Ахах! +1
Реакции: ТехнарьКто

AC520

✩✩✩✩✩✩✩
16 Янв 2023
11
5
опять за рыбу деньги...
всё уже, прибор работает, считает, все довольны. через 10-14 дней будет разобран, т.к. выполнит свою задачу и более без надобности.
я уже термоконтроллер для бочки с брагой ваяю.