Skip to content

Commit

Permalink
feat: building block for encoding (#47)
Browse files Browse the repository at this point in the history
* add manufacturers.json for mapping devices

* feat: implement building block for encoding

* feat(FE): implement building block for encoding

* fix: product name should not contain manufacturer name
  • Loading branch information
muktihari authored Nov 28, 2023
1 parent d74c579 commit 51eab6e
Show file tree
Hide file tree
Showing 19 changed files with 794 additions and 133 deletions.
82 changes: 51 additions & 31 deletions src/components/OpenActivity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ import { Summary } from '@/spec/summary'
:isWebAssemblySupported="isWebAssemblySupported"
>
</TheNavigatorInput>
<div style="font-size: 0.8em" class="pt-1">Supported files: *.fit, *.gpx, *.tcx</div>
<Transition>
<div style="font-size: 0.8em" class="pt-1">
<span v-if="isActivityServiceReady"> Supported files: *.fit, *.gpx, *.tcx </span>
<span v-else>
Instantiating WebAssembly <i class="fas fa-spinner fa-spin"></i>
</span>
</div>
</Transition>
</div>
<div class="mb-3" v-if="isActivityFileReady">
<TheSummary
Expand Down Expand Up @@ -286,6 +293,7 @@ import { Summary } from '@/spec/summary'

<script lang="ts">
import { ActivityFile, Lap, Record, SPORT_GENERIC, Session } from '@/spec/activity'
import { DecodeResult, EncodeResult, ManufacturerListResult } from '@/spec/activity-service'
import { Feature } from 'ol'
import { GeoJSON } from 'ol/format'
import { shallowRef } from 'vue'
Expand All @@ -297,24 +305,6 @@ if (isWebAssemblySupported == false) {
alert('Sorry, it appears that your browser does not support WebAssembly :(')
}
class Result {
err: string | null = null
activities: Array<ActivityFile>
decodeTook: number
serializationTook: number
totalElapsed: number
constructor(json?: any) {
const casted = json as Result
this.err = casted?.err
this.activities = casted?.activities
this.decodeTook = casted?.decodeTook
this.serializationTook = casted?.serializationTook
this.totalElapsed = casted?.totalElapsed
}
}
// shallowRef
const sessions = shallowRef(new Array<Session>())
const activities = shallowRef(new Array<ActivityFile>())
Expand All @@ -330,19 +320,20 @@ const selectedSessions = shallowRef(new Array<Session>())
const selectedLaps = shallowRef(new Array<Lap>())
const selectedFeatures = shallowRef(new Array<Feature>())
const selectedGraphRecords = shallowRef(new Array<Record>())
const manufacturerList = shallowRef(new ManufacturerListResult())
export default {
data() {
return {
loading: false,
decodeWorker: new Worker(new URL('@/workers/activity-service.ts', import.meta.url), {
activityService: new Worker(new URL('@/workers/activity-service.ts', import.meta.url), {
type: 'module'
}),
decodeBeginTimestamp: 0,
sessionSelected: NONE,
summary: new Summary(),
hoveredRecord: new Record(),
hoveredRecordFreeze: new Boolean()
hoveredRecordFreeze: new Boolean(),
isActivityServiceReady: false
}
},
computed: {
Expand Down Expand Up @@ -450,17 +441,33 @@ export default {
Promise.all(promisers)
.then((arr) => {
this.decodeBeginTimestamp = new Date().getTime()
this.loading = true
this.decodeWorker.postMessage(arr)
this.activityService.postMessage({ type: 'decode', input: arr })
})
.catch((e: string) => {
console.log(e)
alert(e)
})
},
decodeWorkerOnMessage(e: MessageEvent) {
const result = new Result(e.data)
activityServiceOnMessage(e: MessageEvent) {
const [type, result, elapsed] = [e.data.type, e.data.result, e.data.elapsed]
switch (type) {
case 'isReady':
this.isActivityServiceReady = true
break
case 'decode':
this.decodeHandler(result, elapsed)
break
case 'encode':
this.encodeHandler(result, elapsed)
break
case 'manufacturerList':
this.manufacturerListHandler(result)
break
}
},
decodeHandler(result: DecodeResult, elapsed: number) {
result = new DecodeResult(result)
if (result.err != null) {
console.error(`Decode: ${result.err}`)
alert(`Decode: ${result.err}`)
Expand All @@ -469,23 +476,34 @@ export default {
}
// Instrumenting...
const totalDuration = new Date().getTime() - this.decodeBeginTimestamp
console.group('Decoding:')
console.group('Spent on WASM:')
console.debug('Decode took:\t\t', result.decodeTook, 'ms')
console.debug('Serialization took:\t', result.serializationTook, 'ms')
console.debug('Total elapsed:\t\t', result.totalElapsed, 'ms')
console.groupEnd()
console.debug('Interop WASM to JS:\t', totalDuration - result.totalElapsed, 'ms')
console.debug('Total elapsed:\t\t', totalDuration, 'ms')
console.debug('Interop WASM to JS:\t', elapsed - result.totalElapsed, 'ms')
console.debug('Total elapsed:\t\t', elapsed, 'ms')
console.groupEnd()
requestAnimationFrame(() => {
this.preprocessing(result)
this.scrollTop()
})
},
preprocessing(result: Result) {
encodeHandler(result: EncodeResult, elapsed: number) {
result = new EncodeResult(result)
if (result.err != null) {
console.error(`Encode: ${result.err}`)
alert(`Encode: ${result.err}`)
this.loading = false
return
}
},
manufacturerListHandler(result: ManufacturerListResult) {
manufacturerList.value = new ManufacturerListResult(result)
},
preprocessing(result: DecodeResult) {
console.time('Preprocessing')
activities.value = result.activities
Expand Down Expand Up @@ -756,7 +774,9 @@ export default {
},
mounted() {
document.getElementById('fileInput')?.addEventListener('change', this.fileInputEventListener)
this.decodeWorker.onmessage = this.decodeWorkerOnMessage
this.activityService.onmessage = this.activityServiceOnMessage
this.activityService.postMessage({ type: 'isReady' })
this.activityService.postMessage({ type: 'manufacturerList' })
this.selectSession(this.sessionSelected)
}
}
Expand Down
73 changes: 73 additions & 0 deletions src/spec/activity-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { ActivityFile } from './activity'

export class DecodeResult {
err: string | null = null
activities: Array<ActivityFile>
decodeTook: number
serializationTook: number
totalElapsed: number

constructor(json?: any) {
const casted = json as DecodeResult

this.err = casted?.err
this.activities = casted?.activities
this.decodeTook = casted?.decodeTook
this.serializationTook = casted?.serializationTook
this.totalElapsed = casted?.totalElapsed
}
}

export class EncodeResult {
err: string | null = null
encodeTook: number
serializationTook: number
totalElapsed: number
fileName: string
fileType: string
fileBytes: Uint8Array

constructor(data?: any) {
const casted = data as EncodeResult
this.err = casted?.err
this.encodeTook = casted?.encodeTook
this.serializationTook = casted?.serializationTook
this.totalElapsed = casted?.totalElapsed
this.fileName = casted?.fileName
this.fileType = casted?.fileType
this.fileBytes = casted?.fileBytes
}
}

export class ManufacturerListResult {
manufacturers: Manufacturer[] = []

constructor(data?: any) {
const casted = data as ManufacturerListResult
this.manufacturers = casted?.manufacturers
}
}

export class Manufacturer {
id: number = 0
name: string = ''
products: Product[] = []

constructor(data?: any) {
const casted = data as Manufacturer
this.id = casted?.id
this.name = casted?.name
this.products = casted?.products
}
}

export class Product {
id: number = 0
name: string = ''

constructor(data?: any) {
const casted = data as Product
this.id = casted?.id
this.name = casted?.name
}
}
6 changes: 0 additions & 6 deletions src/wasm/activity-service/activity/fit/creator.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package fit

import (
"strconv"

"github.com/muktihari/fit/kit/datetime"
"github.com/muktihari/fit/kit/typeconv"
"github.com/muktihari/fit/profile/typedef"
"github.com/muktihari/fit/profile/untyped/fieldnum"
"github.com/muktihari/fit/proto"
"github.com/muktihari/openactivity-fit/activity"
Expand All @@ -23,14 +19,12 @@ func NewCreator(mesg proto.Message) activity.Creator {
continue
}
m.Manufacturer = &manufacturer
m.Name = activity.FormatTitle(typeconv.ToUint16[typedef.Manufacturer](field.Value).String())
case fieldnum.FileIdProduct:
product, ok := field.Value.(uint16)
if !ok {
continue
}
m.Product = &product
m.Name += " (" + strconv.FormatUint(uint64(product), 10) + ")"
case fieldnum.FileIdTimeCreated:
m.TimeCreated = datetime.ToTime(field.Value)
}
Expand Down
31 changes: 31 additions & 0 deletions src/wasm/activity-service/activity/fit/manufacturer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package fit

type Manufacturer struct {
ID uint16
Name string
Products []ManufacturerProduct
}

func (m *Manufacturer) ToMap() map[string]any {
products := make([]any, len(m.Products))
for i := range m.Products {
products[i] = m.Products[i].ToMap()
}
return map[string]any{
"id": uint16(m.ID),
"name": m.Name,
"products": products,
}
}

type ManufacturerProduct struct {
ID uint16
Name string
}

func (p *ManufacturerProduct) ToMap() map[string]any {
return map[string]any{
"id": p.ID,
"name": p.Name,
}
}
38 changes: 35 additions & 3 deletions src/wasm/activity-service/activity/fit/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"strconv"

"github.com/muktihari/fit/decoder"
"github.com/muktihari/openactivity-fit/activity"
Expand All @@ -13,11 +14,15 @@ import (
var _ activity.Service = &service{}

type service struct {
preprocessor *preprocessor.Preprocessor
preprocessor *preprocessor.Preprocessor
manufacturers map[uint16]Manufacturer
}

func NewService(preproc *preprocessor.Preprocessor) activity.Service {
return &service{preprocessor: preproc}
func NewService(preproc *preprocessor.Preprocessor, manufacturers map[uint16]Manufacturer) activity.Service {
return &service{
preprocessor: preproc,
manufacturers: manufacturers,
}
}

func (s *service) Decode(ctx context.Context, r io.Reader) ([]activity.Activity, error) {
Expand Down Expand Up @@ -60,6 +65,11 @@ func (s *service) convertListenerResultToActivity(result *ListenerResult) *activ

s.sanitize(result)

creator := result.Creator
if creator.Manufacturer != nil && creator.Product != nil {
creator.Name = s.creatorName(*creator.Manufacturer, *creator.Product)
}

act := &activity.Activity{
Creator: *result.Creator,
Timezone: result.Timezone,
Expand Down Expand Up @@ -223,3 +233,25 @@ func (s *service) finalizeSession(ses *activity.Session) {
sesFromLaps := activity.NewSessionFromLaps(ses.Laps, ses.Sport)
activity.CombineSession(ses, sesFromLaps)
}

func (s *service) creatorName(manufacturerID, productID uint16) string {
manufacturer, ok := s.manufacturers[manufacturerID]
if !ok {
return activity.Unknown
}

var productName string
for i := range manufacturer.Products {
product := manufacturer.Products[i]
if product.ID == productID {
productName = product.Name
break
}
}

if productName == "" {
productName = "(" + strconv.FormatUint(uint64(productID), 10) + ")"
}

return manufacturer.Name + " " + productName
}
4 changes: 2 additions & 2 deletions src/wasm/activity-service/activity/fit/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func NewSession(mesg proto.Message) *activity.Session {
if !ok || sport == basetype.EnumInvalid {
continue
}
ses.Sport = activity.FormatTitle(typedef.Sport(sport).String())
ses.Sport = kit.FormatTitle(typedef.Sport(sport).String())
case fieldnum.SessionTotalMovingTime:
totalMovingTime, ok := field.Value.(uint32)
if !ok || totalMovingTime == basetype.Uint32Invalid {
Expand Down Expand Up @@ -149,7 +149,7 @@ func NewSession(mesg proto.Message) *activity.Session {
}
}

if ses.Sport == activity.FormatTitle(typedef.SportAll.String()) {
if ses.Sport == kit.FormatTitle(typedef.SportAll.String()) {
ses.Sport = activity.SportGeneric
}

Expand Down
3 changes: 2 additions & 1 deletion src/wasm/activity-service/activity/gpx/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/muktihari/openactivity-fit/activity"
"github.com/muktihari/openactivity-fit/activity/gpx/schema"
"github.com/muktihari/openactivity-fit/kit"
"github.com/muktihari/openactivity-fit/preprocessor"
)

Expand Down Expand Up @@ -60,7 +61,7 @@ func (s *service) Decode(ctx context.Context, r io.Reader) ([]activity.Activity,
for i := range gpx.Tracks { // Sessions
trk := gpx.Tracks[i]

sport := activity.FormatTitle(trk.Type)
sport := kit.FormatTitle(trk.Type)
if sport == "" || sport == "Other" {
sport = activity.SportGeneric
}
Expand Down
Loading

0 comments on commit 51eab6e

Please sign in to comment.