text:
Parallel LDPC decoding using CUDA and OpenMP
Joo-Yul Park1 &
Ki-Seok Chung1
Digital mobile communication technologies, such as next generation mobile communication and mobile TV, are rapidly advancing. Hardware designs to provide baseband processing of new protocol standards are being actively attempted, because of concurrently emerging multiple standards and diverse needs on device functions, hardware-only implementation may have reached a limit. To overcome this challenge, digital communication system designs are adopting software solutions that use central processing units or graphics processing units (GPUs) to implement communication protocols. In this article we propose a parallel software implementation of low density parity check decoding algorithms, and we use a multi-core processor and a GPU to achieve both flexibility and high performance. Specifically, we use OpenMP for parallelizing software on a multi-core processor and Compute Unified Device Architecture (CUDA) for parallel software running on a GPU. We process information on H-matrices using OpenMP pragmas on a multi-core processor and execute decoding algorithms in parallel using CUDA on a GPU. We evaluated the performance of the proposed implementation with respect to two different code rates for the China Multimedia Mobile Broadcasting (CMMB) standard, and we verified that the proposed implementation satisfies the CMMB bandwidth requirement.
Today, wireless devices transmit and receive high rate data in real-time. The need to provide high transmission rates with reliability is increasing, in order to offer various multimedia services with 4G mobile communication systems. Typical data transmission requirements of 4G mobile communication systems are for 100 Mbps in mobile circumstances and for 1 Gbps in a stationary state [1]. Therefore, powerful correcting codes are becoming indispensable.
The low density parity check (LDPC) code is one of the strongest error correcting codes; it is a linear block code originally devised by Gallager in 1960s [2]. However, it was impossible to implement the code in hardware that was available at that time. About 30 years later, the LDPC code was reviewed by Mackay and Neal [3, 4]. They rediscovered the excellent properties of the code and suggested its current feasibility, thanks to the development of communication and integrated circuit technologies. Recently Chung and Richardson [5] showed that the LDPC code can approach the Shannon limit to within 0.0045 dB. The LDPC code has a smaller minimum distance than the Turbo code, which was regarded as the best channel coding technique before the LDPC started to draw attention. Hence, with almost no error floor issues, it shows very good bit error rate curves. Furthermore, iterative LDPC decoding schemes based on the sum-product algorithm (SPA) can fully be parallelized, leading to high-speed decoding [5]. For these reasons, LDPC coding is widely regarded as a very attractive coding technique for high-speed 4G wireless communications.
LDPC codes are used in many standards, and they support multiple data rates for each standard. However, it is very challenging to design decoder hardware that supports various standards and multiple data rates. Recently, software defined radio (SDR) [6] baseband processing has emerged as a promising technology that can provide a cost-effective, flexible alternative by implementing a wide variety of wireless protocols in software. The physical layer baseband processing generally requires very high bandwidth and thus high processing power. Thus, multi-core processors are often employed in modern, embedded communication devices [7, 8]. Also, GPU is often adopted to achieve high computational power [9]. Wide deployment of multi-core processors and rapid advances in GPU performance has led to active studies in designing LDPC decoders using GPUs [10–14]. For example, a recent study proposed a technique to utilize GPU to run SPA and described a way to access LLR data [11]. A related study proposed a method for LDPC decoding using Compute Unified Device Architecture (CUDA) [13]. They showed that a GPU could reduce decoding time dramatically.
In this article, we extend this parallelization further in such a way that various standards and code rates can be supported seamlessly. We propose a design which employs both a central processing unit (CPU) and a graphics processing unit (GPU). To support various code rates, the host multi-core CPU reads the H-matrix, and, using OpenMP, it generates address patterns which help the GPU to effectively execute the LDPC decoding in parallel. The LDPC decoding algorithm is written in CUDA [15], which is a parallel computing language developed by NVIDIA, and the CUDA program is executed by the GPU.
The rest of this article is organized as follows. In Section 2, we review LDPC decoding algorithms and parallelization techniques using CUDA. In Section 3, we present the memory structure for CUDA, an address generation method for LDPC decoding, and existing parallelization techniques. In Section 4, the proposed software implementation and performance evaluation results are presented. Section 5 concludes this article.
2.1 Review of LDPC decoding algorithms
The LDPC code is a linear block code with a very sparse parity check matrix called H-matrix. The rows and columns of an H-matrix denote parity check codes and symbols, respectively. LDPC codes can be represented by a Tanner graph which is a bipartite graph in which the sides represent check nodes and bit nodes, respectively. Thus, check nodes correspond to the rows of the H-matrix, and bit nodes correspond to the columns of the H-matrix. For example, when the (i, j) element of an H-matrix is '1', the i th check node is connected to the j th bit node of the equivalent Tanner graph. Figures 1 and 2 illustrate an H-matrix and the equivalent Tanner graph for (8, 4) LDPC codes.
An example of LDPC H matrix.
An example of Tanner graph.
Most practical LDPC decoders use soft-decisions, because soft-decision decoders typically outperform hard-decision ones. A soft-decision decoding scheme is carried out, based on the concept of belief propagation, by passing messages, which contain the amount of belief for a value being between 0 and 1, between adjacent check nodes and bit nodes. Based on the delivered messages, each node attempts to decode its own value. If the decoded value turns out to contain error, the decoding process is repeated for a predefined number of times. Typically, there are two ways to deliver messages in LDPC decoding. One is to use probabilities, and the other is to use log-likelihood ratios (LLRs). In general, using LLRs is favored since that allows us to replace expensive multiplication operations with inexpensive addition operations.
2.2 Parallelization of LDPC decoding
As explained in Section 2.1, an LDPC decoding algorithm is capable of correcting errors by repeatedly computing and exchanging messages. The amount of computation depends on the size of the H-matrix. However, recently published standards reveal a growing trend that the length of codewords is getting longer as the amount of data transfer is increasing [1]. By the same token, the size of the H-matrix is increasing. For a recent standard, DVB-T2 [16], the length of the codeword is 64,800 bits or 16,200 bits. For China Multimedia Mobile Broadcasting (CMMB) [17], the length is 9,126 bits. The huge size causes both decoding complexity and decoding time to increase. Therefore, it is crucial to distribute the computation load evenly to multiple cores and to parallelize the computation efficiently.
LDPC decoding consists of four general operations: initialization, check node update, bit node update, and parity check. Examining the algorithm reveals that the check node update can be done in parallel, since the rows are uncorrelated with each other. Also, the bit node update on each column can be processed in parallel, because an LDPC decoding algorithm has independent memory accesses among the four types of operations. For example, in the H-matrix in Figure 3, check node operations can process four rows in parallel, and bit node operations can process eight columns concurrently.
Parallelization of LDPC decoding.
This article has three main technical contributions. First, we propose an efficient and flexible technique which can be applied to various protocols and target multi-core platforms, since we propose a solution which employs both CPUs and GPUs. We also introduce an efficient technique to reduce the memory requirement significantly. Next, we propose parallelization techniques for not only check and bit node update operations, but also parity check operations, which will be described in detail in Section 3.
2.3 CUDA programming
CUDA is a GPU software development kit proposed by David Kirk and Mark Harris. One major advantage of CUDA is that it is an extension of the standard C programming language. Hence, those who are familiar with the C/C++ programming language can learn how to program in CUDA relatively easily. Also, CUDA is capable of fully utilizing the fast-improving GPU processing power. Further, NVIDIA hardware engineers actively reflect scientists' opinions as they develop the next generation of CUDA and GPU. For instance, support for double precision computation, error correction capability, and increased shared memory may not be crucial for graphics processing in game applications, but they are important for many scientific and engineering applications. These features have been added in recent versions of CUDA and GPU.
Figure 4 shows the architecture of NVIDIA's 8800GTX. There are 16 multiprocessors, and each multiprocessor has 8 single precision thread processors (SPs). Therefore, the total number of SPs is 128. Each SP can process a block of data with a thread allocation in parallel. However, it is not possible for the CPU and the GPU to share memory space. Thus, the GPU must make a copy of the shared data to its own memory space in advance. If the CPU wants data stored in the memory of the GPU, a similar copy operation must take place. These copy operations incur significant overhead.
GPU architecture.
Figure 5 shows the relation between a block and a thread in the GPU. A kernel function is executed for one thread at a time. For example, if there were 12 threads in Block (1,1), and there were 6 blocks in a grid, then the kernel function would be executed 72 times. When a function is invoked, the thread and the block index are identified by the thread_idx and block_idx variables, respectively [18, 19].
Grid of thread blocks.
2.4 OpenMP
OpenMP is a set of application program interfaces (APIs) for parallelization of C/C++ or Fortran programs in a shared memory multiprocessing environment. OpenMP has gained lots of attention lately as multi-core systems are being widely deployed in many embedded platforms. Recently, version 2.5 was released. OpenMP is a parallelization method based on compiler directives, in which a directive will tell the compiler which part of the program should be parallelized, by generating multiple threads. Many commercial and noncommercial compilers support OpenMP directives, and thus, we can utilize OpenMP in many existing platforms [20].
OpenMP's parallelization model is based on a fork-join model. Starting with only the master thread, additional slave threads are forked on demand. All threads except for the master one are terminated when execution for a parallel region ends. In this article, we use OpenMP pragmas to parallelize address generation computations. Since only the new address is transferred to the CUDA memory, the memory copy overhead is minimal.(Figure 6)
Parallel and serial regions.
3. Proposed LDPC decoder
As described above, when the size of H-matrices increases, the amount of computation grows rapidly. This makes it difficult to achieve satisfactory performance in either software- or hardware-only implementations that attempt to support multiple standards and data rates. Therefore, we propose a novel parallel software implementation of LDPC decoding algorithms that are based on OpenMP and CUDA programming. We will show that the proposed design is a cost-effective and flexible LDPC decoder which satisfies the throughput requirement for various H-matrices and multiple code rates. First, we will show the overall software structure, and next we will explain the parallelization techniques that we propose. (Figure 7)
Proposed LDPC decoding flow.
3.1 Architecture of the proposed LDPC decoder
The overall structure of the proposed LDPC decoder is as follows. We assume that the target platform consists of a single host multi-core processor which can run C codes with OpenMP pragmas and a GPU which can run CUDA codes. To support multiple standards and data rates, multiple H-matrices are stored as files. The host CPU reads the H-matrix for a given standard and signal-to-noise ratio (SNR) constraint. The host CPU then generates an address table of data processed in parallel by the GPU. Generation of the address table is parallelized by OpenMP pragmas. Next, generated address information is transferred to the memory in the GPU. This copy operation takes place only if there is a change in standard, SNR constraint, or code rate.
When signals are received, the host CPU delivers them to the GPU. The GPU executes the proposed LDPC decoding software in parallel. Upon completion of the decoding, decoded bits are transferred to the host CPU. A CUDA API called "CUDA Copy" is used to exchange data between the host and the GPU. The copy overhead may be significant, so it is crucial to minimize it. It should be noted that in our implementation this copy operation takes place only for generated address transfers, received signal transfers, decoded bit transfers, and configuration (standard, code rates, etc.) changes. Therefore, the copy overhead is not large in our implementation.
3.2 One-dimensional address generation for parallelization
Function 1. New address generator
1: {New address :}
#pragma omp parallel for private(i, j)
shared(v_nodes, c_nodes) schedule(static)
2: for i Check Node Num
3: for j weight of check Node
4: for k weight of bit Node
if(v_nodes[c_nodes[i].index[j]].index[k] = = i
c_nodes[i].order[j] = k;
5: end for
We will explain how we generate addresses for CUDA parallelization, using the H-matrix in Figure 1. The H-matrix is stored in a file as a two-dimensional array which contains bit node positions that are necessary for the check node update operation. The first table in Figure 8 shows an example. Since the positions of the LLR values of Bit Node 1 for Check Node 0 and Check Node 1 are different, the bit node order is determined by reading the H-matrix information. We minimize the execution time for this by parallelizing the operation using OpenMP. We use an Intel Quad-Core processor as the host CPU, and the following algorithm with four threads may be used.
One-dimensional address generation for parallelization (check node).
The position of an LLR value is stored in the form of (x, y) where x is the position of a bit node and y indicates that it is the (y + 1)th 1 (0 ≤ y ≤ (degree - 1)) of the same bit node. To make it more convenient to parallelize the execution and reduce the memory access time, this (x, y) information is rearranged as a one-dimensional array, as shown at the bottom of Figure 8. The position of the LLR value for (x, y) in the one-dimensional array which is the address for check node computation is easily computed as follows:
L a d d r = x × W b + y
where Wb is the degree of bit nodes.
By using this method, when CUDA parallelizes the decoding process, the position of LLR values is obtained by reading memory instead of computing a new address. This improves execution time.
Figure 9 shows the positions of check nodes which are necessary for bit node update operations. Using a similar method to compute Laddr, Zaddr, which is the position of bit node (x, y) in the one-dimensional array, is computed as follows:
One-dimensional address generation for parallelization (bit node).
Z a d d r = x × W c + y
where x is the position of a check node, and y indicates that it is the (y + 1)th 1 (0 ≤ y ≤ (degree - 1)) of the same check node and Wc is the degree of check nodes. Using this one-dimensional address arrangement, the number of memory accesses is minimized in all of the operations of check node updates, bit node updates, initialization, and parity checks.
3.3 Parallel LDPC decoding by GPU
LDPC decoding consists of four parts as shown in Figure 10. The first part is that received LLR values are copied into the location of 1's in the H-matrix. Then, check node update operations and bit node update operations are carried out. Lastly, parity check operations are conducted.
Kernel 1. Initialization kernel
1: {Initilization :}
x I n d e x = b l o c k I d x . x × b l o c k s i z e × W b + t h r e a d I d x . x × W b I n d e x = b l o c k I d x . x × b l o c k s i z e + t h r e a d I d x . x
2: for i weight of bit Node
M e m o r y [ Z a d d r [ x I n d e x + i ] ] = I n i t [ I n d e x ]
Kernel 2. Check node update kernel
1: {Check Node Update :}
x I n d e x = b l o c k I d x . x × b l o c k s i z e × W c + t h r e a d I d x . x × W c
2: for i weight of Check Node
M e m o r y [ x I n d e x + i ] = C h e c k N o d e _ C o m p [ x I n d e x + i ]
Kernel 3. Bit node update kernel
1: {Bit Node Update :}
M e m o r y [ Z a d d r [ x I n d e x + i ] ] = B i t N o d e _ C o m p [ Z a d d r [ x I n d e x + i ] ]
4: Decode [ I n d e x ] =BitNode _ Comp
Kernel 4. Parity check kernel
1: {Parity Check :}
x I n d e x = b l o c k I d x . x × b l o c k s i z e × W c + t h r e a d I d x . x × W c I n d e x = b l o c k I d x . x × b l o c k s i z e + t h r e a d I d x . x
c h e c k = D e c o d e [ int ( L a d d r [ x I n d e x + i ] ∕ W b ) ] + c h e c k
c h e c k = int ( c h e c k % 2 )
The first initialization step is carried out with a pre-generated Zaddr. When the number of signals received equals the number of bit nodes, each received value is copied into the position indicated by Zaddr. This task for the H-matrix in Figure 1 can be processed in parallel using eight threads, if we use the GPU in Figure 4.
Second, a check node update operation is conducted after generating as many threads as the number of check nodes. Each thread sequentially reads values from the memory as many times as the degree of check nodes from the memory, and it updates the values and stores them back to the same locations from which they were read.
Third, the bit node update is conducted after generating as many threads as the number of bit nodes. The stored data in memory are arranged in such a way that a check node update operation can effectively be carried out. Therefore, for bit node updates, each thread reads as many values as the degree of bit nodes, using Zaddr. Using the input values, bit node updates and determination of a decode bit are conducted. Updated values are stored back to the same locations from which they were read.
Last, parity check operations are parallelized for each check node. A parity check operation is intended to check that all the checking results are 0; this is done using the addresses in Laddr. When an address from Laddr is divided by the degree of the bit node, we obtain the position of the decode bit for parity check operations.
4. Performance results
Performance evaluation results of the proposed LDPC decoding implementation are presented in this section. H-matrices for the CMMB standard, which is currently used in Digital Multimedia Broadcasting in China, are used for performance evaluation. The length of the codeword in the CMMB standard is 9,216 bits, and two code rates, 1/2 and 3/4, are supported [17].
The execution platform was composed of Intel i5 750 (a Quad-Core CPU with 2.6 GHz) as the host CPU, with 4 GB of DDR3 RAM, Windows XP ServicePack 3 (32 bit). MS Visual Studio 2005 was used as the C compiler. The GPU consisted of NVIDIA GT 8800, 512 MB of memory, and CUDA v2.3.
To optimize the GPU performance, the best block size must be determined. Table 1 summarizes the performance evaluation results for various block sizes. From this evaluation, we determined that the optimal block size was 64 (threads per block).
Table 1 LDPC decoding time (second) versus block size (CMMB 1/2, frame 10,000)
To evaluate the performance of the proposed design, we compared the performance of three cases: (1) where no parallelization technique was applied, (2) where only parallelization utilizing OpenMP was applied, and (3) where both OpenMP and CUDA were utilized. Report times are the latencies to take decoding 10,000 frames for each SNR value.
Table 2 shows that when both OpenMP and CUDA were utilized, we achieved a speedup of 22 over the case in which no parallelization was applied. When the iteration count increased with low SNR values, the speedup became greater. This is mainly because of the fact that as the iteration count increases, the amount of check and bit nodes' operation will increase. Thus, more parallelization can be done, and accordingly the speedup will become bigger. The effective SNR must be greater than 1.5 dB for the CMMB. Hence, the iteration count is typically greater than 20. When we iterated 20 times, the decoding performance was 2.046 Mbps, which satisfies the CMMB standard.
Table 2 LDPC decoding time (second) versus SNR value (CMMB 1/2, frame 10,000)
Table 3 summarizes the performance for a code rate 3/4 of the CMMB. As the code rate increased, LDPC decoding was successfully finished with at least 2.5 dB. To satisfy the CMMB performance requirement, more than 300 frames per second must be processed. Our results verify that signal reception with at least 3 dB will satisfy this requirement.
To show that our method is satisfactory for multiple standards, we implemented an LDPC decoder for DVB-S2 with a code rate of 2/5; Table 4 summaries the results. DVB-S2 is a standard for European satellite broadcasting. The H-matrix has 16,200 bits of code words and 9,720 bits of an information word. Table 4 shows that for an SNR of 1, we achieved a speedup of 24.5.
Table 4 LDPC decoding time (second) versus SNR value (DVB-S2 short 2/5, frame 1,000,000)
Table 5 compares our performance with those of recently reported [11, 13]. The H-matrix in our decoding is about twice the size of those previously reported [11, 13], which suggests that the performance of our proposed decoder is excellent.
Table 5 LDPC decoding time speed up (edges/row = 6)
Owing to the multiple standards and diverse device function needs of current digital communications, hardware-only implementations may not be cost-effective. Instead, software implementations of communication protocols using CPUs or GPUs are rapidly being adopted in digital communication system designs. In this article, we have described a software design that implements parallel processing of LDPC decoding algorithms. In our proposal, we use a combination of a multi-core processor and a GPU to achieve both flexibility and high performance. Specifically, we use OpenMP for parallelizing software on a multi-core processor and CUDA for parallel software running on a GPU. Test results show that our parallel software implementation of LDPC algorithms satisfies the CMMB performance and bandwidth requirements.
Standardization Roadmap for IT839 StrategyVer 2007.
Gallager R: Low-Density Parity Check Codes. MIT Press, Cambridge, MA; 1963.
MacKay D, Neal R: Near Shannon limit performance of low density parity check codes. IEE Electron Lett 1996, 32(18):1645-1646. 10.1049/el:19961141
MacKay D: Good error-correcting codes based on very sparse matrices. IEEE Trans Inf Theory 1999, 45(2):399-431. 10.1109/18.748992
Chung SY, Forney GD Jr, Richardson TJ, Urbanke R: On the design of low-density parity-check codes within 0.0045dB of the Shannon limit. IEEE Commun Lett 2001, 5: 58-60. 10.1109/4234.905935
SDR Forum[http://www.wirelessinnovation.org/]
Sousa L, Momcilovic S, Silva V, Falcão G: Multi-core platforms for signal processing: source and channel coding. IEEE Press Multimedia Expo 2009, 1805-1808.
Kollig P, Osborne C, Henriksson T: Heterogeneous multi-core platform for consumer multimedia applications. Date Conference 2009, 1254-1259.
van Berkel CH(K): Multi-core for mobile phones. Date Conference 2009, 1260-1265.
Falcão G, Sousa L, Silva V: Massive parallel LDPC decoding on GPU. 13th ACM SIGPLAN 2008, 83-90.
Falcão G, Yamagiwa S, Sousa L, Silva V: Parallel LDPC decoding on GPUs using a stream-based computing approach. J Comput Sci Technol 2009, 24(5):913-924. 10.1007/s11390-009-9266-8
Falcão G, Sousa L, Silva V: How GPUs can outperform ASICs for fast LDPC decoding. Proceedings of the 23rd international conference on Supercomputing 2009, 390-399.
Wang S, S C, Wu Q: A parallel decoding algorithm of LDPC codes using CUDA. Signals, Systems and Computers, 2008 42nd Asilomar Conference 2008, 171-175.
Ji H, Cho J, Sung W: Massively parallel implementation of cyclic LDPC codes on a general purpose graphics processing unit. Signal Processing Systems, SiPS 2009 2009, 285-290.
Lin S, Costello DJ Jr: Error Control Coding. 2nd edition. Prentice Hall; 2004.
ETSI EN 302 307, Digital Video Broadcasting (DVB): Second generation framing structure, channel coding and modulation systems for Broadcasting, Interactive Services, News Gathering and other broadband satellite applications.
GY/T 220.1-2006: Mobile Multimedia Broadcasting Part1.
NVIDIA CUDA Development Tools 2.3, Getting Started 2009.
NVIDIA CUDA C Programming Best Practices Guide, Optimization 2009.
Chapman B, Jost G, van der Pas R: Using OpenMP Portable Shared Memory Parallel Programming. The MIT Press; 2007.
This research was supported by the MKE (The Ministry of Knowledge Economy), Korea, under the ITRC(Information Technology Research Center) support program supervised by the NIPA (National IT Industry Promotion Agency) (NIPA-2011-C1090-1100-0010).
Department of Electronics, Computer & Communication Engineering, Hanyang University, Seoul, Korea
Joo-Yul Park & Ki-Seok Chung
Joo-Yul Park
Ki-Seok Chung
Correspondence to Ki-Seok Chung.
Park, JY., Chung, KS. Parallel LDPC decoding using CUDA and OpenMP. J Wireless Com Network 2011, 172 (2011). https://doi.org/10.1186/1687-1499-2011-172
LDPC
graphic processing unit


text:
/**
 * @file lis3mdl.cpp
 *
 * Driver for the LIS3MDL magnetometer connected via I2C or SPI.
 *
 * Based on the hmc5883 driver.
 */

#include <px4_config.h>
#include <px4_defines.h>

#include <drivers/device/i2c.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <semaphore.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <stdio.h>
#include <math.h>
#include <unistd.h>

#include <nuttx/arch.h>
#include <nuttx/wqueue.h>
#include <nuttx/clock.h>

#include <board_config.h>

#include <systemlib/perf_counter.h>
#include <systemlib/err.h>

#include <drivers/drv_mag.h>
#include <drivers/drv_hrt.h>
#include <drivers/device/ringbuffer.h>
#include <drivers/drv_device.h>

#include <uORB/uORB.h>

#include <float.h>
#include <getopt.h>
#include <lib/conversion/rotation.h>

#include "lis3mdl.h"

/*
 * LIS3MDL internal constants and data structures.
 */

/* Max measurement rate is 80Hz */
#define LIS3MDL_CONVERSION_INTERVAL	(1000000 / 80)	/* 12,500 microseconds */

#define ADDR_CTRL_REG1		0x20
#define ADDR_CTRL_REG2		0x21
#define ADDR_CTRL_REG3		0x22
#define ADDR_CTRL_REG4		0x23
#define ADDR_CTRL_REG5		0x24

#define ADDR_STATUS_REG		0x27
#define ADDR_OUT_X_L		0x28
#define ADDR_OUT_X_H		0x29
#define ADDR_OUT_Y_L		0x2a
#define ADDR_OUT_Y_H		0x2b
#define ADDR_OUT_Z_L		0x2c
#define ADDR_OUT_Z_H		0x2d
#define ADDR_OUT_T_L		0x2e
#define ADDR_OUT_T_H		0x2f

#define MODE_REG_CONTINOUS_MODE		(0 << 0)
#define MODE_REG_SINGLE_MODE		(1 << 0) /* default */

enum LIS3MDL_BUS {
	LIS3MDL_BUS_ALL = 0,
	LIS3MDL_BUS_I2C_INTERNAL,
	LIS3MDL_BUS_I2C_EXTERNAL,
	LIS3MDL_BUS_SPI
};

#ifndef CONFIG_SCHED_WORKQUEUE
# error This requires CONFIG_SCHED_WORKQUEUE.
#endif

class LIS3MDL : public device::CDev
{
public:
	LIS3MDL(device::Device *interface, const char *path, enum Rotation rotation);
	virtual ~LIS3MDL();

	virtual int		init();

	virtual ssize_t		read(struct file *filp, char *buffer, size_t buflen);
	virtual int		ioctl(struct file *filp, int cmd, unsigned long arg);

	/**
	 * Stop the automatic measurement state machine.
	 */
	void			stop();

	/**
	 * Diagnostics - print some basic information about the driver.
	 */
	void			print_info();

protected:
	Device			*_interface;

private:
	work_s			_work;
	unsigned		_measure_ticks;

	ringbuffer::RingBuffer	*_reports;
	struct mag_calibration_s	_scale;
	float 			_range_scale;
	float 			_range_ga;
	bool			_collect_phase;
	int			_class_instance;
	int			_orb_class_instance;

	orb_advert_t		_mag_topic;

	perf_counter_t		_sample_perf;
	perf_counter_t		_comms_errors;
	perf_counter_t		_range_errors;
	perf_counter_t		_conf_errors;

	/* status reporting */
	bool			_sensor_ok;		/**< sensor was found and reports ok */
	bool			_calibrated;		/**< the calibration is valid */

	enum Rotation		_rotation;

	struct mag_report	_last_report {};          /**< used for info() */

	uint8_t			_range_bits;
	uint8_t			_cntl_reg1;
	uint8_t			_cntl_reg4;
	uint8_t			_cntl_reg5;
	uint8_t			_temperature_counter;
	uint8_t			_temperature_error_count;
	uint8_t			_check_state_cnt;

	/**
	 * Initialise the automatic measurement state machine and start it.
	 *
	 * @note This function is called at open and error time.  It might make sense
	 *       to make it more aggressive about resetting the bus in case of errors.
	 */
	void			start();

	/**
	 * Reset the device
	 */
	int			reset();

	/**
	 * Perform the on-sensor scale calibration routine.
	 *
	 * @note The sensor will continue to provide measurements, these
	 *	 will however reflect the uncalibrated sensor state until
	 *	 the calibration routine has been completed.
	 *
	 * @param enable set to 1 to enable self-test strap, 0 to disable
	 */
	int			calibrate(struct file *filp, unsigned enable);

	/**
	 * Perform the on-sensor scale calibration routine.
	 *
	 * @note The sensor will continue to provide measurements, these
	 *	 will however reflect the uncalibrated sensor state until
	 *	 the calibration routine has been completed.
	 *
	 * @param enable set to 1 to enable self-test positive strap, -1 to enable
	 *        negative strap, 0 to set to normal mode
	 */
	int			set_excitement(unsigned enable);

	/**
	 * Set the sensor range.
	 *
	 * Sets the internal range to handle at least the argument in Gauss.
	 */
	int 			set_range(unsigned range);

	/**
	 * check the sensor configuration.
	 *
	 * checks that the config of the sensor is correctly set, to
	 * cope with communication errors causing the configuration to
	 * change
	 */
	void 			check_conf(void);

	/**
	 * Perform a poll cycle; collect from the previous measurement
	 * and start a new one.
	 *
	 * This is the heart of the measurement state machine.  This function
	 * alternately starts a measurement, or collects the data from the
	 * previous measurement.
	 *
	 * When the interval between measurements is greater than the minimum
	 * measurement interval, a gap is inserted between collection
	 * and measurement to provide the most recent measurement possible
	 * at the next interval.
	 */
	void			cycle();

	/**
	 * Static trampoline from the workq context; because we don't have a
	 * generic workq wrapper yet.
	 *
	 * @param arg		Instance pointer for the driver that is polling.
	 */
	static void		cycle_trampoline(void *arg);

	/**
	 * Write a register.
	 *
	 * @param reg		The register to write.
	 * @param val		The value to write.
	 * @return		OK on write success.
	 */
	int			write_reg(uint8_t reg, uint8_t val);

	/**
	 * Read a register.
	 *
	 * @param reg		The register to read.
	 * @param val		The value read.
	 * @return		OK on read success.
	 */
	int			read_reg(uint8_t reg, uint8_t &val);

	/**
	 * Issue a measurement command.
	 *
	 * @return		OK if the measurement command was successful.
	 */
	int			measure();

	/**
	 * Collect the result of the most recent measurement.
	 */
	int			collect();

	/**
	 * Convert a big-endian signed 16-bit value to a float.
	 *
	 * @param in		A signed 16-bit big-endian value.
	 * @return		The floating-point representation of the value.
	 */
	float			meas_to_float(uint8_t in[2]);

	/**
	 * Check the current calibration and update device status
	 *
	 * @return 0 if calibration is ok, 1 else
	 */
	int 			check_calibration();

	/**
	* Check the current scale calibration
	*
	* @return 0 if scale calibration is ok, 1 else
	*/
	int 			check_scale();

	/**
	* Check the current offset calibration
	*
	* @return 0 if offset calibration is ok, 1 else
	*/
	int 			check_offset();

	/* this class has pointer data members, do not allow copying it */
	LIS3MDL(const LIS3MDL &);
	LIS3MDL operator=(const LIS3MDL &);
};

/*
 * Driver 'main' command.
 */
extern "C" __EXPORT int lis3mdl_main(int argc, char *argv[]);

LIS3MDL::LIS3MDL(device::Device *interface, const char *path, enum Rotation rotation) :
	CDev("LIS3MDL", path),
	_interface(interface),
	_work{},
	_measure_ticks(0),
	_reports(nullptr),
	_scale{},
	_range_scale(0), /* default range scale from counts to gauss */
	_range_ga(4.0f),
	_collect_phase(false),
	_class_instance(-1),
	_orb_class_instance(-1),
	_mag_topic(nullptr),
	_sample_perf(perf_alloc(PC_ELAPSED, "lis3mdl_read")),
	_comms_errors(perf_alloc(PC_COUNT, "lis3mdl_comms_errors")),
	_range_errors(perf_alloc(PC_COUNT, "lis3mdl_range_errors")),
	_conf_errors(perf_alloc(PC_COUNT, "lis3mdl_conf_errors")),
	_sensor_ok(false),
	_calibrated(false),
	_rotation(rotation),
	_range_bits(0),
	_cntl_reg1(0),
	_cntl_reg4(0),
	_cntl_reg5(0),
	_temperature_counter(0),
	_temperature_error_count(0),
	_check_state_cnt(0)
{
	// set the device type from the interface
	_device_id.devid_s.bus_type = _interface->get_device_bus_type();
	_device_id.devid_s.bus = _interface->get_device_bus();
	_device_id.devid_s.address = _interface->get_device_address();
	_device_id.devid_s.devtype = DRV_MAG_DEVTYPE_LIS3MDL;

	// enable debug() calls
	_debug_enabled = false;

	// default scaling
	_scale.x_offset = 0;
	_scale.x_scale = 1.0f;
	_scale.y_offset = 0;
	_scale.y_scale = 1.0f;
	_scale.z_offset = 0;
	_scale.z_scale = 1.0f;

	// work_cancel in the dtor will explode if we don't do this...
	memset(&_work, 0, sizeof(_work));

	_cntl_reg1 = 0xFC;
	_cntl_reg4 = 0x0C; // Z-axis ultra high performance mode
	_cntl_reg5 = 0x40; // block data update for magnetic data

}

LIS3MDL::~LIS3MDL()
{
	/* make sure we are truly inactive */
	stop();

	if (_reports != nullptr) {
		delete _reports;
	}

	if (_class_instance != -1) {
		unregister_class_devname(MAG_BASE_DEVICE_PATH, _class_instance);
	}

	// free perf counters
	perf_free(_sample_perf);
	perf_free(_comms_errors);
	perf_free(_range_errors);
	perf_free(_conf_errors);
}

int
LIS3MDL::init()
{
	int ret = PX4_ERROR;

	ret = CDev::init();

	if (ret != OK) {
		DEVICE_DEBUG("CDev init failed");
		goto out;
	}

	/* allocate basic report buffers */
	_reports = new ringbuffer::RingBuffer(2, sizeof(mag_report));

	if (_reports == nullptr) {
		goto out;
	}

	/* reset the device configuration */
	reset();

	_class_instance = register_class_devname(MAG_BASE_DEVICE_PATH);

	ret = OK;
	/* sensor is ok, but not calibrated */
	_sensor_ok = true;
out:
	return ret;
}

int LIS3MDL::set_range(unsigned range)
{
	if (range < 6) {
		_range_bits = 0x00;
		_range_scale = 1.0f / 6842.0f;
		_range_ga = 4.0f;

	} else if (range <= 10) {
		_range_bits = 0x01;
		_range_scale = 1.0f / 3421.0f;
		_range_ga = 8.0f;

	} else if (range <= 14) {
		_range_bits = 0x02;
		_range_scale = 1.0f / 2281.0f;
		_range_ga = 12.0f;

	} else {
		_range_bits = 0x03;
		_range_scale = 1.0f / 1711.0f;
		_range_ga = 16.0f;
	}

	int ret;

	/*
	 * Send the command to set the range
	 */
	ret = write_reg(ADDR_CTRL_REG2, (_range_bits << 5));

	if (OK != ret) {
		perf_count(_comms_errors);
	}

	uint8_t range_bits_in = 0;
	ret = read_reg(ADDR_CTRL_REG2, range_bits_in);

	if (OK != ret) {
		perf_count(_comms_errors);
	}

	return !(range_bits_in == (_range_bits << 5));
}

/**
   check that the configuration register has the right value. This is
   done periodically to cope with I2C bus noise causing the
   configuration of the compass to change.
 */
void LIS3MDL::check_conf(void)
{
	int ret;
	uint8_t reg_in = 0;

	switch (_check_state_cnt++) {
	case 0:
		ret = read_reg(ADDR_CTRL_REG1, reg_in);

		if (OK != ret) {
			perf_count(_comms_errors);
			return;
		}

		if (reg_in != _cntl_reg1) {
			perf_count(_conf_errors);
			ret = write_reg(ADDR_CTRL_REG1, _cntl_reg1);

			if (OK != ret) {
				perf_count(_comms_errors);
			}
		}

		break;

	case 50:
		ret = read_reg(ADDR_CTRL_REG4, reg_in);

		if (OK != ret) {
			perf_count(_comms_errors);
			return;
		}

		if (reg_in != _cntl_reg4) {
			perf_count(_conf_errors);
			ret = write_reg(ADDR_CTRL_REG4, _cntl_reg4);

			if (OK != ret) {
				perf_count(_comms_errors);
			}
		}

		break;

	case 100:
		ret = read_reg(ADDR_CTRL_REG5, reg_in);

		if (OK != ret) {
			perf_count(_comms_errors);
			return;
		}

		if (reg_in != _cntl_reg5) {
			perf_count(_conf_errors);
			ret = write_reg(ADDR_CTRL_REG5, _cntl_reg5);

			if (OK != ret) {
				perf_count(_comms_errors);
			}
		}

		break;

	case 150:
		ret = read_reg(ADDR_CTRL_REG2, reg_in);

		if (OK != ret) {
			perf_count(_comms_errors);
			return;
		}

		if (reg_in != (_range_bits << 5)) {
			perf_count(_range_errors);
			ret = write_reg(ADDR_CTRL_REG2, (_range_bits << 5));

			if (OK != ret) {
				perf_count(_comms_errors);
			}
		}

		break;

	default:
		break;
	}
}

ssize_t
LIS3MDL::read(struct file *filp, char *buffer, size_t buflen)
{
	unsigned count = buflen / sizeof(struct mag_report);
	struct mag_report *mag_buf = reinterpret_cast<struct mag_report *>(buffer);
	int ret = 0;

	/* buffer must be large enough */
	if (count < 1) {
		return -ENOSPC;
	}

	/* if automatic measurement is enabled */
	if (_measure_ticks > 0) {
		/*
		 * While there is space in the caller's buffer, and reports, copy them.
		 * Note that we may be pre-empted by the workq thread while we are doing this;
		 * we are careful to avoid racing with them.
		 */
		while (count--) {
			if (_reports->get(mag_buf)) {
				ret += sizeof(struct mag_report);
				mag_buf++;
			}
		}

		/* if there was no data, warn the caller */
		return ret ? ret : -EAGAIN;
	}

	/* manual measurement - run one conversion */
	/* XXX really it'd be nice to lock against other readers here */
	do {
		_reports->flush();

		/* trigger a measurement */
		if (OK != measure()) {
			ret = -EIO;
			break;
		}

		/* wait for it to complete */
		usleep(LIS3MDL_CONVERSION_INTERVAL);

		/* run the collection phase */
		if (OK != collect()) {
			ret = -EIO;
			break;
		}

		if (_reports->get(mag_buf)) {
			ret = sizeof(struct mag_report);
		}
	} while (0);

	return ret;
}

int
LIS3MDL::ioctl(struct file *filp, int cmd, unsigned long arg)
{
	unsigned dummy = arg;

	switch (cmd) {
	case SENSORIOCSPOLLRATE: {
			switch (arg) {

			/* switching to manual polling */
			case SENSOR_POLLRATE_MANUAL:
				stop();
				_measure_ticks = 0;
				return OK;

			/* external signalling (DRDY) not supported */
			case SENSOR_POLLRATE_EXTERNAL:

			/* zero would be bad */
			case 0:
				return -EINVAL;

			/* set default/max polling rate */
			case SENSOR_POLLRATE_MAX:
			case SENSOR_POLLRATE_DEFAULT: {
					/* do we need to start internal polling? */
					bool want_start = (_measure_ticks == 0);

					/* set interval for next measurement to minimum legal value */
					_measure_ticks = USEC2TICK(LIS3MDL_CONVERSION_INTERVAL);

					/* if we need to start the poll state machine, do it */
					if (want_start) {
						start();
					}

					return OK;
				}

			/* adjust to a legal polling interval in Hz */
			default: {
					/* do we need to start internal polling? */
					bool want_start = (_measure_ticks == 0);

					/* convert hz to tick interval via microseconds */
					unsigned ticks = USEC2TICK(1000000 / arg);

					/* check against maximum rate */
					if (ticks < USEC2TICK(LIS3MDL_CONVERSION_INTERVAL)) {
// RobD: quick fix for Phil's testing						return -EINVAL;
					}

					/* update interval for next measurement */
					_measure_ticks = ticks;

					/* if we need to start the poll state machine, do it */
					if (want_start) {
						start();
					}

					return OK;
				}
			}
		}

	case SENSORIOCGPOLLRATE:
		if (_measure_ticks == 0) {
			return SENSOR_POLLRATE_MANUAL;
		}

		return 1000000 / TICK2USEC(_measure_ticks);

	case SENSORIOCSQUEUEDEPTH: {
			/* lower bound is mandatory, upper bound is a sanity check */
			if ((arg < 1) || (arg > 100)) {
				return -EINVAL;
			}

			irqstate_t flags = px4_enter_critical_section();

			if (!_reports->resize(arg)) {
				px4_leave_critical_section(flags);
				return -ENOMEM;
			}

			px4_leave_critical_section(flags);

			return OK;
		}

	case SENSORIOCRESET:
		return reset();

	case MAGIOCSSAMPLERATE:
		/* same as pollrate because device is in single measurement mode*/
		return ioctl(filp, SENSORIOCSPOLLRATE, arg);

	case MAGIOCGSAMPLERATE:
		/* same as pollrate because device is in single measurement mode*/
		return 1000000 / TICK2USEC(_measure_ticks);

	case MAGIOCSRANGE:
		return set_range(arg);

	case MAGIOCGRANGE:
		return _range_ga;

	case MAGIOCSSCALE:
		/* set new scale factors */
		memcpy(&_scale, (struct mag_calibration_s *)arg, sizeof(_scale));
		/* check calibration, but not actually return an error */
		(void)check_calibration();
		return 0;

	case MAGIOCGSCALE:
		/* copy out scale factors */
		memcpy((struct mag_calibration_s *)arg, &_scale, sizeof(_scale));
		return 0;

	case MAGIOCCALIBRATE:
		return calibrate(filp, arg);

	case MAGIOCEXSTRAP:
		return set_excitement(arg);

	case MAGIOCSELFTEST:
		return check_calibration();

	case MAGIOCGEXTERNAL:
		DEVICE_DEBUG("MAGIOCGEXTERNAL in main driver");
		return _interface->ioctl(cmd, dummy);

	case DEVIOCGDEVICEID:
		return _interface->ioctl(cmd, dummy);

	default:
		/* give it to the superclass */
		return CDev::ioctl(filp, cmd, arg);
	}
}

void
LIS3MDL::start()
{
	/* reset the report ring and state machine */
	_collect_phase = false;
	_reports->flush();

	/* schedule a cycle to start things */
	work_queue(HPWORK, &_work, (worker_t)&LIS3MDL::cycle_trampoline, this, 1);
}

void
LIS3MDL::stop()
{
	if (_measure_ticks > 0) {
		/* ensure no new items are queued while we cancel this one */
		_measure_ticks = 0;
		work_cancel(HPWORK, &_work);
	}
}

int
LIS3MDL::reset()
{
	/* set range */
	return set_range(_range_ga);
}

void
LIS3MDL::cycle_trampoline(void *arg)
{
	LIS3MDL *dev = (LIS3MDL *)arg;

	dev->cycle();
}

void
LIS3MDL::cycle()
{
	if (_measure_ticks == 0) {
		return;
	}

	/* collection phase? */
	if (_collect_phase) {

		/* perform collection */
		if (OK != collect()) {
			DEVICE_DEBUG("collection error");
			/* restart the measurement state machine */
			start();
			return;
		}

		/* next phase is measurement */
		_collect_phase = false;

		/*
		 * Is there a collect->measure gap?
		 */
		if (_measure_ticks > USEC2TICK(LIS3MDL_CONVERSION_INTERVAL)) {

			/* schedule a fresh cycle call when we are ready to measure again */
			work_queue(HPWORK,
				   &_work,
				   (worker_t)&LIS3MDL::cycle_trampoline,
				   this,
				   _measure_ticks - USEC2TICK(LIS3MDL_CONVERSION_INTERVAL));

			return;
		}
	}

	/* measurement phase */
	if (OK != measure()) {
		DEVICE_DEBUG("measure error");
	}

	/* next phase is collection */
	_collect_phase = true;

	if (_measure_ticks > 0) {
		/* schedule a fresh cycle call when the measurement is done */
		work_queue(HPWORK,
			   &_work,
			   (worker_t)&LIS3MDL::cycle_trampoline,
			   this,
			   USEC2TICK(LIS3MDL_CONVERSION_INTERVAL));
	}
}

int
LIS3MDL::measure()
{
	int ret;

	/*
	 * Send the command to begin a measurement.
	 */
	ret = write_reg(ADDR_CTRL_REG3, MODE_REG_SINGLE_MODE);

	if (OK != ret) {
		perf_count(_comms_errors);
	}

	return ret;
}

int
LIS3MDL::collect()
{
#pragma pack(push, 1)
	struct { /* status register and data as read back from the device */
		uint8_t		x[2];
		uint8_t		y[2];
		uint8_t		z[2];
		uint8_t		t[2];
	}	lis_report;

	struct {
		int16_t		x;
		int16_t		y;
		int16_t		z;
		int16_t		t;
	} report;
#pragma pack(pop)

	int	ret;
//	uint8_t check_counter;

	perf_begin(_sample_perf);
	struct mag_report new_report;
	bool sensor_is_onboard = false;

	float xraw_f;
	float yraw_f;
	float zraw_f;

	/* this should be fairly close to the end of the measurement, so the best approximation of the time */
	new_report.timestamp = hrt_absolute_time();
	new_report.error_count = perf_event_count(_comms_errors);
	new_report.range_ga = _range_ga;
	new_report.scaling = _range_scale;
	new_report.device_id = _device_id.devid;

	/*
	 * @note  We could read the status register here, which could tell us that
	 *        we were too early and that the output registers are still being
	 *        written.  In the common case that would just slow us down, and
	 *        we're better off just never being early.
	 */

	/* get measurements from the device */
	ret = _interface->read(ADDR_OUT_X_L, (uint8_t *)&lis_report, sizeof(lis_report));

	if (ret != OK) {
		perf_count(_comms_errors);
		DEVICE_DEBUG("data/status read error");
		goto out;
	}

	/* convert the data we just received */
	report.x = (((int16_t)lis_report.x[1]) << 8) + lis_report.x[0];
	report.y = (((int16_t)lis_report.y[1]) << 8) + lis_report.y[0];
	report.z = (((int16_t)lis_report.z[1]) << 8) + lis_report.z[0];
	report.t = (((int16_t)lis_report.t[1]) << 8) + lis_report.t[0];

	/* get measurements from the device */
	new_report.temperature = report.t;
	new_report.temperature = 25 + (report.t / (16 * 8.0f));

	// XXX revisit for SPI part, might require a bus type IOCTL

	unsigned dummy;
	sensor_is_onboard = !_interface->ioctl(MAGIOCGEXTERNAL, dummy);
	new_report.is_external = !sensor_is_onboard;

	/*
	 * RAW outputs
	 *
	 */
	new_report.x_raw = report.x;
	new_report.y_raw = report.y;
	new_report.z_raw = report.z;

	/* the LIS3MDL mag on Pixhawk Pro by Drotek has x pointing towards,
	 * y pointing to the right, and z down, therefore no switch needed,
	 * it is better to have no artificial rotation inside the
	 * driver and then use the startup script with -R command with the
	 * real rotation between the sensor and body frame */
	xraw_f = report.x;
	yraw_f = report.y;
	zraw_f = report.z;

	// apply user specified rotation
	rotate_3f(_rotation, xraw_f, yraw_f, zraw_f);

	new_report.x = ((xraw_f * _range_scale) - _scale.x_offset) * _scale.x_scale;
	/* flip axes and negate value for y */
	new_report.y = ((yraw_f * _range_scale) - _scale.y_offset) * _scale.y_scale;
	/* z remains z */
	new_report.z = ((zraw_f * _range_scale) - _scale.z_offset) * _scale.z_scale;

	if (!(_pub_blocked)) {

		if (_mag_topic != nullptr) {
			/* publish it */
			orb_publish(ORB_ID(sensor_mag), _mag_topic, &new_report);

		} else {
			_mag_topic = orb_advertise_multi(ORB_ID(sensor_mag), &new_report,
							 &_orb_class_instance, (sensor_is_onboard) ? ORB_PRIO_HIGH : ORB_PRIO_MAX);

			if (_mag_topic == nullptr) {
				DEVICE_DEBUG("ADVERT FAIL");
			}
		}
	}

	_last_report = new_report;

	/* post a report to the ring */
	_reports->force(&new_report);

	/* notify anyone waiting for data */
	poll_notify(POLLIN);

	check_conf();

	ret = OK;

out:
	perf_end(_sample_perf);
	return ret;
}

int LIS3MDL::calibrate(struct file *filp, unsigned enable)
{
	struct mag_report report;
	ssize_t sz;
	int ret = 1;

	// XXX do something smarter here
	int fd = (int)enable;

	float sum_excited[3] = {0.0f, 0.0f, 0.0f};
	float sum_non_excited[3] = {0.0f, 0.0f, 0.0f};

	/* start the sensor polling at 50 Hz */
	if (OK != ioctl(filp, SENSORIOCSPOLLRATE, 50)) {
		warn("FAILED: SENSORIOCSPOLLRATE 50Hz");
		ret = 1;
		goto out;
	}

	/* Set to 4 Gauss */
	if (OK != ioctl(filp, MAGIOCSRANGE, 12)) {
		warnx("FAILED: MAGIOCSRANGE 12 Ga");
		ret = 1;
		goto out;
	}

	usleep(20000);

	// discard 10 samples to let the sensor settle
	for (uint8_t i = 0; i < 10; i++) {
		struct pollfd fds;

		/* wait for data to be ready */
		fds.fd = fd;
		fds.events = POLLIN;
		ret = ::poll(&fds, 1, 2000);

		if (ret != 1) {
			warn("ERROR: TIMEOUT 1");
			goto out;
		}

		/* now go get it */
		sz = ::read(fd, &report, sizeof(report));

		if (sz != sizeof(report)) {
			warn("ERROR: READ 1");
			ret = -EIO;
			goto out;
		}
	}

	/* read the sensor up to 10x */
	for (uint8_t i = 0; i < 10; i++) {
		struct pollfd fds;

		/* wait for data to be ready */
		fds.fd = fd;
		fds.events = POLLIN;
		ret = ::poll(&fds, 1, 2000);

		if (ret != 1) {
			warn("ERROR: TIMEOUT 2");
			goto out;
		}

		/* now go get it */
		sz = ::read(fd, &report, sizeof(report));

		if (sz != sizeof(report)) {
			warn("ERROR: READ 2");
			ret = -EIO;
			goto out;
		}

		sum_non_excited[0] += report.x;
		sum_non_excited[1] += report.y;
		sum_non_excited[2] += report.z;
	}

	sum_non_excited[0] /= 10.0f;
	sum_non_excited[1] /= 10.0f;
	sum_non_excited[2] /= 10.0f;

	if (OK != ioctl(filp, MAGIOCEXSTRAP, 1)) {
		warnx("FAILED: MAGIOCEXSTRAP 1");
		ret = 1;
		goto out;
	}

	usleep(60000);

	// discard 10 samples to let the sensor settle
	for (uint8_t i = 0; i < 10; i++) {
		struct pollfd fds;

		/* wait for data to be ready */
		fds.fd = fd;
		fds.events = POLLIN;
		ret = ::poll(&fds, 1, 2000);

		if (ret != 1) {
			warn("ERROR: TIMEOUT 1");
			goto out;
		}

		/* now go get it */
		sz = ::read(fd, &report, sizeof(report));

		if (sz != sizeof(report)) {
			warn("ERROR: READ 1");
			ret = -EIO;
			goto out;
		}
	}

	/* read the sensor up to 10x */
	for (uint8_t i = 0; i < 10; i++) {
		struct pollfd fds;

		/* wait for data to be ready */
		fds.fd = fd;
		fds.events = POLLIN;
		ret = ::poll(&fds, 1, 2000);

		if (ret != 1) {
			warn("ERROR: TIMEOUT 2");
			goto out;
		}

		/* now go get it */
		sz = ::read(fd, &report, sizeof(report));

		if (sz != sizeof(report)) {
			warn("ERROR: READ 2");
			ret = -EIO;
			goto out;
		}

		sum_excited[0] += report.x;
		sum_excited[1] += report.y;
		sum_excited[2] += report.z;
	}

	sum_excited[0] /= 10.0f;
	sum_excited[1] /= 10.0f;
	sum_excited[2] /= 10.0f;

	if (1.0f < fabsf(sum_excited[0] - sum_non_excited[0]) && fabsf(sum_excited[0] - sum_non_excited[0]) < 3.0f &&
	    1.0f < fabsf(sum_excited[1] - sum_non_excited[1]) && fabsf(sum_excited[1] - sum_non_excited[1]) < 3.0f &&
	    0.1f < fabsf(sum_excited[2] - sum_non_excited[2]) && fabsf(sum_excited[2] - sum_non_excited[2]) < 1.0f) {
		ret = OK;

	} else {
		ret = -EIO;
		goto out;
	}

	ret = OK;

out:

	/* set back to normal mode */
	/* Set to 4 Gauss */
	if (OK != ::ioctl(fd, MAGIOCSRANGE, 4)) {
		warnx("FAILED: MAGIOCSRANGE 4 Ga");
	}

	if (OK != ::ioctl(fd, MAGIOCEXSTRAP, 0)) {
		warnx("FAILED: MAGIOCEXSTRAP 0");
	}

	usleep(20000);

	return ret;
}

int LIS3MDL::check_scale()
{
	bool scale_valid;

	if ((-FLT_EPSILON + 1.0f < _scale.x_scale && _scale.x_scale < FLT_EPSILON + 1.0f) &&
	    (-FLT_EPSILON + 1.0f < _scale.y_scale && _scale.y_scale < FLT_EPSILON + 1.0f) &&
	    (-FLT_EPSILON + 1.0f < _scale.z_scale && _scale.z_scale < FLT_EPSILON + 1.0f)) {
		/* scale is one */
		scale_valid = false;

	} else {
		scale_valid = true;
	}

	/* return 0 if calibrated, 1 else */
	return !scale_valid;
}

int LIS3MDL::check_offset()
{
	bool offset_valid;

	if ((-2.0f * FLT_EPSILON < _scale.x_offset && _scale.x_offset < 2.0f * FLT_EPSILON) &&
	    (-2.0f * FLT_EPSILON < _scale.y_offset && _scale.y_offset < 2.0f * FLT_EPSILON) &&
	    (-2.0f * FLT_EPSILON < _scale.z_offset && _scale.z_offset < 2.0f * FLT_EPSILON)) {
		/* offset is zero */
		offset_valid = false;

	} else {
		offset_valid = true;
	}

	/* return 0 if calibrated, 1 else */
	return !offset_valid;
}

int LIS3MDL::check_calibration()
{
	bool offset_valid = (check_offset() == OK);
	bool scale_valid  = (check_scale() == OK);

	if (_calibrated != (offset_valid && scale_valid)) {
		warnx("mag cal status changed %s%s", (scale_valid) ? "" : "scale invalid ",
		      (offset_valid) ? "" : "offset invalid");
		_calibrated = (offset_valid && scale_valid);
	}

	/* return 0 if calibrated, 1 else */
	return !_calibrated;
}

int LIS3MDL::set_excitement(unsigned enable)
{
	int ret;
	/* arm the excitement strap */
	ret = read_reg(ADDR_CTRL_REG1, _cntl_reg1);

	if (OK != ret) {
		perf_count(_comms_errors);
	}

	_cntl_reg1 &= ~0x01; // reset previous excitement mode

	if (((int)enable) < 0) {
		warnx("WARN: set_excitement negative not supported\n");

	} else if (enable > 0) {
		_cntl_reg1 |= 0x01;
	}

	::printf("set_excitement enable=%d cntl1=0x%x\n", (int)enable, (unsigned)_cntl_reg1);

	ret = write_reg(ADDR_CTRL_REG1, _cntl_reg1);

	if (OK != ret) {
		perf_count(_comms_errors);
	}

	uint8_t conf_reg_ret = 0;
	read_reg(ADDR_CTRL_REG1, conf_reg_ret);

	//print_info();

	return !(_cntl_reg1 == conf_reg_ret);
}

int
LIS3MDL::write_reg(uint8_t reg, uint8_t val)
{
	uint8_t buf = val;
	return _interface->write(reg, &buf, 1);
}

int
LIS3MDL::read_reg(uint8_t reg, uint8_t &val)
{
	uint8_t buf = val;
	int ret = _interface->read(reg, &buf, 1);
	val = buf;
	return ret;
}

float
LIS3MDL::meas_to_float(uint8_t in[2])
{
	union {
		uint8_t	b[2];
		int16_t	w;
	} u;

	u.b[0] = in[1];
	u.b[1] = in[0];

	return (float) u.w;
}

void
LIS3MDL::print_info()
{
	perf_print_counter(_sample_perf);
	perf_print_counter(_comms_errors);
	printf("poll interval:  %u ticks\n", _measure_ticks);
	printf("output  (%.2f %.2f %.2f)\n", (double)_last_report.x, (double)_last_report.y, (double)_last_report.z);
	printf("offsets (%.2f %.2f %.2f)\n", (double)_scale.x_offset, (double)_scale.y_offset, (double)_scale.z_offset);
	printf("scaling (%.2f %.2f %.2f) 1/range_scale %.2f range_ga %.2f\n",
	       (double)_scale.x_scale, (double)_scale.y_scale, (double)_scale.z_scale,
	       (double)(1.0f / _range_scale), (double)_range_ga);
	printf("temperature %.2f\n", (double)_last_report.temperature);
	_reports->print_info("report queue");
}

/**
 * Local functions in support of the shell command.
 */
namespace lis3mdl
{

/*
  list of supported bus configurations
 */
struct lis3mdl_bus_option {
	enum LIS3MDL_BUS busid;
	const char *devpath;
	LIS3MDL_constructor interface_constructor;
	uint8_t busnum;
	LIS3MDL	*dev;
} bus_options[] = {
	{ LIS3MDL_BUS_I2C_EXTERNAL, "/dev/lis3mdl_ext", &LIS3MDL_I2C_interface, PX4_I2C_BUS_EXPANSION, NULL },
#ifdef PX4_I2C_BUS_ONBOARD
	{ LIS3MDL_BUS_I2C_INTERNAL, "/dev/lis3mdl_int", &LIS3MDL_I2C_interface, PX4_I2C_BUS_ONBOARD, NULL },
#endif
#ifdef PX4_SPIDEV_LIS
	{ LIS3MDL_BUS_SPI, "/dev/lis3mdl_spi", &LIS3MDL_SPI_interface, PX4_SPI_BUS_SENSORS, NULL },
#endif
};
#define NUM_BUS_OPTIONS (sizeof(bus_options)/sizeof(bus_options[0]))

void	start(enum LIS3MDL_BUS busid, enum Rotation rotation);
int		stop();
bool	start_bus(struct lis3mdl_bus_option &bus, enum Rotation rotation);
struct lis3mdl_bus_option &find_bus(enum LIS3MDL_BUS busid);
void	test(enum LIS3MDL_BUS busid);
void	reset(enum LIS3MDL_BUS busid);
int	info(enum LIS3MDL_BUS busid);
int	calibrate(enum LIS3MDL_BUS busid);
void	usage();

/**
 * start driver for a specific bus option
 */
bool
start_bus(struct lis3mdl_bus_option &bus, enum Rotation rotation)
{
	if (bus.dev != nullptr) {
		errx(1, "bus option already started");
	}

	device::Device *interface = bus.interface_constructor(bus.busnum);

	if (interface->init() != OK) {
		delete interface;
		warnx("no device on bus %u", (unsigned)bus.busid);
		return false;
	}

	bus.dev = new LIS3MDL(interface, bus.devpath, rotation);

	if (bus.dev != nullptr && OK != bus.dev->init()) {
		delete bus.dev;
		bus.dev = NULL;
		return false;
	}

	int fd = open(bus.devpath, O_RDONLY);

	if (fd < 0) {
		return false;
	}

	if (ioctl(fd, SENSORIOCSPOLLRATE, SENSOR_POLLRATE_DEFAULT) < 0) {
		close(fd);
		errx(1, "Failed to setup poll rate");
	}

	/* start the sensor polling at 50 Hz */
	if (OK != ioctl(fd, SENSORIOCSPOLLRATE, 50)) {
		warn("FAILED: SENSORIOCSPOLLRATE 50Hz");
	}

	printf("set poll rate to 50Hz\n");

	/* Set to 4 Gauss */
	if (OK != ioctl(fd, MAGIOCSRANGE, 4)) {
		warnx("FAILED: MAGIOCSRANGE 4 Ga");
	}

	printf("set range to 4 Ga\n");

	close(fd);

	return true;
}

/**
 * Start the driver.
 *
 * This function call only returns once the driver
 * is either successfully up and running or failed to start.
 */
void
start(enum LIS3MDL_BUS busid, enum Rotation rotation)
{
	bool started = false;

	for (unsigned i = 0; i < NUM_BUS_OPTIONS; i++) {
		if (busid == LIS3MDL_BUS_ALL && bus_options[i].dev != NULL) {
			// this device is already started
			continue;
		}

		if (busid != LIS3MDL_BUS_ALL && bus_options[i].busid != busid) {
			// not the one that is asked for
			continue;
		}

		started |= start_bus(bus_options[i], rotation);
	}

	if (!started) {
		exit(1);
	}
}

/**
 * Stop the driver.
 */
int
stop()
{
	bool stopped = false;

	for (unsigned i = 0; i < NUM_BUS_OPTIONS; i++) {
		if (bus_options[i].dev != nullptr) {
			bus_options[i].dev->stop();
			delete bus_options[i].dev;
			bus_options[i].dev = nullptr;
			stopped = true;
		}
	}

	return !stopped;
}

/**
 * find a bus structure for a busid
 */
struct lis3mdl_bus_option &find_bus(enum LIS3MDL_BUS busid)
{
	for (unsigned i = 0; i < NUM_BUS_OPTIONS; i++) {
		if ((busid == LIS3MDL_BUS_ALL ||
		     busid == bus_options[i].busid) && bus_options[i].dev != NULL) {
			return bus_options[i];
		}
	}

	errx(1, "bus %u not started", (unsigned)busid);
}

/**
 * Perform some basic functional tests on the driver;
 * make sure we can collect data from the sensor in polled
 * and automatic modes.
 */
void
test(enum LIS3MDL_BUS busid)
{
	struct lis3mdl_bus_option &bus = find_bus(busid);
	struct mag_report report;
	ssize_t sz;
	int ret;
	const char *path = bus.devpath;

	int fd = open(path, O_RDONLY);

	if (fd < 0) {
		err(1, "%s open failed (try 'lis3mdl start')", path);
	}

	/* do a simple demand read */
	sz = read(fd, &report, sizeof(report));

	if (sz != sizeof(report)) {
		err(1, "immediate read failed");
	}

	warnx("single read");
	warnx("measurement: %.6f  %.6f  %.6f", (double)report.x, (double)report.y, (double)report.z);
	warnx("time:        %lld", report.timestamp);

	/* check if mag is onboard or external */
	if ((ret = ioctl(fd, MAGIOCGEXTERNAL, 0)) < 0) {
		errx(1, "failed to get if mag is onboard or external");
	}

	warnx("device active: %s", ret ? "external" : "onboard");

	/* set the queue depth to 5 */
	if (OK != ioctl(fd, SENSORIOCSQUEUEDEPTH, 10)) {
		errx(1, "failed to set queue depth");
	}

	/* start the sensor polling at 2Hz */
	if (OK != ioctl(fd, SENSORIOCSPOLLRATE, 2)) {
		errx(1, "failed to set 2Hz poll rate");
	}

	/* read the sensor 5x and report each value */
	for (unsigned i = 0; i < 5; i++) {
		struct pollfd fds;

		/* wait for data to be ready */
		fds.fd = fd;
		fds.events = POLLIN;
		ret = poll(&fds, 1, 2000);

		if (ret != 1) {
			errx(1, "timed out waiting for sensor data");
		}

		/* now go get it */
		sz = read(fd, &report, sizeof(report));

		if (sz != sizeof(report)) {
			err(1, "periodic read failed");
		}

		warnx("periodic read %u", i);
		warnx("measurement: %.6f  %.6f  %.6f", (double)report.x, (double)report.y, (double)report.z);
		warnx("time:        %lld", report.timestamp);
	}

	errx(0, "PASS");
}

/**
 * Self test check.
 *
 * Unlike HMC5883, self test feature cannot be used to calculate
 * scale.
 *
 * SELF TEST OPERATION
 * To check the LIS3MDL for proper operation, a self test feature is incorporated :
 * sensor offset straps are excited to create a nominal field strength
 * (bias field) to be measured. To implement self test, the least significant bits
 * (MS1 and MS0) of configuration register A are changed from 00 to 01 (positive bias).
 * A few measurements are taken and stored with and without the additional magnetic
 * field. According to ST datasheet, those values must stay between thresholds in order
 * to pass the self test.
 */
int calibrate(enum LIS3MDL_BUS busid)
{
	int ret;
	struct lis3mdl_bus_option &bus = find_bus(busid);
	const char *path = bus.devpath;

	int fd = open(path, O_RDONLY);

	if (fd < 0) {
		err(1, "%s open failed (try 'lis3mdl start' if the driver is not running", path);
	}

	if (OK != (ret = ioctl(fd, MAGIOCCALIBRATE, fd))) {
		warnx("failed to enable sensor calibration mode");
	}

	close(fd);

	return ret;
}

/**
 * Reset the driver.
 */
void
reset(enum LIS3MDL_BUS busid)
{
	struct lis3mdl_bus_option &bus = find_bus(busid);
	const char *path = bus.devpath;

	int fd = open(path, O_RDONLY);

	if (fd < 0) {
		err(1, "failed ");
	}

	if (ioctl(fd, SENSORIOCRESET, 0) < 0) {
		err(1, "driver reset failed");
	}

	if (ioctl(fd, SENSORIOCSPOLLRATE, SENSOR_POLLRATE_DEFAULT) < 0) {
		err(1, "driver poll restart failed");
	}

	exit(0);
}

/**
 * Print a little info about the driver.
 */
int
info(enum LIS3MDL_BUS busid)
{
	struct lis3mdl_bus_option &bus = find_bus(busid);

	warnx("running on bus: %u (%s)\n", (unsigned)bus.busid, bus.devpath);
	bus.dev->print_info();
	exit(0);
}

void
usage()
{
	warnx("missing command: try 'start', 'info', 'test', 'reset', 'info', 'calibrate'");
	warnx("options:");
	warnx("    -R rotation");
	warnx("    -C calibrate on start");
	warnx("    -X only external bus");
#if (PX4_I2C_BUS_ONBOARD || PX4_SPIDEV_LIS)
	warnx("    -I only internal bus");
#endif
}

} // namespace

int
lis3mdl_main(int argc, char *argv[])
{
	int ch;
	enum LIS3MDL_BUS busid = LIS3MDL_BUS_ALL;
	enum Rotation rotation = ROTATION_NONE;
	bool calibrate = false;

	while ((ch = getopt(argc, argv, "XISR:CT")) != EOF) {
		switch (ch) {
		case 'R':
			rotation = (enum Rotation)atoi(optarg);
			break;
#if (PX4_I2C_BUS_ONBOARD || PX4_SPIDEV_LIS)

		case 'I':
			busid = LIS3MDL_BUS_I2C_INTERNAL;
			break;
#endif

		case 'X':
			busid = LIS3MDL_BUS_I2C_EXTERNAL;
			break;

		case 'S':
			busid = LIS3MDL_BUS_SPI;
			break;

		case 'C':
			calibrate = true;
			break;

		default:
			lis3mdl::usage();
			exit(0);
		}
	}

	const char *verb = argv[optind];

	/*
	 * Start/load the driver.
	 */
	if (!strcmp(verb, "start")) {
		lis3mdl::start(busid, rotation);

		if (calibrate && lis3mdl::calibrate(busid) != 0) {
			errx(1, "calibration failed");
		}

		exit(0);
	}

	/*
	 * Stop the driver.
	 */
	if (!strcmp(verb, "stop")) {
		return lis3mdl::stop();
	}

	/*
	 * Test the driver/device.
	 */
	if (!strcmp(verb, "test")) {
		lis3mdl::test(busid);
	}

	/*
	 * Reset the driver.
	 */
	if (!strcmp(verb, "reset")) {
		lis3mdl::reset(busid);
	}

	/*
	 * Print driver information.
	 */
	if (!strcmp(verb, "info") || !strcmp(verb, "status")) {
		lis3mdl::info(busid);
	}

	/*
	 * Autocalibrate the scaling
	 */
	if (!strcmp(verb, "calibrate")) {
		if (lis3mdl::calibrate(busid) == 0) {
			errx(0, "calibration successful");

		} else {
			errx(1, "calibration failed");
		}
	}

	errx(1, "unrecognized command, try 'start', 'test', 'reset', 'calibrate' 'or 'info'");
}

redpajama_set_name: RedPajamaGithub


text:
Xbox One to Receive Original Xbox Compatibility Alongside Limited Duke Controller
Jeremy Peeples June 12, 2017
Microsoft's 2017 E3 briefing was outstanding, and revealed a lot about the future of the Xbox One platform – while also celebrating its past. Microsoft announced the Xbox One X, a slew of games for the Xbox One that will be optimized for it, and finished off their show with a surprising announcement. The original Xbox will be reborn with backwards compatibility on the Xbox One family of consoles. It isn't known what the full launch lineup will be when this feature rolls out later this year, but they did showcase Crimson Skies – so it seems like a safe bet that will be in the launch lineup and possibly even be a free giveaway game for a bit.
One issue that the Xbox 360's backwards compatibility brought up was digital rights expiring – which has already ensured that some games, including Microsoft's own Forza Horizon, had limited digital lifespans on the Xbox One's marketplace. This issue will surely be compounded on the original Xbox, with some of its best exclusives like Jet Set Radio Future and Crazy Taxi also having music rights to worry about renewing to get the full experience. Fortunately, some other third-party games may be less challenging – like the outstanding Otogi games, if the ownership rights are free and clear for them. The safest bets seem to be games that were already released digitally on the Xbox 360, which were fairly plentiful. The original Xbox controller will also be released in a limited form by Hyperkin – with some modern changes to it.
This is an exciting time to be an Xbox One owner – and with the console family now ranging from $250 for an Xbox One S to $500 for an Xbox One X, players from all income levels can enjoy a high-end gaming experience that scales for their budget. Original Xbox games have been lost to the mists of time in many cases thanks to a lack of support beyond the 360, and support for that ended nearly a decade ago. This is a great time to buy original Xbox games though, as many can be had complete for $10-$15 and only a few go for $50+ like Shenmue II.
backwards compatibility original xbox xbox one	2017-06-12
Jeremy Peeples
Tags backwards compatibility original xbox xbox one

redpajama_set_name: RedPajamaCommonCrawl


Description MEN1 Full-Length MS Protein Standard (NP_570711), Labeled with [U- 13C6, 15N4]-L-Arginine and [U- 13C6, 15N2]-L-Lysine, was produced in human 293 cells (HEK293) with fully chemically defined cell culture medium to obtain incorporation efficiency at Creative-Proteomics. This gene encodes menin, a putative tumor suppressor associated with a syndrome known as multiple endocrine neoplasia type 1. In vitro studies have shown menin is localized to the nucleus, possesses two functional nuclear localization signals, and inhibits transcriptional activation by JunD, however, the function of this protein is not known. Two messages have been detected on northern blots but the larger message has not been characterized. Alternative splicing results in multiple transcript variants.


You know those dreams you have been having about a Hawaiian vacation? The detailed envisions you have about drinking a Pina Colada on the beach, with a gentle breeze blowing, the sun setting, and music playing in the background. You are barefoot with your feet in the sand, the smell of flowers is all around, and you are officially on Island time? Yes, that exact day dream you are having, or maybe you are having it right now! Hawaii is calling and you want to go so badly. You need to get away and take a break. You need a true vacation—not one that is full of family obligations and functions and at a place that you do not really want to be at. You are craving some away time where you can have things to do or you can genuinely relax and have nothing to do all day. Having no obligations to some folks is very rewarding. When you are not go-go-go each and every waking moment, you can have a little breathing room to relax and enjoy the ride of life. On Hawaii you can enjoy the view, the time, the breeze, everything!
So, you want to go to Hawaii. Which island do you choose? You have options, and all have their specialties. Some are only better than others by ruling of opinion. It is the matter of the eye of the beholder, if you know what I mean. If you opt for the rural yet sporty island of Kauai, Wailua Hideaway as seen here http://www.wailua-hideaway.com/ might just be what you had in mind. Tropical meets affordable vacation rental with all of the fixings. Now that is the way to vacation comfortably! Once you see pictures of the real thing, it will give you mind some images to play with and envision yourself being there. It is a neat thing to go to another state, relax, enjoy yourself, and also have the comforts of home at your fingertips. It is the new home away from home if you will! You might be getting so comfortable with it that you will be planning your trips at this vacation rental spot for an annual vacation spot. When you find something you like, don't change it! Perfection should be left alone. Kauai is highly esteemed and well thought of. It makes for a fabulous vacation spot and close to if not the best place to have a vacation rental.


text:
HSHUDDLE
Jackets a testament to SMC's continued strength
Andrew Pearson
apearson@citizen-times.com
HAYESVILLE - It's hard to keep a good conference down.
Four of the Smoky Mountain Conference's seven schools have advanced to the second round of either the NCHSAA 1-A or 1-AA football playoffs.
Many people would say that a two-win team didn't belong in the postseason to begin with.
Sophomore quarterback Alan Gillis (1,190 passing yards, 853 passing yards and 22 combined touchdowns) and Hayesville showed the non-believers in the first round of 1-A with a 41-15 win at Alleghany. The Yellow Jackets (3-8) were a sixth seed that had to take a 500-mile round trip to upset the No. 3 Trojans (5-7).
Hayesville's bus rolled back onto its Clay County campus Saturday at about 3:15 a.m.
"It's a great feeling and I'm so happy for these kids," first-year Yellow Jackets coach Kenneth Dockery said.
"They've had a pretty tough season, and been told all year long how bad they are. But they kept fighting."
Gillis did not begin the year as the Yellow Jackets starter, but went over the 1,000-yard mark last Friday. Gillis continues to build on his relationship with junior Brian Smith (34 receptions for 472 yards and three touchdowns) and sophomore Payton Krieger (31 receptions for 401 yards and three touchdowns).
Colton Thomas (511 yards, eight touchdowns) is second to Gillis in rushing.
Devin Boykin (7.0 tackles per game), Thomas Cothren (6.9) and Krieger (4.4) have taken charge for the defense.
Hayesville has 11 seniors on its roster — Chayton McClure, Seth Hatherly, George Seibert, Kyle Rhinehardt, C.J. Heller, Cristian Arteaga, Zach Raxter, Logan Thurmond, Cordell Lands, Josiah Finnegan and Bronson Kirby.
Rhinehardt (239 yards and one touchdown with seven interceptions) broke the thumb on his throwing hand (right) earlier in the season. He has since returned to contribute at middle linebacker.
"For sure, we're peaking at the right time," Rhinehardt said.
"The playoffs are a brand-new season. Everyone's attitude is great, everyone's work ethic is great. We've come together as a team and it's just been awesome."
Next up is Friday's second-round game at second-seeded Mitchell (11-1). The Mountaineers are the defending 1-AA Western Regional champions, but are playing at the 1-A level this fall.
"We're the underdogs, we know that," Rhinehardt said.
"Everybody is excited about the opportunity. If we play the way we did last week, I think we've got a chance."
Follow the HS Huddle on Facebook at www.facebook.com/hshuddle, Twitter at www.twitter.com/acthshuddle or Instagram at www.instagram.com/high_school_huddle


text:
Be Careful What You Wish For (Book 4)
Be Careful What You Wish For opens with Harry and Emma Clifton rushing to the hospital to learn the fate of their son Sebastian, who has been involved in a fatal car accident. Finding out that there was a misunderstanding, and that it's Bruno, Don Pedro Martinez's son, who has been killed, is both a relief and a shock, for them and for us.
Don Pedro's grief and anger becomes ever more focused on the Cliftons and Barringtons, and he is casting around for a way to exact his revenge. When Ross Buchanan is forced to resign as chairman of the Barrington Shipping Company, Emma Clifton wants to replace him, but it is Don Pedro who is acting behind the scenes, intending to install his puppet, Alex Fisher (remember him?), in order to destroy the Barrington family firm from the inside.
Back in London, Harry and Emma's adopted daughter Jessica (who of course is really Emma's half-sister) wins a scholarship to the Slade Academy of Art where she falls in love and becomes engaged to a fellow student, Clive Bingham. The story has sailed into the 1960s with verve and Both families are delighted until Priscilla Bingham, Jessica's future mother-in-law, has a visit from an old friend, Lady Virginia Fenwick, who does exactly what you'd expect to poor Jessica. What happens as a result is the most bittersweet, tragic moment of the whole series.
The machinations continue on the board of Barringtons, and help comes in the guise of a new board member, Cedric Hardcastle. Cedric's contributions provide a much-needed balance to the threats of Don Pedro Martinez and Lady Virginia, but even still, the suspense over the goings-on inside a boardroom is delicious.
And once again, the book ends on a powerful cliffhanger—clearly Archer's specialty—and so we anxiously await the Clifton and Barrington family story's next installment. . . .
Bhavik Keshvani says:
Its an astonishing piece of work. I just can't wait for the Book – 5 " Mightier than the sword". What i found to be confusing is the last 2 chapters of the book. I am unable to get clarity about who is "LORD GLENARTHUR", or who is finally described worrying whne the bomb explodes and also who is called the "CHAUFFER".
Rebekka says:
Lord Glenarthur is a distant relative of the Barringtons and an IRA member takes on his persona to get a cabin on the ship. After his work is done he drops his disguise. The chauffeur is also s member of the IRA.
Vignesh srinivasan says:
Sir.. Its been a roller coaster ride filled with ups and downs as i reas the 4 books of the amazing clifton chronicles. Cant wait anytime as the majestic climax awaits..
I have been an avid fan of your writings sir.
I hope to read many more of our works in the days to come.
Nancy Ellis says:
No spoiler here, but I'm so excited that my book arrived today….Mightier Than the Sword……and as soon as I could, I sat down to find out how you resolved the bomb issue…………….I was like a kid waiting for Christmas morning…..and I wasn't disappointed! Looking forward to the discussion of the new book………
james collins says:
anyone know the name of book 6 or is it to soon for it to have a title?


The general assessment is really just a basic analysis of bills and maybe some actual weighing of food waste bins. This will give an overview of site wide information with little or no specifics about where and when the food waste is actually produced.
Ideally every business should already be doing this as part of good waste management practices. If your business has a brown bin service then this information may already be provided. If not, then you should separate all food waste into separate food waste bins (this is the law) and weigh them before they are removed. From this you will have a rough idea of the weight of food waste being removed.
Using the weight of food waste you are throwing out, you should be able to estimate the annual food waste cost to your business. This is usually a great motivator to address the food waste issue. From this you can then focus on some of the main areas that are generating this waste by doing more detailed food waste assessments.
Remember, even if you have brown bins 15% of your mixed waste may be food waste. By using clear plastic bags for your mixed waste (this is good practice) it makes it easy to check these bags periodically to make sure there is not a lot of food waste going here and your waste management system is working correctly.
If you are paying for waste by lift instead of by weight make sure only the full bins are removed. On examining their brown bin bills, a Limerick City business noted that they were being charged for 8 lifts each week. This was the same for both busy and quiet weeks. The kitchen staff were informed and now only put out full bins for collection. This has resulted in annual savings of 19% on lift charges.
On getting new brown bins for the catering department, a small west of Ireland Hospital were shocked when they filled the 6 bins before the end of the first week. There was almost 0.7 tonnes of food waste being generated weekly. Based on the value that each tonne of food waste can cost between €2,000 and €4,000, the management realised that this could be costing them as much as €2,800 a week. They have since halved their weekly bills through food waste prevention measures.
During a recent general assessment at a Galway supermarket the food waste bins were examined visually. There was a lot of packaged food in them, many of which had passed their display by date but were still within the best before date.
Supermarkets are entitled to sell these goods (usually on deal) as long as customers are informed that the best before date has passed. While this may not be an option for some fruit and veg (e.g. lettuce) for the items shown here (apples, onions and potatoes), it is certainly a good idea. This will potentially bring in an income, reduce the waste bill and give a good deal to customers.


text:
Cut fiber-optic cable causes Summit County cell outage
Crime | October 9, 2015
Elise Reuter
ereuter@summitdaily.com
A cut fiber-optic cable knocked out cell coverage across Summit County for more than four hours Wednesday. The Colorado Department of Transportation and Comcast are working with contractors to determine the source of the problem.
Getty Images / iStockphoto | iStockphoto
Cell service went down for the fourth time since the beginning of September after a fiber-optic cable was cut by Colorado Department of Transportation contractors near Idaho Springs. The outage affected local schools, police and businesses for more than four hours as Comcast crews scrambled to repair the fiber.
The outage started around 1 p.m. on Wednesday, Oct. 7, and service was restored around 5:30 that evening.
"The good news in this particular situation is that we were able to repair the fiber more quickly," said Cindy Parsons, a spokeswoman for Comcast. "Our fiber crews work so diligently to make sure they can repair these cuts as quickly as possible."
The fiber-optic cable, which is owned by Comcast — but also provides service for carriers Verizon, Sprint and T-Mobile — affects coverage throughout the Interstate 70 mountain corridor. With no redundant cable paths to maintain service in the event of an outage, one cut cable can cause an outage across the county.
"Something else is going on; it wasn't just a one-time occurrence. We're trying to look at the books there. It definitely needs to be addressed immediately. … We're in the process of evaluating what the heck is going on there."Amy FordCDOT communications director
"It could be an increased level of activity at this time. Everyone's trying to get things done before the snow flies," Parsons said. "We are working closely with (CDOT) to ensure we avoid this happening again in the future."
Amy Ford, communications director with the Colorado Department of Transportation, said CDOT would work with the contractor to look into potential discrepancies in marked lines in plans and in the field to ensure no lines are cut in the future.
"Something else is going on; it wasn't just a one-time occurrence. We're trying to look at the books there," she said. "It definitely needs to be addressed immediately. … We're in the process of evaluating what the heck is going on there."
Crews are currently working to install an express lane on I-70 from Empire to Idaho Springs and are expected to finish the paving process over the next several weeks.
Several local businesses, telecommuters and on-call responders have been affected by the outages over the course of the last two months.
With the Summit County School District phone lines connected to Comcast, schools also reported a loss of service during three of the recent outages. While the school has an emergency line for backup, the service loss has left many parents frustrated.
"It's a concern while communications are down between parents and schools, schools and the community," said Julie McCluskie, director of communications and community engagement. "The first time, we just thought it was a one-time event."
The outages also affected on-call medical staff, who have moved from using pagers to cell phones to respond to emergencies.
"We're having to go back to that," said Carolyn Holland, a Certified Registered Nurse Anesthetist (CRNA) for St. Anthony Summit Medical Center.
"I got a landline because I didn't want to miss someone having emergency surgery or an epidural," she said, adding that she got the landline last month after having lived without one for 10 years. "It affects everybody's work. We can't be disconnected."
Distribution of cocaine to a minor charge dropped against Aspen family
Update: Service restored after fiber optic cut causes widespread phone outage, including 911
TreeTop Child Advocacy Center, which helps victims of abuse or sexual assault, set to expand staff after first anniversary
Suspects in Edwards bank robberies announced engagement before alleged crime binge
Breckenridge ski thief gets 4-year deferred judgement and sentence
Summit Daily News E-Edition


The playoffs are upon us, and since we had such a great year predicting a lot of stuff, both Chuck Booth and I are putting our postseason prognostications up.
In other blogs we will take a look at the series and everything, but this is just pure bets.
The Tigers were a lot of people's picks to represent the American League in the World Series this year before the season started. These guys were the preseason selections for yours truly and Chuck Booth as well. They have the playoff pedigree, with a balanced hitting attack, and have to cash in this opportunity while they still have Max Scherzer and Victor Martinez on the squad.
For the World Series, both Chuck and I have the matchup of the Dodgers versus the Tigers – and both ending in 7 games.
I have the Dodgers winning for the 1st time since 1988, and Chuck thinks Detroit finally brings home the title to Motown for the 1st time in 30 years.
A Big thanks goes out to our chief writer Hunter Stokes for preparing today's feature post.
Posted on September 30, 2014, in gambling 101, Playoffs and tagged 2001 world series, 2014 MLB Playoff predictions, 2014 world series, baltimore orioles, Chuck Booth, clayton kershaw, curt schilling, edinson volquez, hunter stokes, james shields, jon lester, kansas city royals, kauffman stadium, los angeles angels of anaheim, los angeles dodgers, max scherzer, oakland athletics, pittsburgh pirates, pnc park, randy johnson, san francisco giants, st louis cardinals, victor martinez, washington nationals, who will win the 2014 MLB World Series, zack greinke. Bookmark the permalink.	Leave a comment.


Police are investigating a burglary in Scunthorpe where a quantity of cash was taken.
Humberside Police said the incident happened on Exeter Road and was reported at 9.50pm last night (Thursday, February 8).
A spokeswoman for the force said: "A burglary at a property on Exeter Road in Scunthorpe was reported to us yesterday at 9.50pm.
"The resident reported that between 5.45pm and 9.30pm yesterday, the house was broken into and a quantity of cash taken after intruders undertook an untidy search of the property.
"Enquiries are ongoing and anyone with information is asked to call us on 101, quoting log number 506 of February 8.
"Calls can also be made anonymously to Crimestoppers on 0800 555 111."


text:
Florida Supreme Court approves ballot question on voting rights for ex-felons
Posted By Monivette Cordeiro on Fri, Apr 21, 2017 at 6:12 PM
Photo via Orange County Supervisor of Elections Office
The Florida Supreme Court approved language for a ballot question that could restore the voting rights of the majority of the state's 1.6 million ex-felons.
The Voting Restoration Amendment would restore the voting rights of convicted felons who've completed their sentences, including parole or probation. Felons who've been convicted of murder or sexual offenses would not be eligible for automatic restoration, but could appear before the governor and Cabinet to get their rights restored on a case-by-case basis. Currently, that clemency process can take years.
To get on the 2018 ballot, the amendment's supporters still need to gather 683,149 signatures from across the state. So far, they have 71,209 valid signatures.
A report from the Brennan Center for Justice last year says Florida's restrictive voting laws surrounding ex-felons can be traced back to Jim Crow laws. Out of the group of a million and a half people who lost the right to vote, about one-third are black, even though African Americans make up just 16 percent of the state's population.
"Florida's criminal disenfranchisement law is rooted in some of our country's most discriminatory voting practices, and it continues to have its intended effects today," the study's author said in a statement. "It is time for Florida to learn from the past and then leave it behind. The right to vote should not be used as a tool for lifetime punishment."
The movement in Florida to restore ex-felons' rights through the ballot is largely led by Desmond Meade, an Orlando resident who heads the Florida Rights Restoration Coalition. The American Civil Liberties Union of Florida is also helping in the effort.
"Now the work of gathering signatures and mounting a successful campaign to change the Florida Constitution begins in earnest," says Kirk Bailey, ACLU of Florida political director, in a statement. "We look forward to Florida voters being given a chance to bring our state's voting rules out of the 19th century and into the 21st."
Tags: florida felons voting, felons voting, voting rights, Image
« Florida ethics commission clears Pa… | Florida lawmakers move closer on so… »
You should sign this petition to restore the voting rights to 1.7 million Florida residents
Ben & Jerry's pushes petition to restore voting rights to ex-felons in Florida
Florida Supreme Court shields city marinas from property taxes
There's a petition to cancel Bill O'Reilly's upcoming Florida event
Class-action lawsuit challenges Florida on felon voting rights
Florida Supreme Court hears arguments on restoring ex-felons' voting rights
Report: Florida is 'radically out of step' in denying felons right to vote
Felons with outstanding court debts are to be removed from voting rolls, state elections officials announced Friday
Florida's landmark voting rights amendment was gutted by the Legislature
Ashley Moody wants the FDLE to investigate Bloomberg's $16M donation to the Florida Rights Restoration Coalition
Bonefish Grill Read More
Nha Trang Subs Read More
Mobile Mud Coffee & Espresso Read More


This is an all year, four season vacation rental. Great for all year family vacations - summer, fall, winter and spring swimming, fishing, hiking snowmobiling and skiing.
Boating: Two Kayaks and one Old Town canoe with gear with rental. There is a free public boat launch at Wellington State Beach about 2 miles away and West Shore Marine with boats for rent. Kayaks can be rented also from Shacketts Market.
Hiking: Hike the Elwell trail from Wellington State Park with easy hiking to the summits of Mt Sugarloaf and Little Sugarloaf with great views of the lake. Also hike an easy flat nature trail around the Wellington State Park peninsula following the water's edge. around the peninsula with great views of the lake, cliffs, and islands.
Fishing: New Hampshire fishing license required. Newfound Lake is one of the best known Landlocked Salmon and Lake Trout lakes in NH. The lake was first stocked with Landlocked Salmon in 1866. The water is well known for its exceptionally large Lake Trout, and the record Lake Trout for the state of NH was caught here. The waters are crystal clear and run up to 183 Ft. in the center. Best fishing for Lake Trout and Landlocked Salmon is in the upper section of the Lake. The 20 mile shoreline of this natural body of water is moderately developed; the bottom is rocky and sandy.
Winter offers ice fishing, snowmobiling, hiking, snow shoeing, cross country skiing and downhill skiing or tubing. Ragged Mountain Ski Resort is only 18 miles away. Ski resorts Loon, Waterville Valley and Cannon Mountain are all good day trips and most ski resorts also offer summer activities such as hiking and zip lines and special events like beer fests.
Snowmobiling is a great option in and around Newfound Lake. There are many snowmobile clubs that maintain an extensive network of trails that interconnect in regions around the entire lake.
603-744-9111 directly across lake on Rt 3A.


Hallmark Channel is in negotiations with Marie Osmond to launch a show on its daytime lineup, a network spokesperson confirmed Thursday.
The channel said Osmond would host a lifestyle show, though it had no further details about the proposed format. She currently appears in a Las Vegas variety show with her brother Donny Osmond.
The news follows reports earlier this month that Hallmark would not renew daytime talker The Martha Stewart Show, with new episodes set to end in May and repeats running through the summer. That would leave a spot for Osmond's show to join the lineup in the fall, though the network spokesperson said the fall schedule has not yet been determined.
Hallmark also said it is still in discussions with Martha Stewart Living Omnimedia about the company's other programs on the channel, Emeril's Table and Mad Hungry with Lucinda Scala Quinn.
The Martha Stewart Show debuted on Hallmark in September 2010 after years in broadcast syndication. The show's production expense and middling ratings were said to be a factor in the network's decision to seek other daytime programming options.


Come join us for a presentation on music4wellness by Diana Le, music therapist with University Hospitals and Case Medical Center, on Thursday, March 21, from 6:30-7:30 p.m. in the LSC Conference Room. Learn how music can improve your mood and enjoy some free food!
Check out the new #Music4Wellness song playlists on the University Counseling Center website: www.jcu.edu/counseling. Submit a song suggestion and be entered into a drawing to win an iTunes giftcard!


I recently congratulated myself on my very first Guerlain purchase. The brand creates so many crazy-tempting products but the one that got me to just go for it was the Maxi Velvet Matte Liquid Lipstick ($30.00) from the Winter Fairytale Collection called Fleur de Givre (M72). It first came to my attention from Temptalia (as most things do) and I went on the hunt for it!
I do love the sleek packaging, although it is shorter than I imagined it would be. However, there is 0.25 oz. of product in there; pretty standard, actually.
It's got a hint of vanilla smell to it and although the texture reminds me a bit of Rimmel's Provocalips, Guerlain's Maxi Velvet performs much better. It glides on semi-opaque and is pretty smooth. The color is a very dainty, soft, cotton candy-like pink and shows up on me as such. Generally shades like this tend to wash me out a bit but this one seemed to just sink into my skin tone and was quite agreeable. I initially thought I preferred two coats of this for that full-coverage look, but one coat is certainly enough, even with my skin tone. By one coat, I mean I don't need to stick the applicator back in the product again to get more; there's more than enough on the wand when it comes out.
I should also mention that this liquid lipstick is sooo moisturizing (even more so than Stila's Stay All Day range). This was surprising to me since a lot of them can be drying, especially if you don't apply lip balm beforehand. I used this with and without lip balm and I still found it moisturizing to my often-dry lips. I shot indoors and outdoors to give you all an idea of how it looks on my skin tone.
Lastly, the lipstick is transferable, so it will transfer to your coffee cup. Amazingly, I didn't feel the strong need to reapply after eating as the color was still pretty vibrant. And when I did reapply, going right over the old was effortless.
The entire Winter Fairytale collection was quite beautiful. Very happy with my first Guerlain purchase!
P.S. I ordered this from Bloomingdales bit it's out of stock now. Looks like it's on backorder via Nordstrom.


A mainstay in our lineup and a timeless staple for your wardrobe. The Thalia Bra is constructed with premium 4-way stretch fabric that makes it buttery smooth to the touch. Comes with high-quality removable cups that don't flip in the wash. Perfect for yoga and spin.
Model is 165 cm. Wearing size S.


text:
Public affairs toolkit on critical policy issues for gene therapies
Given the increasing number of government and stakeholder debates and forums around gene therapies, a global pharmaceutical company asked CRA to set out the critical policy areas that impact gene therapies and develop descriptions and positions for each topic.
CRA Approach
A public affairs toolkit intended to guide cross-functional colleagues in understanding and responding to the current debate on the policy framework for gene therapies
Additional context and guidance, with supporting evidence from case studies and country archetypes
Life Sciences Policy
Tim Wilsdon
+44-20-7664-3707 twilsdon@crai.co.uk
Artes Haderi
+44-20-7664-3743 ahaderi@crai.co.uk
Inequities in patient support, well-being, and treatment access for triple negative breast cancer patients
In this report, Charles River Associates (CRA) and Cancer Support Community (CSC) examine the extent to which policies associated with long-term support,...
Strengthening regulatory systems in low- and middle-income countries: Improving the sustainability of the vaccine innovation ecosystem in Africa
This report explores the challenges in regulating the development, production, safety monitoring and approval of vaccines for use across Africa. The absence of...
Explore our thinking