# File

> Values another program writes to a file in a local or network folder (a test machine's CSV, a data logger's log, a result in JSON or XML) read as tags, and lines or whole files written for another program to read: the Folder and file, Text of the file and Writing cards, what a tag reads in each format, the inbox folder, the scan that offers a tag per column or field, three worked examples and what the driver does not do.

The **File** driver reads the values another program writes to a file: the CSV a test machine adds
a row to after every sample, the log of a data logger, a result file in JSON or XML, a text file that
holds only a lot code. Each tag reads one value of the file, and its values land where every other
tag's do: in Logic, on dashboards, in the runs of a [Process](process) test bench. Where the device
allows it, the driver also writes for another program to read: a line appended to a file, or a whole
file replaced.

Reading is the main use, and it takes three answers: the folder, the file and its format. How the
file's text reads (its encoding, its column separator, its decimal separator) is read off the file
while it is left on detect, the scan offers one tag per column or field, and every tag is born read
only. What belongs to writing appears only once writing is turned on for the device.

A device of this driver is one folder and the file it reads there: two files of the same folder are
two devices. The folder is a drive and a folder (`C:\TestMachine\Logs`) or a network share and a
folder under it (`\\lab-pc\results`), and the station reaches it with the Windows access of the
account it runs under. The devices sit under the driver and may be filed in folders,
**Driver → Folder → Device → Tag**, as under any driver without a serial line or a broker.

## The driver's page {#the-drivers-page}

