| /* |
| * Author: Jon Trulson <jtrulson@ics.com> |
| * Copyright (c) 2015 Intel Corporation. |
| * |
| * 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. |
| */ |
| |
| #include <iostream> |
| #include <string> |
| #include <stdexcept> |
| |
| #include "zfm20.h" |
| |
| using namespace upm; |
| using namespace std; |
| |
| static const int defaultDelay = 100; // max wait time for read |
| |
| ZFM20::ZFM20(int uart) |
| { |
| m_ttyFd = -1; |
| |
| if ( !(m_uart = mraa_uart_init(uart)) ) |
| { |
| throw std::invalid_argument(std::string(__FUNCTION__) + |
| ": mraa_uart_init() failed"); |
| return; |
| } |
| |
| // This requires a recent MRAA (1/2015) |
| const char *devPath = mraa_uart_get_dev_path(m_uart); |
| |
| if (!devPath) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": mraa_uart_get_dev_path() failed"); |
| return; |
| } |
| |
| // now open the tty |
| if ( (m_ttyFd = open(devPath, O_RDWR)) == -1) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": open of " + |
| string(devPath) + " failed: " + |
| string(strerror(errno))); |
| return; |
| } |
| |
| // Set the default password and address |
| setPassword(ZFM20_DEFAULT_PASSWORD); |
| setAddress(ZFM20_DEFAULT_ADDRESS); |
| |
| initClock(); |
| } |
| |
| ZFM20::~ZFM20() |
| { |
| if (m_ttyFd != -1) |
| close(m_ttyFd); |
| |
| mraa_deinit(); |
| } |
| |
| bool ZFM20::dataAvailable(unsigned int millis) |
| { |
| if (m_ttyFd == -1) |
| return false; |
| |
| struct timeval timeout; |
| |
| // no waiting |
| timeout.tv_sec = 0; |
| timeout.tv_usec = millis * 1000; |
| |
| int nfds; |
| fd_set readfds; |
| |
| FD_ZERO(&readfds); |
| |
| FD_SET(m_ttyFd, &readfds); |
| |
| if (select(m_ttyFd + 1, &readfds, NULL, NULL, &timeout) > 0) |
| return true; // data is ready |
| else |
| return false; |
| } |
| |
| int ZFM20::readData(char *buffer, int len) |
| { |
| if (m_ttyFd == -1) |
| return(-1); |
| |
| if (!dataAvailable(defaultDelay)) |
| return 0; // timed out |
| |
| int rv = read(m_ttyFd, buffer, len); |
| |
| if (rv < 0) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": read() failed: " + |
| string(strerror(errno))); |
| return rv; |
| } |
| |
| return rv; |
| } |
| |
| int ZFM20::writeData(char *buffer, int len) |
| { |
| if (m_ttyFd == -1) |
| return(-1); |
| |
| // first, flush any pending but unread input |
| tcflush(m_ttyFd, TCIFLUSH); |
| |
| int rv = write(m_ttyFd, buffer, len); |
| |
| if (rv < 0) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": write() failed: " + |
| string(strerror(errno))); |
| return rv; |
| } |
| |
| if (rv == 0) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": write() failed, no bytes written"); |
| return rv; |
| } |
| |
| tcdrain(m_ttyFd); |
| |
| return rv; |
| } |
| |
| bool ZFM20::setupTty(speed_t baud) |
| { |
| if (m_ttyFd == -1) |
| return(false); |
| |
| struct termios termio; |
| |
| // get current modes |
| tcgetattr(m_ttyFd, &termio); |
| |
| // setup for a 'raw' mode. 81N, no echo or special character |
| // handling, such as flow control. |
| cfmakeraw(&termio); |
| |
| // set our baud rates |
| cfsetispeed(&termio, baud); |
| cfsetospeed(&termio, baud); |
| |
| // make it so |
| if (tcsetattr(m_ttyFd, TCSAFLUSH, &termio) < 0) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": tcsetattr() failed: " + |
| string(strerror(errno))); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int ZFM20::writeCmdPacket(uint8_t *pkt, int len) |
| { |
| uint8_t rPkt[ZFM20_MAX_PKT_LEN]; |
| |
| rPkt[0] = ZFM20_START1; // header bytes |
| rPkt[1] = ZFM20_START2; |
| |
| rPkt[2] = (m_address >> 24) & 0xff; // address |
| rPkt[3] = (m_address >> 16) & 0xff; |
| rPkt[4] = (m_address >> 8) & 0xff; |
| rPkt[5] = m_address & 0xff; |
| |
| rPkt[6] = PKT_COMMAND; |
| |
| rPkt[7] = ((len + 2) >> 8) & 0xff; // length (+ len bytes) |
| rPkt[8] = (len + 2) & 0xff; |
| |
| // compute the starting checksum |
| uint16_t cksum = rPkt[7] + rPkt[8] + PKT_COMMAND; |
| |
| int j = 9; |
| for (int i=0; i<len; i++) |
| { |
| rPkt[j] = pkt[i]; |
| cksum += rPkt[j]; |
| j++; |
| } |
| |
| rPkt[j++] = (cksum >> 8) & 0xff; // store the cksum |
| rPkt[j++] = cksum & 0xff; |
| |
| return writeData((char *)rPkt, j); |
| } |
| |
| void ZFM20::initClock() |
| { |
| gettimeofday(&m_startTime, NULL); |
| } |
| |
| uint32_t ZFM20::getMillis() |
| { |
| struct timeval elapsed, now; |
| uint32_t elapse; |
| |
| // get current time |
| gettimeofday(&now, NULL); |
| |
| // compute the delta since m_startTime |
| if( (elapsed.tv_usec = now.tv_usec - m_startTime.tv_usec) < 0 ) |
| { |
| elapsed.tv_usec += 1000000; |
| elapsed.tv_sec = now.tv_sec - m_startTime.tv_sec - 1; |
| } |
| else |
| { |
| elapsed.tv_sec = now.tv_sec - m_startTime.tv_sec; |
| } |
| |
| elapse = (uint32_t)((elapsed.tv_sec * 1000) + (elapsed.tv_usec / 1000)); |
| |
| // never return 0 |
| if (elapse == 0) |
| elapse = 1; |
| |
| return elapse; |
| } |
| |
| bool ZFM20::verifyPacket(uint8_t *pkt, int len) |
| { |
| // verify packet header |
| if (pkt[0] != ZFM20_START1 || pkt[1] != ZFM20_START2) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": Invalid packet header"); |
| return false; |
| } |
| |
| // check the ack byte |
| if (pkt[6] != PKT_ACK) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": Invalid ACK code"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ZFM20::getResponse(uint8_t *pkt, int len) |
| { |
| char buf[ZFM20_MAX_PKT_LEN]; |
| |
| initClock(); |
| |
| int idx = 0; |
| int timer = 0; |
| int rv; |
| int plen = 0; |
| |
| while (idx < len) |
| { |
| // wait for some data |
| if (!dataAvailable(100)) |
| { |
| timer += getMillis(); |
| if (timer > ZFM20_TIMEOUT) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": Timed out waiting for packet"); |
| return false; |
| } |
| |
| continue; |
| } |
| |
| if ((rv = readData(buf, ZFM20_MAX_PKT_LEN)) == 0) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": readData() failed, no data returned"); |
| return false; |
| } |
| |
| // copy it into the user supplied buffer |
| for (int i=0; i<rv; i++) |
| { |
| pkt[idx++] = buf[i]; |
| if (idx >= len) |
| break; |
| } |
| } |
| |
| // now verify it. |
| return verifyPacket(pkt, len); |
| } |
| |
| bool ZFM20::verifyPassword() |
| { |
| const int pktLen = 5; |
| uint8_t pkt[pktLen] = {CMD_VERIFY_PASSWORD, |
| static_cast<uint8_t>((m_password >> 24) & 0xff), |
| static_cast<uint8_t>((m_password >> 16) & 0xff), |
| static_cast<uint8_t>((m_password >> 8) & 0xff), |
| static_cast<uint8_t>(m_password & 0xff) }; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| |
| return true; |
| } |
| |
| int ZFM20::getNumTemplates() |
| { |
| const int pktLen = 1; |
| uint8_t pkt[pktLen] = {CMD_GET_TMPL_COUNT}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 14; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| // check confirmation code |
| if (rPkt[9] != 0x00) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": Invalid confirmation code"); |
| return 0; |
| } |
| |
| return ((rPkt[10] << 8) | rPkt[11]); |
| } |
| |
| bool ZFM20::setNewPassword(uint32_t pwd) |
| { |
| const int pktLen = 5; |
| uint8_t pkt[pktLen] = {CMD_SET_PASSWORD, |
| static_cast<uint8_t>((pwd >> 24) & 0xff), |
| static_cast<uint8_t>((pwd >> 16) & 0xff), |
| static_cast<uint8_t>((pwd >> 8) & 0xff), |
| static_cast<uint8_t>(pwd & 0xff) }; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| // check confirmation code |
| if (rPkt[9] != 0x00) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": Invalid confirmation code"); |
| return false; |
| } |
| |
| m_password = pwd; |
| |
| return true; |
| } |
| |
| bool ZFM20::setNewAddress(uint32_t addr) |
| { |
| const int pktLen = 5; |
| uint8_t pkt[pktLen] = {CMD_SET_ADDRESS, |
| static_cast<uint8_t>((addr >> 24) & 0xff), |
| static_cast<uint8_t>((addr >> 16) & 0xff), |
| static_cast<uint8_t>((addr >> 8) & 0xff), |
| static_cast<uint8_t>(addr & 0xff) }; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| // check confirmation code |
| if (rPkt[9] != 0x00) |
| { |
| throw std::runtime_error(std::string(__FUNCTION__) + |
| ": Invalid confirmation code"); |
| return false; |
| } |
| |
| m_address = addr; |
| |
| return true; |
| } |
| |
| uint8_t ZFM20::generateImage() |
| { |
| const int pktLen = 1; |
| uint8_t pkt[pktLen] = {CMD_GEN_IMAGE}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| return rPkt[9]; |
| } |
| |
| uint8_t ZFM20::image2Tz(int slot) |
| { |
| if (slot != 1 && slot != 2) |
| { |
| throw std::out_of_range(std::string(__FUNCTION__) + |
| ": slot must be 1 or 2"); |
| return ERR_INTERNAL_ERR; |
| } |
| |
| const int pktLen = 2; |
| uint8_t pkt[pktLen] = {CMD_IMG2TZ, |
| static_cast<uint8_t>(slot & 0xff)}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| return rPkt[9]; |
| } |
| |
| uint8_t ZFM20::createModel() |
| { |
| const int pktLen = 1; |
| uint8_t pkt[pktLen] = {CMD_REGMODEL}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| return rPkt[9]; |
| } |
| |
| uint8_t ZFM20::storeModel(int slot, uint16_t id) |
| { |
| if (slot != 1 && slot != 2) |
| { |
| throw std::out_of_range(std::string(__FUNCTION__) + |
| ": slot must be 1 or 2"); |
| return ERR_INTERNAL_ERR; |
| } |
| |
| const int pktLen = 4; |
| uint8_t pkt[pktLen] = {CMD_STORE, |
| static_cast<uint8_t>(slot & 0xff), |
| static_cast<uint8_t>((id >> 8) & 0xff), |
| static_cast<uint8_t>(id & 0xff)}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| return rPkt[9]; |
| } |
| |
| uint8_t ZFM20::deleteModel(uint16_t id) |
| { |
| const int pktLen = 5; |
| uint8_t pkt[pktLen] = {CMD_DELETE_TMPL, |
| static_cast<uint8_t>((id >> 8) & 0xff), |
| static_cast<uint8_t>(id & 0xff), |
| 0x00, |
| 0x01}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| return rPkt[9]; |
| } |
| |
| uint8_t ZFM20::deleteDB() |
| { |
| const int pktLen = 1; |
| uint8_t pkt[pktLen] = {CMD_EMPTYDB}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 12; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| return rPkt[9]; |
| } |
| |
| uint8_t ZFM20::search(int slot, uint16_t *id, uint16_t *score) |
| { |
| *id = 0; |
| *score = 0; |
| |
| if (slot != 1 && slot != 2) |
| { |
| throw std::out_of_range(std::string(__FUNCTION__) + |
| ": slot must be 1 or 2"); |
| return ERR_INTERNAL_ERR; |
| } |
| |
| // search from page 0x0000 to page 0x00a3 |
| const int pktLen = 6; |
| uint8_t pkt[pktLen] = {CMD_SEARCH, |
| static_cast<uint8_t>(slot & 0xff), |
| 0x00, |
| 0x00, |
| 0x00, |
| 0xa3}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 16; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| // if it was found, extract the location and the score |
| if (rPkt[9] == ERR_OK) |
| { |
| *id = ((rPkt[10] << 8) & 0xff) | (rPkt[11] & 0xff); |
| *score = ((rPkt[12] << 8) & 0xff) | (rPkt[13] & 0xff); |
| } |
| |
| return rPkt[9]; |
| } |
| |
| uint8_t ZFM20::match(uint16_t *score) |
| { |
| *score = 0; |
| |
| const int pktLen = 1; |
| uint8_t pkt[pktLen] = {CMD_MATCH}; |
| |
| writeCmdPacket(pkt, pktLen); |
| |
| // now read a response |
| const int rPktLen = 14; |
| uint8_t rPkt[rPktLen]; |
| |
| getResponse(rPkt, rPktLen); |
| |
| *score = ((rPkt[10] << 8) & 0xff) | (rPkt[11] & 0xff); |
| |
| return rPkt[9]; |
| } |
| |
| |