Support for remote control buttons (#300)

* refactor beacon function inside IR sensor

* towards sim support

* channel labels

* reverting to singletons

* hiding unused apis

* lazy allocation of button instances

* tracking button state

* hook up the state
This commit is contained in:
Peli de Halleux 2018-02-02 09:48:27 -08:00 committed by GitHub
parent f36e14fe69
commit ba47fb0589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 198 additions and 100 deletions

View File

@ -1,23 +1,31 @@
const enum IrSensorMode { const enum InfraredSensorMode {
None = -1, None = -1,
Proximity = 0, Proximity = 0,
Seek = 1, Seek = 1,
RemoteControl = 2, RemoteControl = 2,
} }
const enum IrRemoteChannel { const enum InfraredRemoteChannel {
//% block="channel 0"
Ch0 = 0, // top Ch0 = 0, // top
//% block="channel 1"
Ch1 = 1, Ch1 = 1,
//% block="channel 2"
Ch2 = 2, Ch2 = 2,
//% block="channel 3"
Ch3 = 3, Ch3 = 3,
} }
const enum IrRemoteButton { const enum InfraredRemoteButton {
None = 0x00, //% block="center beacon"
CenterBeacon = 0x01, CenterBeacon = 0x01,
//% block="top left"
TopLeft = 0x02, TopLeft = 0x02,
//% block="bottom left"
BottomLeft = 0x04, BottomLeft = 0x04,
//% block="top right"
TopRight = 0x08, TopRight = 0x08,
//% block="bottom right"
BottomRight = 0x10, BottomRight = 0x10,
} }
@ -31,56 +39,37 @@ const enum InfraredSensorEvent {
namespace sensors { namespace sensors {
function mapButton(v: number) { function mapButton(v: number) {
switch (v) { switch (v) {
case 0: return IrRemoteButton.None case 1: return InfraredRemoteButton.TopLeft
case 1: return IrRemoteButton.TopLeft case 2: return InfraredRemoteButton.BottomLeft
case 2: return IrRemoteButton.BottomLeft case 3: return InfraredRemoteButton.TopRight
case 3: return IrRemoteButton.TopRight case 4: return InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight
case 4: return IrRemoteButton.TopRight | IrRemoteButton.BottomRight case 5: return InfraredRemoteButton.TopLeft | InfraredRemoteButton.TopRight
case 5: return IrRemoteButton.TopLeft | IrRemoteButton.TopRight case 6: return InfraredRemoteButton.TopLeft | InfraredRemoteButton.BottomRight
case 6: return IrRemoteButton.TopLeft | IrRemoteButton.BottomRight case 7: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopRight
case 7: return IrRemoteButton.BottomLeft | IrRemoteButton.TopRight case 8: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.BottomRight
case 8: return IrRemoteButton.BottomLeft | IrRemoteButton.BottomRight case 9: return InfraredRemoteButton.CenterBeacon
case 9: return IrRemoteButton.CenterBeacon case 10: return InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopLeft
case 10: return IrRemoteButton.BottomLeft | IrRemoteButton.TopLeft case 11: return InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight
case 11: return IrRemoteButton.TopRight | IrRemoteButton.BottomRight default: return 0;
default: return IrRemoteButton.None
} }
} }
let buttons: RemoteInfraredBeaconButton[] const __remoteButtons: RemoteInfraredBeaconButton[] = [];
function __irButton(id: InfraredRemoteButton): RemoteInfraredBeaconButton {
function create(ir: InfraredSensor) { for(let i = 0; i < __remoteButtons.length; ++i) {
// it's created by referencing it if (__remoteButtons[i].position == id)
} return __remoteButtons[i];
export function irButton(id: IrRemoteButton): RemoteInfraredBeaconButton {
if (buttons == null) {
buttons = []
for (let i = 0; i < 5; ++i) {
buttons.push(new RemoteInfraredBeaconButton(new brick.Button()))
}
// this defeats our static allocation system
// make sure sensors are up
//create(infraredSensor1)
//create(infraredSensor2)
//create(infraredSensor3)
//create(infraredSensor4)
} }
const btn = new RemoteInfraredBeaconButton(id, new brick.Button());
let num = -1 __remoteButtons.push(btn);
while (id) { return btn;
id >>= 1;
num++;
}
num = Math.clamp(0, buttons.length - 1, num)
return buttons[num]
} }
//% fixedInstances //% fixedInstances
export class RemoteInfraredBeaconButton extends control.Component { export class RemoteInfraredBeaconButton extends control.Component {
position: InfraredRemoteButton;
private button: brick.Button; private button: brick.Button;
constructor(button: brick.Button) { constructor(position: InfraredRemoteButton, button: brick.Button) {
super(); super();
this.button = button; this.button = button;
} }
@ -134,40 +123,51 @@ namespace sensors {
onEvent(ev: ButtonEvent, body: () => void) { onEvent(ev: ButtonEvent, body: () => void) {
this.button.onEvent(ev, body); this.button.onEvent(ev, body);
} }
/**
* Pauses until the given event is raised
* @param ev the event to wait for
*/
//% help=input/remote-infrared-beacon/pause-until
//% blockId=remotebuttonEvent block="pause until %button|%event"
//% parts="remote"
//% blockNamespace=sensors
//% weight=99 blockGap=8
//% group="Remote Infrared Beacon"
pauseUntil(ev: ButtonEvent) {
this.button.pauseUntil(ev);
}
} }
//% fixedInstances //% fixedInstances
export class InfraredSensor extends internal.UartSensor { export class InfraredSensor extends internal.UartSensor {
private channel: IrRemoteChannel; private _channel: InfraredRemoteChannel;
private proximityThreshold: sensors.ThresholdDetector; private _proximityThreshold: sensors.ThresholdDetector;
constructor(port: number) { constructor(port: number) {
super(port) super(port)
this.channel = IrRemoteChannel.Ch0 this._channel = InfraredRemoteChannel.Ch0
this.proximityThreshold = new sensors.ThresholdDetector(this._id, 0, 100, 10, 90); this._proximityThreshold = new sensors.ThresholdDetector(this._id, 0, 100, 10, 90);
irButton(0) // make sure buttons array is initalized this.setMode(InfraredSensorMode.Proximity);
// and set the mode, as otherwise button events won't work
this.mode = IrSensorMode.RemoteControl;
} }
_query() { _query() {
if (this.mode == IrSensorMode.RemoteControl) if (this.mode == InfraredSensorMode.RemoteControl)
return mapButton(this.getNumber(NumberFormat.UInt8LE, this.channel)); return mapButton(this.getNumber(NumberFormat.UInt8LE, this._channel));
else if (this.mode == IrSensorMode.Proximity) { else if (this.mode == InfraredSensorMode.Proximity) {
return this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff; return this.getNumber(NumberFormat.UInt16LE, 0) & 0x0fff;
} }
return 0 return 0
} }
_update(prev: number, curr: number) { _update(prev: number, curr: number) {
if (this.mode == IrSensorMode.RemoteControl) { if (this.mode == InfraredSensorMode.RemoteControl) {
for (let i = 0; i < buttons.length; ++i) { for (let i = 0; i < __remoteButtons.length; ++i) {
let v = !!(curr & (1 << i)) let v = !!(curr & (1 << i))
buttons[i]._update(v) __remoteButtons[i]._update(v)
} }
} else if (this.mode == IrSensorMode.Proximity) { } else if (this.mode == InfraredSensorMode.Proximity) {
this.proximityThreshold.setLevel(curr); this._proximityThreshold.setLevel(curr);
} }
} }
@ -175,13 +175,7 @@ namespace sensors {
return DAL.DEVICE_TYPE_IR return DAL.DEVICE_TYPE_IR
} }
setRemoteChannel(c: IrRemoteChannel) { setMode(m: InfraredSensorMode) {
c = Math.clamp(0, 3, c | 0)
this.channel = c
this.setMode(IrSensorMode.RemoteControl)
}
setMode(m: IrSensorMode) {
this._setMode(m) this._setMode(m)
} }
@ -197,7 +191,7 @@ namespace sensors {
//% weight=100 blockGap=8 //% weight=100 blockGap=8
//% group="Infrared Sensor" //% group="Infrared Sensor"
onEvent(event: InfraredSensorEvent, handler: () => void) { onEvent(event: InfraredSensorEvent, handler: () => void) {
this._setMode(IrSensorMode.Proximity) this._setMode(InfraredSensorMode.Proximity)
control.onEvent(this._id, event, handler); control.onEvent(this._id, event, handler);
} }
@ -212,7 +206,7 @@ namespace sensors {
//% weight=99 blockGap=8 //% weight=99 blockGap=8
//% group="Infrared Sensor" //% group="Infrared Sensor"
pauseUntil(event: InfraredSensorEvent) { pauseUntil(event: InfraredSensorEvent) {
this._setMode(IrSensorMode.Proximity) this._setMode(InfraredSensorMode.Proximity)
control.waitForEvent(this._id, event); control.waitForEvent(this._id, event);
} }
@ -228,33 +222,24 @@ namespace sensors {
//% weight=98 blockGap=8 //% weight=98 blockGap=8
//% group="Infrared Sensor" //% group="Infrared Sensor"
proximity(): number { proximity(): number {
this._setMode(IrSensorMode.Proximity) this._setMode(InfraredSensorMode.Proximity)
return this.getNumber(NumberFormat.UInt8LE, 0) return this.getNumber(NumberFormat.UInt8LE, 0)
} }
/** /**
* Get the remote commandreceived the infrared sensor. * Sets the remote channel to listen from
* @param sensor the infrared sensor * @param channel the channel to listen
*/ */
//% help=input/infrared/remote-command
//% block="%sensor|remote command"
//% blockId=infraredGetRemoteCommand
//% parts="infrared"
//% blockNamespace=sensors //% blockNamespace=sensors
//% blockId=irSetRemoteChannel block="set %sensor|remote channel to %channel"
//% weight=65 //% weight=65
//% group="Infrared Sensor" //% group="Remote Infrared Beacon"
remoteCommand(): number { setRemoteChannel(channel: InfraredRemoteChannel) {
this._setMode(IrSensorMode.RemoteControl) this.setMode(InfraredSensorMode.RemoteControl)
return this.getNumber(NumberFormat.UInt8LE, this.channel) channel = Math.clamp(0, 3, channel | 0)
this._channel = channel;
} }
// TODO
getDirectionAndDistance() {
this._setMode(IrSensorMode.Seek)
return this.getNumber(NumberFormat.UInt16LE, this.channel * 2)
}
/** /**
* Sets a threshold value * Sets a threshold value
* @param condition the dark or bright light condition * @param condition the dark or bright light condition
@ -263,11 +248,11 @@ namespace sensors {
//% blockId=irSetThreshold block="set %sensor|%condition|to %value" //% blockId=irSetThreshold block="set %sensor|%condition|to %value"
//% group="Threshold" blockGap=8 weight=49 //% group="Threshold" blockGap=8 weight=49
//% value.min=0 value.max=100 //% value.min=0 value.max=100
setThreshold(condition: InfraredSensorEvent, value: number) { setPromixityThreshold(condition: InfraredSensorEvent, value: number) {
if (condition == InfraredSensorEvent.ObjectNear) if (condition == InfraredSensorEvent.ObjectNear)
this.proximityThreshold.setLowThreshold(value) this._proximityThreshold.setLowThreshold(value)
else else
this.proximityThreshold.setHighThreshold(value); this._proximityThreshold.setHighThreshold(value);
} }
/** /**
@ -277,9 +262,15 @@ namespace sensors {
//% blockId=irGetThreshold block="%sensor|%condition" //% blockId=irGetThreshold block="%sensor|%condition"
//% group="Threshold" blockGap=8 weight=49 //% group="Threshold" blockGap=8 weight=49
//% sensor.fieldEditor="ports" //% sensor.fieldEditor="ports"
threshold(condition: InfraredSensorEvent): number { proximityThreshold(condition: InfraredSensorEvent): number {
return this.proximityThreshold.threshold(<ThresholdState><number>LightCondition.Dark); return this._proximityThreshold.threshold(<ThresholdState><number>LightCondition.Dark);
} }
// TODO
private getDirectionAndDistance() {
this._setMode(InfraredSensorMode.Seek)
return this.getNumber(NumberFormat.UInt16LE, this._channel * 2)
}
} }
//% fixedInstance whenUsed block="infrared 1" jres=icons.port1 //% fixedInstance whenUsed block="infrared 1" jres=icons.port1
@ -298,29 +289,29 @@ namespace sensors {
* Remote beacon (center) button. * Remote beacon (center) button.
*/ */
//% whenUsed block="remote button center" weight=95 fixedInstance //% whenUsed block="remote button center" weight=95 fixedInstance
export const remoteButtonCenter = irButton(IrRemoteButton.CenterBeacon) export const remoteButtonCenter = __irButton(InfraredRemoteButton.CenterBeacon)
/** /**
* Remote top-left button. * Remote top-left button.
*/ */
//% whenUsed block="remote button top-left" weight=95 fixedInstance //% whenUsed block="remote button top-left" weight=95 fixedInstance
export const remoteButtonTopLeft = irButton(IrRemoteButton.TopLeft) export const remoteButtonTopLeft = __irButton(InfraredRemoteButton.TopLeft)
/** /**
* Remote top-right button. * Remote top-right button.
*/ */
//% whenUsed block="remote button top-right" weight=95 fixedInstance //% whenUsed block="remote button top-right" weight=95 fixedInstance
export const remoteButtonTopRight = irButton(IrRemoteButton.TopRight) export const remoteButtonTopRight = __irButton(InfraredRemoteButton.TopRight)
/** /**
* Remote bottom-left button. * Remote bottom-left button.
*/ */
//% whenUsed block="remote button bottom-left" weight=95 fixedInstance //% whenUsed block="remote button bottom-left" weight=95 fixedInstance
export const remoteButtonBottomLeft = irButton(IrRemoteButton.BottomLeft) export const remoteButtonBottomLeft = __irButton(InfraredRemoteButton.BottomLeft)
/** /**
* Remote bottom-right button. * Remote bottom-right button.
*/ */
//% whenUsed block="remote button bottom-right" weight=95 fixedInstance //% whenUsed block="remote button bottom-right" weight=95 fixedInstance
export const remoteButtonBottomRight = irButton(IrRemoteButton.BottomRight) export const remoteButtonBottomRight = __irButton(InfraredRemoteButton.BottomRight)
} }

View File

@ -13,6 +13,7 @@ namespace pxsim {
motorState: EV3MotorState; motorState: EV3MotorState;
screenState: EV3ScreenState; screenState: EV3ScreenState;
audioState: AudioState; audioState: AudioState;
remoteState: RemoteState;
inputNodes: SensorNode[] = []; inputNodes: SensorNode[] = [];
brickNode: BrickNode; brickNode: BrickNode;
@ -38,6 +39,7 @@ namespace pxsim {
this.motorState = new EV3MotorState(); this.motorState = new EV3MotorState();
this.screenState = new EV3ScreenState(); this.screenState = new EV3ScreenState();
this.audioState = new AudioState(); this.audioState = new AudioState();
this.remoteState = new RemoteState();
} }
receiveMessage(msg: SimulatorMessage) { receiveMessage(msg: SimulatorMessage) {

View File

@ -1,6 +1,48 @@
/// <reference path="./sensor.ts"/> /// <reference path="./sensor.ts"/>
namespace pxsim { namespace pxsim {
export enum InfraredRemoteButton {
//% block="center beacon"
CenterBeacon = 0x01,
//% block="top left"
TopLeft = 0x02,
//% block="bottom left"
BottomLeft = 0x04,
//% block="top right"
TopRight = 0x08,
//% block="bottom right"
BottomRight = 0x10,
}
export class RemoteState {
state: number = 0;
constructor() {
}
unmapButtons() {
switch(this.state) {
case InfraredRemoteButton.TopLeft: return 1;
case InfraredRemoteButton.BottomLeft: return 2;
case InfraredRemoteButton.TopLeft: return 3;
case InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight: return 4;
case InfraredRemoteButton.TopLeft | InfraredRemoteButton.TopRight: return 5;
case InfraredRemoteButton.TopLeft | InfraredRemoteButton.BottomRight: return 6;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopRight: return 7;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.BottomRight: return 8;
case InfraredRemoteButton.CenterBeacon: return 9;
case InfraredRemoteButton.BottomLeft | InfraredRemoteButton.TopLeft: return 10;
case InfraredRemoteButton.TopRight | InfraredRemoteButton.BottomRight: return 11;
default: return 0;
}
}
setPressed(btns: InfraredRemoteButton, down: boolean) {
if (down) this.state = this.state | btns;
else this.state = ~(~this.state | btns);
}
}
export enum InfraredSensorMode { export enum InfraredSensorMode {
None = -1, None = -1,
Proximity = 0, Proximity = 0,
@ -29,7 +71,11 @@ namespace pxsim {
} }
getValue() { getValue() {
return this.proximity; switch(this.mode) {
case InfraredSensorMode.Proximity: return this.proximity;
case InfraredSensorMode.RemoteControl: return ev3board().remoteState.unmapButtons();
default: return 0;
}
} }
} }
} }

View File

@ -234,6 +234,8 @@ namespace pxsim.visuals {
const state = ev3board().getInputNodes()[0] as InfraredSensorNode; const state = ev3board().getInputNodes()[0] as InfraredSensorNode;
if (state.getMode() == InfraredSensorMode.Proximity) if (state.getMode() == InfraredSensorMode.Proximity)
view = new ProximitySliderControl(this.element, this.defs, state, port); view = new ProximitySliderControl(this.element, this.defs, state, port);
else if (state.getMode() == InfraredSensorMode.RemoteControl)
view = new RemoteBeaconButtonsControl(this.element, this.defs, state, port);
break; break;
} }
case NodeType.GyroSensor: { case NodeType.GyroSensor: {

View File

@ -0,0 +1,57 @@
namespace pxsim.visuals {
enum InfraredRemoteButton {
CenterBeacon = 0x01,
TopLeft = 0x02,
BottomLeft = 0x04,
TopRight = 0x08,
BottomRight = 0x10,
}
export class RemoteBeaconButtonsControl extends ControlView<InfraredSensorNode> {
private group: SVGGElement;
getInnerView() {
this.group = svg.elt("g") as SVGGElement;
this.group.setAttribute("transform", `translate(2, 2.5) scale(0.6)`)
const btnIds = [
InfraredRemoteButton.CenterBeacon,
InfraredRemoteButton.TopLeft,
InfraredRemoteButton.TopRight,
InfraredRemoteButton.BottomLeft,
InfraredRemoteButton.BottomRight];
const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', '#6c2d00'];
let cy = -4;
btnIds.forEach((cid, c) => {
const cx = c % 2 == 0 ? 2.2 : 8.2;
if (c % 2 == 0) cy += 5;
if (btnIds[c]) {
const circle = pxsim.svg.child(this.group, "circle", { 'class': 'sim-color-grid-circle', 'cx': cx, 'cy': cy, 'r': '2', 'style': `fill: ${colors[c]}` });
pointerEvents.down.forEach(evid => circle.addEventListener(evid, ev => {
ev3board().remoteState.setPressed(cid, true);
}));
circle.addEventListener(pointerEvents.leave, ev => {
ev3board().remoteState.setPressed(cid, false);
});
circle.addEventListener(pointerEvents.up, ev => {
ev3board().remoteState.setPressed(cid, true);
});
}
})
return this.group;
}
getInnerWidth() {
return 10.2;
}
getInnerHeight() {
return 15;
}
}
}