1544 lines
41 KiB
C++
Executable File
1544 lines
41 KiB
C++
Executable File
// To compile this project with Arduino IDE change sketchbook to <Project>/firmware
|
|
|
|
#include <Wire.h>
|
|
#include <ds3231.h>
|
|
#include <Adafruit_SleepyDog.h>
|
|
#include <PinChangeInterrupt.h>
|
|
#include <sportiduino.h>
|
|
|
|
// Uncomment line below to compile in DEBUG mode
|
|
//#define DEBUG
|
|
// You can also set debug mode by running "make debug=1 ..."
|
|
|
|
// Set PCB version by running "make pcbv=3 ..."
|
|
#ifndef HW_VERS
|
|
// or change here
|
|
#define HW_VERS 3
|
|
#endif
|
|
#define FW_MAJOR_VERS 11
|
|
// If FW_MINOR_VERS more than MAX_FW_MINOR_VERS this is beta version HW_VERS.FW_MAJOR_VERS.0-beta.X
|
|
// where X = (FW_MINOR_VERS - MAX_FW_MINOR_VERS)
|
|
#define FW_MINOR_VERS 0
|
|
|
|
// If PCB has reed switch and you don't want RC522 powered every 25 secs uncomment option bellow
|
|
#define NO_POLL_CARDS_IN_SLEEP_MODE
|
|
// You can also run "make nopoll=1 ..."
|
|
|
|
// Uncomment for BS check battery every 10 min in Sleep Mode and beep SOS if voltage < 3.3V
|
|
#define CHECK_BATTERY_IN_SLEEP
|
|
// You can also run "make check_battery=1 ..."
|
|
|
|
//-------------------------------------------------------------------
|
|
// HARDWARE
|
|
|
|
// Set BUZZER_FREQUENCY by running "make buzzfreq=2500 ..."
|
|
#ifndef BUZZER_FREQUENCY
|
|
// or change here
|
|
#define BUZZER_FREQUENCY 4000 // or 0 for buzzer with generator
|
|
#endif
|
|
|
|
#define BUZ 3
|
|
#define LED 4
|
|
|
|
#define RC522_RST 9
|
|
#define RC522_SS 10
|
|
|
|
#define UART_RX 0
|
|
#define UART_TX 1
|
|
|
|
#define SDA A4
|
|
#define SCL A5
|
|
|
|
// If you added battery voltage measurement circuit, reed switch or I2C EEPROM to your PCB v1 or v2
|
|
// you only need define appropriate pins like for PCB v3
|
|
|
|
#if HW_VERS == 1
|
|
#define DS3231_VCC 5
|
|
#define DS3231_RST A0
|
|
#define RC522_IRQ 6
|
|
|
|
//#define ADC_IN pin_number
|
|
//#define ADC_ENABLE pin_number
|
|
|
|
//#define I2C_EEPROM_VCC pin_number
|
|
//#define REED_SWITCH pin_number
|
|
#elif HW_VERS == 2
|
|
#define DS3231_VCC A1
|
|
#define DS3231_IRQ A3
|
|
#define DS3231_32K 5 // not used, reserved for future
|
|
#define DS3231_RST 2
|
|
|
|
#define RC522_IRQ 6
|
|
|
|
#define ADC_IN A0
|
|
#define ADC_ENABLE A1
|
|
|
|
//#define ADC_IN pin_number
|
|
//#define ADC_ENABLE pin_number
|
|
|
|
//#define I2C_EEPROM_VCC pin_number
|
|
//#define REED_SWITCH pin_number
|
|
#elif HW_VERS == 3
|
|
#define DS3231_VCC A3
|
|
#define DS3231_IRQ A2
|
|
#define DS3231_32K 5 // not used, reserved for future
|
|
#define DS3231_RST 2
|
|
|
|
#define RC522_IRQ 8
|
|
|
|
#define ADC_IN A0
|
|
#define ADC_ENABLE A1
|
|
|
|
#define I2C_EEPROM_VCC 6
|
|
#define REED_SWITCH 7
|
|
#else
|
|
#error Unknown HW_VERS
|
|
#endif
|
|
|
|
//-------------------------------------------------------------------
|
|
|
|
struct __attribute__((packed)) Configuration {
|
|
uint8_t stationNumber;
|
|
// Active Mode duration
|
|
// (xxx) - 2^(bit2:bit0) hours in Active Mode (1 - 32 hours)
|
|
// (110) - always be in Active Mode (check card in 0.25 second period)
|
|
// (111) - always be in Wait Mode (check card in 1 second period)
|
|
uint8_t activeModeDuration: 3;
|
|
uint8_t checkNoPunchesBeforeStart: 1; // Check no punches on a participant card at start station
|
|
uint8_t checkCardInitTime: 1; // Check init time of a participant card
|
|
uint8_t autosleep: 1; // Go to Sleep Mode after AUTOSLEEP_TIME milliseconds in Wait Mode
|
|
uint8_t oldFastPunchMode: 1; // Deprecated
|
|
uint8_t enableFastPunchForCard: 1; // Enable fast punch for card when clear
|
|
uint8_t antennaGain: 3;
|
|
uint8_t _reserved2: 5;
|
|
uint8_t password[3];
|
|
};
|
|
|
|
uint8_t ntagAuthPassword[4];
|
|
|
|
|
|
#ifdef I2C_EEPROM_VCC
|
|
#define USE_I2C_EEPROM
|
|
#define I2C_EEPROM_ADDRESS 0x50
|
|
#endif
|
|
|
|
#define UNKNOWN_PIN 0xFF
|
|
|
|
// 194.18 days after card initialization or clearing the new punches will have incorrect time
|
|
const uint32_t CARD_EXPIRE_TIME = 180UL*24*3600; // 180 days
|
|
|
|
#define EEPROM_CONFIG_ADDR 0x3EE
|
|
#define EEPROM_AUTH_PASSWORD_ADDR 0x3E2 // EEPROM_CONFIG_ADDR - sizeof(Configuration)*3
|
|
|
|
#define LOG_RECORD_SIZE 8 // bytes
|
|
const uint16_t I2C_EEPROM_MEMORY_SIZE = (uint16_t)32*1024; // bytes
|
|
const uint16_t MAX_LOG_RECORDS = I2C_EEPROM_MEMORY_SIZE/LOG_RECORD_SIZE;
|
|
|
|
// Poll time in active mode (milliseconds)
|
|
#define MODE_ACTIVE_CARD_CHECK_PERIOD 250
|
|
// Poll time in wait mode (milliseconds)
|
|
#define MODE_WAIT_CARD_CHECK_PERIOD 1000
|
|
// Poll time in sleep mode (milliseconds)
|
|
#define MODE_SLEEP_CARD_CHECK_PERIOD 25000
|
|
|
|
const uint32_t AUTOSLEEP_TIME = 48UL*3600*1000;
|
|
|
|
#define MODE_ACTIVE 0
|
|
#define MODE_WAIT 1
|
|
#define MODE_SLEEP 2
|
|
|
|
// It would be better to have MODE_WAIT as default
|
|
// If station resets on competition and default
|
|
// mode is SLEEP in this case the participant can't
|
|
// do punch fast
|
|
#define DEFAULT_MODE MODE_WAIT
|
|
#define DEFAULT_STATION_NUM CHECK_STATION_NUM
|
|
#define DEFAULT_ACTIVE_MODE_DURATION 1 // 2 hours
|
|
#define DEFAULT_ALARM_TIME 946684800 // 2000-01-01 00:00:00
|
|
|
|
#define SETTINGS_ALWAYS_ACTIVE 0x06
|
|
#define SETTINGS_ALWAYS_WAIT 0x07
|
|
|
|
#define SERIAL_MSG_START 0xFA
|
|
|
|
#define SERIAL_FUNC_READ_INFO 0xF0
|
|
#define SERIAL_FUNC_WRITE_SETTINGS 0xF1
|
|
#define SERIAL_FUNC_ERASE_LOG 0xF2
|
|
|
|
#define SERIAL_RESP_STATUS 0x01
|
|
#define SERIAL_RESP_INFO 0x02
|
|
|
|
#define SERIAL_OK 0x00
|
|
#define SERIAL_ERROR_CRC 0x01
|
|
#define SERIAL_ERROR_UNKNOWN_FUNC 0x02
|
|
#define SERIAL_ERROR_SIZE 0x03
|
|
#define SERIAL_ERROR_PWD 0x04
|
|
|
|
//--------------------------------------------------------------------
|
|
// VARIABLES
|
|
|
|
// work time in milliseconds
|
|
uint32_t workTimer = 0;
|
|
uint16_t sleepCount = 0;
|
|
Configuration config;
|
|
uint8_t mode = DEFAULT_MODE;
|
|
uint16_t activeModePollPeriod = MODE_ACTIVE_CARD_CHECK_PERIOD;
|
|
Rfid rfid;
|
|
SerialProtocol serialProto;
|
|
// date/time
|
|
static ts t;
|
|
uint32_t alarmTimestamp = DEFAULT_ALARM_TIME;
|
|
// This flag is true when it's DS3231 interrupt
|
|
uint8_t rtcAlarmFlag = 0;
|
|
uint8_t reedSwitchFlag = 0;
|
|
// It's true if there are data from UART in sleep mode
|
|
uint8_t serialWakeupFlag = 0;
|
|
uint16_t logNextRecordAddress = 0;
|
|
|
|
//--------------------------------------------------------------------
|
|
// FUNCTIONS
|
|
|
|
// the third parameter should be the frequency of your buzzer if you solded the buzzer without a generator else 0
|
|
inline void beep(uint16_t ms, uint8_t n) { beep_w(LED, BUZ, BUZZER_FREQUENCY, ms, n); }
|
|
|
|
inline void beepOk() { beep(500, 1); }
|
|
|
|
inline void beepRtcError() { beep(80, 2); }
|
|
inline void beepTimeError() { beep(100, 3); }
|
|
inline void beepPassError() { beep(100, 4); }
|
|
|
|
inline void beepLowBattery() { beep(100, 5); }
|
|
inline void beepBatteryOk() { beep(1000, 1); }
|
|
|
|
inline void beepCardCheckOk() { beepOk(); }
|
|
|
|
inline void beepCardPunchWritten() { beepOk(); }
|
|
inline void beepCardPunchAlreadyWritten() { beep(250, 2); }
|
|
|
|
inline void beepCardClearOk() { beepOk(); }
|
|
|
|
inline void beepMasterCardOk() { beep(250, 1); }
|
|
inline void beepMasterCardError() { beep(250, 2); }
|
|
inline void beepMasterCardReadError() { beep(50, 4); }
|
|
inline void beepMasterCardTimeOk() { beep(1000, 1); }
|
|
inline void beepMasterCardSleepOk() { beep(500, 4); }
|
|
|
|
inline void beepSerialOk() { beep(250, 1); }
|
|
inline void beepSerialError() { beep(250, 2); }
|
|
|
|
inline void beepSos() {
|
|
beep(100, 3);
|
|
delay(200);
|
|
beep(500, 3);
|
|
delay(200);
|
|
beep(100, 3);
|
|
delay(200);
|
|
digitalWrite(LED, HIGH);
|
|
delay(3000);
|
|
digitalWrite(LED, LOW);
|
|
}
|
|
|
|
#ifdef REED_SWITCH
|
|
inline void enableInterruptReedSwitch() { enablePCINT(digitalPinToPCINT(REED_SWITCH)); }
|
|
inline void disableInterruptReedSwitch() { disablePCINT(digitalPinToPCINT(REED_SWITCH)); }
|
|
#else
|
|
inline void enableInterruptReedSwitch() {}
|
|
inline void disableInterruptReedSwitch() {}
|
|
#endif
|
|
|
|
|
|
void(*resetFunc)(void) = 0;
|
|
void reedSwitchIrq();
|
|
void rtcAlarmIrq();
|
|
bool checkCardInitTime();
|
|
void checkParticipantCard();
|
|
void processSerial();
|
|
void serialFuncReadInfo(byte dataSize);
|
|
void serialFuncWriteSettings(byte *data, byte dataSize);
|
|
void serialFuncEraseLog();
|
|
void serialRespStatus(uint8_t code);
|
|
void wakeupByUartRx();
|
|
void setStationNum(uint8_t num);
|
|
void setTime(int16_t year, uint8_t mon, uint8_t day, uint8_t hour, uint8_t mi, uint8_t sec);
|
|
void setWakeupTime(int16_t year, uint8_t mon, uint8_t day, uint8_t hour, uint8_t mi, uint8_t sec);
|
|
void sleep(uint16_t ms);
|
|
void setMode(uint8_t newMode);
|
|
void setModeIfAllowed(uint8_t newMode);
|
|
void clearPunchLog();
|
|
void i2cEepromInit();
|
|
uint16_t i2cEepromReadCardNumber(uint16_t address);
|
|
void i2cEepromWritePunch(uint16_t cardNum);
|
|
bool i2cEepromReadRecord(uint16_t address, uint16_t *cardNum, uint32_t *timestamp);
|
|
void i2cEepromErase();
|
|
void processRfid();
|
|
uint16_t measureBatteryVoltage(bool silent = false);
|
|
uint8_t batteryVoltageToByte(uint16_t voltage);
|
|
bool checkBattery(bool beepEnabled = false);
|
|
void checkRtc();
|
|
void processCard();
|
|
void processMasterCard(uint8_t pageInitData[]);
|
|
void processTimeMasterCard(byte *data);
|
|
void processStationMasterCard(byte *data);
|
|
void processSleepMasterCard(byte *data);
|
|
void processBackupMasterCardWithTimestamps(byte *data);
|
|
void processSettingsMasterCard(byte *data);
|
|
void processPasswordMasterCard(byte *data);
|
|
void processAuthPasswordMasterCard(byte *data);
|
|
void processStateMasterCard();
|
|
void processParticipantCard(uint16_t cardNum);
|
|
bool writePunchToParticipantCard(uint8_t newPage, bool fastPunch);
|
|
void clearParticipantCard();
|
|
void checkParticipantCard();
|
|
void storeConfig();
|
|
void storeAuthPassword();
|
|
|
|
// Note: DS3231 works by UTC time!
|
|
|
|
void setup() {
|
|
MCUSR &= ~(1 << WDRF);
|
|
Watchdog.disable();
|
|
Watchdog.reset();
|
|
|
|
pinMode(LED, OUTPUT);
|
|
pinMode(BUZ, OUTPUT);
|
|
pinMode(RC522_RST, OUTPUT);
|
|
pinMode(RC522_SS, OUTPUT);
|
|
pinMode(RC522_IRQ, INPUT_PULLUP);
|
|
pinMode(DS3231_VCC, OUTPUT);
|
|
#ifdef DS3231_IRQ
|
|
pinMode(DS3231_IRQ, INPUT);
|
|
#endif
|
|
#ifdef DS3231_32K
|
|
pinMode(DS3231_32K, INPUT_PULLUP);
|
|
#endif
|
|
#ifdef REED_SWITCH
|
|
pinMode(REED_SWITCH, INPUT_PULLUP);
|
|
#endif
|
|
#if defined(ADC_IN) && defined(ADC_ENABLE)
|
|
pinMode(ADC_IN, INPUT);
|
|
pinMode(ADC_ENABLE, INPUT);
|
|
#endif
|
|
|
|
#if HW_VERS > 1
|
|
pinMode(DS3231_RST, INPUT); // if set as pull_up it takes additional supply current
|
|
#else
|
|
// not connected in v1
|
|
pinMode(DS3231_RST, INPUT_PULLUP);
|
|
#endif
|
|
|
|
digitalWrite(LED, LOW);
|
|
digitalWrite(BUZ, LOW);
|
|
digitalWrite(RC522_RST, LOW);
|
|
digitalWrite(DS3231_VCC, HIGH);
|
|
delay(5);
|
|
// initialize I2C
|
|
Wire.begin();
|
|
// Config DS3231
|
|
// Reset all interrupts and disable 32 kHz output
|
|
DS3231_set_addr(DS3231_STATUS_ADDR, 0);
|
|
DS3231_init(DS3231_INTCN | DS3231_A1IE);
|
|
checkRtc();
|
|
|
|
#ifdef DS3231_IRQ
|
|
// Config DS3231 interrupts
|
|
attachPCINT(digitalPinToPCINT(DS3231_IRQ), rtcAlarmIrq, FALLING);
|
|
#endif
|
|
|
|
#ifdef REED_SWITCH
|
|
attachPCINT(digitalPinToPCINT(REED_SWITCH), reedSwitchIrq, FALLING);
|
|
disablePCINT(digitalPinToPCINT(REED_SWITCH));
|
|
#endif
|
|
|
|
// Read settings from EEPROM
|
|
readConfig((uint8_t*)&config, sizeof(Configuration), EEPROM_CONFIG_ADDR);
|
|
readConfig(ntagAuthPassword, 4, EEPROM_AUTH_PASSWORD_ADDR);
|
|
|
|
if(config.stationNumber == 0 || config.stationNumber == 0xff ||
|
|
config.antennaGain > MAX_ANTENNA_GAIN || config.antennaGain < MIN_ANTENNA_GAIN) {
|
|
memset(&config, 0, sizeof(Configuration));
|
|
|
|
config.stationNumber = DEFAULT_STATION_NUM;
|
|
config.antennaGain = DEFAULT_ANTENNA_GAIN;
|
|
config.activeModeDuration = DEFAULT_ACTIVE_MODE_DURATION;
|
|
storeConfig();
|
|
memset(ntagAuthPassword, 0xFF, 4);
|
|
storeAuthPassword();
|
|
#ifdef USE_I2C_EEPROM
|
|
i2cEepromErase();
|
|
#endif
|
|
}
|
|
|
|
setModeIfAllowed(DEFAULT_MODE);
|
|
|
|
serialProto.init(SERIAL_MSG_START);
|
|
rfid.init(RC522_SS, RC522_RST, config.antennaGain);
|
|
rfid.setAuthPassword(ntagAuthPassword);
|
|
|
|
delay(500);
|
|
|
|
#ifdef USE_I2C_EEPROM
|
|
i2cEepromInit();
|
|
#endif
|
|
|
|
checkBattery(true);
|
|
|
|
Watchdog.enable(8000);
|
|
}
|
|
|
|
void wakeupIfNeed() {
|
|
if(alarmTimestamp > DEFAULT_ALARM_TIME) {
|
|
if(!DS3231_get(&t)) {
|
|
return;
|
|
}
|
|
if(t.unixtime >= alarmTimestamp) {
|
|
setModeIfAllowed(MODE_ACTIVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void loop() {
|
|
Watchdog.reset();
|
|
|
|
// process DS3231 alarm
|
|
if(rtcAlarmFlag) {
|
|
rtcAlarmFlag = 0;
|
|
DS3231_clear_a1f();
|
|
wakeupIfNeed();
|
|
} else if(mode == MODE_SLEEP) {
|
|
// if HV version is 1 or RTC alarm didn't occur
|
|
wakeupIfNeed();
|
|
}
|
|
|
|
processRfid();
|
|
|
|
// process mode
|
|
switch(mode) {
|
|
case MODE_ACTIVE:
|
|
sleep(activeModePollPeriod);
|
|
|
|
#ifdef DEBUG
|
|
digitalWrite(LED,HIGH);
|
|
#endif
|
|
|
|
if(config.activeModeDuration == SETTINGS_ALWAYS_ACTIVE) {
|
|
workTimer = 0;
|
|
} else if(workTimer >= (1<<(uint32_t)config.activeModeDuration)*3600000UL) {
|
|
setModeIfAllowed(MODE_WAIT);
|
|
}
|
|
break;
|
|
|
|
case MODE_WAIT:
|
|
sleep(MODE_WAIT_CARD_CHECK_PERIOD);
|
|
|
|
#ifdef DEBUG
|
|
digitalWrite(LED,HIGH);
|
|
#endif
|
|
|
|
if(config.activeModeDuration == SETTINGS_ALWAYS_WAIT) {
|
|
workTimer = 0;
|
|
}
|
|
|
|
if(config.autosleep && workTimer > AUTOSLEEP_TIME) {
|
|
setModeIfAllowed(MODE_SLEEP);
|
|
}
|
|
break;
|
|
|
|
case MODE_SLEEP:
|
|
default:
|
|
#if defined(CHECK_BATTERY_IN_SLEEP) && defined(ADC_IN) && defined(ADC_ENABLE)
|
|
if(sleepCount % 20 == 0) {
|
|
uint16_t voltage = measureBatteryVoltage(true);
|
|
if (voltage < 3300) {
|
|
beepSos();
|
|
}
|
|
}
|
|
++sleepCount;
|
|
#endif
|
|
enableInterruptReedSwitch();
|
|
sleep(MODE_SLEEP_CARD_CHECK_PERIOD);
|
|
disableInterruptReedSwitch();
|
|
|
|
#ifdef DEBUG
|
|
digitalWrite(LED,HIGH);
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
processSerial();
|
|
}
|
|
|
|
void setNewConfig(Configuration *newConfig) {
|
|
if(newConfig->stationNumber == 0) {
|
|
newConfig->stationNumber = config.stationNumber;
|
|
}
|
|
if(newConfig->antennaGain > MAX_ANTENNA_GAIN || newConfig->antennaGain < MIN_ANTENNA_GAIN) {
|
|
newConfig->antennaGain = config.antennaGain;
|
|
}
|
|
|
|
memcpy(&config, newConfig, sizeof(Configuration));
|
|
storeConfig();
|
|
}
|
|
|
|
void setStationNum(uint8_t num) {
|
|
if(num == 0) {
|
|
return;
|
|
}
|
|
|
|
config.stationNumber = num;
|
|
}
|
|
|
|
void setTime(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
|
|
memset(&t, 0, sizeof(t));
|
|
|
|
t.year = year;
|
|
t.mon = month;
|
|
t.mday = day;
|
|
t.hour = hour;
|
|
t.min = minute;
|
|
t.sec = second;
|
|
|
|
DS3231_set(t);
|
|
}
|
|
|
|
void setWakeupTime(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
|
|
uint8_t flags[5] = {0,0,0,0,0};
|
|
DS3231_get(&t);
|
|
uint32_t currentTimestamp = t.unixtime;
|
|
memset(&t, 0, sizeof(t));
|
|
t.year = year;
|
|
t.mon = month;
|
|
t.mday = day;
|
|
t.hour = hour;
|
|
t.min = minute;
|
|
t.sec = second;
|
|
alarmTimestamp = get_unixtime(t);
|
|
if(alarmTimestamp < currentTimestamp) {
|
|
alarmTimestamp = DEFAULT_ALARM_TIME;
|
|
t.mday = 1;
|
|
t.hour = 0;
|
|
t.min = 0;
|
|
t.sec = 0;
|
|
}
|
|
|
|
DS3231_clear_a1f();
|
|
// Set alarm
|
|
DS3231_set_a1(t.sec, t.min, t.hour, t.mday, flags);
|
|
}
|
|
|
|
void sleep(uint16_t ms) {
|
|
// We can't sleep if there is data received by UART or special interrupts
|
|
if(reedSwitchFlag || rtcAlarmFlag || serialWakeupFlag || Serial.available() > 0) {
|
|
return;
|
|
}
|
|
|
|
uint16_t period;
|
|
|
|
// Resolve issue #61
|
|
digitalWrite(RC522_RST,LOW);
|
|
digitalWrite(LED,LOW);
|
|
digitalWrite(BUZ,LOW);
|
|
digitalWrite(DS3231_VCC,LOW);
|
|
Wire.end();
|
|
serialProto.end();
|
|
|
|
pinMode(SDA, INPUT); // it is pulled up by hardware
|
|
pinMode(SCL, INPUT); // it is pulled up by hardware
|
|
|
|
for(byte pin = 0; pin < A5; pin++) {
|
|
if(pin == SDA ||
|
|
pin == SCL ||
|
|
pin == RC522_RST ||
|
|
pin == LED ||
|
|
pin == BUZ ||
|
|
pin == UART_TX ||
|
|
pin == UART_RX ||
|
|
#if defined(ADC_IN) && defined(ADC_ENABLE)
|
|
pin == ADC_IN ||
|
|
pin == ADC_ENABLE ||
|
|
#endif
|
|
#ifdef REED_SWITCH
|
|
pin == REED_SWITCH ||
|
|
#endif
|
|
pin == DS3231_VCC ||
|
|
#ifdef DS3231_IRQ
|
|
pin == DS3231_IRQ ||
|
|
#endif
|
|
#ifdef DS3231_32K
|
|
pin == DS3231_32K ||
|
|
#endif
|
|
pin == DS3231_RST) {
|
|
continue;
|
|
}
|
|
|
|
pinMode(pin, OUTPUT);
|
|
digitalWrite(pin, LOW);
|
|
}
|
|
// Turn off ADC
|
|
ADCSRA = 0;
|
|
pinMode(UART_RX, INPUT_PULLUP);
|
|
// Attach PCINT to wake-up CPU when data will arrive by UART in sleep mode
|
|
attachPCINT(digitalPinToPCINT(UART_RX), wakeupByUartRx, CHANGE);
|
|
// Reset watchdog
|
|
Watchdog.reset();
|
|
period = Watchdog.sleep(ms);
|
|
workTimer += period;
|
|
// Use recursion if sleep time below need time
|
|
if(ms > period) {
|
|
sleep(ms - period);
|
|
}
|
|
// no need this interrupt anymore
|
|
detachPCINT(digitalPinToPCINT(UART_RX));
|
|
// Resolve issue #61
|
|
digitalWrite(DS3231_VCC, HIGH);
|
|
serialWakeupFlag = 0;
|
|
serialProto.begin();
|
|
Wire.begin();
|
|
}
|
|
|
|
void setMode(uint8_t newMode) {
|
|
if(newMode == MODE_SLEEP) {
|
|
sleepCount = 0;
|
|
} else {
|
|
if(mode == MODE_SLEEP) {
|
|
// Wake up
|
|
checkRtc();
|
|
checkBattery(true);
|
|
}
|
|
}
|
|
if(newMode != MODE_ACTIVE) {
|
|
activeModePollPeriod = MODE_ACTIVE_CARD_CHECK_PERIOD;
|
|
}
|
|
mode = newMode;
|
|
workTimer = 0;
|
|
}
|
|
|
|
void setModeIfAllowed(uint8_t newMode) {
|
|
// Check mode with settings
|
|
if(config.activeModeDuration == SETTINGS_ALWAYS_WAIT) {
|
|
setMode(MODE_WAIT);
|
|
} else if(config.activeModeDuration == SETTINGS_ALWAYS_ACTIVE) {
|
|
setMode(MODE_ACTIVE);
|
|
} else {
|
|
setMode(newMode);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_I2C_EEPROM
|
|
void i2cEepromInit() {
|
|
pinMode(I2C_EEPROM_VCC, OUTPUT);
|
|
// Power on I2C EEPROM
|
|
digitalWrite(I2C_EEPROM_VCC, HIGH);
|
|
digitalWrite(LED, HIGH);
|
|
delay(1);
|
|
|
|
for(uint16_t i = 0; i < MAX_LOG_RECORDS; ++i) {
|
|
uint16_t address = i*LOG_RECORD_SIZE;
|
|
uint16_t cardNum = i2cEepromReadCardNumber(address);
|
|
if(cardNum == 0) {
|
|
logNextRecordAddress = address;
|
|
break;
|
|
}
|
|
}
|
|
|
|
digitalWrite(LED, LOW);
|
|
digitalWrite(I2C_EEPROM_VCC, LOW);
|
|
delay(100);
|
|
}
|
|
|
|
void i2cEepromEraseRecord(uint8_t address) {
|
|
Wire.beginTransmission(I2C_EEPROM_ADDRESS);
|
|
Wire.write(address >> 8);
|
|
Wire.write(address & 0xff);
|
|
Wire.write(0);
|
|
Wire.write(0);
|
|
Wire.endTransmission();
|
|
}
|
|
|
|
void i2cEepromWritePunch(uint16_t cardNum) {
|
|
pinMode(I2C_EEPROM_VCC, OUTPUT);
|
|
// Power on I2C EEPROM
|
|
digitalWrite(I2C_EEPROM_VCC, HIGH);
|
|
delay(1);
|
|
|
|
DS3231_get(&t);
|
|
uint32_t timestamp = t.unixtime;
|
|
uint16_t recordAddress = logNextRecordAddress;
|
|
if(recordAddress > I2C_EEPROM_MEMORY_SIZE - LOG_RECORD_SIZE) {
|
|
recordAddress = 0;
|
|
}
|
|
Wire.beginTransmission(I2C_EEPROM_ADDRESS);
|
|
Wire.write(recordAddress >> 8);
|
|
Wire.write(recordAddress & 0xff);
|
|
Wire.write(cardNum & 0xff);
|
|
Wire.write(cardNum >> 8);
|
|
for(uint8_t i = 0; i < 4; ++i) {
|
|
Wire.write((timestamp >> (8*i)) & 0xff); // little endian order
|
|
}
|
|
Wire.endTransmission();
|
|
logNextRecordAddress = recordAddress + LOG_RECORD_SIZE;
|
|
delay(5);
|
|
i2cEepromEraseRecord(logNextRecordAddress);
|
|
|
|
digitalWrite(I2C_EEPROM_VCC, LOW);
|
|
}
|
|
|
|
bool i2cEepromReadRecord(uint16_t address, uint16_t *cardNum, uint32_t *timestamp) {
|
|
Wire.beginTransmission(I2C_EEPROM_ADDRESS);
|
|
Wire.write(address >> 8);
|
|
Wire.write(address & 0xff);
|
|
Wire.endTransmission(false);
|
|
Wire.requestFrom(I2C_EEPROM_ADDRESS, 6);
|
|
|
|
if(Wire.available() < 2) {
|
|
return false;
|
|
}
|
|
*cardNum = Wire.read();
|
|
*cardNum |= (uint16_t)Wire.read() << 8;
|
|
*timestamp = 0;
|
|
for(uint8_t i = 0; i < 4; ++i) {
|
|
if(!Wire.available()) {
|
|
return false;
|
|
}
|
|
// Transform timestamp to big endian order
|
|
*timestamp |= ((uint32_t)Wire.read() & 0xff) << (8*i);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint16_t i2cEepromReadCardNumber(uint16_t address) {
|
|
Wire.beginTransmission(I2C_EEPROM_ADDRESS);
|
|
Wire.write(address >> 8);
|
|
Wire.write(address & 0xff);
|
|
Wire.endTransmission(false);
|
|
Wire.requestFrom(I2C_EEPROM_ADDRESS, 2);
|
|
|
|
if(Wire.available() < 2) {
|
|
return 0;
|
|
}
|
|
uint16_t cardNum = Wire.read();
|
|
cardNum |= (uint16_t)Wire.read() << 8;
|
|
return cardNum;
|
|
}
|
|
|
|
void i2cEepromErase() {
|
|
pinMode(I2C_EEPROM_VCC, OUTPUT);
|
|
digitalWrite(I2C_EEPROM_VCC, HIGH);
|
|
digitalWrite(LED, HIGH);
|
|
delay(1);
|
|
|
|
const uint8_t pageSize = 32;
|
|
const uint16_t nPages = I2C_EEPROM_MEMORY_SIZE/pageSize;
|
|
for(uint16_t i = 0; i < nPages; ++i) {
|
|
Watchdog.reset();
|
|
|
|
if(i % 32 == 0) {
|
|
digitalWrite(LED, HIGH);
|
|
} else if(i % 16 == 0) {
|
|
digitalWrite(LED, LOW);
|
|
}
|
|
|
|
Wire.beginTransmission(I2C_EEPROM_ADDRESS);
|
|
uint16_t pageAddress = i*pageSize;
|
|
Wire.write(pageAddress >> 8);
|
|
Wire.write(pageAddress & 0xff);
|
|
for(uint8_t j = 0; j < pageSize; ++j) {
|
|
Wire.write(0);
|
|
Watchdog.reset();
|
|
}
|
|
Wire.endTransmission();
|
|
delay(5);
|
|
}
|
|
digitalWrite(LED, LOW);
|
|
digitalWrite(I2C_EEPROM_VCC, LOW);
|
|
}
|
|
#endif
|
|
|
|
#if defined(ADC_IN) && defined(ADC_ENABLE)
|
|
uint16_t measureBatteryVoltage(bool silent) {
|
|
DEBUG_PRINTLN(F("measureBatteryVoltage"));
|
|
analogReference(INTERNAL);
|
|
pinMode(ADC_ENABLE, OUTPUT);
|
|
digitalWrite(ADC_ENABLE, LOW);
|
|
pinMode(ADC_IN, INPUT);
|
|
ADCSRA |= bit(ADEN);
|
|
|
|
DEBUG_PRINTLN(F("delay"));
|
|
if (silent) {
|
|
delay(100);
|
|
} else {
|
|
// Turn on led to increase current
|
|
digitalWrite(LED, HIGH);
|
|
|
|
Watchdog.reset();
|
|
delay(3000);
|
|
}
|
|
|
|
DEBUG_PRINTLN(F("measure"));
|
|
// Drop first measure, it's wrong
|
|
analogRead(ADC_IN);
|
|
|
|
uint32_t value = 0;
|
|
for(uint8_t i = 0; i < 10; ++i) {
|
|
Watchdog.reset();
|
|
value += analogRead(ADC_IN);
|
|
delay(1);
|
|
}
|
|
value /= 10;
|
|
|
|
if (!silent) {
|
|
digitalWrite(LED, LOW);
|
|
}
|
|
DEBUG_PRINT(F("value: "));
|
|
DEBUG_PRINTLN(value);
|
|
pinMode(ADC_ENABLE, INPUT);
|
|
const uint32_t R_HIGH = 270000; // ohm
|
|
const uint32_t R_LOW = 68000; // ohm
|
|
const uint32_t k = 1100*(R_HIGH + R_LOW)/R_LOW;
|
|
return value*k/1023;
|
|
}
|
|
#endif
|
|
|
|
uint16_t measureVcc() {
|
|
Watchdog.reset();
|
|
// Turn on ADC
|
|
ADCSRA |= bit(ADEN);
|
|
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
|
|
|
|
Watchdog.reset();
|
|
// Turn on led to increase current
|
|
digitalWrite(LED, HIGH);
|
|
|
|
delay(5000);
|
|
|
|
uint32_t value = 0;
|
|
// Measure battery voltage
|
|
for(uint8_t i = 0; i < 10; i++) {
|
|
// Start to measure
|
|
ADCSRA |= _BV(ADSC);
|
|
while(bit_is_set(ADCSRA, ADSC));
|
|
// Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
|
|
uint32_t adcl = ADCL;
|
|
uint32_t adch = ADCH;
|
|
|
|
adcl &= 0xFF;
|
|
adch &= 0xFF;
|
|
value += ((adch << 8) | adcl);
|
|
}
|
|
|
|
const uint32_t refConst = 1125300L; //voltage constanta
|
|
value = (refConst*10)/value;
|
|
|
|
// Turn off ADC
|
|
ADCSRA = 0;
|
|
|
|
digitalWrite(LED, LOW);
|
|
|
|
return value;
|
|
}
|
|
|
|
uint8_t batteryVoltageToByte(uint16_t voltage) {
|
|
const uint16_t maxVoltage = 0xff*20; // mV
|
|
if(voltage > maxVoltage) {
|
|
voltage = maxVoltage;
|
|
}
|
|
return voltage/20;
|
|
}
|
|
|
|
bool checkBattery(bool beepEnabled) {
|
|
#if defined(ADC_IN) && defined(ADC_ENABLE)
|
|
uint16_t voltage = measureBatteryVoltage(not beepEnabled);
|
|
const uint16_t minVoltage = 3600;
|
|
#else
|
|
uint16_t voltage = measureVcc();
|
|
const uint16_t minVoltage = 3100;
|
|
#endif
|
|
|
|
Watchdog.reset();
|
|
delay(250);
|
|
|
|
if(voltage > minVoltage) {
|
|
if(beepEnabled) {
|
|
beepBatteryOk();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if(beepEnabled) {
|
|
beepLowBattery();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void checkRtc() {
|
|
if(!DS3231_get(&t)) {
|
|
// DS3231 broken or not connected
|
|
beepRtcError();
|
|
return;
|
|
}
|
|
// Check current time
|
|
if(t.year < 2025) {
|
|
beepTimeError();
|
|
if(t.unixtime == 0) {
|
|
setTime(2000, 1, 1, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void processRfid() {
|
|
#if defined(REED_SWITCH) && defined(NO_POLL_CARDS_IN_SLEEP_MODE)
|
|
if(mode == MODE_SLEEP && !reedSwitchFlag) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Visual feedback to display rfid works via reed switch
|
|
if(mode == MODE_SLEEP && reedSwitchFlag) {
|
|
digitalWrite(LED, HIGH);
|
|
}
|
|
|
|
reedSwitchFlag = 0;
|
|
|
|
rfid.begin(config.antennaGain);
|
|
processCard();
|
|
rfid.end();
|
|
|
|
if(mode == MODE_SLEEP) {
|
|
digitalWrite(LED, LOW);
|
|
|
|
// Clear uid to process it more than once in sleep mode
|
|
rfid.clearLastCardUid();
|
|
}
|
|
}
|
|
|
|
void processCard() {
|
|
if(!rfid.isNewCardDetected()) {
|
|
return;
|
|
}
|
|
byte pageData[4];
|
|
memset(pageData, 0, sizeof(pageData));
|
|
|
|
if(!rfid.cardPageRead(CARD_PAGE_INIT, pageData)) {
|
|
DEBUG_PRINTLN(F("PAGE_INIT read failed"));
|
|
return;
|
|
}
|
|
// Check the card role
|
|
if(pageData[2] == MASTER_CARD_SIGN) {
|
|
// This is a master card
|
|
processMasterCard(pageData);
|
|
} else {
|
|
setModeIfAllowed(MODE_ACTIVE);
|
|
// Process a participant card
|
|
switch(config.stationNumber) {
|
|
case CLEAR_STATION_NUM:
|
|
clearParticipantCard();
|
|
break;
|
|
case CHECK_STATION_NUM:
|
|
checkParticipantCard();
|
|
break;
|
|
default:
|
|
uint16_t cardNum = pageData[0];
|
|
cardNum <<= 8;
|
|
cardNum |= pageData[1];
|
|
processParticipantCard(cardNum);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void processMasterCard(uint8_t pageInitData[]) {
|
|
// Don't change mode for some types of cards
|
|
if(pageInitData[1] != MASTER_CARD_STATE
|
|
|| pageInitData[1] == MASTER_CARD_SLEEP) {
|
|
setModeIfAllowed(MODE_ACTIVE);
|
|
}
|
|
|
|
byte masterCardData[16];
|
|
memset(masterCardData, 0, sizeof(masterCardData));
|
|
memcpy(masterCardData, pageInitData, 4);
|
|
|
|
byte pageData[4];
|
|
for(uint8_t i = 1; i < 4; ++i) {
|
|
if(!rfid.cardPageRead(CARD_PAGE_INIT + i, pageData)) {
|
|
return;
|
|
}
|
|
memcpy(masterCardData + 4*i, pageData, 4);
|
|
}
|
|
|
|
// Check password
|
|
if( (config.password[0] != masterCardData[4]) ||
|
|
(config.password[1] != masterCardData[5]) ||
|
|
(config.password[2] != masterCardData[6]) ) {
|
|
beepPassError();
|
|
return;
|
|
}
|
|
|
|
switch(masterCardData[1]) {
|
|
case MASTER_CARD_SET_TIME:
|
|
processTimeMasterCard(masterCardData);
|
|
break;
|
|
case MASTER_CARD_SET_NUMBER:
|
|
processStationMasterCard(masterCardData);
|
|
break;
|
|
case MASTER_CARD_SLEEP:
|
|
processSleepMasterCard(masterCardData);
|
|
break;
|
|
case MASTER_CARD_READ_BACKUP:
|
|
#ifdef USE_I2C_EEPROM
|
|
processBackupMasterCardWithTimestamps(masterCardData);
|
|
#endif
|
|
break;
|
|
case MASTER_CARD_CONFIG:
|
|
processSettingsMasterCard(masterCardData);
|
|
break;
|
|
case MASTER_CARD_PASSWORD:
|
|
processPasswordMasterCard(masterCardData);
|
|
break;
|
|
case MASTER_CARD_STATE:
|
|
processStateMasterCard();
|
|
break;
|
|
case MASTER_CARD_AUTH_PASSWORD:
|
|
processAuthPasswordMasterCard(masterCardData);
|
|
break;
|
|
default:
|
|
beepMasterCardReadError();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void deinitCard() {
|
|
rfid.cardPageErase(CARD_PAGE_INIT);
|
|
}
|
|
|
|
void processTimeMasterCard(byte *data) {
|
|
// Note: time is UTC
|
|
setTime(data[9] + 2000, data[8], data[10], data[12], data[13], data[14]);
|
|
|
|
deinitCard();
|
|
|
|
if(!DS3231_get(&t)) {
|
|
beepRtcError();
|
|
} else {
|
|
beepMasterCardTimeOk();
|
|
}
|
|
}
|
|
|
|
void processStationMasterCard(byte *data) {
|
|
uint8_t newNum = data[8];
|
|
|
|
if(newNum > 0) {
|
|
if(config.stationNumber != newNum) {
|
|
setStationNum(newNum);
|
|
storeConfig();
|
|
}
|
|
deinitCard();
|
|
beepMasterCardOk();
|
|
} else {
|
|
beepMasterCardError();
|
|
}
|
|
}
|
|
|
|
void processSleepMasterCard(byte *data) {
|
|
setMode(MODE_SLEEP);
|
|
|
|
// Config alarm
|
|
setWakeupTime(data[9] + 2000, data[8], data[10], data[12], data[13], data[14]);
|
|
|
|
beepMasterCardSleepOk();
|
|
}
|
|
|
|
#ifdef USE_I2C_EEPROM
|
|
void processBackupMasterCardWithTimestamps(byte *data) {
|
|
byte pageData[4];
|
|
memcpy(pageData, data, 4);
|
|
pageData[0] = config.stationNumber;
|
|
pageData[3] = FW_MAJOR_VERS;
|
|
bool result = rfid.cardPageWrite(CARD_PAGE_INIT, pageData);
|
|
|
|
uint8_t maxPage = rfid.getCardMaxPage();
|
|
uint8_t stationNumberFromCard = data[0];
|
|
uint16_t lastRecordAddressFromCard = 0xffff;
|
|
if (stationNumberFromCard == config.stationNumber) {
|
|
result &= rfid.cardPageRead(CARD_PAGE_INFO1, pageData);
|
|
byte lastPageData[4];
|
|
result &= rfid.cardPageRead(maxPage, lastPageData);
|
|
if (pageData[0] == 0 && pageData[1] == 0 && !pageIsEmpty(lastPageData)) {
|
|
lastRecordAddressFromCard = byteArrayToUint32(pageData) & 0xffff;
|
|
}
|
|
}
|
|
|
|
uint8_t page = CARD_PAGE_INFO1;
|
|
// Clear page for lastRecordAddressFromCard
|
|
result &= rfid.cardPageErase(page++);
|
|
uint16_t lastTimestampHiHalf = 0;
|
|
|
|
// Power on I2C EEPROM
|
|
digitalWrite(I2C_EEPROM_VCC, HIGH);
|
|
delay(5);
|
|
digitalWrite(LED, HIGH);
|
|
uint16_t address = logNextRecordAddress;
|
|
if(lastRecordAddressFromCard < I2C_EEPROM_MEMORY_SIZE) {
|
|
address = lastRecordAddressFromCard;
|
|
}
|
|
for(uint16_t i = 1; i <= MAX_LOG_RECORDS; ++i) {
|
|
Watchdog.reset();
|
|
if(page > maxPage) {
|
|
break;
|
|
}
|
|
|
|
if(i % 100 == 0) {
|
|
digitalWrite(LED, LOW);
|
|
} else if(i % 50 == 0) {
|
|
digitalWrite(LED, HIGH);
|
|
}
|
|
|
|
if(address == 0) {
|
|
address = I2C_EEPROM_MEMORY_SIZE;
|
|
}
|
|
address -= LOG_RECORD_SIZE;
|
|
uint16_t cardNum = 0;
|
|
uint32_t timestamp = 0;
|
|
if(!i2cEepromReadRecord(address, &cardNum, ×tamp)) {
|
|
beepMasterCardError();
|
|
return;
|
|
}
|
|
if(cardNum == 0) {
|
|
break;
|
|
}
|
|
if(cardNum == 0xffff && timestamp == 0xffffffff) {
|
|
// No punch
|
|
continue;
|
|
}
|
|
uint16_t timestampHiHalf = timestamp >> 16;
|
|
if(timestampHiHalf != lastTimestampHiHalf) {
|
|
pageData[0] = 0;
|
|
pageData[1] = 0;
|
|
pageData[2] = timestampHiHalf >> 8;
|
|
pageData[3] = timestampHiHalf & 0xff;
|
|
result &= rfid.cardPageWrite(page++, pageData);
|
|
if(!result || page > maxPage) {
|
|
break;
|
|
}
|
|
lastTimestampHiHalf = timestampHiHalf;
|
|
}
|
|
|
|
pageData[0] = cardNum >> 8;
|
|
pageData[1] = cardNum & 0xff;
|
|
pageData[2] = (timestamp >> 8) & 0xff;
|
|
pageData[3] = timestamp & 0xff;
|
|
result &= rfid.cardPageWrite(page++, pageData);
|
|
}
|
|
// Write current address for lastRecordAddressFromCard in first data page
|
|
pageData[0] = 0;
|
|
pageData[1] = 0;
|
|
pageData[2] = address >> 8;
|
|
pageData[3] = address & 0xff;
|
|
result &= rfid.cardPageWrite(CARD_PAGE_INFO1, pageData);
|
|
|
|
digitalWrite(LED, HIGH);
|
|
result &= rfid.cardErase(page, maxPage);
|
|
digitalWrite(LED, LOW);
|
|
|
|
digitalWrite(I2C_EEPROM_VCC, LOW);
|
|
|
|
delay(250);
|
|
|
|
if(result) {
|
|
beepMasterCardOk();
|
|
} else {
|
|
beepMasterCardError();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void processSettingsMasterCard(byte *data) {
|
|
setNewConfig((Configuration*)&data[8]);
|
|
beepMasterCardOk();
|
|
}
|
|
|
|
void processPasswordMasterCard(byte *data) {
|
|
config.password[0] = data[8];
|
|
config.password[1] = data[9];
|
|
config.password[2] = data[10];
|
|
storeConfig();
|
|
beepMasterCardOk();
|
|
}
|
|
|
|
void processAuthPasswordMasterCard(byte *data) {
|
|
memcpy(ntagAuthPassword, &data[8], 4);
|
|
rfid.setAuthPassword(ntagAuthPassword);
|
|
storeAuthPassword();
|
|
beepMasterCardOk();
|
|
}
|
|
|
|
void processStateMasterCard() {
|
|
digitalWrite(LED, HIGH);
|
|
|
|
#if defined(ADC_IN) && defined(ADC_ENABLE)
|
|
// Disable RFID to prevent bad impact on measurements
|
|
rfid.end();
|
|
digitalWrite(LED, HIGH);
|
|
byte batteryByte = batteryVoltageToByte(measureBatteryVoltage(true));
|
|
digitalWrite(LED, LOW);
|
|
rfid.begin(config.antennaGain);
|
|
#else
|
|
byte batteryByte = checkBattery(false);
|
|
#endif
|
|
uint8_t page = CARD_PAGE_START;
|
|
byte pageData[4] = {0,0,0,0};
|
|
|
|
// Write version
|
|
pageData[0] = HW_VERS;
|
|
pageData[1] = FW_MAJOR_VERS;
|
|
pageData[2] = FW_MINOR_VERS;
|
|
pageData[3] = 0;
|
|
bool result = rfid.cardPageWrite(page++, pageData);
|
|
|
|
// Write first 3 bytes of station config (without password)
|
|
memcpy(pageData, &config, 3);
|
|
pageData[3] = 0;
|
|
result &= rfid.cardPageWrite(page++, pageData);
|
|
|
|
// Write station state
|
|
pageData[0] = batteryByte;
|
|
pageData[1] = mode;
|
|
pageData[2] = 0;
|
|
pageData[3] = 0;
|
|
result &= rfid.cardPageWrite(page++, pageData);
|
|
|
|
// Get actual time and date
|
|
DS3231_get(&t);
|
|
// Write current time and date
|
|
result &= rfid.cardPageWrite(page++, t.unixtime);
|
|
|
|
// Write wake-up time
|
|
result &= rfid.cardPageWrite(page++, alarmTimestamp);
|
|
|
|
Watchdog.reset();
|
|
delay(10);
|
|
|
|
if(result) {
|
|
beepMasterCardOk();
|
|
} else {
|
|
beepMasterCardError();
|
|
}
|
|
digitalWrite(LED, LOW);
|
|
}
|
|
|
|
void processParticipantCard(uint16_t cardNum) {
|
|
if(cardNum == 0) {
|
|
return;
|
|
}
|
|
|
|
uint8_t lastNum = 0;
|
|
uint8_t newPage = 0;
|
|
uint8_t maxPage = rfid.getCardMaxPage();
|
|
bool fastPunch = false;
|
|
|
|
// Find the empty page to write new punch
|
|
byte pageData[4] = {0,0,0,0};
|
|
if(rfid.cardPageRead(CARD_PAGE_LAST_RECORD_INFO, pageData)) {
|
|
fastPunch = (pageData[3] == FAST_PUNCH_SIGN);
|
|
} else {
|
|
DEBUG_PRINTLN(F("Page6 read failed"));
|
|
return;
|
|
}
|
|
if(fastPunch) {
|
|
activeModePollPeriod = 100;
|
|
lastNum = pageData[0];
|
|
newPage = pageData[1] + 1;
|
|
if(newPage < CARD_PAGE_START || newPage > maxPage) {
|
|
newPage = CARD_PAGE_START;
|
|
}
|
|
if(rfid.cardPageRead(newPage, pageData)) {
|
|
if (!pageIsEmpty(pageData)) {
|
|
findNewPage(&rfid, &newPage, &lastNum);
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
findNewPage(&rfid, &newPage, &lastNum);
|
|
}
|
|
|
|
if(newPage < CARD_PAGE_START || newPage > maxPage) {
|
|
return;
|
|
}
|
|
|
|
if(lastNum != config.stationNumber) {
|
|
if(config.checkNoPunchesBeforeStart
|
|
&& config.stationNumber == START_STATION_NUM
|
|
&& newPage > CARD_PAGE_START) {
|
|
return;
|
|
}
|
|
|
|
if(config.checkCardInitTime && !checkCardInitTime()) {
|
|
return;
|
|
}
|
|
|
|
if(writePunchToParticipantCard(newPage, fastPunch)) {
|
|
#ifdef USE_I2C_EEPROM
|
|
i2cEepromWritePunch(cardNum);
|
|
#endif
|
|
beepCardPunchWritten();
|
|
}
|
|
} else {
|
|
beepCardPunchAlreadyWritten();
|
|
}
|
|
}
|
|
|
|
bool writePunchToParticipantCard(uint8_t newPage, bool fastPunch) {
|
|
byte pageData[4] = {0,0,0,0};
|
|
|
|
DS3231_get(&t);
|
|
|
|
pageData[0] = config.stationNumber;
|
|
pageData[1] = (t.unixtime >> 16) & 0xFF;
|
|
pageData[2] = (t.unixtime >> 8) & 0xFF;
|
|
pageData[3] = t.unixtime & 0xFF;
|
|
|
|
bool result = rfid.cardPageWrite(newPage, pageData, 4, false);
|
|
|
|
if(fastPunch && result) {
|
|
pageData[0] = config.stationNumber;
|
|
pageData[1] = newPage;
|
|
pageData[2] = 0;
|
|
pageData[3] = FAST_PUNCH_SIGN;
|
|
result &= rfid.cardPageWrite(CARD_PAGE_LAST_RECORD_INFO, pageData);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void clearParticipantCard() {
|
|
uint8_t maxPage = rfid.getCardMaxPage();
|
|
bool result = true;
|
|
|
|
digitalWrite(LED, HIGH);
|
|
// Clear card from last page
|
|
uint8_t c = 0;
|
|
for(uint8_t page = maxPage - 3; page > CARD_PAGE_INIT_TIME; page -= 4) {
|
|
Watchdog.reset();
|
|
|
|
if(c % 10 == 0) {
|
|
digitalWrite(LED, HIGH);
|
|
} else if(c % 5 == 0) {
|
|
digitalWrite(LED, LOW);
|
|
}
|
|
++c;
|
|
if(!rfid.cardErase4Pages(page)) {
|
|
DEBUG_PRINTLN(F("Failed to erase pages"));
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint8_t tail = (maxPage - CARD_PAGE_INIT_TIME)%4;
|
|
// Erase the tail if necessary
|
|
if (result && tail > 0) {
|
|
Watchdog.reset();
|
|
|
|
// Erase the remaining pages individually
|
|
for (uint8_t i = tail; i > 0; --i) {
|
|
uint8_t pageToErase = CARD_PAGE_INIT_TIME + i;
|
|
|
|
// Attempt to erase a single page
|
|
if (!rfid.cardPageErase(pageToErase)) {
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
digitalWrite(LED, LOW);
|
|
|
|
if(result) {
|
|
DS3231_get(&t);
|
|
result &= rfid.cardPageWrite(CARD_PAGE_INIT_TIME, t.unixtime);
|
|
|
|
if(config.enableFastPunchForCard) {
|
|
byte pageData[4];
|
|
pageData[0] = config.stationNumber;
|
|
pageData[1] = 0;
|
|
pageData[2] = 0;
|
|
pageData[3] = FAST_PUNCH_SIGN;
|
|
result &= rfid.cardPageWrite(CARD_PAGE_LAST_RECORD_INFO, pageData);
|
|
}
|
|
}
|
|
|
|
if(result) {
|
|
delay(50);
|
|
beepCardClearOk();
|
|
}
|
|
}
|
|
|
|
void checkParticipantCard() {
|
|
byte pageData[4];
|
|
if(!rfid.cardPageRead(CARD_PAGE_INIT, pageData)) {
|
|
return;
|
|
}
|
|
uint16_t cardNum = (((uint16_t)pageData[0])<<8) + pageData[1];
|
|
if(cardNum == 0 || pageData[2] == MASTER_CARD_SIGN) {
|
|
return;
|
|
}
|
|
|
|
// It shouldn't be punches on a card
|
|
uint8_t newPage = 0;
|
|
uint8_t lastNum = 0;
|
|
findNewPage(&rfid, &newPage, &lastNum);
|
|
if(newPage != CARD_PAGE_START || lastNum != 0) {
|
|
return;
|
|
}
|
|
if(!checkCardInitTime()) {
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_I2C_EEPROM
|
|
i2cEepromWritePunch(cardNum);
|
|
#endif
|
|
|
|
beepCardCheckOk();
|
|
}
|
|
|
|
bool checkCardInitTime() {
|
|
byte pageData[4] = {0,0,0,0};
|
|
|
|
if(!rfid.cardPageRead(CARD_PAGE_INIT_TIME, pageData)) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t cardTime = byteArrayToUint32(pageData);
|
|
|
|
DS3231_get(&t);
|
|
if(t.unixtime > cardTime &&
|
|
t.unixtime - cardTime > CARD_EXPIRE_TIME) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void wakeupByUartRx() {
|
|
serialWakeupFlag = 1;
|
|
}
|
|
|
|
void rtcAlarmIrq() {
|
|
// We can't process interrupt here
|
|
// because it hangs CPU by I2C operations
|
|
// So set flag and process interrupt in main routine
|
|
rtcAlarmFlag = 1;
|
|
}
|
|
|
|
void reedSwitchIrq() {
|
|
reedSwitchFlag = 1;
|
|
}
|
|
|
|
void processSerial() {
|
|
bool error = false;
|
|
uint8_t cmdCode = 0;
|
|
uint8_t dataSize = 0;
|
|
|
|
uint8_t *data = serialProto.read(&error, &cmdCode, &dataSize);
|
|
if(error) {
|
|
serialRespStatus(SERIAL_ERROR_CRC);
|
|
return;
|
|
}
|
|
|
|
if(data) {
|
|
switch(cmdCode) {
|
|
case SERIAL_FUNC_READ_INFO:
|
|
serialFuncReadInfo(dataSize);
|
|
break;
|
|
case SERIAL_FUNC_WRITE_SETTINGS:
|
|
serialFuncWriteSettings(data, dataSize);
|
|
break;
|
|
case SERIAL_FUNC_ERASE_LOG:
|
|
serialFuncEraseLog();
|
|
break;
|
|
default:
|
|
serialRespStatus(SERIAL_ERROR_UNKNOWN_FUNC);
|
|
break;
|
|
}
|
|
return;
|
|
} else {
|
|
// drop bytes before start byte
|
|
serialProto.dropByte();
|
|
}
|
|
}
|
|
|
|
void serialFuncReadInfo(byte dataSize) {
|
|
if(dataSize < 3) {
|
|
serialRespStatus(SERIAL_ERROR_SIZE);
|
|
return;
|
|
}
|
|
|
|
serialProto.start(SERIAL_RESP_INFO);
|
|
serialProto.add(HW_VERS);
|
|
serialProto.add(FW_MAJOR_VERS);
|
|
serialProto.add(FW_MINOR_VERS);
|
|
serialProto.add((uint8_t*)&config, sizeof(Configuration));
|
|
|
|
#if defined(ADC_IN) && defined(ADC_ENABLE)
|
|
serialProto.add(batteryVoltageToByte(measureBatteryVoltage(true)));
|
|
#else
|
|
serialProto.add(checkBattery(false));
|
|
#endif
|
|
serialProto.add(mode);
|
|
|
|
// Write current time
|
|
DS3231_get(&t);
|
|
serialProto.addUint32(t.unixtime);
|
|
|
|
// Write wake-up time
|
|
serialProto.addUint32(alarmTimestamp);
|
|
|
|
serialProto.send();
|
|
|
|
beepSerialOk();
|
|
}
|
|
|
|
void serialFuncWriteSettings(byte *data, byte dataSize) {
|
|
if(dataSize < 22) {
|
|
serialRespStatus(SERIAL_ERROR_SIZE);
|
|
return;
|
|
}
|
|
|
|
setNewConfig((Configuration*)&data[3]);
|
|
|
|
setTime(data[9] + 2000, data[10], data[11], data[12], data[13], data[14]);
|
|
setWakeupTime(data[15] + 2000, data[16], data[17], data[18], data[19], data[20]);
|
|
|
|
setMode(data[21]);
|
|
|
|
serialRespStatus(SERIAL_OK);
|
|
}
|
|
|
|
void serialFuncEraseLog() {
|
|
#ifdef USE_I2C_EEPROM
|
|
i2cEepromErase();
|
|
#endif
|
|
logNextRecordAddress = 0;
|
|
serialRespStatus(SERIAL_OK);
|
|
}
|
|
|
|
void serialRespStatus(uint8_t code) {
|
|
serialProto.start(SERIAL_RESP_STATUS);
|
|
serialProto.add(code);
|
|
serialProto.send();
|
|
|
|
if(code) {
|
|
beepSerialError();
|
|
} else {
|
|
beepSerialOk();
|
|
}
|
|
}
|
|
|
|
void storeConfig() {
|
|
writeConfig((uint8_t*)&config, sizeof(Configuration), EEPROM_CONFIG_ADDR);
|
|
}
|
|
|
|
void storeAuthPassword() {
|
|
writeConfig(ntagAuthPassword, sizeof(uint32_t), EEPROM_AUTH_PASSWORD_ADDR);
|
|
}
|
|
|