From 89a82b54dc99b477b149a6d5e281d69f42249d62 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Thu, 1 Feb 2018 22:03:01 -0800 Subject: [PATCH] Ir proximity in simulator (#299) * support for IR proximity * fixing build issue * missing break * remove auto-start of sensor * setting mode on onEvent * flooring slider value * bump up proximity * fixing threshold blocks --- libs/infrared-sensor/ir.ts | 15 +-- sim/dalboard.ts | 1 + sim/state/infrared.ts | 35 ++++++ sim/state/nodeTypes.ts | 3 +- sim/visuals/assets/infraredsvg.ts | 79 ++++++++++++++ sim/visuals/board.ts | 14 ++- sim/visuals/controls/proximitySlider.ts | 138 ++++++++++++++++++++++++ sim/visuals/nodes/infraredview.ts | 10 ++ 8 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 sim/state/infrared.ts create mode 100644 sim/visuals/assets/infraredsvg.ts create mode 100644 sim/visuals/controls/proximitySlider.ts create mode 100644 sim/visuals/nodes/infraredview.ts diff --git a/libs/infrared-sensor/ir.ts b/libs/infrared-sensor/ir.ts index 208e61c0..cb477745 100644 --- a/libs/infrared-sensor/ir.ts +++ b/libs/infrared-sensor/ir.ts @@ -23,7 +23,7 @@ const enum IrRemoteButton { const enum InfraredSensorEvent { //% block="object near" - ObjectNear = 1, + ObjectNear = 3, //% block="object detected" ObjectDetected = 2 } @@ -60,11 +60,12 @@ namespace sensors { 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) + //create(infraredSensor1) + //create(infraredSensor2) + //create(infraredSensor3) + //create(infraredSensor4) } let num = -1 @@ -196,6 +197,7 @@ namespace sensors { //% weight=100 blockGap=8 //% group="Infrared Sensor" onEvent(event: InfraredSensorEvent, handler: () => void) { + this._setMode(IrSensorMode.Proximity) control.onEvent(this._id, event, handler); } @@ -210,6 +212,7 @@ namespace sensors { //% weight=99 blockGap=8 //% group="Infrared Sensor" pauseUntil(event: InfraredSensorEvent) { + this._setMode(IrSensorMode.Proximity) control.waitForEvent(this._id, event); } @@ -222,7 +225,7 @@ namespace sensors { //% blockId=infraredGetProximity //% parts="infrared" //% blockNamespace=sensors - //% weight=65 blockGap=8 + //% weight=98 blockGap=8 //% group="Infrared Sensor" proximity(): number { this._setMode(IrSensorMode.Proximity) diff --git a/sim/dalboard.ts b/sim/dalboard.ts index 5cda54b6..e430087f 100644 --- a/sim/dalboard.ts +++ b/sim/dalboard.ts @@ -143,6 +143,7 @@ namespace pxsim { case DAL.DEVICE_TYPE_COLOR: this.inputNodes[port] = new ColorSensorNode(port); break; case DAL.DEVICE_TYPE_TOUCH: this.inputNodes[port] = new TouchSensorNode(port); break; case DAL.DEVICE_TYPE_ULTRASONIC: this.inputNodes[port] = new UltrasonicSensorNode(port); break; + case DAL.DEVICE_TYPE_IR: this.inputNodes[port] = new InfraredSensorNode(port); break; } } return this.inputNodes[port]; diff --git a/sim/state/infrared.ts b/sim/state/infrared.ts new file mode 100644 index 00000000..38af0128 --- /dev/null +++ b/sim/state/infrared.ts @@ -0,0 +1,35 @@ +/// + +namespace pxsim { + export enum InfraredSensorMode { + None = -1, + Proximity = 0, + Seek = 1, + RemoteControl = 2 + } + + export class InfraredSensorNode extends UartSensorNode { + id = NodeType.InfraredSensor; + + private proximity: number = 50; // [0..100] + + constructor(port: number) { + super(port); + } + + getDeviceType() { + return DAL.DEVICE_TYPE_IR; + } + + setPromixity(proximity: number) { + if (this.proximity != proximity) { + this.proximity = proximity; + this.setChangedState(); + } + } + + getValue() { + return this.proximity; + } + } +} \ No newline at end of file diff --git a/sim/state/nodeTypes.ts b/sim/state/nodeTypes.ts index aa9b9f06..53d4c9c7 100644 --- a/sim/state/nodeTypes.ts +++ b/sim/state/nodeTypes.ts @@ -7,7 +7,8 @@ namespace pxsim { LargeMotor = 4, GyroSensor = 5, ColorSensor = 6, - UltrasonicSensor = 7 + UltrasonicSensor = 7, + InfraredSensor = 8 } export interface Node { diff --git a/sim/visuals/assets/infraredsvg.ts b/sim/visuals/assets/infraredsvg.ts new file mode 100644 index 00000000..00768506 --- /dev/null +++ b/sim/visuals/assets/infraredsvg.ts @@ -0,0 +1,79 @@ +namespace pxsim { + export const INFRARED_SVG = ` + + + + + + + + + ultra sonic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; +} \ No newline at end of file diff --git a/sim/visuals/board.ts b/sim/visuals/board.ts index f471db28..9cbeca1a 100644 --- a/sim/visuals/board.ts +++ b/sim/visuals/board.ts @@ -230,6 +230,12 @@ namespace pxsim.visuals { view = new DistanceSliderControl(this.element, this.defs, state, port); break; } + case NodeType.InfraredSensor: { + const state = ev3board().getInputNodes()[0] as InfraredSensorNode; + if (state.getMode() == InfraredSensorMode.Proximity) + view = new ProximitySliderControl(this.element, this.defs, state, port); + break; + } case NodeType.GyroSensor: { const state = ev3board().getInputNodes()[port] as GyroSensorNode; view = new RotationSliderControl(this.element, this.defs, state, port); @@ -271,6 +277,8 @@ namespace pxsim.visuals { view = new ColorSensorView(port); break; case NodeType.UltrasonicSensor: view = new UltrasonicSensorView(port); break; + case NodeType.InfraredSensor: + view = new InfraredView(port); break; case NodeType.Brick: //return new BrickView(0); view = this.layoutView.getBrick(); break; @@ -305,11 +313,11 @@ namespace pxsim.visuals { // Add EV3 module element const brickCloseIcon = this.getCloseIconView(); - brickCloseIcon.registerClick(ev => { - this.layoutView.unselectBrick(); + brickCloseIcon.registerClick(ev => { + this.layoutView.unselectBrick(); this.resize(); }); - const brick =new BrickView(-1); + const brick = new BrickView(-1); brick.setSelected(EV3View.isPreviousBrickSelected()); this.layoutView.setBrick(brick, brickCloseIcon); diff --git a/sim/visuals/controls/proximitySlider.ts b/sim/visuals/controls/proximitySlider.ts new file mode 100644 index 00000000..19afa077 --- /dev/null +++ b/sim/visuals/controls/proximitySlider.ts @@ -0,0 +1,138 @@ + + +namespace pxsim.visuals { + + export class ProximitySliderControl extends ControlView { + private group: SVGGElement; + private gradient: SVGLinearGradientElement; + private slider: SVGGElement; + + private reporter: SVGTextElement; + + private static SLIDER_HANDLE_HEIGHT = 26; + private static SLIDER_SIDE_PADDING = 6; + + getInnerView(parent: SVGSVGElement, globalDefs: SVGDefsElement) { + let gid = "gradient-slider-" + this.getId(); + this.group = svg.elt("g") as SVGGElement; + this.gradient = createGradient(gid, this.getGradientDefinition()); + this.gradient.setAttribute('x1', '0%'); + this.gradient.setAttribute('y1', '0%'); + this.gradient.setAttribute('x2', '0%'); + this.gradient.setAttribute('y2', '100%'); + // this.gradient.setAttribute('gradientTransform', 'matrix(50, 0, 0, -110, 21949.45, 46137.67)'); + // this.gradient.setAttribute('gradientUnits', 'userSpaceOnUse'); + globalDefs.appendChild(this.gradient); + + this.group = svg.elt("g") as SVGGElement; + + const reporterGroup = pxsim.svg.child(this.group, "g"); + reporterGroup.setAttribute("transform", `translate(${this.getWidth() / 2}, 42)`); + this.reporter = pxsim.svg.child(reporterGroup, "text", { 'text-anchor': 'middle', 'x': 0, 'y': '0', 'class': 'sim-text number large inverted' }) as SVGTextElement; + + const sliderGroup = pxsim.svg.child(this.group, "g"); + sliderGroup.setAttribute("transform", `translate(${this.getWidth() / 2 - this.getSliderWidth() / 2}, ${this.getReporterHeight()})`) + + const rect = pxsim.svg.child(sliderGroup, "rect", { 'x': ProximitySliderControl.SLIDER_SIDE_PADDING, 'y': 2, 'width': this.getSliderWidth() - ProximitySliderControl.SLIDER_SIDE_PADDING * 2, 'height': this.getSliderHeight(), 'style': `fill: url(#${gid})` }); + + this.slider = pxsim.svg.child(sliderGroup, "g", { "transform": "translate(0,0)" }) as SVGGElement; + const sliderInner = pxsim.svg.child(this.slider, "g"); + pxsim.svg.child(sliderInner, "rect", { 'width': this.getSliderWidth(), 'height': ProximitySliderControl.SLIDER_HANDLE_HEIGHT, 'rx': '2', 'ry': '2', 'style': 'fill: #f12a21' }); + pxsim.svg.child(sliderInner, "rect", { 'x': '0.5', 'y': '0.5', 'width': this.getSliderWidth() - 1, 'height': ProximitySliderControl.SLIDER_HANDLE_HEIGHT - 1, 'rx': '1.5', 'ry': '1.5', 'style': 'fill: none;stroke: #b32e29' }); + + const dragSurface = svg.child(this.group, "rect", { + x: 0, + y: 0, + width: this.getInnerWidth(), + height: this.getInnerHeight(), + opacity: 0, + cursor: '-webkit-grab' + }) + + let pt = parent.createSVGPoint(); + let captured = false; + + touchEvents(dragSurface, ev => { + if (captured && (ev as MouseEvent).clientY != undefined) { + ev.preventDefault(); + this.updateSliderValue(pt, parent, ev as MouseEvent); + } + }, ev => { + captured = true; + if ((ev as MouseEvent).clientY != undefined) { + dragSurface.setAttribute('cursor', '-webkit-grabbing'); + this.updateSliderValue(pt, parent, ev as MouseEvent); + } + }, () => { + captured = false; + dragSurface.setAttribute('cursor', '-webkit-grab'); + }, () => { + captured = false; + dragSurface.setAttribute('cursor', '-webkit-grab'); + }) + + return this.group; + } + + getInnerHeight() { + return 192; + } + + getInnerWidth() { + return 111; + } + + private getReporterHeight() { + return 50; + } + + private getSliderHeight() { + return 110; + } + + private getSliderWidth() { + return 62; + } + + updateState() { + if (!this.visible) { + return; + } + const node = this.state; + const percentage = node.getValue(); + const y = this.getSliderHeight() * percentage / this.getMax(); + this.slider.setAttribute("transform", `translate(0, ${y - ProximitySliderControl.SLIDER_HANDLE_HEIGHT / 2})`); + // Update reporter text + this.reporter.textContent = `${parseFloat((percentage).toString()).toFixed(0)}`; + } + + private updateSliderValue(pt: SVGPoint, parent: SVGSVGElement, ev: MouseEvent) { + let cur = svg.cursorPoint(pt, parent, ev); + const height = this.getSliderHeight(); + const bBox = this.content.getBoundingClientRect(); + let t = Math.max(0, Math.min(1, (ProximitySliderControl.SLIDER_HANDLE_HEIGHT + height + bBox.top / this.scaleFactor - cur.y / this.scaleFactor) / height)) + + const state = this.state; + const v = Math.floor((1 - t) * (this.getMax())); + state.setPromixity(v); + } + + private getMin() { + return 0; + } + + private getMax() { + return 100; + } + + private getGradientDefinition(): LinearGradientDefinition { + return { + stops: [ + { offset: 0, color: '#626262' }, + { offset: 100, color: "#ddd" } + ] + }; + } + } + +} \ No newline at end of file diff --git a/sim/visuals/nodes/infraredview.ts b/sim/visuals/nodes/infraredview.ts new file mode 100644 index 00000000..c32a838b --- /dev/null +++ b/sim/visuals/nodes/infraredview.ts @@ -0,0 +1,10 @@ +/// + +namespace pxsim.visuals { + export class InfraredView extends ModuleView implements LayoutElement { + + constructor(port: number) { + super(INFRARED_SVG, "infrared", NodeType.InfraredSensor, port); + } + } +} \ No newline at end of file