commit 8d8dd74bc65f80d202e612fdb619158fb3e5d87d Author: CismonX Date: Mon May 28 22:02:26 2018 +0800 archive diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..83a5457 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 CismonX + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..feb7fbf --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# BBB-Simple-ACS + +Simple access control system using Beaglebone Black. + +## 1. Documentation + +### 1.1 Requirements + +* Beaglebone Black with official Debian installed. +* RFID-RC522 module using SPI bus. +* OLED screen driven by SSD1306 using SPI bus (with an extra D/C pin). +* Internet connection. +* A server. + +### 1.2 Dependencies (client) + +* The Boost C++ Libraries (v1.62) +* Boost.Beast (v124 release) +* The mysql++ library. +* The qrencode library. +* The Boost helper JSON header. + +### 1.3 Notes + +* Build and install client/lib first before building the client. +* The server code may no longer work since some of dependencies are removed from Packagist. Luckily, the server code is simple enough to be easily rewritten using any PHP web framework. diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..b98ad3d --- /dev/null +++ b/client/Makefile @@ -0,0 +1,16 @@ +SOURCES = $(wildcard *.cpp) +OBJECTS = $(SOURCES:%.cpp=%.o) +APPLICATION = acs +CXXFLAGS = -Wall -c -g -O0 -I/usr/include/mysql -std=c++14 -o +LDFLAGS = -lacsdriver -lboost_system -lpthread -lmysqlpp -lqrencode + +all: ${OBJECTS} ${APPLICATION} + +${APPLICATION}: ${OBJECTS} + ${CXX} -o $@ ${OBJECTS} ${LDFLAGS} + +${OBJECTS}: + ${CXX} ${CXXFLAGS} $@ ${@:%.o=%.cpp} + +clean: + rm -f ${APPLICATION} ${OBJECTS} \ No newline at end of file diff --git a/client/access_controller.cpp b/client/access_controller.cpp new file mode 100644 index 0000000..471c9a9 --- /dev/null +++ b/client/access_controller.cpp @@ -0,0 +1,50 @@ + +#include "access_controller.hpp" +#include "factory.hpp" + +namespace acs +{ + access_controller::access_controller() : state_( + factory::get()->get_state()), extra_gpio_( + factory::get()->get_devices()->extra()), reader_( + factory::get()->get_reader()), writer_( + factory::get()->get_writer()), timer_( + factory::get()->get_await_timer()) {} + + void access_controller::authorized() + { + state_->set_status(state::entry_await); + writer_->authorized(); + extra_gpio_->stream_write(gpio::high); + wait(4'000); + } + + void access_controller::forbidden() + { + state_->set_status(state::entry_await); + writer_->access_denied(); + wait(1'000); + } + + void access_controller::force_reset_if_idle() + { + if (state_->get_status() == state::idle) + reset({ }); + } + + void access_controller::wait(unsigned duration) + { + timer_->defer_with(duration, boost::bind( + &access_controller::reset, this, boost::asio::placeholders::error)); + } + + void access_controller::reset(const boost::system::error_code& ec) + { + if (ec) + exit(1); + writer_->refresh(); + state_->set_status(state::idle); + extra_gpio_->stream_write(gpio::low); + writer_->write_qr_code(); + } +} diff --git a/client/access_controller.hpp b/client/access_controller.hpp new file mode 100644 index 0000000..5e9953d --- /dev/null +++ b/client/access_controller.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +namespace boost { + namespace system { + class error_code; + } +} + +namespace acs +{ + class state; + class card_reader; + class oled_writer; + class await_timer; + + class access_controller + { + state* state_; + + gpio* extra_gpio_; + + card_reader* reader_; + + oled_writer* writer_; + + await_timer* timer_; + + void wait(unsigned duration); + + void reset(const boost::system::error_code& ec); + + public: + + explicit access_controller(); + + void authorized(); + + void forbidden(); + + void force_reset_if_idle(); + }; +} diff --git a/client/await_timer.cpp b/client/await_timer.cpp new file mode 100644 index 0000000..7e26a57 --- /dev/null +++ b/client/await_timer.cpp @@ -0,0 +1,16 @@ +#include "await_timer.hpp" + +#include "factory.hpp" + +namespace acs +{ + void await_timer::callback_handler(const boost::system::error_code& ec) + { + user_cb_(ec); + } + + await_timer::await_timer() : loop_(factory::get()->get_loop()), timer_(*loop_->get_io_service()) + { + + } +} diff --git a/client/await_timer.hpp b/client/await_timer.hpp new file mode 100644 index 0000000..7217a37 --- /dev/null +++ b/client/await_timer.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace acs +{ + class loop; + class await_timer + { + loop* loop_; + boost::asio::deadline_timer timer_; + std::function user_cb_; + void callback_handler(const boost::system::error_code& ec); + public: + explicit await_timer(); + template + void defer_with(int duration, F&& func) + { + user_cb_ = func; + timer_.expires_from_now(boost::posix_time::millisec(duration)); + timer_.async_wait(boost::bind( + &await_timer::callback_handler, this, boost::asio::placeholders::error)); + } + }; + + +} diff --git a/client/card_reader.cpp b/client/card_reader.cpp new file mode 100644 index 0000000..13aa1d4 --- /dev/null +++ b/client/card_reader.cpp @@ -0,0 +1,49 @@ + +#include "card_reader.hpp" +#include "factory.hpp" +#include + +namespace acs +{ + card_reader::card_reader() + { + rfid_ = factory::get()->get_devices()->rfid(); + rfid_->pcd_init(); + } + + uint32_t card_reader::find_card() + { + if (!rfid_->picc_is_new_card_present()) + return 0; + if (!rfid_->picc_read_card_serial()) + return 0; + last_uid_ = rfid_->uid; + uint32_t retval; + memcpy(&retval, last_uid_.uid_byte, 4); + return retval; + } + + std::string card_reader::get_student_number() + { + auto status = rfid_->pcd_authenticate( + mfrc522::PICC_CMD_MF_AUTH_KEY_A, 1, &key_, &last_uid_); + if (status != mfrc522::STATUS_OK) + return {}; + byte buffer[18]; + byte len = 18; + status = rfid_->mifare_read(1, buffer, &len); + if (status != mfrc522::STATUS_OK || len != 18) + return {}; + std::string retval; + for (auto i = 0; i < 10; ++i) + { + if (buffer[i] < 0x30) + break; + retval += buffer[i]; + } + rfid_->pcd_stop_crypto1(); + return retval; + } + + mfrc522::mifare_key card_reader::key_ = { { 1, 2, 3, 4, 5, 6 } }; +} diff --git a/client/card_reader.hpp b/client/card_reader.hpp new file mode 100644 index 0000000..9c968da --- /dev/null +++ b/client/card_reader.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace acs +{ + class card_reader + { + mfrc522* rfid_ = nullptr; + mfrc522::uid_t last_uid_; + static mfrc522::mifare_key key_; + public: + explicit card_reader(); + + uint32_t find_card(); + + std::string get_student_number(); + }; +} \ No newline at end of file diff --git a/client/devices.hpp b/client/devices.hpp new file mode 100644 index 0000000..58f8462 --- /dev/null +++ b/client/devices.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +namespace acs +{ + class devices + { + mfrc522 rfid_; + ssd1306 oled_; + gpio extra_; + + public: + explicit devices() : rfid_(2, 0, 27), oled_(1, 0, 22, 61), extra_(44) + { + extra_.set_direction(gpio::output); + extra_.stream_open(); + extra_.stream_write(gpio::low); + } + + mfrc522* rfid() + { + return &rfid_; + } + + ssd1306* oled() + { + return &oled_; + } + + gpio* extra() + { + return &extra_; + } + }; +} \ No newline at end of file diff --git a/client/factory.cpp b/client/factory.cpp new file mode 100644 index 0000000..8357255 --- /dev/null +++ b/client/factory.cpp @@ -0,0 +1,6 @@ +#include "factory.hpp" + +namespace acs +{ + factory factory::singleton_; +} \ No newline at end of file diff --git a/client/factory.hpp b/client/factory.hpp new file mode 100644 index 0000000..60d77ca --- /dev/null +++ b/client/factory.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include "devices.hpp" +#include "card_reader.hpp" +#include "oled_writer.hpp" +#include "state.hpp" +#include "loop.hpp" +#include "reader_timer.hpp" +#include "await_timer.hpp" +#include "mysql_conn.hpp" +#include "qr_encoder.hpp" +#include "request_delegate.hpp" +#include "access_controller.hpp" +#include "request_timer.hpp" + +namespace acs +{ + class factory + { + devices devices_; + + loop loop_; + + card_reader reader_; + + oled_writer writer_; + + state state_; + + reader_timer reader_timer_; + + await_timer await_timer_; + + mysql_conn mysql_conn_; + + qr_encoder qr_encoder_; + + request_delegate request_delegate_; + + access_controller access_controller_; + + request_timer request_timer_; + + static factory singleton_; + + explicit factory() = default; + + public: + + static factory* get() + { + return &singleton_; + } + + devices* get_devices() + { + return &devices_; + } + + loop* get_loop() + { + return &loop_; + } + + card_reader* get_reader() + { + return &reader_; + } + + oled_writer* get_writer() + { + return &writer_; + } + + state* get_state() + { + return &state_; + } + + reader_timer* get_reader_timer() + { + return &reader_timer_; + } + + await_timer* get_await_timer() + { + return &await_timer_; + } + + mysql_conn* get_mysql() + { + return &mysql_conn_; + } + + qr_encoder* get_qr_encoder() + { + return &qr_encoder_; + } + + request_delegate* get_request_delegate() + { + return &request_delegate_; + } + + access_controller* get_controller() + { + return &access_controller_; + } + }; +} diff --git a/client/http_client.cpp b/client/http_client.cpp new file mode 100644 index 0000000..7d96700 --- /dev/null +++ b/client/http_client.cpp @@ -0,0 +1,76 @@ +#include "http_client.hpp" +#include +#include +#include + +#include +#include "factory.hpp" + +namespace acs +{ + void http_client::on_resolve(const boost::system::error_code& ec, const tcp::resolver::iterator& iterator) + { + if (ec) + return fail("Resolve failed.", ec); + async_connect(socket_, iterator, boost::bind( + &http_client::on_connect, this, boost::asio::placeholders::error)); + } + + void http_client::on_connect(const boost::system::error_code& ec) + { + if (ec) + return fail("Connect failed.", ec); + http::async_write(socket_, request_, boost::bind( + &http_client::on_write, this, boost::asio::placeholders::error)); + } + + + void http_client::on_write(const boost::system::error_code& ec) + { + if (ec) + return fail("Failed to send request.", ec); + http::async_read(socket_, buffer_, response_, boost::bind( + &http_client::on_read, this, boost::asio::placeholders::error)); + } + + void http_client::on_read(const boost::system::error_code& ec) + { + if (ec) + return fail("Failed to fetch responce.", ec); + callback_(buffers_to_string(response_.body().data())); + reset(); + } + + void http_client::fail(const std::string& message, const boost::beast::error_code& ec) + { + std::cerr << message << " Message: " << ec.message() << std::endl; + reset(); + callback_(""); + } + + void http_client::reset() + { + buffer_.consume(buffer_.max_size()); + buffer_.shrink_to_fit(); + request_.clear(); + response_.clear(); + } + + http_client::http_client() : io_service_( + factory::get()->get_loop()->get_io_service()), resolver_(*io_service_), socket_(*io_service_) + { + + } + + void http_client::async_get(const std::string& host, const std::string& query, + const std::function& callback) + { + host_ = host; + callback_ = callback; + request_ = { http::verb::get, query, 11 }; + request_.set(http::field::host, host_); + request_.prepare_payload(); + resolver_.async_resolve({ host, "7722" }, boost::bind(&http_client::on_resolve, this, + boost::asio::placeholders::error, boost::asio::placeholders::iterator)); + } +} diff --git a/client/http_client.hpp b/client/http_client.hpp new file mode 100644 index 0000000..1b5f11e --- /dev/null +++ b/client/http_client.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +using tcp = boost::asio::ip::tcp; +namespace http = boost::beast::http; + +namespace acs +{ + class http_client + { + boost::asio::io_service* io_service_; + + std::string host_; + + tcp::resolver resolver_; + + boost::beast::error_code ec_; + + boost::beast::flat_buffer buffer_; + + tcp::socket socket_; + + http::response response_; + + http::request request_; + + std::function callback_; + + void on_resolve(const boost::system::error_code& ec, const tcp::resolver::iterator& iterator); + + void on_connect(const boost::system::error_code& ec); + + void on_write(const boost::system::error_code& ec); + + void on_read(const boost::system::error_code& ec); + + void fail(const std::string& message, const boost::beast::error_code& ec); + + void reset(); + + public: + + explicit http_client(); + + void async_get( + const std::string& host, + const std::string& query, + const std::function& callback); + }; +} \ No newline at end of file diff --git a/client/lib/Makefile b/client/lib/Makefile new file mode 100644 index 0000000..559c486 --- /dev/null +++ b/client/lib/Makefile @@ -0,0 +1,21 @@ +SOURCES = $(wildcard *.cpp) +HEADERS = $(wildcard *.hpp) +OBJECTS = $(SOURCES:%.cpp=%.o) +LIBRARY = libacsdriver.so +CXXFLAGS = -Wall -c -fpic -g -O0 -std=c++14 -o +LDFLAGS = -shared + +all: ${OBJECTS} ${LIBRARY} + +${LIBRARY}: ${OBJECTS} + ${CXX} -o $@ ${OBJECTS} ${LDFLAGS} + +${OBJECTS}: + ${CXX} ${CXXFLAGS} $@ ${@:%.o=%.cpp} + +clean: + rm -f ${LIBRARY} ${OBJECTS} + +install: + cp -f ${HEADERS} /usr/include/acs-driver + cp ${LIBRARY} /usr/lib \ No newline at end of file diff --git a/client/lib/gpio.cpp b/client/lib/gpio.cpp new file mode 100644 index 0000000..cc1cbed --- /dev/null +++ b/client/lib/gpio.cpp @@ -0,0 +1,187 @@ +/* + * GPIO.cpp Created on: 29 Apr 2014 + * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie) + * Made available for the book "Exploring BeagleBone" + * If you use this code in your work please cite: + * Derek Molloy, "Exploring BeagleBone: Tools and Techniques for Building + * with Embedded Linux", Wiley, 2014, ISBN:9781118935125. + * See: www.exploringbeaglebone.com + * Licensed under the EUPL V.1.1 + * + * This Software is provided to You under the terms of the European + * Union Public License (the "EUPL") version 1.1 as published by the + * European Union. Any use of this Software, other than as authorized + * under this License is strictly prohibited (to the extent such use + * is covered by a right of the copyright holder of this Software). + * + * This Software is provided under the License on an "AS IS" basis and + * without warranties of any kind concerning the Software, including + * without limitation merchantability, fitness for a particular purpose, + * absence of defects or errors, accuracy, and non-infringement of + * intellectual property rights other than copyright. This disclaimer + * of warranty is an essential part of the License and a condition for + * the grant of any rights to this Software. + * + * For more details, see http://www.derekmolloy.ie/ + */ + +#include "gpio.hpp" +#include +#include +#include +#include + +int gpio::write(const std::string& path, const std::string& filename, const std::string& value) { + std::ofstream fs; + fs.open(path + filename); + if (!fs.is_open()) { + std::cerr << "GPIO: write failed to open file." << std::endl; + return -1; + } + fs << value; + fs.close(); + return 0; +} + +std::string gpio::read(const std::string& path, const std::string& filename) { + std::ifstream fs; + fs.open(path + filename); + if (!fs.is_open()) { + std::cerr << "GPIO: read failed to open file " << std::endl; + return { }; + } + std::string input; + getline(fs, input); + fs.close(); + return input; +} + +int gpio::write(const std::string& path, const std::string& filename, int value) { + return write(path, filename, std::to_string(value)); +} + +gpio::gpio(int number) : number_(number) +{ + name_ = std::string("gpio") + std::to_string(number); + path_ = std::string(base_path) + this->name_ + "/"; + this->export_gpio(); + // Need to give Linux time to set up the sysfs structure + usleep(300'000); +} + +int gpio::export_gpio() +{ + return write(base_path, "export", number_); +} + +int gpio::unexport_gpio() +{ + return write(base_path, "unexport", number_); +} + +int gpio::set_direction(direction dir) +{ + switch (dir) { + case input: + return write(path_, "direction", "in"); + case output: + return write(path_, "direction", "out"); + } + return -1; +} + +int gpio::set_value(value value) +{ + switch (value) { + case high: + return write(path_, "value", "1"); + case low: + return write(path_, "value", "0"); + } + return -1; +} + +int gpio::set_edge_type(edge value) +{ + switch (value) { + case none: + return write(path_, "edge", "none"); + case rising: + return write(path_, "edge", "rising"); + case falling: + return write(path_, "edge", "falling"); + case both: + return write(path_, "edge", "both"); + } + return -1; +} + +int gpio::set_active_low(bool is_low) +{ + if (is_low) + return write(path_, "active_low", "1"); + return write(path_, "active_low", "0"); +} + +int gpio::set_active_high() +{ + return this->set_active_low(false); +} + +gpio::value gpio::get_value() const +{ + auto input = read(path_, "value"); + if (input == "0") + return low; + return high; +} + +gpio::direction gpio::get_direction() const +{ + auto direction = read(path_, "direction"); + if (direction == "in") + return input; + return output; +} + +gpio::edge gpio::get_edge_type() const +{ + auto edge = read(path_, "edge"); + if (edge == "rising") + return rising; + if (edge == "falling") + return falling; + if (edge == "both") + return both; + return none; +} + +int gpio::stream_open() +{ + stream_.open(path_ + "value"); + return 0; +} +int gpio::stream_write(value value) +{ + stream_ << value << std::flush; + return 0; +} +int gpio::stream_close() +{ + stream_.close(); + return 0; +} + +int gpio::toggle_output() +{ + set_direction(output); + if (get_value()) + set_value(low); + else + set_value(high); + return 0; +} + +gpio::~gpio() { + this->unexport_gpio(); +} diff --git a/client/lib/gpio.hpp b/client/lib/gpio.hpp new file mode 100644 index 0000000..e8c35cc --- /dev/null +++ b/client/lib/gpio.hpp @@ -0,0 +1,100 @@ +/* + * @file GPIO.h + * @author Derek Molloy + * @version 0.1 + * + * Created on: 29 Apr 2014 + * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie) + * Made available for the book "Exploring BeagleBone" + * See: www.exploringbeaglebone.com + * Licensed under the EUPL V.1.1 + * + * This Software is provided to You under the terms of the European + * Union Public License (the "EUPL") version 1.1 as published by the + * European Union. Any use of this Software, other than as authorized + * under this License is strictly prohibited (to the extent such use + * is covered by a right of the copyright holder of this Software). + * + * This Software is provided under the License on an "AS IS" basis and + * without warranties of any kind concerning the Software, including + * without limitation merchantability, fitness for a particular purpose, + * absence of defects or errors, accuracy, and non-infringement of + * intellectual property rights other than copyright. This disclaimer + * of warranty is an essential part of the License and a condition for + * the grant of any rights to this Software. + * + * For more details, see http://www.derekmolloy.ie/ + */ + +#pragma once + +#include +#include + +class gpio +{ +public: + enum direction + { + input, output + }; + enum value + { + low = 0, high = 1 + }; + enum edge + { + none, rising, falling, both + }; + +private: + static constexpr const char* base_path = "/sys/class/gpio/"; + /// The GPIO number of the object + int number_; + /// The name of the GPIO + std::string name_; + /// The full path to the GPIO + std::string path_; + + /// Write a string to a file. + static int write(const std::string& path, const std::string& filename, const std::string& value); + /// Write an integer to a file. + static int write(const std::string& path, const std::string& filename, int value); + /// Read a string from a file. + static std::string read(const std::string& path, const std::string& filename); + +public: + explicit gpio(int number); + int get_number() const + { + return number_; + } + + // General Input and Output Settings + int set_direction(direction); + direction get_direction() const; + int set_value(value); + int toggle_output(); + value get_value() const; + int set_active_low(bool is_low = true); + int set_active_high(); //default + + // Advanced OUTPUT: Faster write by keeping the stream alive (~20X) + int stream_open(); + int stream_write(value); + int stream_close(); + + // Advanced INPUT: Detect input edges; threaded and non-threaded + int set_edge_type(edge); + edge get_edge_type() const; + + ~gpio(); + +private: + int export_gpio(); + int unexport_gpio(); + std::ofstream stream_; +}; + + + diff --git a/client/lib/mfrc522.cpp b/client/lib/mfrc522.cpp new file mode 100644 index 0000000..15838e0 --- /dev/null +++ b/client/lib/mfrc522.cpp @@ -0,0 +1,1325 @@ +/* + * MFRC522.cpp - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT. + * NOTE: Please also check the comments in MFRC522.h - they provide useful hints and background information. + * Released into the public domain. + */ + +#include "mfrc522.hpp" + +#include +#include + +const byte mfrc522::mfrc522_firmware_reference_v0_0[] = { + 0x00, 0x87, 0x98, 0x0f, 0x49, 0xFF, 0x07, 0x19, + 0xBF, 0x22, 0x30, 0x49, 0x59, 0x63, 0xAD, 0xCA, + 0x7F, 0xE3, 0x4E, 0x03, 0x5C, 0x4E, 0x49, 0x50, + 0x47, 0x9A, 0x37, 0x61, 0xE7, 0xE2, 0xC6, 0x2E, + 0x75, 0x5A, 0xED, 0x04, 0x3D, 0x02, 0x4B, 0x78, + 0x32, 0xFF, 0x58, 0x3B, 0x7C, 0xE9, 0x00, 0x94, + 0xB4, 0x4A, 0x59, 0x5B, 0xFD, 0xC9, 0x29, 0xDF, + 0x35, 0x96, 0x98, 0x9E, 0x4F, 0x30, 0x32, 0x8D +}; + +const byte mfrc522::mfrc522_firmware_reference_v1_0[] = { + 0x00, 0xC6, 0x37, 0xD5, 0x32, 0xB7, 0x57, 0x5C, + 0xC2, 0xD8, 0x7C, 0x4D, 0xD9, 0x70, 0xC7, 0x73, + 0x10, 0xE6, 0xD2, 0xAA, 0x5E, 0xA1, 0x3E, 0x5A, + 0x14, 0xAF, 0x30, 0x61, 0xC9, 0x70, 0xDB, 0x2E, + 0x64, 0x22, 0x72, 0xB5, 0xBD, 0x65, 0xF4, 0xEC, + 0x22, 0xBC, 0xD3, 0x72, 0x35, 0xCD, 0xAA, 0x41, + 0x1F, 0xA7, 0xF3, 0x53, 0x14, 0xDE, 0x7E, 0x02, + 0xD9, 0x0F, 0xB5, 0x5E, 0x25, 0x1D, 0x29, 0x79 +}; + +const byte mfrc522::mfrc522_firmware_reference_v2_0[] = { + 0x00, 0xEB, 0x66, 0xBA, 0x57, 0xBF, 0x23, 0x95, + 0xD0, 0xE3, 0x0D, 0x3D, 0x27, 0x89, 0x5C, 0xDE, + 0x9D, 0x3B, 0xA7, 0x00, 0x21, 0x5B, 0x89, 0x82, + 0x51, 0x3A, 0xEB, 0x02, 0x0C, 0xA5, 0x00, 0x49, + 0x7C, 0x84, 0x4D, 0xB3, 0xCC, 0xD2, 0x1B, 0x81, + 0x5D, 0x48, 0x76, 0xD5, 0x71, 0x61, 0x21, 0xA9, + 0x86, 0x96, 0x83, 0x38, 0xCF, 0x9D, 0x5B, 0x6D, + 0xDC, 0x15, 0xBA, 0x3E, 0x7D, 0x95, 0x3B, 0x2F +}; + +const byte mfrc522::fm17522_firmware_reference[] = { + 0x00, 0xD6, 0x78, 0x8C, 0xE2, 0xAA, 0x0C, 0x18, + 0x2A, 0xB8, 0x7A, 0x7F, 0xD3, 0x6A, 0xCF, 0x0B, + 0xB1, 0x37, 0x63, 0x4B, 0x69, 0xAE, 0x91, 0xC7, + 0xC3, 0x97, 0xAE, 0x77, 0xF4, 0x37, 0xD7, 0x9B, + 0x7C, 0xF5, 0x3C, 0x11, 0x8F, 0x15, 0xC3, 0xD7, + 0xC1, 0x5B, 0x00, 0x2A, 0xD0, 0x75, 0xDE, 0x9E, + 0x51, 0x64, 0xAB, 0x3E, 0xE9, 0x15, 0xB5, 0xAB, + 0x56, 0x9A, 0x98, 0x82, 0x26, 0xEA, 0x2A, 0x62 +}; + +///////////////////////////////////////////////////////////////////////////////////// +// Basic interface functions for communicating with the MFRC522 +///////////////////////////////////////////////////////////////////////////////////// + +mfrc522::mfrc522( + unsigned bus, unsigned device, unsigned reset +) : spi_(bus, device), reset_(reset) {} + +/** + * Writes a byte to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void mfrc522::pcd_write_register( + pcd_register reg, ///< The register to write to. One of the PCD_Register enums. + byte value ///< The value to write. +) { + byte buf[2] = { reg, value }; + spi_.write(buf, 2); +} // End PCD_WriteRegister() + +/** + * Writes a number of bytes to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void mfrc522::pcd_write_register( + pcd_register reg, ///< The register to write to. One of the PCD_Register enums. + byte count, ///< The number of bytes to write to the register + byte *values ///< The values to write. Byte array. +) { + byte buf[count + 1] = { reg }; + memcpy(&buf[1], values, count); + spi_.write(buf, count + 1); +} // End PCD_WriteRegister() + +/** + * Reads a byte from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +byte mfrc522::pcd_read_register( + pcd_register reg ///< The register to read from. One of the PCD_Register enums. +) { + auto value = spi_.read_register(reg); // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. + spi_.write(0); // TODO: is this correct? + return value; +} // End PCD_ReadRegister() + +/** + * Reads a number of bytes from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void mfrc522::pcd_read_register( + pcd_register reg, ///< The register to read from. One of the PCD_Register enums. + byte count, ///< The number of bytes to read + byte *values, ///< Byte array to store the values in. + byte rx_align ///< Only bit positions rxAlign..7 in values[0] are updated. +) { + if (count == 0) { + return; + } + //Serial.print(F("Reading ")); Serial.print(count); printf(" bytes from register.")); + //byte address = reg; // MSB == 1 is for reading. LSB is not used in address. Datasheet section 8.1.2.3. + byte index = 1; // Index in values array. + //count--; // One read is performed outside of the loop + byte value = spi_.read_register(reg); // Tell MFRC522 which address we want to read + if (rx_align) { // Only update bit positions rxAlign..7 in values[0] + // Create bit mask for bit positions rxAlign..7 + byte mask = (0xFF << rx_align) & 0xFF; + + // Apply mask to both current value of values[0] and the new data in value. + values[0] = (values[0] & ~mask) | (value & mask); + } + else { + values[0] = value; + } + while (index < count) { + values[index] = spi_.read_register(reg); // Read value and tell that we want to read the same address again. + index++; + } + spi_.write(0); // Read the final byte. Send 0 to stop reading. +} // End PCD_ReadRegister() + +/** + * Sets the bits given in mask in register reg. + */ +void mfrc522::pcd_set_register_bit_mask( + pcd_register reg, ///< The register to update. One of the PCD_Register enums. + byte mask ///< The bits to set. +) { + byte tmp; + tmp = pcd_read_register(reg); + pcd_write_register(reg, tmp | mask); // set bit mask +} // End PCD_SetRegisterBitMask() + +/** + * Clears the bits given in mask from register reg. + */ +void mfrc522::pcd_clear_register_bit_mask( + pcd_register reg, ///< The register to update. One of the PCD_Register enums. + byte mask ///< The bits to clear. +) { + byte tmp; + tmp = pcd_read_register(reg); + pcd_write_register(reg, tmp & (~mask)); // clear bit mask +} // End PCD_ClearRegisterBitMask() + + +/** + * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::pcd_calculate_crc( + byte *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. + byte length, ///< In: The number of bytes to transfer. + byte *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low byte first. +) { + pcd_write_register(CommandReg, PCD_Idle); // Stop any active command. + pcd_write_register(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit + pcd_write_register(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization + pcd_write_register(FIFODataReg, length, data); // Write data to the FIFO + pcd_write_register(CommandReg, PCD_CalcCRC); // Start the calculation + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs. + // TODO check/modify for other architectures than Arduino Uno 16bit + + // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. + for (uint16_t i = 5000; i > 0; i--) { + // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved + auto n = pcd_read_register(DivIrqReg); + if (n & 0x04) { // CRCIRq bit set - calculation done + pcd_write_register(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. + // Transfer the result from the registers to the result buffer + result[0] = pcd_read_register(CRCResultRegL); + result[1] = pcd_read_register(CRCResultRegH); + return STATUS_OK; + } + } + // 89ms passed and nothing happend. Communication with the MFRC522 might be down. + return STATUS_TIMEOUT; +} // End PCD_CalculateCRC() + + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for manipulating the MFRC522 +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the MFRC522 chip. + */ +void mfrc522::pcd_init() +{ + reset_.set_direction(gpio::output); + reset_.set_value(gpio::low); + usleep(50'000); + reset_.set_value(gpio::high); + + // Reset baud rates + pcd_write_register(TxModeReg, 0x00); + pcd_write_register(RxModeReg, 0x00); + // Reset ModWidthReg + pcd_write_register(ModWidthReg, 0x26); + + // When communicating with a PICC we need a timeout if something goes wrong. + // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. + // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. + pcd_write_register(TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds + pcd_write_register(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. + pcd_write_register(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. + pcd_write_register(TReloadRegL, 0xE8); + + pcd_write_register(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting + pcd_write_register(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) + pcd_antenna_on(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) +} // End PCD_Init() + + +/** + * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. + */ +void mfrc522::pcd_reset() +{ + pcd_write_register(CommandReg, PCD_SoftReset); // Issue the SoftReset command. + // The datasheet does not mention how long the SoftRest command takes to complete. + // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) + // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let us be generous: 50ms. + uint8_t count = 0; + do { + // Wait for the PowerDown bit in CommandReg to be cleared (max 3x50ms) + usleep(50'000); + } while ((pcd_read_register(CommandReg) & (1 << 4)) && (++count) < 3); +} // End PCD_Reset() + +/** + * Turns the antenna on by enabling pins TX1 and TX2. + * After a reset these pins are disabled. + */ +void mfrc522::pcd_antenna_on() +{ + auto value = pcd_read_register(TxControlReg); + if ((value & 0x03) != 0x03) { + pcd_write_register(TxControlReg, value | 0x03); + } +} // End PCD_AntennaOn() + +/** + * Turns the antenna off by disabling pins TX1 and TX2. + */ +void mfrc522::pcd_antenna_off() +{ + pcd_clear_register_bit_mask(TxControlReg, 0x03); +} // End PCD_AntennaOff() + +/** + * Get the current MFRC522 Receiver Gain (RxGain[2:0]) value. + * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * NOTE: Return value scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved bits. + * + * @return Value of the RxGain, scrubbed to the 3 bits used. + */ +byte mfrc522::pcd_get_antenna_gain() +{ + return pcd_read_register(RFCfgReg) & (0x07 << 4); +} // End PCD_GetAntennaGain() + +/** + * Set the MFRC522 Receiver Gain (RxGain) to value specified by given mask. + * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * NOTE: Given mask is scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved bits. + */ +void mfrc522::pcd_set_antenna_gain(byte mask) +{ + if (pcd_get_antenna_gain() != mask) { // only bother if there is a change + pcd_clear_register_bit_mask(RFCfgReg, (0x07 << 4)); // clear needed to allow 000 pattern + pcd_set_register_bit_mask(RFCfgReg, mask & (0x07 << 4)); // only set RxGain[2:0] bits + } +} // End PCD_SetAntennaGain() + +/** + * Performs a self-test of the MFRC522 + * See 16.1.1 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * + * @return Whether or not the test passed. Or false if no firmware reference is available. + */ +bool mfrc522::pcd_perform_self_test() { + // This follows directly the steps outlined in 16.1.1 + // 1. Perform a soft reset. + pcd_reset(); + + // 2. Clear the internal buffer by writing 25 bytes of 00h + byte zeroes[25] = { 0 }; + pcd_write_register(FIFOLevelReg, 0x80); // flush the FIFO buffer + pcd_write_register(FIFODataReg, 25, zeroes); // write 25 bytes of 00h to FIFO + pcd_write_register(CommandReg, PCD_Mem); // transfer to internal buffer + + // 3. Enable self-test + pcd_write_register(AutoTestReg, 0x09); + + // 4. Write 00h to FIFO buffer + pcd_write_register(FIFODataReg, 0x00); + + // 5. Start self-test by issuing the CalcCRC command + pcd_write_register(CommandReg, PCD_CalcCRC); + + // 6. Wait for self-test to complete + byte n; + for (uint8_t i = 0; i < 0xFF; i++) { + // The datasheet does not specify exact completion condition except + // that FIFO buffer should contain 64 bytes. + // While selftest is initiated by CalcCRC command + // it behaves differently from normal CRC computation, + // so one can't reliably use DivIrqReg to check for completion. + // It is reported that some devices does not trigger CRCIRq flag + // during selftest. + n = pcd_read_register(FIFOLevelReg); + if (n >= 64) { + break; + } + } + pcd_write_register(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. + + // 7. Read out resulting 64 bytes from the FIFO buffer. + byte result[64]; + pcd_read_register(FIFODataReg, 64, result, 0); + + // Auto self-test done + // Reset AutoTestReg register to be 0 again. Required for normal operation. + pcd_write_register(AutoTestReg, 0x00); + + // Determine firmware version (see section 9.3.4.8 in spec) + auto version = pcd_read_register(VersionReg); + + // Pick the appropriate reference values + const byte *reference; + switch (version) { + case 0x88: // Fudan Semiconductor FM17522 clone + reference = fm17522_firmware_reference; + break; + case 0x90: // Version 0.0 + reference = mfrc522_firmware_reference_v0_0; + break; + case 0x91: // Version 1.0 + reference = mfrc522_firmware_reference_v1_0; + break; + case 0x92: // Version 2.0 + reference = mfrc522_firmware_reference_v2_0; + break; + default: // Unknown version + return false; // abort test + } + + // Verify that the results match up to our expectations + for (uint8_t i = 0; i < 64; i++) { + // TODO: is this correct? + if (result[i] != reference[i]) { + return false; + } + } + + // Test passed; all is good. + return true; +} // End PCD_PerformSelfTest() + +///////////////////////////////////////////////////////////////////////////////////// +// Power control +///////////////////////////////////////////////////////////////////////////////////// + +//IMPORTANT NOTE!!!! +//Calling any other function that uses CommandReg will disable soft power down mode !!! +//For more details about power control, refer to the datasheet - page 33 (8.6) + +void mfrc522::pcd_soft_power_down() // Note : Only soft power down mode is available throught software +{ + auto val = pcd_read_register(CommandReg); // Read state of the command register + val |= (1<<4);// set PowerDown bit ( bit 4 ) to 1 + pcd_write_register(CommandReg, val);//write new value to the command register +} + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for communicating with PICCs +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Executes the Transceive command. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::pcd_transceive_data( + byte *send_data, ///< Pointer to the data to transfer to the FIFO. + byte send_len, ///< Number of bytes to transfer to the FIFO. + byte *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. + byte *back_len, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. + byte *valid_bits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. Default nullptr. + byte rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. + bool check_crc ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. + ) { + byte waitIRq = 0x30; // RxIRq and IdleIRq + return pcd_communicate_with_picc(PCD_Transceive, waitIRq, + send_data, send_len, back_data, back_len, valid_bits, rx_align, check_crc); +} // End PCD_TransceiveData() + +/** + * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::pcd_communicate_with_picc( + byte command, ///< The command to execute. One of the PCD_Command enums. + byte wait_irq, ///< The bits in the ComIrqReg register that signals successful completion of the command. + byte *send_data, ///< Pointer to the data to transfer to the FIFO. + byte send_len, ///< Number of bytes to transfer to the FIFO. + byte *back_data, ///< nullptr or pointer to buffer if data should be read back after executing the command. + byte *back_len, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. + byte *valid_bits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. + byte rx_align, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. + bool check_crc ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. +) { + // Prepare values for BitFramingReg + byte txLastBits = valid_bits ? *valid_bits : 0; + byte bitFraming = (rx_align << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + pcd_write_register(CommandReg, PCD_Idle); // Stop any active command. + pcd_write_register(ComIrqReg, 0x7F); // Clear all seven interrupt request bits + pcd_write_register(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization + pcd_write_register(FIFODataReg, send_len, send_data); // Write sendData to the FIFO + pcd_write_register(BitFramingReg, bitFraming); // Bit adjustments + pcd_write_register(CommandReg, command); // Execute the command + if (command == PCD_Transceive) { + pcd_set_register_bit_mask(BitFramingReg, 0x80); // StartSend=1, transmission of data starts + } + + // Wait for the command to complete. + // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops transmitting. + // Each iteration of the do-while-loop takes 17.86μs. + // TODO check/modify for other architectures than Arduino Uno 16bit + uint16_t i; + for (i = 2000; i > 0; i--) { + byte n = pcd_read_register(ComIrqReg); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq + if (n & wait_irq) { // One of the interrupts that signal success has been set. + break; + } + if (n & 0x01) { // Timer interrupt - nothing received in 25ms + return STATUS_TIMEOUT; + } + } + // 35.7ms and nothing happend. Communication with the MFRC522 might be down. + if (i == 0) { + return STATUS_TIMEOUT; + } + + // Stop now if any errors except collisions were detected. + auto error_reg_value = pcd_read_register(ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr + if (error_reg_value & 0x13) { // BufferOvfl ParityErr ProtocolErr + return STATUS_ERROR; + } + + byte _validBits = 0; + + // If the caller wants data back, get it from the MFRC522. + if (back_data && back_len) { + byte n = pcd_read_register(FIFOLevelReg); // Number of bytes in the FIFO + if (n > *back_len) { + return STATUS_NO_ROOM; + } + *back_len = n; // Number of bytes returned + pcd_read_register(FIFODataReg, n, back_data, rx_align); // Get received data from FIFO + _validBits = pcd_read_register(ControlReg) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last received byte. If this value is 000b, the whole byte is valid. + if (valid_bits) { + *valid_bits = _validBits; + } + } + + // Tell about collisions + if (error_reg_value & 0x08) { // CollErr + return STATUS_COLLISION; + } + + // Perform CRC_A validation if requested. + if (back_data && back_len && check_crc) { + // In this case a MIFARE Classic NAK is not OK. + if (*back_len == 1 && _validBits == 4) { + return STATUS_MIFARE_NACK; + } + // We need at least the CRC_A value and all 8 bits of the last byte must be received. + if (*back_len < 2 || _validBits != 0) { + return STATUS_CRC_WRONG; + } + // Verify CRC_A - do our own calculation and store the control in controlBuffer. + byte control_buffer[2]; + auto status = pcd_calculate_crc(&back_data[0], *back_len - 2, &control_buffer[0]); + if (status != STATUS_OK) { + return status; + } + if ((back_data[*back_len - 2] != control_buffer[0]) || (back_data[*back_len - 1] != control_buffer[1])) { + return STATUS_CRC_WRONG; + } + } + + return STATUS_OK; +} // End PCD_CommunicateWithPICC() + +/** + * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::picc_request_a( + byte *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + byte *buffer_size ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. +) { + return picc_reqa_or_wupa(PICC_CMD_REQA, buffer_atqa, buffer_size); +} // End PICC_RequestA() + +/** + * Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::picc_wakeup_a( + byte *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + byte *buffer_size ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. +) { + return picc_reqa_or_wupa(PICC_CMD_WUPA, buffer_atqa, buffer_size); +} // End PICC_WakeupA() + +/** + * Transmits REQA or WUPA commands. + * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::picc_reqa_or_wupa( + byte command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + byte *buffer_atqa, ///< The buffer to store the ATQA (Answer to request) in + byte *buffer_size ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. +) { + + if (buffer_atqa == nullptr || *buffer_size < 2) { // The ATQA response is 2 bytes long. + return STATUS_NO_ROOM; + } + pcd_clear_register_bit_mask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + byte valid_bits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) byte. TxLastBits = BitFramingReg[2..0] + auto status = pcd_transceive_data(&command, 1, buffer_atqa, buffer_size, &valid_bits); + if (status != STATUS_OK) { + return status; + } + if (*buffer_size != 2 || valid_bits != 0) { // ATQA must be exactly 16 bits. + return STATUS_ERROR; + } + return STATUS_OK; +} // End PICC_REQA_or_WUPA() + +/** + * Transmits SELECT/ANTICOLLISION commands to select a single PICC. + * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or PICC_WakeupA(). + * On success: + * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the ISO/IEC 14443-3 draft.) + * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. + * + * A PICC UID consists of 4, 7 or 10 bytes. + * Only 4 bytes can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: + * UID size Number of UID bytes Cascade levels Example of PICC + * ======== =================== ============== =============== + * single 4 1 MIFARE Classic + * double 7 2 MIFARE Ultralight + * triple 10 3 Not currently in use? + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::picc_select( + uid_t *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. + byte valid_bits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply uid->size. +) { + bool use_cascade_tag; + byte cascade_level = 1; + status_code result; + byte count; + byte uid_index; // The first index in uid->uidByte[] that is used in the current Cascade Level. + byte buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A + byte buffer_used; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO. + byte tx_last_bits; // Used in BitFramingReg. The number of valid bits in the last transmitted byte. + byte *response_buffer; + byte response_length; + + // Description of buffer structure: + // Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 + // Byte 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits. + // Byte 2: UID-data or CT See explanation below. CT means Cascade Tag. + // Byte 3: UID-data + // Byte 4: UID-data + // Byte 5: UID-data + // Byte 6: BCC Block Check Character - XOR of bytes 2-5 + // Byte 7: CRC_A + // Byte 8: CRC_A + // The BCC and CRC_A are only transmitted if we know all the UID bits of the current Cascade Level. + // + // Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) + // UID size Cascade level Byte2 Byte3 Byte4 Byte5 + // ======== ============= ===== ===== ===== ===== + // 4 bytes 1 uid0 uid1 uid2 uid3 + // 7 bytes 1 CT uid0 uid1 uid2 + // 2 uid3 uid4 uid5 uid6 + // 10 bytes 1 CT uid0 uid1 uid2 + // 2 CT uid3 uid4 uid5 + // 3 uid6 uid7 uid8 uid9 + + // Sanity checks + if (valid_bits > 80) { + return STATUS_INVALID; + } + + // Prepare MFRC522 + pcd_clear_register_bit_mask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. + + // Repeat Cascade Level loop until we have a complete UID. + auto uid_complete = false; + while (!uid_complete) { + // Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2. + switch (cascade_level) { + case 1: + buffer[0] = PICC_CMD_SEL_CL1; + uid_index = 0; + use_cascade_tag = valid_bits && uid->size > 4; // When we know that the UID has more than 4 bytes + break; + + case 2: + buffer[0] = PICC_CMD_SEL_CL2; + uid_index = 3; + use_cascade_tag = valid_bits && uid->size > 7; // When we know that the UID has more than 7 bytes + break; + + case 3: + buffer[0] = PICC_CMD_SEL_CL3; + uid_index = 6; + use_cascade_tag = false; // Never used in CL3. + break; + + default: + return STATUS_INTERNAL_ERROR; + } + + // How many UID bits are known in this Cascade Level? + int8_t current_level_known_bits = valid_bits - (8 * uid_index); + if (current_level_known_bits < 0) { + current_level_known_bits = 0; + } + // Copy the known bits from uid->uidByte[] to buffer[] + byte index = 2; // destination index in buffer[] + if (use_cascade_tag) { + buffer[index++] = PICC_CMD_CT; + } + byte bytesToCopy = current_level_known_bits / 8 + (current_level_known_bits % 8 ? 1 : 0); // The number of bytes needed to represent the known bits for this level. + if (bytesToCopy) { + byte maxBytes = use_cascade_tag ? 3 : 4; // Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag + if (bytesToCopy > maxBytes) { + bytesToCopy = maxBytes; + } + for (count = 0; count < bytesToCopy; count++) { + buffer[index++] = uid->uid_byte[uid_index + count]; + } + } + // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits + if (use_cascade_tag) { + current_level_known_bits += 8; + } + + // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. + auto select_done = false; + while (!select_done) { + // Find out how many bits and bytes to send and receive. + if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT. + //Serial.print(F("SELECT: currentLevelKnownBits=")); printf(currentLevelKnownBits, DEC); + buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole bytes + // Calculate BCC - Block Check Character + buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; + // Calculate CRC_A + result = pcd_calculate_crc(buffer, 7, &buffer[7]); + if (result != STATUS_OK) { + return result; + } + tx_last_bits = 0; // 0 => All 8 bits are valid. + buffer_used = 9; + // Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx) + response_buffer = &buffer[6]; + response_length = 3; + } + else { // This is an ANTICOLLISION. + //Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); printf(currentLevelKnownBits, DEC); + tx_last_bits = current_level_known_bits % 8; + count = current_level_known_bits / 8; // Number of whole bytes in the UID part. + index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs + buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits + buffer_used = index + (tx_last_bits ? 1 : 0); + // Store response in the unused part of buffer + response_buffer = &buffer[index]; + response_length = sizeof(buffer) - index; + } + + // Set bit adjustments + auto rx_align = tx_last_bits; // Having a separate variable is overkill. But it makes the next line easier to read. + pcd_write_register(BitFramingReg, (rx_align << 4) + tx_last_bits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] + + // Transmit the buffer and receive the response. + result = pcd_transceive_data(buffer, buffer_used, response_buffer, &response_length, &tx_last_bits, rx_align); + if (result == STATUS_COLLISION) { // More than one PICC in the field => collision. + byte valueOfCollReg = pcd_read_register(CollReg); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] + if (valueOfCollReg & 0x20) { // CollPosNotValid + return STATUS_COLLISION; // Without a valid collision position we cannot continue + } + byte collisionPos = valueOfCollReg & 0x1F; // Values 0-31, 0 means bit 32. + if (collisionPos == 0) { + collisionPos = 32; + } + if (collisionPos <= current_level_known_bits) { // No progress - should not happen + return STATUS_INTERNAL_ERROR; + } + // Choose the PICC with the bit set. + count = (collisionPos - 1) % 8; // The bit to modify + index = 1 + ((collisionPos - 1) / 8) + (count ? 1 : 0); // First byte is index 0. + buffer[index] |= (1 << count); + //currentLevelKnownBits = collisionPos; // FIXME not used further, maybe bug + } + else if (result != STATUS_OK) { + return result; + } + else { // STATUS_OK + if (current_level_known_bits >= 32) { // This was a SELECT. + select_done = true; // No more anticollision + // We continue below outside the while. + } + else { // This was an ANTICOLLISION. + // We now have all 32 bits of the UID in this Cascade Level + current_level_known_bits = 32; + // Run loop again to do the SELECT. + } + } + } // End of while (!selectDone) + + // We do not check the CBB - it was constructed by us above. + + // Copy the found UID bytes from buffer[] to uid->uidByte[] + index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] + bytesToCopy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; + for (count = 0; count < bytesToCopy; count++) { + uid->uid_byte[uid_index + count] = buffer[index++]; + } + + // Check response SAK (Select Acknowledge) + if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A). + return STATUS_ERROR; + } + // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore. + result = pcd_calculate_crc(response_buffer, 1, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + if ((buffer[2] != response_buffer[1]) || (buffer[3] != response_buffer[2])) { + return STATUS_CRC_WRONG; + } + if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes + cascade_level++; + } + else { + uid_complete = true; + uid->sak = response_buffer[0]; + } + } // End of while (!uidComplete) + + // Set correct uid->size + uid->size = 3 * cascade_level + 1; + + return STATUS_OK; +} // End PICC_Select() + +/** + * Instructs a PICC in state ACTIVE(*) to go to state HALT. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::picc_halt_a() +{ + // Build command buffer + byte buffer[4] = { PICC_CMD_HLTA, 0 }; + + // Calculate CRC_A + auto result = pcd_calculate_crc(buffer, 2, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + + // Send the command. + // The standard says: + // If the PICC responds with any modulation during a period of 1 ms after the end of the frame containing the + // HLTA command, this response shall be interpreted as 'not acknowledge'. + // We interpret that this way: Only STATUS_TIMEOUT is a success. + result = pcd_transceive_data(buffer, sizeof(buffer), nullptr, 0); + if (result == STATUS_TIMEOUT) { + return STATUS_OK; + } + if (result == STATUS_OK) { // That is ironically NOT ok in this case ;-) + return STATUS_ERROR; + } + return result; +} // End PICC_HaltA() + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for communicating with MIFARE PICCs +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Executes the MFRC522 MFAuthent command. + * This command manages MIFARE authentication to enable a secure communication to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. + * The authentication is described in the MFRC522 datasheet section 10.3.1.9 and http://www.nxp.com/documents/data_sheet/MF1S503x.pdf section 10.1. + * For use with MIFARE Classic PICCs. + * The PICC must be selected - ie in state ACTIVE(*) - before calling this function. + * Remember to call PCD_StopCrypto1() after communicating with the authenticated PICC - otherwise no new communications can start. + * + * All keys are set to FFFFFFFFFFFFh at chip delivery. + * + * @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT if you supply the wrong key. + */ +mfrc522::status_code mfrc522::pcd_authenticate( + byte command, ///< PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B + byte block_addr, ///< The block number. See numbering in the comments in the .h file. + mifare_key const *key, ///< Pointer to the Crypteo1 key to use (6 bytes) + uid_t *uid ///< Pointer to Uid struct. The first 4 bytes of the UID is used. +) { + byte wait_irq = 0x10; // IdleIRq + // Build command buffer + byte send_data[12]; + send_data[0] = command; + send_data[1] = block_addr; + for (byte i = 0; i < MF_KEY_SIZE; i++) { // 6 key bytes + send_data[2 + i] = key->key_byte[i]; + } + // Use the last uid bytes as specified in http://cache.nxp.com/documents/application_note/AN10927.pdf + // section 3.2.5 "MIFARE Classic Authentication". + // The only missed case is the MF1Sxxxx shortcut activation, + // but it requires cascade tag (CT) byte, that is not part of uid. + for (byte i = 0; i < 4; i++) { // The last 4 bytes of the UID + send_data[8 + i] = uid->uid_byte[i + uid->size - 4]; + } + + // Start the authentication. + return pcd_communicate_with_picc(PCD_MFAuthent, wait_irq, &send_data[0], sizeof send_data); +} // End PCD_Authenticate() + +/** + * Used to exit the PCD from its authenticated state. + * Remember to call this function after communicating with an authenticated PICC - otherwise no new communications can start. + */ +void mfrc522::pcd_stop_crypto1() { + // Clear MFCrypto1On bit + pcd_clear_register_bit_mask(Status2Reg, 0x08); // Status2Reg[7..0] bits are: TempSensClear I2CForceHS reserved reserved MFCrypto1On ModemState[2:0] +} // End PCD_StopCrypto1() + +/** + * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. + * + * For MIFARE Classic the sector containing the block must be authenticated before calling this function. + * + * For MIFARE Ultralight only addresses 00h to 0Fh are decoded. + * The MF0ICU1 returns a NAK for higher addresses. + * The MF0ICU1 responds to the READ command by sending 16 bytes starting from the page address defined by the command argument. + * For example; if blockAddr is 03h then pages 03h, 04h, 05h, 06h are returned. + * A roll-back is implemented: If blockAddr is 0Eh, then the contents of pages 0Eh, 0Fh, 00h and 01h are returned. + * + * The buffer must be at least 18 bytes because a CRC_A is also returned. + * Checks the CRC_A before returning STATUS_OK. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_read( + byte block_addr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The first page to return data from. + byte *buffer, ///< The buffer to store the data in + byte *buffer_size ///< Buffer size, at least 18 bytes. Also number of bytes returned if STATUS_OK. +) { + // Sanity check + if (buffer == nullptr || *buffer_size < 18) { + return STATUS_NO_ROOM; + } + + // Build command buffer + buffer[0] = PICC_CMD_MF_READ; + buffer[1] = block_addr; + // Calculate CRC_A + auto result = pcd_calculate_crc(buffer, 2, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + + // Transmit the buffer and receive the response, validate CRC_A. + return pcd_transceive_data(buffer, 4, buffer, buffer_size, nullptr, 0, true); +} // End MIFARE_Read() + +/** + * Writes 16 bytes to the active PICC. + * + * For MIFARE Classic the sector containing the block must be authenticated before calling this function. + * + * For MIFARE Ultralight the operation is called "COMPATIBILITY WRITE". + * Even though 16 bytes are transferred to the Ultralight PICC, only the least significant 4 bytes (bytes 0 to 3) + * are written to the specified address. It is recommended to set the remaining bytes 04h to 0Fh to all logic 0. + * * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_write( + byte block_addr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The page (2-15) to write to. + byte *buffer, ///< The 16 bytes to write to the PICC + byte buffer_size ///< Buffer size, must be at least 16 bytes. Exactly 16 bytes are written. +) { + // Sanity check + if (buffer == nullptr || buffer_size < 16) { + return STATUS_INVALID; + } + + // Mifare Classic protocol requires two communications to perform a write. + // Step 1: Tell the PICC we want to write to block blockAddr. + byte cmd_buffer[2] = { PICC_CMD_MF_WRITE, block_addr }; + auto result = pcd_mifare_transceive(cmd_buffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + // Step 2: Transfer the data + result = pcd_mifare_transceive(buffer, buffer_size); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + return STATUS_OK; +} // End MIFARE_Write() + +/** + * Writes a 4 byte page to the active MIFARE Ultralight PICC. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_ultralight_write( + byte page, ///< The page (2-15) to write to. + byte *buffer, ///< The 4 bytes to write to the PICC + byte buffer_size ///< Buffer size, must be at least 4 bytes. Exactly 4 bytes are written. +) { + // Sanity check + if (buffer == nullptr || buffer_size < 4) { + return STATUS_INVALID; + } + + // Build commmand buffer + byte cmd_buffer[6]; + cmd_buffer[0] = PICC_CMD_UL_WRITE; + cmd_buffer[1] = page; + memcpy(&cmd_buffer[2], buffer, 4); + + // Perform the write + auto result = pcd_mifare_transceive(cmd_buffer, 6); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + return STATUS_OK; +} // End MIFARE_Ultralight_Write() + +/** + * MIFARE Decrement subtracts the delta from the value of the addressed block, and stores the result in a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_decrement( + byte block_addr, ///< The block (0-0xff) number. + int32_t delta ///< This number is subtracted from the value of block blockAddr. +) { + return mifare_two_step_helper(PICC_CMD_MF_DECREMENT, block_addr, delta); +} // End MIFARE_Decrement() + +/** + * MIFARE Increment adds the delta to the value of the addressed block, and stores the result in a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_increment( + byte block_addr, ///< The block (0-0xff) number. + int32_t delta ///< This number is added to the value of block blockAddr. +) { + return mifare_two_step_helper(PICC_CMD_MF_INCREMENT, block_addr, delta); +} // End MIFARE_Increment() + +/** + * MIFARE Restore copies the value of the addressed block into a volatile memory. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * Use MIFARE_Transfer() to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_restore( + byte block_addr ///< The block (0-0xff) number. +) { + // The datasheet describes Restore as a two step operation, but does not explain what data to transfer in step 2. + // Doing only a single step does not work, so I chose to transfer 0L in step two. + return mifare_two_step_helper(PICC_CMD_MF_RESTORE, block_addr, 0L); +} // End MIFARE_Restore() + +/** + * Helper function for the two-step MIFARE Classic protocol operations Decrement, Increment and Restore. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_two_step_helper( + byte command, ///< The command to use + byte block_addr, ///< The block (0-0xff) number. + int32_t data ///< The data to transfer in step 2 +) { + // Step 1: Tell the PICC the command and block address + byte cmd_buffer[2] = { + command, block_addr + }; // We only need room for 2 bytes. + + auto result = pcd_mifare_transceive(cmd_buffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + // Step 2: Transfer the data + result = pcd_mifare_transceive(reinterpret_cast(&data), 4, true); // Adds CRC_A and accept timeout as success. + if (result != STATUS_OK) { + return result; + } + + return STATUS_OK; +} // End MIFARE_TwoStepHelper() + +/** + * MIFARE Transfer writes the value stored in the volatile memory into one MIFARE Classic block. + * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. + * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_transfer( + byte block_addr ///< The block (0-0xff) number. +) { + // Tell the PICC we want to transfer the result into block blockAddr. + byte cmd_buffer[2] = { + PICC_CMD_MF_TRANSFER, block_addr + }; // We only need room for 2 bytes. + + auto result = pcd_mifare_transceive(cmd_buffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + return STATUS_OK; +} // End MIFARE_Transfer() + +/** + * Helper routine to read the current value from a Value Block. + * + * Only for MIFARE Classic and only for blocks in "value block" mode, that + * is: with access bits [C1 C2 C3] = [110] or [001]. The sector containing + * the block must be authenticated before calling this function. + * + * @param[in] block_addr The block (0x00-0xff) number. + * @param[out] value Current value of the Value Block. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_get_value(byte block_addr, int32_t *value) +{ + byte buffer[18]; + byte size = sizeof(buffer); + + // Read the block + auto status = mifare_read(block_addr, buffer, &size); + if (status == STATUS_OK) { + // Extract the value + *value = (int32_t(buffer[3]) << 24) | (int32_t(buffer[2]) << 16) | (int32_t(buffer[1]) << 8) | int32_t(buffer[0]); + } + return status; +} // End MIFARE_GetValue() + +/** + * Helper routine to write a specific value into a Value Block. + * + * Only for MIFARE Classic and only for blocks in "value block" mode, that + * is: with access bits [C1 C2 C3] = [110] or [001]. The sector containing + * the block must be authenticated before calling this function. + * + * @param[in] block_addr The block (0x00-0xff) number. + * @param[in] value New value of the Value Block. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::mifare_set_value(byte block_addr, int32_t value) +{ + byte buffer[18]; + + // Translate the int32_t into 4 bytes; repeated 2x in value block + buffer[0] = buffer[8] = (value & 0xFF); + buffer[1] = buffer[9] = (value & 0xFF00) >> 8; + buffer[2] = buffer[10] = (value & 0xFF0000) >> 16; + buffer[3] = buffer[11] = (value & 0xFF000000) >> 24; + // Inverse 4 bytes also found in value block + buffer[4] = ~buffer[0]; + buffer[5] = ~buffer[1]; + buffer[6] = ~buffer[2]; + buffer[7] = ~buffer[3]; + // Address 2x with inverse address 2x + buffer[12] = buffer[14] = block_addr; + buffer[13] = buffer[15] = ~block_addr; + + // Write the whole data block + return mifare_write(block_addr, buffer, 16); +} // End MIFARE_SetValue() + +/** + * Authenticate with a NTAG216. + * + * Only for NTAG216. First implemented by Gargantuanman. + * + * @param[in] password password. + * @param[in] p_ack result success???. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::pcd_ntag216_auth(byte* password, byte p_ack[]) //Authenticate with 32bit password +{ + // TODO: Fix cmdBuffer length and rxlength. They really should match. + // (Better still, rxlength should not even be necessary.) + + byte cmd_buffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. + + cmd_buffer[0] = 0x1B; //Comando de autentificacion + + for (byte i = 0; i < 4; i++) + cmd_buffer[i + 1] = password[i]; + + auto result = pcd_calculate_crc(cmd_buffer, 5, &cmd_buffer[5]); + + if (result != STATUS_OK) { + return result; + } + + // Transceive the data, store the reply in cmdBuffer[] + byte wait_irq = 0x30; // RxIRq and IdleIRq + // byte cmdBufferSize = sizeof(cmdBuffer); + byte validBits = 0; + byte rxlength = 5; + result = pcd_communicate_with_picc(PCD_Transceive, wait_irq, + cmd_buffer, 7, cmd_buffer, &rxlength, &validBits); + + p_ack[0] = cmd_buffer[0]; + p_ack[1] = cmd_buffer[1]; + + if (result != STATUS_OK) { + return result; + } + + return STATUS_OK; +} // End PCD_NTAG216_AUTH() + + +///////////////////////////////////////////////////////////////////////////////////// +// Support functions +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Wrapper for MIFARE protocol communication. + * Adds CRC_A, executes the Transceive command and checks that the response is MF_ACK or a timeout. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +mfrc522::status_code mfrc522::pcd_mifare_transceive( + byte *send_data, ///< Pointer to the data to transfer to the FIFO. Do NOT include the CRC_A. + byte send_len, ///< Number of bytes in sendData. + bool accept_timeout ///< True => A timeout is also success +) { + byte cmd_buffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. + + // Sanity check + if (send_data == nullptr || send_len > 16) { + return STATUS_INVALID; + } + + // Copy sendData[] to cmdBuffer[] and add CRC_A + memcpy(cmd_buffer, send_data, send_len); + auto result = pcd_calculate_crc(cmd_buffer, send_len, &cmd_buffer[send_len]); + if (result != STATUS_OK) { + return result; + } + send_len += 2; + + // Transceive the data, store the reply in cmdBuffer[] + byte wait_irq = 0x30; // RxIRq and IdleIRq + byte cmd_buffer_size = sizeof(cmd_buffer); + byte valid_bits = 0; + result = pcd_communicate_with_picc(PCD_Transceive, wait_irq, cmd_buffer, + send_len, cmd_buffer, &cmd_buffer_size, &valid_bits); + if (accept_timeout && result == STATUS_TIMEOUT) { + return STATUS_OK; + } + if (result != STATUS_OK) { + return result; + } + // The PICC must reply with a 4 bit ACK + if (cmd_buffer_size != 1 || valid_bits != 4) { + return STATUS_ERROR; + } + if (cmd_buffer[0] != MF_ACK) { + return STATUS_MIFARE_NACK; + } + return STATUS_OK; +} // End PCD_MIFARE_Transceive() + +/** + * Translates the SAK (Select Acknowledge) to a PICC type. + * + * @return PICC_Type + */ +mfrc522::picc_type mfrc522::picc_get_type( + byte sak ///< The SAK byte returned from PICC_Select(). +) { + // http://www.nxp.com/documents/application_note/AN10833.pdf + // 3.2 Coding of Select Acknowledge (SAK) + // ignore 8-bit (iso14443 starts with LSBit = bit 1) + // fixes wrong type for manufacturer Infineon (http://nfc-tools.org/index.php?title=ISO14443A) + sak &= 0x7F; + switch (sak) { + case 0x04: + return PICC_TYPE_NOT_COMPLETE; // UID not complete + case 0x09: + return PICC_TYPE_MIFARE_MINI; + case 0x08: + return PICC_TYPE_MIFARE_1K; + case 0x18: + return PICC_TYPE_MIFARE_4K; + case 0x00: + return PICC_TYPE_MIFARE_UL; + case 0x10: + case 0x11: + return PICC_TYPE_MIFARE_PLUS; + case 0x01: + return PICC_TYPE_TNP3XXX; + case 0x20: + return PICC_TYPE_ISO_14443_4; + case 0x40: + return PICC_TYPE_ISO_18092; + default: + return PICC_TYPE_UNKNOWN; + } +} // End PICC_GetType() + +/** + * Calculates the bit pattern needed for the specified access bits. In the [C1 C2 C3] tuples C1 is MSB (=4) and C3 is LSB (=1). + */ +void mfrc522::mifare_set_access_bits( + byte *access_bit_buffer, ///< Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set. + byte g0, ///< Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) + byte g1, ///< Access bits C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) + byte g2, ///< Access bits C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) + byte g3 ///< Access bits C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) +) { + byte c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); + byte c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); + byte c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); + + access_bit_buffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF); + access_bit_buffer[1] = c1 << 4 | (~c3 & 0xF); + access_bit_buffer[2] = c3 << 4 | c2; +} // End MIFARE_SetAccessBits() + +///////////////////////////////////////////////////////////////////////////////////// +// Convenience functions - does not add extra functionality +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns true if a PICC responds to PICC_CMD_REQA. + * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. + * + * @return bool + */ +bool mfrc522::picc_is_new_card_present() { + byte buffer_atqa[2]; + byte buffer_size = sizeof buffer_atqa; + + // Reset baud rates + pcd_write_register(TxModeReg, 0x00); + pcd_write_register(RxModeReg, 0x00); + // Reset ModWidthReg + pcd_write_register(ModWidthReg, 0x26); + + auto result = picc_request_a(buffer_atqa, &buffer_size); + return result == STATUS_OK || result == STATUS_COLLISION; +} // End PICC_IsNewCardPresent() + +/** + * Simple wrapper around PICC_Select. + * Returns true if a UID could be read. + * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. + * The read UID is available in the class variable uid. + * + * @return bool + */ +bool mfrc522::picc_read_card_serial() { + return picc_select(&uid) == STATUS_OK; +} // End diff --git a/client/lib/mfrc522.hpp b/client/lib/mfrc522.hpp new file mode 100644 index 0000000..392e50c --- /dev/null +++ b/client/lib/mfrc522.hpp @@ -0,0 +1,382 @@ +/** + * MFRC522.h - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT. + * Based on code Dr.Leong ( WWW.B2CQSHOP.COM ) + * Created by Miguel Balboa (circuitito.com), Jan, 2012. + * Rewritten by Søren Thing Andersen (access.thing.dk), fall of 2013 (Translation to English, refactored, comments, anti collision, cascade levels.) + * Extended by Tom Clement with functionality to write to sector 0 of UID changeable Mifare cards. + * Released into the public domain. + * + * Please read this file for an overview and then MFRC522.cpp for comments on the specific functions. + * Search for "mf-rc522" on ebay.com to purchase the MF-RC522 board. + * + * There are three hardware components involved: + * 1) The micro controller: An Arduino + * 2) The PCD (short for Proximity Coupling Device): NXP MFRC522 Contactless Reader IC + * 3) The PICC (short for Proximity Integrated Circuit Card): A card or tag using the ISO 14443A interface, eg Mifare or NTAG203. + * + * The microcontroller and card reader uses SPI for communication. + * The protocol is described in the MFRC522 datasheet: http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * + * The card reader and the tags communicate using a 13.56MHz electromagnetic field. + * The protocol is defined in ISO/IEC 14443-3 Identification cards -- Contactless integrated circuit cards -- Proximity cards -- Part 3: Initialization and anticollision". + * A free version of the final draft can be found at http://wg8.de/wg8n1496_17n3613_Ballot_FCD14443-3.pdf + * Details are found in chapter 6, Type A – Initialization and anticollision. + * + * If only the PICC UID is wanted, the above documents has all the needed information. + * To read and write from MIFARE PICCs, the MIFARE protocol is used after the PICC has been selected. + * The MIFARE Classic chips and protocol is described in the datasheets: + * 1K: http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf + * 4K: http://datasheet.octopart.com/MF1S7035DA4,118-NXP-Semiconductors-datasheet-11046188.pdf + * Mini: http://www.idcardmarket.com/download/mifare_S20_datasheet.pdf + * The MIFARE Ultralight chip and protocol is described in the datasheets: + * Ultralight: http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf + * Ultralight C: http://www.nxp.com/documents/short_data_sheet/MF0ICU2_SDS.pdf + * + * MIFARE Classic 1K (MF1S503x): + * Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. + * The blocks are numbered 0-63. + * Block 3 in each sector is the Sector Trailer. See http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf sections 8.6 and 8.7: + * Bytes 0-5: Key A + * Bytes 6-8: Access Bits + * Bytes 9: User data + * Bytes 10-15: Key B (or user data) + * Block 0 is read-only manufacturer data. + * To access a block, an authentication using a key from the block's sector must be performed first. + * Example: To read from block 10, first authenticate using a key from sector 3 (blocks 8-11). + * All keys are set to FFFFFFFFFFFFh at chip delivery. + * Warning: Please read section 8.7 "Memory Access". It includes this text: if the PICC detects a format violation the whole sector is irreversibly blocked. + * To use a block in "value block" mode (for Increment/Decrement operations) you need to change the sector trailer. Use PICC_SetAccessBits() to calculate the bit patterns. + * MIFARE Classic 4K (MF1S703x): + * Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes. + * The blocks are numbered 0-255. + * The last block in each sector is the Sector Trailer like above. + * MIFARE Classic Mini (MF1 IC S20): + * Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. + * The blocks are numbered 0-19. + * The last block in each sector is the Sector Trailer like above. + * + * MIFARE Ultralight (MF0ICU1): + * Has 16 pages of 4 bytes = 64 bytes. + * Pages 0 + 1 is used for the 7-byte UID. + * Page 2 contains the last check digit for the UID, one byte manufacturer internal data, and the lock bytes (see http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2) + * Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0. + * Pages 4-15 are read/write unless blocked by the lock bytes in page 2. + * MIFARE Ultralight C (MF0ICU2): + * Has 48 pages of 4 bytes = 192 bytes. + * Pages 0 + 1 is used for the 7-byte UID. + * Page 2 contains the last check digit for the UID, one byte manufacturer internal data, and the lock bytes (see http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2) + * Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0. + * Pages 4-39 are read/write unless blocked by the lock bytes in page 2. + * Page 40 Lock bytes + * Page 41 16 bit one way counter + * Pages 42-43 Authentication configuration + * Pages 44-47 Authentication key + */ +#pragma once + +// Enable integer limits +#define __STDC_LIMIT_MACROS +#include +#include "spi_device.hpp" +#include "gpio.hpp" +using byte = unsigned char; + +class mfrc522 { + + /// The SPI bus. + spi_device spi_; + /// The reset GPIO pin. + gpio reset_; + + // Size of the MFRC522 FIFO + static constexpr byte fifo_size = 64; + // Default value for unused pin + static constexpr uint8_t unused_pin = UINT8_MAX; + + // Firmware data for self-test + // Reference values based on firmware version + // Hint: if needed, you can remove unused self-test data to save flash memory + // + // Version 0.0 (0x90) + // Philips Semiconductors; Preliminary Specification Revision 2.0 - 01 August 2005; 16.1 self-test + static const byte mfrc522_firmware_reference_v0_0[64]; + // Version 1.0 (0x91) + // NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test + static const byte mfrc522_firmware_reference_v1_0[64]; + // Version 2.0 (0x92) + // NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test + static const byte mfrc522_firmware_reference_v2_0[64]; + // Clone + // Fudan Semiconductor FM17522 (0x88) + static const byte fm17522_firmware_reference[64]; +public: + + // MFRC522 registers. Described in chapter 9 of the datasheet. + // When using SPI all addresses are shifted one bit left in the "SPI address byte" (section 8.1.2.3) + enum pcd_register : byte { + // Page 0: Command and status + // 0x00 // reserved for future use + CommandReg = 0x01 << 1, // starts and stops command execution + ComIEnReg = 0x02 << 1, // enable and disable interrupt request control bits + DivIEnReg = 0x03 << 1, // enable and disable interrupt request control bits + ComIrqReg = 0x04 << 1, // interrupt request bits + DivIrqReg = 0x05 << 1, // interrupt request bits + ErrorReg = 0x06 << 1, // error bits showing the error status of the last command executed + Status1Reg = 0x07 << 1, // communication status bits + Status2Reg = 0x08 << 1, // receiver and transmitter status bits + FIFODataReg = 0x09 << 1, // input and output of 64 byte FIFO buffer + FIFOLevelReg = 0x0A << 1, // number of bytes stored in the FIFO buffer + WaterLevelReg = 0x0B << 1, // level for FIFO underflow and overflow warning + ControlReg = 0x0C << 1, // miscellaneous control registers + BitFramingReg = 0x0D << 1, // adjustments for bit-oriented frames + CollReg = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface + // 0x0F // reserved for future use + + // Page 1: Command + // 0x10 // reserved for future use + ModeReg = 0x11 << 1, // defines general modes for transmitting and receiving + TxModeReg = 0x12 << 1, // defines transmission data rate and framing + RxModeReg = 0x13 << 1, // defines reception data rate and framing + TxControlReg = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2 + TxASKReg = 0x15 << 1, // controls the setting of the transmission modulation + TxSelReg = 0x16 << 1, // selects the internal sources for the antenna driver + RxSelReg = 0x17 << 1, // selects internal receiver settings + RxThresholdReg = 0x18 << 1, // selects thresholds for the bit decoder + DemodReg = 0x19 << 1, // defines demodulator settings + // 0x1A // reserved for future use + // 0x1B // reserved for future use + MfTxReg = 0x1C << 1, // controls some MIFARE communication transmit parameters + MfRxReg = 0x1D << 1, // controls some MIFARE communication receive parameters + // 0x1E // reserved for future use + SerialSpeedReg = 0x1F << 1, // selects the speed of the serial UART interface + + // Page 2: Configuration + // 0x20 // reserved for future use + CRCResultRegH = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation + CRCResultRegL = 0x22 << 1, + // 0x23 // reserved for future use + ModWidthReg = 0x24 << 1, // controls the ModWidth setting? + // 0x25 // reserved for future use + RFCfgReg = 0x26 << 1, // configures the receiver gain + GsNReg = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation + CWGsPReg = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation + ModGsPReg = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation + TModeReg = 0x2A << 1, // defines settings for the internal timer + TPrescalerReg = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. + TReloadRegH = 0x2C << 1, // defines the 16-bit timer reload value + TReloadRegL = 0x2D << 1, + TCounterValueRegH = 0x2E << 1, // shows the 16-bit timer value + TCounterValueRegL = 0x2F << 1, + + // Page 3: Test Registers + // 0x30 // reserved for future use + TestSel1Reg = 0x31 << 1, // general test signal configuration + TestSel2Reg = 0x32 << 1, // general test signal configuration + TestPinEnReg = 0x33 << 1, // enables pin output driver on pins D1 to D7 + TestPinValueReg = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus + TestBusReg = 0x35 << 1, // shows the status of the internal test bus + AutoTestReg = 0x36 << 1, // controls the digital self-test + VersionReg = 0x37 << 1, // shows the software version + AnalogTestReg = 0x38 << 1, // controls the pins AUX1 and AUX2 + TestDAC1Reg = 0x39 << 1, // defines the test value for TestDAC1 + TestDAC2Reg = 0x3A << 1, // defines the test value for TestDAC2 + TestADCReg = 0x3B << 1 // shows the value of ADC I and Q channels + // 0x3C // reserved for production tests + // 0x3D // reserved for production tests + // 0x3E // reserved for production tests + // 0x3F // reserved for production tests + }; + + // MFRC522 commands. Described in chapter 10 of the datasheet. + enum pcd_command : byte { + PCD_Idle = 0x00, // no action, cancels current command execution + PCD_Mem = 0x01, // stores 25 bytes into the internal buffer + PCD_GenerateRandomID = 0x02, // generates a 10-byte random ID number + PCD_CalcCRC = 0x03, // activates the CRC coprocessor or performs a self-test + PCD_Transmit = 0x04, // transmits data from the FIFO buffer + PCD_NoCmdChange = 0x07, // no command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit + PCD_Receive = 0x08, // activates the receiver circuits + PCD_Transceive = 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission + PCD_MFAuthent = 0x0E, // performs the MIFARE standard authentication as a reader + PCD_SoftReset = 0x0F // resets the MFRC522 + }; + + // MFRC522 RxGain[2:0] masks, defines the receiver's signal voltage gain factor (on the PCD). + // Described in 9.3.3.6 / table 98 of the datasheet at http://www.nxp.com/documents/data_sheet/MFRC522.pdf + enum pcd_rx_gain : byte { + RxGain_18dB = 0x00 << 4, // 000b - 18 dB, minimum + RxGain_23dB = 0x01 << 4, // 001b - 23 dB + RxGain_18dB_2 = 0x02 << 4, // 010b - 18 dB, it seems 010b is a duplicate for 000b + RxGain_23dB_2 = 0x03 << 4, // 011b - 23 dB, it seems 011b is a duplicate for 001b + RxGain_33dB = 0x04 << 4, // 100b - 33 dB, average, and typical default + RxGain_38dB = 0x05 << 4, // 101b - 38 dB + RxGain_43dB = 0x06 << 4, // 110b - 43 dB + RxGain_48dB = 0x07 << 4, // 111b - 48 dB, maximum + RxGain_min = 0x00 << 4, // 000b - 18 dB, minimum, convenience for RxGain_18dB + RxGain_avg = 0x04 << 4, // 100b - 33 dB, average, convenience for RxGain_33dB + RxGain_max = 0x07 << 4 // 111b - 48 dB, maximum, convenience for RxGain_48dB + }; + + // Commands sent to the PICC. + enum picc_command : byte { + // The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) + PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. + PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. + PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. + PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 + PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 + PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 + PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. + PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. + // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) + // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on the sector. + // The read/write commands can also be used for MIFARE Ultralight. + PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A + PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B + PICC_CMD_MF_READ = 0x30, // Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. + PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight. + PICC_CMD_MF_DECREMENT = 0xC0, // Decrements the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_INCREMENT = 0xC1, // Increments the contents of a block and stores the result in the internal data register. + PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register. + PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block. + // The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) + // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. + PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 byte page to the PICC. + }; + + // MIFARE constants that does not fit anywhere else + enum mifare_misc { + MF_ACK = 0xA, // The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK. + MF_KEY_SIZE = 6 // A Mifare Crypto1 key is 6 bytes. + }; + + // PICC types we can detect. Remember to update PICC_GetTypeName() if you add more. + // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered + enum picc_type : byte { + PICC_TYPE_UNKNOWN, + PICC_TYPE_ISO_14443_4, // PICC compliant with ISO/IEC 14443-4 + PICC_TYPE_ISO_18092, // PICC compliant with ISO/IEC 18092 (NFC) + PICC_TYPE_MIFARE_MINI, // MIFARE Classic protocol, 320 bytes + PICC_TYPE_MIFARE_1K, // MIFARE Classic protocol, 1KB + PICC_TYPE_MIFARE_4K, // MIFARE Classic protocol, 4KB + PICC_TYPE_MIFARE_UL, // MIFARE Ultralight or Ultralight C + PICC_TYPE_MIFARE_PLUS, // MIFARE Plus + PICC_TYPE_MIFARE_DESFIRE, // MIFARE DESFire + PICC_TYPE_TNP3XXX, // Only mentioned in NXP AN 10833 MIFARE Type Identification Procedure + PICC_TYPE_NOT_COMPLETE = 0xff // SAK indicates UID is not complete. + }; + + // Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. + // last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered + enum status_code : byte { + STATUS_OK, // Success + STATUS_ERROR, // Error in communication + STATUS_COLLISION, // Collission detected + STATUS_TIMEOUT, // Timeout in communication. + STATUS_NO_ROOM, // A buffer is not big enough. + STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-) + STATUS_INVALID, // Invalid argument. + STATUS_CRC_WRONG, // The CRC_A does not match + STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. + }; + + // A struct used for passing the UID of a PICC. + typedef struct { + byte size; // Number of bytes in the UID. 4, 7 or 10. + byte uid_byte[10]; + byte sak; // The SAK (Select acknowledge) byte returned from the PICC after successful selection. + } uid_t; + + // A struct used for passing a MIFARE Crypto1 key + typedef struct { + byte key_byte[MF_KEY_SIZE]; + } mifare_key; + + // Member variables + uid_t uid; // Used by PICC_ReadCardSerial(). + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for setting up the device + ///////////////////////////////////////////////////////////////////////////////////// + explicit mfrc522(unsigned bus, unsigned device, unsigned reset); + + ///////////////////////////////////////////////////////////////////////////////////// + // Basic interface functions for communicating with the MFRC522 + ///////////////////////////////////////////////////////////////////////////////////// + void pcd_write_register(pcd_register reg, byte value); + void pcd_write_register(pcd_register reg, byte count, byte *values); + byte pcd_read_register(pcd_register reg); + void pcd_read_register(pcd_register reg, byte count, byte *values, byte rx_align = 0); + void pcd_set_register_bit_mask(pcd_register reg, byte mask); + void pcd_clear_register_bit_mask(pcd_register reg, byte mask); + status_code pcd_calculate_crc(byte *data, byte length, byte *result); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for manipulating the MFRC522 + ///////////////////////////////////////////////////////////////////////////////////// + void pcd_init(); + void pcd_reset(); + void pcd_antenna_on(); + void pcd_antenna_off(); + byte pcd_get_antenna_gain(); + void pcd_set_antenna_gain(byte mask); + bool pcd_perform_self_test(); + + ///////////////////////////////////////////////////////////////////////////////////// + // Power control functions + ///////////////////////////////////////////////////////////////////////////////////// + void pcd_soft_power_down(); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for communicating with PICCs + ///////////////////////////////////////////////////////////////////////////////////// + status_code pcd_transceive_data( + byte *send_data, byte send_len, byte *back_data, byte *back_len, + byte *valid_bits = nullptr, byte rx_align = 0, bool check_crc = false); + status_code pcd_communicate_with_picc( + byte command, byte wait_irq, byte *send_data, byte send_len, + byte *back_data = nullptr, byte *back_len = nullptr, + byte *valid_bits = nullptr, byte rx_align = 0, bool check_crc = false); + status_code picc_request_a(byte *buffer_atqa, byte *buffer_size); + status_code picc_wakeup_a(byte *buffer_atqa, byte *buffer_size); + status_code picc_reqa_or_wupa(byte command, byte *buffer_atqa, byte *buffer_size); + virtual status_code picc_select(uid_t *uid, byte valid_bits = 0); + status_code picc_halt_a(); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for communicating with MIFARE PICCs + ///////////////////////////////////////////////////////////////////////////////////// + status_code pcd_authenticate(byte command, byte block_addr, mifare_key const *key, uid_t *uid); + void pcd_stop_crypto1(); + status_code mifare_read(byte block_addr, byte *buffer, byte *buffer_size); + status_code mifare_write(byte block_addr, byte *buffer, byte buffer_size); + status_code mifare_ultralight_write(byte page, byte *buffer, byte buffer_size); + status_code mifare_decrement(byte block_addr, int32_t delta); + status_code mifare_increment(byte block_addr, int32_t delta); + status_code mifare_restore(byte block_addr); + status_code mifare_transfer(byte block_addr); + status_code mifare_get_value(byte block_addr, int32_t *value); + status_code mifare_set_value(byte block_addr, int32_t value); + status_code pcd_ntag216_auth(byte *password, byte p_ack[]); + + ///////////////////////////////////////////////////////////////////////////////////// + // Support functions + ///////////////////////////////////////////////////////////////////////////////////// + status_code pcd_mifare_transceive(byte *send_data, byte send_len, bool accept_timeout = false); + static picc_type picc_get_type(byte sak); + + + // Advanced functions for MIFARE + void mifare_set_access_bits(byte *access_bit_buffer, byte g0, byte g1, byte g2, byte g3); + + ///////////////////////////////////////////////////////////////////////////////////// + // Convenience functions - does not add extra functionality + ///////////////////////////////////////////////////////////////////////////////////// + virtual bool picc_is_new_card_present(); + virtual bool picc_read_card_serial(); + +protected: + + // Functions for communicating with MIFARE PICCs + status_code mifare_two_step_helper(byte command, byte block_addr, int32_t data); +}; \ No newline at end of file diff --git a/client/lib/spi_device.cpp b/client/lib/spi_device.cpp new file mode 100644 index 0000000..6be6d04 --- /dev/null +++ b/client/lib/spi_device.cpp @@ -0,0 +1,147 @@ +/* + * SPIDevice.cpp Created on: 22 May 2014 + * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie) + * Made available for the book "Exploring BeagleBone" + * See: www.exploringbeaglebone.com + * Licensed under the EUPL V.1.1 + * + * This Software is provided to You under the terms of the European + * Union Public License (the "EUPL") version 1.1 as published by the + * European Union. Any use of this Software, other than as authorized + * under this License is strictly prohibited (to the extent such use + * is covered by a right of the copyright holder of this Software). + * + * This Software is provided under the License on an "AS IS" basis and + * without warranties of any kind concerning the Software, including + * without limitation merchantability, fitness for a particular purpose, + * absence of defects or errors, accuracy, and non-infringement of + * intellectual property rights other than copyright. This disclaimer + * of warranty is an essential part of the License and a condition for + * the grant of any rights to this Software. + * + * For more details, see http://www.derekmolloy.ie/ + */ + +#include "spi_device.hpp" + +#include +#include +#include +#include +#include +#include +#include + + +spi_device::spi_device(unsigned int bus, unsigned int device) : bus_(bus), device_(device) +{ + filename_ = std::string(base_path) + std::to_string(bus) + '.' + std::to_string(device); + if ((fd_ = open(filename_.c_str(), O_RDWR)) < 0 || + set_mode(mode_) == -1 || + set_speed(speed_) == -1 || + set_speed(speed_) == -1 || + set_bits_per_word(bits_) == -1) + { + std::cerr << "Failed to open SPI device: " << + base_path << bus << '.' << device << std::endl; + exit(1); + } +} + + +int spi_device::transfer(unsigned char send[], unsigned char receive[], unsigned length) +{ + spi_ioc_transfer transfer = { + reinterpret_cast(send), + reinterpret_cast(receive), + length, speed_, delay_, bits_ + }; + auto status = ioctl(this->fd_, SPI_IOC_MESSAGE(1), &transfer); + if (status < 0) + std::cerr << "SPI: SPI_IOC_MESSAGE Failed" << std::endl; + return status; +} + +unsigned char spi_device::read_register(unsigned char register_address) +{ + unsigned char send[2] = { 0 }, receive[2] = { 0 }; + send[0] = static_cast(0x80 | register_address); + transfer(send, receive, 2); + return receive[1]; +} + +unsigned char spi_device::read(unsigned char register_address) +{ + unsigned char send[2] = { register_address }; + unsigned char receive[2] = { 0 }; + transfer(send, receive, 2); + return receive[1]; +} + +int spi_device::write(unsigned char value) +{ + this->transfer(&value, nullptr, 1); + return 0; +} + +int spi_device::write(unsigned char value[], unsigned length) +{ + this->transfer(value, nullptr, length); + return 0; +} + +int spi_device::write_register(unsigned char register_address, unsigned char value) +{ + unsigned char send[2] = { register_address, value }; + unsigned char receive[2] = { 0 }; + this->transfer(send, receive, 2); + return 0; +} + +int spi_device::set_speed(uint32_t speed) +{ + speed_ = speed; + if (ioctl(fd_, SPI_IOC_WR_MAX_SPEED_HZ, &speed_) == -1) { + std::cerr << "SPI: Can't set max speed HZ" << std::endl; + return -1; + } + if (ioctl(fd_, SPI_IOC_RD_MAX_SPEED_HZ, &speed_) == -1) { + std::cerr << "SPI: Can't get max speed HZ." << std::endl; + return -1; + } + return 0; +} + +int spi_device::set_mode(mode mode) +{ + mode_ = mode; + if (ioctl(fd_, SPI_IOC_WR_MODE, &mode_) == -1) { + std::cerr << "SPI: Can't set SPI mode." << std::endl; + return -1; + } + if (ioctl(fd_, SPI_IOC_RD_MODE, &mode_) == -1) { + std::cerr << "SPI: Can't get SPI mode." << std::endl; + return -1; + } + return 0; +} + +int spi_device::set_bits_per_word(uint8_t bits) +{ + bits_ = bits; + if (ioctl(fd_, SPI_IOC_WR_BITS_PER_WORD, &bits_) == -1) { + std::cerr << "SPI: Can't set bits per word." << std::endl; + return -1; + } + if (ioctl(fd_, SPI_IOC_RD_BITS_PER_WORD, &bits_) == -1) { + std::cerr << "SPI: Can't get bits per word." << std::endl; + return -1; + } + return 0; +} + + +spi_device::~spi_device() +{ + close(fd_); +} diff --git a/client/lib/spi_device.hpp b/client/lib/spi_device.hpp new file mode 100644 index 0000000..496c9ad --- /dev/null +++ b/client/lib/spi_device.hpp @@ -0,0 +1,77 @@ +/* + * SPIDevice.h Created on: 22 May 2014 + * Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie) + * Made available for the book "Exploring BeagleBone" + * See: www.exploringbeaglebone.com + * Licensed under the EUPL V.1.1 + * + * This Software is provided to You under the terms of the European + * Union Public License (the "EUPL") version 1.1 as published by the + * European Union. Any use of this Software, other than as authorized + * under this License is strictly prohibited (to the extent such use + * is covered by a right of the copyright holder of this Software). + * + * This Software is provided under the License on an "AS IS" basis and + * without warranties of any kind concerning the Software, including + * without limitation merchantability, fitness for a particular purpose, + * absence of defects or errors, accuracy, and non-infringement of + * intellectual property rights other than copyright. This disclaimer + * of warranty is an essential part of the License and a condition for + * the grant of any rights to this Software. + * + * For more details, see http://www.derekmolloy.ie/ + */ +#pragma once + +#include + +class spi_device +{ +public: + /// SPI modes. + enum mode + { + /// Low at idle, capture on rising clock edge + mode0 = 0, + /// Low at idle, capture on falling clock edge + mode1 = 1, + /// High at idle, capture on falling clock edge + mode2 = 2, + /// High at idle, capture on rising clock edge + mode3 = 3 + }; + +private: + /// Base path of SPI devices. + static constexpr const char* base_path = "/dev/spidev"; + /// Bus number. + unsigned int bus_; + /// Device number on the bus. + unsigned int device_; + /// Device file descriptor. + int fd_ = -1; + /// The precise filename for the SPI device. + std::string filename_; + /// The current SPI mode. + mode mode_ = mode3; + /// The number of bits per word + uint8_t bits_ = 8; + /// The speed of transfer in Hz. + uint32_t speed_ = 500000; + /// The transfer delay in usecs. + uint16_t delay_ = 0; + +public: + + spi_device(unsigned bus, unsigned device); + unsigned char read(unsigned char register_address); + unsigned char read_register(unsigned char register_address); + int write_register(unsigned char register_address, unsigned char value); + int write(unsigned char value); + int write(unsigned char value[], unsigned length); + int set_speed(uint32_t speed); + int set_mode(mode mode); + int set_bits_per_word(uint8_t bits); + ~spi_device(); + int transfer(unsigned char send[], unsigned char receive[], unsigned length); +}; \ No newline at end of file diff --git a/client/lib/ssd1306.cpp b/client/lib/ssd1306.cpp new file mode 100644 index 0000000..90f79d0 --- /dev/null +++ b/client/lib/ssd1306.cpp @@ -0,0 +1,1543 @@ +/* + * MIT License + +Copyright (c) 2017 DeeplyEmbedded + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + * SSD1306_OLED.c + * + * Created on : Sep 26, 2017 + * Author : Vinay Divakar + * Description : SSD1306 OLED Driver, Graphics API's. + * Website : www.deeplyembedded.org + */ + +/* Lib Includes */ +#include +#include +#include +#include + +#include "ssd1306.hpp" + +/* MACROS */ +#define PGM_READ_BYTE(addr) (*TO_CONST_UCHAR_STR(addr)) +#define PGM_READ_WORD(addr) (*reinterpret_cast(addr)) +#define PGM_READ_DWORD(addr) (*reinterpret_cast(addr)) +#define PGM_READ_POINTER(addr) (reinterpret_castPGM_READ_WORD(addr)) + + +const unsigned char ssd1306::font[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, + 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, + 0x18, 0x3C, 0x7E, 0x3C, 0x18, 0x1C, 0x57, 0x7D, 0x57, 0x1C, + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 0x00, 0x18, 0x3C, 0x18, 0x00, + 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 0x00, 0x18, 0x24, 0x18, 0x00, + 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 0x30, 0x48, 0x3A, 0x06, 0x0E, + 0x26, 0x29, 0x79, 0x29, 0x26, 0x40, 0x7F, 0x05, 0x05, 0x07, + 0x40, 0x7F, 0x05, 0x25, 0x3F, 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, + 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 0x08, 0x1C, 0x1C, 0x3E, 0x7F, + 0x14, 0x22, 0x7F, 0x22, 0x14, 0x5F, 0x5F, 0x00, 0x5F, 0x5F, + 0x06, 0x09, 0x7F, 0x01, 0x7F, 0x00, 0x66, 0x89, 0x95, 0x6A, + 0x60, 0x60, 0x60, 0x60, 0x60, 0x94, 0xA2, 0xFF, 0xA2, 0x94, + 0x08, 0x04, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E, 0x20, 0x10, + 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x08, 0x1C, 0x2A, 0x08, 0x08, + 0x1E, 0x10, 0x10, 0x10, 0x10, 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, + 0x30, 0x38, 0x3E, 0x38, 0x30, 0x06, 0x0E, 0x3E, 0x0E, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x07, 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, + 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, + 0x36, 0x49, 0x56, 0x20, 0x50, 0x00, 0x08, 0x07, 0x03, 0x00, + 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x41, 0x22, 0x1C, 0x00, + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x08, 0x08, 0x3E, 0x08, 0x08, + 0x00, 0x80, 0x70, 0x30, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x60, 0x60, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, + 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x42, 0x7F, 0x40, 0x00, + 0x72, 0x49, 0x49, 0x49, 0x46, 0x21, 0x41, 0x49, 0x4D, 0x33, + 0x18, 0x14, 0x12, 0x7F, 0x10, 0x27, 0x45, 0x45, 0x45, 0x39, + 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x41, 0x21, 0x11, 0x09, 0x07, + 0x36, 0x49, 0x49, 0x49, 0x36, 0x46, 0x49, 0x49, 0x29, 0x1E, + 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x40, 0x34, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x41, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x00, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x59, 0x09, 0x06, + 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x7C, 0x12, 0x11, 0x12, 0x7C, + 0x7F, 0x49, 0x49, 0x49, 0x36, 0x3E, 0x41, 0x41, 0x41, 0x22, + 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x7F, 0x49, 0x49, 0x49, 0x41, + 0x7F, 0x09, 0x09, 0x09, 0x01, 0x3E, 0x41, 0x41, 0x51, 0x73, + 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x41, 0x7F, 0x41, 0x00, + 0x20, 0x40, 0x41, 0x3F, 0x01, 0x7F, 0x08, 0x14, 0x22, 0x41, + 0x7F, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x02, 0x1C, 0x02, 0x7F, + 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x3E, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x09, 0x09, 0x09, 0x06, 0x3E, 0x41, 0x51, 0x21, 0x5E, + 0x7F, 0x09, 0x19, 0x29, 0x46, 0x26, 0x49, 0x49, 0x49, 0x32, + 0x03, 0x01, 0x7F, 0x01, 0x03, 0x3F, 0x40, 0x40, 0x40, 0x3F, + 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x3F, 0x40, 0x38, 0x40, 0x3F, + 0x63, 0x14, 0x08, 0x14, 0x63, 0x03, 0x04, 0x78, 0x04, 0x03, + 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00, 0x7F, 0x41, 0x41, 0x41, + 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x41, 0x41, 0x41, 0x7F, + 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x03, 0x07, 0x08, 0x00, 0x20, 0x54, 0x54, 0x78, 0x40, + 0x7F, 0x28, 0x44, 0x44, 0x38, 0x38, 0x44, 0x44, 0x44, 0x28, + 0x38, 0x44, 0x44, 0x28, 0x7F, 0x38, 0x54, 0x54, 0x54, 0x18, + 0x00, 0x08, 0x7E, 0x09, 0x02, 0x18, 0xA4, 0xA4, 0x9C, 0x78, + 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7D, 0x40, 0x00, + 0x20, 0x40, 0x40, 0x3D, 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, + 0x00, 0x41, 0x7F, 0x40, 0x00, 0x7C, 0x04, 0x78, 0x04, 0x78, + 0x7C, 0x08, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, + 0xFC, 0x18, 0x24, 0x24, 0x18, 0x18, 0x24, 0x24, 0x18, 0xFC, + 0x7C, 0x08, 0x04, 0x04, 0x08, 0x48, 0x54, 0x54, 0x54, 0x24, + 0x04, 0x04, 0x3F, 0x44, 0x24, 0x3C, 0x40, 0x40, 0x20, 0x7C, + 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x3C, 0x40, 0x30, 0x40, 0x3C, + 0x44, 0x28, 0x10, 0x28, 0x44, 0x4C, 0x90, 0x90, 0x90, 0x7C, + 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x08, 0x36, 0x41, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x41, 0x36, 0x08, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x02, 0x3C, 0x26, 0x23, 0x26, 0x3C, + 0x1E, 0xA1, 0xA1, 0x61, 0x12, 0x3A, 0x40, 0x40, 0x20, 0x7A, + 0x38, 0x54, 0x54, 0x55, 0x59, 0x21, 0x55, 0x55, 0x79, 0x41, + 0x22, 0x54, 0x54, 0x78, 0x42, 0x21, 0x55, 0x54, 0x78, 0x40, + 0x20, 0x54, 0x55, 0x79, 0x40, 0x0C, 0x1E, 0x52, 0x72, 0x12, + 0x39, 0x55, 0x55, 0x55, 0x59, 0x39, 0x54, 0x54, 0x54, 0x59, + 0x39, 0x55, 0x54, 0x54, 0x58, 0x00, 0x00, 0x45, 0x7C, 0x41, + 0x00, 0x02, 0x45, 0x7D, 0x42, 0x00, 0x01, 0x45, 0x7C, 0x40, + 0x7D, 0x12, 0x11, 0x12, 0x7D, 0xF0, 0x28, 0x25, 0x28, 0xF0, + 0x7C, 0x54, 0x55, 0x45, 0x00, 0x20, 0x54, 0x54, 0x7C, 0x54, + 0x7C, 0x0A, 0x09, 0x7F, 0x49, 0x32, 0x49, 0x49, 0x49, 0x32, + 0x3A, 0x44, 0x44, 0x44, 0x3A, 0x32, 0x4A, 0x48, 0x48, 0x30, + 0x3A, 0x41, 0x41, 0x21, 0x7A, 0x3A, 0x42, 0x40, 0x20, 0x78, + 0x00, 0x9D, 0xA0, 0xA0, 0x7D, 0x3D, 0x42, 0x42, 0x42, 0x3D, + 0x3D, 0x40, 0x40, 0x40, 0x3D, 0x3C, 0x24, 0xFF, 0x24, 0x24, + 0x48, 0x7E, 0x49, 0x43, 0x66, 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, + 0xFF, 0x09, 0x29, 0xF6, 0x20, 0xC0, 0x88, 0x7E, 0x09, 0x03, + 0x20, 0x54, 0x54, 0x79, 0x41, 0x00, 0x00, 0x44, 0x7D, 0x41, + 0x30, 0x48, 0x48, 0x4A, 0x32, 0x38, 0x40, 0x40, 0x22, 0x7A, + 0x00, 0x7A, 0x0A, 0x0A, 0x72, 0x7D, 0x0D, 0x19, 0x31, 0x7D, + 0x26, 0x29, 0x29, 0x2F, 0x28, 0x26, 0x29, 0x29, 0x29, 0x26, + 0x30, 0x48, 0x4D, 0x40, 0x20, 0x38, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x38, 0x2F, 0x10, 0xC8, 0xAC, 0xBA, + 0x2F, 0x10, 0x28, 0x34, 0xFA, 0x00, 0x00, 0x7B, 0x00, 0x00, + 0x08, 0x14, 0x2A, 0x14, 0x22, 0x22, 0x14, 0x2A, 0x14, 0x08, + 0x55, 0x00, 0x55, 0x00, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x10, 0x10, 0x10, 0xFF, 0x00, 0x14, 0x14, 0x14, 0xFF, 0x00, + 0x10, 0x10, 0xFF, 0x00, 0xFF, 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x14, 0x14, 0x14, 0xFC, 0x00, 0x14, 0x14, 0xF7, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x14, 0x14, 0xF4, 0x04, 0xFC, + 0x14, 0x14, 0x17, 0x10, 0x1F, 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0x1F, 0x00, 0x10, 0x10, 0x10, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 0x10, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0xF0, 0x10, 0x00, 0x00, 0x00, 0xFF, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xFF, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x14, 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x1F, 0x10, 0x17, 0x00, 0x00, 0xFC, 0x04, 0xF4, + 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, 0x14, 0xF4, 0x04, 0xF4, + 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14, 0x14, 0x17, 0x14, + 0x10, 0x10, 0x1F, 0x10, 0x1F, 0x14, 0x14, 0x14, 0xF4, 0x14, + 0x10, 0x10, 0xF0, 0x10, 0xF0, 0x00, 0x00, 0x1F, 0x10, 0x1F, + 0x00, 0x00, 0x00, 0x1F, 0x14, 0x00, 0x00, 0x00, 0xFC, 0x14, + 0x00, 0x00, 0xF0, 0x10, 0xF0, 0x10, 0x10, 0xFF, 0x10, 0xFF, + 0x14, 0x14, 0x14, 0xFF, 0x14, 0x10, 0x10, 0x10, 0x1F, 0x00, + 0x00, 0x00, 0x00, 0xF0, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x38, 0x44, 0x44, 0x38, 0x44, 0xFC, 0x4A, 0x4A, 0x4A, 0x34, + 0x7E, 0x02, 0x02, 0x06, 0x06, 0x02, 0x7E, 0x02, 0x7E, 0x02, + 0x63, 0x55, 0x49, 0x41, 0x63, 0x38, 0x44, 0x44, 0x3C, 0x04, + 0x40, 0x7E, 0x20, 0x1E, 0x20, 0x06, 0x02, 0x7E, 0x02, 0x02, + 0x99, 0xA5, 0xE7, 0xA5, 0x99, 0x1C, 0x2A, 0x49, 0x2A, 0x1C, + 0x4C, 0x72, 0x01, 0x72, 0x4C, 0x30, 0x4A, 0x4D, 0x4D, 0x30, + 0x30, 0x48, 0x78, 0x48, 0x30, 0xBC, 0x62, 0x5A, 0x46, 0x3D, + 0x3E, 0x49, 0x49, 0x49, 0x00, 0x7E, 0x01, 0x01, 0x01, 0x7E, + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x44, 0x44, 0x5F, 0x44, 0x44, + 0x40, 0x51, 0x4A, 0x44, 0x40, 0x40, 0x44, 0x4A, 0x51, 0x40, + 0x00, 0x00, 0xFF, 0x01, 0x03, 0xE0, 0x80, 0xFF, 0x00, 0x00, + 0x08, 0x08, 0x6B, 0x6B, 0x08, 0x36, 0x12, 0x36, 0x24, 0x36, + 0x06, 0x0F, 0x09, 0x0F, 0x06, 0x00, 0x00, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x10, 0x10, 0x00, 0x30, 0x40, 0xFF, 0x01, 0x01, + 0x00, 0x1F, 0x01, 0x01, 0x1E, 0x00, 0x19, 0x1D, 0x17, 0x12, + 0x00, 0x3C, 0x3C, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/**************************************************************** + * Function Name : clearDisplay + * Description : Clear the display memory buffer + * Returns : NONE. + * Params : NONE. + ****************************************************************/ +void ssd1306::clear_display() +{ + memset(screen_, 0x00, DISPLAY_BUFF_SIZE); +} + +/**************************************************************** + * Function Name : display_Init_seq + * Description : Performs SSD1306 OLED Initialization Sequence + * Returns : NONE. + * Params : NONE. + ****************************************************************/ +void ssd1306::display_init_seq() +{ + /* Add the reset code, If needed */ + + /* Send display OFF command */ + cmd(SSD1306_DISPLAY_OFF); + /* Set display clock frequency */ + cmd(SSD1306_SET_DISP_CLK); + /* Send display CLK command parameter */ + cmd(SSD1306_DISPCLK_DIV); + /* Set display multiplex */ + cmd(SSD1306_SET_MULTIPLEX); + /* Send display MULT command parameter */ + cmd(SSD1306_MULT_DAT); + /* Set display OFFSET */ + cmd(SSD1306_SET_DISP_OFFSET); + /* Send display OFFSET command parameter */ + cmd(SSD1306_DISP_OFFSET_VAL); + /* Set display START LINE - Check this command if something weird happens */ + cmd(SSD1306_SET_DISP_START_LINE); + /* Enable CHARGEPUMP*/ + cmd(SSD1306_CONFIG_CHARGE_PUMP); + /* Send display CHARGEPUMP command parameter */ + cmd(SSD1306_CHARGE_PUMP_EN); + /* Set display MEMORYMODE */ + cmd(SSD1306_SET_MEM_ADDR_MODE); + /* Send display HORIZONTAL MEMORY ADDR MODE command parameter */ + cmd(SSD1306_HOR_MM); + /* Set display SEG_REMAP */ + cmd(SSD1306_SEG_REMAP); + /* Set display DIR */ + cmd(SSD1306_SET_COMSCANDEC); + /* Set display COM */ + cmd(SSD1306_SET_COMPINS); + /* Send display COM command parameter */ + cmd(SSD1306_CONFIG_COM_PINS); + /* Set display CONTRAST */ + cmd(SSD1306_SET_CONTRAST); + /* Send display CONTRAST command parameter */ + cmd(SSD1306_CONTRAST_VAL); + /* Set display PRECHARGE */ + cmd(SSD1306_SET_PRECHARGE); + /* Send display PRECHARGE command parameter */ + cmd(SSD1306_PRECHARGE_VAL); + /* Set display VCOMH */ + cmd(SSD1306_SET_VCOMDETECT); + /* Send display VCOMH command parameter */ + cmd(SSD1306_VCOMH_VAL); + /* Set display ALL-ON */ + cmd(SSD1306_DISPLAYALLON_RESUME); + /* Set display to NORMAL-DISPLAY */ + cmd(SSD1306_NORMAL_DISPLAY); + /* Set display to DEACTIVATE_SCROLL */ + cmd(SSD1306_DEACTIVATE_SCROLL); + /* Set display to TURN-ON */ + cmd(SSD1306_DISPLAYON); +} + +/**************************************************************** + * Function Name : transfer + * Description : Transfer the frame buffer onto the display + * Returns : NONE. + * Params : NONE. + ****************************************************************/ +void ssd1306::transfer() +{ + data(); + for (auto i = 0; i < 1024; ) { + for (auto& byte : chunk_) + byte = screen_[i++]; + if (spi_.write(chunk_, 16)) + exit(1); + memset(chunk_, 0x00, 16); + } +} + +/**************************************************************** + * Function Name : Display + * Description : 1. Resets the column and page addresses. + * 2. Displays the contents of the memory buffer. + * Returns : NONE. + * Params : NONE. + * Note : Each new form can be preceded by a clearDisplay. + ****************************************************************/ +void ssd1306::display() +{ + init_col_pg_addrs(SSD1306_COL_START_ADDR, SSD1306_COL_END_ADDR, + SSD1306_PG_START_ADDR, SSD1306_PG_END_ADDR); + transfer(); +} + +/**************************************************************** + * Function Name : Init_Col_PG_addrs + * Description : Sets the column and page, start and + * end addresses. + * Returns : NONE. + * Params : @col_start_addr: Column start address + * @col_end_addr: Column end address + * @pg_start_addr: Page start address + * @pg_end_addr: Page end address + ****************************************************************/ +void ssd1306::init_col_pg_addrs(unsigned char col_start_addr, unsigned char col_end_addr, + unsigned char pg_start_addr, unsigned char pg_end_addr) +{ + /* Send COLMN address setting command */ + cmd(SSD1306_SET_COL_ADDR); + /* Set COLMN start address */ + cmd(col_start_addr); + /* Set COLMN end address */ + cmd(col_end_addr); + /* Send PAGE address setting command */ + cmd(SSD1306_PAGEADDR); + /* Set PAGE start address */ + cmd(pg_start_addr); + /* Set PAGE end address */ + cmd(pg_end_addr); +} + +/**************************************************************** + * Function Name : setRotation + * Description : Set the display rotation + * Returns : NONE. + * Params : @x: Display rotation parameter + ****************************************************************/ +void ssd1306::set_rotation(unsigned char x) +{ + rotation_ = x & 3; + switch(rotation_) { + case 0: + case 2: + width_ = SSD1306_LCDWIDTH; + height_ = SSD1306_LCDHEIGHT; + break; + case 1: + case 3: + width_ = SSD1306_LCDHEIGHT; + height_ = SSD1306_LCDWIDTH; + break; + default: + break; + } +} + +/**************************************************************** + * Function Name : startscrollright + * Description : Activate a right handed scroll for rows start + * through stop + * Returns : NONE. + * Params : @start: Start location + * @stop: Stop location + * HINT. : the display is 16 rows tall. To scroll the whole + * display, run: display.scrollright(0x00, 0x0F) + ****************************************************************/ +void ssd1306::start_scroll_right(unsigned char start, unsigned char stop) +{ + /* Send SCROLL horizontal right command */ + cmd(SSD1306_RIGHT_HORIZONTAL_SCROLL); + cmd(0x00); + cmd(start); + cmd(0x00); + cmd(stop); + cmd(0x00); + cmd(0xFF); + + /* Send SCROLL Activate command */ + cmd(SSD1306_ACTIVATE_SCROLL); +} + +/**************************************************************** + * Function Name : startscrollleft + * Description : Activate a left handed scroll for rows start + * through stop + * Returns : NONE. + * Params : @start: Start location + * @stop: Stop location + * HINT. : the display is 16 rows tall. To scroll the whole + * display, run: display.scrollright(0x00, 0x0F) + ****************************************************************/ +void ssd1306::start_scroll_left(unsigned char start, unsigned char stop) +{ + /* Send SCROLL horizontal left command */ + cmd(SSD1306_LEFT_HORIZONTAL_SCROLL); + cmd(0x00); + cmd(start); + cmd(0x00); + cmd(stop); + cmd(0x00); + cmd(0xFF); + + /* Send SCROLL Activate command */ + cmd(SSD1306_ACTIVATE_SCROLL); +} + +/**************************************************************** + * Function Name : startscrolldiagright + * Description : Activate a diagonal scroll for rows start + * through stop + * Returns : NONE. + * Params : @start: Start location + * @stop: Stop location + * HINT. : the display is 16 rows tall. To scroll the whole + * display, run: display.scrollright(0x00, 0x0F) + ****************************************************************/ +void ssd1306::start_scroll_diag_right(unsigned char start, unsigned char stop) +{ + /* Send SCROLL diagonal right command */ + cmd(SSD1306_SET_VERTICAL_SCROLL_AREA); + cmd(0x00); + cmd(SSD1306_LCDHEIGHT); + cmd(SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL); + cmd(0x00); + cmd(start); + cmd(0x00); + cmd(stop); + cmd(0x01); + + /* Send SCROLL Activate command */ + cmd(SSD1306_ACTIVATE_SCROLL); +} + +/**************************************************************** + * Function Name : startscrolldiagleft + * Description : Activate a diagonal scroll for rows start + * through stop + * Returns : NONE. + * Params : @start: Start location + * @stop: Stop location + * HINT. : the display is 16 rows tall. To scroll the whole + * display, run: display.scrollright(0x00, 0x0F) + ****************************************************************/ +void ssd1306::start_scroll_diag_left(unsigned char start, unsigned char stop) +{ + /* Send SCROLL diagonal right command */ + cmd(SSD1306_SET_VERTICAL_SCROLL_AREA); + cmd(0x00); + cmd(SSD1306_LCDHEIGHT); + cmd(SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL); + cmd(0x00); + cmd(start); + cmd(0x00); + cmd(stop); + cmd(0x01); + + /* Send SCROLL Activate command */ + cmd(SSD1306_ACTIVATE_SCROLL); +} + +/**************************************************************** + * Function Name : stopscroll + * Description : Stop scrolling + * Returns : NONE. + * Params : NONE. + ****************************************************************/ +void ssd1306::stop_scroll() +{ + cmd(SSD1306_DEACTIVATE_SCROLL); +} + +/**************************************************************** + * Function Name : invertDisplay + * Description : Invert or Normalize the display + * Returns : NONE. + * Params : @i: 0x00 to Normal and 0x01 for Inverting + ****************************************************************/ +void ssd1306::invert_display(unsigned char i) +{ + cmd(i ? SSD1306_INVERTDISPLAY : SSD1306_NORMAL_DISPLAY); +} + +/**************************************************************** + * Function Name : drawPixel + * Description : Draw a pixel + * Returns : -1 on error and 0 on success + * Params : @x: X - Co-ordinate + * @y: Y - Co-ordinate + * @color: Color + ****************************************************************/ +signed char ssd1306::draw_pixel(short x, short y, short color) +{ + /* Return if co-ordinates are out of display dimension's range */ + if (x < 0 || x >= width_ || y < 0 || y >= height_) + return -1; + switch (rotation_) + { + case 1: + std::swap(x,y); + x = width_ - x - 1; + break; + case 2: + x = width_ - x - 1; + y = height_ - y - 1; + break; + case 3: + std::swap(x,y); + y = height_ - y - 1; + break; + default: + break; + } + + /* x is the column */ + switch(color) + { + case WHITE: + screen_[x + y/8 * SSD1306_LCDWIDTH] |= 1 << (y & 7); + break; + case BLACK: + screen_[x + y/8 * SSD1306_LCDWIDTH] &= ~(1 << (y & 7)); + break; + case INVERSE: + screen_[x + y/8 * SSD1306_LCDWIDTH] ^= 1 << (y & 7); + break; + default: + break; + } + return 0; +} + +/**************************************************************** + * Function Name : writeLine + * Description : Bresenham's algorithm + * Returns : NONE + * Params : @x0: X0 Co-ordinate + * @y0: Y0 Co-ordinate + * @x1: X1 Co-ordinate + * @y1: Y1 Co-ordinate + * @color: Pixel color + ****************************************************************/ +void ssd1306::write_line(short x0, short y0, short x1, short y1, short color) +{ + auto steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + std::swap(x0, y0); + std::swap(x1, y1); + } + + if (x0 > x1) { + std::swap(x0, x1); + std::swap(y0, y1); + } + short dx = x1 - x0; + short dy = abs(y1 - y0); + + short err = dx / 2; + short ystep = y0 < y1 ? 1 : -1; + + for (; x0 <= x1; ++x0) { + if (steep) { + draw_pixel(y0, x0, color); + } else { + draw_pixel(x0, y0, color); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +/* (x,y) is topmost point; if unsure, calling function +should sort endpoints or call writeLine() instead */ +void ssd1306::draw_fast_v_line(short x, short y,short h, short color) +{ + write_line(x, y, x, y + h - 1, color); +} + +/* (x,y) is topmost point; if unsure, calling function +should sort endpoints or call writeLine() instead */ +void ssd1306::write_fast_v_line(short x, short y, short h, short color) +{ + draw_fast_v_line(x, y, h, color); +} + +/* (x,y) is leftmost point; if unsure, calling function + should sort endpoints or call writeLine() instead */ +void ssd1306::draw_fast_h_line(short x, short y,short w, short color) +{ + write_line(x, y, x + w - 1, y, color); +} + +// (x,y) is leftmost point; if unsure, calling function +// should sort endpoints or call writeLine() instead +void ssd1306::write_fast_h_line(short x, short y, short w, short color) +{ + draw_fast_h_line(x, y, w, color); +} + +/**************************************************************** + * Function Name : drawCircleHelper + * Description : Draw a.... + * Returns : NONE + * Params : @x: X Co-ordinate + * @y: Y Co-ordinate + * @w: Width + * @h: height + * @r: Corner radius + * @color: Pixel color + ****************************************************************/ +void ssd1306::draw_circle_helper(short x0, short y0, short r, unsigned char cornername, short color) +{ + short f = 1 - r; + short dd_f_x = 1; + short dd_f_y = -2 * r; + short x = 0; + short y = r; + + while (x= 0) { + y--; + dd_f_y += 2; + f += dd_f_y; + } + x++; + dd_f_x += 2; + f += dd_f_x; + if (cornername & 0x4) { + draw_pixel(x0 + x, y0 + y, color); + draw_pixel(x0 + y, y0 + x, color); + } + if (cornername & 0x2) { + draw_pixel(x0 + x, y0 - y, color); + draw_pixel(x0 + y, y0 - x, color); + } + if (cornername & 0x8) { + draw_pixel(x0 - y, y0 + x, color); + draw_pixel(x0 - x, y0 + y, color); + } + if (cornername & 0x1) { + draw_pixel(x0 - y, y0 - x, color); + draw_pixel(x0 - x, y0 - y, color); + } + } +} + +/**************************************************************** + * Function Name : drawLine + * Description : Draw line between two points + * Returns : NONE + * Params : @x0: X0 Starting X Co-ordinate + * @y0: Y0 Starting Y Co-ordinate + * @x1: X1 Ending X Co-ordinate + * @y1: Y1 Ending Y Co-ordinate + * @color: Pixel color + ****************************************************************/ +void ssd1306::draw_line(short x0, short y0, short x1, short y1, short color) +{ + if (x0 == x1) { + if (y0 > y1) + std::swap(y0, y1); + draw_fast_v_line(x0, y0, y1 - y0 + 1, color); + } else if (y0 == y1) { + if (x0 > x1) + std::swap(x0, x1); + draw_fast_h_line(x0, y0, x1 - x0 + 1, color); + } else { + write_line(x0, y0, x1, y1, color); + } +} + +/**************************************************************** + * Function Name : drawRect + * Description : Draw a rectangle + * Returns : NONE + * Params : @x: Corner X Co-ordinate + * @y: Corner Y Co-ordinate + * @w: Width in pixels + * @h: Height in pixels + * @color: Pixel color + ****************************************************************/ +void ssd1306::draw_rect(short x, short y, short w, short h, short color) +{ + //startWrite(); + write_fast_h_line(x, y, w, color); + write_fast_h_line(x, y+h-1, w, color); + write_fast_v_line(x, y, h, color); + write_fast_v_line(x+w-1, y, h, color); + //endWrite(); +} + +/**************************************************************** + * Function Name : fillRect + * Description : Fill the rectangle + * Returns : NONE + * Params : @x: Starting X Co-ordinate + * @y: Starting Y Co-ordinate + * @w: Width in pixels + * @h: Height in pixels + * @color: Pixel color + ****************************************************************/ +void ssd1306::fill_rect(short x, short y, short w, short h, short color) +{ + //startWrite(); + for (auto i = x; i < x + w; ++i) { + write_fast_v_line(i, y, h, color); + } + //endWrite(); +} + +/**************************************************************** + * Function Name : drawCircle + * Description : Draw a circle + * Returns : NONE + * Params : @x: Center X Co-ordinate + * @y: Center Y Co-ordinate + * @r: Radius in pixels + * @color: Pixel color + ****************************************************************/ +void ssd1306::draw_circle(short x0, short y0, short r, short color) +{ + short f = 1 - r; + short dd_f_x = 1; + short dd_f_y = -2 * r; + short x = 0; + short y = r; + + //startWrite(); + draw_pixel(x0 , y0 + r, color); + draw_pixel(x0 , y0 - r, color); + draw_pixel(x0 + r, y0 , color); + draw_pixel(x0 - r, y0 , color); + + while (x < y) { + if (f >= 0) { + y--; + dd_f_y += 2; + f += dd_f_y; + } + x++; + dd_f_x += 2; + f += dd_f_x; + + draw_pixel(x0 + x, y0 + y, color); + draw_pixel(x0 - x, y0 + y, color); + draw_pixel(x0 + x, y0 - y, color); + draw_pixel(x0 - x, y0 - y, color); + draw_pixel(x0 + y, y0 + x, color); + draw_pixel(x0 - y, y0 + x, color); + draw_pixel(x0 + y, y0 - x, color); + draw_pixel(x0 - y, y0 - x, color); + } +} + +/**************************************************************** + * Function Name : fillCircleHelper + * Description : Used to do circles and roundrects + * Returns : NONE + * Params : @x: Center X Co-ordinate + * @y: Center Y Co-ordinate + * @r: Radius in pixels + * @cornername: Corner radius in pixels + * @color: Pixel color + ****************************************************************/ +void ssd1306::fill_circle_helper(short x0, short y0, short r, unsigned char cornername, short delta, short color) +{ + + short f = 1 - r; + short dd_f_x = 1; + short dd_f_y = -2 * r; + short x = 0; + short y = r; + + while (x= 0) { + y--; + dd_f_y += 2; + f += dd_f_y; + } + x++; + dd_f_x += 2; + f += dd_f_x; + + if (cornername & 0x1) { + write_fast_v_line(x0+x, y0-y, 2*y+1+delta, color); + write_fast_v_line(x0+y, y0-x, 2*x+1+delta, color); + } + if (cornername & 0x2) { + write_fast_v_line(x0-x, y0-y, 2*y+1+delta, color); + write_fast_v_line(x0-y, y0-x, 2*x+1+delta, color); + } + } +} + +/**************************************************************** + * Function Name : fillCircle + * Description : Fill the circle + * Returns : NONE + * Params : @x0: Center X Co-ordinate + * @y0: Center Y Co-ordinate + * @r: Radius in pixels + * @color: Pixel color + ****************************************************************/ +void ssd1306::fill_circle(short x0, short y0, short r, short color) +{ + write_fast_v_line(x0, y0-r, 2*r+1, color); + fill_circle_helper(x0, y0, r, 3, 0, color); +} + +/**************************************************************** + * Function Name : drawTriangle + * Description : Draw a triangle + * Returns : NONE + * Params : @x0: Corner-1 X Co-ordinate + * @y0: Corner-1 Y Co-ordinate + * @x1: Corner-2 X Co-ordinate + * @y1: Corner-2 Y Co-ordinate + * @x2: Corner-3 X Co-ordinate + * @y2: Corner-3 Y Co-ordinate + * @color: Pixel color + ****************************************************************/ +void ssd1306::draw_triangle(short x0, short y0, short x1, short y1, short x2, short y2, short color) +{ + draw_line(x0, y0, x1, y1, color); + draw_line(x1, y1, x2, y2, color); + draw_line(x2, y2, x0, y0, color); +} + +/**************************************************************** + * Function Name : fillTriangle + * Description : Fill a triangle + * Returns : NONE + * Params : @x0: Corner-1 X Co-ordinate + * @y0: Corner-1 Y Co-ordinate + * @x1: Corner-2 X Co-ordinate + * @y1: Corner-2 Y Co-ordinate + * @x2: Corner-3 X Co-ordinate + * @y2: Corner-3 Y Co-ordinate + * @color: Pixel color + ****************************************************************/ +void ssd1306::fill_triangle(short x0, short y0, short x1, short y1, short x2, short y2, short color) +{ + short a, b, y, last; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) { + std::swap(y0, y1); + std::swap(x0, x1); + } + if (y1 > y2) { + std::swap(y2, y1); + std::swap(x2, x1); + } + if (y0 > y1) { + std::swap(y0, y1); + std::swap(x0, x1); + } + + //startWrite(); + if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing + a = b = x0; + if (x1 < a) + a = x1; + else if (x1 > b) + b = x1; + if (x2 < a) + a = x2; + else if (x2 > b) + b = x2; + write_fast_h_line(a, y0, b-a+1, color); + // endWrite(); + return; + } + + auto dx01 = x1 - x0; + auto dy01 = y1 - y0; + auto dx02 = x2 - x0; + auto dy02 = y2 - y0; + auto dx12 = x2 - x1; + auto dy12 = y2 - y1; + auto sa = 0; + auto sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if (y1 == y2) + last = y1; // Include y1 scanline + else + last = y1-1; // Skip it + + for (y=y0; y<=last; y++) + { + a = x0 + sa / dy01; + b = x0 + sb / dy02; + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + std::swap(a,b); + write_fast_h_line(a, y, b-a+1, color); + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + sa = dx12 * (y - y1); + sb = dx02 * (y - y0); + for (; y<=y2; y++) + { + a = x1 + sa / dy12; + b = x0 + sb / dy02; + sa += dx12; + sb += dx02; + /* longhand: + a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + std::swap(a,b); + write_fast_h_line(a, y, b-a+1, color); + } + //endWrite(); +} + +/**************************************************************** + * Function Name : drawRoundRect + * Description : Draw a rounded rectangle + * Returns : NONE + * Params : @x: X Co-ordinate + * @y: Y Co-ordinate + * @w: Width + * @h: height + * @r: Corner radius + * @color: Pixel color + ****************************************************************/ +void ssd1306::draw_round_rect(short x, short y, short w, short h, short r, short color) +{ + // smarter version + write_fast_h_line(x + r , y , w - 2 * r, color); // Top + write_fast_h_line(x + r , y + h - 1, w - 2 * r, color); // Bottom + write_fast_v_line(x , y + r , h - 2 * r, color); // Left + write_fast_v_line(x + w - 1, y + r , h - 2 * r, color); // Right + // draw four corners + draw_circle_helper(x + r , y + r , r, 1, color); + draw_circle_helper(x + w - r - 1, y + r , r, 2, color); + draw_circle_helper(x + w - r - 1, y + h - r - 1, r, 4, color); + draw_circle_helper(x + r , y + h - r - 1, r, 8, color); +} + +/**************************************************************** + * Function Name : fillRoundRect + * Description : Fill a rounded rectangle + * Returns : NONE + * Params : @x: X Co-ordinate + * @y: Y Co-ordinate + * @w: Width + * @h: height + * @r: Corner radius + * @color: Pixel color + ****************************************************************/ +void ssd1306::fill_round_rect(short x, short y, short w, short h, short r, short color) +{ + // smarter version + fill_rect(x + r, y, w - 2 * r, h, color); + + // draw four corners + fill_circle_helper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color); + fill_circle_helper(x + r , y + r, r, 2, h - 2 * r - 1, color); +} + +/*---------------------------------------------------------------------------- + * BITMAP API's + ----------------------------------------------------------------------------*/ + +/**************************************************************** + * Function Name : drawBitmap + * Description : Draw a bitmap + * Returns : NONE + * Params : @x: X Co-ordinate + * @y: Y Co-ordinate + * @bitmap: bitmap to display + * @w: Width + * @h: height + * @color: Pixel color + ****************************************************************/ +void ssd1306::draw_bitmap(short x, short y, const unsigned char* bitmap, short w, short h, short color) +{ + unsigned char byte = 0; + short byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte + + for (auto j=0; j= width_) || (y >= height_) || ((x + 6 * size - 1) < 0) || ((y + 8 * size - 1) < 0)) + return; + + // Handle 'classic' charset behavior + if (!cp437_ && (c >= 176)) + c++; + + // Char bitmap = 5 columns + for (char i=0; i<5; i++ ) { + auto line = PGM_READ_BYTE(&font[c * 5 + i]); + for (char j=0; j<8; j++, line >>= 1) { + if (line & 1) { + if (size == 1) + draw_pixel(x+i, y+j, color); + else + fill_rect(x+i*size, y+j*size, size, size, color); + } else if (bg != color) { + if (size == 1) + draw_pixel(x+i, y+j, bg); + else + fill_rect(x+i*size, y+j*size, size, size, bg); + } + } + } + + // If opaque, draw vertical line for last column + if (bg != color) { + if (size == 1) + write_fast_v_line(x+5, y, 8, bg); + else + fill_rect(x+5*size, y, size, 8*size, bg); + } + + } else { + // Character is assumed previously filtered by write() to eliminate + // newlines, returns, non-printable characters, etc. Calling + // drawChar() directly with 'bad' characters of font may cause mayhem! + + c -= static_cast(PGM_READ_BYTE(&gfx_font_->first)); + auto glyph = &(static_cast(PGM_READ_POINTER(&gfx_font_->glyph))[c]); + auto bitmap = static_cast(PGM_READ_POINTER(&gfx_font_->bitmap)); + short bo = PGM_READ_WORD(&glyph->bitmap_offset); + auto w = PGM_READ_BYTE(&glyph->width); + auto h = PGM_READ_BYTE(&glyph->height); + char xo = PGM_READ_BYTE(&glyph->x_offset); + char yo = PGM_READ_BYTE(&glyph->y_offset); + + short xo16 = 0, yo16 = 0; + if (size > 1) { + xo16 = xo; + yo16 = yo; + } + + // Todo: Add character clipping here + + // NOTE: THERE IS NO 'BACKGROUND' COLOR OPTION ON CUSTOM FONTS. + // THIS IS ON PURPOSE AND BY DESIGN. The background color feature + // has typically been used with the 'classic' font to overwrite old + // screen contents with new data. This ONLY works because the + // characters are a uniform size; it's not a sensible thing to do with + // proportionally-spaced fonts with glyphs of varying sizes (and that + // may overlap). To replace previously-drawn text when using a custom + // font, use the getTextBounds() function to determine the smallest + // rectangle encompassing a string, erase the area with fillRect(), + // then draw new text. This WILL unfortunately 'blink' the text, but + // is unavoidable. Drawing 'background' pixels will NOT fix this, + // only creates a new set of problems. Have an idea to work around + // this (a canvas object type for MCUs that can afford the RAM and + // displays supporting setAddrWindow() and pushColors()), but haven't + // implemented this yet. + unsigned char bits = 0, bit = 0; + for (unsigned char yy = 0; yy < h; yy++) { + for (unsigned char xx=0; xx width_) { + // Off right? + cursor_x_ = 0; // Reset x to zero, + cursor_y_ += textsize_ * 8; // advance y one line + } + draw_char(cursor_x_, cursor_y_, c, textcolor_, textbgcolor_, textsize_); + cursor_x_ += textsize_ * 6; // Advance x one char + } + + } else { + // Custom font + if (c == '\n') { + cursor_x_ = 0; + cursor_y_ += static_cast(textsize_) * + static_cast(PGM_READ_BYTE(&gfx_font_->y_advance)); + } else if (c != '\r') { + auto first = PGM_READ_BYTE(&gfx_font_->first); + if (c >= first && c <= static_cast(PGM_READ_BYTE(&gfx_font_->last))) { + auto glyph = &static_cast(PGM_READ_POINTER(&gfx_font_->glyph))[c - first]; + auto w = PGM_READ_BYTE(&glyph->width); + auto h = PGM_READ_BYTE(&glyph->height); + if (w > 0 && h > 0) { + // Is there an associated bitmap? + auto xo = static_cast(PGM_READ_BYTE(&glyph->x_offset)); // sic + if (wrap_ && cursor_x_ + textsize_ * (xo + w) > width_) + { + cursor_x_ = 0; + cursor_y_ += static_cast(textsize_) *static_cast(PGM_READ_BYTE(&gfx_font_->y_advance)); + } + draw_char(cursor_x_, cursor_y_, c, textcolor_, textbgcolor_, textsize_); + } + cursor_x_ += static_cast(PGM_READ_BYTE(&glyph->x_advance)) * static_cast(textsize_); + } + } + } + return 1; +} + +/**************************************************************** + * Function Name : print + * Description : Base function for printing strings + * Returns : No. of characters printed + * Params : @buffer: Ptr to buffer containing the string + * @size: Length of the string. + ****************************************************************/ +short ssd1306::print(const unsigned char *buffer, short size) +{ + short n = 0; + while (size--) { + if (oled_write(*buffer++)) + n++; + else + break; + } + return n; +} + +ssd1306::ssd1306( + unsigned bus, unsigned device, + unsigned reset, unsigned dc) : spi_(bus, device), reset_(reset), dc_(dc) +{ + reset_.set_direction(gpio::output); + reset_.set_value(gpio::low); + usleep(50'000); + reset_.set_value(gpio::high); + dc_.set_direction(gpio::output); + dc_.stream_open(); +} + +ssd1306::~ssd1306() +{ + dc_.stream_close(); +} + +/**************************************************************** + * Function Name : print_str + * Description : Print strings + * Returns : No. of characters printed + * Params : @strPtr: Ptr to buffer containing the string + ****************************************************************/ +short ssd1306::print_str(const char *strPtr) +{ + return print(TO_CONST_UCHAR_STR(strPtr), strlen(strPtr)); +} + +/**************************************************************** + * Function Name : println + * Description : Move to next line + * Returns : No. of characters printed + * Params : NONE. + ****************************************************************/ +short ssd1306::println() +{ + return print_str("\r\n"); +} + +/**************************************************************** + * Function Name : print_strln + * Description : Print strings and move to next line + * Returns : No. of characters printed + * Params : @strPtr: Ptr to buffer containing the string + ****************************************************************/ +short ssd1306::print_strln(const char *strPtr) +{ + auto n = print(TO_CONST_UCHAR_STR(strPtr), strlen(strPtr)); + n += print_str("\r\n"); + return n; +} + +/*---------------------------------------------------------------------------- + * NUMBERS HANDLING API's + ----------------------------------------------------------------------------*/ + +/**************************************************************** + * Function Name : printNumber + * Description : Base function to print unsigned numbers + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number(unsigned long n, unsigned char base) +{ + char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte. + auto *str = &buf[sizeof(buf) - 1]; + + *str = '\0'; + + // prevent crash if called with base == 1 + if (base < 2) + base = 10; + do + { + auto m = n; + n /= base; + char c = m - base * n; + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } + while(n); + //return oled_write((unsigned char)str); + return print_str(str); +} + +/**************************************************************** + * Function Name : printNumber_UL + * Description : Print unsigned long data types + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_ul(unsigned long n, int base) +{ + if (base == 0) + return oled_write(n); + return print_number(n, base); +} + +/**************************************************************** + * Function Name : printNumber_UL_ln + * Description : Print unsigned long & advance to next line + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_ul_ln(unsigned long num, int base) +{ + auto n = print_number(num, base); + n += println(); + return n; +} + +/**************************************************************** + * Function Name : printNumber_UI + * Description : Print unsigned int data types + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_ui(unsigned int n, int base) +{ + return print_number(static_cast(n), base); +} + +/**************************************************************** + * Function Name : printNumber_UI_ln + * Description : Print unsigned int & advance to next line + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_ui_ln(unsigned int n, int base) +{ + auto a = print_number(static_cast(n), base); + a += println(); + return a; +} + +/**************************************************************** + * Function Name : printNumber_UC + * Description : Print unsigned char data types + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_uc(unsigned char b, int base) +{ + return print_number(static_cast(b), base); +} + +/**************************************************************** + * Function Name : printNumber_UC_ln + * Description : Print unsigned char & advance to next line + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_uc_ln(unsigned char b, int base) +{ + auto n = print_number(static_cast(b), base); + n += println(); + return n; +} + +/**************************************************************** + * Function Name : printNumber_L + * Description : Print Long data types + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_l(long n, int base) +{ + if (base == 0) { + return oled_write(n); + } + if (base == 10) { + if (n < 0) { + auto t = oled_write('-'); + n = -n; + return print_number(n, 10) + t; + } + return print_number(n, 10); + } + return print_number(n, base); +} + +/**************************************************************** + * Function Name : printNumber_UC_ln + * Description : Print long & advance to next line + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_l_ln(long num, int base) +{ + auto n = print_number_l(num, base); + n += println(); + return n; +} + +/**************************************************************** + * Function Name : printNumber_I + * Description : Print int data types + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_i(int n, int base) +{ + return print_number_l(static_cast(n), base); +} + +/**************************************************************** + * Function Name : printNumber_I_ln + * Description : Print int & advance to next line + * Returns : No. of characters printed + * Params : @n: Number + * @base: Base e.g. HEX, BIN... + ****************************************************************/ +short ssd1306::print_number_i_ln(int n, int base) +{ + auto a = print_number_l(static_cast(n), base); + a += println(); + return a; +} + +/**************************************************************** + * Function Name : printFloat + * Description : Print floating Pt. No's. + * Returns : No. of characters printed + * Params : @n: Number + * @digits: Resolution + ****************************************************************/ +short ssd1306::print_float(double number, unsigned char digits) +{ + short n = 0; + + // Round correctly so that print(1.999, 2) prints as "2.00" + auto rounding = 0.5; + + if (std::isnan(number)) + return print_str("nan"); + if (std::isinf(number)) + return print_str("inf"); + if (number > 4294967040.0) + return print_str("ovf"); // constant determined empirically + if (number < -4294967040.0) + return print_str("ovf"); // constant determined empirically + + // Handle negative numbers + if (number < 0.0) { + n += oled_write('-'); + number = -number; + } + + + for (auto i = 0; i < digits; ++i) + rounding /= 10.0; + + number += rounding; + + // Extract the integer part of the number and print it + auto int_part = static_cast(number); + auto remainder = number - static_cast(int_part); + n += print_number_ul(int_part,DEC); + + // Print the decimal point, but only if there are digits beyond + if (digits > 0) { + n += print_str("."); + } + + // Extract digits from the remainder one at a time + while(digits-- > 0) { + remainder *= 10.0; + double to_print = static_cast(remainder); + n += print_number_i(to_print,DEC); + remainder -= to_print; + } + return n; +} + +/**************************************************************** + * Function Name : printFloat_ln + * Description : Print floating Pt. No and advance to next line + * Returns : No. of characters printed + * Params : @n: Number + * @digits: Resolution + ****************************************************************/ +short ssd1306::print_float_ln(double num, int digits) +{ + auto n = print_float(num, digits); + n += println(); + return n; +} diff --git a/client/lib/ssd1306.hpp b/client/lib/ssd1306.hpp new file mode 100644 index 0000000..104b33a --- /dev/null +++ b/client/lib/ssd1306.hpp @@ -0,0 +1,207 @@ +/* + * MIT License + +Copyright (c) 2017 DeeplyEmbedded + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + * SSD1306_OLED.h + * + * Created on : Sep 21, 2017 + * Author : Vinay Divakar + * Website : www.deeplyembedded.org + */ +#pragma once + +#include "spi_device.hpp" +#include "gpio.hpp" + +#define DISPLAY_BUFF_SIZE (SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8) + +/* COLOR MACROS */ +#define WHITE 1 +#define BLACK 0 +#define INVERSE 2 + +/* Number output format */ +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 +#define DEFAULT 0 + +#define SSD1306_LCDWIDTH 128 +#define SSD1306_LCDHEIGHT 64 + +/* SSD1306 Commands */ +#define SSD1306_DISPLAY_OFF 0xAE +#define SSD1306_SET_DISP_CLK 0xD5 +#define SSD1306_SET_MULTIPLEX 0xA8 +#define SSD1306_SET_DISP_OFFSET 0xD3 +#define SSD1306_SET_DISP_START_LINE 0x40 +#define SSD1306_CONFIG_CHARGE_PUMP 0x8D +#define SSD1306_SET_MEM_ADDR_MODE 0x20 +#define SSD1306_SEG_REMAP (0xA0 | 0x01) +#define SSD1306_SET_COMSCANDEC 0xC8 +#define SSD1306_SET_COMPINS 0xDA +#define SSD1306_SET_CONTRAST 0x81 +#define SSD1306_SET_PRECHARGE 0xD9 +#define SSD1306_SET_VCOMDETECT 0xDB +#define SSD1306_DISPLAYALLON_RESUME 0xA4 +#define SSD1306_NORMAL_DISPLAY 0xA6 +#define SSD1306_DISPLAYON 0xAF +#define SSD1306_SET_COL_ADDR 0x21 +#define SSD1306_PAGEADDR 0x22 +#define SSD1306_INVERT_DISPLAY 0x01 +#define SSD1306_NORMALIZE_DISPLAY 0x00 + +/* SDD1306 Scroll Commands */ +#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3 +#define SSD1306_ACTIVATE_SCROLL 0x2F +#define SSD1306_DEACTIVATE_SCROLL 0x2E +#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26 +#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27 +#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 +#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A +#define SSD1306_INVERTDISPLAY 0xA7 + +/* SSD1306 Configuration Commands */ +#define SSD1306_DISPCLK_DIV 0x80 +#define SSD1306_MULT_64 0x3F +#define SSD1306_DISP_OFFSET_VAL 0x00 +#define SSD1306_COL_START_ADDR 0x00 +#define SSD1306_COL_END_ADDR (SSD1306_LCDWIDTH - 1) +#define SSD1306_PG_START_ADDR 0x00 +#define SSD1306_PG_END_ADDR 7 +#define SSD1306_CHARGE_PUMP_EN 0x14 +#define SSD1306_CONFIG_COM_PINS 0x12 +#define SSD1306_CONTRAST_VAL 0xCF +#define SSD1306_PRECHARGE_VAL 0xF1 +#define SSD1306_VCOMH_VAL 0x40 +#define SSD1306_MULT_DAT (SSD1306_LCDHEIGHT - 1) +#define SSD1306_HOR_MM 0x00 + +#define TO_CONST_UCHAR_STR(str) (reinterpret_cast(str)) + +typedef struct { // Data stored PER GLYPH + unsigned short bitmap_offset; // Pointer into GFXfont->bitmap + unsigned char width, height; // Bitmap dimensions in pixels + unsigned char x_advance; // Distance to advance cursor (x axis) + char x_offset, y_offset; // Dist from cursor pos to UL corner +} gf_xglyph_t, *gf_xglyph_ptr; + +typedef struct { // Data stored for FONT AS A WHOLE: + unsigned char *bitmap; // Glyph bitmaps, concatenated + gf_xglyph_ptr glyph; // Glyph array + unsigned char first, last; // ASCII extents + unsigned char y_advance; // Newline distance (y axis) +} gf_xfont_t, *gf_xfont_ptr; + +class ssd1306 +{ + gf_xfont_ptr gfx_font_ = nullptr; + unsigned char chunk_[16] = { 0 }; + unsigned char screen_[DISPLAY_BUFF_SIZE] = { 0 }; + const static unsigned char font[1280]; + unsigned char rotation_ = 0; + unsigned char textsize_ = 0; + spi_device spi_; + gpio reset_; + gpio dc_; + short width_ = SSD1306_LCDWIDTH; + short height_ = SSD1306_LCDHEIGHT; + short cursor_x_ = 0, cursor_y_ = 0, textcolor_ = 0, textbgcolor_ = 0; + bool cp437_ = false, wrap_ = true; + void transfer(); + void draw_fast_v_line(short x, short y, short h, short color); + void write_fast_v_line(short x, short y, short h, short color); + void draw_fast_h_line(short x, short y, short w, short color); + void write_fast_h_line(short x, short y, short w, short color); + short print(const unsigned char *buffer, short size); + + int cmd(const unsigned char byte) + { + if (dc_.stream_write(gpio::low)) + return -1; + return spi_.write(byte); + } + int data() + { + return dc_.stream_write(gpio::high); + } +public: + + explicit ssd1306(unsigned bus, unsigned device, unsigned reset, unsigned dc); + ~ssd1306(); + + void clear_display(); + void display_init_seq(); + void display(); + void init_col_pg_addrs(unsigned char col_start_addr, unsigned char col_end_addr, + unsigned char pg_start_addr, unsigned char pg_end_addr); + void set_rotation(unsigned char x); + void start_scroll_right(unsigned char start, unsigned char stop); + void start_scroll_left(unsigned char start, unsigned char stop); + void start_scroll_diag_right(unsigned char start, unsigned char stop); + void start_scroll_diag_left(unsigned char start, unsigned char stop); + void stop_scroll(); + void set_cursor(short x, short y); + short get_cursor_x() const; + short get_cursor_y() const; + unsigned char get_rotation() const; + void invert_display(unsigned char i); + + signed char draw_pixel(short x, short y, short color); + void write_line(short x0, short y0, short x1, short y1, short color); + void draw_circle_helper( short x0, short y0, short r, unsigned char cornername, short color); + void draw_line(short x0, short y0, short x1, short y1, short color); + void draw_rect(short x, short y, short w, short h, short color); + void fill_rect(short x, short y, short w, short h, short color); + void draw_circle(short x0, short y0, short r, short color); + void fill_circle_helper(short x0, short y0, short r, unsigned char cornername, short delta, short color); + void fill_circle(short x0, short y0, short r, short color); + void draw_triangle(short x0, short y0, short x1, short y1, short x2, short y2, short color); + void fill_triangle(short x0, short y0, short x1, short y1, short x2, short y2, short color); + void draw_round_rect(short x, short y, short w, short h, short r, short color); + void fill_round_rect(short x, short y, short w, short h, short r, short color); + void draw_bitmap(short x, short y, const unsigned char* bitmap, short w, short h, short color); + short oled_write(unsigned char c); + + void set_text_size(unsigned char s); + void set_text_color(short c); + void set_text_wrap(bool w); + void draw_char(short x, short y, unsigned char c, short color, short bg, unsigned char size); + short print_str(const char *strPtr); + short println(); + short print_strln(const char *strPtr); + + short print_number(unsigned long n, unsigned char base); + short print_number_ul(unsigned long n, int base); + short print_number_ul_ln(unsigned long num, int base); + short print_number_ui(unsigned int n, int base); + short print_number_ui_ln(unsigned int n, int base); + short print_number_uc(unsigned char b, int base); + short print_number_uc_ln(unsigned char b, int base); + short print_number_l(long n, int base); + short print_number_l_ln(long num, int base); + short print_number_i(int n, int base); + short print_number_i_ln(int n, int base); + short print_float(double number, unsigned char digits); + short print_float_ln(double num, int digits); +}; diff --git a/client/loop.hpp b/client/loop.hpp new file mode 100644 index 0000000..0ab0259 --- /dev/null +++ b/client/loop.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace acs +{ + class loop + { + boost::asio::io_service io_service_; + public: + + explicit loop() = default; + + boost::asio::io_service* get_io_service() + { + return &io_service_; + } + + void run() + { + io_service_.run(); + } + }; +} \ No newline at end of file diff --git a/client/main.cpp b/client/main.cpp new file mode 100644 index 0000000..17c1812 --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,6 @@ +#include "factory.hpp" + +int main() +{ + acs::factory::get()->get_loop()->run(); +} diff --git a/client/mysql_conn.cpp b/client/mysql_conn.cpp new file mode 100644 index 0000000..07cfcb4 --- /dev/null +++ b/client/mysql_conn.cpp @@ -0,0 +1,34 @@ +#include "mysql_conn.hpp" +#include + +namespace acs +{ + mysql_conn::mysql_conn() : conn_(false) + { + if (!conn_.connect("acs", "localhost", "acs", "testpass", 3306)) + { + std::cerr << "Failed to connect to database." << std::endl; + exit(1); + } + } + + std::string mysql_conn::get_stunum(uint32_t uid) + { + char query_str[60] = { 0 }; + std::sprintf(query_str, "SELECT `stunum` FROM `auth` WHERE uid = %d LIMIT 1", uid); + auto query = conn_.query(query_str); + auto result = query.store(); + if (!result) + return {}; + for (auto&& row : result) + return row[0].c_str(); + return {}; + } + + bool mysql_conn::stunum_exists(const std::string& stunum) + { + std::string query_str("SELECT `id` FROM `auth` WHERE stunum = \""); + auto query = conn_.query(query_str + stunum + "\" LIMIT 1"); + return query.store(); + } +} diff --git a/client/mysql_conn.hpp b/client/mysql_conn.hpp new file mode 100644 index 0000000..497088b --- /dev/null +++ b/client/mysql_conn.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace acs +{ + class mysql_conn + { + mysqlpp::Connection conn_; + + public: + explicit mysql_conn(); + + std::string get_stunum(uint32_t uid); + + bool stunum_exists(const std::string& stunum); + }; +} \ No newline at end of file diff --git a/client/oled_writer.cpp b/client/oled_writer.cpp new file mode 100644 index 0000000..26b739c --- /dev/null +++ b/client/oled_writer.cpp @@ -0,0 +1,112 @@ + +#include "oled_writer.hpp" +#include "factory.hpp" + +namespace acs +{ + const unsigned char oled_writer::tick[] = { + 0b00000000, 0b01111110, 0b00000000, + 0b00000011, 0b11111111, 0b11000000, + 0b00001111, 0b11111111, 0b11110000, + 0b00011111, 0b11111111, 0b11111000, + 0b00111111, 0b11111111, 0b11111100, + 0b01111111, 0b11111111, 0b11111110, + 0b01111111, 0b11111111, 0b11000110, + 0b01111111, 0b11111111, 0b10000110, + 0b11111111, 0b11111111, 0b00000111, + 0b11111111, 0b11111110, 0b00001111, + 0b11100011, 0b11111100, 0b00011111, + 0b11100001, 0b11111000, 0b00111111, + 0b11100000, 0b11110000, 0b01111111, + 0b11110000, 0b01100000, 0b11111111, + 0b11111000, 0b00000001, 0b11111111, + 0b11111100, 0b00000011, 0b11111111, + 0b01111110, 0b00000111, 0b11111110, + 0b01111111, 0b00001111, 0b11111110, + 0b01111111, 0b10011111, 0b11111110, + 0b00111111, 0b11111111, 0b11111100, + 0b00011111, 0b11111111, 0b11111000, + 0b00001111, 0b11111111, 0b11110000, + 0b00000011, 0b11111111, 0b11000000, + 0b00000000, 0b01111110, 0b00000000 + }; + const unsigned char oled_writer::cross[] = { + 0b00000000, 0b11111111, 0b00000000, + 0b00000011, 0b11111111, 0b11000000, + 0b00001111, 0b11111111, 0b11110000, + 0b00011111, 0b11111111, 0b11111000, + 0b00110001, 0b11111111, 0b10001100, + 0b00110000, 0b11111111, 0b00001100, + 0b01110000, 0b01111110, 0b00001110, + 0b01111000, 0b00111100, 0b00011110, + 0b11111100, 0b00011000, 0b00111111, + 0b11111110, 0b00000000, 0b01111111, + 0b11111111, 0b00000000, 0b11111111, + 0b11111111, 0b10000001, 0b11111111, + 0b11111111, 0b10000001, 0b11111111, + 0b11111111, 0b00000000, 0b11111111, + 0b11111110, 0b00000000, 0b01111111, + 0b11111100, 0b00011000, 0b00111111, + 0b01111000, 0b00111100, 0b00011110, + 0b01110000, 0b01111110, 0b00001110, + 0b00110000, 0b11111111, 0b00001100, + 0b00110001, 0b11111111, 0b10001100, + 0b00011111, 0b11111111, 0b11111000, + 0b00001111, 0b11111111, 0b11110000, + 0b00000011, 0b11111111, 0b11000000, + 0b00000000, 0b11111111, 0b00000000 + }; + void oled_writer::refresh() + { + oled_->clear_display(); + oled_->display(); + } + + oled_writer::oled_writer() : qr_encoder_(factory::get()->get_qr_encoder()) + { + oled_ = factory::get()->get_devices()->oled(); + oled_->set_text_color(WHITE); + oled_->display_init_seq(); + refresh(); + } + + void oled_writer::authorized() + { + refresh([this]() + { + oled_->set_text_size(2); + oled_->set_cursor(5, 43); + oled_->print_str("Authorized"); + oled_->draw_bitmap(52, 8, tick, 24, 24, WHITE); + }); + } + + void oled_writer::access_denied() + { + refresh([this]() + { + oled_->set_text_size(2); + oled_->set_cursor(9, 43); + oled_->print_str("Forbidden"); + oled_->draw_bitmap(52, 8, cross, 24, 24, WHITE); + }); + } + + void oled_writer::write_qr_code() + { + refresh([this]() + { + auto width = qr_encoder_->get_width(); + auto map = qr_encoder_->get_map(); + auto border = (64 - width) / 2; + for (auto i = border; i < width + border; ++i) + { + for (auto j = border; j < width + border; ++j) + { + if (map[(i - border) * width + (j - border)]) + oled_->draw_pixel(j, i, WHITE); + } + } + }); + } +} diff --git a/client/oled_writer.hpp b/client/oled_writer.hpp new file mode 100644 index 0000000..3c430c7 --- /dev/null +++ b/client/oled_writer.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace acs +{ + class qr_encoder; + class oled_writer + { + static const unsigned char tick[72]; + static const unsigned char cross[72]; + qr_encoder* qr_encoder_; + ssd1306* oled_ = nullptr; + template + void refresh(F func) + { + oled_->clear_display(); + func(); + oled_->display(); + } + public: + explicit oled_writer(); + void refresh(); + void authorized(); + void access_denied(); + void write_qr_code(); + }; +} diff --git a/client/qr_encoder.cpp b/client/qr_encoder.cpp new file mode 100644 index 0000000..0107c49 --- /dev/null +++ b/client/qr_encoder.cpp @@ -0,0 +1,32 @@ + +#include "qr_encoder.hpp" +#include +#include +namespace acs +{ + + bool qr_encoder::encode_url(const std::string& suffix) + { + memset(map_, 0, 64 * 64); + auto url = std::string(base_url) + suffix; + auto qr_code = QRcode_encodeString(url.c_str(), 8, QR_ECLEVEL_M, QR_MODE_8, true); + width_ = qr_code->width; + if (width_ > 64) + return false; + for (auto i = 0U; i < width_ * width_; ++i) + { + map_[i] = qr_code->data[i] % 2 ? 0 : 1; + } + return true; + } + + unsigned char* qr_encoder::get_map() + { + return map_; + } + + unsigned qr_encoder::get_width() const + { + return width_; + } +} diff --git a/client/qr_encoder.hpp b/client/qr_encoder.hpp new file mode 100644 index 0000000..af2dfc9 --- /dev/null +++ b/client/qr_encoder.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace acs +{ + class qr_encoder + { + unsigned char map_[64 * 64] = { 0 }; + unsigned width_ = 0; + static constexpr auto base_url = "https://api.cismon.net/auth/"; + public: + explicit qr_encoder() = default; + bool encode_url(const std::string& suffix); + + unsigned char* get_map(); + + unsigned get_width() const; + }; +} \ No newline at end of file diff --git a/client/reader_timer.cpp b/client/reader_timer.cpp new file mode 100644 index 0000000..bf06da4 --- /dev/null +++ b/client/reader_timer.cpp @@ -0,0 +1,45 @@ + +#include "reader_timer.hpp" +#include "factory.hpp" +#include +#include + +namespace acs +{ + void reader_timer::callback_handler(const boost::system::error_code& ec) + { + if (ec) + exit(1); + auto factory = factory::get(); + auto state = factory->get_state(); + if (state->get_status() == state::idle) { + auto reader = factory->get_reader(); + auto uid = reader->find_card(); + if (uid) { + std::cout << "UID: " << uid << std::endl; + auto stu_num = reader->get_student_number(); + if (conn_->get_stunum(uid) == stu_num) { + controller_->authorized(); + } else { + controller_->forbidden(); + } + } + } + defer(); + } + + void reader_timer::defer() + { + timer_.expires_from_now(boost::posix_time::millisec(200)); + timer_.async_wait(boost::bind( + &reader_timer::callback_handler, this, boost::asio::placeholders::error)); + } + + reader_timer::reader_timer() : loop_( + factory::get()->get_loop()), timer_( + *loop_->get_io_service()), controller_( + factory::get()->get_controller()), conn_(factory::get()->get_mysql()) + { + defer(); + } +} diff --git a/client/reader_timer.hpp b/client/reader_timer.hpp new file mode 100644 index 0000000..0073da2 --- /dev/null +++ b/client/reader_timer.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace acs +{ + class loop; + class access_controller; + class mysql_conn; + + class reader_timer + { + loop* loop_; + + boost::asio::deadline_timer timer_; + + access_controller* controller_; + + mysql_conn* conn_; + + void callback_handler(const boost::system::error_code& ec); + void defer(); + public: + explicit reader_timer(); + }; +} diff --git a/client/request_delegate.cpp b/client/request_delegate.cpp new file mode 100644 index 0000000..68fc73b --- /dev/null +++ b/client/request_delegate.cpp @@ -0,0 +1,41 @@ + +#include "request_delegate.hpp" +#include "http_client.hpp" +#include "factory.hpp" +#include + +namespace acs +{ + void request_delegate::response_handler(const std::string& response_body) const + { + if (response_body == "") { + callback_({ {}, {} }); + return; + } + boost_helper::json response(response_body); + if (!response.has("err")) { + std::cerr << "Bad response. " << response_body << std::endl; + callback_({ {}, {} }); + return; + } + if (response["err"].val() != "0") { + std::cerr << "Server responded with error " << + response["err"].val() << response["data"].val() << std::endl; + callback_({ {}, {} }); + return; + } + callback_({ response["data"]["token"].val(), response["data"]["stunum"].val() }); + } + + void request_delegate::execute(const callback_t& callback) + { + callback_ = callback; + // Unfortunately we can't DI client here. + // There's a bug in lower versions of Boost.Beast, + // resulting flat_buffer instance not being cleared properly. + delete client_; + client_ = new http_client; + client_->async_get(host, query, boost::bind( + &request_delegate::response_handler, this, _1)); + } +} diff --git a/client/request_delegate.hpp b/client/request_delegate.hpp new file mode 100644 index 0000000..9010ed9 --- /dev/null +++ b/client/request_delegate.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace acs +{ + class http_client; + + class request_delegate + { + http_client* client_ = nullptr; + + static constexpr auto host = "api.cismon.net"; + + static constexpr auto query = "/client/1/testacsclient"; + + using callback_t = std::function&)>; + + callback_t callback_; + + void response_handler(const std::string& response_body) const; + + public: + + explicit request_delegate() = default; + + void execute(const callback_t& callback); + }; +} diff --git a/client/request_timer.cpp b/client/request_timer.cpp new file mode 100644 index 0000000..94de986 --- /dev/null +++ b/client/request_timer.cpp @@ -0,0 +1,53 @@ +#include "request_timer.hpp" +#include "factory.hpp" + +namespace acs +{ + void request_timer::response_handler(const std::pair& response) + { + auto factory = factory::get(); + auto token = response.first; + auto stunum = response.second; + if (token != "") + { + auto qr_encoder = factory->get_qr_encoder(); + qr_encoder->encode_url(token); + if (stunum != "") { + if (conn_->stunum_exists(stunum)) + controller_->authorized(); + else + controller_->forbidden(); + } else { + controller_->force_reset_if_idle(); + } + } + defer(); + } + + void request_timer::callback_handler(const boost::system::error_code& ec) + { + if (ec) + exit(1); + auto factory = factory::get(); + auto state = factory->get_state(); + if (state->get_status() == state::idle) + request_delegate_->execute(boost::bind(&request_timer::response_handler, this, _1)); + } + + void request_timer::defer() + { + timer_.expires_from_now(boost::posix_time::millisec(5'000)); + timer_.async_wait(boost::bind( + &request_timer::callback_handler, this, boost::asio::placeholders::error)); + } + + request_timer::request_timer() : loop_( + factory::get()->get_loop()), timer_( + *loop_->get_io_service()), conn_( + factory::get()->get_mysql()), request_delegate_( + factory::get()->get_request_delegate()), controller_( + factory::get()->get_controller()) + { + defer(); + } +} diff --git a/client/request_timer.hpp b/client/request_timer.hpp new file mode 100644 index 0000000..dbe5114 --- /dev/null +++ b/client/request_timer.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace acs +{ + class loop; + class mysql_conn; + class request_delegate; + class access_controller; + class request_timer + { + loop* loop_; + + boost::asio::deadline_timer timer_; + + mysql_conn* conn_; + + request_delegate* request_delegate_; + + access_controller* controller_; + + void response_handler(const std::pair& response); + + void callback_handler(const boost::system::error_code& ec); + void defer(); + public: + explicit request_timer(); + }; +} diff --git a/client/state.hpp b/client/state.hpp new file mode 100644 index 0000000..3885da7 --- /dev/null +++ b/client/state.hpp @@ -0,0 +1,25 @@ +#pragma once + +namespace acs +{ + class state + { + public: + enum status { + idle, entry_await + }; + + private: + status status_ = idle; + public: + explicit state() = default; + status get_status() const + { + return status_; + } + void set_status(status new_status) + { + status_ = new_status; + } + }; +} \ No newline at end of file diff --git a/server/acs b/server/acs new file mode 100644 index 0000000..1e7a0ff --- /dev/null +++ b/server/acs @@ -0,0 +1,10 @@ +#!/usr/bin/env php +=7.1", + "cismonx/acast": "^1.2", + "twt/sso": "^1.0" + } +} \ No newline at end of file diff --git a/server/constants.php b/server/constants.php new file mode 100644 index 0000000..e0783e9 --- /dev/null +++ b/server/constants.php @@ -0,0 +1,12 @@ + '127.0.0.1', + 'port' => '3306', + 'user' => 'root', + 'password' => 'generator', + 'db_name' => 'acs', + 'charset' => 'utf8mb4' +]; \ No newline at end of file diff --git a/server/src/Controller/Auth.php b/server/src/Controller/Auth.php new file mode 100644 index 0000000..7696314 --- /dev/null +++ b/server/src/Controller/Auth.php @@ -0,0 +1,55 @@ +sso_api = new SSO\Api(Config::get('TWT_SSO_ID'), Config::get('TWT_SSO_KEY')); + } + + function init() + { + $token = strval($this->params['token']); + $login_url = $this->sso_api->getLoginUrl('https://api.cismon.net/auth-confirm/'.$token); + Http::header('Location: '.$login_url); + } + + function confirm() + { + $auth_token = strval($this->params['token']); + $token = strval($_GET['token']); + $stunum = strval($this->sso_api->fetchUserInfo($token)->result->user_number); + if ($stunum != '') { + if ($this->model->updateStunum($auth_token, $stunum)) { + $this->view->retJson("Auth complete, waiting."); + return; + } + $this->view->retJson("Pending auth or bad token.", 3); + return; + } + $this->view->retJson("Auth failed.", 4); + } +} \ No newline at end of file diff --git a/server/src/Controller/Client.php b/server/src/Controller/Client.php new file mode 100644 index 0000000..00ee296 --- /dev/null +++ b/server/src/Controller/Client.php @@ -0,0 +1,59 @@ +id = intval($this->params['id']); + $this->key = strval($this->params['key']); + } + + function tick() + { + if ($this->id < 1 || empty($this->key)) { + $this->view->retJson('Bad request.', -1, 400); + return; + } + $result = $this->model->fetchById($this->id); + if (!$result || $result['key'] != $this->key) { + $this->view->retJson('Invalid credentials.', 1, 403); + return; + } + $token = ''; + $expired = time() > $result['updated'] + Config::get('EXPIRY_TIME'); + // No token yet; Token expired; Token used. + if ($result['token'] == '' || $expired || $result['stunum'] != '') { + $token = strtr(base64_encode(random_bytes(15)), '+/', '-_'); + if (!$this->model->updateToken($this->id, $token)) { + $this->view->retJson('Failed to update token', -3, 500); + return; + } + } + $stunum = strval($result['stunum']); + $this->view->retJson([ + 'token' => $token, + 'stunum' => $stunum + ]); + } +} \ No newline at end of file diff --git a/server/src/Init/config.php b/server/src/Init/config.php new file mode 100644 index 0000000..bfb06b1 --- /dev/null +++ b/server/src/Init/config.php @@ -0,0 +1,13 @@ + 13, + 'TWT_SSO_KEY' => 'mBA0G56tzc1L2DbY2oXV', + 'EXPIRY_TIME' => 300 + ]); +} \ No newline at end of file diff --git a/server/src/Init/database.php b/server/src/Init/database.php new file mode 100644 index 0000000..fb01a50 --- /dev/null +++ b/server/src/Init/database.php @@ -0,0 +1,15 @@ +addServer(_MEMCACHED_HOST, _MEMCACHED_PORT)) + Console::warning('Failed to add memcached server.'); + //Init mysql connection. + Model::config(_MYSQL_CONFIG); +} \ No newline at end of file diff --git a/server/src/Init/routes.php b/server/src/Init/routes.php new file mode 100644 index 0000000..4d81a4a --- /dev/null +++ b/server/src/Init/routes.php @@ -0,0 +1,21 @@ +add(['auth', '/token'], 'GET', function () { + $this->invoke(); + })->bind(['Auth', 'init']); + $router->add(['client', '/id', '/key'], 'GET', function () { + $this->invoke(); + })->bind(['Client', 'tick']); + $router->add(['auth-confirm', '/token'], 'GET', function () { + $this->invoke(); + })->bind(['Auth', 'confirm']); + Server::app('acs')->route('acs'); +} \ No newline at end of file diff --git a/server/src/Init/structure.php b/server/src/Init/structure.php new file mode 100644 index 0000000..d334832 --- /dev/null +++ b/server/src/Init/structure.php @@ -0,0 +1,15 @@ +table('client'); + } + + function fetchById($id) + { + return $this->_select(['`key`', 'token', 'stunum', 'UNIX_TIMESTAMP(updated) AS updated'], + 'id='.$id, null, null, null, self::ROW); + } + function fetchByToken($token) + { + if (empty($token)) + return false; + return $this->_select(['id', '`key`', 'stunum', 'UNIX_TIMESTAMP(updated) AS updated'], + 'token=:t', ['t' => $token], null, null, self::ROW); + } + function updateToken($id, $token) + { + return $this->_update(['token', 'stunum'], 'id='.$id, ['token' => $token, 'stunum' => ''], 1); + } + + function updateStunum($token, $stunum) + { + if (empty($token)) + return 0; + return $this->_update(['stunum'], ['token=:t', 'stunum=\'\''], [ + 't' => $token, + 'stunum' => $stunum + ], 1); + } +} \ No newline at end of file diff --git a/server/src/View/Auth.php b/server/src/View/Auth.php new file mode 100644 index 0000000..f137ce9 --- /dev/null +++ b/server/src/View/Auth.php @@ -0,0 +1,7 @@ +_controller->retMsg = json_encode([ + 'err' => $err, + 'data' => $msg + ]); + } +} \ No newline at end of file diff --git a/server/src/main.php b/server/src/main.php new file mode 100644 index 0000000..58319a3 --- /dev/null +++ b/server/src/main.php @@ -0,0 +1,24 @@ +workerConfig([ + 'name' => 'acs', + 'count' => 1 +]); + +Server::app('acs')->event('WorkerStart', function () { + init_config(); + init_structure(); + init_routes(); + init_database(); +}); \ No newline at end of file