The line under the header reads "Each device reads one file, or the files arriving in one folder. Add a device, enter its folder, its file and its format, then scan it to add its values as tags."
For Configure connector, the page holds the driver's own Scan scope card, with the file the scan
reads (see [Discovery](#discovery)). Under it stands the **Devices** table of any driver (see
[The device table](connector-drivers#the-device-table)), grown by **Add device** at the right of its
title; empty, it reads "No devices yet." The driver's bar carries **Discover**, greyed until the Scan
scope card is complete. The driver is listed under **Files and databases**, the category that sits
between Web and APIs and Test in every list of drivers.

## Device {#device}

A device of this driver has no Connection card: the **Folder and file** card stands in its place,
followed by the **Text of the file** card and, on a device that reads one file, the **Writing** card.
The **Poll interval (ms)** of the Device card, 1000 by default, says how often the tags read the file,
each tag able to override it as on any driver.

A new device has no folder: it reads "Folder not entered yet", its page says "The station does not read this device until it has a folder, so nothing is read from it yet and there is no error to show. Enter the folder in the Folder and file card below and the station starts reading it."
and nothing is read until the folder is entered. The Base URI the header shows is the runtime
address, with the folder and the name written in URI form, and what is not on detect after them:
`file://single?folder=C%3A%5CTestMachine%5CLogs&name=Log_%7Byyyy-MM-dd%7D.csv&format=delimited`, or
`file://inbox?...` for an inbox folder.

### The Folder and file card {#the-folder-and-file-card}

| Field | Meaning | Default |
| --- | --- | --- |
| **Folder path** | "A drive and a folder, or a network share. The folder is reached with the Windows access of the account the station runs under." The box shows `C:\TestMachine\Logs` as a hint. | empty |
| **File name** | On a device that reads one file: "A name inside the folder. Parts between braces are the station's date and time: Log_{yyyy-MM-dd}.csv is a new file every day." A subfolder of the folder may be part of the name (`results\last.csv`). | empty |
| **Files to read** | The same box on an inbox folder: "The files to read, directly in the folder: * stands for any characters and ? for one, such as Test_*.csv." | empty |
| **Read from** | **One file**: "The file is read again whenever it changes. A file that grows is read from where the last read stopped." **Inbox folder**: "Each new file is read once, in arrival order, and moved to the Processed subfolder. Nothing is deleted." | **One file** |
| **File format** | **CSV and text in columns**, **Free text**, **JSON** or **XML**: how a tag finds its value in the file (see [Tag addressing](#tag-addressing)). | **CSV and text in columns** |

A name with a date part is written in braces with a .NET date and time format, on the station's own
clock: `Log_{yyyy-MM-dd}.csv` is `Log_2026-10-03.csv` today and the next day's file after midnight,
and `Data_{yyyy-MM}.txt` changes every month. Under the box the card shows which file that is now:
"Today: Log_2026-10-03.csv". Doubled braces `{{` and `}}` are braces of the name. A date part never
writes a colon, which no file name holds: `{HH-mm}`, not `{HH:mm}`.

Once the device runs, the card says which file it reads:

| The card says | When |
| --- | --- |
| "Reading now: Log_2026-10-03.csv" | The file in use, as the device reads it now. |
| "Reading now: Log_2026-10-03.csv (not there yet)" | The file does not exist yet: its tags wait and the device stays online. |
| "A tag wrote this name. After a restart the device reads its configured file again." | A tag reading the file name was written and points the device at another file (see [Writing](#writing)). |
| "3 files waiting in the inbox" | An inbox folder, with the files matching its pattern still to be read ("1 file waiting in the inbox" for one). |

A setting the card cannot take is said under the cards, and the device is not saved until it is
corrected:

| You enter | The card says |
| --- | --- |
| A folder that is not a full path, such as `Logs` | "The folder is a full path: a drive and a folder (C:\Test), or a network share and a folder under it (\\server\tests)." |
| A folder with a character no folder name holds, or a share with no folder under it (`\\server`) | "The folder holds a character no folder name holds, or is a network share without a folder under it." |
| No file name | Not refused: the device is saved and waits, "File name not entered yet" ("Files to read not entered yet" on an Inbox folder), until a name is entered. |
| A full path as the name, such as `C:\Logs\last.csv` | "The file name is a name inside the folder, not a full path: the folder goes in the Folder path box." |
| A name with `..`, such as `..\other\last.csv` | "The file name climbs out of the folder with "..". The driver never leaves its folder." |
| A name with `:` or another character no file name holds, or a part ending with a dot or a space | "The file name holds a character no file name holds (such as : or \|), or a part of it ends with a dot or a space. A date part must not write a colon: use {HH-mm}, not {HH:mm}." |
| A name Windows keeps for a device, such as `CON` or `COM1` | "Windows keeps this name for a device (CON, NUL, COM1 and the like). Choose another." |
| `*` or `?` on a device that reads one file | "Only the pattern of an inbox folder takes * or ?. Give the name of one file, or read from an inbox folder." |
| A subfolder in the pattern of an inbox folder | "An inbox is the folder itself: put the subfolder in the Folder path box and keep only the pattern here." |
| A date part not closed, or not a date format | "A date part of the name is not closed, or is not a date format. Write it between braces, such as Log_{yyyy-MM-dd}.csv." |

The save holds the same rules, and the feed says so when it refuses one:
"Device 'Tensile tester' has a file setting its driver cannot use. Open it and correct the field its panel marks."

### The Text of the file card {#the-text-of-the-file-card}

How the file's characters, columns and numbers read. Every setting starts on **Detect from the file**,
and the driver reads it off the file in use. Under a setting left so, once the device has read the
file, a line says what it found, such as "Found in the file: UTF-8". Any setting can be fixed by
picking it, for a file whose first lines do not tell.

| Field | Shown | Choices | Default |
| --- | --- | --- | --- |
| **Encoding** | always | **Detect from the file**, `UTF-8`, `Windows-1252 (ANSI)` (the encoding of older Windows programs) | **Detect from the file** |
| **Column separator** | CSV | **Detect from the file**, **Semicolon (;)**, **Comma (,)**, **Tab** | **Detect from the file** |
| **Decimal separator** | always | **Detect from the file**, **Decimal point (12.5)**, **Decimal comma (12,5)** | **Detect from the file** |
| **Header row** | CSV | 0 to 1000: "The line that holds the column names, counted from 1; 0 when the file has none." The lines before it are skipped. | 1 |

| Setting | What detecting reads |
| --- | --- |
| Encoding | A byte order mark at the start of the file, which wins over any choice; else UTF-8 when the bytes are valid UTF-8; else Windows-1252. |
| Column separator | The first of tab, semicolon and comma that splits the first lines into the same number of columns; a first line holding none of them is a file of one column. |
| Decimal separator | The comma when more numbers in the file carry a comma than a point between their digits; else the point. JSON numbers always carry the point; the setting applies to numbers written as text. |

### The Writing card {#the-writing-card}

A device that reads one file carries the **Writing** card; an inbox folder is only read and has none.

| Field | Shown | Meaning | Default |
| --- | --- | --- | --- |
| **Allow writing** | always | Off: "The device only reads. Turn writing on to append lines, replace the file or switch to another file while the station runs." On: "Tags of this device may write to the file. Each tag still only reads until its access says otherwise." | off |
| **Header line** | writing on, CSV or free text | "Written first in a file a write creates, such as Time;Pressure." One line, without a line break. | empty |
| **Line end** | writing on | **Detect from the file** (the file's own, CR LF in a new file), **CR LF, as on Windows** or **LF**. | **Detect from the file** |

Writing is turned off only once no tag of the device writes. While tags still do, the switch stays
drawn and greyed, and says why: "2 tags of this device write to the file. Make them read only before turning writing off."
("A tag of this device writes to the file. Make it read only before turning writing off." for one).

### Online, waiting and offline {#online-waiting-and-offline}

| Situation | What the device and its tags do |
| --- | --- |
| No folder entered | The device waits: "Folder not entered yet", grey dot, nothing read and no error. |
| A folder entered, no file name | The device waits: "File name not entered yet", grey dot, nothing read and no error; its page says "The station does not read this device until it has a file name, so nothing is read from it yet and there is no error to show. Enter the file name in the Folder and file card below and the station starts reading it." |
| Inbox folder, a folder entered, no files to read | The device waits: "Files to read not entered yet", grey dot, nothing read and no error; its page says "The station does not read this device until it knows which files to read, so nothing is read from it yet and there is no error to show. Enter the files in Files to read, in the Folder and file card below (such as Test_*.csv), and the station starts reading them." |
| The folder cannot be reached (a share that is down, a drive that is gone, an account without access) | The device is offline and its tags fail their reads; the folder is looked at again at most every second, and the device connects as soon as it is back. |
| The folder is reached, the file does not exist yet | The device stays online. The tags reading values have no reading and wait; **File exists** reads false. |
| Another program holds the file without sharing it | No reading this time; the next read tries again. The tag keeps its last reading, and one older than its poll interval plus 10 s counts as a failed read, as on every driver. |
| A value is not there, or does not fit the tag's type | That tag's failed read, with the reason; the device keeps reading its other tags. |

## Tag addressing {#tag-addressing}

### What a tag reads {#what-a-tag-reads}

A tag of this driver has no address box of its own: its **Source** card asks what it reads, and for a
value, where the value is in the device's format.

| **Reads** | What the tag shows | Data type |
| --- | --- | --- |
| **Value** | A value inside the file, found by the fields of the device's format below. | Boolean, Int32, Int64, Float, Double or String; Double for a new tag |
| **Whole file** | The whole content, the line ends at its end dropped: a file that holds only a lot code or a status. At most 4 MiB. | the same; String for a new tag |
| **File exists** | Whether the file in use exists now. | Boolean |
| **File size** | The file's size in bytes. | Int64 |
| **Last change** | When the file was last written: watching it change is how the Logic knows new data arrived. | DateTime |
| **File name** | The name of the file in use, inside the folder. Written, it names another file (see [Writing](#writing)). | String |
| **Line count** | "Counts every line of the file, the header row and blank lines included." | Int64 |

A type the source does not read as is refused with the ones it does: "Choose one of the types this reads as: Int64."
Picking another source brings its own type. The text taken from a file is read as the tag's type the
way the [generic serial and TCP driver](driver-generic-serial-tcp#tag-addressing) reads a message,
with the file's decimal separator: a Boolean reads `1`, `true`, `on` or `yes` and `0`, `false`, `off`
or `no`, an integer reads a whole number, and a String takes the characters as they are, so a code
such as `0412` keeps its leading zero only as a String.

Every tag is born **Read only**, and on a device that does not allow writing the **Access** field
offers nothing else. Once the device allows writing, a tag reading **Value** or **Whole file** may be
**Read and write** or **Write only**, and a tag reading **File name** **Read and write**; the facts of
the file (exists, size, last change, line count) are never written: "Nothing writes a fact of the file. Keep the tag read only."

### CSV and text in columns {#csv-and-text-in-columns}

| Field | Shown | Meaning | Default |
| --- | --- | --- | --- |
| **Column** | Reads Value | "The column's name in the header row, or its number counted from 1." A text of digits only is a number; a name ignores case and the spaces around it. On a file without a header row: "The column's number, counted from 1. The file has no header row, so its columns have no names." | empty |
| **Row to read** | Reads Value | **Last**, **By number** or **By what it holds**. "Rows are counted after the header row. By what it holds reads the last row whose key column holds the key value." | **Last** |
| **Row number** | By number | The data row, counted from 1 after the header. | empty |
| **Key column** | By what it holds | The column whose value picks the row, by name or number. | empty |
| **Key value** | By what it holds | The value the key column holds in the row to read, such as `0412`. | empty |

A field in double quotes may hold the separator; a field holding a line break is not read. A tag whose
fields cannot work is not saved, and its page says what it lacks:

| You leave or enter | The tag's page says |
| --- | --- |
| No column | "Enter the column, by its name in the header row or its number." |
| Column 0 | "Columns are counted from 1." |
| A column by name on a file without a header row | "The file has no header row, so a column is given by its number." |
| By number, with no row number | "Enter the number, counted from 1." |
| By what it holds, with no key column | "Enter the key column: the column whose value picks the row." |
| By what it holds, with no key value | "Enter what the row or the line holds." |

The read-only **Source address (wire)** line shows what the driver receives: `column?name=Force` for
the last row's Force, `column?number=3&row=number&rowNumber=5` for the third column of the fifth row,
`column?name=Force&row=match&key=Sample&equals=0412` for the row of sample 0412.

### Free text {#free-text}

| Field | Shown | Meaning | Default |
| --- | --- | --- | --- |
| **Line to read** | Reads Value | **Last** (the last line that is not blank), **By number** or **By what it holds**. "Lines are counted from the start of the file. By what it holds reads the last line containing the text." | **Last** |
| **Line number** | By number | The line, counted from 1 at the start of the file. | empty |
| **Line contains** | By what it holds | A text the line to read contains, such as `Result`. | empty |
| **Value is** | Reads Value | "The same choices as the Generic serial and TCP driver: the whole line, a fixed position, a field between separators, or a regular expression." Its choices are that driver's, the first named for a line: **The whole line**, **Fixed position** (**First character** and **Length**), **Field between separators** (**Separator** and **Field number**) and **Capture group of a pattern** (**Pattern**). | **The whole line** |

Positions and fields count from 1, a pattern is a .NET regular expression with exactly one capture
group around the value, and a line it takes longer than 250 ms to read is not read. The refusals are
the generic driver's own, such as "Give the first character of the value, counted from 1." or "The pattern needs exactly one capture group, around the value."
The wire address reads `line` followed by what applies, for example
`line?row=match&contains=Result&extract=regex&pattern=...`.

### JSON and XML {#json-and-xml}

| Field | Format | Meaning |
| --- | --- | --- |
| **JSON path** | JSON | "Members separated by dots and positions in brackets from 0, such as result.force or items[0].value." A member whose name holds a dot is written `['flow.rate']`. |
| **XML path** | XML | "Elements from the root separated by slashes, a position from 1 where an element repeats, and an attribute last, such as Result/Force or Result/@unit." Names match by their local name, the namespace prefix ignored. |

A JSON number, `true` or `false` is taken as it is, and a string is read as the tag's type; an object
or a list reads only as a String, its JSON. A JSON or XML document is read whole when it changed, at
most 4 MiB, and no DTD is read. The wire address is `json?path=result.force` or `xml?path=Result%2FForce`.

| You leave or enter | The tag's page says |
| --- | --- |
| No path | "Enter the path to the value. To read the whole file, choose Whole file." |
| A JSON path that does not parse | "This is not a JSON path: members separated by dots and positions in brackets, such as result.items[0].value." |
| An XML path that does not parse | "This is not an XML path: elements separated by slashes, a position such as Item[2], and an attribute last, such as @unit." |

## Reading {#reading}

At each read the driver asks Windows for the file's size and times, which works on a network share,
where Windows' change notices do not, and opens the file only when one of them changed. It always
opens it sharing read, write and delete, so the program writing the file is never blocked.

| The file | What the read does |
| --- | --- |
| Grew, and still starts and ends where it did | Only what was added is read, never the whole file again: a log of many megabytes costs what its new lines cost. |
| Got shorter, was written again or created again | Read again from its start. |
| Ends with a line no line end closes yet | That line waits, so a value is never read half written; once the file has stayed unchanged for 2 s, it is read as the last line. |
| JSON, XML, or a tag reading the whole file | Read whole when it changed, at most 4 MiB; a document that does not parse while it changed less than 2 s ago is taken as still being written, and the tags wait. |
| Has a line longer than 1 MiB | Not text in lines: its tags fail their reads. |

Every reading is dated by the file's last change, not by the moment it was read, so the run record
places a sample when the other program wrote it.

## The inbox folder {#the-inbox-folder}

**Read from** **Inbox folder** is for the machine that writes one file per sample (`Test_0412.json`,
`Test_0413.json`...): each new file matching the pattern is read once, in arrival order.

| Step | What happens |
| --- | --- |
| A file arrives | It is taken once it has stayed the same size and time for 1 s and no program holds it open to write. |
| Which comes first | The one created first, then the one written first, then by name. Two files that arrive together are read in two passes: none is skipped. |
| Its reading | Every tag of the device reads that file, and the file is one reading, dated by the file's time: the run record records each one. **File name** says which file the values came from. |
| Once every tag has read it | It is moved to the `Processed` subfolder, which the driver creates and whose name is the same in every language, since other programs may look for it; a name already taken there becomes `name (2).json`. Its values stay the tags' reading until the next file. |
| A move that fails | Tried again at each read; the file is not read again meanwhile. |
| The station restarts before the move | The file is read again: a reading counted twice rather than one lost. |

Nothing is ever deleted: the `Processed` subfolder grows until somebody empties it. An inbox folder is
never written.

## Writing {#writing}

On a device that reads one file and allows writing, a tag reading **Value** or **Whole file** whose
access writes says what a write does:

| Field | Shown | Meaning | Default |
| --- | --- | --- | --- |
| **A write** | the tag writes | **Append a line**: "Adds one line at the end of the file. Every write counts: none is dropped for a newer one." **Replace the file**: "Replaces the whole file with the value, through a temporary file, so no program reads it half written." | **Append a line** |
| **Line template** | Append a line | "Empty: the value written is the whole line. {value} is the value, {datetime} the moment of the write, each with a format after a colon if needed, such as {value:0.00}." The box shows `{datetime};{value}` as a hint. | empty |

| The template | Writing 12.5 appends |
| --- | --- |
| empty | `12.5` |
| `{datetime};{value}` | `2026-10-03 14:05:12;12.5` |
| `{datetime:dd/MM/yyyy HH:mm};{value:0.00}` | `03/10/2026 14:05;12.50` |

`{value:FORMAT}` takes a .NET number format, `{datetime:FORMAT}` a .NET date and time format, on the
station's clock, `yyyy-MM-dd HH:mm:ss` when none is given. A number is written with the file's
decimal separator, so the same template writes `12,50` in a file that uses the comma. A Boolean tag's
template may leave `{value}` out: written true, it appends the line, as a command. A template the
driver cannot fill is refused on the tag's page:

| You enter | The tag's page says |
| --- | --- |
| A part that is not `{value}` or `{datetime}`, or a brace not closed | "The template takes {value} and {datetime}, each with a format after a colon if needed; {{ and }} are braces of the line." |
| No `{value}` on a tag that is not Boolean | "The template has no {value}, so the value written would never reach the file. Only a true or false tag may leave it out: written true, it appends the line." |
| A line break | "The template is one line, without a line break." |
| A tag that writes, on a device that does not allow writing | "The device does not allow writing. Turn writing on in the device, or keep the tag read only." |

| A write | How it goes |
| --- | --- |
| Append a line | The line goes in one write and is flushed to disk before the write counts as done. A file the write creates gets the **Header line** first, and a file whose last line has no line end gets one first, so the line is whole. A value holding a line break is refused. Every write counts: a write still waiting on the device's queue is never dropped for a newer one to the same tag, as a command never is. |
| Replace the file | The value goes to a temporary file beside the file, flushed, which takes the file's place in one step. A newer value still waiting replaces an older one, as for any setpoint. |
| The file name | A tag reading **File name**, **Read and write**, written with another name, points the device at that file of the same folder from then on: "Writing a name here makes the device read and write that file of the same folder from then on. A name outside the folder is refused." A full path, `..`, `:` or a name Windows keeps is refused. When the device connects again, a restart included, it reads its configured file again. |
| Text | In the file's own encoding, line end and decimal separator, read off the file where the device leaves them on detect. A new file is UTF-8 without a byte order mark, with CR LF and the decimal point, unless the device says otherwise. |
| Answer | Done, or refused with the cause: the device does not allow writing, the account has no permission, the file is marked read-only (clearing that attribute lets the next write through), the disk is full, or the file is open in another program. A replace is refused while any program has the file open, an append only while a program holds it without sharing it; once that program closes the file, the next write goes through. |

The folder and the subfolders a name points to are created when a write needs them, always inside the
device's folder.

## Discovery {#discovery}

### The Scan scope card {#the-scan-scope-card}

For Configure connector, the driver's page carries its **Scan scope** card, under the line
"Reads one file and offers a tag for each column or field it finds, and for the facts of the file. Pick a device to read its file, or type a folder, a file and a format."

| Field | Meaning | Default |
| --- | --- | --- |
| **File to scan** | **A file typed here**, or a device of the driver by its name. A device's file is read with the device's settings as they are stored, and an edit of the device still waiting to be saved is saved first. | **A file typed here** |
| **Folder path** | A typed file: the folder, as on a device. | empty |
| **File name** or **Files to read** | A typed file: the name, or an inbox folder's pattern. | empty |
| **Read from** | A typed file: **One file** or **Inbox folder**. | **One file** |
| **File format** | A typed file: the format. Every text setting of a typed file is detected, the header row included: the first line is a header when none of its fields is a number and the next line has one. | **CSV and text in columns** |

What the scope still lacks is said in red under the card, and Discover stays greyed until it is
complete:

| The scope | The card says |
| --- | --- |
| A typed file with no folder | "Type the folder the file is in." |
| A typed folder that is not a full path | "'Logs' is not a folder this station can read. Write a full path, for example C:\Test or \\server\tests." |
| A typed name that is no file of the folder | "'..\last.csv' is not the name of a file inside the folder." |
| A device that no longer exists | "The device picked to scan no longer exists. Pick another file to scan." |
| A device without its folder or its file | "Device 'Tensile tester' has no folder or no file yet, or has a setting its driver cannot use. Complete its Folder and file card first." |

When the device to scan has an edit that cannot be saved, the scan does not start:
"Discovery was not started because the edit of the device to scan could not be saved."

### What the scan offers {#what-the-scan-offers}

The scan reads the file a device of one file reads now, or the newest file of an inbox folder, which
it does not move; a file that is not there, a folder that cannot be reached and a document that does
not parse end the scan with the reason. It offers one candidate, with the values it found as candidate
tags, every one **Read only**:

| Format | Candidate tags |
| --- | --- |
| CSV | One per column, reading the last row, named by its header (by its number where the header names none or names it twice), typed by the values its first rows hold: Double when every value is a number, Boolean when every one is true or false, String otherwise. A code with a leading zero, such as `0412`, stays a String. |
| Free text | The last line, a String. |
| JSON, XML | One per value, named and addressed by its path, at most 500; a JSON list offers its first 20 items, and the walk goes 8 levels deep. |
| Every format | The facts of the file: **File exists**, **File size**, **Last change**, **File name** and **Line count**. |

A scan offers at most 500 values, a CSV file's columns included. One cut there says so in the line
that closes the scan and over its results:
"The scan of result.json stopped early. A scan offers at most 500 values: add the others as tags by their column, JSON path or XML path."

A typed file's candidate is named after the file (`run` for `run.csv`), or after the folder for a name
with a date part or an inbox folder. A candidate whose folder, name and **Read from** a device of the
driver already reads (the device scanned, or another the same) is marked **Already configured**: its
card lists only the values no tag of that device reads yet, ticked, and **Add tags**, before
**Open**, adds the ticked ones to that device, read only. The rest of the scan's behaviour is on
[Discovery](connector-discovery).

A device's own page does the same without its driver's page: **Discover tags**, on the head of
its Tags card, reads that device's file as it is saved and offers only the values it has no tag
for yet (see [Discovering a device's tags](connector-discovery#discovering-a-devices-tags)).

## Example: a test machine's daily CSV {#example-a-test-machines-daily-csv}

A tensile tester writes one CSV a day in `C:\TestMachine\Logs`, named `Log_2026-10-03.csv`, with a
header row `Sample;Force;Elongation` and a row such as `0412;12,5;3,1` after every sample. Read it
through the simple path:

1. On the driver's page, **Add device**; name it `Tensile tester`.
2. On the **Folder and file** card enter **Folder path** `C:\TestMachine\Logs` and **File name**
   `Log_{yyyy-MM-dd}.csv`; keep **One file** and **CSV and text in columns**. The card shows
   "Today: Log_2026-10-03.csv", and once the device reads it, "Reading now: Log_2026-10-03.csv".
   Leave the **Text of the file** card on **Detect from the file**: it finds the semicolon and the
   decimal comma.
3. On the driver's page, pick `Tensile tester` in **File to scan** and **Discover**. The candidate
   is **Already configured** and lists `Sample` (String, keeping the zero of `0412`), `Force` and
   `Elongation` (Double), and the facts of the file. **Add tags**.

Each tag reads the last row; **Last change** moves with every new sample, which is how a Logic action
knows one arrived. At midnight the device turns to the next day's file, and until the tester writes
it, the values have no reading and **File exists** reads false. An agent does the same with the tools:

```
device_upsert name="Tensile tester" driver=File fileFolder="C:\TestMachine\Logs" fileName="Log_{yyyy-MM-dd}.csv" fileFormat=Delimited
device_discover driver=File fileDevice="Tensile tester"
tag_upsert device="Tensile tester" name=Force address=Force opcUaType=Double
tag_upsert device="Tensile tester" name=Sample address=Sample opcUaType=String
tag_upsert device="Tensile tester" name="Last change" fileSource=Modified
```

## Example: an inbox of JSON result files {#example-an-inbox-of-json-result-files}

A test cell drops one result file per sample in `C:\Results\Inbox`, such as `Test_0413.json` holding
`{"sample":"0413","result":{"force":12.5,"ok":true}}`:

1. **Add device**; name it `Results`, **Folder path** `C:\Results\Inbox`, **Read from**
   **Inbox folder**, **Files to read** `Test_*.json`, **File format** **JSON**.
2. **Add tag** `Force`, **JSON path** `result.force`, Double; **Add tag** `Pass`, **JSON path**
   `result.ok`, Boolean; **Add tag** `Sample`, **JSON path** `sample`, String.
3. **Add tag** `Source file`, **Reads** **File name**: it shows which file the values came from.

Each file is read once, after it has stood still for 1 s and no program holds it open to write, in
the order the files arrived; each one is a reading of its own, dated by the file's time; once every
tag has read it, it moves to `C:\Results\Inbox\Processed`. Nothing is deleted. With the tools:

```
device_upsert name=Results driver=File fileFolder="C:\Results\Inbox" fileName="Test_*.json" fileMode=Inbox fileFormat=Json
tag_upsert device=Results name=Force address=result.force opcUaType=Double
tag_upsert device=Results name=Pass address=result.ok opcUaType=Boolean
tag_upsert device=Results name=Sample address=sample opcUaType=String
tag_upsert device=Results name="Source file" fileSource=Name
```

## Example: appending a line for another program {#example-appending-a-line-for-another-program}

Another program reads `D:\Records\run.csv`, one line per second with the time and the pressure:

1. **Add device**; name it `Run log`, **Folder path** `D:\Records`, **File name** `run.csv`.
2. On the **Writing** card turn **Allow writing** on and enter **Header line** `Time;Pressure`.
3. **Add tag** `Line`, Double, **Access** **Write only**, **A write** **Append a line**,
   **Line template** `{datetime};{value:0.00}`.

Writing 12.5 to `Line` appends `2026-10-03 14:05:12;12.50`, with the comma where the file already
uses one or where **Decimal separator** says **Decimal comma (12,5)**; a file the write creates gets
`Time;Pressure` first. Writes that follow each other quickly are all kept, in order. To write a file
per run, add a tag `File`, **Reads** **File name**, **Read and write**: the Logic writes the serial
number with `.csv` when a run starts, and the lines go to that file. With the tools:

```
device_upsert name="Run log" driver=File fileFolder="D:\Records" fileName=run.csv fileFormat=Delimited fileWrites=true fileHeaderText="Time;Pressure"
tag_upsert device="Run log" name=Line opcUaType=Double accessType=WriteOnly fileWriteMode=AppendLine fileLineTemplate="{datetime};{value:0.00}"
tag_upsert device="Run log" name=File fileSource=Name accessType=ReadAndWrite
```

## Agent tools {#agent-tools}

`driver_capabilities` describes this driver under `file`: the simple path, every device field, the
folder rule and the confinement to it, the file name forms, the two ways of reading, the formats with
their address grammar, the sources and their types, the text settings, how reads and writes behave,
discovery and three worked examples. `device_upsert driver=File` takes `fileFolder`, `fileName`,
`fileMode` (`SingleFile`, the default, or `Inbox`), `fileFormat` (`Delimited`, the default, `Text`,
`Json` or `Xml`), `fileEncoding` (`Automatic`, `Utf8` or `Windows1252`), `fileSeparator`
(`Automatic`, `Semicolon`, `Comma` or `Tab`), `fileDecimal` (`Automatic`, `Point` or `Comma`),
`fileHeaderRow` (0 to 1000), `fileWrites`, and with writing on `fileHeaderText` and `fileLineEnd`
(`Automatic`, `CrLf` or `Lf`); a device it returns carries `file`, with `fileStatus`, what the running
driver found: the file in use, whether it exists, the encoding, separators and line end it read, and
the files waiting in an inbox. `tag_upsert` takes `fileSource` (`Value`, `WholeFile`, `Exists`,
`Size`, `Modified`, `Name` or `Lines`), `fileRow` (`Last`, `Number` or `Matching`), `fileRowNumber`,
`fileKeyColumn`, `fileMatchText`, `fileWriteMode` (`AppendLine` or `ReplaceFile`) and
`fileLineTemplate`; the column or the path is the `address`, a free-text value takes the generic
driver's `valueExtraction`, `valueStart`, `valueLength`, `valueSeparator`, `valueField` and
`valuePattern`, and a new tag is read only and takes the source's own type unless `opcUaType` says
otherwise. `device_discover driver=File` reads either `fileDevice`, the name of a device of the
driver, or `fileFolder`, `fileName`, `fileMode` and `fileFormat` typed for the scan; the tags a
configured device lacks are added with `tag_upsert`. `connector_browse` returns each device's
`fileFolder`, `fileName`, `fileMode` and `fileFormat`. The other tools are on
[Agent tools](agent-tools).

## Limits worth knowing {#limits-worth-knowing}

- Excel workbooks are not read: a workbook is read once its program saves it as CSV.
- The driver never deletes a file. An inbox folder moves what it read to `Processed`, and only the
  temporary file of a replace that failed is ever removed.
- "The newest file of the folder" is not a way of reading: it loses files that arrive together and
  can show the previous sample's values. A machine that writes one file per sample is read through an
  inbox folder.
- A value is not changed in place inside a file (a cell of a CSV, a member of another program's
  JSON): a write appends a line or replaces the whole file, because two programs writing the same
  file can lose each other's change.
- A network share is reached with the Windows access of the account the station runs under: the
  device stores no user and no password, so the share has to let that account in (to read, and to
  write and to move for a device that writes or an inbox folder).
- The driver never touches anything outside its folder: a name that points elsewhere is refused where
  it is typed and where it is written.
- The file is looked at on each read: a change is seen at the next read, never the instant the other
  program writes it.
- A CSV field that holds a line break is not read, and a JSON, XML or whole-file tag reads at most
  4 MiB.
