DUDS
Distributed Update of Data from Something
clockLCD.cpp

Uses a 20x4 text LCD to display the time and date.

/*
* This file is part of the DUDS project. It is subject to the BSD-style
* license terms in the LICENSE file found in the top-level directory of this
* distribution and at http://www.somewhere.org/somepath/license.html.
* No part of DUDS, including this file, may be copied, modified, propagated,
* or distributed except according to the terms contained in the LICENSE file.
*
* Copyright (C) 2018 Jeff Jackowski
*/
#ifdef USE_SYSFS_PORT
#else
#endif
#include <boost/property_tree/info_parser.hpp>
#include <iostream>
#include <sstream>
#include <thread>
#include <iomanip>
#include <algorithm>
#include <chrono>
#include <assert.h>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/program_options.hpp>
struct LargeCharUnsupported : duds::hardware::display::DisplayError { };
void WriteLarge(
const std::shared_ptr<duds::hardware::display::TextDisplay> &disp,
const std::string &str,
unsigned int c,
unsigned int r
);
enum DigitPartCodes {
Clear,
UpLeft,
BarLeft,
BarUp,
BarCorn,
Dot,
DownLeft = UpLeft,
BarDown = BarUp,
};
char DigitPart(int ud, int x, int y, char d);
constexpr std::uint32_t DigSeg(
std::uint32_t x,
std::uint32_t y,
std::uint32_t c
) {
return (c << (x * 3)) << (y * 9);
}
const std::uint32_t DigitFont[2][10] = {
{ // shifted upward and to the left
// 0
DigSeg(0, 0, UpLeft) | DigSeg(1, 0, BarUp) | DigSeg(2, 0, BarLeft) |
DigSeg(0, 1, BarLeft) | DigSeg(1, 1, Clear) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, BarUp) | DigSeg(1, 2, BarUp) | DigSeg(2, 2, BarCorn),
// 1
DigSeg(0, 0, Clear) | DigSeg(1, 0, BarLeft) | DigSeg(2, 0, Clear) |
DigSeg(0, 1, Clear) | DigSeg(1, 1, BarLeft) | DigSeg(2, 1, Clear) |
DigSeg(0, 2, Clear) | DigSeg(1, 2, BarCorn) | DigSeg(2, 2, Clear),
// 2
DigSeg(0, 0, BarUp) | DigSeg(1, 0, BarUp) | DigSeg(2, 0, BarLeft) |
DigSeg(0, 1, UpLeft) | DigSeg(1, 1, BarUp) | DigSeg(2, 1, BarCorn) |
DigSeg(0, 2, BarUp) | DigSeg(1, 2, BarUp) | DigSeg(2, 2, BarCorn),
// 3
DigSeg(0, 0, BarUp) | DigSeg(1, 0, BarUp) | DigSeg(2, 0, BarLeft) |
DigSeg(0, 1, BarUp) | DigSeg(1, 1, BarUp) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, BarUp) | DigSeg(1, 2, BarUp) | DigSeg(2, 2, BarCorn),
// 4
DigSeg(0, 0, BarLeft) | DigSeg(1, 0, Clear) | DigSeg(2, 0, BarLeft) |
DigSeg(0, 1, BarUp) | DigSeg(1, 1, BarUp) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, Clear) | DigSeg(1, 2, Clear) | DigSeg(2, 2, BarCorn),
// 5
DigSeg(0, 0, UpLeft) | DigSeg(1, 0, BarUp) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, BarUp) | DigSeg(1, 1, BarUp) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, BarUp) | DigSeg(1, 2, BarUp) | DigSeg(2, 2, BarCorn),
// 6
DigSeg(0, 0, UpLeft) | DigSeg(1, 0, BarUp) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, UpLeft) | DigSeg(1, 1, BarUp) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, BarUp) | DigSeg(1, 2, BarUp) | DigSeg(2, 2, BarCorn),
// 7
DigSeg(0, 0, BarUp) | DigSeg(1, 0, BarUp) | DigSeg(2, 0, BarLeft) |
DigSeg(0, 1, Clear) | DigSeg(1, 1, Clear) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, Clear) | DigSeg(1, 2, Clear) | DigSeg(2, 2, BarCorn),
// 8
DigSeg(0, 0, UpLeft) | DigSeg(1, 0, BarUp) | DigSeg(2, 0, BarLeft) |
DigSeg(0, 1, UpLeft) | DigSeg(1, 1, BarUp) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, BarUp) | DigSeg(1, 2, BarUp) | DigSeg(2, 2, BarCorn),
// 9
DigSeg(0, 0, UpLeft) | DigSeg(1, 0, BarUp) | DigSeg(2, 0, BarLeft) |
DigSeg(0, 1, BarUp) | DigSeg(1, 1, BarUp) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, BarUp) | DigSeg(1, 2, BarUp) | DigSeg(2, 2, BarCorn)
// could extend for hex
},
{ // shifted downward and to the left
// 0
DigSeg(0, 0, BarDown) | DigSeg(1, 0, BarDown) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, BarLeft) | DigSeg(1, 1, Clear) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, DownLeft)| DigSeg(1, 2, BarDown) | DigSeg(2, 2, BarLeft),
// 1
DigSeg(0, 0, Clear) | DigSeg(1, 0, BarCorn) | DigSeg(2, 0, Clear) |
DigSeg(0, 1, Clear) | DigSeg(1, 1, BarLeft) | DigSeg(2, 1, Clear) |
DigSeg(0, 2, Clear) | DigSeg(1, 2, BarLeft) | DigSeg(2, 2, Clear),
// 2
DigSeg(0, 0, BarDown) | DigSeg(1, 0, BarDown) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, BarDown) | DigSeg(1, 1, BarDown) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, DownLeft)| DigSeg(1, 2, BarDown) | DigSeg(2, 2, BarCorn),
// 3
DigSeg(0, 0, BarDown) | DigSeg(1, 0, BarDown) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, BarDown) | DigSeg(1, 1, BarDown) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, BarDown) | DigSeg(1, 2, BarDown) | DigSeg(2, 2, BarLeft),
// 4
DigSeg(0, 0, BarCorn) | DigSeg(1, 0, Clear) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, DownLeft)| DigSeg(1, 1, BarDown) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, Clear) | DigSeg(1, 2, Clear) | DigSeg(2, 2, BarLeft),
// 5
DigSeg(0, 0, BarDown) | DigSeg(1, 0, BarDown) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, DownLeft)| DigSeg(1, 1, BarDown) | DigSeg(2, 1, BarCorn) |
DigSeg(0, 2, BarDown) | DigSeg(1, 2, BarDown) | DigSeg(2, 2, BarLeft),
// 6
DigSeg(0, 0, BarDown) | DigSeg(1, 0, BarDown) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, DownLeft)| DigSeg(1, 1, BarDown) | DigSeg(2, 1, BarCorn) |
DigSeg(0, 2, DownLeft)| DigSeg(1, 2, BarDown) | DigSeg(2, 2, BarLeft),
// 7
DigSeg(0, 0, BarDown) | DigSeg(1, 0, BarDown) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, Clear) | DigSeg(1, 1, Clear) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, Clear) | DigSeg(1, 2, Clear) | DigSeg(2, 2, BarLeft),
// 8
DigSeg(0, 0, BarDown) | DigSeg(1, 0, BarDown) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, DownLeft)| DigSeg(1, 1, BarDown) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, DownLeft)| DigSeg(1, 2, BarDown) | DigSeg(2, 2, BarLeft),
// 9
DigSeg(0, 0, BarDown) | DigSeg(1, 0, BarDown) | DigSeg(2, 0, BarCorn) |
DigSeg(0, 1, DownLeft)| DigSeg(1, 1, BarDown) | DigSeg(2, 1, BarLeft) |
DigSeg(0, 2, BarDown) | DigSeg(1, 2, BarDown) | DigSeg(2, 2, BarLeft)
// could extend for hex
},
};
char DigitPart(int ud, int x, int y, char d) {
return (DigitFont[ud][d - '0'] >> (y * 9 + x * 3)) & 7;
};
enum GlyphSet {
None,
Upward,
Downward
};
static int glyphSet = 2;
static const char *glyphNames[2][5] = {
{ // up
"UpNumPartUpLeft",
"UpNumPartBarLeft",
"UpNumPartBarUp",
"UpNumPartBarCorn",
"UpNumPartDot"
},
{ // down
"DownNumPartDownLeft",
"DownNumPartBarLeft",
"DownNumPartBarDown",
"DownNumPartBarCorn",
"DownNumPartDot"
}
};
void WriteLarge(
const std::shared_ptr<duds::hardware::devices::displays::HD44780> &disp,
const std::string &str,
unsigned int c,
unsigned int r
) {
// must start on line 0 or 1
if (r > 1) {
)
);
}
// do glyphs need to be loaded?
if ((glyphSet + 1) != r) {
glyphSet = r + 1;
for (int i = 0; i < 5; ++i) {
disp->setGlyph(imgArc.get(glyphNames[r][i]), i + 1);
}
}
// work out width
int width = 0;
for (char c : str) {
if (((c >= '0') && (c <= '9')) || (c == ' ')) {
width += 3;
} else if ((c == ':') || (c == '~') || (c == '.')) {
++width;
} else {
DUDS_THROW_EXCEPTION(LargeCharUnsupported());
}
}
if ((c + width) > disp->columns()) {
)
);
}
// write out 3 lines
for (int y = 0; y < 3; ++y) {
std::string line;
for (char c : str) {
if (c == ':') {
// character only on two rows; other is a space
if ((!r && (y < 2)) || (r && ( y > 0))) {
line.push_back(Dot);
} else {
line.push_back(' ');
}
} else if (c == '~') {
line.push_back(' ');
} else if (c == '.') {
line.push_back(BarCorn);
} else if (c == ' ') {
line += " ";
}else {
for (int x = 0; x < 3; ++x) {
char dp = DigitPart(r, x, y, c);
if (!dp) {
dp = ' ';
}
line.push_back(dp);
}
}
}
// write the line
tdbs << duds::hardware::display::move(c, r + y) << line;
}
}
// -----------------------------------------------------------------------
namespace displays = duds::hardware::devices::displays;
namespace display = duds::hardware::display;
bool quit = false;
void runtest(const std::shared_ptr<displays::HD44780> &tmd)
try {
//display::TextDisplayStream tds(tmd);
std::chrono::high_resolution_clock::time_point start;
float dispTime = 32768.0f; // in microseconds
// examples never delete these
boost::gregorian::date_facet *dateform =
new boost::gregorian::date_facet("%a %b %e, %Y");
// change how dates are output to the stream
tds.imbue(std::locale(tds.getloc(), dateform));
do {
// used to tell how long it took to write the time & date to the display
start = std::chrono::high_resolution_clock::now();
//tds << displays::move(0,0);
lcd->sampleTime(ts);
// put time in time_t; integer value is in seconds UTC
std::time_t tt = duds::time::planetary::earth->timeUtc(ts.value);
// compute microseconds after time in tt
int uS = (ts.value.time_since_epoch().count() / 1000) % 1000000;
// adavnce time ahead by 64ms plus an estimate of how long it takes to
// write out the time and date
int advT = uS + 64000 + (int)dispTime;
// advancing past the sampled second in tt?
if (advT > 1000000) {
// advance tt to match
++tt;
}
// request local time; GNU version includes current timezone
std::tm ltime;
localtime_r(&tt, &ltime);
// test for showing both sets of large digits
if (ltime.tm_min & 1) {
tds << display::move(0,3);
} else {
tds << display::move(0,0);
}
// Get the date of the local time; Boost is better with the dates than
// C++11, but lacks local timezone. Looks like much of the Boost
// date_time library will be in C++20.
boost::gregorian::date date = boost::gregorian::date_from_tm(ltime);
// write out the date and timezone
tds << date << ' ' << std::setw(3) << ltime.tm_zone << display::startLine;
// write out the time to a string stream
std::ostringstream oss;
char sep;
// blinking colon
if (ltime.tm_sec & 1) {
sep = '~';
} else {
sep = ':';
}
oss << std::setfill(' ') << std::setw(2) << ltime.tm_hour << sep <<
std::setfill('0') << std::setw(2) << ltime.tm_min << sep <<
std::setw(2) << ltime.tm_sec;
// write out the time as 3x3 digits to the display
if (ltime.tm_min & 1) { // test for showing both sets of large digits
WriteLarge(tmd, tds, oss.str(), 0, 0);
} else {
WriteLarge(tmd, tds, oss.str(), 0, 1);
}
tds.flush();
// update exponential moving average of how long it takes to display the
// thime
auto timeTaken = std::chrono::high_resolution_clock::now() - start;
dispTime = dispTime * 0.8f + 0.2f *
(float)(std::chrono::duration_cast<std::chrono::microseconds>(
timeTaken
).count());
// time to wait for display update
std::chrono::microseconds delay(1000000 - uS - 64000 - (int)dispTime);
// sleep for at least 16ms
if (delay.count() > 16384) {
std::this_thread::sleep_for(delay);
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
} while (!quit);
// I am curious how long this is. Answer is about 70ms.
std::cout << "Last moving average of time taken to display: " <<
dispTime << "uS" << std::endl;
} catch (...) {
std::cerr << "Program failed in runtest(): " <<
boost::current_exception_diagnostic_information() << std::endl;
}
//typedef duds::general::IntegerBiDirIterator<unsigned int> uintIterator;
int main(int argc, char *argv[])
try {
std::string confpath;
{ // option parsing
boost::program_options::options_description optdesc(
"Options for clockLCD"
);
optdesc.add_options()
( // help info
"help,h",
"Show this help message"
)
(
"conf,c",
boost::program_options::value<std::string>(&confpath)->
default_value("samples/pins.conf"),
"Pin configuration file; REQUIRED"
)
;
boost::program_options::variables_map vm;
boost::program_options::store(
boost::program_options::parse_command_line(argc, argv, optdesc),
vm
);
boost::program_options::notify(vm);
if (vm.count("help")) {
std::cout << "Show the time and date on the attached text LCD\n" <<
argv[0] << " [options]\n" << optdesc << std::endl;
return 0;
}
}
{
std::string imgpath(argv[0]);
int found = 0;
while (!imgpath.empty() && (found < 3)) {
imgpath.pop_back();
if (imgpath.back() == '/') {
++found;
}
}
// load some icons before messing with hardware
imgArc.load(imgpath + "images/numberparts.bppia");
}
// read in digital pin config
boost::property_tree::ptree tree;
boost::property_tree::read_info(confpath, tree);
boost::property_tree::ptree &pinconf = tree.get_child("pins");
// configure display
#ifdef USE_SYSFS_PORT
std::shared_ptr<duds::hardware::interface::linux::SysFsPort> port =
#else
std::shared_ptr<duds::hardware::interface::linux::GpioDevPort> port =
#endif
pc.getPinSetAndSelect(lcdset, lcdsel, "lcdText");
// LCD driver
std::shared_ptr<displays::HD44780> tmd =
std::make_shared<displays::HD44780>(
std::move(lcdset), std::move(lcdsel), 20, 4, std::chrono::microseconds(12)
);
tmd->initialize();
std::thread doit(&runtest, std::ref(tmd));
std::cin.get();
quit = true;
doit.join();
} catch (...) {
std::cerr << "Test failed in main():\n" <<
boost::current_exception_diagnostic_information() << std::endl;
return 1;
}