Add Machine Data seed (tables, stored procedures, sample data) and fix SA password for shell compatibility
This commit is contained in:
@@ -24,11 +24,17 @@ The MS SQL container does not auto-run init scripts. After the first `docker com
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
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
|
-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
|
## Stopping & Teardown
|
||||||
|
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ services:
|
|||||||
- "1433:1433"
|
- "1433:1433"
|
||||||
environment:
|
environment:
|
||||||
ACCEPT_EULA: "Y"
|
ACCEPT_EULA: "Y"
|
||||||
MSSQL_SA_PASSWORD: "ScadaLink_Dev1!"
|
MSSQL_SA_PASSWORD: "ScadaLink_Dev1#"
|
||||||
MSSQL_PID: "Developer"
|
MSSQL_PID: "Developer"
|
||||||
volumes:
|
volumes:
|
||||||
- scadalink-mssql-data:/var/opt/mssql
|
- scadalink-mssql-data:/var/opt/mssql
|
||||||
- ./mssql/setup.sql:/docker-entrypoint-initdb.d/setup.sql:ro
|
- ./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
|
restart: unless-stopped
|
||||||
|
|
||||||
smtp:
|
smtp:
|
||||||
|
|||||||
330
infra/mssql/machinedata_seed.sql
Normal file
330
infra/mssql/machinedata_seed.sql
Normal 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
|
||||||
@@ -14,7 +14,7 @@ GO
|
|||||||
|
|
||||||
-- Create application login
|
-- Create application login
|
||||||
IF NOT EXISTS (SELECT name FROM sys.server_principals WHERE name = 'scadalink_app')
|
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
|
GO
|
||||||
|
|
||||||
-- Grant db_owner on ScadaLinkConfig
|
-- Grant db_owner on ScadaLinkConfig
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import pymssql
|
|||||||
DEFAULT_HOST = "localhost"
|
DEFAULT_HOST = "localhost"
|
||||||
DEFAULT_PORT = 1433
|
DEFAULT_PORT = 1433
|
||||||
DEFAULT_USER = "sa"
|
DEFAULT_USER = "sa"
|
||||||
DEFAULT_PASSWORD = "ScadaLink_Dev1!"
|
DEFAULT_PASSWORD = "ScadaLink_Dev1#"
|
||||||
EXPECTED_DBS = ["ScadaLinkConfig", "ScadaLinkMachineData"]
|
EXPECTED_DBS = ["ScadaLinkConfig", "ScadaLinkMachineData"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,16 @@ cd infra
|
|||||||
docker compose up -d
|
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
|
```bash
|
||||||
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
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
|
-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
|
## Per-Service Documentation
|
||||||
@@ -44,8 +48,8 @@ For use in `appsettings.Development.json`:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"ScadaLinkConfig": "Server=localhost,1433;Database=ScadaLinkConfig;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"
|
"ScadaLinkMachineData": "Server=localhost,1433;Database=ScadaLinkMachineData;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true"
|
||||||
},
|
},
|
||||||
"Ldap": {
|
"Ldap": {
|
||||||
"Server": "localhost",
|
"Server": "localhost",
|
||||||
@@ -99,6 +103,7 @@ infra/
|
|||||||
teardown.sh # Teardown script (volumes, images, venv)
|
teardown.sh # Teardown script (volumes, images, venv)
|
||||||
glauth/config.toml # LDAP users and groups
|
glauth/config.toml # LDAP users and groups
|
||||||
mssql/setup.sql # Database and user creation
|
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
|
opcua/nodes.json # Custom OPC UA tag definitions
|
||||||
restapi/app.py # Flask REST API server
|
restapi/app.py # Flask REST API server
|
||||||
restapi/Dockerfile # REST API container build
|
restapi/Dockerfile # REST API container build
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ The test database uses Microsoft SQL Server 2022 Developer Edition running in Do
|
|||||||
|
|
||||||
| Account | Username | Password | Purpose |
|
| Account | Username | Password | Purpose |
|
||||||
|---------|----------|----------|---------|
|
|---------|----------|----------|---------|
|
||||||
| SA | `sa` | `ScadaLink_Dev1!` | Server admin (setup only) |
|
| SA | `sa` | `ScadaLink_Dev1#` | Server admin (setup only) |
|
||||||
| App | `scadalink_app` | `ScadaLink_Dev1!` | Application login (db_owner on both databases) |
|
| App | `scadalink_app` | `ScadaLink_Dev1#` | Application login (db_owner on both databases) |
|
||||||
|
|
||||||
## 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 |
|
| `ScadaLinkConfig` | Configuration Database component — templates, deployments, users, audit log |
|
||||||
| `ScadaLinkMachineData` | Machine/operational data storage |
|
| `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
|
## Data Persistence
|
||||||
|
|
||||||
@@ -42,22 +42,30 @@ After the first `docker compose up -d`, run the setup script:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -i scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
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
|
-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
|
## Connection Strings
|
||||||
|
|
||||||
For `appsettings.Development.json`:
|
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
|
## Verification
|
||||||
@@ -72,7 +80,7 @@ docker ps --filter name=scadalink-mssql
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
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"
|
-Q "SELECT name FROM sys.databases"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -80,7 +88,7 @@ docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -it scadalink-mssql /opt/mssql-tools18/bin/sqlcmd \
|
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 \
|
-d ScadaLinkConfig \
|
||||||
-Q "SELECT DB_NAME()"
|
-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"
|
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
|
## 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`.
|
- **Deployment Manager** — reads/writes deployment records in `ScadaLinkConfig`.
|
||||||
- **Template Engine** — reads/writes template definitions in `ScadaLinkConfig`.
|
- **Template Engine** — reads/writes template definitions in `ScadaLinkConfig`.
|
||||||
- **Security & Auth** — user/role data stored 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
|
## Notes
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user