1 | const Board = require("./board");
|
2 | const Emitter = require("events");
|
3 | const { constrain, fscale } = require("./fn");
|
4 | const priv = new Map();
|
5 | const axes = ["x", "y"];
|
6 |
|
7 | class Multiplexer {
|
8 | constructor({pins, io}) {
|
9 | this.pins = pins;
|
10 | this.io = io;
|
11 |
|
12 |
|
13 | this.io.pinMode(this.pins[0], this.io.MODES.OUTPUT);
|
14 | this.io.pinMode(this.pins[1], this.io.MODES.OUTPUT);
|
15 | this.io.pinMode(this.pins[2], this.io.MODES.OUTPUT);
|
16 | this.io.pinMode(this.pins[3], this.io.MODES.OUTPUT);
|
17 | }
|
18 |
|
19 | select(channel) {
|
20 | this.io.digitalWrite(this.pins[0], channel & 1 ? this.io.HIGH : this.io.LOW);
|
21 | this.io.digitalWrite(this.pins[1], channel & 2 ? this.io.HIGH : this.io.LOW);
|
22 | this.io.digitalWrite(this.pins[2], channel & 4 ? this.io.HIGH : this.io.LOW);
|
23 | this.io.digitalWrite(this.pins[3], channel & 8 ? this.io.HIGH : this.io.LOW);
|
24 | }
|
25 | }
|
26 |
|
27 | const Controllers = {
|
28 | ANALOG: {
|
29 | initialize: {
|
30 | value({pins}, callback) {
|
31 | const axisValues = {
|
32 | x: null,
|
33 | y: null
|
34 | };
|
35 |
|
36 | pins.forEach((pin, index) => {
|
37 | this.io.pinMode(pin, this.io.MODES.ANALOG);
|
38 | this.io.analogRead(pin, value => {
|
39 | axisValues[axes[index]] = value;
|
40 |
|
41 | if (axisValues.x !== null && axisValues.y !== null) {
|
42 | callback({
|
43 | x: axisValues.x,
|
44 | y: axisValues.y
|
45 | });
|
46 |
|
47 | axisValues.x = null;
|
48 | axisValues.y = null;
|
49 | }
|
50 | });
|
51 | });
|
52 | }
|
53 | },
|
54 | toAxis: {
|
55 | value(raw, axis) {
|
56 | const state = priv.get(this);
|
57 | return constrain(fscale(raw - state[axis].zeroV, -511, 511, -1, 1), -1, 1);
|
58 | }
|
59 | }
|
60 | },
|
61 | ESPLORA: {
|
62 | initialize: {
|
63 | value(options, callback) {
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | const multiplexer = new Multiplexer({
|
70 |
|
71 |
|
72 |
|
73 | pins: [18, 19, 20, 21],
|
74 | io: this.io
|
75 | });
|
76 | const channels = [11, 12];
|
77 | let index = 1;
|
78 | const axisValues = {
|
79 | x: null,
|
80 | y: null
|
81 | };
|
82 |
|
83 | this.io.pinMode(4, this.io.MODES.ANALOG);
|
84 |
|
85 | const handler = value => {
|
86 | axisValues[axes[index]] = value;
|
87 |
|
88 | if (axisValues.x !== null && axisValues.y !== null) {
|
89 | callback({
|
90 | x: axisValues.x,
|
91 | y: axisValues.y
|
92 | });
|
93 |
|
94 | axisValues.x = null;
|
95 | axisValues.y = null;
|
96 | }
|
97 |
|
98 |
|
99 |
|
100 | this.io.removeListener("analog-read-4", handler);
|
101 |
|
102 | setTimeout(read, 10);
|
103 | };
|
104 |
|
105 | var read = () => {
|
106 | multiplexer.select(channels[index ^= 1]);
|
107 | this.io.analogRead(4, handler);
|
108 | };
|
109 |
|
110 | read();
|
111 | }
|
112 | },
|
113 | toAxis: {
|
114 | value(raw, axis) {
|
115 | const state = priv.get(this);
|
116 | return constrain(fscale(raw - state[axis].zeroV, -511, 511, -1, 1), -1, 1);
|
117 | }
|
118 | }
|
119 | }
|
120 | };
|
121 |
|
122 | Controllers.DEFAULT = Controllers.ANALOG;
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | class Joystick extends Emitter {
|
140 | constructor(options) {
|
141 | super();
|
142 |
|
143 | Board.Component.call(
|
144 | this, options = Board.Options(options)
|
145 | );
|
146 |
|
147 | Board.Controller.call(this, Controllers, options);
|
148 |
|
149 | if (!this.toAxis) {
|
150 | this.toAxis = options.toAxis || (raw => raw);
|
151 | }
|
152 |
|
153 | const state = {
|
154 | x: {
|
155 | invert: false,
|
156 | value: 0,
|
157 | previous: 0,
|
158 | zeroV: 0,
|
159 | calibrated: false
|
160 | },
|
161 | y: {
|
162 | invert: false,
|
163 | value: 0,
|
164 | previous: 0,
|
165 | zeroV: 0,
|
166 | calibrated: false
|
167 | }
|
168 | };
|
169 |
|
170 | state.x.zeroV = options.zeroV === undefined ? 0 : (options.zeroV.x || 0);
|
171 | state.y.zeroV = options.zeroV === undefined ? 0 : (options.zeroV.y || 0);
|
172 |
|
173 | state.x.invert = options.invertX || options.invert || false;
|
174 | state.y.invert = options.invertY || options.invert || false;
|
175 |
|
176 | priv.set(this, state);
|
177 |
|
178 | if (typeof this.initialize === "function") {
|
179 | this.initialize(options, data => {
|
180 | let isChange = false;
|
181 | const computed = {
|
182 | x: null,
|
183 | y: null
|
184 | };
|
185 |
|
186 | Object.keys(data).forEach(axis => {
|
187 | const value = data[axis];
|
188 | const sensor = state[axis];
|
189 |
|
190 |
|
191 | sensor.value = value;
|
192 |
|
193 | if (!state[axis].calibrated) {
|
194 | state[axis].calibrated = true;
|
195 | state[axis].zeroV = value;
|
196 | isChange = true;
|
197 | }
|
198 |
|
199 |
|
200 | computed[axis] = this[axis];
|
201 |
|
202 | const absAxis = Math.abs(computed[axis]);
|
203 | const absPAxis = Math.abs(sensor.previous);
|
204 |
|
205 | if ((absAxis < absPAxis) ||
|
206 | (absAxis > absPAxis)) {
|
207 | isChange = true;
|
208 | }
|
209 |
|
210 | sensor.previous = computed[axis];
|
211 | });
|
212 |
|
213 | this.emit("data", {
|
214 | x: computed.x,
|
215 | y: computed.y
|
216 | });
|
217 |
|
218 | if (isChange) {
|
219 | this.emit("change", {
|
220 | x: computed.x,
|
221 | y: computed.y
|
222 | });
|
223 | }
|
224 | });
|
225 | }
|
226 |
|
227 | Object.defineProperties(this, {
|
228 | x: {
|
229 | get() {
|
230 | return this.toAxis(state.x.value, "x") * (state.x.invert ? -1 : 1);
|
231 | }
|
232 | },
|
233 | y: {
|
234 | get() {
|
235 | return this.toAxis(state.y.value, "y") * (state.y.invert ? -1 : 1);
|
236 | }
|
237 | }
|
238 | });
|
239 | }
|
240 | }
|
241 |
|
242 |
|
243 | if (!!process.env.IS_TEST_MODE) {
|
244 | Joystick.Controllers = Controllers;
|
245 | Joystick.purge = () => {
|
246 | priv.clear();
|
247 | };
|
248 | }
|
249 |
|
250 | module.exports = Joystick;
|