Create Vue Component based on Real Hardware/Thing #LED - part 1: implementing from basic math equation while doing UoM and CoU

Repository

What Will I Learn?

This tutorial can be considered as a small part of Proof of Concept of my previous post that mention about simulating hardware using webcomponent. In this tutorials you will learn:

  • How to build Vue components
  • How to utilize mathjs for applying UoM (Unit of Measurement) and automatically doing CoU (Conversion of Unit)
  • How to build webcomponent

Requirements

  • Basic undersatnding of SVG
  • Basic understanding of HTML and Typescript
  • Basic understanding of Vuejs and Class-Style Vue Components
  • Install vue-cli and yarn
  • Install some code-editor/IDE (strongly suggest to use VSCode + Vetur)

Difficulty

  • Intermediate

Tutorial Contents

Edit Vue Led Component

In this tutorials, we will create Vue component based on real hardware LED and how it works.

Preparation [commit #2]

First, let's create vue project with name vue-hardware-led

vue create vue-hardware-led

It will ask you step-by-step how would you like to configure your project. For this tutorials fill it with

? Please pick a preset:
  default (babel, eslint)
 Manually select features

? Check the features needed for your project:
  TypeScript
  Progressive Web App (PWA) Support
  Router
  Vuex
  CSS Pre-processors
  Linter / Formatter
  Unit Testing
  E2E Testing

? Use class-style component syntax? (Y/n) Y

? Use Babel alongside TypeScript for auto-detected polyfills? (Y/n) Y

? Pick a linter / formatter config: (Use arrow keys)
 TSLint
  ESLint with error prevention only
  ESLint + Airbnb config
  ESLint + Standard config
  ESLint + Prettier

? Pick additional lint features:
  Lint on save
  Lint and fix on commit

? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)
 In dedicated config files
  In package.json

Now you can add mathjs to your project

cd vue-hardware-led
yarn add mathjs
yarn add @types/mathjs -D

To avoid typescript error, you need to add shim-math.d.ts until @types/mathjs PR #25860 is merged

Finally, let's run it and keep it open

yarn serve --open

any change in your code is immediately compiled so you can see the change by just saving the file.

