Introduction: Arduino Time Recorder_Introduction

動機與介紹

由於先前勞基法修惡,也因此展開了一場勞方與資方之間的攻防戰。一邊是希望能早點下班,並且可以矇過資方。另一邊則是希望勞工超時工作之餘,並且可以躲避勞基法的追查。有鑑於此,本計畫僅提供一個專門對付打卡機的工具,搭載多種操作模式並可自動計算調整吾人上下班打卡時間的功能。

Step 1: Arduino Time Recorder_Material

材料

■Arduino UNO 一片

■LCD 液晶顯示器(+I2C) 一個

■DS3231 時鐘模組 一個

■麵包板 一片

■LED燈 一盞

■1路繼電器模組 5V 一組

■擴充版 一片

■4x4薄膜鍵盤 一片

Step 2: Arduino Time Recorder_Modes of Time Recorder

■Clock Mode:顯示當前時間,也是此自動打卡機的主畫面。

■Check Mode :強制打卡機完成打卡(手動操作),不需等到自動打卡時間方可打卡作業。長按「*」完成,期間需耗時十秒。

■Schedule Mode:可查詢當日上下班的自動打卡時間。長按數字鍵「0」進入查詢, 再次長按則回到Clock Mode。

■Setting Mode: 調整日期與時間,長按「#」進入設定,0~9更改當前游標數 字,A、B、C、D分別為游標上下左右移動。

長按「*」取消更改,長按「#」確認更改。

Step 3: Arduino Time Recorder_Statistics Behind the Time Recorder

上下班的打卡時間服從 Truncated normal

(因為常態分配為負無窮但正無窮,並且上下班要控制在一定時間,避免過早以及過晚到達。所以才設定為結尾的常態分配)

上班的分布: mu = 8:40 ; sigma^2 = 4 min ; Max = 8:50 ; Min = 8:30

下班的分布: mu = 18:15 ; sigma^2 = 4 min ; Max = 18:25 ; Min = 18:05

附圖左邊為使用R語言進行模擬的散布圖,右邊則是從Arduino收集來的資料。

Step 4: Arduino Time Recorder_ Arduino Code

/*

Auto_Clocker

Root Kuo

2018/4/12

*/

#include

#include

#include

#include

//Set const

const float pi = 3.14159265359;

//Set the control pin of relay

byte relayControl = 2;

//Set the clock

DS3231 Clock;

bool Century = false;

bool h12, PM; //Get the output from getHour()

bool detPM;

byte second, minute, hour, day, month, year;

//Set the lcd

LiquidCrystal_I2C lcd(0x27,16,2);

//Set the 4 by 4 keypad

AnalogMatrixKeypad aKeypad(A0);

//Set the variables

byte Mode = 0, checkStage = 0, checkSelection = 0, checkSelection2 = 0;

byte pressHashTime = 0, pressStarTime = 0, pressOneTime = 0, pressTwoTime = 0, pressThreeTime = 0;

byte cursorPos1, cursorPos2;

int testTime, lightTime = 0;

float randStdNormal;

byte Interrupt;

char key;

byte OnTime[5] = {0}, OffTime[5] = {0};

short OnTimeBound[2] = {30.0,50.0}, OffTimeBound[2] = {605.0,625.0};

bool changeMode = false;

bool beginSetting = true;

//char scheduleMatrix[16][16] = {{'N','o','.','O','n','T','i','m','e','O','f','f','T','i','m','e'}};

short inputSeed;

