Added script
This commit is contained in:
parent
f49261c2a6
commit
fce05f7dee
|
@ -2,11 +2,13 @@
|
|||
|
||||
## DEPRECATION NOTICE
|
||||
Scripts written in `IronPython` are no longer supported from version `4.5.0` and up. Use `C#` examples instead.
|
||||
|
||||
# Scripting examples
|
||||
Feel free to use our examples as base for improving or writing your own scripts. Written using `IronPython`.
|
||||
[IronPython](https://ironpython.net/ "IronPython") is an open-source implementation of the Python programming language which is tightly integrated with the .NET Framework.
|
||||
|
||||
### Contents:
|
||||
## Contents:
|
||||
|
||||
#### IronPython
|
||||
- Access level use limit - fully working script. This script allows you to limit entry to selected entry points according to a schedule.
|
||||
- Active directory import - import users from AD.
|
||||
|
@ -16,5 +18,6 @@ Feel free to use our examples as base for improving or writing your own scripts.
|
|||
- Elevator - incorporate elevator control with HID devices.
|
||||
- Live latest events - simple script that shows live events from CredoID. Separated process with own http daemon.
|
||||
- Users monitor - script that shows user data on access grant. Separated process with own http daemon.
|
||||
|
||||
#### C#
|
||||
- User monitor in csharp - script that shows user data on access grant.
|
||||
|
|
|
@ -0,0 +1,311 @@
|
|||
public class Tenant
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Id { get; set; }
|
||||
public int CurrentOccupancy { get; set; }
|
||||
public int MaxOccupancy { get; set; }
|
||||
}
|
||||
|
||||
internal class ScriptConfiguration
|
||||
{
|
||||
public int AccessLevelId { get; set; }
|
||||
public int[] EntryReaderIds { get; set; }
|
||||
public int[] ExitReaderIds { get; set; }
|
||||
public int RefreshInterval { get; set; }
|
||||
}
|
||||
|
||||
public class Update
|
||||
{
|
||||
public int NewOccupancyValue { get; set; }
|
||||
public int MaxOccupancyValue { get; set; }
|
||||
}
|
||||
|
||||
public class AccessLevel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool IsEntry { get; set; }
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim _syncLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
|
||||
Context.OnEventReceived += OnEventReceived;
|
||||
|
||||
Configuration = await ReadConfiguration();
|
||||
|
||||
// setup script HTTP API endpoints
|
||||
SetupHttpEndpoints();
|
||||
|
||||
// run script till it receives stop signal
|
||||
CancellationToken.WaitHandle.WaitOne();
|
||||
|
||||
|
||||
private async Task<ScriptConfiguration> ReadConfiguration()
|
||||
{
|
||||
if (!await Database.ContainsAsync<ScriptConfiguration>("Configuration"))
|
||||
{
|
||||
await Database.InsertAsync<ScriptConfiguration>("Configuration", new ScriptConfiguration()
|
||||
{
|
||||
AccessLevelId = 3
|
||||
});
|
||||
}
|
||||
|
||||
return await Database.GetAsync<ScriptConfiguration>("Configuration");
|
||||
}
|
||||
|
||||
private Task<int> CreateCompanyFilterAsync(int companyId)
|
||||
{
|
||||
return Context.CreateFilterAsync(new Filter()
|
||||
{
|
||||
Name = Guid.NewGuid().ToString(),
|
||||
Companies = { companyId }
|
||||
});
|
||||
}
|
||||
|
||||
public ScriptConfiguration Configuration { get; set; }
|
||||
|
||||
private void SetupHttpEndpoints()
|
||||
{
|
||||
Http.MapPost("/sync", async _ => { await SyncTenantsAsync(); });
|
||||
|
||||
Http.MapGet("/tenants", async context =>
|
||||
{
|
||||
// avoid retrieving during sync
|
||||
await _syncLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var tenants = await Database.GetAllAsync<Tenant>();
|
||||
return tenants;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncLock.Release();
|
||||
}
|
||||
});
|
||||
|
||||
Http.MapGet("/ping", async _ => "pong");
|
||||
|
||||
Http.MapPost("/reset", async context =>
|
||||
{
|
||||
await Database.ClearAsync<Tenant>();
|
||||
|
||||
var scriptConfiguration = new ScriptConfiguration()
|
||||
{
|
||||
AccessLevelId = 0,
|
||||
EntryReaderIds = null,
|
||||
ExitReaderIds = null
|
||||
};
|
||||
|
||||
await Database.UpdateAsync("Configuration", scriptConfiguration);
|
||||
|
||||
Configuration = scriptConfiguration;
|
||||
|
||||
await SyncTenantsAsync();
|
||||
});
|
||||
|
||||
Http.MapGet("/access-levels", async context =>
|
||||
{
|
||||
var accessLevels = await Context.GetAccessLevelsAsync();
|
||||
|
||||
return accessLevels.Select(e => new AccessLevel()
|
||||
{
|
||||
Id = e.Id,
|
||||
Name = e.Name
|
||||
});
|
||||
});
|
||||
|
||||
Http.MapGet("/readers", async context =>
|
||||
{
|
||||
var doorReaders = await Context.GetDoorReadersAsync();
|
||||
|
||||
return doorReaders
|
||||
.Where(e => e.DeviceType == (int)DoorDeviceType.EntryReader ||
|
||||
e.DeviceType == (int)DoorDeviceType.ExitReader).Select(e => new AccessLevel
|
||||
{
|
||||
Id = e.Id,
|
||||
Name = $"{e.DoorName}",
|
||||
IsEntry = e.DeviceType == (int)DoorDeviceType.EntryReader
|
||||
});
|
||||
});
|
||||
|
||||
Http.MapPut("/settings", async context =>
|
||||
{
|
||||
var settings = await context.GetBodyAsync<ScriptConfiguration>();
|
||||
|
||||
Configuration = settings;
|
||||
|
||||
await Database.UpdateAsync("Configuration", settings);
|
||||
});
|
||||
|
||||
Http.MapGet("/settings", async _ => Configuration);
|
||||
|
||||
Http.MapPut("/tenants/{id}/set-occupancy", async context =>
|
||||
{
|
||||
var id = context.Get("id");
|
||||
|
||||
var value = await context.GetBodyAsync<Update>();
|
||||
var tenant = await Database.GetAsync<Tenant>(int.Parse(id));
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
context.FailResponse();
|
||||
return;
|
||||
}
|
||||
|
||||
await EvaluateOccupancyAsync(tenant, value.NewOccupancyValue, value.MaxOccupancyValue, true);
|
||||
|
||||
await Database.UpdateAsync(tenant.Id, tenant);
|
||||
});
|
||||
|
||||
Http.Run();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when system receives event
|
||||
/// </summary>
|
||||
/// <param name="received"></param>
|
||||
private async void OnEventReceived(Event received)
|
||||
{
|
||||
Guid typeUid = Guid.Parse(received.TypeUid);
|
||||
|
||||
if (Configuration.EntryReaderIds == null || Configuration.ExitReaderIds == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeUid == EventTypes.AccessGranted && received.UserId.HasValue)
|
||||
{
|
||||
var user = await Context.GetUserAsync(received.UserId.Value);
|
||||
|
||||
if (!user.CompanyId.HasValue || !received.ReaderId.HasValue ||
|
||||
!Configuration.EntryReaderIds.Contains(received.ReaderId.Value) &&
|
||||
!Configuration.ExitReaderIds.Contains(received.ReaderId.Value))
|
||||
{
|
||||
// event without user or reader
|
||||
return;
|
||||
}
|
||||
|
||||
var isEntry = Configuration.EntryReaderIds.Contains(received.ReaderId.Value);
|
||||
|
||||
var tenant = await Database.GetAsync<Tenant>(user.CompanyId.Value);
|
||||
|
||||
await EvaluateOccupancyAsync(tenant, tenant.CurrentOccupancy + (isEntry ? 1 : -1),
|
||||
tenant.MaxOccupancy);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate tenant state and act based on occupancy values
|
||||
/// NOTE: this function is optimized and will trigger al addition/removal only when upper/lower limit is reached unless
|
||||
/// explicitly set to force update
|
||||
/// </summary>
|
||||
/// <param name="tenant"></param>
|
||||
/// <param name="newOccupancyValue"></param>
|
||||
/// <param name="newMaxOccupancyValue"></param>
|
||||
/// <param name="forceAccessLevelUpdate">Force update relevant user access levels</param>
|
||||
private async Task EvaluateOccupancyAsync(Tenant tenant, int newOccupancyValue, int newMaxOccupancyValue,
|
||||
bool forceAccessLevelUpdate = false)
|
||||
{
|
||||
newOccupancyValue = Math.Clamp(newOccupancyValue, 0, int.MaxValue);
|
||||
|
||||
if (newOccupancyValue >= newMaxOccupancyValue)
|
||||
{
|
||||
await AddRemoveOccupancyAccessLevelAsync(tenant.Id, true);
|
||||
}
|
||||
else if (newOccupancyValue < newMaxOccupancyValue && (tenant.CurrentOccupancy >= newMaxOccupancyValue || forceAccessLevelUpdate))
|
||||
{
|
||||
await AddRemoveOccupancyAccessLevelAsync(tenant.Id, false);
|
||||
}
|
||||
|
||||
|
||||
// normalize
|
||||
tenant.CurrentOccupancy = Math.Min(tenant.MaxOccupancy, newOccupancyValue);
|
||||
|
||||
tenant.MaxOccupancy = newMaxOccupancyValue;
|
||||
|
||||
await Database.UpdateAsync(tenant.Id, tenant);
|
||||
}
|
||||
|
||||
private async Task AddRemoveOccupancyAccessLevelAsync(int tenantId, bool remove)
|
||||
{
|
||||
var filterId = await CreateCompanyFilterAsync(tenantId);
|
||||
|
||||
try
|
||||
{
|
||||
await Context.BulkUpdateUsersAsync(0, 1000, Array.Empty<int>(), new BulkUpdateUser
|
||||
{
|
||||
AccessLevels =
|
||||
{
|
||||
new EntityUpdate[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = Configuration.AccessLevelId,
|
||||
Remove = remove,
|
||||
Update = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}, filterId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Context.DeleteFilterAsync(filterId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize tenants/companies with script storage and return up to date list
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task SyncTenantsAsync()
|
||||
{
|
||||
// this request can't happen twice at the same time as there might state where database
|
||||
await _syncLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var existing = (await Database.GetAllAsync<Tenant>()).ToDictionary(e => e.Id);
|
||||
|
||||
List<Company> companies = await Context.GetCompaniesAsync();
|
||||
|
||||
foreach (Company company in companies)
|
||||
{
|
||||
if (!existing.TryGetValue(company.Id, out var existingCompany))
|
||||
{
|
||||
var tenant = new Tenant()
|
||||
{
|
||||
Id = company.Id,
|
||||
Name = company.Name
|
||||
};
|
||||
|
||||
await Database.InsertAsync(tenant.Id, tenant);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tenant = new Tenant
|
||||
{
|
||||
Name = company.Name,
|
||||
Id = company.Id,
|
||||
CurrentOccupancy = existingCompany.CurrentOccupancy,
|
||||
MaxOccupancy = existingCompany.MaxOccupancy
|
||||
};
|
||||
|
||||
await Database.UpdateAsync(tenant.Id, tenant);
|
||||
|
||||
existing.Remove(company.Id);
|
||||
}
|
||||
|
||||
foreach (int key in existing.Keys)
|
||||
{
|
||||
await Database.RemoveAsync<Tenant>(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncLock.Release();
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Loading…
Reference in New Issue