Add Led.vue [commit #3]

After you have setup your project, you can begin to build Led component. First, let's draw Led like shape using SVG (you can use any vector graphics editor or type it manually).

In src/components/Led.vue

<template>
  <svg width="108" height="232" viewBox="85 -12 108 232">
    <path id="bulb" fill="red" fill-opacity="50" stroke="red" stroke-width="5" d="M 94.8 89 L 94.8 27.9 C 94.8 5.9 114.4 -12 138.5 -12 C 162.6 -12 182.3 5.9 182.3 27.9 L 182.3 89 L 94.8 89 Z " />
    <rect id="cathode" width="14" height="82" x="162" y="93" fill="orange" />
    <rect id="anode" width="14" height="126" x="103" y="93" fill="orange" />
    <rect id="flat-surface" width="108" height="13" x="85" y="82" fill="crimson" />
  </svg>
</template>

Then you can test it by registering to src/App.vue.

In <script> blocks, import and register Led.vue

import Led from '@/components/Led.vue'

@Component({ components: { Led } })
export default class App extends Vue {}

Now in <template> blocks, you can add <led /> component

<template>
  <div id="app">
    <Led />
  </div>
</template>

Here is how SVG element in Led.vue build manually (without vector graphics editor)

led svg element

Define the usage [commit #4]

Now we have basic Led component based on SVG drawing. Let's break down how LED in the real world works. Brightness in LED depends on power (watt) you feed to it notated as P_{in}.
In summary

brightness~% = \frac{P_{in}} {P_{max}}

P_{max} = Total Power Consumption in watt (W)

because

P_{in} = I_{in} \times V_{in}

I_{in} = electric current of the input in ampere (A)
V_{in} = voltage of the input in volt (V)

then

brightness~% = \frac{I_{in} \times V_{in}} {P_{max}}

Now we can define the usage of our component to be like

<Led size="3mm" scale="10px/mm"
     input-voltage="3.3V" input-current="50mA"
     max-power="43mW" />

And register the vue props

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'

@Component
export default class Led extends Vue {
  @Prop() scale!: string
  @Prop() size!: string

  @Prop() inputVoltage!: string
  @Prop() inputCurrent!: string
  @Prop() maxPower!: string
}
</script>

Now, let's identify attributes of the SVG element that is dynamic (the value depends on the vue props).

  <svg :height="height" viewBox="85 -12 108 232">
    <path id="bulb" fill="red" :fill-opacity="brightness" stroke="red" stroke-width="5" d="M 94.8 89 L 94.8 27.9 C 94.8 5.9 114.4 -12 138.5 -12 C 162.6 -12 182.3 5.9 182.3 27.9 L 182.3 89 L 94.8 89 Z " />

then bind it to the computed properties

  get height(): number { return 232 }
  get brightness(): number { return 50 }

Here we see that attributes fill-opacity in <path id="bulb"> tag depend on computed properties brightness, hence <path id="bulb" :fill-opacity="brightness". Also, attributes height in <svg> tag depend on computed properties height, hence <svg :height="height". Notice that I remove attributes width in <svg> tag because to make the component automatically maintain the aspect ratio.

Apply Unit Measurement check using mathjs [commit #4]

After we define properties of our component, we can begin to add validation check based on Unit of Measurement. To do this, we will utilize mathjs so when we provide value to the props like <Led input-voltage="43cm"> it will cause an error.

Before doing that first let's create unit measurement called px or pixel (because mathjs not defined it by default)

import { unit, createUnit } from 'mathjs'

createUnit('px', { aliases: ['pixel', 'pixels', 'dot'] })

then we need to create wrapper function for props validation to make the implementation more easier

// baseUnit('volt')('43cm'); // will return false
const baseUnit = (unitName: string) => (value: string) => unit(value).equalBase(unit(unitName))

now we ready to apply validation check for Unit of Measurement

  @Prop({ validator: baseUnit('pixel/meter') }) scale!: string
  @Prop({ validator: baseUnit('meter') }) size!: string

  @Prop({ validator: baseUnit('watt') }) maxPower!: string
  @Prop({ validator: baseUnit('volt') }) inputVoltage!: string
  @Prop({ validator: baseUnit('ampere') }) inputCurrent!: string

And now when we assign the wrong Unit, it will produce an error

UoM check demo

Calculate brightness and size of LED [commit #5]

Before doing a calculation that use automatic Unit Conversion provided by mathjs, we can create a wrapper function to make it less to write.

Create a wrapper function calculate(expr: string) to calculate a mathematical expression

// calculate('1m / 100cm') will return 1
const calculate = (expression: string) => compile(expression).eval()
// calculate('1px/cm / 100cm').toNumber('pixel') will return 100

and don't forget to import function compile

import { unit, createUnit, compile } from 'mathjs'

After that, we can do some calculation.
Because

brightness~% = \frac{I_{in} \times V_{in}} {P_{max}}

and luckily attributes fill-opacity range value are from 0 to 1, we can write the computed properties for brightness

get brightness(): number { return calculate(`${this.inputVoltage}*${this.inputCurrent} / ${this.maxPower}`) }

If we take a closer look, the dimension SVG drawing of the LED is more like

LED size proportion ilustration

where a height of the led bulb is 30% of the height of drawing. If we want size props to reflect the size of the bulb we can calculate it by

height = 130%~(size \times scale)

height = svg led height in pixel (px)
size = bulb size in milimeter (mm)
scale = scaling ratio in pixel per milimeter (px/mm)

Now, we can write the computed properties of height

get height(): number { return calculate(`1.3 * ${this.size}*${this.scale}`).toNumber('pixel') }

We can see that opacity of the led bulb are depend on input voltage/current and also the size of the LED can be adjusted.

calculation demo

Add default value [commit #6]

So far we have define the basic need of LED component. However, if we defined the LED component and not assign all the properties, for example

<Led input-voltage="2.3V" input-current="13mA" />

will give you an error

error lack props

To avoid that, we can define the default value of all Led props

  @Prop({ validator: baseUnit('pixel/meter'), default: '10px/mm' }) scale!: string
  @Prop({ validator: baseUnit('meter'), default: '3mm' }) size!: string

  @Prop({ validator: baseUnit('watt'), default: '43mW' }) maxPower!: string
  @Prop({ validator: baseUnit('volt'), default: '0V' }) inputVoltage!: string
  @Prop({ validator: baseUnit('ampere'), default: '0mA' }) inputCurrent!: string

notice that by default the Led is off because default value for input-voltage and input-current is 0. Now the component will work if all or one of the props is not assigned with value.

Make LED broken when over voltage/current [commit #7]

In real world, if we short-circuited the LED (neither by over voltage or over current) then the LED will broke. This is happening because input power P_{in} is passed the total consumption power P_{max} that specific LED can handle.
In summary

led broken equation

in short, the brightness value is not allowed to be more than 100%. Now, in brightness computed property, we need to add a condition where the Led should be broken

  broken: boolean = false
  get brightness(): number {
    const result = calculate(`${this.inputVoltage}*${this.inputCurrent} / ${this.maxPower}`)
    this.broken = (result > 1.00)
    return this.broken ? 0 : result
  }

Notice that I also add vue member data broken so that we can display text "BROKEN" when we short-circuit it. To apply it

  <svg :height="height" viewBox="85 -12 108 232">
    <text v-if="broken" x="103" y="45">BROKEN</text>

Now our Led component is more like LED in the real world

demo led broken

Build Led.vue as webcomponent

As a bonus, vue-cli have the ability to build .vue component into webcomponent. This makes it easier to share and demonstrate your work. To build our Led.vue as a webcomponent execute

yarn build --target wc --name hardware-led src/components/Led.vue

Noted that you need to give the webcomponent a name (e.g --name hardware-led). The name must contain 2 or more word separated with a hyphen(-). After you build it as a webcomponent, you will see folder dist in your root project. There is also dist/demo.html that shows how to use the webcomponent you build.

You can open dist/demo.html in the browser, edit it, refresh the browser and see the change.

<title>hardware-led demo</title>
<script src="https://unpkg.com/vue"></script>
<script src="./hw-led.js"></script>

<hardware-led input-voltage="1.3V" input-current="13mA"></hardware-led>

Proof of Work Done

https://github.com/DrSensor/vue-hardware/commits/led

H2
H3
H4
3 columns
2 columns
1 column
4 Comments