Arduino Time Recorder_Introduction
動機與介紹
由於先前勞基法修惡,也因此展開了一場勞方與資方之間的攻防戰。一邊是希望能早點下班,並且可以矇過資方。另一邊則是希望勞工超時工作之餘,並且可以躲避勞基法的追查。有鑑於此,本計畫僅提供一個專門對付打卡機的工具,搭載多種操作模式並可自動計算調整吾人上下班打卡時間的功能。
Arduino Time Recorder_Material
材料
■Arduino UNO 一片
■LCD 液晶顯示器(+I2C) 一個
■DS3231 時鐘模組 一個
■麵包板 一片
■LED燈 一盞
■1路繼電器模組 5V 一組
■擴充版 一片
■4x4薄膜鍵盤 一片
Arduino Time Recorder_Modes of Time Recorder
■Clock Mode:顯示當前時間,也是此自動打卡機的主畫面。
■Check Mode :強制打卡機完成打卡(手動操作),不需等到自動打卡時間方可打卡作業。長按「*」完成,期間需耗時十秒。
■Schedule Mode:可查詢當日上下班的自動打卡時間。長按數字鍵「0」進入查詢, 再次長按則回到Clock Mode。
■Setting Mode: 調整日期與時間,長按「#」進入設定,0~9更改當前游標數 字,A、B、C、D分別為游標上下左右移動。
長按「*」取消更改,長按「#」確認更改。
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收集來的資料。
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;
}
}