Add 7-segment display daemon

This daemon takes the path of a 7-segment display device node as its
argument and then writes POST codes which were received over D-Bus to
that device.

Signed-off-by: Benjamin Fair <benjaminfair@google.com>
Change-Id: Ic00548c3ebb5a7aa3c9a927c2de748595eb90e71
diff --git a/7seg.cpp b/7seg.cpp
new file mode 100644
index 0000000..57a18fd
--- /dev/null
+++ b/7seg.cpp
@@ -0,0 +1,73 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+#include <cstdio>
+#include <experimental/filesystem>
+#include <lpcsnoop/snoop_listen.hpp>
+#include <string>
+
+static const char* device_node_path;
+
+namespace fs = std::experimental::filesystem;
+
+static void DisplayDbusValue(uint64_t postcode)
+{
+    // Uses cstdio instead of streams because the device file has
+    // very strict requirements about the data format and streaming
+    // abstractions tend to muck it up.
+    FILE* f = std::fopen(device_node_path, "r+");
+    if (f)
+    {
+        int rc = std::fprintf(f, "%d%02x\n", (postcode > 0xff),
+                              static_cast<uint8_t>(postcode & 0xff));
+        if (rc < 0)
+        {
+            std::fprintf(stderr, "failed to write 7seg value: rc=%d\n", rc);
+        }
+        std::fflush(f);
+        std::fclose(f);
+    }
+}
+
+/*
+ * This is the entry point for the application.
+ *
+ * This application simply creates an object that registers for incoming value
+ * updates for the POST code dbus object.
+ */
+int main(int argc, const char* argv[])
+{
+    if (argc != 2 || !fs::exists(argv[1]))
+    {
+        std::fprintf(stderr, "usage: %s <device_node>\n", argv[0]);
+        return -1;
+    }
+
+    device_node_path = argv[1];
+
+    auto ListenBus = sdbusplus::bus::new_default();
+    std::unique_ptr<lpcsnoop::SnoopListen> snoop =
+        std::make_unique<lpcsnoop::SnoopListen>(ListenBus, DisplayDbusValue);
+
+    while (true)
+    {
+        ListenBus.process_discard();
+        ListenBus.wait();
+    }
+
+    return 0;
+}
diff --git a/80-7seg.rules b/80-7seg.rules
new file mode 100644
index 0000000..e71f5c0
--- /dev/null
+++ b/80-7seg.rules
@@ -0,0 +1 @@
+SUBSYSTEM=="disp_state", ACTION=="add", TAG+="systemd"
diff --git a/Makefile.am b/Makefile.am
index eb59cdf..fd01b4d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,6 +7,21 @@
 
 bin_PROGRAMS = snoopd snooper
 
+if ENABLE_7SEG
+if HAVE_SYSTEMD
+systemdsystemunit_DATA += postcode-7seg.service
+endif # HAVE_SYSTEMD
+
+bin_PROGRAMS += postcode_7seg
+
+postcode_7seg_SOURCES = 7seg.cpp
+postcode_7seg_LDADD = $(SDBUSPLUS_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) -lstdc++fs
+postcode_7seg_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
+
+udevrulesdir = $(UDEVRULESDIR)
+udevrules_DATA = 80-7seg.rules
+endif # ENABLE_7SEG
+
 snoopd_SOURCES = main.cpp
 snoopd_LDADD = $(SDBUSPLUS_LIBS) $(SDEVENTPLUS_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) -lpthread
 snoopd_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(SDEVENTPLUS_CFLAGS) $(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
diff --git a/configure.ac b/configure.ac
index fe7c1c7..9ecf38f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -51,6 +51,19 @@
 PKG_CHECK_MODULES([GMOCK], [gmock], [], [AC_MSG_NOTICE([gmock not found, tests will not build])])
 AX_PTHREAD([GTEST_CFLAGS+=" -DGTEST_HAS_PTHREAD=1 "],[GTEST_CFLAGS+=" -DGTEST_HAS_PTHREAD=0 "])
 
+AC_ARG_ENABLE([7seg],
+    AS_HELP_STRING([--enable-7seg], [Build daemon to send POST codes to a 7-segment display.])
+)
+AM_CONDITIONAL([ENABLE_7SEG], [test "x$enable_7seg" = "xyes"])
+
+AS_IF([test "x$enable_7seg" = "xyes"], [
+    PKG_CHECK_VAR([UDEVRULESDIR], [udev], [udevdir])
+    AC_MSG_CHECKING([udev rules path])
+    AS_IF([test "x$UDEVRULESDIR" = "x"], [
+        AC_MSG_FAILURE([Unable to identify udev rules path.])
+    ])
+])
+
 # Add --enable-oe-sdk flag to configure script
 AC_ARG_ENABLE([oe-sdk],
     AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.])
diff --git a/postcode-7seg@.service b/postcode-7seg@.service
new file mode 100644
index 0000000..a4c5aa0
--- /dev/null
+++ b/postcode-7seg@.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=SevenSeg POSTcode daemon for /dev/%I
+After=dev-%i.device
+After=lpcsnoop.service
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/env postcode_7seg /dev/%I
+
+[Install]
+WantedBy=multi-user.target