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