{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.htmlelements.com/industrial/studio/schema/project.schema.json",
  "title": "Scada Studio project",
  "description": "A Scada Studio project file: the settings, the sources, the pages with their components, and the flows (message flows and dataflow diagrams). Schema smart-industrial-project/1. Studio.migrate() brings older files to it; Studio.validateProject() checks the same rules without a validator.",
  "type": "object",
  "required": [
    "schema",
    "name",
    "pages"
  ],
  "properties": {
    "schema": {
      "const": "smart-industrial-project/1"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "description": "The project's name, shown as the app's title."
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "The author's version number of the project."
    },
    "theme": {
      "type": "string",
      "description": "bench, industrial, industrial-dark, or empty for the default theme.",
      "examples": [
        "bench",
        "industrial",
        "industrial-dark",
        ""
      ]
    },
    "grid": {
      "type": "object",
      "properties": {
        "columns": {
          "type": "integer",
          "minimum": 1,
          "maximum": 48,
          "default": 12
        },
        "rowHeight": {
          "type": "number",
          "minimum": 8,
          "default": 40,
          "description": "Pixels."
        },
        "gap": {
          "type": "number",
          "minimum": 0,
          "default": 12,
          "description": "Pixels between cells."
        }
      },
      "additionalProperties": true
    },
    "sources": {
      "type": "array",
      "description": "Where the tags come from, in the browser: a simulator, an agent, a websocket, sse, poll or manual source.",
      "items": {
        "type": "object",
        "required": [
          "id",
          "type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^[A-Za-z][A-Za-z0-9_-]*$"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "simulator",
              "agent",
              "websocket",
              "sse",
              "poll",
              "manual"
            ]
          },
          "url": {
            "type": "string",
            "description": "The address of an agent, a websocket, an SSE or a poll source; empty for the agent that serves the app."
          },
          "token": {
            "type": "string",
            "description": "A token for an agent with auth, for a screen that must never ask."
          },
          "interval": {
            "type": "number",
            "minimum": 0
          },
          "tags": {
            "description": "The simulator's tags by name, or the list of tags of an agent, websocket, sse, poll or manual source.",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "object"
                }
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "name"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "unit": {
                      "type": "string"
                    },
                    "writable": {
                      "type": "boolean"
                    }
                  }
                }
              }
            ]
          }
        },
        "additionalProperties": true
      }
    },
    "pages": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": [
          "id",
          "components"
        ],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1
          },
          "name": {
            "type": "string"
          },
          "components": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "tag",
                "at"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "minLength": 1,
                  "description": "Unique in the project: the terminals on the diagrams name it."
                },
                "tag": {
                  "type": "string",
                  "pattern": "^[a-z][a-z0-9-]*$",
                  "description": "The element: smart-gauge, smart-led, studio-text, studio-box…"
                },
                "at": {
                  "type": "object",
                  "required": [
                    "col",
                    "row",
                    "w",
                    "h"
                  ],
                  "properties": {
                    "col": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "row": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "w": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "h": {
                      "type": "integer",
                      "minimum": 1
                    }
                  }
                },
                "caption": {
                  "type": "string",
                  "description": "The owned label above the component."
                },
                "props": {
                  "type": "object",
                  "description": "The element's properties, as the inspector sets them."
                },
                "bind": {
                  "type": "object",
                  "additionalProperties": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "object",
                        "required": [
                          "tag"
                        ],
                        "properties": {
                          "tag": {
                            "type": "string"
                          },
                          "map": {
                            "type": "object",
                            "description": "value -> what the property is given: { \"true\": \"running\", \"false\": \"stopped\" }; \"*\" catches the rest."
                          }
                        }
                      }
                    ]
                  },
                  "description": "property -> tag reference (source.tag), or { tag, map }."
                },
                "feed": {
                  "type": "object",
                  "description": "A chart's pens: property -> tags."
                },
                "on": {
                  "type": "object",
                  "description": "event -> what the operator's request writes: { write: property } sends detail.value to the tag bound to that property; { tag } sends it to a tag named outright; map ({ \"start\": 1, \"stop\": 0 }) or a fixed value changes what is written; sign asks for an electronic signature. A device's command word counts as its value."
                },
                "actions": {
                  "type": "object",
                  "description": "event -> { tag, value } writes."
                },
                "station": {
                  "type": "string",
                  "description": "The agent source whose records a recipe or job panel shows; the project's first agent when absent."
                }
              },
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      }
    },
    "flows": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "nodes",
          "wires"
        ],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "message",
              "dataflow"
            ],
            "description": "Absent means a message flow."
          },
          "runOn": {
            "type": "string",
            "enum": [
              "browser",
              "agent"
            ]
          },
          "enabled": {
            "type": "boolean"
          },
          "nodes": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "type"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "description": "A node type of studio-flows.js or studio-dataflow.js."
                },
                "x": {
                  "type": "number"
                },
                "y": {
                  "type": "number"
                },
                "w": {
                  "type": "number"
                },
                "h": {
                  "type": "number"
                },
                "case": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "The case of the Case frame the node is in."
                },
                "config": {
                  "type": "object",
                  "description": "The node's fields."
                }
              },
              "additionalProperties": true
            }
          },
          "wires": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "from",
                "to"
              ],
              "properties": {
                "from": {
                  "type": "string"
                },
                "port": {
                  "type": "integer",
                  "minimum": 0
                },
                "to": {
                  "type": "string"
                },
                "toPort": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      }
    },
    "alarms": {
      "description": "The alarms: one condition on one tag each, evaluated by the station that serves the app (and by the page when there is none). Their summary is the tag alarms.summary; alarms.active, alarms.unacknowledged, alarms.total and alarms.highest count it.",
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "tag"
        ],
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "description": "Unique in the project; what an acknowledgement names."
          },
          "tag": {
            "type": "string",
            "minLength": 1,
            "description": "The tag watched, as source.tag."
          },
          "condition": {
            "enum": [
              "above",
              "below",
              "equals",
              "not-equals",
              "on",
              "off",
              "bad"
            ],
            "default": "above",
            "description": "bad: the source says the value is not good."
          },
          "limit": {
            "type": [
              "number",
              "string"
            ],
            "description": "For above, below, equals and not-equals."
          },
          "deadband": {
            "type": "number",
            "minimum": 0,
            "default": 0,
            "description": "How far back past the limit the value goes before the alarm clears."
          },
          "delay": {
            "type": "number",
            "minimum": 0,
            "default": 0,
            "description": "Seconds the condition holds before the alarm is raised."
          },
          "priority": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4,
            "default": 2,
            "description": "1 critical, 2 high, 3 medium, 4 low."
          },
          "message": {
            "type": "string",
            "description": "What the operator reads; {tag} {value} {limit} {unit} {area} are filled in. Empty: the condition in words."
          },
          "area": {
            "type": "string",
            "description": "The part of the plant, for filtering the summary."
          },
          "unit": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean",
            "default": true
          }
        }
      }
    }
  },
  "additionalProperties": true
}
