Merge branch 'main' of https://git.innov.energy/Innovenergy/git_trunk
This commit is contained in:
commit
c8922c5e58
|
@ -27,6 +27,7 @@
|
|||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.6" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters.Abstractions" Version="7.0.6" />
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ public class Controller : ControllerBase
|
|||
|
||||
if (user is null)
|
||||
{
|
||||
Console.WriteLine("I have no user");
|
||||
throw new Exceptions(400,"Null User Exception", "Must provide a user to log in as.", Request.Path.Value!);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,10 +55,9 @@ public static class SessionMethods
|
|||
var installation = Db.GetInstallationById(installationId);
|
||||
var parent = Db.GetFolderById(parentId);
|
||||
|
||||
if(installation.ParentId == parentId) return false;
|
||||
if(installation == null || installation.ParentId == parentId) return false;
|
||||
|
||||
return user is not null
|
||||
&& installation is not null
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(installation)
|
||||
&& user.HasAccessTo(parent)
|
||||
|
|
|
@ -7,54 +7,61 @@ namespace InnovEnergy.App.Backend.Database;
|
|||
|
||||
public static partial class Db
|
||||
{
|
||||
private static Boolean Insert(Object obj)
|
||||
{
|
||||
var success = Connection.Insert(obj) > 0;
|
||||
if(success) BackupDatabase();
|
||||
return success;
|
||||
}
|
||||
|
||||
public static Boolean Create(Installation installation)
|
||||
{
|
||||
// SQLite wrapper is smart and *modifies* t's Id to the one generated (autoincrement) by the insertion
|
||||
return Connection.Insert(installation) > 0;
|
||||
return Insert(installation);
|
||||
}
|
||||
|
||||
public static Boolean Create(DeletedInstallation installation)
|
||||
{
|
||||
return Connection.Insert(installation) > 0;
|
||||
return Insert(installation);
|
||||
}
|
||||
|
||||
public static Boolean Create(Folder folder)
|
||||
{
|
||||
return Connection.Insert(folder) > 0;
|
||||
return Insert(folder);
|
||||
}
|
||||
|
||||
public static Boolean Create(DeletedFolder folder)
|
||||
{
|
||||
return Connection.Insert(folder) > 0;
|
||||
return Insert(folder);
|
||||
}
|
||||
|
||||
public static Boolean Create(User user)
|
||||
{
|
||||
return Connection.Insert(user) > 0;
|
||||
return Insert(user);
|
||||
}
|
||||
|
||||
public static Boolean Create(DeletedUser user)
|
||||
{
|
||||
return Connection.Insert(user) > 0;
|
||||
return Insert(user);
|
||||
}
|
||||
|
||||
public static Boolean Create(Session session)
|
||||
{
|
||||
return Connection.Insert(session) > 0;
|
||||
return Insert(session);
|
||||
}
|
||||
|
||||
public static Boolean Create(InstallationAccess installationAccess)
|
||||
{
|
||||
return Connection.Insert(installationAccess) > 0;
|
||||
return Insert(installationAccess);
|
||||
}
|
||||
|
||||
public static Boolean Create(FolderAccess folderAccess)
|
||||
{
|
||||
return Connection.Insert(folderAccess) > 0;
|
||||
return Insert(folderAccess);
|
||||
}
|
||||
|
||||
public static Boolean Create(OrderNumber2Installation o2i)
|
||||
{
|
||||
return Connection.Insert(o2i) > 0;
|
||||
return Insert(o2i);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
using System.Data.SQLite;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using CliWrap;
|
||||
using CliWrap.Buffered;
|
||||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
using InnovEnergy.App.Backend.Relations;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using Microsoft.Identity.Client;
|
||||
using SQLite;
|
||||
using SQLiteConnection = SQLite.SQLiteConnection;
|
||||
|
||||
|
||||
namespace InnovEnergy.App.Backend.Database;
|
||||
|
@ -16,8 +20,79 @@ public static partial class Db
|
|||
{
|
||||
internal const String DbPath = "./db.sqlite";
|
||||
|
||||
private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath);
|
||||
private static SQLiteConnection Connection { get; } = ((Func<SQLiteConnection>)(() =>
|
||||
{
|
||||
var latestDb = new DirectoryInfo(@"DbBackups").GetFiles()
|
||||
.OrderBy(f => f.LastWriteTime)
|
||||
.First().Name;
|
||||
|
||||
var fileConnection = new SQLiteConnection("DbBackups/"+latestDb);
|
||||
|
||||
var memoryConnection = new SQLiteConnection(":memory:");
|
||||
|
||||
// fileConnection.Backup(memoryConnection.DatabasePath);
|
||||
|
||||
memoryConnection.CreateTable<User>();
|
||||
memoryConnection.CreateTable<DeletedUser>();
|
||||
memoryConnection.CreateTable<Installation>();
|
||||
memoryConnection.CreateTable<DeletedInstallation>();
|
||||
memoryConnection.CreateTable<DeletedFolder>();
|
||||
memoryConnection.CreateTable<Folder>();
|
||||
memoryConnection.CreateTable<FolderAccess>();
|
||||
memoryConnection.CreateTable<InstallationAccess>();
|
||||
memoryConnection.CreateTable<Session>();
|
||||
memoryConnection.CreateTable<OrderNumber2Installation>();
|
||||
|
||||
foreach (var obj in fileConnection.Table<Session>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<Folder>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<Installation>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<User>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<FolderAccess>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<InstallationAccess>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<OrderNumber2Installation>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<DeletedInstallation>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<DeletedUser>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
foreach (var obj in fileConnection.Table<DeletedFolder>())
|
||||
{
|
||||
memoryConnection.Insert(obj);
|
||||
}
|
||||
|
||||
return memoryConnection;
|
||||
}))();
|
||||
|
||||
public static void BackupDatabase()
|
||||
{
|
||||
var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
|
||||
Connection.Backup("DbBackups/"+filename);
|
||||
}
|
||||
|
||||
public static TableQuery<Session> Sessions => Connection.Table<Session>();
|
||||
public static TableQuery<Folder> Folders => Connection.Table<Folder>();
|
||||
public static TableQuery<Installation> Installations => Connection.Table<Installation>();
|
||||
|
|
|
@ -22,9 +22,10 @@ public static partial class Db
|
|||
Boolean DeleteDescendantFolderAndItsDependencies(Folder f)
|
||||
{
|
||||
FolderAccess .Delete(r => r.FolderId == f.Id);
|
||||
Installations.Delete(r => r.ParentId == f.Id);
|
||||
|
||||
return Folders.Delete(r => r.Id == f.Id) > 0;
|
||||
Installations.Delete(r => r.ParentId == f.Id);
|
||||
var delete = Folders.Delete(r => r.Id == f.Id);
|
||||
BackupDatabase();
|
||||
return delete > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +36,9 @@ public static partial class Db
|
|||
Boolean DeleteInstallationAndItsDependencies()
|
||||
{
|
||||
InstallationAccess.Delete(i => i.InstallationId == installation.Id);
|
||||
return Installations.Delete(i => i.Id == installation.Id) > 0;
|
||||
var delete = Installations.Delete(i => i.Id == installation.Id);
|
||||
BackupDatabase();
|
||||
return delete > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +51,9 @@ public static partial class Db
|
|||
FolderAccess .Delete(u => u.UserId == user.Id);
|
||||
InstallationAccess.Delete(u => u.UserId == user.Id);
|
||||
|
||||
return Users.Delete(u => u.Id == user.Id) > 0;
|
||||
var delete = Users.Delete(u => u.Id == user.Id);
|
||||
BackupDatabase();
|
||||
return delete > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +63,8 @@ public static partial class Db
|
|||
// private!!
|
||||
private static Boolean Delete(Session session)
|
||||
{
|
||||
return Sessions.Delete(s => s.Id == session.Id) > 0;
|
||||
var delete = Sessions.Delete(s => s.Id == session.Id) > 0;
|
||||
BackupDatabase();
|
||||
return delete;
|
||||
}
|
||||
}
|
|
@ -5,14 +5,21 @@ namespace InnovEnergy.App.Backend.Database;
|
|||
|
||||
public static partial class Db
|
||||
{
|
||||
private static Boolean Update(Object obj)
|
||||
{
|
||||
var success = Connection.Update(obj) > 0;
|
||||
if(success) BackupDatabase();
|
||||
return success;
|
||||
}
|
||||
|
||||
public static Boolean Update(Folder folder)
|
||||
{
|
||||
return Connection.Update(folder) > 0;
|
||||
return Update(obj: folder);
|
||||
}
|
||||
|
||||
public static Boolean Update(Installation installation)
|
||||
{
|
||||
return Connection.Update(installation) > 0;
|
||||
return Update(obj: installation);
|
||||
}
|
||||
|
||||
public static Boolean Update(User user)
|
||||
|
@ -24,7 +31,7 @@ public static partial class Db
|
|||
user.ParentId = originalUser.ParentId;
|
||||
user.Name = originalUser.Name;
|
||||
|
||||
return Connection.Update(user) > 0;
|
||||
return Update(obj: user);
|
||||
}
|
||||
|
||||
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"Key": "EXO1abcb772bf43ab72951ba1dc",
|
||||
"Secret": "_ym1KsGBSp90S5dwhZn18XD-u9Y4ghHvyIxg5gv5fHw"
|
||||
}
|
|
@ -9,9 +9,5 @@ public static class S3Access
|
|||
{
|
||||
public static S3Cmd ReadOnly => Deserialize<S3Cmd>(OpenRead("./Resources/s3ReadOnlyKey.json"))!;
|
||||
public static S3Cmd ReadWrite => Deserialize<S3Cmd>(OpenRead("./Resources/s3ReadWriteKey.json"))!;
|
||||
|
||||
public static async Task<String> CreateKey(String bucketName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public static S3Cmd Admin => Deserialize<S3Cmd>(OpenRead("./Resources/s3AdminKey.json"))!;
|
||||
}
|
|
@ -57,11 +57,23 @@ public class S3Cmd
|
|||
</CORSConfiguration>";
|
||||
|
||||
var result = await Run(bucketName, "mb");
|
||||
var setCors = await Run(bucketName, "PutBucketCors", cors);
|
||||
var setCors = await Run(bucketName, "setcors", cors);
|
||||
|
||||
return result.ExitCode == 0 && setCors.ExitCode == 0;
|
||||
}
|
||||
|
||||
public async Task<String> ListFilesInBucket(String bucketName)
|
||||
{
|
||||
var result = await Run(bucketName, "ls");
|
||||
return result.StandardOutput;
|
||||
}
|
||||
|
||||
public async Task<String[]> GetFileText(String bucketName, String filename)
|
||||
{
|
||||
var result = await Run(bucketName + "/" + filename, "get", "--force");
|
||||
return File.ReadAllLines("./" + filename);
|
||||
}
|
||||
|
||||
public async Task<Boolean> DeleteBucket(String bucketName)
|
||||
{
|
||||
var result = await Run(bucketName, "rb");
|
||||
|
@ -86,4 +98,5 @@ public class S3Cmd
|
|||
.WithArguments(args)
|
||||
.ExecuteBufferedAsync();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
using InnovEnergy.App.Backend.S3;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace S3Explorer;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
|
||||
public static async Task<Int32> Main(String[] args)
|
||||
{
|
||||
// Todo refactor S3Access into Lib
|
||||
|
||||
// Sssssecret
|
||||
if (args.Contains("-s"))
|
||||
{
|
||||
await SnakeGameSs.PlaySnake();
|
||||
}
|
||||
|
||||
// Help message
|
||||
if (args.Length < 4 || args.Contains("-h"))
|
||||
{
|
||||
Console.WriteLine("Usage: S3Explorer [BucketId] [from:Unix-time] [to:Unix-time] [#Data-points]");
|
||||
Console.WriteLine("-h Shows this message.");
|
||||
Console.WriteLine("-s 🐍");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parsing Arguments
|
||||
var bucketName = args[0] + "-3e5b3069-214a-43ee-8d85-57d72000c19d";
|
||||
var startTime = Int64.Parse(args[1]);
|
||||
var endTime = Int64.Parse(args[2]);
|
||||
var numberOfDataPoints = Int64.Parse(args[3]);
|
||||
|
||||
var timeBetweenDataPoints = TimeBetweenDataPoints(startTime, endTime, numberOfDataPoints);
|
||||
|
||||
// Building a List of the timestamps we want to grab the files for.
|
||||
var timestampList = new List<String> { };
|
||||
for (var i = startTime; i <= endTime; i += timeBetweenDataPoints)
|
||||
{
|
||||
//Rounding to even numbers only (we only save every second second)
|
||||
timestampList.Add((i/2 *2).ToString());
|
||||
}
|
||||
|
||||
await PrintFiles(bucketName,timestampList);
|
||||
|
||||
// Success
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static async Task PrintFiles(String bucketName, List<String> timestampList)
|
||||
{
|
||||
var newestDataFilename = timestampList.Last();
|
||||
var csvFileText = await GetFileText(bucketName, newestDataFilename);
|
||||
|
||||
// Building Header-Row from the newest data
|
||||
csvFileText
|
||||
.Select(l => l.Split(";"))
|
||||
.Select(l => l[0])
|
||||
.Prepend("Timestamp")
|
||||
.JoinWith(";")
|
||||
.WriteLine();
|
||||
|
||||
foreach (var timestamp in timestampList)
|
||||
{
|
||||
csvFileText = await GetFileText(bucketName, timestamp);
|
||||
|
||||
// Writing Data below data-keys in a timestamped row
|
||||
csvFileText.Select(l => l.Split(";"))
|
||||
.Select(l => l[1])
|
||||
.Prepend(timestamp)
|
||||
.JoinWith(";")
|
||||
.WriteLine();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Int64 TimeBetweenDataPoints(Int64 startTime, Int64 endTime, Int64 numberOfDataPoints)
|
||||
{
|
||||
// Calculating temporal distance of data files from the number of requested points.
|
||||
var timeSpan = endTime - startTime;
|
||||
var timeBetweenDataPoints = timeSpan / numberOfDataPoints;
|
||||
|
||||
// We only upload data every second second so sampling more is impossible.
|
||||
// If this ever changes we might have to change this as well.
|
||||
timeBetweenDataPoints = Math.Max(timeBetweenDataPoints, 2);
|
||||
return timeBetweenDataPoints;
|
||||
}
|
||||
|
||||
// This Method extracts the Text from a given csv file on the s3 bucket
|
||||
private static async Task<String[]> GetFileText(String bucketName, String filename)
|
||||
{
|
||||
return await S3Access.Admin.GetFileText(bucketName, filename + ".csv");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Backend\Backend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,283 @@
|
|||
namespace S3Explorer;
|
||||
|
||||
public static class SnakeGameSs
|
||||
{
|
||||
public static async Task PlaySnake()
|
||||
{
|
||||
var tickRate = TimeSpan.FromMilliseconds(100);
|
||||
var snakeGame = new SnakeGame();
|
||||
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
async Task MonitorKeyPresses()
|
||||
{
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
var key = Console.ReadKey(intercept: true).Key;
|
||||
snakeGame.OnKeyPress(key);
|
||||
}
|
||||
|
||||
await Task.Delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
var monitorKeyPresses = MonitorKeyPresses();
|
||||
|
||||
do
|
||||
{
|
||||
snakeGame.OnGameTick();
|
||||
snakeGame.Render();
|
||||
await Task.Delay(tickRate);
|
||||
} while (!snakeGame.GameOver);
|
||||
|
||||
// Allow time for user to weep before application exits.
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
Console.Clear();
|
||||
await Task.Delay(500);
|
||||
snakeGame.Render();
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
cts.Cancel();
|
||||
await monitorKeyPresses;
|
||||
}
|
||||
}
|
||||
|
||||
enum Direction
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
interface IRenderable
|
||||
{
|
||||
void Render();
|
||||
}
|
||||
|
||||
readonly struct Position
|
||||
{
|
||||
public Position(int top, int left)
|
||||
{
|
||||
Top = top;
|
||||
Left = left;
|
||||
}
|
||||
|
||||
public int Top { get; }
|
||||
public int Left { get; }
|
||||
|
||||
public Position RightBy(int n) => new Position(Top, Left + n);
|
||||
public Position DownBy(int n) => new Position(Top + n, Left);
|
||||
}
|
||||
|
||||
class Apple : IRenderable
|
||||
{
|
||||
public Apple(Position position)
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
|
||||
public Position Position { get; }
|
||||
|
||||
public void Render()
|
||||
{
|
||||
Console.SetCursorPosition(Position.Left, Position.Top);
|
||||
Console.Write("🍏");
|
||||
}
|
||||
}
|
||||
|
||||
class Snake : IRenderable
|
||||
{
|
||||
private List<Position> _body;
|
||||
private int _growthSpurtsRemaining;
|
||||
|
||||
public Snake(Position spawnLocation, int initialSize = 1)
|
||||
{
|
||||
_body = new List<Position> { spawnLocation };
|
||||
_growthSpurtsRemaining = Math.Max(0, initialSize - 1);
|
||||
Dead = false;
|
||||
}
|
||||
|
||||
public bool Dead { get; private set; }
|
||||
public Position Head => _body.First();
|
||||
private IEnumerable<Position> Body => _body.Skip(1);
|
||||
|
||||
public void Move(Direction direction)
|
||||
{
|
||||
if (Dead) throw new InvalidOperationException();
|
||||
|
||||
Position newHead;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case Direction.Up:
|
||||
newHead = Head.DownBy(-1);
|
||||
break;
|
||||
|
||||
case Direction.Left:
|
||||
newHead = Head.RightBy(-1);
|
||||
break;
|
||||
|
||||
case Direction.Down:
|
||||
newHead = Head.DownBy(1);
|
||||
break;
|
||||
|
||||
case Direction.Right:
|
||||
newHead = Head.RightBy(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (_body.Contains(newHead) || !PositionIsValid(newHead))
|
||||
{
|
||||
Dead = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_body.Insert(0, newHead);
|
||||
|
||||
if (_growthSpurtsRemaining > 0)
|
||||
{
|
||||
_growthSpurtsRemaining--;
|
||||
}
|
||||
else
|
||||
{
|
||||
_body.RemoveAt(_body.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Grow()
|
||||
{
|
||||
if (Dead) throw new InvalidOperationException();
|
||||
|
||||
_growthSpurtsRemaining++;
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
Console.SetCursorPosition(Head.Left, Head.Top);
|
||||
Console.Write("◉");
|
||||
|
||||
foreach (var position in Body)
|
||||
{
|
||||
Console.SetCursorPosition(position.Left, position.Top);
|
||||
Console.Write("■");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool PositionIsValid(Position position) =>
|
||||
position.Top >= 0 && position.Left >= 0;
|
||||
}
|
||||
|
||||
class SnakeGame : IRenderable
|
||||
{
|
||||
private static readonly Position Origin = new Position(0, 0);
|
||||
|
||||
private Direction _currentDirection;
|
||||
private Direction _nextDirection;
|
||||
private Snake _snake;
|
||||
private Apple _apple;
|
||||
|
||||
public SnakeGame()
|
||||
{
|
||||
_snake = new Snake(Origin, initialSize: 5);
|
||||
_apple = CreateApple();
|
||||
_currentDirection = Direction.Right;
|
||||
_nextDirection = Direction.Right;
|
||||
}
|
||||
|
||||
public bool GameOver => _snake.Dead;
|
||||
|
||||
public void OnKeyPress(ConsoleKey key)
|
||||
{
|
||||
Direction newDirection;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case ConsoleKey.W:
|
||||
newDirection = Direction.Up;
|
||||
break;
|
||||
|
||||
case ConsoleKey.A:
|
||||
newDirection = Direction.Left;
|
||||
break;
|
||||
|
||||
case ConsoleKey.S:
|
||||
newDirection = Direction.Down;
|
||||
break;
|
||||
|
||||
case ConsoleKey.D:
|
||||
newDirection = Direction.Right;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Snake cannot turn 180 degrees.
|
||||
if (newDirection == OppositeDirectionTo(_currentDirection))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_nextDirection = newDirection;
|
||||
}
|
||||
|
||||
public void OnGameTick()
|
||||
{
|
||||
if (GameOver) throw new InvalidOperationException();
|
||||
|
||||
_currentDirection = _nextDirection;
|
||||
_snake.Move(_currentDirection);
|
||||
|
||||
// If the snake's head moves to the same position as an apple, the snake
|
||||
// eats it.
|
||||
if (_snake.Head.Equals(_apple.Position))
|
||||
{
|
||||
_snake.Grow();
|
||||
_apple = CreateApple();
|
||||
}
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
Console.Clear();
|
||||
_snake.Render();
|
||||
_apple.Render();
|
||||
Console.SetCursorPosition(0, 0);
|
||||
}
|
||||
|
||||
private static Direction OppositeDirectionTo(Direction direction)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case Direction.Up: return Direction.Down;
|
||||
case Direction.Left: return Direction.Right;
|
||||
case Direction.Right: return Direction.Left;
|
||||
case Direction.Down: return Direction.Up;
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private static Apple CreateApple()
|
||||
{
|
||||
// Can be factored elsewhere.
|
||||
const int numberOfRows = 20;
|
||||
const int numberOfColumns = 20;
|
||||
|
||||
var random = new Random();
|
||||
var top = random.Next(0, numberOfRows + 1);
|
||||
var left = random.Next(0, numberOfColumns + 1);
|
||||
var position = new Position(top, left);
|
||||
var apple = new Apple(position);
|
||||
|
||||
return apple;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,14 +73,14 @@ internal static class Program
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
e.LogError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task Run()
|
||||
{
|
||||
Console.WriteLine("Starting SaliMax");
|
||||
"Starting SaliMax".LogInfo();
|
||||
|
||||
// Send the initial "service started" message to systemd
|
||||
var sdNotifyReturn = sd_notify(0, "READY=1");
|
||||
|
@ -99,25 +99,25 @@ internal static class Program
|
|||
|
||||
StatusRecord ReadStatus()
|
||||
{
|
||||
Console.WriteLine(" Reading AcDC");
|
||||
"Reading AcDC".LogInfo();
|
||||
var acDc = acDcDevices.Read();
|
||||
|
||||
Console.WriteLine(" Reading dcDc");
|
||||
"Reading dcDc".LogInfo();
|
||||
var dcDc = dcDcDevices.Read();
|
||||
|
||||
Console.WriteLine(" Reading battery");
|
||||
"Reading battery".LogInfo();
|
||||
var battery = batteryDevices.Read();
|
||||
|
||||
Console.WriteLine(" Reading relays");
|
||||
"Reading relays".LogInfo();
|
||||
var relays = saliMaxRelaysDevice.Read();
|
||||
|
||||
Console.WriteLine(" loadOnAcIsland");
|
||||
"Reading loadOnAcIsland".LogInfo();
|
||||
var loadOnAcIsland = acIslandLoadMeter.Read();
|
||||
|
||||
Console.WriteLine(" Reading gridMeter");
|
||||
"Reading gridMeter".LogInfo();
|
||||
var gridMeter = gridMeterDevice.Read();
|
||||
|
||||
Console.WriteLine(" Reading pvOnDc");
|
||||
"Reading pvOnDc".LogInfo();
|
||||
var pvOnDc = amptDevice.Read();
|
||||
|
||||
var pvOnAcGrid = AcPowerDevice.Null;
|
||||
|
@ -177,30 +177,35 @@ internal static class Program
|
|||
dcDcDevices.Write(r.DcDc);
|
||||
}
|
||||
|
||||
|
||||
const Int32 delayTime = 10;
|
||||
|
||||
Console.WriteLine("press ctrl-C to stop");
|
||||
while (true)
|
||||
{
|
||||
sd_notify(0, "WATCHDOG=1");
|
||||
|
||||
var t = UnixTime.FromTicks(UnixTime.Now.Ticks / 2 * 2);
|
||||
|
||||
//t.ToUtcDateTime().WriteLine();
|
||||
|
||||
var t = UnixTime.Now;
|
||||
while (t.Ticks % UpdateIntervalSeconds != 0)
|
||||
{
|
||||
await Task.Delay(delayTime);
|
||||
t = UnixTime.Now;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var record = ReadStatus();
|
||||
|
||||
PrintTopology(record);
|
||||
|
||||
if (record.Relays is not null)
|
||||
record.Relays.ToCsv().WriteLine();
|
||||
record.Relays.ToCsv().LogInfo();
|
||||
|
||||
record.ControlConstants();
|
||||
|
||||
record.ControlSystemState();
|
||||
|
||||
Console.WriteLine($"{record.StateMachine.State}: {record.StateMachine.Message}");
|
||||
|
||||
var essControl = record.ControlEss().WriteLine();
|
||||
(t + "\n").LogInfo();
|
||||
$"{record.StateMachine.State}: {record.StateMachine.Message}".LogInfo();
|
||||
$"Batteries SOC: {record.Battery.Soc}".LogInfo();
|
||||
var essControl = record.ControlEss().LogInfo();
|
||||
|
||||
record.EssControl = essControl;
|
||||
|
||||
|
@ -211,11 +216,13 @@ internal static class Program
|
|||
|
||||
WriteControl(record);
|
||||
|
||||
PrintTopology(record);
|
||||
|
||||
await UploadCsv(record, t);
|
||||
|
||||
record.Config.Save();
|
||||
|
||||
"===========================================".WriteLine();
|
||||
"===========================================".LogInfo();
|
||||
}
|
||||
// ReSharper disable once FunctionNeverReturns
|
||||
}
|
||||
|
@ -234,13 +241,32 @@ internal static class Program
|
|||
var islandToGridBusPower = inverterPower + islandLoadPower;
|
||||
var gridLoadPower = s.LoadOnAcGrid is null ? 0: s.LoadOnAcGrid.Power.Active;
|
||||
|
||||
var gridPowerByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Power.Active.ToDisplayString(),
|
||||
TextBlock gridBusColumn;
|
||||
TextBlock gridBox;
|
||||
TextBlock totalBoxes;
|
||||
|
||||
|
||||
if (s.GridMeter is not null)
|
||||
{
|
||||
var gridPowerByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Power.Active.ToDisplayString(),
|
||||
s.GridMeter.Ac.L2.Power.Active.ToDisplayString(),
|
||||
s.GridMeter.Ac.L3.Power.Active.ToDisplayString());
|
||||
|
||||
var gridVoltageByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Voltage.ToDisplayString(),
|
||||
s.GridMeter.Ac.L2.Voltage.ToDisplayString(),
|
||||
s.GridMeter.Ac.L3.Voltage.ToDisplayString());
|
||||
|
||||
gridBusColumn = ColumnBox("Pv", "Grid Bus", "Load" , gridVoltageByPhase , gridLoadPower);
|
||||
gridBox = TextBlock.AlignLeft(gridPowerByPhase).TitleBox("Grid");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
gridBusColumn = TextBlock.Spacer(0);
|
||||
gridBox = TextBlock.Spacer(0);
|
||||
}
|
||||
|
||||
|
||||
var gridVoltageByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Voltage.ToDisplayString(),
|
||||
s.GridMeter.Ac.L2.Voltage.ToDisplayString(),
|
||||
s.GridMeter.Ac.L3.Voltage.ToDisplayString());
|
||||
|
||||
var inverterPowerByPhase = TextBlock.AlignLeft(s.AcDc.Ac.L1.Power.Active.ToDisplayString(),
|
||||
s.AcDc.Ac.L2.Power.Active.ToDisplayString(),
|
||||
|
@ -264,7 +290,6 @@ internal static class Program
|
|||
var anyBatteryAlarm = s.Battery.Alarms.Any();
|
||||
var anyBatteryWarning = s.Battery.Warnings.Any();
|
||||
|
||||
var gridBusColumn = ColumnBox("Pv", "Grid Bus", "Load" , gridVoltageByPhase , gridLoadPower);
|
||||
var islandBusColumn = ColumnBox("Pv", "Island Bus", "Load" , inverterPowerByPhase, islandLoadPower);
|
||||
var dcBusColumn = ColumnBox("Pv", "Dc Bus", "Load" , dcLinkVoltage, 0, pvOnDcPower);
|
||||
var gridBusFlow = Flow.Horizontal(gridPower);
|
||||
|
@ -274,8 +299,7 @@ internal static class Program
|
|||
var flowDcBusToDcDc = Flow.Horizontal(dcdcPower);
|
||||
var flowDcDcToBattery = Flow.Horizontal(dcBatteryPower);
|
||||
|
||||
var gridBox = TextBlock.AlignLeft(gridPowerByPhase).TitleBox("Grid");
|
||||
var inverterBox = TextBlock.AlignLeft(inverterPowerByAcDc).TitleBox("Inverter");
|
||||
var inverterBox = TextBlock.AlignLeft(inverterPowerByAcDc).TitleBox("AC/DC");
|
||||
var dcDcBox = TextBlock.AlignLeft(dc48Voltage).TitleBox("DC/DC");
|
||||
var batteryAvgBox = TextBlock.AlignLeft(batteryVoltage,
|
||||
batterySoc,
|
||||
|
@ -297,11 +321,27 @@ internal static class Program
|
|||
var individualBatteries = batteryBoxes.Any()
|
||||
? TextBlock.AlignLeft(batteryBoxes)
|
||||
: TextBlock.Spacer(1);
|
||||
|
||||
var totalBoxes = TextBlock.AlignCenterVertical(gridBox,
|
||||
gridBusFlow,
|
||||
gridBusColumn,
|
||||
flowGridBusToIslandBus,
|
||||
|
||||
if (s.GridMeter is not null)
|
||||
{
|
||||
totalBoxes = TextBlock.AlignCenterVertical(gridBox,
|
||||
gridBusFlow,
|
||||
gridBusColumn,
|
||||
flowGridBusToIslandBus,
|
||||
islandBusColumn,
|
||||
flowIslandBusToInverter,
|
||||
inverterBox,
|
||||
flowInverterToDcBus,
|
||||
dcBusColumn,
|
||||
flowDcBusToDcDc,
|
||||
dcDcBox,
|
||||
flowDcDcToBattery,
|
||||
batteryAvgBox,
|
||||
individualBatteries);
|
||||
}
|
||||
else
|
||||
{
|
||||
totalBoxes = TextBlock.AlignCenterVertical(
|
||||
islandBusColumn,
|
||||
flowIslandBusToInverter,
|
||||
inverterBox,
|
||||
|
@ -312,6 +352,8 @@ internal static class Program
|
|||
flowDcDcToBattery,
|
||||
batteryAvgBox,
|
||||
individualBatteries);
|
||||
}
|
||||
|
||||
totalBoxes.WriteLine();
|
||||
}
|
||||
|
||||
|
@ -363,10 +405,18 @@ internal static class Program
|
|||
private static void ControlConstants(this StatusRecord r)
|
||||
{
|
||||
var inverters = r.AcDc.Devices;
|
||||
var dcDevices = r.DcDc.Devices;
|
||||
|
||||
inverters.ForEach(d => d.Control.Dc.MaxVoltage = r.Config.MaxDcBusVoltage);
|
||||
inverters.ForEach(d => d.Control.Dc.MinVoltage = r.Config.MinDcBusVoltage);
|
||||
inverters.ForEach(d => d.Control.Dc.ReferenceVoltage = r.Config.ReferenceDcBusVoltage);
|
||||
|
||||
// dcDevices.ForEach(d => d.Control. Dc.MaxVoltage = r.Config.MaxDcBusVoltage);
|
||||
// dcDevices.ForEach(d => d.Control. Dc.MinVoltage = r.Config.MinDcBusVoltage);
|
||||
// dcDevices.ForEach(d => d.Control. Dc.ReferenceVoltage = r.Config.ReferenceDcBusVoltage);
|
||||
|
||||
r.DcDc.ResetAlarms();
|
||||
r.AcDc.ResetAlarms();
|
||||
}
|
||||
|
||||
|
||||
|
@ -408,11 +458,13 @@ internal static class Program
|
|||
sc.UseSlaveIdForAddressing = true;
|
||||
sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed;
|
||||
sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off;
|
||||
|
||||
sc.ResetAlarmsAndWarnings = true;
|
||||
}
|
||||
|
||||
private static void ApplyDcDcDefaultSettings(this SystemControlRegisters? sc)
|
||||
{
|
||||
|
||||
if (sc is null)
|
||||
return;
|
||||
|
||||
|
@ -427,7 +479,8 @@ internal static class Program
|
|||
sc.UseSlaveIdForAddressing = true;
|
||||
sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed;
|
||||
sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off;
|
||||
sc.ResetAlarmsAndWarnings = true;
|
||||
|
||||
sc.ResetAlarmsAndWarnings = true;
|
||||
}
|
||||
|
||||
private static async Task UploadCsv(StatusRecord status, UnixTime timeStamp)
|
||||
|
|
|
@ -77,6 +77,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adam6360D", "Lib\Devices\Ad
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IEM3kGridMeter", "Lib\Devices\IEM3kGridMeter\IEM3kGridMeter.csproj", "{1391165D-51F1-45B4-8B7F-042A20AA0277}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Explorer", "App\S3Explorer\S3Explorer.csproj", "{EB56EF94-D8A7-4111-A8E7-A87EF13596DA}"
|
||||
EndProject
|
||||
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -196,6 +198,10 @@ Global
|
|||
{1391165D-51F1-45B4-8B7F-042A20AA0277}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1391165D-51F1-45B4-8B7F-042A20AA0277}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1391165D-51F1-45B4-8B7F-042A20AA0277}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||
|
@ -230,5 +236,6 @@ Global
|
|||
{1A56992B-CB72-490F-99A4-DF1186BA3A18} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}
|
||||
{A3C79247-4CAA-44BE-921E-7285AB39E71F} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
||||
{1391165D-51F1-45B4-8B7F-042A20AA0277} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
||||
{EB56EF94-D8A7-4111-A8E7-A87EF13596DA} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
Loading…
Reference in New Issue