tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/irremote/receiver.go (about)

     1  package irremote // import "tinygo.org/x/drivers/irremote"
     2  
     3  import (
     4  	"machine"
     5  	"time"
     6  )
     7  
     8  // NEC protocol references
     9  // https://www.sbprojects.net/knowledge/ir/nec.php
    10  // https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol
    11  // https://simple-circuit.com/arduino-nec-remote-control-decoder/
    12  
    13  // Data encapsulates the data received by the ReceiverDevice.
    14  type Data struct {
    15  	// Code is the raw IR data received.
    16  	Code uint32
    17  	// Address is the decoded address from the IR data received.
    18  	Address uint16
    19  	// Command is the decoded command from the IR data recieved
    20  	Command uint16
    21  	// Flags provides additional information about the IR data received. See DataFlags
    22  	Flags DataFlags
    23  }
    24  
    25  // DataFlags provides bitwise flags representing various information about recieved IR data.
    26  type DataFlags uint16
    27  
    28  // Valid values for DataFlags
    29  const (
    30  	// DataFlagIsRepeat set indicates that the IR data is a repeat commmand
    31  	DataFlagIsRepeat DataFlags = 1 << iota
    32  )
    33  
    34  // CommandHandler defines the callback function used to provide IR data received by the ReceiverDevice.
    35  type CommandHandler func(data Data)
    36  
    37  // nec_ir_state represents the various internal states used to decode the NEC IR protocol commands
    38  type nec_ir_state uint8
    39  
    40  // Valid values for nec_ir_state
    41  const (
    42  	lead_pulse_start nec_ir_state = iota // Start receiving IR data, beginning of 9ms lead pulse
    43  	lead_space_start                     // End of 9ms lead pulse, start of 4.5ms space
    44  	lead_space_end                       // End of 4.5ms space, start of 562µs pulse
    45  	bit_read_start                       // End of 562µs pulse, start of 562µs or 1687µs space
    46  	bit_read_end                         // End of 562µs or 1687µs space
    47  	trail_pulse_end                      // End of 562µs trailing pulse
    48  )
    49  
    50  // ReceiverDevice is the device for receiving IR commands
    51  type ReceiverDevice struct {
    52  	pin      machine.Pin    // IR input pin.
    53  	ch       CommandHandler // client callback function
    54  	necState nec_ir_state   // internal state machine
    55  	data     Data           // decoded data for client
    56  	lastTime time.Time      // used to track states
    57  	bitIndex int            // tracks which bit (0-31) of necCode is being read
    58  }
    59  
    60  // NewReceiver returns a new IR receiver device
    61  func NewReceiver(pin machine.Pin) ReceiverDevice {
    62  	return ReceiverDevice{pin: pin}
    63  }
    64  
    65  // Configure configures the input pin for the IR receiver device
    66  func (ir *ReceiverDevice) Configure() {
    67  	// The IR receiver sends logic HIGH when NOT receiving IR, and logic LOW when receiving IR
    68  	ir.pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
    69  }
    70  
    71  // SetCommandHandler is used to start or stop receiving IR commands via a callback function (pass nil to stop)
    72  func (ir *ReceiverDevice) SetCommandHandler(ch CommandHandler) {
    73  	ir.ch = ch
    74  	ir.resetStateMachine()
    75  	if ch != nil {
    76  		// Start monitoring IR output pin for changes
    77  		ir.pin.SetInterrupt(machine.PinFalling|machine.PinRising, ir.pinChange)
    78  	} else {
    79  		// Stop monitoring IR output pin for changes
    80  		ir.pin.SetInterrupt(0, nil)
    81  	}
    82  }
    83  
    84  // Internal helper function to reset state machine on protocol failure
    85  func (ir *ReceiverDevice) resetStateMachine() {
    86  	ir.data = Data{}
    87  	ir.bitIndex = 0
    88  	ir.necState = lead_pulse_start
    89  }
    90  
    91  // Internal pin rising/falling edge interrupt handler
    92  func (ir *ReceiverDevice) pinChange(pin machine.Pin) {
    93  	/* Currently TinyGo is sending machine.NoPin (0xff) for all pins, at least on RP2040
    94  	if pin != ir.pin {
    95  		return // This is not the pin you're looking for
    96  	}
    97  	*/
    98  	now := time.Now()
    99  	duration := now.Sub(ir.lastTime)
   100  	ir.lastTime = now
   101  	switch ir.necState {
   102  	case lead_pulse_start:
   103  		if !ir.pin.Get() {
   104  			// IR is 'on' (pin is pulled high and sent low when IR is received)
   105  			ir.necState = lead_space_start // move to next state
   106  		}
   107  	case lead_space_start:
   108  		if duration > time.Microsecond*9500 || duration < time.Microsecond*8500 {
   109  			// Invalid interval for 9ms lead pulse. Reset
   110  			ir.resetStateMachine()
   111  		} else {
   112  			// 9ms lead pulse detected, move to next state
   113  			ir.necState = lead_space_end
   114  		}
   115  	case lead_space_end:
   116  		if duration > time.Microsecond*5000 || duration < time.Microsecond*1750 {
   117  			// Invalid interval for 4.5ms lead space OR 2.25ms repeat space. Reset
   118  			ir.resetStateMachine()
   119  		} else {
   120  			// 4.5ms lead space OR 2.25ms repeat space detected
   121  			if duration > time.Microsecond*3000 {
   122  				// 4.5ms lead space detected, new code incoming, move to next state
   123  				ir.resetStateMachine()
   124  				ir.necState = bit_read_start
   125  			} else {
   126  				// 2.25ms repeat space detected.
   127  				if ir.data.Code != 0 {
   128  					// Valid repeat code. Invoke client callback with repeat flag set
   129  					ir.data.Flags |= DataFlagIsRepeat
   130  					if ir.ch != nil {
   131  						ir.ch(ir.data)
   132  					}
   133  					ir.necState = lead_pulse_start
   134  				} else {
   135  					// ir.data is not in a valid state for a repeat. Reset
   136  					ir.resetStateMachine()
   137  				}
   138  			}
   139  		}
   140  	case bit_read_start:
   141  		if duration > time.Microsecond*700 || duration < time.Microsecond*400 {
   142  			// Invalid interval for 562.5µs pulse. Reset
   143  			ir.resetStateMachine()
   144  		} else {
   145  			// 562.5µs pulse detected, move to next state
   146  			ir.necState = bit_read_end
   147  		}
   148  	case bit_read_end:
   149  		if duration > time.Microsecond*1800 || duration < time.Microsecond*400 {
   150  			// Invalid interval for 562.5µs space OR 1687.5µs space. Reset
   151  			ir.resetStateMachine()
   152  		} else {
   153  			// 562.5µs OR 1687.5µs space detected
   154  			mask := uint32(1 << ir.bitIndex)
   155  			if duration > time.Microsecond*1000 {
   156  				// 1687.5µs space detected (logic 1) - Set bit
   157  				ir.data.Code |= mask
   158  			} else {
   159  				// 562.5µs space detected (logic 0) - Clear bit
   160  				ir.data.Code &^= mask
   161  			}
   162  
   163  			ir.bitIndex++
   164  			if ir.bitIndex > 31 {
   165  				// We've read all bits for this code, move to next state
   166  				ir.necState = trail_pulse_end
   167  			} else {
   168  				// Read next bit
   169  				ir.necState = bit_read_start
   170  			}
   171  		}
   172  	case trail_pulse_end:
   173  		if duration > time.Microsecond*700 || duration < time.Microsecond*400 {
   174  			// Invalid interval for trailing 562.5µs pulse. Reset
   175  			ir.resetStateMachine()
   176  		} else {
   177  			// 562.5µs trailing pulse detected. Decode & validate data
   178  			err := ir.decode()
   179  			if err == irDecodeErrorNone {
   180  				// Valid data, invoke client callback
   181  				if ir.ch != nil {
   182  					ir.ch(ir.data)
   183  				}
   184  				// around we go again. Note: we don't resetStateMachine() since repeat codes are now possible
   185  				ir.necState = lead_pulse_start
   186  			} else {
   187  				ir.resetStateMachine()
   188  			}
   189  		}
   190  	}
   191  }
   192  
   193  // Error type for NEC format decoding
   194  type irDecodeError int
   195  
   196  // Valid values for irDecodeError
   197  const (
   198  	irDecodeErrorNone             irDecodeError = iota // no error occurred
   199  	irDecodeErrorInverseCheckFail                      // validation of inverse cmd does not match cmd
   200  )
   201  
   202  func (ir *ReceiverDevice) decode() irDecodeError {
   203  	// Decode cmd and inverse cmd and perform validation check
   204  	cmd := uint8((ir.data.Code & 0x00ff0000) >> 16)
   205  	invCmd := uint8((ir.data.Code & 0xff000000) >> 24)
   206  	if cmd != ^invCmd {
   207  		// Validation failure. cmd and inverse cmd do not match
   208  		return irDecodeErrorInverseCheckFail
   209  	}
   210  	// cmd validation pass, decode address
   211  	ir.data.Command = uint16(cmd)
   212  	addrLow := uint8(ir.data.Code & 0xff)
   213  	addrHigh := uint8((ir.data.Code & 0xff00) >> 8)
   214  	if addrHigh == ^addrLow {
   215  		// addrHigh is inverse of addrLow. This is not a valid 16-bit address in extended NEC coding
   216  		// since it is indistinguishable from 8-bit address with inverse validation. Use the 8-bit address
   217  		ir.data.Address = uint16(addrLow)
   218  	} else {
   219  		// 16-bit extended NEC address
   220  		ir.data.Address = (uint16(addrHigh) << 8) | uint16(addrLow)
   221  	}
   222  	// Clear repeat flag
   223  	ir.data.Flags &^= DataFlagIsRepeat
   224  	return irDecodeErrorNone
   225  }