Skip to content

Commit 803e755

Browse files
committed
feat(servo): add servo element
1 parent 9c85a07 commit 803e755

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export { NeopixelMatrixElement } from './neopixel-matrix-element';
1414
export { SSD1306Element } from './ssd1306-element';
1515
export { BuzzerElement } from './buzzer-element';
1616
export { RotaryDialerElement } from './rotary-dialer-element';
17+
export { ServoElement } from './servo-element';

src/react-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { PushbuttonElement } from './pushbutton-element';
1414
import { ResistorElement } from './resistor-element';
1515
import { RotaryDialerElement } from './rotary-dialer-element';
1616
import { SSD1306Element } from './ssd1306-element';
17+
import { ServoElement } from './servo-element';
1718

1819
declare global {
1920
namespace JSX {
@@ -31,6 +32,7 @@ declare global {
3132
'wokwi-ssd1306': Partial<SSD1306Element>;
3233
'wokwi-buzzer': Partial<BuzzerElement>;
3334
'wokwi-rotary-dialer': Partial<RotaryDialerElement>;
35+
'wokwi-servo': Partial<ServoElement>;
3436
}
3537
}
3638
}

src/servo-element.stories.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { number, withKnobs } from '@storybook/addon-knobs';
2+
import { storiesOf, forceReRender } from '@storybook/web-components';
3+
import { html } from 'lit-html';
4+
import './servo-element';
5+
6+
class SweepAnimation {
7+
angle = 0;
8+
goingUp = true;
9+
10+
step() {
11+
if (this.goingUp) {
12+
this.angle++;
13+
} else {
14+
this.angle--;
15+
}
16+
if (this.angle === 180) {
17+
this.goingUp = false;
18+
}
19+
if (this.angle === 0) {
20+
this.goingUp = true;
21+
}
22+
}
23+
}
24+
25+
const sweepAnimation = new SweepAnimation();
26+
setInterval(() => {
27+
sweepAnimation.step();
28+
forceReRender();
29+
}, 20);
30+
31+
storiesOf('Servo', module)
32+
.addParameters({ component: 'wokwi-servo' })
33+
.addDecorator(withKnobs)
34+
.add(
35+
'Default',
36+
() => html` <wokwi-servo angle=${number('angle', 45, { min: 0, max: 180 })}></wokwi-servo> `
37+
)
38+
.add('Animated: sweep', () => {
39+
return html` <wokwi-servo angle=${sweepAnimation.angle}></wokwi-servo> `;
40+
})
41+
.add(
42+
'Horn: double',
43+
() =>
44+
html`
45+
<wokwi-servo horn="double" angle=${number('angle', 45, { min: 0, max: 180 })}></wokwi-servo>
46+
`
47+
)
48+
.add(
49+
'Horn: cross',
50+
() =>
51+
html`
52+
<wokwi-servo horn="cross" angle=${number('angle', 45, { min: 0, max: 180 })}></wokwi-servo>
53+
`
54+
);

