WinCC Flexible 2008: Loading CSV Datalogs into HMI Tag Arrays

David Krause19 min read
HMI ProgrammingSiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

WinCC Flexible 2008 (and updates through SP5) is still deployed on SIMATIC Panels and PC-based HMI stations, and a recurring engineering task is loading a comma-separated recipe or datalog into a DB array on an S7-300/400 or S7-200 controller. The naive path looks friendly: drop a VBScript on a button, point FileSystemObject at a USB stick, loop through the rows, write to SmartTags(...), and let the tag polling push the values to the PLC. Two constraints in the runtime break this approach:

  1. HMI tags are single-dimension. You cannot declare MyArray[0..10,0..10] as one tag; the tag editor only accepts [0..N].
  2. Default tag types are numeric. A 37-character serial code does not fit into a 16-bit INT or 32-bit DINT, and date strings with / or : separators can stall the parser if the wrong tag type is selected.

This reference shows how to flatten a multi-dimension PLC array into one or more HMI tags, parse CSV rows in VBScript, push the values to the PLC in 25-element chunks, and migrate to a STRING or STRUCTURE container when the serial length exceeds the integer boundary. The official Siemens VBScript file-IO FAQ at support.industry.siemens.com - VBScript file read/write in WinCC Flexible documents the underlying file primitives referenced throughout this guide.

Prerequisites

Item Requirement
Engineering tool WinCC Flexible 2008 SP2 or later (SP5 recommended for TP/OP/MP 277, MP 377, and PC Runtime)
Runtime target PC Runtime, Panel PC 477/577/677, MP 277/377, TP 277 with Windows CE 5.0/6.0, or WinCC Flexible RT on Windows XP/7/10
Controller SIMATIC S7-200 (PPI/MPI/Profibus), S7-300, or S7-400 with a DB array up to 500 elements per dimension
Connection MPI/Profibus at 1.5 Mbit/s or PROFINET 100 Mbit/s; 1.5 Mbit/s MPI is the worst case for the chunked-transfer sizing
Storage USB stick on panel (\Storage Card USB\), network share on PC Runtime (\\server\share\recipes\), or local CF card (\Storage Card CF\)
CSV format ANSI/Windows-1252 or pure ASCII with CR/LF line endings; first row optional header
Recipe row count Up to 500 rows; beyond this, raise the chunked-transfer delay to 1500 ms or split across multiple recipes
Tag count budget TP 277 = 4096 tags, MP 377 = 8192 tags, PC Runtime = 32 768 tags. A 500-row 4-field structure array consumes 2000 tags
WinCC Flexible 2008 does not reliably support multi-byte code pages for serial codes with non-ASCII characters. Save the CSV as ANSI/Windows-1252 or pure ASCII for serial numbers, and use a separate UTF-8 file for descriptive text if needed.

The Single-Dimension HMI Tag Constraint

Every HMI tag in WinCC Flexible 2008 lives in the tag editor as a flat list. The Array elements column accepts only a single range such as [0..9] or [1..500]. There is no syntax for [0..10,0..10], and there is no rank property in the tag interface. A multi-dimension PLC array, for example:

DATA_BLOCK "MyDB"
  STRUCT
    MyArray : ARRAY [0..10, 0..10] OF INT;  // 121 elements
  END_STRUCT;
END_DATA_BLOCK

cannot be mirrored to a single HMI tag. The SmartTags() call would fail at runtime with Type mismatch because the runtime looks for a one-dimensional buffer. The accepted workaround is to create one HMI tag per row of the source array and to append the row data sequentially into each tag, as documented for the WinCC Unified successor in TIA Portal Cloud - Creating Array Tags (RT Unified). The same flattened layout, declared in the WinCC Flexible tag editor, gives the legacy runtime the contiguous buffer it expects.

Two practical points about the constraint:

  • The element count of each HMI tag is limited to 1000 by the editor. A 2D PLC array whose product of dimensions exceeds 1000 must be split across multiple HMI tags regardless.
  • The HMI tag type must match the row data type. A mixed array in the PLC (for example, ARRAY [0..10, 0..10] OF STRUCT with a STRING and a DINT) cannot be flattened into a single INT array; the only valid target is a structure array, covered later in this guide.

Data Type Mapping and Runtime Functions

