# Technical Documentation: frangoteam/FUXA

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/frangoteam/FUXA)

### Overview (README.md)

# FUXA
[](LICENSE)
[](https://frangoteam.github.io/FUXA/)
[](https://nodejs.org/)
[](https://github.com/frangoteam/FUXA/stargazers)
[](https://hub.docker.com/r/frangoteam/fuxa)
[](https://www.npmjs.com/package/@frangoteam/fuxa)

FUXA is a **web-based SCADA / HMI platform** for industrial automation, IoT and real-time process visualization.

It allows you to build modern dashboards, connect industrial devices and monitor machines using standard industrial protocols such as **Modbus, OPC-UA, MQTT and Siemens S7**.

⭐ If you find FUXA useful, please consider giving the project a star.







## ✨ Features
- **Industrial protocol support**
  Modbus RTU/TCP, Siemens S7 Protocol, OPC-UA, BACnet IP, MQTT, Ethernet/IP (Allen Bradley), ODBC, ADSclient, Gpio (Raspberry), WebCam, MELSEC, Redis
- **Database and data storage**
  Built-in data historian (DAQ) with support for SQLite, InfluxDB and other time-series databases.
  External integrations via ODBC and Redis.
- **SCADA/HMI Web-Editor**
  Engineering and Design completely web-based
- **Cross-platform architecture**
  Backend: Node.js
  Frontend: Angular, HTML5, CSS, SVG

## Why FUXA

FUXA provides a modern **web-based platform for industrial monitoring, SCADA/HMI applications and IoT dashboards**.

It is designed to simplify the creation of real-time visualizations and industrial integrations using standard web technologies.

Key advantages:

- Modern **web-based SCADA / HMI architecture**
- Visual editor for dashboards and process visualization
- Support for industrial protocols (Modbus, OPC-UA, MQTT, Siemens S7 and more)
- Built with modern technologies (Node.js, Angular, SVG)
- Runs on **Linux, Windows, macOS, Docker, Raspberry Pi and more**
- Open-source and extensible

## 🚀 Live Demo
Here is a [live demo](https://frangoteam.github.io) example of FUXA editor.

## 📚 Documentation

Official documentation is available at:

👉 https://frangoteam.github.io/FUXA/

The documentation source is located in the `/docs` directory of this repository.

The site is built using MkDocs (Material theme) and automatically deployed via GitHub Actions.

## 🛠 Installing and Running
FUXA is developed with NodeJS (backend) and Angular (frontend).

For detailed guides and advanced configuration options, see the official documentation:

👉 https://frangoteam.github.io/FUXA/


### 1° Option - Running from docker
```
docker pull frangoteam/fuxa:latest
docker run -d -p 1881:1881 frangoteam/fuxa:latest

// persistent storage of application data (project), daq (tags history), logs and images (resource)
docker run -d -p 1881:1881 -v fuxa_appdata:/usr/src/app/FUXA/server/_appdata -v fuxa_db:/usr/src/app/FUXA/server/_db -v fuxa_logs:/usr/src/app/FUXA/server/_logs -v fuxa_images:/usr/src/app/FUXA/server/_images frangoteam/fuxa:latest

// with Docker compose
// persistent storage will be at ./appdata ./db ./logs and ./images
wget https://raw.githubusercontent.com/frangoteam/FUXA/master/compose.yml
docker compose up -d
```

Open up a browser (better Chrome) and navigate to http://localhost:1881

### 2° Option - Install from source
[Download the latest release](https://github.com/frangoteam/FUXA/releases) and unpack it

You need to have installed [Node.js](https://nodejs.org/en/about/previous-releases)
- Recommended: Node.js 18 LTS

**Note** Starting from FUXA 1.2.7, Node.js 14 and older versions are not supported due to upstream dependency updates.

**WARNING** On Linux systems (especially Raspberry Pi), installing native dependencies with Node.js 18 may require additional build tools.
If you do not intend to use specific features, you can safely remove them from ```server/package.json```:
- Remove ```node-snap7``` if you do not need Siemens S7 communication
- Remove ```odbc``` if you do not need external database connectivity

```
cd ./server
npm install
npm start
```

Open up a browser (better Chrome) and navigate to http://localhost:1881

### 3° Option - Install from [NPM](https://www.npmjs.com/package/@frangoteam/fuxa)

You need to have installed [Node.js](https://nodejs.org/en/about/previous-releases)
- Recommended: Node.js 18 LTS

**WARNING** In linux with nodejs Version 18 the installation could be a challenge.
If you don't intend communicate with Siemens PLCs via S7 (node-snap7 library) you can install from [NPM @frangoteam/fuxa-min](https://www.npmjs.com/package/@frangoteam/fuxa-min)

```
npm install --unsafe-perm @frangoteam/fuxa
fuxa
```

Open up a browser (better Chrome) and navigate to http://localhost:1881

### 4° Option - Install using prebuilt Electron Packages

You will need to be logged into github to access the download button for Electron Action Builds,
click on the workflow and scroll down to Artifacts and click the download icon for you system

[Electron Action Builds](https://github.com/frangoteam/FUXA/actions/workflows/electron_latest.yml)

<img width="2082" height="531" alt="image" src="https://github.com/user-attachments/assets/40f01e1d-cf39-4145-99a0-e8fedf791edf" />

### 5° Option - Headless Portable Binaries for Embedded Devices

For headless deployments on embedded devices or servers without GUI, FUXA provides self-contained portable binaries for Windows, macOS, and Linux.

These binaries include everything needed (server, client) and run as standalone executables.

Download the latest builds from GitHub Actions artifacts:

[Headless Portable Builds](https://github.com/frangoteam/FUXA/actions/workflows/headless_packaging.yml)

For detailed installation and running instructions, see the documentation.

### Creating the Electron Application
Electron is a framework for building cross-platform desktop applications using web technologies. An Electron application is standalone, meaning it can be run independently on your desktop without needing a web browser.

To create the Electron application, you need to have node.js 18 installed. Follow these steps:

Build Server and Client First
```
cd ./server
npm install
cd ../client
npm install
npm run build
```

Packaging
```
cd ./app
npm install
npm run package
```

After following these steps, you will have a standalone Electron application for FUXA. The application can be found in the ./app directory.

## Usage and Documentation
- 📚 Official Documentation: https://frangoteam.github.io/FUXA/
- Look video from [frangoteam](https://www.youtube.com/@umbertonocelli5301)
- Look video from [Fusion Automate - Urvish Nakum](https://youtube.com/playlist?list=PLxrSjjYyzaaK8uY3kVaFzfGnwhVXiCEAO&si=aU1OxgkUvLQ3bXHq)
- Browse the [DeepWiki](https://deepwiki.com/frangoteam/FUXA) for AI-assisted docs and code navigation

## Community SVG Widgets

Looking for ready-made, reusable SVG widgets?
Check out the companion repository **FUXA-SVG-Widgets**:

- Repository: https://github.com/frangoteam/FUXA-SVG-Widgets
- Authoring guide & examples: see the repo README and the Wiki page:
  https://github.com/frangoteam/FUXA/wiki/HowTo-Widgets

## 🧪 To Debug (Full Stack)
Install and start to serve the frontend
```
cd ./client
npm install
npm start
```

Start the Server and Client (Browser) in Debug Mode
```
In vscode: Debug ‘Server & Client’
```

## 🏗 To Build
Build the frontend for production
```
cd ./client
ng build --configuration=production
```

## Who uses FUXA

FUXA is used in industrial automation, IoT, monitoring and research environments.

### FUXA Pro

If you are using FUXA in production, consider supporting the development of the project by using **FUXA Pro**.**.

FUXA Pro includes additional professional features such as:

- White-label branding (custom logo and labels)
- Additional resources and templates
- User and script event logging
- Unlimited installations

The open-source version of FUXA remains fully available and continues to evolve with community contributions.

**License:** one-time payment – €100

More information:
https://frangoteam.org

## 🤝 Contributing

Contributions are welcome and greatly appreciated.

You can contribute by:

- Improving or fixing code
- Enhancing documentation
- Reporting bugs
- Proposing new features
- Sharing examples and use cases

Before submitting a Pull Request, please open an issue to discuss major changes.

For full contribution guidelines (code and documentation), please read:

👉 [CONTRIBUTING.md](CONTRIBUTING.md)

## 💬 Let us know!
We’d be really happy if you send us your own shapes in order to collect a library to share it with others. Just send an email to info@frangoteam.org and do let us know if you have any questions or suggestions regarding our work.

## <a href="https://discord.gg/WZhxz9uHh4" target="_blank" > <img src="https://skillicons.dev/icons?i=discord" alt=""></a>

## 📄 License
MIT.

---

### Getting Started (`docs/Getting-Started.md`)

## Getting started with FUXA

The goal of this tutorial is to demonstrate the basic usage of the most popular FUXA features. You will learn how to:
- Connect devices to FUXA
- Push data from devices to FUXA
- Build a real-time GUI SCADA/HMI/Dashboard
- Define thresholds and trigger alarms

## Prerequisites

You will need to have FUXA server up and running. To install FUXA using [**Installation Guide**](https://github.com/frangoteam/FUXA/wiki/Installing-and-Running).
FUXA UI will be available using the URL: http://localhost:1881.

Principally FUXA web server performs two page: 
- Visualization for end-user http://localhost:1881/home
- Editor to project and design http://localhost:1881/editor

## Step 1. Connect device and configure tags
To connect the device you need to get the device credentials first. FUXA supports various devices: Modbus RTU/TCP, Siemens S7 Protocol, OPC-UA, BACnet IP, MQTT, Ethernet/IP (Allen Bradley). After you connected to a device, you can configure the subscriptions of the live values, Tags, Sensor, etc.
You may find configuration guide by here [**HowTo Devices and Tags**](https://github.com/frangoteam/FUXA/wiki/HowTo-Devices-and-Tags)

## Step 2. Create Visualization
We will create a View and add the most popular widgets. See the instructions below.

   ### Step 2.1 Create Empty View
   [**HowTo create View**](https://github.com/frangoteam/FUXA/wiki/HowTo-View)
   ### Step 2.2 Bind Controls to View
   [**HowTo bind Controls**](https://github.com/frangoteam/FUXA/wiki/HowTo-bind-Controls)
   ### Step 2.3 Bind Shapes to View
   [**HowTo bind Shapes**](https://github.com/frangoteam/FUXA/wiki/HowTo-bind-Shapes)
   ### Step 2.4 Bind Chart Control to View
   [**HowTo chart Control**](https://github.com/frangoteam/FUXA/wiki/HowTo-Chart-Control)

## Step 3. Configure UI Layout
[**HowTo UI Layout**](https://github.com/frangoteam/FUXA/wiki/HowTo-UI-Layout)

## Step 4. Configure Alarms
[**HowTo setup Alarms**](https://github.com/frangoteam/FUXA/wiki/HowTo-setup-Alarms)

## Step 5. Activate and create customer user

## Tips and tricks

---

### Index (`docs/index.md`)

## Welcome to the FUXA wiki!

FUXA is a powerful **web-based** software to rapidly build and deploy scalable SCADA, HMI, Dashboard or IIoT systems. With FUXA you can create modern process visualizations with individual designs for your machines and real-time data display, as well as control instrument of automated industrial plants.



FUXA supports commonly used communication standards such as Siemens S7 protocol and OPC UA that allow to connect third-party OPC servers. The list of supported  communication standards can be extended by developing additional drivers.

The software model of FUXA is based on a Node.js runtime that you point a web browser at to access the editor to create your app (SCADA/HMI/Dashboard) and as client you can run your visualizations.

## User Interface

FUXA is composed from two differents views: FUXA-editor to edit the project and FUXA-view to show the product of your visualization project.



## Features

The FUXA not require any runtime licenses. You can build your HMI projects in any number and size and you do not need to worry about further licensing the runtime environment.

### Communication Protocols
The FUXA Platform includes connectors to achieve direct communications to Siemens PLCs and OPC UA data sources:
- A client for OPC UA connectivity.
- S7 Protocol to communicate over Ethernet with Siemens CPU 200, 300, 400, 1200, and 1500.
- Modbus RTU/TCP, BACnet IP, MQTT, Ethernet/IP (Allen Bradley), WebAPI

### Cross-Platform Full-Stack
This system is designed to operate in various operating systems, with easy installation. Backend is develop with NodeJs. The user-interface is a scalable HTML5 web frontend, develop with Web technologies (HTML5, CSS, Javascript, Angular, SVG) and compatible with all recent browsers.

---

### HowTo Chart Control (`docs/HowTo-Chart-Control.md`)

To add a **Chart** Control to a View:: going in editor and select the View.


In **Line charts** you can define your diagrams to be attached to the chart control.

---

### HowTo Devices And Tags (`docs/HowTo-Devices-and-Tags.md`)

To add your Device and Tags going to **Connections** in editor.



Add and connect a **OPCUA** device.


To add OPCUA Tags the device have to be connected.


To add a **Modbus** connection you have to install the driver in **Plugins**


Add a **MQTT** connection and a topic subscription


Add a **WebAPI** connection and a Tag of the JSON result


**Tag Options**

If you have any Tag types which don't have a defined data type you can set this. For example TIME data type is no a defined type, but the base data type is normally int64 or uint64 and the time is in milliseconds.



You can also under tag options use the scale script to do any data type conversions etc for example the data from TIME is an array of LOW and HIGH, we can create a simple script to grab the array element and return it as a number type. We can then just return the actual value for write if needed.



**Important!** the script parameter must be called value as there is a filter to display relevant scripts for selection. Do not use comments in this script as they are not supported. 

Here is the read script which returns the array element



Here is the write script returning the actual tag value

---

### HowTo Node Red (`docs/HowTo-Node-Red.md`)

# HowTo Node-RED Integration

## Overview

FUXA includes full Node-RED integration, allowing you to create powerful automation flows that interact with your SCADA system.

## Setup and Installation

### Node-RED in FUXA
Node-RED is automatically included with FUXA and requires no additional installation. Access it through the FUXA settings menu.

### Installing Dashboard 2
Dashboard 2 must be installed separately if needed:

1. Open Node-RED editor (via FUXA settings)
2. Click the menu (☰) → **Manage Palette**
3. Search for: `@flowfuse/node-red-dashboard`
4. Click **Install**
5. Restart Node-RED/FUXA

### Required Dependencies
- **@flowfuse/node-red-dashboard**: For modern dashboard creation
- **node-red-contrib-fuxa**: Automatically included (provides FUXA integration nodes)

### Optional ping requests in Docker

The default FUXA Docker image does not install `ping`. If a Node-RED flow needs the
system executable, build an optional custom image. Installing a palette package
that invokes `ping` does not install the operating-system executable.

Save the following as `Dockerfile.ping`. This example extends the Debian-based FUXA
image and requires its package manager to be available during the build:

```dockerfile
ARG FUXA_IMAGE=frangoteam/fuxa:latest
FROM ${FUXA_IMAGE}

RUN apt-get update \
    && apt-get install -y --no-install-recommends iputils-ping \
    && rm -rf /var/lib/apt/lists/* \
    && ping -V
```

Build and check the custom image:

```bash
docker build --build-arg FUXA_IMAGE=frangoteam/fuxa:latest -f Dockerfile.ping -t fuxa-with-ping:local .
docker run --rm --entrypoint ping fuxa-with-ping:local -V
docker run --rm --entrypoint ping fuxa-with-ping:local -n -c 1 -W 2 127.0.0.1
```

For a reproducible build, replace the `FUXA_IMAGE` argument with the release tag or
digest used by your deployment. Select `fuxa-with-ping:local` as its image while
retaining the existing data volumes, environment and network settings. Recreate
the container to use the new image; restarting an existing container does not
install the executable. The default FUXA image remains unchanged.

In the custom image, use the built-in Node-RED **exec** node to check a fixed host
without installing an additional palette package:

1. Connect an **inject** node to an **exec** node.
2. Set the exec command to `ping -n -c 1 -W 2 127.0.0.1` for an initial loopback check.
3. Disable **Append msg.payload** and leave spawn mode disabled.
4. Connect the exec outputs to **debug** nodes. The first output contains stdout,
   the second stderr, and the third the return code object (`msg.payload.code`).
5. Deploy and inject. A return code of `0` indicates a reply. Replace `127.0.0.1`
   with the fixed IP address or hostname of the device to monitor.

The count and reply timeout bound the ICMP probe. Loopback (`127.0.0.1`) checks the
container itself, not the Docker host or a PLC. A failed ping does not necessarily
mean the device is offline: routing or a firewall can block ICMP replies.

To verify the executable and loopback from outside Node-RED, replace
`<container-name>` with the running custom FUXA container name:

```bash
docker exec <container-name> ping -V
docker exec <container-name> ping -n -c 1 -W 2 127.0.0.1
```

Palette nodes that invoke the system `ping` command can use the same executable,
but their Node-RED packages still need to be installed separately. If ping reports
`Operation not permitted`, check the container's ICMP socket permissions and runtime
policy. Installing the executable does not change container capabilities.

### Configuration
No additional configuration is required. Node-RED automatically connects to FUXA's runtime environment.

### Security and Access Mode
Node-RED access can be configured when FUXA security is enabled.

**Setting**: `nodeRedAuthMode`

- `secure` (default): Node-RED editor and admin APIs require authentication (JWT or API key).
- `legacy-open`: Node-RED remains open (no auth) to preserve legacy behavior.

**Notes**:
- Changes apply after server restart.
- If `secureEnabled=true` and `nodeRedAuthMode=secure`, external systems can access Node-RED HTTP endpoints using an API key header:
  `x-api-key: <api-key>`
  API keys are managed in FUXA under **Editor → Setup → API Keys**.

## Accessing Node-RED

### Node-RED Editor
Access the Node-RED flow editor through the FUXA settings:

1. Click the **Settings** button in FUXA
2. Navigate to **Node-RED** section
3. Click **Open Node-RED Editor**

This opens the Node-RED flow editor in a new window/tab.

### Dashboard 2
**Note**: Dashboard 2 is not installed by default and must be installed separately.

#### Installation
1. In Node-RED, go to **Menu → Manage Palette**
2. Search for and install: `@flowfuse/node-red-dashboard`
3. Restart Node-RED/FUXA

#### Accessing Dashboard 2
Once installed, view your Node-RED dashboards:

**Embedded in FUXA Views**: Add an iframe component to any FUXA view

#### Embedding Dashboard in FUXA View

1. Open your FUXA project in the editor
2. Add an iframe component to your view
3. Set the URL to: `/dashboard`
4. Configure the iframe size and properties as needed
5. If you only want to display a single page you can adjust dashboard settings and use `/dashboard/page1` or the name of your page

**Important**: Dashboard 2 must be installed before attempting to access `/dashboard`

## FUXA Contrib Nodes

The FUXA contrib package provides specialized nodes for SCADA integration, this package is designed to only work with the FUXA Node-Red Integration and will not work in a standalone Node-Red installation

### Tags Nodes

#### get-tag
**Purpose**: Retrieve the current value of a FUXA tag

**Parameters**:
- **Name**: Optional node name for identification
- **Tag**: Tag name (dropdown populated from FUXA devices)

**Input**: Any message (triggers tag read)
**Output**: `msg.payload` contains the tag value

**Example Output**:
```json
{
  "payload": 25.3,
  "_msgid": "abc123"
}
```

#### set-tag
**Purpose**: Write a value to a FUXA tag

**Parameters**:
- **Name**: Optional node name for identification
- **Tag**: Tag name (dropdown populated from FUXA devices)

**Input**: `msg.payload` contains the value to write
**Output**: `msg.payload` contains the written value

**Example Input**:
```json
{
  "payload": 50.0
}
```

#### get-tag-change
**Purpose**: Monitor a FUXA tag for value changes based on device polling events

**Parameters**:
- **Name**: Optional node name for identification
- **Tag**: Tag name (dropdown populated from FUXA devices)

**Input**: None (event-driven)
**Output**: `msg.payload` contains the new tag value when it changes

**How it works**: This node subscribes to FUXA's device polling events. When devices are polled and a tag value has changed since the last poll, the node outputs a message with the new value. The timing depends on the device's polling interval (typically 1-5 seconds).

**Example Output**:
```json
{
  "payload": 25.3,
  "topic": "temperature",
  "tagId": "device1.temperature",
  "tagName": "temperature",
  "timestamp": "2025-01-01T10:30:00.000Z",
  "previousValue": 24.8
}
```

**Note**: This node only outputs messages when tag values actually change, not on every polling cycle. It efficiently monitors for changes without requiring manual polling.

#### get-tag-id
**Purpose**: Get the internal ID of a tag by name

**Parameters**:
- **Name**: Optional node name for identification
- **Tag**: Tag name (dropdown populated from FUXA devices)

**Input**: Any message
**Output**: `msg.payload` contains the tag ID

#### get-historical-tags
**Purpose**: Retrieve historical tag data for multiple tags simultaneously

**Parameters**:
- **Name**: Optional node name for identification
- **Tags**: Comma-separated list of tag names (e.g., "temp1,temp2,pressure")
- **From Time**: Start of historical period (datetime-local picker)
- **To Time**: End of historical period (datetime-local picker)

**Input**: Any message or override with `msg.tags`, `msg.from`, `msg.to`
**Output**: `msg.payload` contains historical data array

**Multiple Tags Support**:
- Enter tags as comma-separated values: `temp1,temp2,pressure`
- Each tag's historical data is retrieved for the same time period
- Results are combined in a single response

**Example Input** (comma-separated):
```json
{
  "tags": "temp1,temp2,pressure",
  "from": "2025-01-01T08:00",
  "to": "2025-01-01T17:00"
}
```

**Example Input** (array):
```json
{
  "tags": ["temp1", "temp2", "pressure"],
  "from": "2025-01-01T08:00",
  "to": "2025-01-01T17:00"
}
```

**Example Output**:
```json
{
  "payload": [
    {
      "tag": "temp1",
      "data": [
        {"timestamp": "2025-01-01T08:00:00Z", "value": 25.3},
        {"timestamp": "2025-01-01T09:00:00Z", "value": 26.1}
      ]
    },
    {
      "tag": "temp2",
      "data": [
        {"timestamp": "2025-01-01T08:00:00Z", "value": 24.8},
        {"timestamp": "2025-01-01T09:00:00Z", "value": 25.2}
      ]
    }
  ]
}
```

#### get-tag-daq-settings
**Purpose**: Get DAQ (Data Acquisition) settings for a tag

**Parameters**:
- **Name**: Optional node name for identification
- **Tag**: Tag name (dropdown populated from FUXA devices)

**Input**: Any message
**Output**: `msg.payload` contains DAQ settings object

#### set-tag-daq-settings
**Purpose**: Configure DAQ (Data Acquisition) settings for a tag

**Parameters**:
- **Name**: Optional node name for identification
- **Tag**: Tag name (dropdown populated from FUXA devices)
- **Enabled**: Checkbox to enable/disable DAQ for this tag
- **Interval**: Sampling interval in milliseconds
- **Deadband**: Minimum change threshold for data updates

**Input**: Any message or override with `msg.tag`, `msg.enabled`, `msg.interval`, `msg.deadband`
**Output**: `msg.payload` contains updated settings

**DAQ Settings**:
- **Enabled**: Turn data acquisition on/off for this tag
- **Interval**: How often to sample the tag (in milliseconds)
- **Deadband**: Only report changes greater than this threshold (prevents noise)

### Devices Nodes

#### enable-device
**Purpose**: Enable or disable a device connection

**Parameters**:
- **Name**: Optional node name for identification
- **Device Name**: Device name (dropdown populated from FUXA devices)
- **Enabled**: Checkbox to enable/disable the device

**Input**: Any message or override with `msg.deviceName`, `msg.enabled`
**Output**: `msg.payload` contains operation result

**Enable/Disable**: Check the box to enable device, uncheck to disablenterface for connecting devices, processing data, and controlling your industrial processes.

#### get-device
**Purpose**: Get information about a FUXA device

**Parameters**:
- **Name**: Optional node name for identification
- **Device Name**: Device name (dropdown populated from FUXA devices)
- **Include Tags**: Checkbox to include tag information in the response

**Input**: Any message or override with `msg.deviceName`, `msg.includeTags`
**Output**: `msg.payload` contains device information object

**Include Tags**: When checked, the response includes all tags associated with the device

#### enable-device
**Purpose**: Enable or disable a device connection

**Parameters**:
- **Name**: Optional node name for identification
- **Device**: Device name (dropdown populated from FUXA devices)
- **Enable**: Checkbox to enable/disable

**Input**: Any message or override with `msg.enable`
**Output**: `msg.payload` contains operation result

#### get-device-property
**Purpose**: Get a property value from a device

**Parameters**:
- **Name**: Optional node name for identification
- **Device Name**: Device name (dropdown populated from FUXA devices)
- **Property**: Property name to retrieve

**Input**: Any message or override with `msg.deviceName`, `msg.property`
**Output**: `msg.payload` contains property value

**Property Names**: Common properties include "status", "connected", "lastError", etc.

#### set-device-property
**Purpose**: Set a property value on a device

**Parameters**:
- **Name**: Optional node name for identification
- **Device Name**: Device name (dropdown populated from FUXA devices)
- **Property**: Property name to set
- **Value**: Value to set for the property

**Input**: `msg.payload` contains the value or override with `msg.deviceName`, `msg.property`, `msg.value`
**Output**: `msg.payload` contains the set value

### Alarms Nodes

#### get-alarms
**Purpose**: Get current active alarms

**Parameters**:
- **Name**: Optional node name for identification

**Input**: Any message
**Output**: `msg.payload` contains array of active alarms

**Example Output**:
```json
{
  "payload": [
    {
      "id": "alarm1",
      "name": "High Temperature",
      "status": "active",
      "timestamp": "2025-01-01T10:30:00Z",
      "value": 85.5
    }
  ]
}
```

#### get-history-alarms
**Purpose**: Get historical alarm data

**Parameters**:
- **Name**: Optional node name for identification
- **Start Time**: Start of historical period (datetime-local picker)
- **End Time**: End of historical period (datetime-local picker)

**Input**: Any message or override with `msg.startTime` and `msg.endTime`
**Output**: `msg.payload` contains historical alarms array

#### ack-alarm
**Purpose**: Acknowledge alarms in FUXA

**Parameters**:
- **Name**: Optional node name for identification
- **Alarm Name**: Alarm name (dropdown populated from FUXA alarms)
- **Types**: Comma-separated alarm types (optional)

**Input**: Any message or override with `msg.alarmName`, `msg.types`
**Output**: `msg.payload` contains acknowledgment result

**Alarm Types**: Specify alarm types to acknowledge specific alarm categories (comma-separated: "warning,error,critical")

### Views Nodes

#### set-view
**Purpose**: Change the current view in FUXA

**Parameters**:
- **Name**: Optional node name for identification
- **View Name**: View name (dropdown populated from FUXA views)

**Input**: Any message or override with `msg.viewName`
**Output**: `msg.payload` contains operation result

#### open-card
**Purpose**: Open a specific card/dialog in FUXA

**Parameters**:
- **Name**: Optional node name for identification
- **Card**: Card name (dropdown populated from FUXA cards)

**Input**: Any message or override with `msg.cardName`
**Output**: `msg.payload` contains operation result

### Scripts Nodes

#### execute-script
**Purpose**: Execute a FUXA script

**Parameters**:
- **Name**: Optional node name for identification
- **Script**: Script name (dropdown populated from FUXA scripts)

**Input**: Any message or override with `msg.scriptName`
**Output**: `msg.payload` contains execution result

**Example Output**:
```json
{
  "payload": {
    "success": true,
    "script": "myScript",
    "result": "Script completed successfully",
    "timestamp": "2025-01-01T10:45:00Z"
  }
}
```

### DAQ Nodes

#### get-daq
**Purpose**: Get Data Acquisition (DAQ) data for a single tag

**Parameters**:
- **Name**: Optional node name for identification
- **Tag**: Single tag name (dropdown populated from FUXA devices)
- **From Time**: Start of DAQ period (datetime-local picker)
- **To Time**: End of DAQ period (datetime-local picker)

**Input**: Any message or override with `msg.from`, `msg.to`
**Output**: `msg.payload` contains DAQ data for the single tag

**Single Tag Only**: Unlike get-historical-tags, this node only processes one tag at a time. For multiple tags, use separate get-daq nodes or get-historical-tags.

**Example Output**:
```json
{
  "payload": [
    {"timestamp": "2025-01-01T10:00:00Z", "value": 25.3, "quality": "good"},
    {"timestamp": "2025-01-01T10:01:00Z", "value": 25.8, "quality": "good"}
  ]
}
```

### Events Nodes

#### emit-event
**Purpose**: Emit custom events through FUXA's event system for inter-system communication and automation triggering

**Parameters**:
- **Name**: Optional node name for identification
- **Event Type**: Custom event name/type (e.g., "machine-fault", "production-complete", "maintenance-required")

**Input**: `msg.payload` contains the event data (any format: string, number, object, array)
**Output**: `msg.payload` unchanged (passes through)

**Event Types and Usage**:

**System Events** (built-in FUXA events):
- `device-status` - Device connection status changes
- `device-property` - Device property updates
- `device-values` - Tag value changes
- `alarms-status` - Alarm state changes
- `script-console` - Script console output
- `heartbeat` - System heartbeat/alive signals

**Custom Events** (user-defined):
- `production-start` - Production line started
- `production-stop` - Production line stopped
- `quality-alert` - Quality control alert
- `maintenance-due` - Equipment maintenance required
- `operator-login` - Operator authentication
- `batch-complete` - Production batch finished

**Event Data Examples**:

**Simple string event**:
```json
{
  "payload": "Machine fault detected"
}
```

**Structured event data**:
```json
{
  "payload": {
    "eventType": "production-alert",
    "machine": "Line-A",
    "severity": "high",
    "timestamp": "2025-01-01T10:30:00Z",
    "details": {
      "temperature": 85.5,
      "pressure": 2.1,
      "errorCode": "E-202"
    }
  }
}
```

**Array data event**:
```json
{
  "payload": [
    {"sensor": "temp1", "value": 75.2},
    {"sensor": "temp2", "value": 76.8},
    {"sensor": "temp3", "value": 74.9}
  ]
}
```

**How Events Work**:
1. Events are emitted to FUXA's internal event system
2. Other FUXA components can listen for these events
3. Events can trigger scripts, update displays, or initiate automation sequences
4. Events persist until consumed or the system restarts

**Common Use Cases**:
- **Machine Monitoring**: Emit events when equipment enters fault states
- **Production Tracking**: Signal production milestones or quality issues
- **Maintenance Alerts**: Notify when equipment needs servicing
- **Operator Notifications**: Alert operators to system conditions
- **Data Integration**: Send structured data to external systems

**Integration with Scripts**:
Events can be consumed by FUXA scripts using the event system. Scripts can listen for specific event types and execute automated responses.

#### send-message
**Purpose**: Send email notifications or messages through FUXA's notification system

**Parameters**:
- **Name**: Optional node name for identification
- **Address**: Recipient email address
- **Subject**: Email subject line
- **Message**: Default message content (can be overridden)

**Input**: `msg.payload` can contain message data or override with `msg.address`, `msg.subject`, `msg.message`
**Output**: `msg.payload` contains send result

**Message Format Options**:

**Simple text message** (uses configured address/subject):
```json
{
  "payload": "Alert: Temperature exceeded threshold"
}
```

**Complete message override**:
```json
{
  "address": "operator@company.com",
  "subject": "Production Alert",
  "message": "Line A has stopped due to maintenance requirement"
}
```

**Multiple recipients**:
```json
{
  "address": ["operator1@company.com", "supervisor@company.com"],
  "subject": "Quality Control Alert",
  "message": "Batch #1234 failed quality inspection"
}
```

## Data Formats

### Tag Values
Tag values can be:
- **Numbers**: `25.3`, `100`, `0`
- **Strings**: `"Running"`, `"Stopped"`
- **Booleans**: `true`, `false`
- **Objects**: Complex data structures

### Timestamps
All timestamps use ISO 8601 format:
```json
"2025-01-01T10:30:00.000Z"
```

### Error Handling
All nodes output errors to the debug tab. Common error patterns:
```json
{
  "payload": null,
  "_msgid": "abc123",
  "error": "Tag not found: myTag"
}
```

## Troubleshooting

### Dashboard Not Loading
**Problem**: `/dashboard` shows error or blank page

**Solution**: Install Dashboard 2
1. Open Node-RED editor
2. Go to **Manage Palette**
3. Install `@flowfuse/node-red-dashboard`
4. Restart Node-RED/FUXA

### FUXA Nodes Not Appearing
**Problem**: FUXA contrib nodes not visible in palette

**Solution**: Check Node-RED logs for errors. The nodes are automatically registered when FUXA starts Node-RED.

### Dropdowns Not Populated
**Problem**: Tag/device dropdowns are empty

**Solution**: Ensure you have devices and tags configured in FUXA. The dropdowns populate from your FUXA project data.

### Events Not Working
**Problem**: Emitted events not triggering expected behavior

**Solution**: Check that other FUXA components (scripts, views) are listening for the correct event types. Events are case-sensitive.

### Email Notifications Not Sending
**Problem**: send-message node not delivering emails

**Solution**: Configure SMTP settings in FUXA server configuration. Check FUXA logs for SMTP connection errors.

---

### HowTo ODBC (`docs/HowTo-ODBC.md`)

To get started with ODBC it is recommended to use the Docker version as the ODBC drivers are pre installed and ready to go. 

**Installing ODBC Drivers**

The available drivers that can be used and there defined name can be found here: [ODBC Driver ini](https://github.com/frangoteam/FUXA/blob/master/odbc/odbcinst.ini)

If you install via NPM on a Debian based Linux system you can try manually install ODBC drivers for your system with the following steps:


**1:** `sudo apt-get update && sudo apt-get install -y unixodbc unixodbc-dev` 

**2:** copy both files from here onto your system [ODBC Driver Files](https://github.com/frangoteam/FUXA/tree/master/odbc)

**3:** Go to the directory of the downloaded files and make script executable and install and most ini file
```
sudo chmod +x install_odbc_drivers.sh
sudo ./install_odbc_drivers.sh
sudo cp odbcinst.ini /etc/odbcinst.ini
```
**4:** Test the installed drivers and you can connect using unixODBC change the connection string to suit your DB

`sudo myodbc-installer -s -a -c2 -n "test" -t "DRIVER=MySQL;SERVER=YourIP;PORT=3306;DATABASE=testDB;UID=User;PWD=MyPass"`

`sudo isql test`

**How to use ODBC**

First you need to create a connection to your DB by adding a Device in Fuxa and select ODBC

If you are using a defined DSN you can enter that, or just add the complete connection string in the same field as seen below



Here are some connection strings for different DBs

`DRIVER=PostgreSQL;SERVER=Your_DB_IP;PORT=5432;DATABASE=testDB`

`DRIVER=MySQL;SERVER=Your_DB_IP;PORT=3306;DATABASE=testDB;SSLMODE=DISABLED`

MySQL can cause connection issues due to SSLMODE, so please try with out SSLMODE in the connection string and try enabled and disabled.
`SSLMODE=ENABLE`
`SSLMODE=DISABLED`

Replace testDB with your actual DB name

Create a Server Side Script to test, you can use the test tab and console there to display the result

First you need to get ODBC connection via devices, in the example it's named postgreSQL as you can see in the above pic
```
// Initialize the device, sane name as connection
let myDevice = await $getDevice('postgreSQL', true);
```
Read data from the DB
```
let result = await myDevice.pool.query(`SELECT * FROM "testTable"`);
console.log(JSON.stringify(result));
```

**Some important notes:** 

Depending on your database you may need to remove the quotes `"testTable"` to `testTable` or add the schema `"DB_Schema"."testTable"`

If you need to use ODBC in another script you cannot use the same connection name: 

Script 1 `let myDevice1 = await $getDevice('postgreSQL', true);`

Script 2 `let myDevice2 = await $getDevice('postgreSQL', true);`

**Full Example**

This example also polls Tags every 100ms and uses a trigger to execute the SQL Query, please be carful you do not overload your system using this method, in the future the ideal solution would be creating an event listener for the tags and we can use a simple addEventListener method and no need to poll

There is also a 1sec loop that updates values from the DB and pushes them to the Table in Fuxa UI 

There is a function called query manager and this handles the trigger and the one shot of the execution 

Note: you may have to restart Fuxa for this to work correctly and each time you modify the script, sometimes it's fine if you modify the script other times it requires a restart, so if you have any weird issues try a restart before creating an issue

Create a Server Side Script and set it to on startup

```
/* Detailed source-code truncated for AI context efficiency. */
```

Create a Client Side Script with an interval of 1 sec this script will put the data into the Table 

```
let customerData = JSON.parse($getTag($getTagId('customerDataArray'  ,)));

// Column ID's must match DB column
var tableData = {
  columns: [{
    id: 'Name',
    label: 'Name'
    }, {
    id: 'Phone',    
    label: 'Phone'
    }, {
    id: 'Email',    
    label: 'Email'
    }, {
    id: 'Age',    
    label: 'Age'
  }],
  rows: customerData
};

// Name of table used in Fuxa
$invokeObject('customerTable', 'setTableAndData', tableData);
```

---

### HowTo Recipes (`docs/HowTo-Recipes.md`)

# HowTo Recipes

Recipes are pre-defined sets of tag values that can be applied to devices on demand. A recipe captures the operating parameters of a machine or process (temperature setpoints, durations, quantities) and lets an operator push those values to the device with a single action (download), or capture the current device values back into a recipe for later reuse (upload).

## Overview

FUXA distinguishes between two kinds of recipes:

- **Recipe Types**: Templates that define which tags participate and what their default values are. Types are created and maintained in the Recipes management page.
- **Recipe Instances**: Concrete copies of a type, each with its own values for the type's tags. The Recipe widget is bound to a type, but displays and operates on that type's instances.

Key features include:

- **Download**: Write all tag values of a recipe to their devices, one entry at a time, with live progress reporting
- **Upload**: Read current device values back into a recipe and persist them
- **Recipe Types**: Reusable templates for sets of tag entries
- **Recipe Instances**: Independent copies of a type, each holding its own values
- **Recipe Widget**: Operator-facing widget bound to a recipe type, with create/edit/delete/download/upload actions
- **Export/Import**: Share recipes as JSON or CSV files
- **Cancellation**: Stop a running download or upload at any time
- **Authorization**: Role-based control over who can manage recipes and interact with the widget

## Recipe Types and Instances

A **recipe type** is the master template. It has a name, an optional description, and a list of tag entries (tag id, tag name, tag type, and a default value). Types are the units that appear in the Recipes management page and in the widget's property panel.

A **recipe instance** is a concrete snapshot derived from a type. Instances share the type's tag set but each one stores its own values, so different products or batches can be saved as separate instances of the same type. An instance is linked to its type through a `typeId` reference; recipes without a `typeId` are types.

## Managing Recipes

Recipes are managed from the Recipes management page. It is reached through the setup card **Recipes** (receipt icon, menu label "Recipes") in the editor, which opens the `/recipes` route.

### Recipe List

The page lists all recipe types in a table with **Name**, **Description**, **Entries** and **Actions** columns. Recipes are sorted by name (case-insensitive). From the list you can:

- **Add a recipe type**: Opens the recipe editor dialog for a new type
- **Edit**: Opens the recipe editor dialog pre-filled with the selected type
- **Delete**: Removes the type after a confirmation prompt (requires a non-guest role, see Authorization)
- **Download**: Pushes the recipe's values to its devices
- **Upload**: Reads the current device values back into the recipe
- **Export**: Downloads the recipe as a JSON or CSV file
- **Import**: Loads a recipe from a JSON or CSV file

### Recipe Editor Dialog

The editor dialog lets you define or change a recipe type:

- **Name**: Required, up to 128 characters
- **Description**: Optional, up to 512 characters
- **Entries**: A table of tag entries with tag name, tag id and tag type; at least one entry is required

Use **Add entry** to open the tag browser and pick tags from your project's devices, and the remove button to drop an entry. Saving requires a name and at least one entry.

### Tag Browser

The tag browser helps select the tags a recipe entry should reference. It lists the devices of the current project; after selecting a device you see its tags and can filter by tag name or id. Selecting a tag adds it to the recipe with its name and type.

### Download, Upload and Progress

Clicking **Download** or **Upload** on a recipe opens a progress dialog (it cannot be closed while an execution is running) that shows:

- The current entry being processed and its status (writing/reading, success, error)
- A progress bar with the percentage completed
- The per-entry error messages, if any
- The final success and error counts

The dialog has a **Cancel** button that stops the running execution and a **Close** button once the execution has finished.

## The Recipe Widget

The Recipe widget is the operator-facing component for working with a recipe type directly in a view.

### Adding the Widget

In the editor's toolbar, select the **Recipe** control (receipt icon, tooltip "Recipe"). Drop it onto the view and open its properties.

### Widget Properties

The property panel of the widget offers:

- **Recipe**: Select the recipe type the widget is bound to. Instances of this type are shown at runtime. "No recipe selected" leaves the widget in an idle state.
- **Behavior**: Toggle **Read-only mode**. In read-only mode the widget hides the New, Delete and Save actions, shows values as read-only text, and only offers Download and Upload.
- **Authorization**: Configure which user roles may interact with the widget. The standard FUXA permission dialog is used; it controls both whether the action bar is shown and whether it is enabled.
- **Visible actions**: Independently show or hide the New, Delete, Save, Download and Upload buttons (visibility is independent of the permission setting).
- **Appearance**: Colors for the widget's background, text, border and accent.

### Runtime Operation

At runtime the widget loads all instances of the bound recipe type and displays them one at a time:

- **Navigation**: Previous/next buttons step through the instances, showing the current position (e.g. "2 / 5")
- **Entries**: The entries table shows each tag with a colored type badge and an editable value field (numeric fields use a number input, string values a text input)
- **New**: Creates a new instance of the type from a name/description dialog; the type's default entries are copied into it
- **Delete**: Removes the current instance after a confirmation prompt
- **Save**: Persists the edited values of the current instance
- **Download**: Saves any edited values first, then pushes the instance values to the devices and shows a progress bar ("Sending...")
- **Upload**: Reads the current device values into the current instance, shows progress, and refreshes the displayed entries when finished

In editor mode the widget renders inside the SVG canvas and permission checks are bypassed, so the designer can configure it freely.

## How Download and Upload Work

Both operations are asynchronous: the client starts them with a single API call and follows the live progress over WebSocket.

### Download (write to device)

1. The client calls `POST /api/recipes/download` with the recipe id
2. The server verifies the recipe exists, has at least one entry, and is not already running
3. It answers immediately with `202` and the total entry count, and continues in the background
4. For every entry the server coerces the stored value to the tag type and writes it to the device via `setTagValue`, emitting `recipe:download-progress` events with the status (`writing`, `success`, `error`), the entry index and the total
5. Entries that fail to write are reported individually and the loop continues; on completion a `recipe:download-complete` event carries the success/error counts and the error list

### Upload (read from device)

1. The client calls `POST /api/recipes/upload` with the recipe id
2. Validation is identical to download, and the server also answers `202` first
3. For every entry the server reads the current value from the device via `getTagValue` and updates the entry, emitting `recipe:upload-progress` events (`reading`, `success`, `error`)
4. If at least one entry succeeded, the updated values are persisted; a `recipe:upload-complete` event reports the outcome, and the client reloads the instance list

### Value Coercion

Values are converted to the correct JavaScript type before being written, according to the entry's tag type:

- **bool/boolean**: accepts booleans, `"true"`/`"1"` and `"false"`/`"0"`
- **int/dint/int16/int32**: parsed as integers
- **real/float/double/number**: parsed as floating point
- **byte**: clamped to the range 0-255
- **string/word**: passed through unchanged

### Cancellation and Concurrency

- A recipe that is already running cannot be started again; the API answers `400 "Recipe execution already in progress"`. This guard covers both download and upload of the same recipe.
- The client requests cancellation by emitting `recipe:cancel-execution` over WebSocket (requires write authorization). The server removes the recipe from the running set, the loop stops at the next entry, and a `recipe:cancel-confirmed` event is emitted.

## Authorization

Recipe authorization is enforced in two places:

- **Server (REST API)**: Recipe templates are project data and use dedicated endpoints: `GET/POST/DELETE /api/recipes/types`. When secure mode is enabled, creating, editing, deleting and importing templates (`POST /api/recipes/types/import`) require admin permission resolved from the user's groups and roles. Recipe instances are operational data and use `GET/POST/DELETE /api/recipes/instances`; in secure mode they require an authenticated non-guest user and the linked recipe template's permission must allow the operation. `POST /api/recipes/download` and `POST /api/recipes/upload` use the same template permission check because they write/read live tag values.
- **Recipe widget**: The permission configured in the widget's **Authorization** setting decides at runtime whether the action bar is shown and enabled. Editor mode always bypasses the check. The **Visible actions** setting is independent of the permission and only controls which buttons render.

## Data Storage

Recipe data is split between project configuration and operational storage:

- Recipe types/templates are stored in the project, like alarm definitions, because they define the tag structure and reference project tags.
- Recipe instances are stored in a SQLite database (`recipes.db` in the server working directory). Instances keep their own values and reference the template through `typeId`.

- Recipe ids look like `r_` followed by 12 hex characters; entry ids like `e_` followed by 8
- Validation limits: name required (max 128 chars), description optional (max 512), between 1 and 1000 entries, every entry with a non-empty tag id and a valid tag type

## Usage Examples

### CIP (Clean-In-Place) Recipe Type

- Type: "CIP Cycle"
- Entries: temperature setpoint (real), caustic concentration (real), wash time (int), rinse time (int), pump speed (int)
- Instances: one per product — "Product A", "Product B", "Product C" — each with different setpoints

Before a production run the operator selects the product instance in the Recipe widget and presses **Download**. The server writes every value in order to the tank PLC; the widget shows the progress. When the line changes to another product, the operator picks a different instance and downloads it again.

### Capture Current Operating Values

An operator tunes the process manually and wants to keep the result. In the widget they select an instance and press **Upload**. The server reads the live values from the device into the instance, persists them, and the widget shows the new values. The instance can later be downloaded again to reproduce the same conditions.

## Best Practices

1. **Design types first**: define a recipe type for each machine or process, then create instances for the concrete parameter sets
2. **Keep entries manageable**: a recipe supports up to 1000 entries; split unrelated tags into separate types for clarity
3. **Verify device connectivity**: download/upload report per-entry failures, but a disconnected device fails every entry — check the device status first
4. **Use read-only mode for operator screens**: if operators should only apply values, enable read-only mode so the values cannot be edited in the view
5. **Limit the visible actions**: use the widget's visibility settings to show only the actions operators actually need
6. **Test with a single entry**: start with a small recipe to validate the flow before working with large ones

## Troubleshooting

### Common Issues

- **"Recipe execution already in progress"**: the recipe is still running (or a previous execution did not finish). Wait for it to complete or cancel it from the progress dialog.
- **"Write failed for tag" / "Read failed for tag"**: the device is not reachable or the tag id is no longer valid in the project.
- **Download completes with errors**: the per-entry error list in the progress dialog names the failing tags; check their values and types.
- **Guest cannot save or delete recipes**: when secure mode is enabled, write operations require a non-guest role.
- **Widget shows "No recipe type configured"**: open the widget properties and select a recipe type.
- **Widget action bar is hidden**: the configured authorization or the visible-actions settings are hiding the buttons; in editor mode the permission check is bypassed.

### Debug Information

Recipe operations log to the server console and log files with details about:

- Recipe data validation errors (invalid name, entries, tag types or values)
- Download/upload start, per-entry write/read results, and completion summaries
- Execution cancellations and unauthorized requests
- Storage errors (database access, JSON parsing)

---

### HowTo Scheduler (`docs/HowTo-Scheduler.md`)

# HowTo Scheduler

The Scheduler is a powerful time-based automation system in FUXA that allows you to create events that trigger at specific times based on days of the week, days of the month, and months. It provides both timer mode (on/off periods) and event mode (duration-based triggers) with full device control capabilities.

## Overview

The Scheduler enables automated control of devices and tags based on time schedules. Key features include:

- **Day-of-Week Scheduling**: Schedule events for specific days (Monday-Sunday)
- **Month Mode**: Advanced scheduling by month and day-of-month combinations
- **Timer Mode**: Define start and end times for on/off periods
- **Event Mode**: Trigger events for specific durations
- **Device Actions**: Execute additional actions like setting values or running scripts
- **Master Control**: Scheduler acts as master controller for device tags

## Scheduling Modes

### Day-of-Week Mode

The default scheduling mode allows you to select specific days of the week for your events.

- **Days**: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
- **Time Range**: Define start time and end time for timer mode, or start time and duration for event mode
- **Visual Indicators**: Selected days are highlighted in the schedule display

### Month Mode

Advanced scheduling that combines month and day-of-month selections for precise control.

- **Months**: January through December
- **Days of Month**: 1st through 31st
- **Important Notes**:
  - If a month has fewer than 31 days (e.g., February with 28 days), events scheduled for the 31st will not execute
  - Node.js scheduler does not support "last day of month" functionality
  - Events only trigger on valid date combinations

## Device and Tag Relationship

### Device Binding

Each schedule is bound to a specific device and controls its associated tag:

- **Device Selection**: Choose from available devices in your project
- **Tag Control**: Scheduler directly controls the device's primary tag
- **Master Authority**: Scheduler always takes precedence over manual tag changes

### Device Actions

In addition to controlling the primary device tag, you can configure additional actions:

- **Set Value**: Set other device tags to specific values
- **Run Script**: Execute custom JavaScript functions
- **Trigger Points**: Actions execute on both start and end events

Device actions are configured in the scheduler properties and provide extended automation capabilities beyond basic on/off control.

## Authorization

The Scheduler implements a two-tier authorization system:

### Master Authorization
- **Automatic Assignment**: Scheduled devices automatically receive master authorization level
- **Minimum Requirement**: Users must have at least master authorization to interact with scheduled devices
- **Override Capability**: Master auth allows the scheduler to override manual tag changes during active events

### Per-Device Authorization
- **Additional Security**: Individual devices can have higher authorization levels beyond the master requirement
- **Granular Control**: Different devices can require different permission levels (e.g., critical equipment may need admin access)
- **Layered Security**: Master auth provides base access, while per-device auth adds device-specific restrictions

## Add/Edit Schedule Form

### Basic Settings

- **Device**: Select the target device to control
- **Start Time**: When the event should begin (HH:MM format)
- **Mode Toggle**: Switch between Timer Mode and Event Mode

### Timer Mode vs Event Mode

#### Timer Mode
- **End Time**: Define when the event should stop
- **Tag Behavior**: Tag remains ON from start time to end time
- **Use Case**: Regular on/off periods (e.g., 8:00 AM to 6:00 PM)

#### Event Mode
- **Duration**: Set how long the event should last
- **Duration Units**: Hours, minutes, seconds
- **Tag Behavior**: Tag goes ON for the specified duration, then automatically OFF
- **Use Case**: Timed triggers (e.g., run pump for 30 seconds)

### Recurring vs One-Time Events

- **Recurring**: Event repeats according to schedule (default)
- **One-Time**: Event executes once and is automatically removed
- **Auto-Cleanup**: Non-recurring events are deleted after completion

### Month Mode Configuration

When Month Mode is enabled:

- **Month Selection**: Choose which months the event should run
- **Day-of-Month Selection**: Select specific dates within those months
- **Calendar View**: Click the calendar button to see a visual representation of your schedule

## Data Storage

### Database Structure

Schedules are stored in the project database with the following structure:

```json
{
  "schedules": {
    "Device Name": [
      {
        "id": "unique_schedule_id",
        "startTime": "08:00",
        "endTime": "18:00",
        "days": [false, true, true, true, true, true, false],
        "months": [false, false, false, false, false, false, false, false, false, true, false, false],
        "daysOfMonth": [false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false],
        "monthMode": true,
        "recurring": true,
        "eventMode": false,
        "duration": 0,
        "deviceName": "Device Name",
        "variableId": "tag_id"
      }
    ]
  },
  "settings": {
    "deviceActions": [...]
  }
}
```

### Key Fields

- **days**: Boolean array for Monday-Sunday (7 elements)
- **months**: Boolean array for January-December (12 elements)
- **daysOfMonth**: Boolean array for 1st-31st (31 elements)
- **monthMode**: Enables month-based scheduling when true
- **eventMode**: Enables duration-based events when true
- **recurring**: Controls whether event repeats or executes once

## Usage Examples

### Daily Pump Schedule
- Device: Water Pump
- Days: Monday-Friday
- Start: 08:00, End: 18:00
- Mode: Timer Mode
- Result: Pump runs during business hours on weekdays

### Monthly Maintenance
- Device: Maintenance Valve
- Months: January, April, July, October
- Days: 15th of each month
- Start: 09:00, Duration: 2 hours
- Mode: Event Mode, Non-recurring
- Result: Valve opens for maintenance on quarterly schedule

### Holiday Lighting
- Device: Holiday Lights
- Months: December
- Days: 1st through 31st
- Start: 17:00, End: 23:00
- Mode: Timer Mode
- Result: Lights run every evening in December

## Best Practices

1. **Test Schedules**: Always test new schedules during development
2. **Month Limitations**: Avoid scheduling for invalid date combinations
3. **Device Actions**: Use device actions for complex automation logic
4. **Time Zones**: Be aware of server time zone for scheduling
5. **Resource Management**: Non-recurring events auto-cleanup to prevent accumulation

## Troubleshooting

### Common Issues

- **Event Not Triggering**: Check month/day combinations for validity
- **Tag Not Changing**: Verify device connection and permissions
- **Actions Not Executing**: Check device action configuration
- **Schedule Not Saving**: Ensure proper permissions and database access

### Debug Information

Scheduler logs include detailed information about:
- Schedule loading and parsing
- Event triggering with timestamps
- Tag write operations
- Device action execution
- Month mode date validation

---

### HowTo UI Layout (`docs/HowTo-UI-Layout.md`)

To define the Layout of end user going to **Layout settings** in editor.



In **General** tab you can define:
- **Start View**, the first View (home) show by going to http://localhost:1881.
- **Zoom**, if enabled you can Zoom the View with the Mouse Scroll Wheel and move the View with the Mouse Left Button.
- **Dialog Mode of input field**, if enabled will be show a dialog by Input value control.
    - 

- **Show Navigation**, setting to Show and Hide the Header bar and the navigation Menu
- Hide the development button to switch between Edit and Home.

  


In **Navigation Side Menu** tab you can define the Menu items and diifferent style attributes.



In **Header Navigation Bar** tab you can define if and how to show the Alarms notification items, and diifferent style attributes.

---

### HowTo View (`docs/HowTo-View.md`)

To create a View going in editor. Click on the "+" icon in the top left corner. Select "Canvas/SVG".<br />


Click to "Property".<br />


Define the View size and background.<br />

---

### HowTo WebSockets (`docs/HowTo-WebSockets.md`)

WebSocket is a great simple way to communicate with other web applications such as Node-Red. We can use WebSocket directly in Fuxa Scripts.

Here is an example of bidirectional data transfer, we wrap all Fuxa tags and values into a JSON Data object with a Timestamp and Payload. We use a for loop to grab all the tag values and insert into the Payload and we also use a for loop to write tags values received from the WebSocket.

The Script has to be a Server side script and set for on Startup. You have to have a already running WebSocket Server as the below code is for WebSocket Client. 

It is possible to also create a WebSocket Server in the Fuxa Script if needed. The below code only covers client connection to a server. 

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### HowTo Widgets (`docs/HowTo-Widgets.md`)

**Fuxa Widgets**

Fuxa widgets use pure SVGs with Javascript by the use of script tags
```
<svg>
  SVG Content Here
  <script>
     JS Script Content Here
  </script>
</svg>
```
We can transfer data between the SVG script using some simple functions and declaring the variables as global
```
_pb_ = bool   parameter ( true or false )
_pn_ = number parameter ( Int, Float, Real etc )
_ps_ = string parameter ( string can be entered directly )
_pc_ = color parameter in hex color code ( #00ff00ff )
```
It's also very important that the variables are contained within these comments `//!export-start` and `//!export-end`
```
//!export-start
let _pn_value = 50;
//!export-end
```
The variables will now be available in Fuxa in the SVG properties panel where you can bind Fuxa Tags
 

In order to transfer data between the Widget/SVG we have 2x functions we need to use:

To send values to Fuxa from the SVG
```
function postValue(id, value) {
  console.error('Not defined!');
}
```
You need to call the function, the ID needs to match the exact variable name
```
postValue('_pn_value', someNewValue);
```

To receive values from Fuxa to the SVG
```
function putValue(id, value) {
  if (id === '_pn_value') {
    callFunction(value);
    newVar = value;
  }
}
```
Here we wait for the function to be called in Fuxa and check for the ID we are after which is the exact variable name defined

If you use `setInterval` you also need to clear the interval when you change page or enter the editor, if you do not do this the widget will become broken due to another `setInterval` being called. In our examples we check the object if it exist and use the MutationObserver

```
const checkDestroy = document.getElementById('svgIdName'); // Important must be name of the SVG!
if (!checkDestroy) {
  clearBlinking(); 
  return;
}
```
And the Mutation Observer 
```
// Set up the MutationObserver to watch for removal of the SVG element
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    mutation.removedNodes.forEach((node) => {
      if (node.id === 'svgIdName') { // Important must be name of the SVG!
        clearBlinking(); 
        observer.disconnect(); 
      }
    });
  });
});
// Start observing the body or a parent element of the SVG
observer.observe(document.body, { childList: true, subtree: true }); 
```

It's important you use the SVG Id `<svg id=svgIdName>` at the top of the SVG file

You can also use CSS in the SVG and full JS and access the elements/ids using standard `getElementById` and `addEventListener`

For full working examples, see the examples in the widget section https://github.com/frangoteam/FUXA/tree/master/server/_widgets

---

### HowTo Animate Pipe (`docs/HowTo-animate-Pipe.md`)

First you design your pipe form and then you define the action by bind the Device-Tag (Variable) with the animation.

---

### HowTo Bind Controls (`docs/HowTo-bind-Controls.md`)

To bind a **Output** Control to the Device Tag (Variable): going in editor and select the View.


To bind a **Input** Control to Device Tag.


To bind a **Select** Control to Device Tag.


To bind a **Slider** Control to Device Tag.

---

### HowTo Bind Shapes (`docs/HowTo-bind-Shapes.md`)

To bind **Shapes** to the Device Tag (Variable): going in editor and select the View.


To bind **Process Engineering** Shapes (Tank) to Device Tag.

---

### HowTo Configure Script (`docs/HowTo-configure-Script.md`)

To configure scripts going to **Scripts** in editor.



Add a new script (a script is a javascript function), configure the function name and parameters, there are 2 type of parameters:
 - Tag ID
 - Value, number or string

Then you write your logic into the function. There are system call that you can use:
 - $setTag, to set value of Tag
 - $getTag, to get current Tag value



You can test the script, to verify it makes sense to use the console.log method.



From the GUI you can configure in **Events** the call of script.



If you use intervals in a script it's important you handle them correctly. For example if you have setInterval in a script and run the test multiple times this will create a new interval each time calling the same piece of code and you will have to restart Fuxa to clear it. One method to handle this is assign an ID to the interval and do a simple check if it exist. 

```
if (typeof globalThis.myTimer === 'undefined') globalThis.myTimer = null;

if (!globalThis.myTimer) globalThis.myTimer = setInterval(myTimerFunction, 1000);

async function myTimerFunction() {
  //myTimer code here every 1 sec
}
```

---

### HowTo Configure Events (`docs/HowTo-configure-events.md`)

In the Shapes and button control you can configure the mouse **Events** click, mouseDown, mouseUp to execute tasks.

   - [**Open Page**](#open-page)
   - [**Open Card**](#open-card)
   - [**Open Dialog**](#open-dialog)
   - [**Open iframe**](#open-iframe)
   - [**Open Window**](#open-window)
   - [**Set Value**](#set-value)
   - [**Toggle Value**](#toggle-value)
   - [**Set from Input and Close**](#set-from-input-and-close)


## Open Page

Used to display a View in **Main** window.



## Open Card

Used to display a View as Popup window. The **Card** will be displayed next to the mouse at the time of the event, several cards can be displayed at the same time. The same **Card** can be used for example to show values of more pumps.



## Open Dialog

Used to display a View as Dialog, typically to configure values. The **Dialog** will be displayed on top of the screen. The same **Dialog** can be used for example to configure values of more pumps.



## Open iframe

Used to open a window as **iframe** (embedded HTML document from external sources). You can define the window size and the zoom scala.



## Open Window

Used to open a **window** in a new browser. You can define the **window** size.



## Set Value

Used to set Tags value or increase and decrease current value.



## Toggle Value

Used to **toggle** the Tags value 1/0 (if 1 set to 0, if 0 set to 1).



## Set from Input and Close

Combined can be used to set some values through a dialog with a OK confirmation

---

### HowTo Define Shapes (`docs/HowTo-define-Shapes.md`)

The shapes folder are located in: ‘client/dist/assets/lib/svgeditor/shapes’ (or ‘client/src/lib/svgeditor/shapes’ if you are debugging), you can add a new shape by creating a new javascript file (best copying it from ‘my-shapes.js’) or editing an existing file.

In the javascript file you can change the following content:
```
var shapesGroupName = 'Shapes'; // used to organize and gropping the shapes, displayed in editor menu as label with expand/collapse

var typeId = 'shapes';  // used to identify shapes type, 'shapes' is binded with angular component 'ShapesComponent'
                        // if you make a new type you have to implement the angular component too

```

in this array your shapes data, the shape object has the following attributes:
```
var shapes = [{ name: 'diamond', 
                ico: 'assets/lib/svgeditor/shapes/img/shape-diamond.svg', 
                content:[{ id: '', 
                            type: 'path', 
                            attr: { d: 'M 20 0 L 40 20 L 20 40 L 0 20 Z' }
                        }]
		},...]

// 'name': is unique for shape type
// 'ico': path icon displayed in editor menu
// 'content': array of svg element
// 'id': element id used if you like make an animation managed in angular component
// 'type': svg element type (path, text, ellipse,...) see svg description
// 'attr': element attribute, depending of type

```

To design my shapes I used the Inkscape application. You can see with XML Editor the elements and nodes attribute to add in 'type' and 'attr'.

**!You have to leave the rest of file content**

If you have created a new javascript shapes file, you have to add to loader too, 
then add to ‘shapesLoader.js’ your file name that will be loaded dynamically (don’t remove ‘shapesLoader.js’)
```
var shapesToLoad = ['my-shapes.js', 'your shape file name.js'];

```

If you have created a new shapes type, for example to define your own animations, you have to implement the correspondent angular component too. It is better to looking the files content in ‘client/src/app/gauges/shapes/’

---

### HowTo Save Load Project (`docs/HowTo-save-load-Project.md`)

### Create a project
You can create a new project, the current project will be overwritten with the new void project (that have only a void MainView).



### Save the project
The project will be automatically saved in an internal database after every change of settings (dialogs confirmation) like editings 'Device', 'Tag', 'Charts',....
The Views will be automatically saved by selecting another or by leaving the editor.

By **Save Project** you can force the internal saving process.
By **Save Project As…** you can export the whole project to a file in JSON format (MyProject.json), useful for making backups of the project.

### Open the project
You can open a project from the exported file (MyProject.json).

---

### HowTo Setup Alarms (`docs/HowTo-setup-Alarms.md`)

To configure alarms going to **Alarms** in editor.



An alarm ist bind to one Tag to whitch can you define 4 types of conditions for respective levels (High High, High, Low and Message).



The active alarms and the history can be displayed by configuring the view from the menu or the button on the header. [**Layout settings**](https://github.com/frangoteam/FUXA/wiki/HowTo-View-Layout).



It is also possible to configure several actions per alarm like show popup dialog or set a value to a tag.

---

### HowTo Use Same View (`docs/HowTo-use-same-view.md`)

You can reuse the same **View** for repeating components like pumps and valves.



You have to define a device as **internal** and some variable (Tag) and bind it to the controls of reusable **View**.
For example the title dialog and a variable for the input value.



Then define in every component the **Events** to open the dialog, where you map the internal tags to the device tags.



If you wish, you can also add a confirmation button.



## Extended Method: Shared detail page with Target Device

The core purpose of this feature is to dynamically display detailed data of multiple devices of the same type through one shared page template, avoiding separate detail pages for each device.

### Implementation Steps

1. Establish device connections and tags
Create two (or more) device connections of the same type in **Connections**.
Use exactly the same tag names under each device to keep data structure and references consistent.


2. Create view pages
Create two views:
- **Device List Page**: displays all devices and contains the "View Details" buttons.
- **Device Detail Page**: a reusable template for detailed device data.


3. Configure navigation logic in list page buttons
On each "View Details" button:
- **Target Page**: set the shared Device Detail Page.
- **Target Device**: set the specific device for that button.


4. Bind tags in the detail page template
In the detail page, bind variables using `@[tag_name]` (example: `@[device_name]`).
When opened from the list page with Target Device, placeholders are resolved against the selected device tags.


- Chart configuration supports placeholders, so charts can be reused with the same dynamic device-binding approach.



Summary:
One template page can show details for multiple devices. Clicking different device buttons opens the same detail page with the corresponding real-time data.

Reference and images:
- Discussion #2118: https://github.com/frangoteam/FUXA/discussions/2118

### Binding with internal adapter proxy
Internal connection adapter as UI proxy
You can define internal connection adapters and create tags bound to UI components independently of a specific device.
Via client-side scripts, assign which real device the internal adapter should reference at runtime.
Reads/writes are redirected to the selected device when tag names match.




Reference and images:
- Pull Request #1810: https://github.com/frangoteam/FUXA/pull/1810

---

### Installing And Running (`docs/Installing-and-Running.md`)

FUXA is developed with NodeJS (backend) and Angular (frontend).

**Prebuilt Electron App** (first option)

You will need to be logged into github to access the download button for Electron Action Builds, 
click on the workflow and scroll down to Artifacts and click the download icon for you system

[Electron Action Builds](https://github.com/frangoteam/FUXA/actions/workflows/electron_latest.yml)

<img width="2082" height="531" alt="image" src="https://github.com/user-attachments/assets/cf0e5282-f1ef-48b3-a122-de2a2754cf19" />

**Headless Portable Binaries** (alternative to Electron)

For headless deployments on embedded devices, servers, or systems without a GUI, FUXA provides self-contained portable binaries that bundle the entire application (server, client) into a single executable file.

These binaries are ideal for industrial environments, IoT devices, and automated systems where no user interface is needed.

### Downloading Headless Binaries

Headless binaries are available as GitHub Actions artifacts. You need to be logged into GitHub to download them.

[Headless Portable Builds](https://github.com/frangoteam/FUXA/actions/workflows/headless_packaging.yml)

Click on the latest workflow run, scroll down to "Artifacts", and download the appropriate file for your platform:

- **Linux**: `fuxa-headless-linux-x64` (or `fuxa-headless-linux-arm64` for ARM devices)
- **Windows**: `fuxa-headless-win-x64.exe`
- **macOS**: `fuxa-headless-macos-x64` (or `fuxa-headless-macos-arm64` for Apple Silicon)

### Running Headless Binaries

#### Linux
1. Download the Linux binary (e.g., `fuxa-headless-linux-x64`).
2. Make it executable: `chmod +x fuxa-headless-linux-x64`
3. Run: `./fuxa-headless-linux-x64`
4. Access via web browser at `http://localhost:1881` (or the device's IP:1881)

#### Windows
1. Download the Windows executable (e.g., `fuxa-headless-win-x64.exe`).
2. Double-click the `.exe` file to run.
3. Access via web browser at `http://localhost:1881`

#### macOS
1. Download the macOS binary (e.g., `fuxa-headless-macos-x64`).
2. Make it executable: `chmod +x fuxa-headless-macos-x64`
3. Run: `./fuxa-headless-macos-x64`
4. Access via web browser at `http://localhost:1881`

### Headless Features
- **Self-contained**: No external dependencies or installations required.
- **Data persistence**: User data is stored in `~/.fuxa-headless-data` on the host system.
- **Cross-platform**: Runs on Windows, macOS, and Linux (including ARM architectures).
- **Industrial protocols**: Full support for Modbus, OPC-UA, MQTT, Siemens S7, and more.
- **Web interface**: Access the full FUXA editor and dashboards via any web browser.

### Troubleshooting
- Ensure port 1881 is available and not blocked by firewalls.
- For embedded devices, check system resources (RAM, CPU) as FUXA uses Node.js runtime.
- Logs are available in the user data directory (`~/.fuxa-headless-data/logs`).

**Access with Web browser once installed non Electron Install**

Once you have finished one of the install processes you can access the Fuxa UI via default port 1881 and the web server IP address either localhost or the IP of the Host machine, first try localhost:1881 or host machine IP:1881

**Docker Compose** (second option)

Install Docker
```
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh ./get-docker.sh
```
```
cd
mkdir docker
mkdir fuxa
cd docker/fuxa
sudo nano docker-compose.yml
```

Enter this into compose file:
```
version: '3.5'

services:
    fuxa:
        image: frangoteam/fuxa:latest
        network_mode: "host"
        volumes:
            - ./fuxa_appdata:/usr/src/app/FUXA/server/_appdata
            - ./fuxa_db:/usr/src/app/FUXA/server/_db
            - ./fuxa_logs:/usr/src/app/FUXA/server/_logs
        environment:
            - TZ=America/New_York
        restart: always
```
Note: This Compose file uses host network mode see [Docker Networks](https://docs.docker.com/engine/network/) for more details, host allows the docker container to be on the same networks as the host machine. Works best for direct access to PLCs/Databases etc but there are some limitations, it only works on Linux based systems, if you need to run on Windows you will need to use bridge network mode.

`network_mode: "bridge"`

Start Docker Compose

`sudo docker compose up -d`

**Docker** (third option)
```
docker pull frangoteam/fuxa:latest
docker run -d -p 1881:1881 frangoteam/fuxa:latest
```
Persistent storage of application data (project), daq (tags history), logs and resources images
```
docker run -d -p 1881:1881 -v fuxa_appdata:/usr/src/app/FUXA/server/_appdata -v fuxa_db:/usr/src/app/FUXA/server/_db -v fuxa_logs:/usr/src/app/FUXA/server/_logs -v fuxa_shapes:/usr/src/app/FUXA/client/assets/lib/svgeditor/shapes -v fuxa_images:/usr/src/app/FUXA/server/_images frangoteam/fuxa:latest
```
**Build Custom Docker Image from Source** (fourth option)

This will build from the latest master branch ( you can edit the docker file to change the branch )
```
cd
mkdir docker
mkdir fuxa
cd docker/fuxa
mkdir fuxa-build
cd fuxa-build
```
`wget https://raw.githubusercontent.com/frangoteam/FUXA/master/Dockerfile`

`sudo docker build -t fuxa-custom-image-name --no-cache .`

Once the build is complete, you can now use the custom image for example:

```
version: '3.5'

services:
    fuxa:
        image: fuxa-custom-image-name
        network_mode: "host"
        volumes:
            - ./fuxa_appdata:/usr/src/app/FUXA/server/_appdata
            - ./fuxa_db:/usr/src/app/FUXA/server/_db
            - ./fuxa_logs:/usr/src/app/FUXA/server/_logs
        environment:
            - TZ=America/New_York
        restart: always
```

**Installing with Node and NPM**

You need to have installed [Node](https://nodejs.org/en/about/previous-releases) Version 18.

**WARNING** In linux with nodejs Version 18 the installation could be a challenge. If you don't intend communicate with Siemens PLCs via S7 (node-snap7 library) you can install from [NPM @frangoteam/fuxa-min](https://www.npmjs.com/package/@frangoteam/fuxa-min)

**NPM** (fifth option)

Install from [NPM](https://www.npmjs.com/package/@frangoteam/fuxa)
```
npm install --unsafe-perm @frangoteam/fuxa
fuxa
```

**Latest Release** (fifth option)

[Download the latest release](https://github.com/frangoteam/FUXA/releases) and unpack it

**WARNING** In linux with nodejs Version 18 the installation could be a challenge. If you don't intend communicate with Siemens PLCs via S7 you can remove the node-snap7 library from the server/package.json

```
cd ./server
npm install
npm start
```

**Open up a browser (better Chrome) and navigate to http://localhost:1881**


**Setting up as an HMI** ( Linux Only )

This has only been tested on linux, but some of the steps may work on Windows
 
We will use electron instead of a web browser, as it's not bloated and works well with Touch Screens ( can disable right click menu etc )

The app is only accessing the already running Fuxa Web Server 

`sudo apt-get update`

`curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -`

`sudo apt-get install -y nodejs`


Verify the installation by checking the versions of Node.js and npm:

`node -v`

`npm -v`

`sudo npm install electron --unsafe-perm=true --allow-root`

```
cd /opt 
sudo mkdir electron
cd electron
sudo mkdir fuxa-electron
cd fuxa-electron
```

`sudo npm init -y`

`sudo npm install electron --save-dev`

`sudo nano main.js`

```
const { app, BrowserWindow } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    //width: 800,
    //height: 600,
    fullscreen: true, // Enable full-screen mode
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
    }
  });

  win.loadURL('http://localhost:1881'); // Replace with your web server URL
}

app.whenReady().then(createWindow);
```

`sudo nano package.json`

```
{
  "name": "fuxa-electron",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "description": "",
  "devDependencies": {
    "electron": "^31.3.1"
  }
}
```

Start the app from within the directory to test

`npm start`

Setup auto startup using a system service and a script

`sudo nano /opt/fuxa-electron-startup.sh`

```
#!/bin/bash
cd /opt/electron/fuxa-electron
npm start
```

`sudo chmod +x /opt/fuxa-electron-startup.sh`

`systemctl edit --user --force --full fuxa-electron-startup.service`

```
[Unit]
Description=Start Fuxa Electron Script
After=default.target
[Service]
ExecStart=/opt/fuxa-electron-startup.sh
[Install]
WantedBy=default.target
```

`systemctl enable --user fuxa-electron-startup.service` 

**Under a reverse proxy**

When working under a reverse proxy, you can specify a custom BASE_PATH with trailing slashes

> e.g. to serve at `foo.bar/fuxa` you can set the env variable BASE_PATH=/fuxa

---

### Settings (`docs/Settings.md`)

Server settings are located in 'server\\_appdata\settings.js' file. You have to restart the server after your change.

### Authentication
To enable and config the authentication:

```
secureEnabled: true,            // enable or diasable
secureOnlyEditor: false,        // if true, sign-in is required only for the Editor and admin pages, values can be changed by anyone
secretCode: '<strong-random-secret>', // secret code to encode the token
tokenExpiresIn: '1h'            // token expiration delay '1h'=1hour, 60=60seconds, '1d'=1day
```

The default user ‘admin’ have the ‘123456’ as password, of course you can change it.

---

### Tips And Tricks (`docs/Tips-and-Tricks.md`)

Editor Keyboard Shortcuts:
- Ctrl + Left / Right: rotate selected item
- Ctrl + Shift + Left / Right: rotate the selected item in big step
- Shift + O / P: select the prev / next item
- Tab / Shift + Tab: select the prev / next item
- Ctrl + Up / Down: central zoom in / out
- Ctrl + Z / Y: undo / redo
- Shift + 'resize with mouse the selected item': lock width and height
- Shift + Up / Down / Left / Right: move the selected item
- Shift + SCROLLER: zoom by mouse position
- Ctrl + A: select all item
- Ctrl + G: group or ungroup selected item
- Ctrl + D: duplicate selected item
- Shift + 'Draw line': line gradient horizonal, vertical 45° diagonal
- Ctrl + X: cut selected item
- Ctrl + C / V: copy / paste selected item

---

### Ar Mode (`docs/ar-mode.md`)

# FUXA AR Mode

## Goal
Browser-based AR mode for tablets.
The camera detects physical plant markers and displays live FUXA cards/gauges linked to marker IDs.

## MVP Decision
First pass uses a dedicated `/ar` route with camera feed and lightweight live tag cards.
No full SVG View rendering.
No backend changes.
No native app.

## Proposed MVP Stack
- Angular fullscreen route
- Browser `getUserMedia`
- Browser `BarcodeDetector` for first QR/barcode prototype
- FUXA `hmiService.viewsTagsSubscribe(tags, true)`
- FUXA `hmiService.onVariableChanged`

## Later AR Tracking
Replace/extend BarcodeDetector with:
- MindAR for image tracking
- or AR.js for marker tracking

## Files
- `client/src/app/_models/ar.ts`
- `client/src/app/_models/project.ts`
- `client/src/app/ar/ar.component.ts`
- `client/src/app/ar/ar.component.html`
- `client/src/app/ar/ar.component.scss`
- `client/src/app/app.routing.ts`
- `client/src/app/app.module.ts`
- `client/src/app/app.component.ts`

## MVP Scope
- `/ar` route
- fullscreen camera
- marker detection
- markerId → tag card
- live values update
- no editor UI yet

## Open Questions
- Marker format
- Operator auth behavior
- Multi-marker behavior
- Safari/iPad support
- MindAR integration after MVP

---

--- METRICS ---
- Files Extracted: 26
- Estimated Token Budget: ~21422 tokens
- Canonical Reference: https://codewiki.google/github.com/frangoteam/FUXA
