Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BroadcastTime Utilities #637

Merged
merged 10 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/openlcb/BroadcastTime.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,27 @@ void BroadcastTime::clear_timezone()
#endif
}

extern "C"
{
// normally requires _GNU_SOURCE
char *strptime(const char *, const char *, struct tm *);
}

//
// BroadcastTimeClient::set_data_year_str
//
void BroadcastTime::set_date_year_str(const char *date_year)
{
struct tm tm;
if (strptime(date_year, "%b %e, %Y", &tm) != nullptr)
{
if (tm.tm_year >= (0 - 1900) && tm.tm_year <= (4095 - 1900))
{
// date valid
set_date(tm.tm_mon + 1, tm.tm_mday);
set_year(tm.tm_year + 1900);
}
}
}

} // namespace openlcb
45 changes: 41 additions & 4 deletions src/openlcb/BroadcastTime.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ class BroadcastTime : public SimpleEventHandler
public:
typedef std::vector<std::function<void()>>::size_type UpdateSubscribeHandle;

/// Destructor.
virtual ~BroadcastTime()
{
}

/// Set the time in seconds since the system Epoch. The new time does not
/// become valid until the update callbacks are called.
/// @param hour hour (0 to 23)
Expand All @@ -77,6 +82,10 @@ public:
new SetFlow(this, SetFlow::Command::SET_YEAR, year);
}

/// Set the date and year from a C string.
/// @param data_year date and year format in "Mmm dd, yyyy" format
void set_date_year_str(const char *date_year);

/// Set Rate. The new rate does not become valid until the update callbacks
/// are called.
/// @param rate clock rate ratio as 12 bit sign extended fixed point
Expand Down Expand Up @@ -151,6 +160,22 @@ public:
return ::gmtime_r(&now, result);
}

/// Get the date (month/day).
/// @param month month (1 to 12)
/// @param day day of month (1 to 31)
/// @return 0 upon success, else -1 on failure
int date(int *month, int *day)
{
struct tm tm;
if (gmtime_r(&tm) == nullptr)
{
return -1;
}
*month = tm.tm_mon + 1;
*day = tm.tm_mday;
return 0;
}

/// Get the day of the week.
/// @returns day of the week (0 - 6, Sunday - Saturday) upon success,
/// else -1 on failure
Expand All @@ -176,6 +201,18 @@ public:
return tm.tm_yday;
}

/// Get the year.
/// @returns year (0 - 4095) upon success, else -1 on failure
int year()
{
struct tm tm;
if (gmtime_r(&tm) == nullptr)
{
return -1;
}
return tm.tm_year + 1900;
}

/// Report the clock rate as a 12-bit fixed point number
/// (-512.00 to 511.75).
/// @return clock rate
Expand Down Expand Up @@ -362,6 +399,10 @@ public:
/// @return true if a time server has been detected, else false
virtual bool is_server_detected() = 0;

/// Test if this is a server.
/// @return true if a BroadcastTimeServer, else false
virtual bool is_server_self() = 0;

protected:
class SetFlow : public StateFlowBase
{
Expand Down Expand Up @@ -484,10 +525,6 @@ protected:
tm_.tm_isdst = 0;
}

virtual ~BroadcastTime()
{
}

/// Try the possible set event shortcut. This is typically a bypass of the
/// OpenLCB loopback.
/// @param event event that we would be "setting"
Expand Down
19 changes: 17 additions & 2 deletions src/openlcb/BroadcastTimeAlarm.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -165,31 +165,46 @@ protected:

private:
// Wakeup helper
class Wakeup : public Executable
class Wakeup : public Executable, protected Atomic
{
public:
/// Constructor.
/// @param alarm our parent alarm that we will awaken
Wakeup(BroadcastTimeAlarm *alarm)
: alarm_(alarm)
, armed(false)
{
}

/// Trigger the wakeup to run.
void trigger()
{
alarm_->service()->executor()->add(this);
bool add = false;
{
AtomicHolder h(this);
if (!armed)
{
armed = true;
add = true;
}
}
if (add)
{
alarm_->service()->executor()->add(this);
}
}

private:
/// Entry point. This funciton will be called when *this gets scheduled
/// on the CPU.
void run() override
{
armed = false;
alarm_->wakeup();
}

BroadcastTimeAlarm *alarm_; ///< our parent alarm we will wakeup
bool armed;
};

/// Setup, or wait to setup alarm.
Expand Down
7 changes: 7 additions & 0 deletions src/openlcb/BroadcastTimeClient.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ public:
return serverDetected_;
}

