Skip to content

Commit

Permalink
add new pages for editing digital twin and preview data from altruist
Browse files Browse the repository at this point in the history
  • Loading branch information
vol4tim committed Feb 5, 2025
1 parent 1ddde74 commit 025d42e
Show file tree
Hide file tree
Showing 18 changed files with 1,007 additions and 4 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"dayjs": "^1.11.9",
"decimal.js-light": "^2.5.1",
"ethers": "^6.13.4",
"highcharts": "^12.1.2",
"highcharts-vue": "^2.0.1",
"ipfs-http-client": "^60.0.0",
"libp2p": "^1.2.4",
"mipd": "^0.0.7",
Expand Down
17 changes: 17 additions & 0 deletions src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ const routes = [
title: "Pay the robot with cryptocurrency"
}
},
{
path: "/altruist/:address?",
props: true,
name: "AltruistPage",
component: () => import("./views/altruist/Page.vue"),
meta: {
title: "Altruist"
}
},
{
path: "/dtwin",
name: "DTwinPage",
component: () => import("./views/dtwin/Page.vue"),
meta: {
title: "Digital Twin"
}
},
{ path: "/:pathMatch(.*)*", component: PathNotFound }
];

Expand Down
36 changes: 36 additions & 0 deletions src/views/altruist/AltruistChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<template>
<div v-if="isLaoded">
<datalog-chart :log="log" />
<robo-button @click="showRaw">Raw</robo-button>
<datalog-list v-if="isShowRaw" :log="log" />
</div>
<datalog-loader v-else />
</template>

<script>
import { ref, toRefs } from "vue";
import { useDatalog } from "./datalog";
import DatalogChart from "./DatalogChart.vue";
import DatalogList from "./DatalogList.vue";
import DatalogLoader from "./DatalogLoader.vue";
export default {
props: ["address"],
components: { DatalogList, DatalogLoader, DatalogChart },
setup(props) {
const datalog = useDatalog(toRefs(props).address);
const isShowRaw = ref(false);
const showRaw = () => {
isShowRaw.value = !isShowRaw.value;
};
return {
log: datalog.log,
isLaoded: datalog.isLaoded,
isShowRaw,
showRaw
};
}
};
</script>
87 changes: 87 additions & 0 deletions src/views/altruist/DatalogChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<template>
<chart :options="chartOptions" ref="chart" />
</template>

<script>
import { Chart } from "highcharts-vue";
import { getCurrentInstance, onMounted, watch } from "vue";
export default {
components: { Chart },
props: ["log"],
setup(props) {
const getSeries = (data) => {
const seriesRaw = {};
for (const row of data) {
for (const item of row.data) {
if (seriesRaw[item.name]) {
seriesRaw[item.name].data.push([
row.moment,
parseFloat(item.value)
]);
} else {
seriesRaw[item.name] = {
name: item.name,
data: [[row.moment, parseFloat(item.value)]]
};
}
}
}
return Object.values(seriesRaw);
};
const series = getSeries(props.log);
let chart;
onMounted(() => {
chart = getCurrentInstance().refs.chart.chart;
});
watch(props.log, (value) => {
if (!chart) {
return;
}
const series = getSeries(value);
if (series.length > 0) {
series.forEach((newdata) => {
const id = chart.series.findIndex((m) => m.name === newdata.name);
if (id >= 0) {
chart.series[id].setData(newdata.data, false);
} else {
chart.addSeries(newdata);
}
});
chart.redraw();
}
});
return {
chartOptions: {
lang: {
locale: "en"
},
title: {
text: ""
},
time: {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
},
xAxis: {
title: false,
type: "datetime",
labels: {
overflow: "justify",
format: "{value: %H:%M }"
}
},
yAxis: {
title: false
},
tooltip: {
valueDecimals: 2
},
series: series
}
};
}
};
</script>
37 changes: 37 additions & 0 deletions src/views/altruist/DatalogList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<template>
<div class="datalog-list">
<div
class="datalog-item"
v-for="(item, key) in log.slice().reverse()"
:key="key"
>
<span class="time">{{ new Date(item.moment).toLocaleTimeString() }}</span>
<code>{{ item.raw }}</code>
</div>
</div>
</template>

<script>
export default {
props: ["log", "isLaoded"]
};
</script>

