Leosac  0.8.0
Open Source Access Control
PFDigitalModule.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014-2016 Leosac
3 
4  This file is part of Leosac.
5 
6  Leosac is free software: you can redistribute it and/or modify
7  it under the terms of the GNU Affero General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Leosac is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU Affero General Public License for more details.
15 
16  You should have received a copy of the GNU Affero General Public License
17  along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "PFDigitalModule.hpp"
21 #include "PFGPIO.hpp"
22 #include "core/CoreUtils.hpp"
27 #include "hardware/GPIO_odb.h"
29 #include "mcp23s17.h"
31 #include "modules/pifacedigital/PFGPIO_odb.h"
34 #include "pifacedigital.h"
35 #include "tools/AssertCast.hpp"
36 #include "tools/db/DBService.hpp"
37 #include "tools/enforce.hpp"
38 #include "tools/log.hpp"
39 #include "tools/timeout.hpp"
40 #include <boost/iterator/transform_iterator.hpp>
41 #include <boost/uuid/uuid_io.hpp>
42 #include <fcntl.h>
44 #include <odb/schema-catalog.hxx>
45 #include <thread>
46 
47 namespace Leosac
48 {
49 namespace Module
50 {
51 namespace Piface
52 {
53 
55  zmqpp::socket *module_manager_pipe,
56  const boost::property_tree::ptree &config,
57  CoreUtilsPtr utils)
58  : BaseModule(ctx, module_manager_pipe, config, utils)
59  , bus_push_(ctx_, zmqpp::socket_type::push)
60  , ws_helper_thread_(utils)
61  , degraded_mode_(false)
62 {
63  if (pifacedigital_open(0) == -1)
64  {
65  // Failed to init piface device. Run module in degraded mode (only WS api)
66  degraded_mode_ = true;
67  ERROR("Cannot open PifaceDigital device. Are you running on device with SPI "
68  "bus and have SPI linux kernel module enabled ?");
70  return;
71  }
72  for (uint8_t hw_addr = 1; hw_addr < 4; ++hw_addr)
73  {
74  if (pifacedigital_open(hw_addr) == -1)
75  {
76  ERROR("Failed to initialize pifacedigital with hardware address"
77  << hw_addr);
78  }
79  }
80 
81  int ret = pifacedigital_enable_interrupts();
82  ASSERT_LOG(ret == 0, "Failed to enable interrupt on piface board");
83 
85  bus_push_.connect("inproc://zmq-bus-pull");
86  for (auto &gpio : gpios_)
87  {
88  reactor_.add(gpio.sock_, std::bind(&PFDigitalPin::handle_message, &gpio));
89  }
90 
91  std::string path_to_gpio =
92  "/sys/class/gpio/gpio" + std::to_string(GPIO_INTERRUPT_PIN) + "/value";
93  interrupt_fd_ = open(path_to_gpio.c_str(), O_RDONLY | O_NONBLOCK);
94  LEOSAC_ENFORCE(interrupt_fd_ > 0, "Failed to open GPIO file");
95  pifacedigital_read_reg(0x11, 0); // flush
96 
97  // Somehow it was required poll with "poll_pri" and "poll_error". It used to
98  // work with poll_pri alone before. Need to investigate more. todo !
100  zmqpp::poller::poll_pri | zmqpp::poller::poll_error);
101 }
102 
104 {
105  auto hwd_service =
107  if (hwd_service)
108  hwd_service->unregister_serializer<PFGPIO>();
109 }
110 
112 {
113  while (is_running_)
114  {
115  auto itr_transform =
116  [](const PFDigitalPin &p) -> std::chrono::system_clock::time_point {
117  return p.next_update();
118  };
119 
120  auto timeout = Tools::compute_timeout(
121  boost::make_transform_iterator(gpios_.begin(), itr_transform),
122  boost::make_transform_iterator(gpios_.end(), itr_transform));
123  reactor_.poll(timeout);
124  for (auto &gpio_pin : gpios_)
125  {
126  if (gpio_pin.next_update() < std::chrono::system_clock::now())
127  gpio_pin.update();
128  }
129  }
130 
132  if (ws_service)
134 }
135 
137 {
138  // get interrupt state.
139  std::array<char, 64> buffer{};
140  ssize_t ret;
141 
142  ret = ::read(interrupt_fd_, &buffer[0], buffer.size());
143  ASSERT_LOG(ret >= 0,
144  "Reading on interrupt_fd gave unexpected return value: " << ret);
145  ret = ::lseek(interrupt_fd_, 0, SEEK_SET);
146  ASSERT_LOG(ret >= 0,
147  "Lseeking on interrupt_fd gave unexpected return value: " << ret);
148 
149  for (uint8_t hwaddr = 0; hwaddr < 4; ++hwaddr)
150  {
151  uint8_t states = pifacedigital_read_reg(0x11, hwaddr);
152  for (int i = 0; i < 8; ++i)
153  {
154  if (((states >> i) & 0x01) == 0)
155  {
156  // signal interrupt if needed (ie the pin is registered in config)
157  std::string gpio_name;
158  if (get_input_pin_name(gpio_name, i, hwaddr))
159  {
160  bus_push_.send(zmqpp::message()
161  << std::string("S_INT:" + gpio_name));
162  }
163  }
164  }
165  }
166 }
167 
168 bool PFDigitalModule::get_input_pin_name(std::string &dest, int idx, uint8_t hw_addr)
169 {
170  for (const auto &gpio : gpios_)
171  {
172  if (gpio.gpio_no_ == idx && gpio.direction_ == PFDigitalPin::Direction::In &&
173  gpio.hardware_address_ == hw_addr)
174  {
175  dest = gpio.name_;
176  return true;
177  }
178  }
179  return false;
180 }
181 
182 void PFDigitalModule::process_xml_config(const boost::property_tree::ptree &cfg)
183 {
184  boost::property_tree::ptree module_config = cfg.get_child("module_config");
185 
186  for (auto &node : module_config.get_child("gpios"))
187  {
188  boost::property_tree::ptree gpio_cfg = node.second;
189 
190  std::string gpio_name = gpio_cfg.get<std::string>("name");
191  int gpio_no = gpio_cfg.get<uint8_t>("no");
192  std::string gpio_direction = gpio_cfg.get<std::string>("direction");
193  bool gpio_value = gpio_cfg.get<bool>("value", false);
194  uint8_t hw_addr = gpio_cfg.get<uint8_t>("hardware_address", 0);
195 
196  INFO("Creating GPIO " << gpio_name << ", with no " << gpio_no
197  << ". direction = " << gpio_direction
198  << "Hardware address: " << (int)hw_addr);
199 
200  PFDigitalPin pin(ctx_, gpio_name, gpio_no,
201  gpio_direction == "in" ? PFDigitalPin::Direction::In
203  gpio_value, hw_addr);
204 
205  if (gpio_direction != "in" && gpio_direction != "out")
206  throw GpioException("Direction (" + gpio_direction + ") is invalid");
207  gpios_.push_back(std::move(pin));
208  utils_->config_checker().register_object(gpio_name,
210  }
211 }
212 
214 {
215  bool use_db = config_.get<bool>("module_config.use_database", false);
216  if (!use_db && degraded_mode_)
217  {
218  throw LEOSACException("We failed to open piface digital device and wont "
219  "enable database support. There is nothing to do but "
220  "exit.");
221  }
222  else if (!use_db)
223  {
224  // Process XML based config.
226  }
227  else
228  {
229  // Database enabled configuration.
230  setup_database();
231 
232  // Register serializer for our GPIO object.
233  auto hwd_service =
235  ASSERT_LOG(hwd_service, "No hardware service but we have database.");
236  hwd_service->register_serializer<PFGPIO>(&PFGPIOSerializer::serialize);
237 
238  ModuleParameters parameters{};
239  parameters.degraded_mode = degraded_mode_;
240  ws_helper_thread_.set_parameter(parameters);
243  }
244 }
245 
247 {
248  using namespace odb;
249  using namespace odb::core;
250  auto db = utils_->database();
251  schema_version v = db->schema_version("module_pifacedigital");
252  schema_version cv(schema_catalog::current_version(*db, "module_pifacedigital"));
253  if (v == 0)
254  {
255  transaction t(db->begin());
256  schema_catalog::create_schema(*db, "module_pifacedigital");
257  t.commit();
258  }
259  else if (v < cv)
260  {
261  INFO("PIFACEDIGITAL_GPIO Module performing database migration. Going from "
262  "version "
263  << v << " to version " << cv);
264  transaction t(db->begin());
265  schema_catalog::migrate(*db, cv, "module_pifacedigital");
266  t.commit();
267  }
268 }
269 
271 {
272  using Result = odb::result<PFGPIO>;
273  DBPtr db = utils_->database();
274  odb::transaction t(db->begin());
275  Result result = db->query<PFGPIO>();
276 
277  // Don't attempt to configure GPIO when running in degraded mode.
278  if (degraded_mode_)
279  {
280  INFO("Not creating GPIO object because the module is running in degraded "
281  "mode.");
282  return;
283  }
284  for (const auto &gpio : result)
285  {
286  // For each PFGPIO object in the database, create a PFDigitalPin
287 
288  if (gpio.number() > std::numeric_limits<uint8_t>::max())
289  {
290  WARN("Cannot create GPIO "
291  << gpio.name()
292  << " because its number is too big: " << gpio.number());
293  continue;
294  }
295 
296  INFO("Creating GPIO "
297  << gpio.name() << ", with no " << gpio.number() << ". direction = "
298  << (gpio.direction() == PFDigitalPin::Direction::In ? "in" : "out"));
299  PFDigitalPin pin(ctx_, gpio.name(), gpio.number(), gpio.direction(),
300  gpio.default_value(), gpio.hardware_address());
301  gpios_.push_back(std::move(pin));
302  utils_->config_checker().register_object(gpio.name(),
304  }
305 
306  t.commit();
307 }
308 
310 {
311  // We want to make the target output pin blink.
312  // We must first make sure a few assumptions hold:
313  // + The GPIO exists in our database (so we can retrieve its name)
314  // + The GPIO is an output GPIO.
315  // + The GPIO exists in the ConfigChecker (ie, the server
316  // up-to-date with regards to what the database stores).
317 
318  if (parameters_.degraded_mode)
319  throw LEOSACException(
320  "Cannot test Piface Digital GPIO when running in degraded mode.");
321 
323  odb::transaction t(db->begin());
324  auto gpio = db->find<PFGPIO>(gpio_id);
325  t.commit();
326 
327  if (!gpio)
328  throw EntityNotFound(gpio_id, "pfdigital.gpio");
329 
330  if (gpio->direction() != Hardware::GPIO::Direction::Out)
331  throw ModelException("", "GPIO is not an output GPIO.");
332 
333  if (!core_utils_->config_checker().has_object(gpio->name(),
335  {
336  throw ModelException(
337  "", "GPIO doesn't exist in the runtime configuration checker. "
338  "This probably means that you need to restart Leosac to load the "
339  "new configuration");
340  }
341 
342  // We have to use the ZMQ based messaging infrastructure.
343  Hardware::FGPIO gpio_facade(core_utils_->zmqpp_context(), gpio->name());
344  // This will block the Piface GPIO module from responding
345  // to WS message. This is fine for now...
346  for (int i = 0; i < 8; ++i)
347  {
348  gpio_facade.toggle();
349  std::this_thread::sleep_for(std::chrono::milliseconds(750));
350  }
351 }
352 
354 {
355  ws_service.register_handler(
356  [mode = parameters_.degraded_mode](const WebSockAPI::RequestContext) {
357  json j{{"mode", mode}};
358  return j;
359  },
360  "pfdigital.is_degraded_mode");
361 
362  ws_service.register_handler(
363  [this](const WebSockAPI::RequestContext rc) {
365  {});
366 
367  UUID gpio_id = rc.original_msg.content.at("gpio_id").get<UUID>();
368  this->test_output_pin(gpio_id);
369  return json{};
370  },
371  "pfdigital.test_output_pin");
372 
373  ws_service.register_crud_handler("pfdigital.gpio", &CRUDHandler::instanciate);
374 }
375 
377 {
378  ws_service.unregister_handler("pfdigital.is_degraded_mode");
379  ws_service.unregister_handler("pfdigital.test_output_pin");
380 
381  // Remove 4 handlers, one for each CRUD operation
382  ws_service.unregister_handler("pfdigital.gpio.create");
383  ws_service.unregister_handler("pfdigital.gpio.read");
384  ws_service.unregister_handler("pfdigital.gpio.update");
385  ws_service.unregister_handler("pfdigital.gpio.delete");
386 }
387 }
388 }
389 }
FGPIO.hpp
Leosac::Module::WebSockAPI::BaseModuleSupportThread::set_parameter
void set_parameter(const ParameterT &p)
Definition: WSHelperThread.hpp:97
Leosac::Module::Piface::CRUDHandler::instanciate
static WebSockAPI::CRUDResourceHandlerUPtr instanciate(WebSockAPI::RequestContext)
Definition: CRUDHandler.cpp:170
Leosac::json
nlohmann::json json
Definition: AuditSerializer.hpp:29
PFDigitalPin
This is a implementation class.
Definition: PFDigitalPin.hpp:33
Leosac::Module::BaseModule
Base class for module implementation.
Definition: BaseModule.hpp:110
Leosac::Tools::compute_timeout
int compute_timeout(InputIterator begin, InputIterator end)
Compute the time until the next timeout from a collection of time point.
Definition: timeout.hpp:39
WARN
@ WARN
Definition: log.hpp:33
Leosac::Hardware::HardwareService
Database aware Hardware Service.
Definition: HardwareService.hpp:39
zmqpp
Definition: CoreUtils.hpp:27
Leosac::Module::Piface::PFDigitalModule::get_input_pin_name
bool get_input_pin_name(std::string &dest, int idx, uint8_t hw_addr)
Retrieve the (user-given) name of the pin and store it in dest.
Definition: PFDigitalModule.cpp:168
Leosac::Module::Piface::PFDigitalModule::bus_push_
zmqpp::socket bus_push_
Socket to push event to the bus.
Definition: PFDigitalModule.hpp:122
Leosac::Module::Piface::PFDigitalModule::PFDigitalModule
PFDigitalModule(zmqpp::context &ctx, zmqpp::socket *module_manager_pipe, const boost::property_tree::ptree &config, CoreUtilsPtr utils)
Definition: PFDigitalModule.cpp:54
ERROR
@ ERROR
Definition: log.hpp:32
gpioexception.hpp
Exception class for Gpio related errors.
Leosac::Hardware::GPIO::Direction::Out
@ Out
Leosac::get_service_registry
ServiceRegistry & get_service_registry()
A function to retrieve the ServiceRegistry from pretty much anywhere.
Definition: GetServiceRegistry.cpp:25
Leosac::Module::WebSockAPI::BaseModuleSupportThread::start_running
void start_running()
Effectively starts an helper thread and run its io_service.
Definition: WSHelperThread.hpp:92
ASSERT_LOG
#define ASSERT_LOG(cond, msg)
Definition: log.hpp:190
Leosac::Hardware::DeviceClass::GPIO
@ GPIO
timeout.hpp
INFO
@ INFO
Definition: log.hpp:34
Leosac::DBPtr
std::shared_ptr< odb::database > DBPtr
Definition: db_fwd.hpp:31
Leosac::Module::Piface::WSHelperThread::unregister_ws_handlers
void unregister_ws_handlers(WebSockAPI::Service &ws_service) override
Definition: PFDigitalModule.cpp:376
Leosac::Hardware::FGPIO::toggle
bool toggle()
Toggle the GPIO value by sending a message to the backend GPIO impl.
Definition: FGPIO.cpp:79
odb
Provide ODB magic to be able to store an Leosac::Audit::EventType (FlagSet) object.
Definition: AuditEventMaskODB.hpp:31
Leosac::EntityNotFound
Definition: EntityNotFound.hpp:27
Leosac::Module::Piface::PFDigitalModule::load_config_from_database
void load_config_from_database()
Definition: PFDigitalModule.cpp:270
Leosac::Module::Piface::PFDigitalModule::process_xml_config
void process_xml_config(const boost::property_tree::ptree &cfg)
Process the XML configuration, preparing configured GPIO pin.
Definition: PFDigitalModule.cpp:182
Leosac::DBService
Provides various database-related services to consumer.
Definition: DBService.hpp:34
Leosac::Module::Piface::PFGPIO
Piface Module GPIO descriptor.
Definition: PFGPIO.hpp:44
Leosac::Module::Piface::WSHelperThread::test_output_pin
void test_output_pin(const UUID &gpio_id)
Implements the "pfdigital.test_output_pin" API call.
Definition: PFDigitalModule.cpp:309
Leosac::SecurityContext::Action::IS_ADMIN
@ IS_ADMIN
A workaround permission that requires the user to be administrator.
Leosac::Module::Piface::PFDigitalModule::setup_database
void setup_database()
Create / update database schema for the module.
Definition: PFDigitalModule.cpp:246
Leosac::Module::Piface::WSHelperThread::register_ws_handlers
void register_ws_handlers(WebSockAPI::Service &ws_service) override
Called when websocket handler registration is possible.
Definition: PFDigitalModule.cpp:353
Leosac::Hardware::FGPIO
A Facade to a GPIO object.
Definition: FGPIO.hpp:45
enforce.hpp
PFDigitalPin::handle_message
void handle_message()
The PFGpioModule will register this method so its called when a message is ready on the pin socket.
Definition: PFDigitalPin.cpp:64
Leosac::Module::WebSockAPI::ClientMessage::content
json content
Definition: Messages.hpp:58
Leosac
This is the header file for a generated source file, GitSHA1.cpp.
Definition: APIStatusCode.hpp:22
Leosac::Module::WebSockAPI::RequestContext::original_msg
const ClientMessage & original_msg
The original, complete, client message object.
Definition: RequestContext.hpp:45
Leosac::Module::Piface::PFGPIOSerializer::serialize
static json serialize(const PFGPIO &in, const SecurityContext &sc)
Definition: PFGPIO.cpp:37
GetServiceRegistry.hpp
Leosac::Module::Piface::PFDigitalModule::run
virtual void run() override
Module's main loop.
Definition: PFDigitalModule.cpp:111
Leosac::Module::WebSockAPI::Service
A service object provided by the Websocket module.
Definition: Service.hpp:45
Messages.hpp
Leosac::Module::BaseModule::config_
boost::property_tree::ptree config_
The configuration tree passed to the start_module function.
Definition: BaseModule.hpp:193
Leosac::Module::Piface::PFDigitalModule::gpios_
std::vector< PFDigitalPin > gpios_
GPIO vector.
Definition: PFDigitalModule.hpp:127
Leosac::Module::Piface::PFDigitalModule::~PFDigitalModule
~PFDigitalModule()
Definition: PFDigitalModule.cpp:103
Leosac::Module::BaseModule::reactor_
zmqpp::reactor reactor_
The reactor object we poll() on in the main loop.
Definition: BaseModule.hpp:214
LEOSACException
A base class for Leosac specific exception.
Definition: leosacexception.hpp:40
Leosac::Module::Piface::PFDigitalModule::degraded_mode_
bool degraded_mode_
True if we are running in "degraded" mode (ie, not on a device that support the PifaceDigital).
Definition: PFDigitalModule.hpp:153
CRUDHandler.hpp
Leosac::Module::Piface::PFDigitalModule::handle_interrupt
void handle_interrupt()
An interrupt was triggered.
Definition: PFDigitalModule.cpp:136
Leosac::Module::BaseModule::utils_
CoreUtilsPtr utils_
Pointer to the core utils, which gives access to scheduler and others.
Definition: BaseModule.hpp:198
ModelException.hpp
PFDigitalModule.hpp
Leosac::Module::Piface::PFDigitalModule::process_config
void process_config()
Process configuration.
Definition: PFDigitalModule.cpp:213
Leosac::Module::WebSockAPI::RequestContext::security_ctx
SecurityContext & security_ctx
Definition: RequestContext.hpp:47
Service.hpp
Leosac::Module::BaseModule::is_running_
bool is_running_
Boolean indicating whether the main loop should run or not.
Definition: BaseModule.hpp:203
Leosac::Module::WebSockAPI::Service::register_handler
bool register_handler(HandlerT &&handler, const std::string &type)
Register a handler for a websocket message.
Definition: Service.hpp:88
ModelException
An exception class for general API error.
Definition: ModelException.hpp:33
HardwareService.hpp
DBService.hpp
GpioException
Definition: gpioexception.hpp:32
Leosac::SecurityContext::enforce_permission
void enforce_permission(Action a, const ActionParam &ap) const
Similar to check_permission(), but throws is the permission is denied.
Definition: SecurityContext.cpp:36
Leosac::ExtensibleSerializer::unregister_serializer
void unregister_serializer()
Definition: ExtensibleSerializer.hpp:112
Leosac::Module::WebSockAPI::Service::unregister_handler
void unregister_handler(const std::string &name)
Remove an handler by name.
Definition: Service.cpp:36
Leosac::Module::Piface::PFDigitalModule::interrupt_fd_
int interrupt_fd_
File descriptor of the PIN that triggers interrupts.
Definition: PFDigitalModule.hpp:142
Leosac::ServiceRegistry::get_service
std::shared_ptr< ServiceInterface > get_service() const
Retrieve the service instance implementing the ServiceInterface, or nullptr if no such service was re...
Definition: ServiceRegistry.hpp:290
Leosac::Hardware::GPIO::Direction::In
@ In
log.hpp
CoreUtils.hpp
Leosac::Module::Piface::PFDigitalModule::ws_helper_thread_
WSHelperThread ws_helper_thread_
Support thread for processing websocket requests.
Definition: PFDigitalModule.hpp:147
Leosac::Module::WebSockAPI::RequestContext
Holds valuable pointer to provide context to a request.
Definition: RequestContext.hpp:36
PFGPIO.hpp
LEOSAC_ENFORCE
#define LEOSAC_ENFORCE(cond,...)
Similar to enforce, except that it will throw a LEOSACException.
Definition: enforce.hpp:47
Leosac::Module::BaseModule::ctx_
zmqpp::context & ctx_
A reference to the ZeroMQ context in case you need it to create additional socket.
Definition: BaseModule.hpp:183
Leosac::CoreUtilsPtr
std::shared_ptr< CoreUtils > CoreUtilsPtr
Definition: LeosacFwd.hpp:35
Leosac::Module::Piface::ModuleParameters::degraded_mode
bool degraded_mode
Definition: PFDigitalModule.hpp:57
EntityNotFound.hpp
Result
odb::result< Tools::LogEntry > Result
Definition: LogEntry.cpp:37
Leosac::UUID
Thin wrapper around boost::uuids::uuid.
Definition: Uuid.hpp:35
AssertCast.hpp
Leosac::Module::Piface::ModuleParameters
Some ~const parameter that are required to process websocket requests.
Definition: PFDigitalModule.hpp:55