src/servo-element.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { customElement, html, LitElement, property } from 'lit-element';
2+
3+
@customElement('wokwi-servo')
4+
export class ServoElement extends LitElement {
5+
/**
6+
* The angle of the servo's horn
7+
*/
8+
@property() angle = 0;
9+
10+
/**
11+
* Servo horn (arm) type. The horn is attached to the
12+
* servo's output shaft, and they rotate together.
13+
*/
14+
@property() horn: 'single' | 'double' | 'cross' = 'single';
15+
16+
/** Servo horn color (as an HTML color) */
17+
@property() hornColor = '#ccc';
18+
19+
hornPath() {
20+
switch (this.horn) {
21+
case 'cross':
22+
return 'm119.54 50.354h-18.653v-18.653a8.4427 8.4427 0 0 0-8.4427-8.4427h-1.9537a8.4427 8.4427 0 0 0-8.4427 8.4427v18.653h-18.653a8.4427 8.4427 0 0 0-8.4427 8.4427v1.9537a8.4427 8.4427 0 0 0 8.4427 8.4427h18.653v18.653a8.4427 8.4427 0 0 0 8.4427 8.4427h1.9537a8.4427 8.4427 0 0 0 8.4427-8.4427v-18.653h18.653a8.4427 8.4427 0 0 0 8.4426-8.4427v-1.9537a8.4427 8.4427 0 0 0-8.4426-8.4427zm-57.447 12.136a2.7165 2.7165 0 1 1 2.7119-2.7165 2.7165 2.7165 0 0 1-2.7165 2.7165zm8.7543 0a2.7165 2.7165 0 1 1 2.7119-2.7165 2.7165 2.7165 0 0 1-2.7165 2.7165zm20.621-34.813a2.7165 2.7165 0 1 1-2.7165 2.7165 2.7165 2.7165 0 0 1 2.7165-2.7165zm0 8.7543a2.7165 2.7165 0 1 1-2.7165 2.7165 2.7165 2.7165 0 0 1 2.7165-2.7165zm0 55.438a2.7165 2.7165 0 1 1 2.7165-2.7165 2.7165 2.7165 0 0 1-2.7165 2.7165zm0-8.7543a2.7165 2.7165 0 1 1 2.7165-2.7165 2.7165 2.7165 0 0 1-2.7165 2.7165zm5.9215-17.42a8.3729 8.3729 0 1 1 0-11.843 8.3729 8.3729 0 0 1 0 11.843zm14.704-3.205a2.7165 2.7165 0 1 1 2.7165-2.7165 2.7165 2.7165 0 0 1-2.7165 2.7165zm8.7543 0a2.7165 2.7165 0 1 1 2.7165-2.7165 2.7165 2.7165 0 0 1-2.7165 2.7165z';
23+
case 'double':
24+
return 'm101.63 57.808c-0.0768-0.48377-0.16978-0.8838-0.23258-1.1629l-4.112-51.454c0-2.8654-2.6026-5.1912-5.8145-5.1912s-5.8145 2.3258-5.8145 5.1912l-4.1004 51.447c-0.07443 0.28607-0.16746 0.69774-0.24421 1.1629a12.473 12.473 0 0 0 0 3.9306c0.07675 0.48377 0.16978 0.8838 0.24421 1.1629l4.1004 51.461c0 2.8654 2.6026 5.1912 5.8145 5.1912s5.8145-2.3258 5.8145-5.1912l4.1004-51.447c0.0744-0.28607 0.16746-0.69774 0.23258-1.1629a12.473 12.473 0 0 0 0.0116-3.9376zm-4.2376 7.8868a8.3426 8.3426 0 0 1-3.5375 2.1072c-0.25816 0.07443-0.52098 0.13955-0.7838 0.19072a8.7217 8.7217 0 0 1-1.1978 0.1442c-0.26747 0.01163-0.53726 0.01163-0.80473 0a8.7217 8.7217 0 0 1-1.1978-0.1442c-0.26282-0.05117-0.52563-0.11629-0.78379-0.19072a8.3729 8.3729 0 0 1 0-16.048c0.25816-0.07675 0.52098-0.13955 0.78379-0.19072a8.7217 8.7217 0 0 1 1.1978-0.1442c0.26747-0.01163 0.53726-0.01163 0.80473 0a8.7217 8.7217 0 0 1 1.1978 0.1442c0.26282 0.05117 0.52563 0.11396 0.7838 0.19072a8.3729 8.3729 0 0 1 3.5375 13.955zm-5.9215-54.996a2.791 2.791 0 1 1-2.791 2.791 2.791 2.791 0 0 1 2.791-2.791zm0 8.6055a2.791 2.791 0 1 1-2.791 2.791 2.791 2.791 0 0 1 2.791-2.791zm0 8.3729a2.791 2.791 0 1 1-2.791 2.791 2.791 2.791 0 0 1 2.791-2.791zm0 8.6055a2.791 2.791 0 1 1-2.791 2.791 2.791 2.791 0 0 1 2.791-2.791zm0 72.565a2.791 2.791 0 1 1 2.791-2.791 2.791 2.791 0 0 1-2.791 2.791zm0-8.6055a2.791 2.791 0 1 1 2.791-2.791 2.791 2.791 0 0 1-2.791 2.791zm0-8.3729a2.791 2.791 0 1 1 2.791-2.791 2.791 2.791 0 0 1-2.791 2.791zm0-8.6055a2.791 2.791 0 1 1 2.791-2.791 2.791 2.791 0 0 1-2.791 2.791z';
25+
case 'single':
26+
default:
27+
return 'm101.6 59.589-4.3167-54.166c0-2.8654-2.6026-5.1912-5.8145-5.1912s-5.8145 2.3258-5.8145 5.1912l-4.3167 54.166a8.3264 8.3264 0 0 0-0.10234 1.2792c0 5.047 4.5818 9.1381 10.234 9.1381s10.234-4.0911 10.234-9.1381a8.3264 8.3264 0 0 0-0.10233-1.2792zm-10.131-48.658a2.791 2.791 0 1 1-2.791 2.791 2.791 2.791 0 0 1 2.791-2.791zm0 8.6055a2.791 2.791 0 1 1-2.791 2.791 2.791 2.791 0 0 1 2.791-2.791zm0 8.3729a2.791 2.791 0 1 1-2.791 2.791 2.791 2.791 0 0 1 2.791-2.791zm0 8.6055a2.791 2.791 0 1 1-2.791 2.791 2.791 2.791 0 0 1 2.791-2.791zm5.9215 29.412a8.3729 8.3729 0 1 1 0-11.843 8.3729 8.3729 0 0 1 0 11.843z';
28+
}
29+
}
30+
31+
render() {
32+
return html`
33+
<svg
34+
xmlns="http://www.w3.org/2000/svg"
35+
width="45mm"
36+
height="31.63mm"
37+
version="1.1"
38+
viewBox="0 0 170.08 119.55"
39+
>
40+
<g stroke-width="2.3206">
41+
<path stroke="#b44200" d="m83.326 64.141h-62.894" />
42+
<path stroke="#ff2300" d="m83.326 59.638h-62.971" />
43+
<path stroke="#f47b00" d="m83.326 55.231h-62.971" />
44+
</g>
45+
<g>
46+
<rect fill="#666" y="52.21" width="25.71" height="14.885" rx="1.14" />
47+
<g fill="#333">
48+
<rect x="4.74" y="53.90" width="3.72" height="2.71" />
49+
<path d="m6.7658 53.905h13.532l-13.425 0.51865z" />
50+
<path d="m6.7658 56.612h13.532l-13.425-0.50702z" />
51+
<rect x="4.74" y="58.30" width="3.72" height="2.71" />
52+
<path d="m6.7658 58.303h13.532l-13.425 0.50702z" />
53+
<path d="m6.7658 61.01h13.532l-13.425-0.50702z" />
54+
<rect x="4.74" y="62.70" width="3.72" height="2.71" />
55+
<path d="m6.7658 62.701h13.532l-13.425 0.50702z" />
56+
<path d="m6.7658 65.408h13.532l-13.425-0.50702z" />
57+
</g>
58+
<rect fill="#ccc" x="5.07" y="54.58" width="3.04" height="1.46" rx=".15" />
59+
<rect fill="#ccc" x="5.07" y="58.98" width="3.04" height="1.35" rx=".15" />
60+
<rect fill="#ccc" x="5.07" y="63.38" width="3.04" height="1.35" rx=".15" />
61+
<path
62+
fill="#4d4d4d"
63+
d="m163.92 66.867a7.09 7.09 0 1 1 5.8145-11.136 0.18 0.18 0 0 0 0.33-0.10234v-14.346h-17.664v36.98h17.676v-14.346a0.18 0.18 0 0 0-0.333-0.107 7.08 7.08 0 0 1-5.83 3.06z"
64+
/>
65+
<path
66+
fill="#4d4d4d"
67+
d="m55.068 66.75a7.09 7.09 0 1 0-5.8261-11.136 0.18 0.18 0 0 1-0.33-0.10234v-14.346h17.676v36.98h-17.676v-14.346a0.18 0.18 0 0 1 0.333-0.107 7.08 7.08 0 0 0 5.83 3.06z"
68+
/>
69+
<rect fill="#666" x="64.255" y="37.911" width="90.241" height="43.725" rx="5.3331" />
70+
<path fill="gray" d="m110.07 50.005h-14.42v19.537h14.42a9.7684 9.7684 0 0 0 0-19.537z" />
71+
<circle fill="#999" cx="91.467" cy="59.773" r="18.606" />
72+
<path
73+
fill=${this.hornColor}
74+
transform="rotate(${this.angle})"
75+
transform-origin="91.467 59.773"
76+
d="${this.hornPath()}"
77+
/>
78+
<circle fill="gray" cx="91.467" cy="59.773" r="8.3729" />
79+
<circle fill="#ccc" cx="91.467" cy="59.773" r="6.2494" />
80+
<path
81+
fill="#666"
82+
d="m94.911 62.543-2.3839-2.4165a0.42562 0.42562 0 0 1 0-0.60471l2.4281-2.3863a0.64657 0.64657 0 0 0 0.06512-0.8652 0.62797 0.62797 0 0 0-0.93032-0.05117l-2.4351 2.4049a0.4326 0.4326 0 0 1-0.60703 0l-2.3863-2.4165a0.6489 0.6489 0 0 0-0.8652-0.06512 0.63262 0.63262 0 0 0-0.05117 0.93032l2.4049 2.4328a0.42795 0.42795 0 0 1 0 0.60703l-2.4142 2.3863a0.65122 0.65122 0 0 0-0.06745 0.8652 0.63029 0.63029 0 0 0 0.93032 0.05117l2.4351-2.4049a0.42562 0.42562 0 0 1 0.60471 0l2.3863 2.4398a0.63262 0.63262 0 0 0 0.93032-0.04186 0.64657 0.64657 0 0 0-0.04419-0.8652z"
83+
/>
84+
</g>
85+
</svg>
86+
`;
87+
}
88+
}

0 commit comments

Comments
 (0)