The HMI tag type system is a strict subset of the PLC type system. The script engine accepts a narrow set of conversions, and the wrong choice truncates or rejects the value at write time.

PLC S7 type WinCC Flexible HMI type VBScript conversion Range / notes
BOOL BOOL CBool, True/False 1 bit, 0 or 1
INT INT CInt, 16-bit signed -32768 to 32767
DINT DINT CLng, 32-bit signed -2147483648 to 2147483647
REAL REAL CDbl, IEEE 754 32-bit 7 significant digits; CSng not supported
WORD WORD Hex literal &H0000..&HFFFF 16-bit unsigned bit field
DWORD DWORD Hex literal &H00000000..&HFFFFFFFF 32-bit unsigned bit field
STRING[N] STRING[N+1] Direct string assignment Max 254 in WinCC Flex; allocate +1 for the null terminator
DATE_AND_TIME (DT) DATE_AND_TIME (DT) String conversion via FC 6 / FC 7 8 bytes BCD; never assign directly from a VBScript Date
CHAR STRING[2] Mid() / Chr() Single ASCII char; +1 for the null
S5TIME S5TIME String conversion via FC 40 16-bit BCD time base and value

The two runtime functions the script uses to read and write tags are SmartTags() and HiField():

  • SmartTags(tagname) returns an object handle to a tag. Assigning to SmartTags("x") = 5 writes the value; reading y = SmartTags("x") reads it. Array element access uses VBScript indexer syntax: SmartTags("a")(i) = 7. Structure field access uses dotted path: SmartTags("r")(i).Field. The object cannot be passed as a function argument - copy to a local Variant first.
  • HiField(array, index) returns the element at index from a one-dimensional source array. HiField(array, row, col) recovers the original 2D coordinates from an HMI tag that was bound to a 2D DB range. The runtime flattens row-major by default: index = row * column_count + col. Out-of-range indices return 0 silently; they do not raise an error.
Pass tags to HiField by reference through SmartTags: HiField(SmartTags("MyDB_MyArray_Source"), i, 0). The runtime resolves the bound DB area; passing a local VBScript array does not address the PLC area and returns zeros.

Flattening a 2D PLC Array into HMI Tags

Declare one HMI tag per row of the source array. The element count must equal the row length of the PLC array (11 in this example), and the tags must share a common naming prefix so the script can build the tag name with a numeric suffix.

HMI tag name PLC source range Element count Type Acquisition cycle
MyDB_MyArrayA MyDB.MyArray[0..10, 0] 11 INT 1 s
MyDB_MyArrayB MyDB.MyArray[0..10, 1] 11 INT 1 s
MyDB_MyArrayC MyDB.MyArray[0..10, 2] 11 INT 1 s
MyDB_MyArrayD MyDB.MyArray[0..10, 3] 11 INT 1 s
MyDB_MyArray_Source MyDB.MyArray[0..10, 0..10] 121 INT 1 s

The script writes each row as a flat element using SmartTags("MyDB_MyArrayA")(i) and reads from the PLC source using HiField(SmartTags("MyDB_MyArray_Source"), i, 0). The complete pattern is:

Dim i, j
For i = 0 To 10
  For j = 0 To 3
    Select Case j
      Case 0
        SmartTags("MyDB_MyArrayA")(i) = HiField(SmartTags("MyDB_MyArray_Source"), i, 0)
      Case 1
        SmartTags("MyDB_MyArrayB")(i) = HiField(SmartTags("MyDB_MyArray_Source"), i, 1)
      Case 2
        SmartTags("MyDB_MyArrayC")(i) = HiField(SmartTags("MyDB_MyArray_Source"), i, 2)
      Case 3
        SmartTags("MyDB_MyArrayD")(i) = HiField(SmartTags("MyDB_MyArray_Source"), i, 3)
    End Select
  Next
Next
The Source tag MyDB_MyArray_Source must be a 121-element single-dimension INT tag declared on the HMI and bound to the full MyDB.MyArray[0..10,0..10] range. The runtime flattens the source automatically; HiField(...,row,col) then recovers the original 2D coordinates. This is the only path that round-trips a 2D PLC array through a single-dimension HMI tag.

CSV File I/O in WinCC Flexible VBScript

