Add Machine Data seed (tables, stored procedures, sample data) and fix SA password for shell compatibility

This commit is contained in:
Joseph Doherty
2026-03-16 14:41:28 -04:00
parent 0513a104a9
commit e3a418d603
7 changed files with 397 additions and 19 deletions

View File

@@ -24,11 +24,17 @@ The MS SQL container does not auto-run init scripts. After the first `docker com
```bash
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'ScadaLink_Dev1!' -C \
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
-i /docker-entrypoint-initdb.d/setup.sql
```
This creates the `ScadaLinkConfig` and `ScadaLinkMachineData` databases and the `scadalink_app` login.
This creates the `ScadaLinkConfig` and `ScadaLinkMachineData` databases and the `scadalink_app` login. Then seed the Machine Data database:
```bash
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
-i /docker-entrypoint-initdb.d/machinedata_seed.sql
```
## Stopping & Teardown

View File

@@ -34,11 +34,12 @@ services:
- "1433:1433"
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "ScadaLink_Dev1!"
MSSQL_SA_PASSWORD: "ScadaLink_Dev1#"
MSSQL_PID: "Developer"
volumes:
- scadalink-mssql-data:/var/opt/mssql
- ./mssql/setup.sql:/docker-entrypoint-initdb.d/setup.sql:ro
- ./mssql/machinedata_seed.sql:/docker-entrypoint-initdb.d/machinedata_seed.sql:ro
restart: unless-stopped
smtp:

View File

@@ -0,0 +1,330 @@
-- ScadaLink Machine Data Database seed script
-- Populates ScadaLinkMachineData with realistic SCADA/MES tables,
-- sample data, and stored procedures for development and testing.
--
-- Run after setup.sql:
-- docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-- -S localhost -U sa -P 'ScadaLink_Dev1#' -C \
-- -i /docker-entrypoint-initdb.d/machinedata_seed.sql
USE ScadaLinkMachineData;
GO
-- =========================================================================
-- Tables
-- =========================================================================
-- Tag history: time-series data collected from OPC UA / custom protocols
IF OBJECT_ID('dbo.TagHistory', 'U') IS NULL
CREATE TABLE dbo.TagHistory (
Id BIGINT IDENTITY(1,1) PRIMARY KEY,
TagPath NVARCHAR(256) NOT NULL,
Timestamp DATETIME2(3) NOT NULL,
Value FLOAT NULL,
StringValue NVARCHAR(512) NULL,
Quality TINYINT NOT NULL DEFAULT 192, -- 192 = Good
SiteId NVARCHAR(64) NOT NULL,
INDEX IX_TagHistory_TagPath_Timestamp (TagPath, Timestamp DESC),
INDEX IX_TagHistory_SiteId_Timestamp (SiteId, Timestamp DESC)
);
GO
-- Production counts: shift/line production totals
IF OBJECT_ID('dbo.ProductionCounts', 'U') IS NULL
CREATE TABLE dbo.ProductionCounts (
Id BIGINT IDENTITY(1,1) PRIMARY KEY,
SiteId NVARCHAR(64) NOT NULL,
LineName NVARCHAR(128) NOT NULL,
ShiftDate DATE NOT NULL,
ShiftNumber TINYINT NOT NULL,
GoodCount INT NOT NULL DEFAULT 0,
RejectCount INT NOT NULL DEFAULT 0,
Efficiency DECIMAL(5,2) NULL,
RecordedAt DATETIME2(3) NOT NULL DEFAULT SYSUTCDATETIME(),
INDEX IX_ProductionCounts_Site_Date (SiteId, ShiftDate DESC)
);
GO
-- Equipment events: state changes, faults, maintenance
IF OBJECT_ID('dbo.EquipmentEvents', 'U') IS NULL
CREATE TABLE dbo.EquipmentEvents (
Id BIGINT IDENTITY(1,1) PRIMARY KEY,
SiteId NVARCHAR(64) NOT NULL,
EquipmentId NVARCHAR(128) NOT NULL,
EventType NVARCHAR(32) NOT NULL, -- 'StateChange', 'Fault', 'Maintenance', 'Alarm'
PreviousState NVARCHAR(64) NULL,
NewState NVARCHAR(64) NOT NULL,
Description NVARCHAR(512) NULL,
Timestamp DATETIME2(3) NOT NULL,
INDEX IX_EquipmentEvents_Equipment_Time (EquipmentId, Timestamp DESC),
INDEX IX_EquipmentEvents_Site_Type (SiteId, EventType, Timestamp DESC)
);
GO
-- Batch records: production batch tracking
IF OBJECT_ID('dbo.BatchRecords', 'U') IS NULL
CREATE TABLE dbo.BatchRecords (
Id BIGINT IDENTITY(1,1) PRIMARY KEY,
BatchId NVARCHAR(64) NOT NULL UNIQUE,
SiteId NVARCHAR(64) NOT NULL,
RecipeId NVARCHAR(64) NOT NULL,
Status NVARCHAR(32) NOT NULL DEFAULT 'InProgress', -- 'InProgress', 'Complete', 'Aborted'
StartTime DATETIME2(3) NOT NULL,
EndTime DATETIME2(3) NULL,
TotalQuantity DECIMAL(10,2) NULL,
Operator NVARCHAR(128) NULL,
Notes NVARCHAR(1024) NULL,
INDEX IX_BatchRecords_Site_Status (SiteId, Status),
INDEX IX_BatchRecords_StartTime (StartTime DESC)
);
GO
-- Alarm history: historical alarm events
IF OBJECT_ID('dbo.AlarmHistory', 'U') IS NULL
CREATE TABLE dbo.AlarmHistory (
Id BIGINT IDENTITY(1,1) PRIMARY KEY,
SiteId NVARCHAR(64) NOT NULL,
AlarmName NVARCHAR(256) NOT NULL,
Severity TINYINT NOT NULL, -- 1=Low, 2=Medium, 3=High, 4=Critical
State NVARCHAR(32) NOT NULL, -- 'Active', 'Acknowledged', 'Cleared'
ActivatedAt DATETIME2(3) NOT NULL,
AcknowledgedAt DATETIME2(3) NULL,
ClearedAt DATETIME2(3) NULL,
AcknowledgedBy NVARCHAR(128) NULL,
Message NVARCHAR(512) NULL,
INDEX IX_AlarmHistory_Site_Active (SiteId, State, ActivatedAt DESC),
INDEX IX_AlarmHistory_Severity (Severity, ActivatedAt DESC)
);
GO
-- =========================================================================
-- Stored Procedures
-- =========================================================================
-- Get tag history for a tag path within a date range
IF OBJECT_ID('dbo.usp_GetTagHistory', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_GetTagHistory;
GO
CREATE PROCEDURE dbo.usp_GetTagHistory
@TagPath NVARCHAR(256),
@StartTime DATETIME2(3),
@EndTime DATETIME2(3),
@MaxRows INT = 10000
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP (@MaxRows)
TagPath, Timestamp, Value, StringValue, Quality, SiteId
FROM dbo.TagHistory
WHERE TagPath = @TagPath
AND Timestamp >= @StartTime
AND Timestamp <= @EndTime
ORDER BY Timestamp DESC;
END;
GO
-- Get production summary for a site over a date range
IF OBJECT_ID('dbo.usp_GetProductionSummary', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_GetProductionSummary;
GO
CREATE PROCEDURE dbo.usp_GetProductionSummary
@SiteId NVARCHAR(64),
@StartDate DATE,
@EndDate DATE
AS
BEGIN
SET NOCOUNT ON;
SELECT
LineName,
SUM(GoodCount) AS TotalGood,
SUM(RejectCount) AS TotalReject,
SUM(GoodCount) + SUM(RejectCount) AS TotalProduced,
CASE
WHEN SUM(GoodCount) + SUM(RejectCount) > 0
THEN CAST(SUM(GoodCount) * 100.0 / (SUM(GoodCount) + SUM(RejectCount)) AS DECIMAL(5,2))
ELSE 0
END AS YieldPercent,
AVG(Efficiency) AS AvgEfficiency,
COUNT(DISTINCT ShiftDate) AS DaysReported
FROM dbo.ProductionCounts
WHERE SiteId = @SiteId
AND ShiftDate >= @StartDate
AND ShiftDate <= @EndDate
GROUP BY LineName
ORDER BY LineName;
END;
GO
-- Insert a batch record (used by CachedWrite from scripts)
IF OBJECT_ID('dbo.usp_InsertBatchRecord', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_InsertBatchRecord;
GO
CREATE PROCEDURE dbo.usp_InsertBatchRecord
@BatchId NVARCHAR(64),
@SiteId NVARCHAR(64),
@RecipeId NVARCHAR(64),
@TotalQuantity DECIMAL(10,2) = NULL,
@Operator NVARCHAR(128) = NULL,
@Notes NVARCHAR(1024) = NULL
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.BatchRecords (BatchId, SiteId, RecipeId, Status, StartTime, TotalQuantity, Operator, Notes)
VALUES (@BatchId, @SiteId, @RecipeId, 'InProgress', SYSUTCDATETIME(), @TotalQuantity, @Operator, @Notes);
SELECT SCOPE_IDENTITY() AS Id, @BatchId AS BatchId;
END;
GO
-- Complete or abort a batch
IF OBJECT_ID('dbo.usp_CompleteBatch', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_CompleteBatch;
GO
CREATE PROCEDURE dbo.usp_CompleteBatch
@BatchId NVARCHAR(64),
@Status NVARCHAR(32), -- 'Complete' or 'Aborted'
@TotalQuantity DECIMAL(10,2) = NULL,
@Notes NVARCHAR(1024) = NULL
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.BatchRecords
SET Status = @Status,
EndTime = SYSUTCDATETIME(),
TotalQuantity = COALESCE(@TotalQuantity, TotalQuantity),
Notes = COALESCE(@Notes, Notes)
WHERE BatchId = @BatchId
AND Status = 'InProgress';
IF @@ROWCOUNT = 0
THROW 50001, 'Batch not found or not in progress.', 1;
SELECT BatchId, Status, StartTime, EndTime, TotalQuantity
FROM dbo.BatchRecords
WHERE BatchId = @BatchId;
END;
GO
-- Get recent equipment events
IF OBJECT_ID('dbo.usp_GetEquipmentEvents', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_GetEquipmentEvents;
GO
CREATE PROCEDURE dbo.usp_GetEquipmentEvents
@SiteId NVARCHAR(64),
@EquipmentId NVARCHAR(128) = NULL,
@EventType NVARCHAR(32) = NULL,
@Hours INT = 24
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Since DATETIME2(3) = DATEADD(HOUR, -@Hours, SYSUTCDATETIME());
SELECT
EquipmentId, EventType, PreviousState, NewState, Description, Timestamp
FROM dbo.EquipmentEvents
WHERE SiteId = @SiteId
AND Timestamp >= @Since
AND (@EquipmentId IS NULL OR EquipmentId = @EquipmentId)
AND (@EventType IS NULL OR EventType = @EventType)
ORDER BY Timestamp DESC;
END;
GO
-- Get active alarms for a site
IF OBJECT_ID('dbo.usp_GetActiveAlarms', 'P') IS NOT NULL DROP PROCEDURE dbo.usp_GetActiveAlarms;
GO
CREATE PROCEDURE dbo.usp_GetActiveAlarms
@SiteId NVARCHAR(64),
@MinSeverity TINYINT = 1
AS
BEGIN
SET NOCOUNT ON;
SELECT
AlarmName, Severity, State, ActivatedAt, AcknowledgedAt,
AcknowledgedBy, Message
FROM dbo.AlarmHistory
WHERE SiteId = @SiteId
AND State IN ('Active', 'Acknowledged')
AND Severity >= @MinSeverity
ORDER BY Severity DESC, ActivatedAt DESC;
END;
GO
-- =========================================================================
-- Sample Data
-- =========================================================================
-- Tag history (last few hours of data for two sites)
DECLARE @now DATETIME2(3) = SYSUTCDATETIME();
INSERT INTO dbo.TagHistory (TagPath, Timestamp, Value, Quality, SiteId) VALUES
('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -120, @now), 45.2, 192, 'SiteA'),
('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -110, @now), 46.1, 192, 'SiteA'),
('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -100, @now), 44.8, 192, 'SiteA'),
('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -90, @now), 47.3, 192, 'SiteA'),
('SiteA/Pump-001/Pressure', DATEADD(MINUTE, -80, @now), 45.9, 192, 'SiteA'),
('SiteA/Pump-001/Flow', DATEADD(MINUTE, -120, @now), 120.5, 192, 'SiteA'),
('SiteA/Pump-001/Flow', DATEADD(MINUTE, -110, @now), 121.2, 192, 'SiteA'),
('SiteA/Pump-001/Flow', DATEADD(MINUTE, -100, @now), 119.8, 192, 'SiteA'),
('SiteA/Tank-001/Level', DATEADD(MINUTE, -120, @now), 72.0, 192, 'SiteA'),
('SiteA/Tank-001/Level', DATEADD(MINUTE, -110, @now), 73.5, 192, 'SiteA'),
('SiteA/Tank-001/Level', DATEADD(MINUTE, -100, @now), 75.1, 192, 'SiteA'),
('SiteA/Tank-001/Level', DATEADD(MINUTE, -90, @now), 76.8, 192, 'SiteA'),
('SiteA/Tank-001/Temperature', DATEADD(MINUTE, -120, @now), 65.3, 192, 'SiteA'),
('SiteA/Tank-001/Temperature', DATEADD(MINUTE, -110, @now), 65.5, 192, 'SiteA'),
('SiteA/Conv-001/Speed', DATEADD(MINUTE, -120, @now), 2.4, 192, 'SiteA'),
('SiteA/Conv-001/Speed', DATEADD(MINUTE, -110, @now), 0.0, 0, 'SiteA'), -- Bad quality (stopped)
('SiteA/Conv-001/Speed', DATEADD(MINUTE, -100, @now), 2.3, 192, 'SiteA'),
('SiteB/Mixer-001/RPM', DATEADD(MINUTE, -120, @now), 450.0, 192, 'SiteB'),
('SiteB/Mixer-001/RPM', DATEADD(MINUTE, -110, @now), 452.0, 192, 'SiteB'),
('SiteB/Mixer-001/RPM', DATEADD(MINUTE, -100, @now), 448.0, 192, 'SiteB'),
('SiteB/Mixer-001/Temperature',DATEADD(MINUTE, -120, @now), 82.1, 192, 'SiteB'),
('SiteB/Mixer-001/Temperature',DATEADD(MINUTE, -110, @now), 83.0, 192, 'SiteB');
-- Production counts (last 3 days, 2 shifts per day)
DECLARE @today DATE = CAST(SYSUTCDATETIME() AS DATE);
INSERT INTO dbo.ProductionCounts (SiteId, LineName, ShiftDate, ShiftNumber, GoodCount, RejectCount, Efficiency) VALUES
('SiteA', 'Line-1', DATEADD(DAY, -2, @today), 1, 4100, 82, 92.5),
('SiteA', 'Line-1', DATEADD(DAY, -2, @today), 2, 3900, 95, 91.2),
('SiteA', 'Line-2', DATEADD(DAY, -2, @today), 1, 3050, 120, 88.1),
('SiteA', 'Line-2', DATEADD(DAY, -2, @today), 2, 2900, 105, 87.5),
('SiteA', 'Line-1', DATEADD(DAY, -1, @today), 1, 4200, 75, 93.1),
('SiteA', 'Line-1', DATEADD(DAY, -1, @today), 2, 4050, 88, 92.0),
('SiteA', 'Line-2', DATEADD(DAY, -1, @today), 1, 3100, 98, 89.2),
('SiteA', 'Line-2', DATEADD(DAY, -1, @today), 2, 3000, 110, 88.0),
('SiteA', 'Line-1', @today, 1, 2100, 40, 93.5),
('SiteA', 'Line-2', @today, 1, 1550, 65, 88.8),
('SiteB', 'Mixing', DATEADD(DAY, -1, @today), 1, 850, 12, 95.0),
('SiteB', 'Mixing', DATEADD(DAY, -1, @today), 2, 820, 15, 94.2),
('SiteB', 'Packing', DATEADD(DAY, -1, @today), 1, 1600, 45, 90.5),
('SiteB', 'Packing', DATEADD(DAY, -1, @today), 2, 1550, 50, 89.8);
-- Equipment events
INSERT INTO dbo.EquipmentEvents (SiteId, EquipmentId, EventType, PreviousState, NewState, Description, Timestamp) VALUES
('SiteA', 'PUMP-001', 'StateChange', 'Idle', 'Running', 'Shift start', DATEADD(HOUR, -8, @now)),
('SiteA', 'PUMP-001', 'Fault', 'Running', 'Faulted', 'Overcurrent detected', DATEADD(HOUR, -5, @now)),
('SiteA', 'PUMP-001', 'StateChange', 'Faulted', 'Running', 'Manual reset by operator', DATEADD(HOUR, -4, @now)),
('SiteA', 'TANK-001', 'Alarm', NULL, 'HighLevel','Tank level exceeded 90%', DATEADD(HOUR, -3, @now)),
('SiteA', 'TANK-001', 'Alarm', NULL, 'Normal', 'Tank level returned to normal',DATEADD(HOUR, -2, @now)),
('SiteA', 'CONV-001', 'Maintenance', 'Running', 'Maintenance', 'Scheduled belt inspection', DATEADD(HOUR, -6, @now)),
('SiteA', 'CONV-001', 'StateChange', 'Maintenance', 'Running', 'Maintenance complete', DATEADD(HOUR, -5, @now)),
('SiteB', 'MIXER-001','StateChange', 'Idle', 'Running', 'Batch R-100 started', DATEADD(HOUR, -7, @now)),
('SiteB', 'MIXER-001','StateChange', 'Running', 'Idle', 'Batch R-100 complete', DATEADD(HOUR, -3, @now));
-- Batch records
INSERT INTO dbo.BatchRecords (BatchId, SiteId, RecipeId, Status, StartTime, EndTime, TotalQuantity, Operator, Notes) VALUES
('BATCH-20260314-001', 'SiteA', 'R-100', 'Complete', DATEADD(HOUR, -26, @now), DATEADD(HOUR, -22, @now), 450.00, 'jsmith', 'Normal run'),
('BATCH-20260314-002', 'SiteA', 'R-200', 'Complete', DATEADD(HOUR, -20, @now), DATEADD(HOUR, -16, @now), 380.50, 'jsmith', 'Slight yield loss on Material-B'),
('BATCH-20260315-001', 'SiteB', 'R-100', 'Complete', DATEADD(HOUR, -10, @now), DATEADD(HOUR, -6, @now), 445.00, 'mdoe', NULL),
('BATCH-20260315-002', 'SiteA', 'R-150', 'Aborted', DATEADD(HOUR, -8, @now), DATEADD(HOUR, -7, @now), NULL, 'jsmith', 'Material-A out of spec, aborted early'),
('BATCH-20260316-001', 'SiteA', 'R-100', 'InProgress', DATEADD(HOUR, -2, @now), NULL, NULL, 'bwilson', 'Current batch');
-- Alarm history
INSERT INTO dbo.AlarmHistory (SiteId, AlarmName, Severity, State, ActivatedAt, AcknowledgedAt, ClearedAt, AcknowledgedBy, Message) VALUES
('SiteA', 'Tank-001 High Level', 3, 'Cleared', DATEADD(HOUR, -3, @now), DATEADD(HOUR, -3, @now), DATEADD(HOUR, -2, @now), 'jsmith', 'Level exceeded 90% setpoint'),
('SiteA', 'Pump-001 Overcurrent', 4, 'Cleared', DATEADD(HOUR, -5, @now), DATEADD(HOUR, -5, @now), DATEADD(HOUR, -4, @now), 'jsmith', 'Current draw 15.2A (limit 12A)'),
('SiteA', 'Conv-001 Belt Slip', 2, 'Active', DATEADD(HOUR, -1, @now), NULL, NULL, NULL, 'Belt speed deviation >5%'),
('SiteA', 'Tank-001 Temperature High', 3, 'Acknowledged', DATEADD(MINUTE, -30, @now), DATEADD(MINUTE, -25, @now), NULL, 'bwilson', 'Temperature 68.2C (limit 65C)'),
('SiteB', 'Mixer-001 Vibration', 2, 'Active', DATEADD(HOUR, -2, @now), NULL, NULL, NULL, 'Vibration level elevated');
GO
PRINT 'ScadaLinkMachineData seed complete.';
PRINT 'Tables: TagHistory, ProductionCounts, EquipmentEvents, BatchRecords, AlarmHistory';
PRINT 'Stored Procedures: usp_GetTagHistory, usp_GetProductionSummary, usp_InsertBatchRecord, usp_CompleteBatch, usp_GetEquipmentEvents, usp_GetActiveAlarms';
GO

View File

@@ -14,7 +14,7 @@ GO
-- Create application login
IF NOT EXISTS (SELECT name FROM sys.server_principals WHERE name = 'scadalink_app')
CREATE LOGIN scadalink_app WITH PASSWORD = 'ScadaLink_Dev1!', DEFAULT_DATABASE = ScadaLinkConfig;
CREATE LOGIN scadalink_app WITH PASSWORD = 'ScadaLink_Dev1#', DEFAULT_DATABASE = ScadaLinkConfig;
GO
-- Grant db_owner on ScadaLinkConfig

View File

@@ -10,7 +10,7 @@ import pymssql
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 1433
DEFAULT_USER = "sa"
DEFAULT_PASSWORD = "ScadaLink_Dev1!"
DEFAULT_PASSWORD = "ScadaLink_Dev1#"
EXPECTED_DBS = ["ScadaLinkConfig", "ScadaLinkMachineData"]

View File

@@ -19,12 +19,16 @@ cd infra
docker compose up -d
```
After the first startup, run the SQL setup script to create databases and the application user:
After the first startup, run the SQL setup and seed scripts:
```bash
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'ScadaLink_Dev1!' -C \
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
-i /docker-entrypoint-initdb.d/setup.sql
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
-i /docker-entrypoint-initdb.d/machinedata_seed.sql
```
## Per-Service Documentation
@@ -44,8 +48,8 @@ For use in `appsettings.Development.json`:
```json
{
"ConnectionStrings": {
"ScadaLinkConfig": "Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1!;TrustServerCertificate=true",
"ScadaLinkMachineData": "Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1!;TrustServerCertificate=true"
"ScadaLinkConfig": "Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true",
"ScadaLinkMachineData": "Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true"
},
"Ldap": {
"Server": "localhost",
@@ -99,6 +103,7 @@ infra/
teardown.sh # Teardown script (volumes, images, venv)
glauth/config.toml # LDAP users and groups
mssql/setup.sql # Database and user creation
mssql/machinedata_seed.sql # Machine Data tables, stored procedures, sample data
opcua/nodes.json # Custom OPC UA tag definitions
restapi/app.py # Flask REST API server
restapi/Dockerfile # REST API container build

View File

@@ -14,8 +14,8 @@ The test database uses Microsoft SQL Server 2022 Developer Edition running in Do
| Account | Username | Password | Purpose |
|---------|----------|----------|---------|
| SA | `sa` | `ScadaLink_Dev1!` | Server admin (setup only) |
| App | `scadalink_app` | `ScadaLink_Dev1!` | Application login (db_owner on both databases) |
| SA | `sa` | `ScadaLink_Dev1#` | Server admin (setup only) |
| App | `scadalink_app` | `ScadaLink_Dev1#` | Application login (db_owner on both databases) |
## Databases
@@ -24,7 +24,7 @@ The test database uses Microsoft SQL Server 2022 Developer Edition running in Do
| `ScadaLinkConfig` | Configuration Database component — templates, deployments, users, audit log |
| `ScadaLinkMachineData` | Machine/operational data storage |
Both databases are created empty by `infra/mssql/setup.sql`. EF Core migrations populate the schema.
Both databases are created by `infra/mssql/setup.sql`. EF Core migrations populate the `ScadaLinkConfig` schema. The `ScadaLinkMachineData` database is seeded with sample tables and stored procedures by `infra/mssql/machinedata_seed.sql`.
## Data Persistence
@@ -42,22 +42,30 @@ After the first `docker compose up -d`, run the setup script:
```bash
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'ScadaLink_Dev1!' -C \
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
-i /docker-entrypoint-initdb.d/setup.sql
```
This creates the databases and the `scadalink_app` login. You only need to run this once (or again after deleting the volume).
This creates the databases and the `scadalink_app` login. Then seed the Machine Data database with sample tables, stored procedures, and data:
```bash
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
-i /docker-entrypoint-initdb.d/machinedata_seed.sql
```
You only need to run these once (or again after deleting the volume).
## Connection Strings
For `appsettings.Development.json`:
```
Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1!;TrustServerCertificate=true
Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true
```
```
Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1!;TrustServerCertificate=true
Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true
```
## Verification
@@ -72,7 +80,7 @@ docker ps --filter name=scadalink-mssql
```bash
docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'ScadaLink_Dev1!' -C \
-S localhost -U sa -P 'ScadaLink_Dev1#' -C \
-Q "SELECT name FROM sys.databases"
```
@@ -80,7 +88,7 @@ docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
```bash
docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U scadalink_app -P 'ScadaLink_Dev1!' -C \
-S localhost -U scadalink_app -P 'ScadaLink_Dev1#' -C \
-d ScadaLinkConfig \
-Q "SELECT DB_NAME()"
```
@@ -110,7 +118,32 @@ python infra/tools/mssql_tool.py tables --database ScadaLinkConfig
python infra/tools/mssql_tool.py query --database ScadaLinkConfig --sql "SELECT name FROM sys.tables"
```
Use `--host`, `--port`, `--user`, `--password` to override defaults (localhost:1433, sa, ScadaLink_Dev1!). Run with `--help` for full usage.
Use `--host`, `--port`, `--user`, `--password` to override defaults (localhost:1433, sa, ScadaLink_Dev1#). Run with `--help` for full usage.
## Machine Data Tables & Stored Procedures
The `machinedata_seed.sql` script creates the following in `ScadaLinkMachineData`:
**Tables**:
| Table | Description | Sample Data |
|-------|-------------|-------------|
| `TagHistory` | Time-series tag values from OPC UA / custom protocols | Pressure, flow, level, temperature, speed readings for SiteA/SiteB |
| `ProductionCounts` | Shift/line production totals (good, reject, efficiency) | 3 days of 2-shift data across 2 sites |
| `EquipmentEvents` | State changes, faults, maintenance, alarm events | Pump faults, belt inspections, batch starts |
| `BatchRecords` | Production batch tracking (start, complete, abort) | 5 batches including one in-progress |
| `AlarmHistory` | Historical alarm activations, acks, and clears | Active, acknowledged, and cleared alarms |
**Stored Procedures**:
| Procedure | Description |
|-----------|-------------|
| `usp_GetTagHistory` | Get tag values for a tag path within a date range |
| `usp_GetProductionSummary` | Aggregate production by line over a date range |
| `usp_InsertBatchRecord` | Insert a new batch (for `CachedWrite` testing) |
| `usp_CompleteBatch` | Complete or abort a batch |
| `usp_GetEquipmentEvents` | Get recent equipment events with optional filters |
| `usp_GetActiveAlarms` | Get active/acknowledged alarms by severity |
## Relevance to ScadaLink Components
@@ -118,6 +151,9 @@ Use `--host`, `--port`, `--user`, `--password` to override defaults (localhost:1
- **Deployment Manager** — reads/writes deployment records in `ScadaLinkConfig`.
- **Template Engine** — reads/writes template definitions in `ScadaLinkConfig`.
- **Security & Auth** — user/role data stored in `ScadaLinkConfig`.
- **External System Gateway** — scripts use `Database.Connection("machineDataConnection")` to query `ScadaLinkMachineData` tables and stored procedures.
- **Site Runtime** — scripts call stored procedures via `Database.Connection()` and `Database.CachedWrite()` for batch recording and data queries.
- **Inbound API** — methods can query machine data via named database connections.
## Notes