mirror of https://github.com/periph/devices
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
5.9 KiB
Go
280 lines
5.9 KiB
Go
// Copyright 2016 The Periph Authors. All rights reserved.
|
|
// Use of this source code is governed under the Apache License, Version 2.0
|
|
// that can be found in the LICENSE file.
|
|
|
|
package lirc
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"periph.io/x/periph/conn"
|
|
"periph.io/x/periph/conn/gpio/gpioreg"
|
|
"periph.io/x/periph/conn/ir"
|
|
)
|
|
|
|
// New returns a IR receiver / emitter handle.
|
|
func New() (*Conn, error) {
|
|
w, err := net.Dial("unix", "/var/run/lirc/lircd")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c := &Conn{w: w, c: make(chan ir.Message), list: map[string][]string{}}
|
|
// Unconditionally retrieve the list of all known keys at start.
|
|
if _, err := w.Write([]byte("LIST\n")); err != nil {
|
|
_ = w.Close()
|
|
return nil, err
|
|
}
|
|
go c.loop(bufio.NewReader(w))
|
|
return c, nil
|
|
}
|
|
|
|
// Conn is an open port to lirc.
|
|
type Conn struct {
|
|
w net.Conn
|
|
c chan ir.Message
|
|
|
|
mu sync.Mutex
|
|
list map[string][]string // list of remotes and associated keys
|
|
pendingList map[string][]string // list of remotes and associated keys being created.
|
|
}
|
|
|
|
// String implements conn.Resource.
|
|
func (c *Conn) String() string {
|
|
return "lirc"
|
|
}
|
|
|
|
// Halt implements conn.Resource.
|
|
//
|
|
// It has no effect.
|
|
func (c *Conn) Halt() error {
|
|
return nil
|
|
}
|
|
|
|
// Close closes the socket to lirc. It is not a requirement to close before
|
|
// process termination.
|
|
func (c *Conn) Close() error {
|
|
return c.w.Close()
|
|
}
|
|
|
|
// Emit implements ir.IR.
|
|
func (c *Conn) Emit(remote string, key ir.Key) error {
|
|
// http://www.lirc.org/html/lircd.html#lbAH
|
|
_, err := fmt.Fprintf(c.w, "SEND_ONCE %s %s", remote, key)
|
|
return err
|
|
}
|
|
|
|
// Channel implements ir.IR.
|
|
func (c *Conn) Channel() <-chan ir.Message {
|
|
return c.c
|
|
}
|
|
|
|
// Codes returns all the known codes.
|
|
//
|
|
// Empty if the list was not retrieved yet.
|
|
func (c *Conn) Codes() map[string][]string {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
return c.list
|
|
}
|
|
|
|
//
|
|
|
|
func (c *Conn) loop(r *bufio.Reader) {
|
|
defer func() {
|
|
close(c.c)
|
|
c.c = nil
|
|
}()
|
|
for {
|
|
line, err := read(r)
|
|
if line == "BEGIN" {
|
|
err = c.readData(r)
|
|
} else if len(line) != 0 {
|
|
// Format is: <code> <repeat count> <button name> <remote control name>
|
|
// http://www.lirc.org/html/lircd.html#lbAG
|
|
if parts := strings.SplitN(line, " ", 5); len(parts) != 4 {
|
|
log.Printf("ir: corrupted line: %v", line)
|
|
} else {
|
|
if i, err2 := strconv.Atoi(parts[1]); err2 != nil {
|
|
log.Printf("ir: corrupted line: %v", line)
|
|
} else if len(parts[2]) != 0 && len(parts[3]) != 0 {
|
|
c.c <- ir.Message{Key: ir.Key(parts[2]), RemoteType: parts[3], Repeat: i != 0}
|
|
}
|
|
}
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Conn) readData(r *bufio.Reader) error {
|
|
// Format is:
|
|
// BEGIN
|
|
// <original command>
|
|
// SUCCESS
|
|
// DATA
|
|
// <number of entries 1 based>
|
|
// <entries>
|
|
// ...
|
|
// END
|
|
cmd, err := read(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch cmd {
|
|
case "SIGHUP":
|
|
_, err = c.w.Write([]byte("LIST\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
// In case of any error, ignore the rest.
|
|
line, err := read(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if line != "SUCCESS" {
|
|
log.Printf("ir: unexpected line: %v, expected SUCCESS", line)
|
|
return nil
|
|
}
|
|
if line, err = read(r); err != nil {
|
|
return err
|
|
}
|
|
if line != "DATA" {
|
|
log.Printf("ir: unexpected line: %v, expected DATA", line)
|
|
return nil
|
|
}
|
|
if line, err = read(r); err != nil {
|
|
return err
|
|
}
|
|
nbLines, err := strconv.Atoi(line)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
list := make([]string, nbLines)
|
|
for i := 0; i < nbLines; i++ {
|
|
if list[i], err = read(r); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
switch {
|
|
case cmd == "LIST":
|
|
// Request the codes for each remote.
|
|
c.pendingList = map[string][]string{}
|
|
for _, l := range list {
|
|
if _, ok := c.pendingList[l]; ok {
|
|
log.Printf("ir: unexpected %s", cmd)
|
|
} else {
|
|
c.pendingList[l] = []string{}
|
|
if _, err = fmt.Fprintf(c.w, "LIST %s\n", l); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
case strings.HasPrefix(line, "LIST "):
|
|
if c.pendingList == nil {
|
|
log.Printf("ir: unexpected %s", cmd)
|
|
} else {
|
|
remote := cmd[5:]
|
|
c.pendingList[remote] = list
|
|
all := true
|
|
for _, v := range c.pendingList {
|
|
if len(v) == 0 {
|
|
all = false
|
|
break
|
|
}
|
|
}
|
|
if all {
|
|
c.mu.Lock()
|
|
c.list = c.pendingList
|
|
c.pendingList = nil
|
|
c.mu.Unlock()
|
|
}
|
|
}
|
|
default:
|
|
}
|
|
}
|
|
line, err := read(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if line != "END" {
|
|
log.Printf("ir: unexpected line: %v, expected END", line)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func read(r *bufio.Reader) (string, error) {
|
|
raw, err := r.ReadBytes('\n')
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(raw) != 0 {
|
|
raw = raw[:len(raw)-1]
|
|
}
|
|
return string(raw), nil
|
|
}
|
|
|
|
// driver implements periph.Driver.
|
|
type driver struct {
|
|
}
|
|
|
|
func (d *driver) String() string {
|
|
return "lirc"
|
|
}
|
|
|
|
func (d *driver) Init() (bool, error) {
|
|
in, out := getPins()
|
|
if in == -1 && out == -1 {
|
|
return false, nil
|
|
}
|
|
if in != -1 {
|
|
if err := gpioreg.RegisterAlias("IR_IN", strconv.Itoa(in)); err != nil {
|
|
return true, err
|
|
}
|
|
}
|
|
if out != -1 {
|
|
if err := gpioreg.RegisterAlias("IR_OUT", strconv.Itoa(out)); err != nil {
|
|
return true, err
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// getPins queries the kernel module to determine which GPIO pins are taken by
|
|
// the driver.
|
|
//
|
|
// The return values can be converted to bcm238x.Pin. Return (-1, -1) on
|
|
// failure.
|
|
func getPins() (int, int) {
|
|
// This is configured in /boot/config.txt as:
|
|
// dtoverlay=gpio_in_pin=23,gpio_out_pin=22
|
|
bytes, err := ioutil.ReadFile("/sys/module/lirc_rpi/parameters/gpio_in_pin")
|
|
if err != nil || len(bytes) == 0 {
|
|
return -1, -1
|
|
}
|
|
in, err := strconv.Atoi(strings.TrimRight(string(bytes), "\n"))
|
|
if err != nil {
|
|
return -1, -1
|
|
}
|
|
bytes, err = ioutil.ReadFile("/sys/module/lirc_rpi/parameters/gpio_out_pin")
|
|
if err != nil || len(bytes) == 0 {
|
|
return -1, -1
|
|
}
|
|
out, err := strconv.Atoi(strings.TrimRight(string(bytes), "\n"))
|
|
if err != nil {
|
|
return -1, -1
|
|
}
|
|
return in, out
|
|
}
|
|
|
|
var _ ir.Conn = &Conn{}
|
|
var _ conn.Resource = &Conn{}
|