WinCC Flexible 2008 exposes the standard VBScript FileSystemObject through the runtime. The two functions that matter for CSV ingest are OpenTextFile with read mode 1 (ForReading) and the ReadLine method. The path differs by runtime target:

Runtime Path prefix for USB Path prefix for network Notes
PC Runtime (Windows XP/7/10) E:\Recipes\ \\server\share\Recipes\ Drive letter assigned by the OS
Panel PC 477/577/677 \Storage Card USB\ \\server\share\Recipes\ UNC paths require SMB1 enabled on the server
MP 277/377, TP 277 (WinCE) \Storage Card USB\ Not supported WinCE panels have no network stack for UNC paths
PC Runtime with WinCC Flexible RT C:\Recipes\ \\nas01\Recipes\ RT service runs as SYSTEM; UNC needs stored credentials

The base read loop using the Siemens-documented VBScript file API is:

Dim fso, ts, sLine, aFields
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts  = fso.OpenTextFile("\Storage Card USB\recipes\Recipe_007.csv", 1, False, 0)
Do While Not ts.AtEndOfStream
  sLine = ts.ReadLine
  aFields = Split(sLine, ",")
  ' process aFields(0), aFields(1), ...
Loop
ts.Close
Set ts  = Nothing
Set fso = Nothing

The fourth argument to OpenTextFile selects the code page: 0 = ASCII, -1 = Unicode, -2 = system default. Pin the code page to 0 for serial codes that must be byte-exact across panels. The third argument, False, disables file creation if the path does not exist - the OpenTextFile call then returns error 53 (file not found), which the script must trap with On Error Resume Next or an explicit fso.FileExists check.

Parsing Serial Codes and Date Strings

Two parsing issues typically break a CSV import in WinCC Flexible:

Serial codes longer than 16 characters

The HMI tag data types INT, DINT, REAL, and WORD cannot store a 37-character serial. Switch the HMI tag to STRING with Length = 38 (one extra for the VBScript null terminator) and bind it to a STRING[38] field in the DB. The script writes the field as a single string element:

SmartTags("MyDB_SerialCode")(i) = aFields(0)

For panel projects, limit the STRING length to 254 (the WinCC Flexible ceiling for STRING tags). If the serial exceeds 254 characters, declare it as a WSTRING in the DB and read it in 254-character slices on the HMI, then assemble on the controller side. The WSTRING path is supported on S7-300/400 from STEP 7 V5.4 SP3 with the WSTRING library.

Date strings with / and :

VBScript's Split with a , delimiter ignores / and : within a field, but downstream conversion to DATE_AND_TIME (DT) on the S7 side fails if the field contains the locale-specific date separator. Strip the field, normalize to ISO 8601 YYYY-MM-DD HH:MM:SS in the script, and then call CDate() only if the runtime supports it. The safer pattern is to push the field as a STRING and let the PLC's FC 6 / FC 7 or a user FC parse the structure:

Dim sRaw, sIso
sRaw = Trim(aFields(1))                ' e.g. "07/14/2024 13:45:02"
sIso = Mid(sRaw,7,4) & "-" & Mid(sRaw,1,2) & "-" & Mid(sRaw,4,2) & " " & Mid(sRaw,12,8)
SmartTags("MyDB_TimestampIso")(i) = sIso

For European locales where the date format is DD/MM/YYYY rather than MM/DD/YYYY, swap the Mid indices. Detect the format by inspecting the day field: if the second token is greater than 12, the source is unambiguously DD/MM/YYYY and the script reorders without ambiguity.

Chunked Transfer and Performance Tuning

Writing 500 elements in one script pass overloads the WinCC Flexible runtime and the script returns without committing. The empirical upper limit on a TP 277 at 1.5 Mbit/s MPI is 25 INT elements per script invocation. The pattern is to schedule the write as a recurring job with a 1000 ms delay between chunks.

Const CHUNK = 25
Const TOTAL = 500
Const DELAY_MS = 1000
Dim i, iStart
iStart = SmartTags("ChunkIndex")         ' persistent HMI tag, INT, range 0..500
For i = 0 To CHUNK - 1
  If (iStart + i) >= TOTAL Then Exit For
  SmartTags("MyDB_MyArrayA")(iStart + i) = SmartTags("CSV_BufferA")(iStart + i)