void PrintTimeOnLCD(byte s, byte m, byte h, byte D, byte M, byte Y, bool dPM){

lcd.setCursor(0, 0); // 設定游標位置在第一行行首

lcd.print("Date=");

//lcd.print(" =");

lcd.setCursor(5, 0);

lcd.print(2000 + Y,DEC);

lcd.setCursor(9, 0);

lcd.print('/');

lcd.setCursor(10, 0);

if(M >= 10){

lcd.print(M,DEC);

}else{

lcd.print('0');

lcd.setCursor(11,0);

lcd.print(M,DEC);

}

lcd.setCursor(12, 0);

lcd.print('/');

lcd.setCursor(13, 0);

if(D >= 10){

lcd.print(D,DEC);

}else{

lcd.print('0');

lcd.setCursor(14,0);

lcd.print(D,DEC);

}

lcd.setCursor(0, 1); // 設定游標位置在第二行行首

lcd.print("Time=");

lcd.setCursor(5, 1);

if(h >= 10){

lcd.print(h,DEC);

}else{

lcd.print('0');

lcd.setCursor(6,1);

lcd.print(h,DEC);

}

lcd.setCursor(7, 1);

lcd.print(':');

lcd.setCursor(8, 1);

if(m >= 10){

lcd.print(m,DEC);

}else{

lcd.print('0');

lcd.setCursor(9,1);

lcd.print(m,DEC);

}

lcd.setCursor(10, 1);

lcd.print(':');

lcd.setCursor(11, 1);

if(s >= 10){

lcd.print(s,DEC);

}else{

lcd.print('0');

lcd.setCursor(12,1);

lcd.print(s,DEC);

}

lcd.setCursor(13, 1);

if(dPM){

lcd.print(" PM ");

}else{

lcd.print(" AM ");

}

}

float randomStdNorm(short inputSeed){

byte Seed, Seed2;

float randUniform,randUniform2;

Seed = analogRead(1) + inputSeed;

randomSeed(Seed*2);

randUniform = float(random(10000))/10000;

Seed2 = -analogRead(2) + randUniform*512 - inputSeed;

randomSeed(Seed2*2+1);

randUniform2 = float(random(10000))/10000;

return(sqrt(-2*log(randUniform))*cos(2*pi*randUniform2));

}

void getRandomCheckTime(short inputSeed, byte *onTime, byte *offTime){

float tempOn = 0;

float Mean = (OnTimeBound[1] + OnTimeBound[0])/2;

float Sd = (OnTimeBound[1] - OnTimeBound[0] - 8)/3;

while(tempOn <= OnTimeBound[0] || OnTimeBound[1] <= tempOn)

tempOn = randomStdNorm(inputSeed) * Sd + Mean;

float tempOff = 630.0;

Mean = (OffTimeBound[1] + OffTimeBound[0])/2;

Sd = (OffTimeBound[1] - OffTimeBound[0] - 8)/3;

while(tempOff <= (tempOn+540) || tempOff <= OffTimeBound[0] || OffTimeBound[1] <= tempOff)

tempOff = randomStdNorm(inputSeed) * Sd + Mean;

onTime[0] = 8 + int(tempOn) / 60;

if(onTime[0] >= 12){

if(onTime[0]!=12) onTime[0] -= 12;

onTime[3] = 1;

}else onTime[3] = 0;

onTime[1] = int(tempOn) % 60;

randomSeed(2*(analogRead(1) + inputSeed));

onTime[2] = random(56);

offTime[0] = 8 + int(tempOff) / 60;

if(offTime[0] >= 12){

if(offTime[0]!=12) offTime[0] -= 12;

offTime[3] = 1;

}else offTime[3] = 0;

offTime[1] = int(tempOff) % 60;

randomSeed(1+2*(-analogRead(2) + onTime[2]*512 - inputSeed));

if(OnTime[0]+9==OffTime[0] && OnTime[1]==OffTime[1])

offTime[2] = random(onTime[2]+1,57);

else

offTime[2] = random(56);

}

void setup() {

// Start the serial interface

Serial.begin(9600);

// Start the I2C interface

Wire.begin();

/*

Clock.setYear(18); //Set the year (Last two digits of the year)

Clock.setMonth(4); //Set the month of the year

Clock.setDate(18); //Set the date of the month

Clock.setDoW(3); //Set the day of the week

Clock.setHour(13); //Set the hour

Clock.setMinute(16);//Set the minute

Clock.setSecond(0);//Set the second

*/

// Set relay control

pinMode(relayControl,OUTPUT);

digitalWrite(relayControl,LOW);

// 初始化 LCD,一行 16 的字元,共 2 行,預設開啟背光

lcd.begin(16,2);

// 閃爍三次

for(int i = 0; i < 3; i++) {

lcd.backlight(); // 開啟背光

delay(250);

lcd.noBacklight(); // 關閉背光

delay(250);

}

lcd.backlight();

// 輸出初始化文字

lcd.setCursor(0, 0); // 設定游標位置在第一行行首

lcd.print("A-LO-HA--");

delay(1000);

lcd.setCursor(0, 1); // 設定游標位置在第二行行首

lcd.print("Have a good time!");

delay(2000);

lcd.clear();

}

