Skip to content

Commit 98f954b

Browse files
committed
Adds DPP demo module
1 parent ce69709 commit 98f954b

File tree

9 files changed

+593
-1
lines changed

9 files changed

+593
-1
lines changed
2.98 KB
Binary file not shown.
2.95 KB
Binary file not shown.
2.97 KB
Binary file not shown.

aas-web-ui/public/worker.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
<template>
2+
<v-card rounded="lg" border>
3+
<v-card-title class="d-flex flex-column flex-sm-row align-start align-sm-center ga-2 px-4 py-3">
4+
<span class="text-h6">General Product Passport Information</span>
5+
<v-chip size="small" variant="tonal" color="primary">Digital Nameplate v3.0</v-chip>
6+
</v-card-title>
7+
<v-card-text class="pt-0 px-0 px-sm-4 pb-4">
8+
<v-sheet v-if="isLoading" class="px-4 pt-2" color="transparent">
9+
<v-skeleton-loader type="table-heading@3, table-row-divider@8" />
10+
</v-sheet>
11+
12+
<v-alert
13+
v-else-if="!hasSelectedAas"
14+
class="mx-4"
15+
type="info"
16+
variant="tonal"
17+
icon="mdi-information-outline"
18+
text="Select an Asset Administration Shell to load Digital Nameplate data." />
19+
20+
<v-alert
21+
v-else-if="!hasNameplateSubmodel"
22+
class="mx-4"
23+
type="warning"
24+
variant="tonal"
25+
icon="mdi-alert-outline"
26+
text="No Digital Nameplate v3.0 submodel was found for the selected AAS." />
27+
28+
<div v-else class="overflow-x-auto">
29+
<v-table density="comfortable" class="text-no-wrap border rounded-lg">
30+
<thead>
31+
<tr>
32+
<th class="text-left text-caption text-sm-body-2">Property</th>
33+
<th class="text-left text-caption text-sm-body-2">Value</th>
34+
</tr>
35+
</thead>
36+
<tbody>
37+
<tr
38+
v-for="(row, index) in generalRows"
39+
:key="`${row.idShort}-${index}`"
40+
:class="index % 2 === 0 ? 'bg-tableEven' : 'bg-tableOdd'">
41+
<td class="text-caption text-sm-body-2">{{ row.label }}</td>
42+
<td class="text-caption text-sm-body-2">
43+
<v-img
44+
v-if="row.type === 'logo' && logoSrc"
45+
:src="logoSrc"
46+
width="140"
47+
max-height="80"
48+
contain
49+
class="my-1" />
50+
<span v-else>{{ getDisplayValue(row.value) }}</span>
51+
</td>
52+
</tr>
53+
</tbody>
54+
</v-table>
55+
</div>
56+
</v-card-text>
57+
</v-card>
58+
</template>
59+
60+
<script lang="ts" setup>
61+
import { computed, onMounted, ref, watch } from 'vue';
62+
import { useReferableUtils } from '@/composables/AAS/ReferableUtils';
63+
import { useSMHandling } from '@/composables/AAS/SMHandling';
64+
import { useSMEFile } from '@/composables/AAS/SubmodelElements/File';
65+
import { useSME } from '@/composables/AAS/SubmodelElements/SubmodelElement';
66+
import { useContactInformation_v1_0Utils } from '@/composables/AAS/SubmodelTemplates/ContactInformation_v1_0Utils';
67+
import { useDppSubmodelResolver } from '@/pages/modules/DPPDemo/submodelResolver';
68+
import { useAASStore } from '@/store/AASDataStore';
69+
import { firstLangStringSetText } from '@/utils/AAS/SubmodelElements/MultiLanguagePropertyUtils';
70+
import { getCountryName } from '@/utils/LocaleUtils';
71+
72+
type GeneralRow = {
73+
idShort: string;
74+
label: string;
75+
value: string;
76+
type?: 'logo' | 'text';
77+
};
78+
79+
type SubmodelWithPath = Record<string, unknown> & { path: string };
80+
type NameplateSubmodel = Record<string, unknown> & {
81+
submodelElements?: Array<Record<string, unknown>>;
82+
};
83+
84+
const semanticIdDigitalNameplate = 'https://admin-shell.io/idta/nameplate/3/0/Nameplate';
85+
86+
const aasStore = useAASStore();
87+
const { resolveSubmodelBySemanticId } = useDppSubmodelResolver();
88+
const { getSubmodelElementByIdShort, checkIdShort, nameToDisplay } = useReferableUtils();
89+
const { setData } = useSMHandling();
90+
const { hasValue, valueToDisplay } = useSME();
91+
const { valueBlob } = useSMEFile();
92+
const { determineAddress, getTypeOfEmailAddress, getTypeOfFaxNumber, getTypeOfTelephone } =
93+
useContactInformation_v1_0Utils();
94+
95+
const selectedAas = computed(() => aasStore.getSelectedAAS);
96+
const isLoading = ref(false);
97+
const nameplateSubmodel = ref({} as NameplateSubmodel);
98+
const generalRows = ref([] as Array<GeneralRow>);
99+
const logoSrc = ref('');
100+
101+
const hasSelectedAas = computed(() => !!selectedAas.value && Object.keys(selectedAas.value).length > 0);
102+
const hasNameplateSubmodel = computed(
103+
() => !!nameplateSubmodel.value && Object.keys(nameplateSubmodel.value).length > 0
104+
);
105+
106+
onMounted(() => {
107+
initializeGeneralData();
108+
});
109+
110+
watch(
111+
() => selectedAas.value?.id,
112+
() => {
113+
initializeGeneralData();
114+
}
115+
);
116+
117+
async function initializeGeneralData(): Promise<void> {
118+
isLoading.value = true;
119+
generalRows.value = [];
120+
logoSrc.value = '';
121+
nameplateSubmodel.value = {};
122+
123+
if (!selectedAas.value || Object.keys(selectedAas.value).length === 0) {
124+
isLoading.value = false;
125+
return;
126+
}
127+
128+
const resolvedNameplate = (await resolveSubmodelBySemanticId(
129+
selectedAas.value,
130+
semanticIdDigitalNameplate
131+
)) as SubmodelWithPath;
132+
133+
if (!resolvedNameplate || Object.keys(resolvedNameplate).length === 0) {
134+
isLoading.value = false;
135+
return;
136+
}
137+
138+
if (!resolvedNameplate.path || resolvedNameplate.path.trim() === '') {
139+
isLoading.value = false;
140+
return;
141+
}
142+
143+
nameplateSubmodel.value = await setData({ ...resolvedNameplate }, resolvedNameplate.path);
144+
145+
extractRows();
146+
await extractLogo();
147+
148+
isLoading.value = false;
149+
}
150+
151+
function extractRows(): void {
152+
if (!nameplateSubmodel.value?.submodelElements || !Array.isArray(nameplateSubmodel.value.submodelElements)) {
153+
return;
154+
}
155+
156+
const productPropertyIdShorts = [
157+
'URIOfTheProduct',
158+
'ManufacturerProductDesignation',
159+
'ManufacturerProductRoot',
160+
'ManufacturerProductFamily',
161+
'ManufacturerProductType',
162+
'OrderCodeOfManufacturer',
163+
'ProductArticleNumberOfManufacturer',
164+
'SerialNumber',
165+
'YearOfConstruction',
166+
'DateOfManufacture',
167+
'HardwareVersion',
168+
'FirmwareVersion',
169+
'SoftwareVersion',
170+
'CountryOfOrigin',
171+
];
172+
173+
const manufacturerPropertyIdShorts = ['ManufacturerName', 'UniqueFacilityIdentifier', 'CompanyLogo'];
174+
175+
for (const sme of nameplateSubmodel.value.submodelElements) {
176+
for (const idShort of productPropertyIdShorts) {
177+
if (checkIdShort(sme, idShort) && hasValue(sme)) {
178+
let value = valueToDisplay(sme, '');
179+
180+
if (checkIdShort(sme, 'CountryOfOrigin')) {
181+
value = getCountryName(value, value) || value;
182+
}
183+
184+
generalRows.value.push({
185+
idShort: idShort,
186+
label: nameToDisplay(sme, 'en', idShort),
187+
value,
188+
type: 'text',
189+
});
190+
}
191+
}
192+
193+
for (const idShort of manufacturerPropertyIdShorts) {
194+
if (checkIdShort(sme, idShort) && hasValue(sme)) {
195+
generalRows.value.push({
196+
idShort: idShort,
197+
label: nameToDisplay(sme, 'en', idShort),
198+
value: idShort === 'CompanyLogo' ? 'Logo' : valueToDisplay(sme, ''),
199+
type: idShort === 'CompanyLogo' ? 'logo' : 'text',
200+
});
201+
}
202+
}
203+
}
204+
205+
extractContactRows();
206+
}
207+
208+
function extractContactRows(): void {
209+
const manufacturerContactInformation = getSubmodelElementByIdShort(
210+
'AddressInformation',
211+
nameplateSubmodel.value
212+
);
213+
214+
if (!hasValue(manufacturerContactInformation)) return;
215+
216+
const address = determineAddress(manufacturerContactInformation);
217+
if (address.trim() !== '') {
218+
generalRows.value.push({
219+
idShort: 'Address',
220+
label: 'Address',
221+
value: address,
222+
type: 'text',
223+
});
224+
}
225+
226+
const phoneSMC = getSubmodelElementByIdShort('Phone', manufacturerContactInformation);
227+
if (hasValue(phoneSMC)) {
228+
const telephoneNumberMLP = getSubmodelElementByIdShort('TelephoneNumber', phoneSMC);
229+
const typeOfTelephoneProperty = getSubmodelElementByIdShort('TypeOfTelephone', phoneSMC);
230+
if (hasValue(telephoneNumberMLP)) {
231+
const type = getTypeOfTelephone(valueToDisplay(typeOfTelephoneProperty));
232+
generalRows.value.push({
233+
idShort: 'TelephoneNumber',
234+
label: type ? `Telephone (${type})` : 'Telephone',
235+
value: valueToDisplay(telephoneNumberMLP, 'en', firstLangStringSetText(telephoneNumberMLP)),
236+
type: 'text',
237+
});
238+
}
239+
}
240+
241+
const faxSMC = getSubmodelElementByIdShort('Fax', manufacturerContactInformation);
242+
if (hasValue(faxSMC)) {
243+
const faxNumberMLP = getSubmodelElementByIdShort('FaxNumber', faxSMC);
244+
const typeOfFaxNumberProperty = getSubmodelElementByIdShort('TypeOfFaxNumber', faxSMC);
245+
if (hasValue(faxNumberMLP)) {
246+
const type = getTypeOfFaxNumber(valueToDisplay(typeOfFaxNumberProperty));
247+
generalRows.value.push({
248+
idShort: 'FaxNumber',
249+
label: type ? `Fax (${type})` : 'Fax',
250+
value: valueToDisplay(faxNumberMLP, 'en', firstLangStringSetText(faxNumberMLP)),
251+
type: 'text',
252+
});
253+
}
254+
}
255+
256+
const emailSMC = getSubmodelElementByIdShort('Email', manufacturerContactInformation);
257+
if (hasValue(emailSMC)) {
258+
const emailAddressMLP = getSubmodelElementByIdShort('EmailAddress', emailSMC);
259+
const typeOfEmailAddressProperty = getSubmodelElementByIdShort('TypeOfEmailAddress', emailSMC);
260+
if (hasValue(emailAddressMLP)) {
261+
const type = getTypeOfEmailAddress(valueToDisplay(typeOfEmailAddressProperty));
262+
generalRows.value.push({
263+
idShort: 'Email',
264+
label: type ? `Email (${type})` : 'Email',
265+
value: valueToDisplay(emailAddressMLP, 'en', firstLangStringSetText(emailAddressMLP)),
266+
type: 'text',
267+
});
268+
}
269+
}
270+
}
271+
272+
async function extractLogo(): Promise<void> {
273+
const companyLogo = getSubmodelElementByIdShort('CompanyLogo', nameplateSubmodel.value);
274+
if (!companyLogo || Object.keys(companyLogo).length === 0) return;
275+
276+
logoSrc.value = await valueBlob(companyLogo);
277+
}
278+
279+
function getDisplayValue(value: string): string {
280+
return value && value.trim() !== '' ? value : '-';
281+
}
282+
</script>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<template>
2+
<v-card rounded="lg" border>
3+
<v-card-title class="text-h6 mt-1">Product Carbon Footprint</v-card-title>
4+
<v-card-text class="pt-2">
5+
<v-sheet v-if="isLoading" color="transparent">
6+
<v-skeleton-loader type="subtitle" class="mb-2" />
7+
<v-skeleton-loader type="image" />
8+
</v-sheet>
9+
10+
<v-alert
11+
v-else-if="!hasSelectedAas"
12+
type="info"
13+
variant="tonal"
14+
icon="mdi-information-outline"
15+
text="Select an Asset Administration Shell to load Product Carbon Footprint data." />
16+
17+
<v-alert
18+
v-else-if="!hasPcfSubmodel"
19+
type="warning"
20+
variant="tonal"
21+
icon="mdi-alert-outline"
22+
text="No Carbon Footprint v1.0 submodel was found for the selected AAS." />
23+
24+
<CarbonFootprint_v1_0 v-else :submodel-element-data="pcfSubmodel" />
25+
</v-card-text>
26+
</v-card>
27+
</template>
28+
29+
<script lang="ts" setup>
30+
import { computed, onMounted, ref, watch } from 'vue';
31+
import CarbonFootprint_v1_0 from '@/components/Plugins/Submodels/CarbonFootprint_v1_0.vue';
32+
import { useDppSubmodelResolver } from '@/pages/modules/DPPDemo/submodelResolver';
33+
import { useAASStore } from '@/store/AASDataStore';
34+
35+
const semanticIdCarbonFootprint = 'https://admin-shell.io/idta/CarbonFootprint/CarbonFootprint/1/0';
36+
37+
const aasStore = useAASStore();
38+
const { resolveSubmodelBySemanticId } = useDppSubmodelResolver();
39+
40+
const selectedAas = computed(() => aasStore.getSelectedAAS);
41+
const isLoading = ref(false);
42+
const pcfSubmodel = ref({} as Record<string, unknown>);
43+
44+
const hasSelectedAas = computed(() => !!selectedAas.value && Object.keys(selectedAas.value).length > 0);
45+
const hasPcfSubmodel = computed(() => !!pcfSubmodel.value && Object.keys(pcfSubmodel.value).length > 0);
46+
47+
onMounted(() => {
48+
initializePcf();
49+
});
50+
51+
watch(
52+
() => selectedAas.value?.id,
53+
() => {
54+
initializePcf();
55+
}
56+
);
57+
58+
async function initializePcf(): Promise<void> {
59+
isLoading.value = true;
60+
pcfSubmodel.value = {};
61+
62+
if (!selectedAas.value || Object.keys(selectedAas.value).length === 0) {
63+
isLoading.value = false;
64+
return;
65+
}
66+
67+
pcfSubmodel.value = await resolveSubmodelBySemanticId(selectedAas.value, semanticIdCarbonFootprint);
68+
isLoading.value = false;
69+
}
70+
</script>

0 commit comments

Comments
 (0)