Next
iStart = iStart + CHUNK
If iStart >= TOTAL Then iStart = 0       ' roll over for next recipe
SmartTags("ChunkIndex") = iStart
Pair this with a WinCC Flexible Scheduled task set to 1000 ms. The runtime invokes the script, writes 25 elements, and the next tick processes the next 25. A full 500-element recipe completes in 20 seconds, which matches the typical 30-second operator timeout for a recipe changeover on a TP 277.

The throughput ceiling is set by the bus and the tag acquisition cycle. The 1.5 Mbit/s MPI bus carries one write transaction of 14 bytes per INT (2 bytes payload + 12 bytes Profibus-MPI overhead) in 75 microseconds at the wire level, but the per-transaction software overhead on the panel side is 4-10 ms. Effective throughput: 100-250 INT elements per second. At 25 elements per 1000 ms tick, the chunked transfer uses 10-25 percent of the bus capacity and leaves headroom for concurrent operator activity.

Connection Effective throughput Recommended chunk size Recommended delay
MPI 1.5 Mbit/s 100-250 elements/s 25 1000 ms
Profibus 12 Mbit/s 500-1500 elements/s 100 500 ms
PROFINET 100 Mbit/s 2000+ elements/s 500 500 ms
Ethernet/IP (PG/PC) 5000+ elements/s 500 200 ms

The Tag acquisition cycle in the HMI tag properties also affects how fast a script write reaches the PLC. A tag with a 100 ms cycle reflects changes within 100 ms; a 2 s cycle queues writes and may drop intermediate values. For recipe data, set all 25 chunked tags to a uniform 1 s cycle to match the scheduled-task rate and avoid buffer overflows in the runtime. The minimum cycle is 100 ms; values below that are silently clamped to 100 ms and may produce unexpected log entries on the panel.

Using Structures as an Array Container

When a recipe row contains mixed data types - INT, STRING, DINT - and the row count is fixed, declare a Structure in the HMI tag editor with one element per column, and instantiate it as a flat list of HMI tags with a numeric suffix. The runtime treats each element of the structure as an independent tag, so the single-dimension rule is preserved while the script can group fields by row index.

Structure field HMI type PLC type (DB) Length
SerialCode STRING STRING[38] 38
Timestamp STRING STRING[20] 20
ValueA DINT DINT 4
ValueB REAL REAL 4

Then declare an array of structure instances, for example RecipeRow[0..499], where each element carries the four fields. The script access pattern is:

SmartTags("RecipeRow")(i).SerialCode = aFields(0)
SmartTags("RecipeRow")(i).Timestamp  = sIso
SmartTags("RecipeRow")(i).ValueA     = CLng(aFields(2))
SmartTags("RecipeRow")(i).ValueB     = CDbl(aFields(3))

This is the closest equivalent in WinCC Flexible 2008 to the array of UDT available in STEP 7 V5.x and in TIA Portal. The limitation is that the structure must be declared manually in the HMI tag editor and cannot be a single tag - each structure instance is still a flat array of single-dimension tags behind the scenes. A 500-row structure array consumes 4 HMI tags per row (4 * 500 = 2000 tags); a TP 277 supports up to 4096 HMI tags, so a 1000-row recipe is the practical ceiling on that panel. Larger recipes require either a Panel PC 677 (8192 tags) or a PC Runtime (32 768 tags).

Complete Script: Read-CSV-and-Write-PLC

The following script ties all the pieces together. It targets a PC Runtime with a network share, reads a 500-row recipe with four fields (serial, timestamp, valueA, valueB), writes into a structure-array of HMI tags, and triggers the chunked transfer to the PLC.

Const RECIPE_PATH = "\\nas01\Recipes\Recipe_007.csv"
Const TOTAL_ROWS  = 500
Const CHUNK       = 25

Dim fso, ts, sLine, aFields, i, sIso, sRaw
Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FileExists(RECIPE_PATH) Then
  SmartTags("LoadStatus") = "ERR: file not found"
  Exit Sub
End If