void loop(){

if(Mode!=1){

second = Clock.getSecond();

minute = Clock.getMinute();

hour = Clock.getHour(h12, PM);

if(hour >= 12){

if(hour != 12) hour -= 12;

detPM = true;

}else{

detPM = false;

}

day = Clock.getDate();

month = Clock.getMonth(Century);

year = Clock.getYear();

}

/*

ON[i] = 0

while(!(0

ON[i] = rnorm(1,Mean,SD)

}

mvOFF = (630 - (ON[i] + 540))/3

OFF[i] = 630

while(!((ON[i]+540)

OFF[i] = ON[i] + 540 + rnorm(1,mvOFF/2,sqrt(mvOFF))

}

*/

inputSeed = 512/60*second - 512/60*minute + 512/24*hour - 512/30*day + 512/12*month;

//if(true){

if((hour==11 && minute==59 && second==59 && detPM) || beginSetting){

OnTime[4] = 0;

OffTime[4] = 0;

beginSetting = false;

getRandomCheckTime(inputSeed, OnTime, OffTime);

}

Serial.print(OnTime[0]);

Serial.print(",");

Serial.print(OnTime[1]);

Serial.print(",");

Serial.print(OnTime[2]);

Serial.print(",");

Serial.print(OffTime[0]);

Serial.print(",");

Serial.print(OffTime[1]);

Serial.print(",");

Serial.println(OffTime[2]);

/*

OnTime[0] = 8;

OnTime[1] = 15;

OnTime[2] = 37;

OffTime[0] = 15;

OffTime[1] = 45;

OffTime[2] = 0;

*/

if((!OnTime[4] && detPM==OnTime[3] && hour==OnTime[0] && minute==OnTime[1] &&

OnTime[2] <= second && second <= (OnTime[2]+3)) ||

(!OffTime[4] && detPM==OffTime[3] && hour==OffTime[0] && minute==OffTime[1] &&

OffTime[2] <= second && second <= (OffTime[2]+3))){

if(!detPM) OnTime[4] = 1;

else OffTime[4] = 1;

Mode = 2;

changeMode = true;

}

switch(Mode){

case 0: //Clock Mode

if(changeMode){

changeMode = false;

lcd.clear();

delay(500);

lcd.setCursor(0,0);

lcd.print("<>");

delay(1000);

lcd.clear();

}

delay(1000*13/14);

PrintTimeOnLCD(second, minute, hour, day, month, year, detPM);

/*

Serial.print(randStdNormal,4);

lcd.setCursor(0, 0);

lcd.print(lightTime,DEC);

Serial.print('\n');

Serial.print(lightTime);

if(lightTime > 0){

digitalWrite(relayControl,LOW);

}else{

digitalWrite(relayControl,HIGH);

}

lightTime = lightTime - 1;

while(lightTime < 0){

randStdNormal = randomStdNorm(second, minute, hour, day, month);

lightTime = randStdNormal*sqrt(5)+4;

}

*/

key = aKeypad.readKey();

if(key == '2'){ // 如果不是「沒有按鍵被按下」…

pressTwoTime++;

}else{

if(pressTwoTime > 0) pressTwoTime--;

}

if(pressTwoTime == 2){

pressTwoTime = 0;

Mode = 1;

changeMode = true;

}

if(key == '3'){ // 如果不是「沒有按鍵被按下」…

pressThreeTime++;

}else{

if(pressThreeTime > 0) pressThreeTime--;

}

if(pressThreeTime == 2){

pressThreeTime = 0;

Mode = 2;

changeMode = true;

}

if(key == '1'){ // 如果不是「沒有按鍵被按下」…

pressOneTime++;

}else{

if(pressOneTime > 0) pressOneTime--;

}

if(pressOneTime == 2){

pressOneTime = 0;

Mode = 3;

changeMode = true;

}

break;

case 1: //Setting Mode

if(changeMode){

changeMode = false;

lcd.clear();

lcd.setCursor(0,0);

lcd.print("<>");

delay(1000);

lcd.clear();

lcd.blink();

cursorPos1 = 7;

cursorPos2 = 0;

}

PrintTimeOnLCD(second, minute, hour, day, month, year, detPM);

lcd.setCursor(cursorPos1,cursorPos2);

delay(300);

key = aKeypad.readKey();

if(key == '#'){

pressHashTime++;

}else if(key == '*'){

pressStarTime++;

}else if(48 <= key && key <= 57){

key = key - 48;

if(cursorPos2==0){

switch(cursorPos1){

case 7:

year = key*10 + year%10;

cursorPos1++;

break;

case 8:

year = (year/10)*10 + key;

cursorPos1 += 2;

break;

case 10:

month = key*10 + month%10;

cursorPos1++;

break;

case 11:

month = (month/10)*10 + key;

cursorPos1 += 2;

break;

case 13:

day = key*10 + day%10;

cursorPos1++;

break;

case 14:

day = (day/10)*10 + key;

cursorPos1 = 5;

cursorPos2 = 1;

break;

}

}else if(cursorPos2==1){

switch(cursorPos1){

case 5:

hour = key*10 + hour%10;

cursorPos1++;

break;

case 6:

hour = (hour/10)*10 + key;

cursorPos1 += 2;

break;

case 8:

minute = key*10 + minute%10;

cursorPos1++;

break;

case 9:

minute = (minute/10)*10 + key;

cursorPos1 += 2;

break;

case 11:

second = key*10 + second%10;

cursorPos1++;

break;

case 12:

second = (second/10)*10 + key;

cursorPos1 += 2;

break;

case 14:

detPM = !detPM;

cursorPos1 = 7;

cursorPos2 = 0;

break;

}

}

}else if(key == 'A' || key == 'B'){

if(cursorPos2 == 0){

cursorPos2 = 1;

if(cursorPos1 == 7 || cursorPos1 == 10 || cursorPos1 == 13) cursorPos1++;

}else if(cursorPos2 == 1){

cursorPos2 = 0;

if(cursorPos1 == 5 || cursorPos1 == 6) cursorPos1 = 7;

else if(cursorPos1 == 9 || cursorPos1 == 12) cursorPos1++;

}

}else if(key == 'C'){

cursorPos1--;

if(cursorPos2 == 0){

if(cursorPos1 == 9 || cursorPos1 == 12){

cursorPos1--;

}else if(cursorPos1 < 7){

cursorPos1 = 14;

cursorPos2 = 1;

}

}else if(cursorPos2 == 1){

if(cursorPos1 == 7 || cursorPos1 == 10 || cursorPos1 == 13){

cursorPos1--;

}else if(cursorPos1 < 5){

cursorPos1 = 14;

cursorPos2 = 0;

}

}

}else if(key == 'D'){

cursorPos1++;

if(cursorPos2 == 0){

if(cursorPos1 == 9 || cursorPos1 == 12){

cursorPos1++;

}else if(cursorPos1 > 14){

cursorPos1 = 5;

cursorPos2 = 1;

}

}else if(cursorPos2 == 1){

if(cursorPos1 == 7 || cursorPos1 == 10 || cursorPos1 == 13){

cursorPos1++;

}else if(cursorPos1 > 14){

cursorPos1 = 7;

cursorPos2 = 0;

}

}

}

if(month > 12) month = 12;

else if(month < 1) month = 1;

if(day > 31) day = 31;

else if(day < 1) day = 1;

if(hour > 12) hour = 12;

else if(hour < 1) hour = 1;

if(minute > 59) minute = 59;

else if(minute < 1) minute = 1;

if(second > 59) second = 59;

else if(second < 1) second = 1;

if(pressHashTime > 0 && key != '#'){

pressHashTime--;

}

if(pressHashTime == 4){

Clock.setYear(year); //Set the year (Last two digits of the year)

Clock.setMonth(month); //Set the month of the year

Clock.setDate(day); //Set the date of the month

//Clock.setDoW(3); //Set the day of the week

if(detPM)

Clock.setHour(hour + 12); //Set the hour

else

Clock.setHour(hour); //Set the hour

Clock.setMinute(minute);//Set the minute

Clock.setSecond(second);//Set the second

pressHashTime = 0;

Mode = 0;

changeMode = true;

lcd.noBlink();

lcd.clear();

lcd.setCursor(0,0);

lcd.print("<>");

lcd.setCursor(0,1);

lcd.print("Setting is done!");

delay(1500);

lcd.clear();

}

if(pressStarTime > 0 && key != '*'){

pressStarTime--;

}

if(pressStarTime == 4){

pressStarTime = 0;

Mode = 0;

changeMode = true;

lcd.noBlink();

lcd.clear();

lcd.setCursor(0,0);

lcd.print("<>");

lcd.setCursor(0,1);

lcd.print("+Cancel Setting+");

delay(1500);

lcd.clear();

}

break;

case 2: //Check Mode

if(changeMode){

changeMode = false;

lcd.clear();

lcd.setCursor(0,0);

lcd.print("<>");

digitalWrite(relayControl,LOW);

delay(1500);

}

switch(checkStage){

case 0:

Interrupt = 0;

lcd.setCursor(0,0);

lcd.print("Check On or Off?");

lcd.setCursor(0,1);

lcd.print(" On Off Esc ");

if(checkSelection==0){

lcd.setCursor(0,1);

lcd.print('[');

lcd.setCursor(3,1);

lcd.print(']');

}else if(checkSelection==1){

lcd.setCursor(5,1);

lcd.print('[');

lcd.setCursor(9,1);

lcd.print(']');

}else if(checkSelection==2){

lcd.setCursor(11,1);

lcd.print('[');

lcd.setCursor(15,1);

lcd.print(']');

}

key = aKeypad.readKey();

if(key=='C'){

if(checkSelection == 0) checkSelection = 2;

else checkSelection--;

}

if(key=='D'){

if(checkSelection == 2) checkSelection = 0;

else checkSelection++;

}

if(key=='#'){

if((checkSelection == 0 && OnTime[4]) ||

(checkSelection == 1 && OffTime[4])){

lcd.setCursor(0,1);

lcd.print("Already ckecked!");

delay(1500);

/*

lcd.clear();

if((checkSelection == 0 && OnTime[4]) ||

(checkSelection == 1 && OffTime[4])){

checkStage = 1;

checkSelection2 = 1;

delay(500);

lcd.setCursor(0,0);

lcd.print("Already ckecked!");

delay(1000);

lcd.setCursor(0,1);

lcd.print(" Do_again ESC ");

*/

}else{

checkStage = 2;

}

}

delay(200);

break;

/*

case 1:

lcd.setCursor(0,1);

lcd.print(" Do_again ESC ");

key = aKeypad.readKey();

if(checkSelection2==0){

lcd.setCursor(0,1);

lcd.print('[');

lcd.setCursor(9,1);

lcd.print(']');

}else if(checkSelection2==1){

lcd.setCursor(10,1);

lcd.print('[');

lcd.setCursor(14,1);

lcd.print(']');

}

if(key=='C' || key=='D'){

if(checkSelection2 == 0) checkSelection2 = 1;

else if(checkSelection2 == 1) checkSelection2 = 0;

}

if(key=='#'){

checkStage = 2;

}

delay(200);

break;

*/

case 2:

if(checkSelection==2 ||

checkSelection2==1){

lcd.setCursor(0,0);

lcd.print("Check On or Off");

lcd.setCursor(0,1);

lcd.print("+Cancel Checking");

}else{

lcd.clear();

digitalWrite(relayControl,HIGH);

lcd.setCursor(0,0);

if(checkSelection==0){

lcd.print("Checking On ");

}else if(checkSelection==1){

lcd.print("Checking Off ");

}

lcd.setCursor(0,1);

lcd.print("||");

lcd.setCursor(12,1);

lcd.print("||");

lcd.setCursor(2,1);

for(byte i=0;i<10;i++){

key = aKeypad.readKey();

if(key == '*'){

Interrupt = 1;

break;

}

delay(500);

lcd.print('-');

delay(500);

}

lcd.setCursor(2,1);

for(byte i=0;i<10;i++){

key = aKeypad.readKey();

if(key == '*'){

Interrupt = 1;

break;

}

delay(500);

lcd.print('=');

delay(500);

}

digitalWrite(relayControl,LOW);

if(Interrupt){

lcd.setCursor(0,1);

lcd.print("++Interrupted++");

}else{

lcd.setCursor(2,1);

lcd.print(" Complete ");

second = Clock.getSecond();

minute = Clock.getMinute();

hour = Clock.getHour(h12, PM);

if(checkSelection==0){

if(hour >= 12){

if(hour!=12) OnTime[0] = hour-12;

OnTime[3] = 1;

}else{

OnTime[0] = hour;

OnTime[3] = 0;

}

OnTime[1] = minute;

OnTime[2] = second;

OnTime[4] = 1;

}else if(checkSelection==1){

if(hour >= 12){

if(hour!=12) OffTime[0] = hour-12;

OffTime[3] = 1;

}else{

OffTime[0] = hour;

OffTime[3] = 0;

}

OffTime[1] = minute;

OffTime[2] = second;

OffTime[4] = 1;

}

}

}

delay(2000);

checkStage = 0;

checkSelection = 0;

Mode = 0;

changeMode = true;

break;

}

break;

case 3: //Schedule Mode

if(changeMode){

changeMode = false;

lcd.clear();

lcd.setCursor(0,0);

lcd.print("<>");

delay(1500);

lcd.clear();

}

lcd.setCursor(0,0);

lcd.print("On time/Off time");

lcd.setCursor(0,1);

if(OnTime[4]) lcd.print('v');

else lcd.print(' ');

if(OnTime[0] >= 10){

lcd.print(OnTime[0],DEC);

}else{

lcd.print('0');

lcd.setCursor(2,1);

lcd.print(OnTime[0],DEC);

}

lcd.print(':');

if(OnTime[1] >= 10){

lcd.print(OnTime[1],DEC);

}else{

lcd.print('0');

lcd.setCursor(5,1);

lcd.print(OnTime[1],DEC);

}

if(OnTime[3]) lcd.print('P');

else lcd.print('A');

lcd.print("-");

if(OffTime[4]) lcd.print('v');

else lcd.print(' ');

if(OffTime[0]-12 >= 10){

lcd.print(OffTime[0]-12,DEC);

}else{

lcd.print('0');

lcd.setCursor(10,1);

lcd.print(OffTime[0],DEC);

}

lcd.print(':');

if(OffTime[1] >= 10){

lcd.print(OffTime[1],DEC);

}else{

lcd.print('0');

lcd.setCursor(13,1);

lcd.print(OffTime[1],DEC);

}

if(OffTime[3]) lcd.print('P');

else lcd.print('A');

delay(500);

key = aKeypad.readKey();

if(key == '#'){

pressHashTime++;

}else if(pressHashTime > 0){

pressHashTime--;

}

if(pressHashTime == 3){

OnTime[4] = 0;

OffTime[4] = 0;

getRandomCheckTime(inputSeed, OnTime, OffTime);

lcd.clear();

lcd.setCursor(0,0);

lcd.print("On time/Off time");

lcd.setCursor(0,1);

lcd.print("Refresh the Time");

delay(1500);

lcd.clear();

}

if(key == '*'){

pressStarTime++;

}else if(pressStarTime > 0){

pressStarTime--;

}

if(pressStarTime == 3){

pressStarTime = 0;

Mode = 0;

changeMode = true;

delay(500);

lcd.clear();

}

break;

}

}