diff --git a/HikvisionAttendanceWindowsService.cs b/HikvisionAttendanceWindowsService.cs
index f4aebfe..dc4efb5 100644
--- a/HikvisionAttendanceWindowsService.cs
+++ b/HikvisionAttendanceWindowsService.cs
@@ -237,7 +237,7 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
{
if (!File.Exists(configPath))
{
- return new HikvisionServiceConfig
+ var missingCfg = new HikvisionServiceConfig
{
LogDirectory = @"C:\Users\Public\HikvisionAttendanceService",
SqlConnectionString = "",
@@ -284,6 +284,9 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
UserSyncFaceCacheDirectory = "",
SyncPolicies = new UserSyncPoliciesConfig()
};
+ ApplyDeviceSettingsOverlay(missingCfg, configPath);
+ NormalizeLoadedDeviceConfig(missingCfg);
+ return missingCfg;
}
var raw = File.ReadAllText(configPath);
@@ -296,6 +299,7 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
{
var cfg = (HikvisionServiceConfig)serializer.ReadObject(ms);
ApplyEnvironmentOverrides(cfg);
+ ApplyDeviceSettingsOverlay(cfg, configPath);
NormalizeLoadedDeviceConfig(cfg);
return cfg;
}
@@ -308,6 +312,7 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
{
var cfg = (HikvisionServiceConfig)serializer.ReadObject(ms);
ApplyEnvironmentOverrides(cfg);
+ ApplyDeviceSettingsOverlay(cfg, configPath);
NormalizeLoadedDeviceConfig(cfg);
return cfg;
}
@@ -315,7 +320,7 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
}
catch
{
- return new HikvisionServiceConfig
+ var fallbackCfg = new HikvisionServiceConfig
{
LogDirectory = @"C:\Users\Public\HikvisionAttendanceService",
SqlConnectionString = "",
@@ -362,6 +367,9 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
UserSyncFaceCacheDirectory = "",
SyncPolicies = new UserSyncPoliciesConfig()
};
+ ApplyDeviceSettingsOverlay(fallbackCfg, configPath);
+ NormalizeLoadedDeviceConfig(fallbackCfg);
+ return fallbackCfg;
}
}
@@ -481,6 +489,60 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
cfg.DbPort = parsedPort;
}
+ ///
+ /// Overlays device-scope / template-direction settings from DeviceSettings.config
+ /// (same folder as serviceconfig.json). Property names and behavior are unchanged.
+ ///
+ private static void ApplyDeviceSettingsOverlay(HikvisionServiceConfig cfg, string serviceConfigPath)
+ {
+ if (cfg == null)
+ return;
+
+ string dir;
+ try
+ {
+ dir = Path.GetDirectoryName(Path.GetFullPath(serviceConfigPath ?? "")) ?? "";
+ }
+ catch
+ {
+ dir = "";
+ }
+ if (string.IsNullOrWhiteSpace(dir))
+ dir = AppContext.BaseDirectory;
+
+ var path = Path.Combine(dir, "DeviceSettings.config");
+ if (!File.Exists(path))
+ return;
+
+ try
+ {
+ var raw = File.ReadAllText(path);
+ var serializer = new DataContractJsonSerializer(typeof(DeviceSettingsFile));
+ DeviceSettingsFile? overlay;
+ using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(raw)))
+ overlay = serializer.ReadObject(ms) as DeviceSettingsFile;
+ if (overlay == null)
+ return;
+
+ if (!string.IsNullOrWhiteSpace(overlay.MachineScopeMode))
+ cfg.MachineScopeMode = overlay.MachineScopeMode;
+ if (overlay.ScopedMachineIps != null)
+ cfg.ScopedMachineIps = new List(overlay.ScopedMachineIps);
+ cfg.EnableTemplateDeviceToDbSync = overlay.EnableTemplateDeviceToDbSync;
+ cfg.EnableTemplateDbToDeviceSync = overlay.EnableTemplateDbToDeviceSync;
+ if (overlay.SourceMachineIp != null)
+ cfg.SourceMachineIp = overlay.SourceMachineIp;
+ if (overlay.TargetMachineIps != null)
+ cfg.TargetMachineIps = new List(overlay.TargetMachineIps);
+ if (overlay.SyncEmployeeIds != null)
+ cfg.SyncEmployeeIds = new List(overlay.SyncEmployeeIds);
+ }
+ catch
+ {
+ // Keep serviceconfig / defaults if DeviceSettings.config cannot be parsed.
+ }
+ }
+
private static void NormalizeLoadedDeviceConfig(HikvisionServiceConfig? cfg)
{
if (cfg == null)
@@ -524,6 +586,32 @@ public sealed class HikvisionAttendanceWindowsService : ServiceBase
}
}
+ /// Device-scope and template-direction settings loaded from DeviceSettings.config.
+ [DataContract]
+ internal sealed class DeviceSettingsFile
+ {
+ [DataMember]
+ public string MachineScopeMode { get; set; } = "CENTRAL";
+
+ [DataMember]
+ public List ScopedMachineIps { get; set; } = new List();
+
+ [DataMember]
+ public bool EnableTemplateDeviceToDbSync { get; set; }
+
+ [DataMember]
+ public bool EnableTemplateDbToDeviceSync { get; set; }
+
+ [DataMember]
+ public string SourceMachineIp { get; set; } = "";
+
+ [DataMember]
+ public List TargetMachineIps { get; set; } = new List();
+
+ [DataMember]
+ public List SyncEmployeeIds { get; set; } = new List();
+ }
+
/// Policies for multi-device user/face sync (ISAPI). Loaded from service JSON only.
[DataContract]
internal sealed class UserSyncPoliciesConfig