<style scoped>
.datalog-list {
overflow-y: scroll;
height: 500px;
border: 1px solid var(--robo-color-text);
padding: 10px;
background-color: #fff;
}
.datalog-item {
padding: 5px;
margin: 5px;
border-bottom: 1px solid var(--robo-color-text);
}
.time {
font-size: 12px;
display: block;
}
</style>
5 changes: 5 additions & 0 deletions src/views/altruist/DatalogLoader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<robo-text weight="normal-italic" align="center">
<robo-loader size="1.5" /> Loading
</robo-text>
</template>
50 changes: 50 additions & 0 deletions src/views/altruist/Page.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<template>

Check warning on line 1 in src/views/altruist/Page.vue

View workflow job for this annotation

GitHub Actions / deploy

Component name "Page" should always be multi-word
<robo-layout-section>
<robo-section offset="x2" width="narrow" style="text-align: center">
<h2>Altruist</h2>
</robo-section>

<robo-section offset="x2" width="narrow">
<datalog-loader v-if="isFind" />
<robo-status v-else-if="altruistAddress === null" type="warning">
Not found
</robo-status>
<altruist-chart
v-else-if="altruistAddress !== undefined"
:address="altruistAddress"
/>
</robo-section>
</robo-layout-section>
</template>

<script>
import { watch } from "vue";
import AltruistChart from "./AltruistChart.vue";
import DatalogLoader from "./DatalogLoader.vue";
import { useFindAltruist } from "./dtwin.js";
export default {
props: ["address"],
components: { AltruistChart, DatalogLoader },
setup(props) {
const { address: altruistAddress, isFind, runFind } = useFindAltruist();
watch(
() => props.address,
(address) => {
if (address) {
altruistAddress.value = address;
} else {
runFind();
}
},
{ immediate: true }
);
return {
altruistAddress,
isFind
};
}
};
</script>
113 changes: 113 additions & 0 deletions src/views/altruist/datalog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useRobonomics } from "@/hooks/useRobonomics";
import { onUnmounted, ref, watch } from "vue";

const mapName = new Map([
["p1", "PM10"],
["p2", "PM2.5"],
["nm", "Noise Max."],
["na", "Noise Avg."],
["t", "Temperature"],
["p", "Pressure"],
["h", "Humidity"]
]);

/**
* Given a key, return the corresponding name if the key exists in the map.
* Otherwise, return the key itself.
*
* @param {string} key
* @return {string}
*/
const getName = (key) => {
const name = mapName.get(key);
if (name) {
return name;
}
return key;
};

/**
* Parses a comma-separated string of key-value pairs into an array of objects.
* Each object contains the original key, its mapped name from the mapName if available,
* and the corresponding value.
*
* @param {string} data - A string containing key-value pairs separated by commas,
* where each pair is separated by a colon.
* @return {Array<{key: string, name: string, value: string}>} - An array of objects
* with parsed key, name, and value.
*/
const parseData = (data) => {
return data.split(",").map((item) => {
const [key, value] = item.split(":");
return {
key,
name: getName(key),
value
};
});
};

/**
* Hook to read and subscribe to Robonomics datalog events from a given address.
*
* @param {string} address - The Robonomics address to read.
* @return {{log: Ref<Array<{moment: number, data: Array<{key: string, name: string, value: string}>, raw: string}>}, isLaoded: Ref<boolean>}}
* A reactive object containing the log and a boolean indicating if the log is loaded.
*/
export const useDatalog = (address) => {
const { isReady, getInstance } = useRobonomics();

const isLaoded = ref(false);
const log = ref([]);
let unsubscribe;

const read = async (address) => {
const robonomics = getInstance();
const data = await robonomics.datalog.read(address);
return data.map((item) => {
return {
moment: item[0].toNumber(),
data: parseData(item[1].toHuman()),
raw: item[1].toHuman()
};
});
};

const listener = async (address) => {
const robonomics = getInstance();
unsubscribe = await robonomics.datalog.on({}, (result) => {
for (const item of result) {
if (item.data[0].toHuman() === address) {
log.value.push({
moment: item.data[1].toNumber(),
data: parseData(item.data[2].toHuman()),
raw: item.data[2].toHuman()
});
}
}
});
};

onUnmounted(() => {
if (unsubscribe) {
unsubscribe();
}
});

watch(
[address, isReady],
async ([address, isReady]) => {
if (isReady) {
log.value = await read(address);
await listener(address);
isLaoded.value = true;
}
},
{ immediate: true }
);

return {
log,
isLaoded
};
};
Loading

0 comments on commit 025d42e

Please sign in to comment.