Set ts = fso.OpenTextFile(RECIPE_PATH, 1, False, 0)
i = 0
Do While (Not ts.AtEndOfStream) And (i < TOTAL_ROWS)
  sLine = ts.ReadLine
  aFields = Split(sLine, ",")
  If UBound(aFields) >= 3 Then
    sRaw = Trim(aFields(1))
    sIso = Mid(sRaw,7,4) & "-" & Mid(sRaw,1,2) & "-" & Mid(sRaw,4,2) & " " & Mid(sRaw,12,8)
    SmartTags("RecipeRow")(i).SerialCode = aFields(0)
    SmartTags("RecipeRow")(i).Timestamp  = sIso
    SmartTags("RecipeRow")(i).ValueA     = CLng(aFields(2))
    SmartTags("RecipeRow")(i).ValueB     = CDbl(aFields(3))
    i = i + 1
  End If
Loop
ts.Close
Set ts  = Nothing
Set fso = Nothing

SmartTags("RowsLoaded")   = i
SmartTags("LoadStatus")   = "OK"
SmartTags("ChunkIndex")   = 0           ' reset the chunked transfer
SmartTags("TransferRun")  = True        ' signal the scheduled task to start

The companion scheduled task (1000 ms cycle) handles the chunked write to the PLC DB. It runs until TransferRun clears, then idles.

' Scheduled task "TransferRecipeChunk", cycle 1000 ms
If Not SmartTags("TransferRun") Then Exit Sub
Dim k
For k = 0 To 24
  If (SmartTags("ChunkIndex") + k) >= SmartTags("RowsLoaded") Then
    SmartTags("TransferRun")  = False
    SmartTags("ChunkIndex")   = 0
    Exit For
  End If
  SmartTags("MyDB_RecipeRow")(SmartTags("ChunkIndex") + k).SerialCode = _
    SmartTags("RecipeRow")(SmartTags("ChunkIndex") + k).SerialCode
  SmartTags("MyDB_RecipeRow")(SmartTags("ChunkIndex") + k).ValueA     = _
    SmartTags("RecipeRow")(SmartTags("ChunkIndex") + k).ValueA
Next
SmartTags("ChunkIndex") = SmartTags("ChunkIndex") + 25

Verification, Backup, and Migration to WinCC Unified

Run the following checks in sequence before signing off the recipe-loading function:

  1. Place a 1-row CSV on the USB stick or share, run the script, and confirm in the HMI tag diagnostics view that RowsLoaded increments to 1 and that the four fields of RecipeRow(0) match the CSV.
  2. Cross-check on the PLC side using STEP 7 Monitor/Modify on the corresponding DB. The values should appear within one tag-acquisition cycle (default 1 s) after the script terminates.
  3. Repeat with a 100-row CSV to validate the chunked transfer. The 25-element chunk completes in ~1 s, the full 100 rows in ~4 s, and the runtime CPU on a TP 277 should stay under 40 percent.
  4. Insert an intentional error: a row with a 40-character serial, a malformed date, and a non-numeric value for ValueA. The script must not crash; the offending row should be skipped, the count of RowsLoaded must match the good rows, and the runtime must continue.
  5. Verify the recipe backup: confirm the loaded DB values remain intact across a panel restart. The HMI tag values are persistent in the runtime; the PLC DB values are persistent in the controller. On a Panel PC, recipe CSV files on the CF card survive power cycles; on a WinCE panel, the USB stick holds the master copy.

For projects that will be ported to a Unified Comfort Panel (MTP700, MTP1000, MTP1200, MTP1500, MTP1900, MTP2200) or to a Unified PC Runtime, the legacy flattening pattern can be replaced by a direct UDT binding. The TIA Portal Cloud documentation at TIA Portal Cloud - Creating Array Tags (RT Unified) shows the modern declaration flow: in the HMI tag table of the Unified device, double-click Add in the Name column, set Data type to WString or to a PLC UDT, declare the array as [0..500] for 1D or [0..10,0..10] for 2D, and bind the tag directly to the PLC DB array. The 25-element chunked transfer is no longer required on Unified; the runtime handles arrays of 500+ elements in a single write cycle, and the STRING length is up to 254 characters in the HMI tag by default (extendable to 16384 for WSTRING on Unified PC).

Troubleshooting Matrix