/// Test if this is a server.
/// @return true if a BroadcastTimeServer, else false
bool is_server_self() override
{
return false;
}

/// Handle requested identification message.
/// @param entry registry entry for the event range
/// @param event information about the incoming message
Expand Down
158 changes: 158 additions & 0 deletions src/openlcb/BroadcastTimeDefs.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/** @copyright
* Copyright (c) 2018, Stuart W. Baker
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @file BroadcastTimeDefs.cxx
*
* Static definitions for implementations of the OpenLCB Broadcast Time
* Protocol.
*
* @author Stuart W. Baker
* @date 30 July 2022
*/

#include "BroadcastTimeDefs.hxx"

#include <string>

#include "utils/format_utils.hxx"

namespace openlcb
{

extern "C"
{
// normally requires _GNU_SOURCE
char *strptime(const char *, const char *, struct tm *);
}

//
// BroadcastTimeDefs::time_to_string()
//
std::string BroadcastTimeDefs::time_to_string(int hour, int min)
{
if (hour < 0 || hour > 23)
{
hour = 0;
}
if (min < 0 || min > 59)
{
min = 0;
}

struct tm tm;
tm.tm_hour = hour;
tm.tm_min = min;
char value[6];
if (strftime(value, 6, "%R", &tm) != 0)
{
return value;
}
else
{
return "Error";
}
}

//
// BroadcastTimeDefs::rate_quarters_to_string()
//
std::string BroadcastTimeDefs::rate_quarters_to_string(int16_t rate)
{
std::string result;
if (rate < 0)
{
result.push_back('-');
rate = -rate;
}
uint16_t whole = rate >> 2;
uint16_t frac = rate & 0x3;

result += integer_to_string(whole);

switch (frac)
{
default:
case 0:
result.append(".00");
break;
case 1:
result.append(".25");
break;
case 2:
result.append(".50");
break;
case 3:
result.append(".75");
break;
}
return result;
}

//
// BroadcastTimeDefs::string_to_time()
//
bool BroadcastTimeDefs::string_to_time(
const std::string &stime, int *hour, int *min)
{
struct tm tm;
if (strptime(stime.c_str(), "%R", &tm) == nullptr)
{
return false;
}

if (hour)
{
*hour = tm.tm_hour;
}
if (min)
{
*min = tm.tm_min;
}
return true;
}

//
// BroadcastTimeDefs::string_to_rate_quaraters()
//
int16_t BroadcastTimeDefs::string_to_rate_quarters(const std::string &srate)
{
float rate = std::stof(srate);
if (rate < -512)
{
// set to minimum valid rate
rate = -512;
}
else if (rate > 511.75)
{
// set to maximum valid rate
rate = 511.75;
}
rate *= 4;
rate += rate < 0 ? -0.5 : 0.5;
return rate;
}

} // namespace openlcb
23 changes: 23 additions & 0 deletions src/openlcb/BroadcastTimeDefs.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,29 @@ struct BroadcastTimeDefs
return event_base + RATE_EVENT_BASE_SUFFIX +
((r.rate_ & EVENT_RATE_MASK) << EVENT_RATE_SHIFT);
}

/// Convert time in integer hours/minutes to a string ("hh:mm").
/// @param hour hours in integer form (0 to 23)
/// @param min minutes in integer form (0 to 59)
/// @return time represented in the form of a string ("hh:mm")
static std::string time_to_string(int hour, int min);

/// Convert rate in integer rate quarters to a string (float).
/// @param rate rate in the form of rate quarters
/// @return rate represented in the form of a string (float)
static std::string rate_quarters_to_string(int16_t rate);

/// Convert a string (hh:mm) to hour and minute component integers.
/// @param stime time in the form of a string
/// @param hour resulting hour integer (0 to 23)
/// @param min resulting minute integer (0 to 59)
/// @return true on success, else false on fault
static bool string_to_time(const std::string &stime, int *hour, int *min);

/// Convert a string (float) to rate quarters.
/// @param srate rate in the form of a string float
/// @return rate in the form of an int16_t in rate quarters
static int16_t string_to_rate_quarters(const std::string &srate);
};

} // namespace openlcb
Expand Down
7 changes: 7 additions & 0 deletions src/openlcb/BroadcastTimeServer.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ public:
return true;
}

/// Test if this is a server.
/// @return true if a BroadcastTimeServer, else false
bool is_server_self() override
{
return true;
}

#if defined(GTEST)
void shutdown();

Expand Down
Loading