Symptom Root cause Fix
Type mismatch on SmartTags("MyArray") for a 2D PLC array HMI tag is single-dimension, source is multi-dimension Flatten the source into one HMI tag per row and use HiField(...,row,col) in the script
Script terminates after 25-30 elements when writing 500 Runtime tag-acquisition buffer overflows Chunk the write in 25-element loops with a 1000 ms scheduled task between chunks
Serial code truncated to 16 characters HMI tag is INT, not STRING Change HMI tag to STRING[38] and bind to STRING[38] in the DB
Date field with / separators stalls the script Locale-specific separator breaks the implicit type conversion Normalize the field to ISO 8601 in the script before writing to a STRING tag
File not found error on the panel's USB stick Path uses drive letter instead of the WinCE \Storage Card USB\ prefix Use the \Storage Card USB\ prefix on WinCE panels; the Siemens VBScript file FAQ at support.industry.siemens.com - VBScript file read/write in WinCC Flexible documents the panel-specific paths
Network share path returns Path not found on WinCE panel WinCE has no SMB client Copy the CSV to a USB stick on the panel side; only PC Runtime and Panel PC support UNC
HiField returns zero for a valid element Source tag is bound to a 1D area only, not the full 2D DB array Re-bind the source HMI tag to the full 2D DB range so the runtime flattens it correctly
Structure array element Type mismatch when assigning a STRING String length in HMI tag shorter than the source field Set STRING length to source + 1 in the HMI tag editor
Runtime CPU spikes to 100% after script runs Scheduled task fires faster than the script can finish Add a TransferRun gate at the top of the scheduled task and set the cycle to 1000 ms
Recipe load is correct on the HMI tag monitor but the PLC sees zeros Tag acquisition cycle is set to On demand rather than a fixed interval Set the cycle to 1 s on every recipe tag; On demand tags are only refreshed when the script reads them
OpenTextFile raises error 53 (file not found) on a Panel PC Path uses backslashes without escaping, or the USB stick is mounted as a different drive letter Use fso.FileExists(path) first; verify the drive letter in the panel's Control Panel > Storage
Script returns silently with no values written VBScript runtime is throttled because too many tags are polled at 100 ms Raise the slowest tags to 1 s; keep at most 20 tags at the 100 ms cycle on a TP 277

FAQ

Why does WinCC Flexible 2008 reject a multi-dimension HMI tag in the tag editor?

The runtime stores HMI tag values in a single-dimension buffer, so the tag editor accepts only one index range such as [0..9]. The fix is to declare one HMI tag per row of the PLC array and use HiField(...,row,col) in the script to address the original 2D coordinates.

What is the maximum number of array elements I can write to the PLC per script invocation?

On a TP 277 or MP 277 over MPI at 1.5 Mbit/s, the empirical limit is 25 INT elements per script invocation. Use a scheduled task at 1000 ms to drive the next chunk. PC Runtime and Panel PC can typically handle 100 elements per cycle, and PROFINET-connected panels handle 500 elements per cycle.

How do I store a 37-character serial code in a WinCC Flexible 2008 HMI tag?

Change the HMI tag data type from INT to STRING with Length = 38, bind it to a STRING[38] field in the DB, and write the field as SmartTags("SerialTag")(i) = aFields(0). The runtime will not pad or truncate as long as the source CSV is byte-exact ASCII. WinCC Flexible supports STRING tags up to 254 characters; for longer serials, use WSTRING on the controller and slice the data in 254-character chunks on the HMI.

Can I create an array of structures in WinCC Flexible 2008?

Yes, by declaring a Structure data type in the HMI tag editor with one element per column and instantiating it as a flat array of HMI tags with a numeric suffix. Access each row as SmartTags("RecipeRow")(i).FieldName. The structure array is the closest equivalent to a STEP 7 ARRAY OF UDT on the HMI side, with the caveat that each structure instance is still a flat array of single-dimension tags behind the scenes.

Where does a WinCC Flexible 2008 script look for a CSV file on a SIMATIC Panel?

WinCE panels expect \Storage Card USB\<filename> for USB and \Storage Card CF\<filename> for the CF card. PC Runtime and Panel PC accept standard Windows paths including E:\Recipes\<filename> and UNC paths such as \\server\share\<filename>. The official Siemens VBScript file-IO FAQ at support.industry.siemens.com - VBScript file read/write in WinCC Flexible documents the panel-specific paths and code-page arguments in detail.

Back to blog