From 5ff0384def05542667de457fbae3bcde173bbbd8 Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Thu, 27 Nov 2025 14:02:00 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B8=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 487 +++++++++ .../AmineContentAnalyzerService.cs | 6 + .../AmineContentCalibrationDataService.cs | 6 + .../AmineContentCalibrationService.cs | 6 + .../20251125054200_Initial.Designer.cs | 184 ++++ .../Database/20251125054200_Initial.cs | 130 +++ .../AmineContentCalibrationContext.cs | 78 ++ .../AmineContentCalibrationContextFactory.cs | 17 + ...eContentCalibrationContextModelSnapshot.cs | 181 ++++ GSS2.Core/GSS2.Core.csproj | 26 + GSS2.Core/Hardware/CameraService.cs | 0 GSS2.Core/Hardware/IlluminatorService.cs | 221 +++++ GSS2.Core/Hardware/LightsService.cs | 445 +++++++++ .../Hardware/TemperatureHumidityService.cs | 174 ++++ .../GSS2.LightsControl.CSharpClient.csproj | 22 + GSS2.LightsControl/Server/.env | 10 + GSS2.LightsControl/Server/.gitignore | 1 + GSS2.LightsControl/Server/lights_deamon.py | 932 ++++++++++++++++++ GSS2.LightsControl/Server/lights_pb2.py | 74 ++ GSS2.LightsControl/Server/lights_pb2_grpc.py | 914 +++++++++++++++++ GSS2.LightsControl/Server/requirements.txt | Bin 0 -> 222 bytes GSS2.LightsControl/Server/test_client.py | 287 ++++++ GSS2.LightsControl/lights.proto | 189 ++++ GSS2.Test/GSS2.Test.csproj | 17 + GSS2.Test/Program.cs | 66 ++ GSS2.Test/Worker.cs | 30 + GSS2.Test/appsettings.json | 33 + GranuSightSoftware2.sln | 70 ++ 28 files changed, 4606 insertions(+) create mode 100644 .gitignore create mode 100644 GSS2.Core/Analysis/AmineContent/AmineContentAnalyzerService.cs create mode 100644 GSS2.Core/Analysis/AmineContent/AmineContentCalibrationDataService.cs create mode 100644 GSS2.Core/Analysis/AmineContent/AmineContentCalibrationService.cs create mode 100644 GSS2.Core/Analysis/AmineContent/Database/20251125054200_Initial.Designer.cs create mode 100644 GSS2.Core/Analysis/AmineContent/Database/20251125054200_Initial.cs create mode 100644 GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContext.cs create mode 100644 GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContextFactory.cs create mode 100644 GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContextModelSnapshot.cs create mode 100644 GSS2.Core/GSS2.Core.csproj create mode 100644 GSS2.Core/Hardware/CameraService.cs create mode 100644 GSS2.Core/Hardware/IlluminatorService.cs create mode 100644 GSS2.Core/Hardware/LightsService.cs create mode 100644 GSS2.Core/Hardware/TemperatureHumidityService.cs create mode 100644 GSS2.LightsControl/CSharpClient/GSS2.LightsControl.CSharpClient.csproj create mode 100644 GSS2.LightsControl/Server/.env create mode 100644 GSS2.LightsControl/Server/.gitignore create mode 100644 GSS2.LightsControl/Server/lights_deamon.py create mode 100644 GSS2.LightsControl/Server/lights_pb2.py create mode 100644 GSS2.LightsControl/Server/lights_pb2_grpc.py create mode 100644 GSS2.LightsControl/Server/requirements.txt create mode 100644 GSS2.LightsControl/Server/test_client.py create mode 100644 GSS2.LightsControl/lights.proto create mode 100644 GSS2.Test/GSS2.Test.csproj create mode 100644 GSS2.Test/Program.cs create mode 100644 GSS2.Test/Worker.cs create mode 100644 GSS2.Test/appsettings.json create mode 100644 GranuSightSoftware2.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5424cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,487 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +*.db +.vscode + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/GSS2.Core/Analysis/AmineContent/AmineContentAnalyzerService.cs b/GSS2.Core/Analysis/AmineContent/AmineContentAnalyzerService.cs new file mode 100644 index 0000000..96936f0 --- /dev/null +++ b/GSS2.Core/Analysis/AmineContent/AmineContentAnalyzerService.cs @@ -0,0 +1,6 @@ +namespace GSS2.Core.Analysis.AmineContent; + +public class AmineContentAnalyzerService +{ + +} \ No newline at end of file diff --git a/GSS2.Core/Analysis/AmineContent/AmineContentCalibrationDataService.cs b/GSS2.Core/Analysis/AmineContent/AmineContentCalibrationDataService.cs new file mode 100644 index 0000000..9c95910 --- /dev/null +++ b/GSS2.Core/Analysis/AmineContent/AmineContentCalibrationDataService.cs @@ -0,0 +1,6 @@ +namespace GSS2.Core.Analysis.AmineContent; + +public class AmineContentCalibrationDataService +{ + +} \ No newline at end of file diff --git a/GSS2.Core/Analysis/AmineContent/AmineContentCalibrationService.cs b/GSS2.Core/Analysis/AmineContent/AmineContentCalibrationService.cs new file mode 100644 index 0000000..d5c3786 --- /dev/null +++ b/GSS2.Core/Analysis/AmineContent/AmineContentCalibrationService.cs @@ -0,0 +1,6 @@ +namespace GSS2.Core.Analysis.AmineContent; + +public class AmineContentCalibrationService +{ + +} \ No newline at end of file diff --git a/GSS2.Core/Analysis/AmineContent/Database/20251125054200_Initial.Designer.cs b/GSS2.Core/Analysis/AmineContent/Database/20251125054200_Initial.Designer.cs new file mode 100644 index 0000000..e858fb4 --- /dev/null +++ b/GSS2.Core/Analysis/AmineContent/Database/20251125054200_Initial.Designer.cs @@ -0,0 +1,184 @@ +// +using System; +using GSS2.Core.Analysis.AmineContent.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GSS2.Core.Analysis.AmineContent.Database +{ + [DbContext(typeof(AmineContentCalibrationContext))] + [Migration("20251125054200_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.0"); + + modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+ImageData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasComment("Уникальный идентификатор"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Путь к файлу изображения"); + + b.Property("ImageType") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Тип изображения (sample - проба или separator - сепаратор)"); + + b.Property("Uv254Intensity") + .HasColumnType("REAL") + .HasComment("Интенсивность УФ излучения 254 нм (0..1)"); + + b.Property("Uv365Intensity") + .HasColumnType("REAL") + .HasComment("Интенсивность УФ излучения 365 нм (0..1)"); + + b.Property("VisibleIntensity") + .HasColumnType("REAL") + .HasComment("Интенсивность видимого света (0..1)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+Record", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasComment("Уникальный идентификатор"); + + b.Property("AirHumidity") + .HasColumnType("REAL") + .HasComment("Относительная влажность воздуха (%)"); + + b.Property("AirTemperature") + .HasColumnType("REAL") + .HasComment("Температура воздуха (℃)"); + + b.Property("MixtureActualRate") + .HasColumnType("REAL") + .HasComment("Истинный расход кондиционирующей смеси (кг/т)"); + + b.Property("MixtureAmineContent") + .HasColumnType("REAL") + .HasComment("Содержание аминов в кондиционирующей смеси (%)"); + + b.Property("MixtureName") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Название используемой кондиционирующей смеси"); + + b.Property("MixtureNormalRate") + .HasColumnType("REAL") + .HasComment("Норма расхода кондиционирующей смеси (кг/т)"); + + b.Property("SampleBrand") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Марка пробы"); + + b.Property("SampleComment") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Дополнительная информация о пробе"); + + b.Property("SampleMass") + .HasColumnType("REAL") + .HasComment("Масса пробы (кг)"); + + b.Property("SampleName") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Название пробы"); + + b.Property("SamplingDateTime") + .HasColumnType("TEXT") + .HasComment("Дата и время отбора пробы"); + + b.Property("SamplingPlace") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Место отбора пробы"); + + b.HasKey("Id"); + + b.ToTable("Records"); + }); + + modelBuilder.Entity("ImageDataRecord", b => + { + b.Property("SampleImagesId") + .HasColumnType("INTEGER"); + + b.Property("SampleRecordsId") + .HasColumnType("INTEGER"); + + b.HasKey("SampleImagesId", "SampleRecordsId"); + + b.HasIndex("SampleRecordsId"); + + b.ToTable("SampleImages", (string)null); + }); + + modelBuilder.Entity("ImageDataRecord1", b => + { + b.Property("SeparatorImagesId") + .HasColumnType("INTEGER"); + + b.Property("SeparatorRecordsId") + .HasColumnType("INTEGER"); + + b.HasKey("SeparatorImagesId", "SeparatorRecordsId"); + + b.HasIndex("SeparatorRecordsId"); + + b.ToTable("SeparatorImages", (string)null); + }); + + modelBuilder.Entity("ImageDataRecord", b => + { + b.HasOne("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+ImageData", null) + .WithMany() + .HasForeignKey("SampleImagesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+Record", null) + .WithMany() + .HasForeignKey("SampleRecordsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ImageDataRecord1", b => + { + b.HasOne("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+ImageData", null) + .WithMany() + .HasForeignKey("SeparatorImagesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+Record", null) + .WithMany() + .HasForeignKey("SeparatorRecordsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/GSS2.Core/Analysis/AmineContent/Database/20251125054200_Initial.cs b/GSS2.Core/Analysis/AmineContent/Database/20251125054200_Initial.cs new file mode 100644 index 0000000..5782027 --- /dev/null +++ b/GSS2.Core/Analysis/AmineContent/Database/20251125054200_Initial.cs @@ -0,0 +1,130 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GSS2.Core.Analysis.AmineContent.Database +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Images", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор") + .Annotation("Sqlite:Autoincrement", true), + VisibleIntensity = table.Column(type: "REAL", nullable: false, comment: "Интенсивность видимого света (0..1)"), + Uv365Intensity = table.Column(type: "REAL", nullable: false, comment: "Интенсивность УФ излучения 365 нм (0..1)"), + Uv254Intensity = table.Column(type: "REAL", nullable: false, comment: "Интенсивность УФ излучения 254 нм (0..1)"), + ImageType = table.Column(type: "TEXT", nullable: false, comment: "Тип изображения (sample - проба или separator - сепаратор)"), + ImagePath = table.Column(type: "TEXT", nullable: false, comment: "Путь к файлу изображения") + }, + constraints: table => + { + table.PrimaryKey("PK_Images", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Records", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false, comment: "Уникальный идентификатор") + .Annotation("Sqlite:Autoincrement", true), + SampleName = table.Column(type: "TEXT", nullable: false, comment: "Название пробы"), + SampleComment = table.Column(type: "TEXT", nullable: false, comment: "Дополнительная информация о пробе"), + SampleBrand = table.Column(type: "TEXT", nullable: false, comment: "Марка пробы"), + SampleMass = table.Column(type: "REAL", nullable: false, comment: "Масса пробы (кг)"), + SamplingDateTime = table.Column(type: "TEXT", nullable: false, comment: "Дата и время отбора пробы"), + SamplingPlace = table.Column(type: "TEXT", nullable: false, comment: "Место отбора пробы"), + MixtureName = table.Column(type: "TEXT", nullable: false, comment: "Название используемой кондиционирующей смеси"), + MixtureAmineContent = table.Column(type: "REAL", nullable: false, comment: "Содержание аминов в кондиционирующей смеси (%)"), + MixtureNormalRate = table.Column(type: "REAL", nullable: false, comment: "Норма расхода кондиционирующей смеси (кг/т)"), + MixtureActualRate = table.Column(type: "REAL", nullable: false, comment: "Истинный расход кондиционирующей смеси (кг/т)"), + AirTemperature = table.Column(type: "REAL", nullable: false, comment: "Температура воздуха (℃)"), + AirHumidity = table.Column(type: "REAL", nullable: false, comment: "Относительная влажность воздуха (%)") + }, + constraints: table => + { + table.PrimaryKey("PK_Records", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SampleImages", + columns: table => new + { + SampleImagesId = table.Column(type: "INTEGER", nullable: false), + SampleRecordsId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SampleImages", x => new { x.SampleImagesId, x.SampleRecordsId }); + table.ForeignKey( + name: "FK_SampleImages_Images_SampleImagesId", + column: x => x.SampleImagesId, + principalTable: "Images", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SampleImages_Records_SampleRecordsId", + column: x => x.SampleRecordsId, + principalTable: "Records", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SeparatorImages", + columns: table => new + { + SeparatorImagesId = table.Column(type: "INTEGER", nullable: false), + SeparatorRecordsId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparatorImages", x => new { x.SeparatorImagesId, x.SeparatorRecordsId }); + table.ForeignKey( + name: "FK_SeparatorImages_Images_SeparatorImagesId", + column: x => x.SeparatorImagesId, + principalTable: "Images", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SeparatorImages_Records_SeparatorRecordsId", + column: x => x.SeparatorRecordsId, + principalTable: "Records", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_SampleImages_SampleRecordsId", + table: "SampleImages", + column: "SampleRecordsId"); + + migrationBuilder.CreateIndex( + name: "IX_SeparatorImages_SeparatorRecordsId", + table: "SeparatorImages", + column: "SeparatorRecordsId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SampleImages"); + + migrationBuilder.DropTable( + name: "SeparatorImages"); + + migrationBuilder.DropTable( + name: "Images"); + + migrationBuilder.DropTable( + name: "Records"); + } + } +} diff --git a/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContext.cs b/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContext.cs new file mode 100644 index 0000000..be5da75 --- /dev/null +++ b/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContext.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore; + +namespace GSS2.Core.Analysis.AmineContent.Database; + +public class AmineContentCalibrationContext : DbContext +{ + public class Record + { + [Comment("Уникальный идентификатор")] + public int Id { get; set; } + [Comment("Название пробы")] + public string SampleName { get; set; } = string.Empty; + [Comment("Дополнительная информация о пробе")] + public string SampleComment { get; set; } = string.Empty; + [Comment("Марка пробы")] + public string SampleBrand { get; set; } = string.Empty; + [Comment("Масса пробы (кг)")] + public double SampleMass { get; set; } = double.NaN; + [Comment("Дата и время отбора пробы")] + public DateTime SamplingDateTime { get; set; } = DateTime.Now; + [Comment("Место отбора пробы")] + public string SamplingPlace { get; set; } = string.Empty; + [Comment("Название используемой кондиционирующей смеси")] + public string MixtureName { get; set; } = string.Empty; + [Comment("Содержание аминов в кондиционирующей смеси (%)")] + public double MixtureAmineContent { get; set; } = double.NaN; + [Comment("Норма расхода кондиционирующей смеси (кг/т)")] + public double MixtureNormalRate { get; set; } = double.NaN; + [Comment("Истинный расход кондиционирующей смеси (кг/т)")] + public double MixtureActualRate { get; set; } = double.NaN; + [Comment("Изображения пробы")] + public List SampleImages { get; set; } = new(); + [Comment("Изображения пустого сепаратора")] + public List SeparatorImages { get; set; } = new(); + [Comment("Температура воздуха (℃)")] + public double AirTemperature { get; set; } = new(); + [Comment("Относительная влажность воздуха (%)")] + public double AirHumidity { get; set; } = new(); + } + public class ImageData + { + [Comment("Уникальный идентификатор")] + public int Id { get; set; } + [Comment("Интенсивность видимого света (0..1)")] + public double VisibleIntensity { get; set; } = double.NaN; + [Comment("Интенсивность УФ излучения 365 нм (0..1)")] + public double Uv365Intensity { get; set; } = double.NaN; + [Comment("Интенсивность УФ излучения 254 нм (0..1)")] + public double Uv254Intensity { get; set; } = double.NaN; + [Comment("Тип изображения (sample - проба или separator - сепаратор)")] + public string ImageType { get; set; } = string.Empty; + [Comment("Путь к файлу изображения")] + public string ImagePath { get; set; } = string.Empty; + + [Comment("Записи в которых это изображение используется как изображение пробы")] + public List SampleRecords { get; set; } = new(); + [Comment("Записи в которых это изображение используется как изображение сепаратора")] + public List SeparatorRecords { get; set; } = new(); + } + + public DbSet Records => Set(); + public DbSet Images => Set(); + + public AmineContentCalibrationContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(r => r.SampleImages) + .WithMany(i => i.SampleRecords) + .UsingEntity(j => j.ToTable("SampleImages")); + + modelBuilder.Entity() + .HasMany(r => r.SeparatorImages) + .WithMany(i => i.SeparatorRecords) + .UsingEntity(j => j.ToTable("SeparatorImages")); + } +} \ No newline at end of file diff --git a/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContextFactory.cs b/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContextFactory.cs new file mode 100644 index 0000000..eee2a4b --- /dev/null +++ b/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContextFactory.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace GSS2.Core.Analysis.AmineContent.Database; + +public class AmineContentCalibrationContextFactory : IDesignTimeDbContextFactory +{ + public AmineContentCalibrationContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + + // строка подключения для миграций (в design-time) + optionsBuilder.UseSqlite("Data Source=AmineContentCalibration.db"); + + return new AmineContentCalibrationContext(optionsBuilder.Options); + } +} diff --git a/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContextModelSnapshot.cs b/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContextModelSnapshot.cs new file mode 100644 index 0000000..79130e7 --- /dev/null +++ b/GSS2.Core/Analysis/AmineContent/Database/AmineContentCalibrationContextModelSnapshot.cs @@ -0,0 +1,181 @@ +// +using System; +using GSS2.Core.Analysis.AmineContent.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GSS2.Core.Analysis.AmineContent.Database +{ + [DbContext(typeof(AmineContentCalibrationContext))] + partial class AmineContentCalibrationContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.0"); + + modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+ImageData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasComment("Уникальный идентификатор"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Путь к файлу изображения"); + + b.Property("ImageType") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Тип изображения (sample - проба или separator - сепаратор)"); + + b.Property("Uv254Intensity") + .HasColumnType("REAL") + .HasComment("Интенсивность УФ излучения 254 нм (0..1)"); + + b.Property("Uv365Intensity") + .HasColumnType("REAL") + .HasComment("Интенсивность УФ излучения 365 нм (0..1)"); + + b.Property("VisibleIntensity") + .HasColumnType("REAL") + .HasComment("Интенсивность видимого света (0..1)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+Record", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasComment("Уникальный идентификатор"); + + b.Property("AirHumidity") + .HasColumnType("REAL") + .HasComment("Относительная влажность воздуха (%)"); + + b.Property("AirTemperature") + .HasColumnType("REAL") + .HasComment("Температура воздуха (℃)"); + + b.Property("MixtureActualRate") + .HasColumnType("REAL") + .HasComment("Истинный расход кондиционирующей смеси (кг/т)"); + + b.Property("MixtureAmineContent") + .HasColumnType("REAL") + .HasComment("Содержание аминов в кондиционирующей смеси (%)"); + + b.Property("MixtureName") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Название используемой кондиционирующей смеси"); + + b.Property("MixtureNormalRate") + .HasColumnType("REAL") + .HasComment("Норма расхода кондиционирующей смеси (кг/т)"); + + b.Property("SampleBrand") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Марка пробы"); + + b.Property("SampleComment") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Дополнительная информация о пробе"); + + b.Property("SampleMass") + .HasColumnType("REAL") + .HasComment("Масса пробы (кг)"); + + b.Property("SampleName") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Название пробы"); + + b.Property("SamplingDateTime") + .HasColumnType("TEXT") + .HasComment("Дата и время отбора пробы"); + + b.Property("SamplingPlace") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("Место отбора пробы"); + + b.HasKey("Id"); + + b.ToTable("Records"); + }); + + modelBuilder.Entity("ImageDataRecord", b => + { + b.Property("SampleImagesId") + .HasColumnType("INTEGER"); + + b.Property("SampleRecordsId") + .HasColumnType("INTEGER"); + + b.HasKey("SampleImagesId", "SampleRecordsId"); + + b.HasIndex("SampleRecordsId"); + + b.ToTable("SampleImages", (string)null); + }); + + modelBuilder.Entity("ImageDataRecord1", b => + { + b.Property("SeparatorImagesId") + .HasColumnType("INTEGER"); + + b.Property("SeparatorRecordsId") + .HasColumnType("INTEGER"); + + b.HasKey("SeparatorImagesId", "SeparatorRecordsId"); + + b.HasIndex("SeparatorRecordsId"); + + b.ToTable("SeparatorImages", (string)null); + }); + + modelBuilder.Entity("ImageDataRecord", b => + { + b.HasOne("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+ImageData", null) + .WithMany() + .HasForeignKey("SampleImagesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+Record", null) + .WithMany() + .HasForeignKey("SampleRecordsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ImageDataRecord1", b => + { + b.HasOne("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+ImageData", null) + .WithMany() + .HasForeignKey("SeparatorImagesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GSS2.Core.Analysis.AmineContent.Database.AmineContentCalibrationContext+Record", null) + .WithMany() + .HasForeignKey("SeparatorRecordsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/GSS2.Core/GSS2.Core.csproj b/GSS2.Core/GSS2.Core.csproj new file mode 100644 index 0000000..71940ae --- /dev/null +++ b/GSS2.Core/GSS2.Core.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/GSS2.Core/Hardware/CameraService.cs b/GSS2.Core/Hardware/CameraService.cs new file mode 100644 index 0000000..e69de29 diff --git a/GSS2.Core/Hardware/IlluminatorService.cs b/GSS2.Core/Hardware/IlluminatorService.cs new file mode 100644 index 0000000..2633872 --- /dev/null +++ b/GSS2.Core/Hardware/IlluminatorService.cs @@ -0,0 +1,221 @@ +using System; +using System.Device.Gpio; +using System.Device.Pwm; +using System.Device.Pwm.Drivers; +using System.IO; +using System.Threading; +using Microsoft.Extensions.Logging; + +namespace GSS2.Core.Hardware; + +public class IlluminatorService : IDisposable +{ + public class IlluminatorServiceConfiguration + { + public static IlluminatorServiceConfiguration Default => new IlluminatorServiceConfiguration + { + WhitePwmPin = 13, + Uv254PwmPin = 18, + Uv365PwmPin = 12, + WhiteRelayPin = 16, + Uv254RelayPin = 15, + Uv365RelayPin = 20, + FanPin = 21, + + PwmFrequency = 200, + + MaxWhitePwmDuty = 1.0, + MaxUv254PwmDuty = 1.0, + MaxUv365PwmDuty = 1.0 + }; + + public int WhitePwmPin { get; set; } + public int Uv254PwmPin { get; set; } + public int Uv365PwmPin { get; set; } + public int WhiteRelayPin { get; set; } + public int Uv254RelayPin { get; set; } + public int Uv365RelayPin { get; set; } + public int FanPin { get; set; } + + public int PwmFrequency { get; set; } + + public double MaxWhitePwmDuty { get; set; } + public double MaxUv254PwmDuty { get; set; } + public double MaxUv365PwmDuty { get; set; } + } + + private static readonly Dictionary PinPwmChips = new() + { + { 12, 0 }, + { 13, 0 }, + { 14, 0 }, + { 15, 0 }, + { 18, 0 }, + { 19, 0 }, + }; + private static readonly Dictionary PinPwmChannels = new() + { + { 12, 0 }, + { 13, 1 }, + { 14, 2 }, + { 15, 3 }, + { 18, 2 }, + { 19, 3 } + }; + + private bool _disposedValue; + private readonly ILogger _logger; + private readonly IlluminatorServiceConfiguration _configuration; + + private readonly GpioController _gpio; + private readonly PwmChannel _whitePwm; + private readonly PwmChannel _uv254Pwm; + private readonly PwmChannel _uv365Pwm; + private readonly GpioPin _whiteRelay; + private readonly GpioPin _uv254Relay; + private readonly GpioPin _uv365Relay; + private readonly GpioPin _fanRelay; + + public IlluminatorService(ILogger logger, IlluminatorServiceConfiguration? configuration = null) + { + _logger = logger; + + _logger.LogInformation("Illuminator initialization"); + + if (configuration is null) + { + _logger.LogWarning("Illuminator configuration not set. Using default configuration."); + _configuration = IlluminatorServiceConfiguration.Default; + } + else + { + _configuration = configuration; + } + + _logger.LogDebug(@$"Illuminator use following configuration: + WhitePwmPin: {_configuration.WhitePwmPin} + Uv254PwmPin: {_configuration.Uv254PwmPin} + Uv365PwmPin: {_configuration.Uv365PwmPin} + WhiteRelayPin: {_configuration.WhiteRelayPin} + Uv254RelayPin: {_configuration.Uv254RelayPin} + Uv365RelayPin: {_configuration.Uv365RelayPin} + FanPin: {_configuration.FanPin} + PwmFrequency: {_configuration.PwmFrequency} + MaxWhitePwmDuty: {_configuration.MaxWhitePwmDuty} + MaxUv254PwmDuty: {_configuration.MaxUv254PwmDuty} + MaxUv365PwmDuty: {_configuration.MaxUv365PwmDuty} + "); + + CheckPwmPin(_configuration.WhitePwmPin); + CheckPwmPin(_configuration.Uv254PwmPin); + CheckPwmPin(_configuration.Uv365PwmPin); + + try + { + _gpio = new GpioController(); + _whitePwm = PwmChannel.Create(PinPwmChips[_configuration.WhitePwmPin], PinPwmChannels[_configuration.WhitePwmPin], _configuration.PwmFrequency, CalculateDuty(0, _configuration.MaxWhitePwmDuty)); + _uv254Pwm = PwmChannel.Create(PinPwmChips[_configuration.Uv254PwmPin], PinPwmChannels[_configuration.Uv254PwmPin], _configuration.PwmFrequency, CalculateDuty(0, _configuration.MaxUv254PwmDuty)); + _uv365Pwm = PwmChannel.Create(PinPwmChips[_configuration.Uv365PwmPin], PinPwmChannels[_configuration.Uv365PwmPin], _configuration.PwmFrequency, CalculateDuty(0, _configuration.MaxUv365PwmDuty)); + + _whiteRelay = _gpio.OpenPin(_configuration.WhiteRelayPin, PinMode.Output, PinValue.Low); + _uv254Relay = _gpio.OpenPin(_configuration.Uv254RelayPin, PinMode.Output, PinValue.Low); + _uv365Relay = _gpio.OpenPin(_configuration.Uv365RelayPin, PinMode.Output, PinValue.Low); + _fanRelay = _gpio.OpenPin(_configuration.FanPin, PinMode.Output, PinValue.Low); + } + catch (Exception e) + { + _logger.LogCritical(e, "An exception occurred while illuminator initialization"); + throw; + } + } + + public void SetIntensity(double? white = null, double? uv365 = null, double? uv254 = null) + { + try + { + if (white is not null) _whitePwm.DutyCycle = CalculateDuty(white.Value, _configuration.MaxWhitePwmDuty); + if (uv365 is not null) _uv365Pwm.DutyCycle = CalculateDuty(uv365.Value, _configuration.MaxUv365PwmDuty); + if (uv254 is not null) _uv254Pwm.DutyCycle = CalculateDuty(uv254.Value, _configuration.MaxUv254PwmDuty); + } + catch (Exception e) + { + _logger.LogError(e, "An exception occurred while setting illuminator intensity"); + } + } + public void TurnOn() + { + try + { + _fanRelay.Write(PinValue.High); + + _whitePwm.Start(); + _uv254Pwm.Start(); + _uv365Pwm.Start(); + + _whiteRelay.Write(PinValue.High); + _uv254Relay.Write(PinValue.High); + _uv365Relay.Write(PinValue.High); + } + catch (Exception e) + { + _logger.LogError(e, "An exception occurred while turning on illuminator"); + } + } + public void TurnOff() + { + try + { + _whitePwm.Stop(); + _uv254Pwm.Stop(); + _uv365Pwm.Stop(); + + _whiteRelay.Write(PinValue.Low); + _uv254Relay.Write(PinValue.Low); + _uv365Relay.Write(PinValue.Low); + + _fanRelay.Write(PinValue.Low); + } + catch (Exception e) + { + _logger.LogError(e, "An exception occurred while turning off illuminator"); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + try + { + TurnOff(); + } + catch { } + + _whiteRelay?.Dispose(); + _uv254Relay?.Dispose(); + _uv365Relay?.Dispose(); + _fanRelay?.Dispose(); + _whitePwm?.Dispose(); + _uv254Pwm?.Dispose(); + _uv365Pwm?.Dispose(); + _gpio?.Dispose(); + } + + _disposedValue = true; + } + } + void IDisposable.Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private static void CheckPwmPin(int pin) + { + if (!PinPwmChips.TryGetValue(pin, out var _) || !PinPwmChannels.TryGetValue(pin, out var _)) + throw new ArgumentException($"Unsupported PWM pin {pin}"); + } + private static double CalculateDuty(double intensity, double maxDuty) => Math.Clamp(intensity, 0.0, 1.0) * Math.Clamp(maxDuty, 0.0, 1.0); +} diff --git a/GSS2.Core/Hardware/LightsService.cs b/GSS2.Core/Hardware/LightsService.cs new file mode 100644 index 0000000..da1e271 --- /dev/null +++ b/GSS2.Core/Hardware/LightsService.cs @@ -0,0 +1,445 @@ +using System.Drawing; +using System.Runtime.CompilerServices; +using Grpc.Core; +using Grpc.Net.Client; +using GSS2.LightsControl.CSharpClient; +using Microsoft.Extensions.Logging; + +namespace GSS2.Core.Hardware; + +public class LightsService +{ + public class LightsServiceConfiguration() + { + public static LightsServiceConfiguration Default => new LightsServiceConfiguration + { + ServerAddress = "localhost:50051" + }; + + public string ServerAddress { get; set; } = string.Empty; + } + + private readonly ILogger _logger; + private readonly LightsServiceConfiguration _configuration; + private readonly Lights.LightsClient _client; + + public LightsService(ILogger logger, LightsServiceConfiguration? configuration = null) + { + _logger = logger; + + _logger.LogInformation("Lights initialization"); + + if (configuration is null) + { + _logger.LogWarning("Lights configuration not set. Using default configuration."); + _configuration = LightsServiceConfiguration.Default; + } + else + { + _configuration = configuration; + } + + _logger.LogDebug(@$"Lights use following configuration: + ServerAddress: {_configuration.ServerAddress} + "); + + _client = new Lights.LightsClient(GrpcChannel.ForAddress(_configuration.ServerAddress)); + } + + public async Task Run(CancellationToken cancellationToken) + { + _logger.LogInformation("LightsService started"); + + while (!cancellationToken.IsCancellationRequested) + { + await PingAsync(cancellationToken); + try + { + await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); + } + catch (TaskCanceledException) + { + break; + } + } + + _logger.LogInformation("LightsService stopped"); + } + + private void SendRequest(Func method, T request, CancellationToken cancellationToken = default, [CallerMemberName] string sender = "") + { + try + { +#pragma warning disable CS8625 + method.Invoke(request, null, null, cancellationToken); ; +#pragma warning restore CS8625 + _logger.LogTrace("Request \"{}\" sent", sender); + } + catch (RpcException ex) + { + _logger.LogWarning(ex, "Request \"{}\" RPC error", sender); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Request \"{}\" Unexpected error", method); + } + } + private async Task SendRequestAsync(Func> method, T request, CancellationToken cancellationToken = default, [CallerMemberName] string sender = "") + { + try + { +#pragma warning disable CS8625 + await method.Invoke(request, null, null, cancellationToken); ; +#pragma warning restore CS8625 + _logger.LogTrace("Request \"{}\" sent", sender); + } + catch (RpcException ex) + { + _logger.LogWarning(ex, "Request \"{}\" RPC error", sender); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Request \"{}\" Unexpected error", method); + } + } + + public void Ping(CancellationToken cancellationToken = default) => SendRequest(_client.Ping, new Empty(), cancellationToken); + public void Off(CancellationToken cancellationToken = default) => SendRequest(_client.SetOff, new Empty(), cancellationToken); + public void StaticColor(double brightness, Color color, CancellationToken cancellationToken = default) => SendRequest(_client.SetStaticColor, new StaticColorRequest + { + Brightness = brightness, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public void StaticGradient2(double brightness, Color leftColor, Color rightColor, CancellationToken cancellationToken = default) => SendRequest(_client.SetStaticGradient2, new StaticGradient2Request + { + Brightness = brightness, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public void StaticGradient3(double brightness, Color leftColor, Color middleColor, Color rightColor, CancellationToken cancellationToken = default) => SendRequest(_client.SetStaticGradient3, new StaticGradient3Request + { + Brightness = brightness, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + MiddleR = middleColor.R, + MiddleG = middleColor.G, + MiddleB = middleColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public void StaticRainbow(double brightness, CancellationToken cancellationToken = default) => SendRequest(_client.SetStaticRainbow, new StaticRainbowRequest + { + Brightness = brightness + }, cancellationToken); + public void BreathingColor(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => SendRequest(_client.SetBreathingColor, new BreathingColorRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public void BreathingGradient2(double brightness, double speed, Color leftColor, Color rightColor, CancellationToken cancellationToken = default) => SendRequest(_client.SetBreathingGradient2, new BreathingGradient2Request + { + Brightness = brightness, + Speed = speed, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public void BreathingGradient3(double brightness, double speed, Color leftColor, Color middleColor, Color rightColor, CancellationToken cancellationToken = default) => SendRequest(_client.SetBreathingGradient3, new BreathingGradient3Request + { + Brightness = brightness, + Speed = speed, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + MiddleR = middleColor.R, + MiddleG = middleColor.G, + MiddleB = middleColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public void BreathingRainbow(double brightness, double speed, CancellationToken cancellationToken = default) => SendRequest(_client.SetBreathingRainbow, new BreathingRainbowRequest + { + Brightness = brightness, + Speed = speed + }, cancellationToken); + public void RunningColor(double brightness, double speed, int length, int fade, Color color, CancellationToken cancellationToken = default) => SendRequest(_client.SetRunningColor, new RunningColorRequest + { + Brightness = brightness, + Speed = speed, + Length = length, + Fade = fade, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public void RunningGradient2(double brightness, double speed, Color leftColor, Color rightColor, CancellationToken cancellationToken = default) => SendRequest(_client.SetRunningGradient2, new RunningGradient2Request + { + Brightness = brightness, + Speed = speed, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public void RunningGradient3(double brightness, double speed, Color leftColor, Color middleColor, Color rightColor, CancellationToken cancellationToken = default) => SendRequest(_client.SetRunningGradient3, new RunningGradient3Request + { + Brightness = brightness, + Speed = speed, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + MiddleR = middleColor.R, + MiddleG = middleColor.G, + MiddleB = middleColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public void RunningRainbow(double brightness, double speed, CancellationToken cancellationToken = default) => SendRequest(_client.SetRunningRainbow, new RunningRainbowRequest + { + Brightness = brightness, + Speed = speed + }, cancellationToken); + public void Snake(double brightness, double speed, int headLength, int tailLength, Color headColor, Color tailColor, CancellationToken cancellationToken = default) => SendRequest(_client.SetSnake, new SnakeRequest + { + Brightness = brightness, + Speed = speed, + HeadLength = headLength, + TailLength = tailLength, + HeadR = headColor.R, + HeadG = headColor.G, + HeadB = headColor.B, + TailR = tailColor.R, + TailG = tailColor.G, + TailB = tailColor.B + }, cancellationToken); + public void Fire(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => SendRequest(_client.SetFire, new FireRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public void Snowfall(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => SendRequest(_client.SetSnowfall, new SnowfallRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public void Flash(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => SendRequest(_client.SetFlash, new FlashRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public void Load(double brightness, double speed, double percent, double lastPercent, Color color, CancellationToken cancellationToken = default) => SendRequest(_client.SetLoad, new LoadRequest + { + Brightness = brightness, + Speed = speed, + Percent = percent, + LastPercent = lastPercent, + R = color.R, + G = color.G, + B = color.B, + }, cancellationToken); + public void CpuLoad(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => SendRequest(_client.SetCpuLoad, new CpuLoadRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + + public async Task PingAsync(CancellationToken cancellationToken = default) => await SendRequestAsync(_client.PingAsync, new Empty(), cancellationToken); + public async Task OffAsync(CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetOffAsync, new Empty(), cancellationToken); + public async Task StaticColorAsync(double brightness, Color color, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetStaticColorAsync, new StaticColorRequest + { + Brightness = brightness, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public async Task StaticGradient2Async(double brightness, Color leftColor, Color rightColor, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetStaticGradient2Async, new StaticGradient2Request + { + Brightness = brightness, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public async Task StaticGradient3Async(double brightness, Color leftColor, Color middleColor, Color rightColor, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetStaticGradient3Async, new StaticGradient3Request + { + Brightness = brightness, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + MiddleR = middleColor.R, + MiddleG = middleColor.G, + MiddleB = middleColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public async Task StaticRainbowAsync(double brightness, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetStaticRainbowAsync, new StaticRainbowRequest + { + Brightness = brightness + }, cancellationToken); + public async Task BreathingColorAsync(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetBreathingColorAsync, new BreathingColorRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public async Task BreathingGradient2Async(double brightness, double speed, Color leftColor, Color rightColor, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetBreathingGradient2Async, new BreathingGradient2Request + { + Brightness = brightness, + Speed = speed, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public async Task BreathingGradient3Async(double brightness, double speed, Color leftColor, Color middleColor, Color rightColor, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetBreathingGradient3Async, new BreathingGradient3Request + { + Brightness = brightness, + Speed = speed, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + MiddleR = middleColor.R, + MiddleG = middleColor.G, + MiddleB = middleColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public async Task BreathingRainbowAsync(double brightness, double speed, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetBreathingRainbowAsync, new BreathingRainbowRequest + { + Brightness = brightness, + Speed = speed + }, cancellationToken); + public async Task RunningColorAsync(double brightness, double speed, int length, int fade, Color color, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetRunningColorAsync, new RunningColorRequest + { + Brightness = brightness, + Speed = speed, + Length = length, + Fade = fade, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public async Task RunningGradient2Async(double brightness, double speed, Color leftColor, Color rightColor, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetRunningGradient2Async, new RunningGradient2Request + { + Brightness = brightness, + Speed = speed, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public async Task RunningGradient3Async(double brightness, double speed, Color leftColor, Color middleColor, Color rightColor, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetRunningGradient3Async, new RunningGradient3Request + { + Brightness = brightness, + Speed = speed, + LeftR = leftColor.R, + LeftG = leftColor.G, + LeftB = leftColor.B, + MiddleR = middleColor.R, + MiddleG = middleColor.G, + MiddleB = middleColor.B, + RightR = rightColor.R, + RightG = rightColor.G, + RightB = rightColor.B + }, cancellationToken); + public async Task RunningRainbowAsync(double brightness, double speed, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetRunningRainbowAsync, new RunningRainbowRequest + { + Brightness = brightness, + Speed = speed + }, cancellationToken); + public async Task SnakeAsync(double brightness, double speed, int headLength, int tailLength, Color headColor, Color tailColor, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetSnakeAsync, new SnakeRequest + { + Brightness = brightness, + Speed = speed, + HeadLength = headLength, + TailLength = tailLength, + HeadR = headColor.R, + HeadG = headColor.G, + HeadB = headColor.B, + TailR = tailColor.R, + TailG = tailColor.G, + TailB = tailColor.B + }, cancellationToken); + public async Task FireAsync(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetFireAsync, new FireRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public async Task SnowfallAsync(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetSnowfallAsync, new SnowfallRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public async Task FlashAsync(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetFlashAsync, new FlashRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); + public async Task LoadAsync(double brightness, double speed, double percent, double lastPercent, Color color, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetLoadAsync, new LoadRequest + { + Brightness = brightness, + Speed = speed, + Percent = percent, + LastPercent = lastPercent, + R = color.R, + G = color.G, + B = color.B, + }, cancellationToken); + public async Task CpuLoadAsync(double brightness, double speed, Color color, CancellationToken cancellationToken = default) => await SendRequestAsync(_client.SetCpuLoadAsync, new CpuLoadRequest + { + Brightness = brightness, + Speed = speed, + R = color.R, + G = color.G, + B = color.B + }, cancellationToken); +} diff --git a/GSS2.Core/Hardware/TemperatureHumidityService.cs b/GSS2.Core/Hardware/TemperatureHumidityService.cs new file mode 100644 index 0000000..d6a8e44 --- /dev/null +++ b/GSS2.Core/Hardware/TemperatureHumidityService.cs @@ -0,0 +1,174 @@ +using System.Device.I2c; +using Microsoft.Extensions.Logging; + +namespace GSS2.Core.Hardware; + +public class TemperatureHumidityService : IDisposable +{ + public class TemperatureHumidityServiceConfiguration + { + public static TemperatureHumidityServiceConfiguration Default => new TemperatureHumidityServiceConfiguration + { + Bus = 0, + Address = 0x44 + }; + + public int Bus { get; set; } + public int Address { get; set; } + } + private bool _disposedValue; + private readonly ILogger _logger; + private readonly TemperatureHumidityServiceConfiguration _configuration; + + private readonly I2cDevice _sensor; + + public TemperatureHumidityService(ILogger logger, TemperatureHumidityServiceConfiguration? configuration = null) + { + _logger = logger; + + _logger.LogInformation("Temperature and humidity sensor initialization"); + + if (configuration is null) + { + _logger.LogWarning("Temperature and humidity sensor configuration not set. Using default configuration."); + _configuration = TemperatureHumidityServiceConfiguration.Default; + } + else + { + _configuration = configuration; + } + + _logger.LogDebug(@$"Temperature and humidity sensor use following configuration: + Bus: {_configuration.Bus} + Address: {_configuration.Address} + "); + + try + { + _sensor = I2cDevice.Create(new I2cConnectionSettings(_configuration.Bus, _configuration.Address)); + } + catch (Exception e) + { + _logger.LogCritical(e, "An exception occurred while temperature and humidity sensor initialization"); + throw; + } + } + + public async Task<(double temperature, double relativeHumidity)> MeasureAsync(int measurementsCount = 1, CancellationToken cancellationToken = default) + { + // В соответствии с даташитом на SHT45-AD1B-R2 псевдокод измерения с высокой точностью следующий: + // i2c_write(i2c_addr=0x44, tx_bytes=[0xFD]) + // wait_seconds(0.01) + // rx_bytes = i2c_read(i2c_addr=0x44, number_of_bytes=6) + // t_ticks = rx_bytes[0] * 256 + rx_bytes[1] + // checksum_t = rx_bytes[2] + // rh_ticks = rx_bytes[3] * 256 + rx_bytes[4] + // checksum_rh = rx_bytes[5] + // t_degC = -45 + 175 * t_ticks/65535 + // rh_pRH = -6 + 125 * rh_ticks/65535 + // if (rh_pRH > 100): + // rh_pRH = 100 + // if (rh_pRH < 0): + // rh_pRH = 0 + + if (measurementsCount <= 0) + throw new ArgumentOutOfRangeException(nameof(measurementsCount)); + + double sumTemperature = 0; + double sumRelativeHumidity = 0; + int successMeasurementsCount = 0; + + Span command = stackalloc byte[] { 0xFD }; + Span data = stackalloc byte[6]; + + for (int i = 0; i < measurementsCount; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + // Отправляем команду + _sensor.Write(command); + // 10 мс ожидания + await Task.Delay(10, cancellationToken); + // Читаем данные + _sensor.Read(data); + + // Распаковываем данные [2 * 8-bit T-data; 8-bit CRC; 2 * 8-bit RH-data; 8-bit CRC] + ushort temperatureTicks = BitConverter.ToUInt16(data.ToArray(), 0); + byte temperatureChecksum = data[2]; + int relativeHumidityTicks = BitConverter.ToUInt16(data.ToArray(), 3); + byte relativeHumidityChecksum = data[5]; + + // Проверка по CRC + if (!CheckCrc(data.Slice(0, 2), temperatureChecksum) || + !CheckCrc(data.Slice(3, 2), relativeHumidityChecksum)) + { + _logger.LogWarning("Temperature and humidity sensor CRC error"); + continue; + } + + // Пересчёт + double temperature = -45 + 175 * (temperatureTicks / 65535.0); + double relativeHumidity = -6 + 125 * (relativeHumidityTicks / 65535.0); + relativeHumidity = Math.Clamp(relativeHumidity, 0, 100); + + // Сумма для усреднения + sumTemperature += temperature; + sumRelativeHumidity += relativeHumidity; + successMeasurementsCount++; + } + catch (Exception e) + { + _logger.LogWarning(e, "Temperature and humidity sensor not available on I2C bus"); + } + } + + if (successMeasurementsCount == 0) + { + _logger.LogError("Temperature and humidity sensor: no successful measurements"); + return (double.NaN, double.NaN); + } + + return (sumTemperature / successMeasurementsCount, sumRelativeHumidity / successMeasurementsCount); + } + + private static bool CheckCrc(Span data, byte expectedCrc) + { + byte crc = 0xFF; + foreach (var b in data) + { + crc ^= b; + for (int i = 0; i < 8; i++) + { + if ((crc & 0x80) != 0) + crc = (byte)((crc << 1) ^ 0x31); + else + crc <<= 1; + } + } + return crc == expectedCrc; + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + try + { + _sensor?.Dispose(); + } + catch { } + } + + _disposedValue = true; + } + } + void IDisposable.Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/GSS2.LightsControl/CSharpClient/GSS2.LightsControl.CSharpClient.csproj b/GSS2.LightsControl/CSharpClient/GSS2.LightsControl.CSharpClient.csproj new file mode 100644 index 0000000..8b20a5c --- /dev/null +++ b/GSS2.LightsControl/CSharpClient/GSS2.LightsControl.CSharpClient.csproj @@ -0,0 +1,22 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/GSS2.LightsControl/Server/.env b/GSS2.LightsControl/Server/.env new file mode 100644 index 0000000..5348c5a --- /dev/null +++ b/GSS2.LightsControl/Server/.env @@ -0,0 +1,10 @@ +LED_COUNT=9 +LED_PIN=23 + +GAMMA=2.2 +TIMEOUT=5 + +IDLE_BRIGHTNESS=0.30 +IDLE_R=0 +IDLE_G=96 +IDLE_B=175 diff --git a/GSS2.LightsControl/Server/.gitignore b/GSS2.LightsControl/Server/.gitignore new file mode 100644 index 0000000..1e18f27 --- /dev/null +++ b/GSS2.LightsControl/Server/.gitignore @@ -0,0 +1 @@ +!.env \ No newline at end of file diff --git a/GSS2.LightsControl/Server/lights_deamon.py b/GSS2.LightsControl/Server/lights_deamon.py new file mode 100644 index 0000000..07d266d --- /dev/null +++ b/GSS2.LightsControl/Server/lights_deamon.py @@ -0,0 +1,932 @@ +import os, time, threading, colorsys, math, random, datetime +from abc import ABC, abstractmethod +from typing import List, Tuple +from concurrent import futures +from dotenv import load_dotenv +import grpc +import board +import neopixel +import psutil + +import lights_pb2 +import lights_pb2_grpc + +Color = Tuple[int, int , int] + +load_dotenv() +def get_env(name) -> str: + value = os.getenv(name) + if value is None: + raise RuntimeError(f"Environment variable not found: {name}") + return value +def get_env_int(name) -> int: + return int(get_env(name)) +def get_env_float(name) -> float: + return float(get_env(name)) +def get_env_str(name) -> str: + return get_env(name) + +def hasattr_all(obj: object, names: List[str]) -> bool: + return all([hasattr(obj, name) for name in names]) + +LED_COUNT = get_env_int("LED_COUNT") +LED_PIN_NUM = get_env_int("LED_PIN") +LED_PIN = getattr(board, f"D{LED_PIN_NUM}") + +GAMMA = get_env_float("GAMMA") +TIMEOUT = get_env_float("TIMEOUT") + +IDLE_SPEED = get_env_float("IDLE_SPEED") +IDLE_BRIGHTNESS = get_env_float("IDLE_BRIGHTNESS") +IDLE_COLOR = ( + get_env_int("IDLE_R"), + get_env_int("IDLE_G"), + get_env_int("IDLE_B") +) + +class Animation(ABC): + @abstractmethod + def get_pixels(self, time: float, count: int) -> List[Color]: + raise NotImplementedError() + +class OffAnimation(Animation): + def get_pixels(self, time: float, count: int) -> List[Color]: + return [(0, 0, 0)] * count + +class StaticColorAnimation(Animation): + def __init__(self, color: Color): + self._color = color + + def get_pixels(self, time: float, count: int) -> List[Color]: + return [self._color] * count + +class StaticGradient2Animation(Animation): + def __init__(self, left_color: Color, right_color: Color): + self._left_color = left_color + self._right_color = right_color + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count <= 0: + return [] + if count == 1: + return [self._left_color] + + out = [] + (r1, g1, b1) = self._left_color + (r2, g2, b2) = self._right_color + + for i in range(count): + pos = i / (count - 1) + r = int(r1 * (1 - pos) + r2 * pos) + g = int(g1 * (1 - pos) + g2 * pos) + b = int(b1 * (1 - pos) + b2 * pos) + out.append((r, g, b)) + + return out + +class StaticGradient3Animation(Animation): + def __init__(self, left_color: Color, middle_color: Color, right_color: Color): + self._left_color = left_color + self._middle_color = middle_color + self._right_color = right_color + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count <= 0: + return [] + if count == 1: + return [self._left_color] + + out = [] + (c1r, c1g, c1b) = self._left_color + (c2r, c2g, c2b) = self._middle_color + (c3r, c3g, c3b) = self._right_color + + for i in range(count): + pos = i / (count - 1) + + if pos < 0.5: + f = pos * 2 + r = int(c1r * (1 - f) + c2r * f) + g = int(c1g * (1 - f) + c2g * f) + b = int(c1b * (1 - f) + c2b * f) + else: + f = (pos - 0.5) * 2 + r = int(c2r * (1 - f) + c3r * f) + g = int(c2g * (1 - f) + c3g * f) + b = int(c2b * (1 - f) + c3b * f) + + out.append((r, g, b)) + + return out + +class StaticRainbowAnimation(Animation): + def get_pixels(self, time: float, count: int) -> List[Color]: + if count == 0: + return [] + if count == 1: + return [(255, 0, 0)] + + out = [] + + for i in range(count): + hue = i / count + r, g, b = colorsys.hsv_to_rgb(hue, 1.0, 1.0) + out.append((int(r * 255), int(g * 255), int(b * 255))) + + return out + +class BreathingColorAnimation(Animation): + def __init__(self, speed: float, color: Color): + self._speed = speed + self._color = color + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count <= 0: + return [] + + intensity = (math.sin(time * self._speed) + 1) / 2 + + r = int(self._color[0] * intensity) + g = int(self._color[1] * intensity) + b = int(self._color[2] * intensity) + + return [(r, g, b)] * count + +class BreathingGradient2Animation(Animation): + def __init__(self, speed: float, left_color: Color, right_color: Color): + self._speed = speed + self._left_color = left_color + self._right_color = right_color + + def get_pixels(self, time: float, count: int) -> List[Color]: + intensity = (math.sin(time * self._speed) + 1) / 2 + + if count <= 0: + return [] + if count == 1: + return [( + int(self._left_color[0] * intensity), + int(self._left_color[1] * intensity), + int(self._left_color[2] * intensity) + )] + + out = [] + (r1, g1, b1) = self._left_color + (r2, g2, b2) = self._right_color + + for i in range(count): + pos = i / (count - 1) + r = int((r1 * (1 - pos) + r2 * pos) * intensity) + g = int((g1 * (1 - pos) + g2 * pos) * intensity) + b = int((b1 * (1 - pos) + b2 * pos) * intensity) + out.append((r, g, b)) + + return out + +class BreathingGradient3Animation(Animation): + def __init__(self, speed: float, left_color: Color, middle_color: Color, right_color: Color): + self._speed = speed + self._left_color = left_color + self._middle_color = middle_color + self._right_color = right_color + + def get_pixels(self, time: float, count: int) -> List[Color]: + intensity = (math.sin(time * self._speed) + 1) / 2 + + if count == 0: + return [] + if count == 1: + return [( + int(self._left_color[0] * intensity), + int(self._left_color[1] * intensity), + int(self._left_color[2] * intensity) + )] + + out = [] + (c1r, c1g, c1b) = self._left_color + (c2r, c2g, c2b) = self._middle_color + (c3r, c3g, c3b) = self._right_color + + for i in range(count): + pos = i / (count - 1) + + if pos < 0.5: + f = pos * 2 + r = int((c1r * (1 - f) + c2r * f) * intensity) + g = int((c1g * (1 - f) + c2g * f) * intensity) + b = int((c1b * (1 - f) + c2b * f) * intensity) + else: + f = (pos - 0.5) * 2 + r = int((c2r * (1 - f) + c3r * f) * intensity) + g = int((c2g * (1 - f) + c3g * f) * intensity) + b = int((c2b * (1 - f) + c3b * f) * intensity) + + out.append((r, g, b)) + + return out + +class BreathingRainbowAnimation(Animation): + def __init__(self, speed: float): + self._speed = speed + + def get_pixels(self, time: float, count: int) -> List[Color]: + intensity = (math.sin(time * self._speed) + 1) / 2 + + if count == 0: + return [] + if count <= 1: + return [(255 * intensity, 0, 0)] + + out = [] + + for i in range(count): + hue = i / count + r, g, b = colorsys.hsv_to_rgb(hue, 1.0, 1.0) + out.append((int(r * 255 * intensity), int(g * 255 * intensity), int(b * 255 * intensity))) + + return out + +class RunningColorAnimation(Animation): + def __init__(self, speed: float, length: int, fade: int, color: Color): + self._speed = speed + self._length = length + self._fade = fade + self._color = color + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count <= 0: + return [] + + out = [] + + center = (time * self._speed) % count + + for i in range(count): + raw_dist = abs(i - center) + dist = min(raw_dist, count - raw_dist) + + if dist <= self._length: + intensity = 1.0 + elif dist <= self._length + self._fade: + fade_pos = dist - self._length + intensity = 1.0 - fade_pos / self._fade + else: + intensity = 0.0 + + r = int(self._color[0] * intensity) + g = int(self._color[1] * intensity) + b = int(self._color[2] * intensity) + out.append((r, g, b)) + + return out + +class RunningGradient2Animation(Animation): + def __init__(self, speed: float, left_color: Color, right_color: Color): + self._speed = speed + self._left_color = left_color + self._right_color = right_color + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count <= 0: + return [] + + (r1, g1, b1) = self._left_color + (r2, g2, b2) = self._right_color + + shift = (time * self._speed) / (count - 1) + out = [] + + for i in range(count): + pos = (i / (count - 1) + shift) % 1.0 + + r = int(r1 * (1 - pos) + r2 * pos) + g = int(g1 * (1 - pos) + g2 * pos) + b = int(b1 * (1 - pos) + b2 * pos) + out.append((r, g, b)) + + return out + +class RunningGradient3Animation(Animation): + def __init__(self, speed: float, left_color: Color, middle_color: Color, right_color: Color): + self._speed = speed + self._left_color = left_color + self._middle_color = middle_color + self._right_color = right_color + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count <= 0: + return [] + + (c1r, c1g, c1b) = self._left_color + (c2r, c2g, c2b) = self._middle_color + (c3r, c3g, c3b) = self._right_color + + shift = (time * self._speed) / (count - 1) + out = [] + + for i in range(count): + pos = (i / (count - 1) + shift) % 1.0 + + if pos < 0.5: + f = pos * 2 + r = int(c1r * (1 - f) + c2r * f) + g = int(c1g * (1 - f) + c2g * f) + b = int(c1b * (1 - f) + c2b * f) + else: + f = (pos - 0.5) * 2 + r = int(c2r * (1 - f) + c3r * f) + g = int(c2g * (1 - f) + c3g * f) + b = int(c2b * (1 - f) + c3b * f) + + out.append((r, g, b)) + + return out + +class RunningRainbowAnimation(Animation): + def __init__(self, speed: float): + self._speed = speed + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count <= 0: + return [] + + shift = (time * self._speed) + out = [] + + for i in range(count): + hue = (i + shift) % 1.0 + r, g, b = colorsys.hsv_to_rgb(hue, 1.0, 1.0) + out.append((int(r * 255), int(g * 255), int(b * 255))) + + return out + +class SnakeAnimation(Animation): + def __init__(self, speed: float, head_length: int, tail_length: int, head_color: Color, tail_color: Color): + self._speed = speed + self._head_length = head_length + self._tail_length = tail_length + self._head_color = head_color + self._tail_color = tail_color + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count == 0: + return [] + + out = [(0, 0, 0)] * count + head_pos = (time * self._speed) % count + + for i in range(count): + dist = (i - head_pos) % count + + if 0 <= dist < self._head_length: + factor = 1.0 + elif self._head_length <= dist < self._head_length + self._tail_length: + t = dist - self._head_length + factor = max(0.0, 1.0 - t / self._tail_length) + else: + continue + + (hr, hg, hb) = self._head_color + (tr, tg, tb) = self._tail_color + + r = int(hr * factor + tr * (1 - factor)) + g = int(hg * factor + tg * (1 - factor)) + b = int(hb * factor + tb * (1 - factor)) + out[i] = (r, g, b) + + return out + +class FireAnimation(Animation): + def __init__(self, speed: float, color: Color): + self._speed = speed + self._color = color + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count == 0: + return [] + + (r0, g0, b0) = self._color + out = [] + + for i in range(count): + flicker = math.sin(i * 0.25 + time * self._speed) + flicker = 0.5 + 0.5 * flicker # -> [0..1] + + flicker *= 0.7 + 0.3 * random.random() + + r = int(r0 * flicker) + g = int(g0 * flicker) + b = int(b0 * flicker) + out.append((r, g, b)) + + return out + +class SnowfallAnimation(Animation): + def __init__(self, speed: float, color: Color): + self._speed = speed + self._color = color + self._state = [] + + def get_pixels(self, time: float, count: int) -> List[Color]: + if len(self._state) != count: + self._state = [0.0] * count + + (r0, g0, b0) = self._color + out = [] + + for i in range(count): + if self._state[i] <= 0 and random.random() < self._speed * 0.02: + self._state[i] = 1.0 + + self._state[i] = max(0.0, self._state[i] - 0.02 * self._speed) + + brightness = self._state[i] + + r = int(r0 * brightness) + g = int(g0 * brightness) + b = int(b0 * brightness) + out.append((r, g, b)) + + return out + +class FlashAnimation(Animation): + def __init__(self, speed: float, color: Color): + self._speed = speed + self._color = color + self._start_time = None + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count == 0: + return [] + + if self._start_time == None: + self._start_time = time + + (r0, g0, b0) = self._color + + brightness = math.exp(-(time - self._start_time) * self._speed) + brightness = max(0.0, min(1.0, brightness)) + + r = int(r0 * brightness) + g = int(g0 * brightness) + b = int(b0 * brightness) + + return [(r, g, b)] * count + +class LoadAnimation(Animation): + def __init__(self, speed: float, percent: float, last_percent: float, color: Color): + self._speed = speed + self._percent = max(0.0, min(100.0, percent)) + self._last_percent = max(0.0, min(100.0, last_percent)) + self._color = color + self._last_time = None + + def get_pixels(self, time: float, count: int) -> List[Color]: + if count <= 0: + return [] + + if self._last_time == None: + self._last_time = time + dt = time - self._last_time + self._last_time = time + + smoothed_percent = self._last_percent + (self._percent - self._last_percent) * min(1.0, self._speed * dt) + + (r0, g0, b0) = self._color + + filled_float = (smoothed_percent / 100.0) * count + filled = int(filled_float) + fractional = filled_float - filled + + out = [] + + for i in range(count): + if i < filled: + out.append((r0, g0, b0)) + elif i == filled and fractional > 0: + r = int(r0 * fractional) + g = int(g0 * fractional) + b = int(b0 * fractional) + out.append((r, g, b)) + else: + out.append((0, 0, 0)) + + # фиксируем новое сглаженное значение как last_percent + self._last_percent = smoothed_percent + + return out + +class CpuLoadAnimation(LoadAnimation): + def __init__(self, speed: float, color: Color): + super().__init__(speed, 0.0, 0.0, color) + + def get_pixels(self, time: float, count: int) -> List[Color]: + self._percent = psutil.cpu_percent(interval=0.0) + pixels = super().get_pixels(time, count) + return pixels + +class IdleAnimationUsual(RunningColorAnimation): + def __init__(self): + super().__init__(IDLE_SPEED, 1, 4, IDLE_COLOR) + + def get_pixels(self, time: float, count: int) -> List[Color]: + return super().get_pixels(time, count) + +class IdleAnimationNewYear(RunningRainbowAnimation): # Новогодняя пасхалка + def __init__(self): + super().__init__(IDLE_SPEED / 10) + + def get_pixels(self, time: float, count: int) -> List[Color]: + return super().get_pixels(time, count) + +today = datetime.datetime.now() +if (today.month == 12 and today.day == 31) or (today.month == 1 and today.day == 1): + IdleAnimation = IdleAnimationNewYear +else: + IdleAnimation = IdleAnimationUsual + +class LightsDaemon(lights_pb2_grpc.LightsServicer): + def __init__(self): + self._running = True + self._lock = threading.Lock() + with self._lock: + self._pixels = neopixel.NeoPixel( + LED_PIN, + LED_COUNT, + brightness=IDLE_BRIGHTNESS, + auto_write=False + ) + + self._gamma = [int((i / 255) ** GAMMA * 255 + 0.5) for i in range(256)] + self._color_clamp = lambda x: max(0, min(255, x)) + + self._control_mode = False + self._last_ping = 0 + + self._animation = IdleAnimation() + + self._worker = threading.Thread(target=self.loop, daemon=True) + self._worker.start() + + def stop(self): + self._running = False + self._worker.join() + + for i in range(len(self._pixels)): + self._pixels[i] = (0, 0, 0) + self._pixels.show() + + def loop(self): + while self._running: + with self._lock: + if self._control_mode and time.time() - self._last_ping > TIMEOUT: + self._control_mode = False + self._pixels.brightness = IDLE_BRIGHTNESS + self._animation = IdleAnimation() + self._run_animation() + time.sleep(0.02) + + def _run_animation(self): + new_colors = self._animation.get_pixels(time.time(), LED_COUNT) + for i in range(LED_COUNT): + self._pixels[i] = new_colors[i] + self._pixels.show() + + def _update_last_ping(self): + with self._lock: + self._control_mode = True + self._last_ping = time.time() + + def Ping(self, request, context): + self._update_last_ping() + return lights_pb2.Empty() + + def SetOff(self, request, context): + self._update_last_ping() + with self._lock: + self._control_mode = True + self._animation = OffAnimation() + return lights_pb2.Empty() + + def SetStaticColor(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "r", "g", "b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + color = ( + self._gamma[self._color_clamp(request.r)], + self._gamma[self._color_clamp(request.g)], + self._gamma[self._color_clamp(request.b)], + ) + self._animation = StaticColorAnimation(color) + return lights_pb2.Empty() + + def SetStaticGradient2(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "left_r", "left_g", "left_b", "right_r", "right_g", "right_b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + left_color = ( + self._gamma[self._color_clamp(request.left_r)], + self._gamma[self._color_clamp(request.left_g)], + self._gamma[self._color_clamp(request.left_b)], + ) + right_color = ( + self._gamma[self._color_clamp(request.right_r)], + self._gamma[self._color_clamp(request.right_g)], + self._gamma[self._color_clamp(request.right_b)], + ) + self._animation = StaticGradient2Animation(left_color, right_color) + return lights_pb2.Empty() + + def SetStaticGradient3(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "left_r", "left_g", "left_b", "middle_r", "middle_g", "middle_b", "right_r", "right_g", "right_b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + left_color = ( + self._gamma[self._color_clamp(request.left_r)], + self._gamma[self._color_clamp(request.left_g)], + self._gamma[self._color_clamp(request.left_b)], + ) + middle_color = ( + self._gamma[self._color_clamp(request.middle_r)], + self._gamma[self._color_clamp(request.middle_g)], + self._gamma[self._color_clamp(request.middle_b)], + ) + right_color = ( + self._gamma[self._color_clamp(request.right_r)], + self._gamma[self._color_clamp(request.right_g)], + self._gamma[self._color_clamp(request.right_b)], + ) + self._animation = StaticGradient3Animation(left_color, middle_color, right_color) + return lights_pb2.Empty() + + def SetStaticRainbow(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + self._animation = StaticRainbowAnimation() + return lights_pb2.Empty() + + def SetBreathingColor(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "r", "g", "b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + color = ( + self._gamma[self._color_clamp(request.r)], + self._gamma[self._color_clamp(request.g)], + self._gamma[self._color_clamp(request.b)], + ) + self._animation = BreathingColorAnimation(request.speed, color) + return lights_pb2.Empty() + + def SetBreathingGradient2(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "left_r", "left_g", "left_b", "right_r", "right_g", "right_b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + left_color = ( + self._gamma[self._color_clamp(request.left_r)], + self._gamma[self._color_clamp(request.left_g)], + self._gamma[self._color_clamp(request.left_b)], + ) + right_color = ( + self._gamma[self._color_clamp(request.right_r)], + self._gamma[self._color_clamp(request.right_g)], + self._gamma[self._color_clamp(request.right_b)], + ) + self._animation = BreathingGradient2Animation(request.speed, left_color, right_color) + return lights_pb2.Empty() + + def SetBreathingGradient3(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "left_r", "left_g", "left_b", "middle_r", "middle_g", "middle_b", "right_r", "right_g", "right_b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + left_color = ( + self._gamma[self._color_clamp(request.left_r)], + self._gamma[self._color_clamp(request.left_g)], + self._gamma[self._color_clamp(request.left_b)], + ) + middle_color = ( + self._gamma[self._color_clamp(request.middle_r)], + self._gamma[self._color_clamp(request.middle_g)], + self._gamma[self._color_clamp(request.middle_b)], + ) + right_color = ( + self._gamma[self._color_clamp(request.right_r)], + self._gamma[self._color_clamp(request.right_g)], + self._gamma[self._color_clamp(request.right_b)], + ) + self._animation = BreathingGradient3Animation(request.speed, left_color, middle_color, right_color) + return lights_pb2.Empty() + + def SetBreathingRainbow(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + self._animation = BreathingRainbowAnimation(request.speed) + return lights_pb2.Empty() + + def SetRunningColor(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "length", "fade", "r", "g", "b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + color = ( + self._gamma[self._color_clamp(request.r)], + self._gamma[self._color_clamp(request.g)], + self._gamma[self._color_clamp(request.b)], + ) + self._animation = RunningColorAnimation(request.speed, request.length, request.fade, color) + return lights_pb2.Empty() + + def SetRunningGradient2(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "left_r", "left_g", "left_b", "right_r", "right_g", "right_b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + left_color = ( + self._gamma[self._color_clamp(request.left_r)], + self._gamma[self._color_clamp(request.left_g)], + self._gamma[self._color_clamp(request.left_b)], + ) + right_color = ( + self._gamma[self._color_clamp(request.right_r)], + self._gamma[self._color_clamp(request.right_g)], + self._gamma[self._color_clamp(request.right_b)], + ) + self._animation = RunningGradient2Animation(request.speed, left_color, right_color) + return lights_pb2.Empty() + + def SetRunningGradient3(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "left_r", "left_g", "left_b", "middle_r", "middle_g", "middle_b", "right_r", "right_g", "right_b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + left_color = ( + self._gamma[self._color_clamp(request.left_r)], + self._gamma[self._color_clamp(request.left_g)], + self._gamma[self._color_clamp(request.left_b)], + ) + middle_color = ( + self._gamma[self._color_clamp(request.middle_r)], + self._gamma[self._color_clamp(request.middle_g)], + self._gamma[self._color_clamp(request.middle_b)], + ) + right_color = ( + self._gamma[self._color_clamp(request.right_r)], + self._gamma[self._color_clamp(request.right_g)], + self._gamma[self._color_clamp(request.right_b)], + ) + self._animation = RunningGradient3Animation(request.speed, left_color, middle_color, right_color) + return lights_pb2.Empty() + + def SetRunningRainbow(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + self._animation = RunningRainbowAnimation(request.speed) + return lights_pb2.Empty() + + def SetSnake(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "head_length", "tail_length", "head_r", "head_g", "head_b", "tail_r", "tail_g", "tail_b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + head_color = ( + self._gamma[self._color_clamp(request.head_r)], + self._gamma[self._color_clamp(request.head_g)], + self._gamma[self._color_clamp(request.head_b)], + ) + tail_color = ( + self._gamma[self._color_clamp(request.tail_r)], + self._gamma[self._color_clamp(request.tail_g)], + self._gamma[self._color_clamp(request.tail_b)], + ) + self._animation = SnakeAnimation(request.speed, request.head_length, request.tail_length, head_color, tail_color) + return lights_pb2.Empty() + + def SetFire(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "r", "g", "b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + color = ( + self._gamma[self._color_clamp(request.r)], + self._gamma[self._color_clamp(request.g)], + self._gamma[self._color_clamp(request.b)], + ) + self._animation = FireAnimation(request.speed, color) + return lights_pb2.Empty() + + def SetSnowfall(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "r", "g", "b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + color = ( + self._gamma[self._color_clamp(request.r)], + self._gamma[self._color_clamp(request.g)], + self._gamma[self._color_clamp(request.b)], + ) + self._animation = SnowfallAnimation(request.speed, color) + return lights_pb2.Empty() + + def SetFlash(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "r", "g", "b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + color = ( + self._gamma[self._color_clamp(request.r)], + self._gamma[self._color_clamp(request.g)], + self._gamma[self._color_clamp(request.b)], + ) + self._animation = FlashAnimation(request.speed, color) + return lights_pb2.Empty() + + def SetLoad(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "percent", "last_percent", "r", "g", "b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + color = ( + self._gamma[self._color_clamp(request.r)], + self._gamma[self._color_clamp(request.g)], + self._gamma[self._color_clamp(request.b)], + ) + self._animation = LoadAnimation(request.speed, request.percent, request.last_percent, color) + return lights_pb2.Empty() + + def SetCpuLoad(self, request, context): + self._update_last_ping() + if not hasattr_all(request, ["brightness", "speed", "r", "g", "b"]): + return lights_pb2.Empty() + with self._lock: + self._control_mode = True + self._pixels.brightness = request.brightness + color = ( + self._gamma[self._color_clamp(request.r)], + self._gamma[self._color_clamp(request.g)], + self._gamma[self._color_clamp(request.b)], + ) + self._animation = CpuLoadAnimation(request.speed, color) + return lights_pb2.Empty() + +def serve(): + daemon = LightsDaemon() + server = grpc.server(futures.ThreadPoolExecutor(max_workers=1)) + lights_pb2_grpc.add_LightsServicer_to_server(daemon, server) + server.add_insecure_port('localhost:50051') + server.start() + print("Lights daemon started on port 50051") + try: + server.wait_for_termination() + except KeyboardInterrupt: + print("Shutting down") + server.stop(0) + daemon.stop() + print("Lights daemon stopped") + +if __name__ == "__main__": + serve() diff --git a/GSS2.LightsControl/Server/lights_pb2.py b/GSS2.LightsControl/Server/lights_pb2.py new file mode 100644 index 0000000..db90086 --- /dev/null +++ b/GSS2.LightsControl/Server/lights_pb2.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: lights.proto +# Protobuf Python Version: 6.31.1 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 31, + 1, + '', + 'lights.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0clights.proto\x12\x12GSS2.LightsControl\"\x07\n\x05\x45mpty\"I\n\x12StaticColorRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\t\n\x01r\x18\x02 \x01(\x05\x12\t\n\x01g\x18\x03 \x01(\x05\x12\t\n\x01\x62\x18\x04 \x01(\x05\"\x8f\x01\n\x16StaticGradient2Request\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\x0e\n\x06left_r\x18\x02 \x01(\x05\x12\x0e\n\x06left_g\x18\x03 \x01(\x05\x12\x0e\n\x06left_b\x18\x04 \x01(\x05\x12\x0f\n\x07right_r\x18\x05 \x01(\x05\x12\x0f\n\x07right_g\x18\x06 \x01(\x05\x12\x0f\n\x07right_b\x18\x07 \x01(\x05\"\xc5\x01\n\x16StaticGradient3Request\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\x0e\n\x06left_r\x18\x02 \x01(\x05\x12\x0e\n\x06left_g\x18\x03 \x01(\x05\x12\x0e\n\x06left_b\x18\x04 \x01(\x05\x12\x10\n\x08middle_r\x18\x05 \x01(\x05\x12\x10\n\x08middle_g\x18\x06 \x01(\x05\x12\x10\n\x08middle_b\x18\x07 \x01(\x05\x12\x0f\n\x07right_r\x18\x08 \x01(\x05\x12\x0f\n\x07right_g\x18\t \x01(\x05\x12\x0f\n\x07right_b\x18\n \x01(\x05\"*\n\x14StaticRainbowRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\"[\n\x15\x42reathingColorRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\t\n\x01r\x18\x03 \x01(\x05\x12\t\n\x01g\x18\x04 \x01(\x05\x12\t\n\x01\x62\x18\x05 \x01(\x05\"\xa1\x01\n\x19\x42reathingGradient2Request\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\x0e\n\x06left_r\x18\x03 \x01(\x05\x12\x0e\n\x06left_g\x18\x04 \x01(\x05\x12\x0e\n\x06left_b\x18\x05 \x01(\x05\x12\x0f\n\x07right_r\x18\x06 \x01(\x05\x12\x0f\n\x07right_g\x18\x07 \x01(\x05\x12\x0f\n\x07right_b\x18\x08 \x01(\x05\"\xd7\x01\n\x19\x42reathingGradient3Request\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\x0e\n\x06left_r\x18\x03 \x01(\x05\x12\x0e\n\x06left_g\x18\x04 \x01(\x05\x12\x0e\n\x06left_b\x18\x05 \x01(\x05\x12\x10\n\x08middle_r\x18\x06 \x01(\x05\x12\x10\n\x08middle_g\x18\x07 \x01(\x05\x12\x10\n\x08middle_b\x18\x08 \x01(\x05\x12\x0f\n\x07right_r\x18\t \x01(\x05\x12\x0f\n\x07right_g\x18\n \x01(\x05\x12\x0f\n\x07right_b\x18\x0b \x01(\x05\"<\n\x17\x42reathingRainbowRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\"w\n\x13RunningColorRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\x0e\n\x06length\x18\x03 \x01(\x05\x12\x0c\n\x04\x66\x61\x64\x65\x18\x04 \x01(\x05\x12\t\n\x01r\x18\x05 \x01(\x05\x12\t\n\x01g\x18\x06 \x01(\x05\x12\t\n\x01\x62\x18\x07 \x01(\x05\"\x9f\x01\n\x17RunningGradient2Request\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\x0e\n\x06left_r\x18\x03 \x01(\x05\x12\x0e\n\x06left_g\x18\x04 \x01(\x05\x12\x0e\n\x06left_b\x18\x05 \x01(\x05\x12\x0f\n\x07right_r\x18\x06 \x01(\x05\x12\x0f\n\x07right_g\x18\x07 \x01(\x05\x12\x0f\n\x07right_b\x18\x08 \x01(\x05\"\xd5\x01\n\x17RunningGradient3Request\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\x0e\n\x06left_r\x18\x03 \x01(\x05\x12\x0e\n\x06left_g\x18\x04 \x01(\x05\x12\x0e\n\x06left_b\x18\x05 \x01(\x05\x12\x10\n\x08middle_r\x18\x06 \x01(\x05\x12\x10\n\x08middle_g\x18\x07 \x01(\x05\x12\x10\n\x08middle_b\x18\x08 \x01(\x05\x12\x0f\n\x07right_r\x18\t \x01(\x05\x12\x0f\n\x07right_g\x18\n \x01(\x05\x12\x0f\n\x07right_b\x18\x0b \x01(\x05\":\n\x15RunningRainbowRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\"\xbb\x01\n\x0cSnakeRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\x13\n\x0bhead_length\x18\x03 \x01(\x05\x12\x13\n\x0btail_length\x18\x04 \x01(\x05\x12\x0e\n\x06head_r\x18\x05 \x01(\x05\x12\x0e\n\x06head_g\x18\x06 \x01(\x05\x12\x0e\n\x06head_b\x18\x07 \x01(\x05\x12\x0e\n\x06tail_r\x18\x08 \x01(\x05\x12\x0e\n\x06tail_g\x18\t \x01(\x05\x12\x0e\n\x06tail_b\x18\n \x01(\x05\"Q\n\x0b\x46ireRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\t\n\x01r\x18\x03 \x01(\x05\x12\t\n\x01g\x18\x04 \x01(\x05\x12\t\n\x01\x62\x18\x05 \x01(\x05\"U\n\x0fSnowfallRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\t\n\x01r\x18\x03 \x01(\x05\x12\t\n\x01g\x18\x04 \x01(\x05\x12\t\n\x01\x62\x18\x05 \x01(\x05\"R\n\x0c\x46lashRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\t\n\x01r\x18\x03 \x01(\x05\x12\t\n\x01g\x18\x04 \x01(\x05\x12\t\n\x01\x62\x18\x05 \x01(\x05\"x\n\x0bLoadRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\x0f\n\x07percent\x18\x03 \x01(\x01\x12\x14\n\x0clast_percent\x18\x04 \x01(\x01\x12\t\n\x01r\x18\x05 \x01(\x05\x12\t\n\x01g\x18\x06 \x01(\x05\x12\t\n\x01\x62\x18\x07 \x01(\x05\"T\n\x0e\x43puLoadRequest\x12\x12\n\nbrightness\x18\x01 \x01(\x01\x12\r\n\x05speed\x18\x02 \x01(\x01\x12\t\n\x01r\x18\x03 \x01(\x05\x12\t\n\x01g\x18\x04 \x01(\x05\x12\t\n\x01\x62\x18\x05 \x01(\x05\x32\x9a\r\n\x06Lights\x12<\n\x04Ping\x12\x19.GSS2.LightsControl.Empty\x1a\x19.GSS2.LightsControl.Empty\x12>\n\x06SetOff\x12\x19.GSS2.LightsControl.Empty\x1a\x19.GSS2.LightsControl.Empty\x12S\n\x0eSetStaticColor\x12&.GSS2.LightsControl.StaticColorRequest\x1a\x19.GSS2.LightsControl.Empty\x12[\n\x12SetStaticGradient2\x12*.GSS2.LightsControl.StaticGradient2Request\x1a\x19.GSS2.LightsControl.Empty\x12[\n\x12SetStaticGradient3\x12*.GSS2.LightsControl.StaticGradient3Request\x1a\x19.GSS2.LightsControl.Empty\x12W\n\x10SetStaticRainbow\x12(.GSS2.LightsControl.StaticRainbowRequest\x1a\x19.GSS2.LightsControl.Empty\x12Y\n\x11SetBreathingColor\x12).GSS2.LightsControl.BreathingColorRequest\x1a\x19.GSS2.LightsControl.Empty\x12\x61\n\x15SetBreathingGradient2\x12-.GSS2.LightsControl.BreathingGradient2Request\x1a\x19.GSS2.LightsControl.Empty\x12\x61\n\x15SetBreathingGradient3\x12-.GSS2.LightsControl.BreathingGradient3Request\x1a\x19.GSS2.LightsControl.Empty\x12]\n\x13SetBreathingRainbow\x12+.GSS2.LightsControl.BreathingRainbowRequest\x1a\x19.GSS2.LightsControl.Empty\x12U\n\x0fSetRunningColor\x12\'.GSS2.LightsControl.RunningColorRequest\x1a\x19.GSS2.LightsControl.Empty\x12]\n\x13SetRunningGradient2\x12+.GSS2.LightsControl.RunningGradient2Request\x1a\x19.GSS2.LightsControl.Empty\x12]\n\x13SetRunningGradient3\x12+.GSS2.LightsControl.RunningGradient3Request\x1a\x19.GSS2.LightsControl.Empty\x12Y\n\x11SetRunningRainbow\x12).GSS2.LightsControl.RunningRainbowRequest\x1a\x19.GSS2.LightsControl.Empty\x12G\n\x08SetSnake\x12 .GSS2.LightsControl.SnakeRequest\x1a\x19.GSS2.LightsControl.Empty\x12\x45\n\x07SetFire\x12\x1f.GSS2.LightsControl.FireRequest\x1a\x19.GSS2.LightsControl.Empty\x12M\n\x0bSetSnowfall\x12#.GSS2.LightsControl.SnowfallRequest\x1a\x19.GSS2.LightsControl.Empty\x12G\n\x08SetFlash\x12 .GSS2.LightsControl.FlashRequest\x1a\x19.GSS2.LightsControl.Empty\x12\x45\n\x07SetLoad\x12\x1f.GSS2.LightsControl.LoadRequest\x1a\x19.GSS2.LightsControl.Empty\x12K\n\nSetCpuLoad\x12\".GSS2.LightsControl.CpuLoadRequest\x1a\x19.GSS2.LightsControl.Emptyb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'lights_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_EMPTY']._serialized_start=36 + _globals['_EMPTY']._serialized_end=43 + _globals['_STATICCOLORREQUEST']._serialized_start=45 + _globals['_STATICCOLORREQUEST']._serialized_end=118 + _globals['_STATICGRADIENT2REQUEST']._serialized_start=121 + _globals['_STATICGRADIENT2REQUEST']._serialized_end=264 + _globals['_STATICGRADIENT3REQUEST']._serialized_start=267 + _globals['_STATICGRADIENT3REQUEST']._serialized_end=464 + _globals['_STATICRAINBOWREQUEST']._serialized_start=466 + _globals['_STATICRAINBOWREQUEST']._serialized_end=508 + _globals['_BREATHINGCOLORREQUEST']._serialized_start=510 + _globals['_BREATHINGCOLORREQUEST']._serialized_end=601 + _globals['_BREATHINGGRADIENT2REQUEST']._serialized_start=604 + _globals['_BREATHINGGRADIENT2REQUEST']._serialized_end=765 + _globals['_BREATHINGGRADIENT3REQUEST']._serialized_start=768 + _globals['_BREATHINGGRADIENT3REQUEST']._serialized_end=983 + _globals['_BREATHINGRAINBOWREQUEST']._serialized_start=985 + _globals['_BREATHINGRAINBOWREQUEST']._serialized_end=1045 + _globals['_RUNNINGCOLORREQUEST']._serialized_start=1047 + _globals['_RUNNINGCOLORREQUEST']._serialized_end=1166 + _globals['_RUNNINGGRADIENT2REQUEST']._serialized_start=1169 + _globals['_RUNNINGGRADIENT2REQUEST']._serialized_end=1328 + _globals['_RUNNINGGRADIENT3REQUEST']._serialized_start=1331 + _globals['_RUNNINGGRADIENT3REQUEST']._serialized_end=1544 + _globals['_RUNNINGRAINBOWREQUEST']._serialized_start=1546 + _globals['_RUNNINGRAINBOWREQUEST']._serialized_end=1604 + _globals['_SNAKEREQUEST']._serialized_start=1607 + _globals['_SNAKEREQUEST']._serialized_end=1794 + _globals['_FIREREQUEST']._serialized_start=1796 + _globals['_FIREREQUEST']._serialized_end=1877 + _globals['_SNOWFALLREQUEST']._serialized_start=1879 + _globals['_SNOWFALLREQUEST']._serialized_end=1964 + _globals['_FLASHREQUEST']._serialized_start=1966 + _globals['_FLASHREQUEST']._serialized_end=2048 + _globals['_LOADREQUEST']._serialized_start=2050 + _globals['_LOADREQUEST']._serialized_end=2170 + _globals['_CPULOADREQUEST']._serialized_start=2172 + _globals['_CPULOADREQUEST']._serialized_end=2256 + _globals['_LIGHTS']._serialized_start=2259 + _globals['_LIGHTS']._serialized_end=3949 +# @@protoc_insertion_point(module_scope) diff --git a/GSS2.LightsControl/Server/lights_pb2_grpc.py b/GSS2.LightsControl/Server/lights_pb2_grpc.py new file mode 100644 index 0000000..718b75c --- /dev/null +++ b/GSS2.LightsControl/Server/lights_pb2_grpc.py @@ -0,0 +1,914 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +import lights_pb2 as lights__pb2 + +GRPC_GENERATED_VERSION = '1.76.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + ' but the generated code in lights_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class LightsStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Ping = channel.unary_unary( + '/GSS2.LightsControl.Lights/Ping', + request_serializer=lights__pb2.Empty.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetOff = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetOff', + request_serializer=lights__pb2.Empty.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetStaticColor = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetStaticColor', + request_serializer=lights__pb2.StaticColorRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetStaticGradient2 = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetStaticGradient2', + request_serializer=lights__pb2.StaticGradient2Request.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetStaticGradient3 = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetStaticGradient3', + request_serializer=lights__pb2.StaticGradient3Request.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetStaticRainbow = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetStaticRainbow', + request_serializer=lights__pb2.StaticRainbowRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetBreathingColor = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetBreathingColor', + request_serializer=lights__pb2.BreathingColorRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetBreathingGradient2 = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetBreathingGradient2', + request_serializer=lights__pb2.BreathingGradient2Request.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetBreathingGradient3 = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetBreathingGradient3', + request_serializer=lights__pb2.BreathingGradient3Request.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetBreathingRainbow = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetBreathingRainbow', + request_serializer=lights__pb2.BreathingRainbowRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetRunningColor = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetRunningColor', + request_serializer=lights__pb2.RunningColorRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetRunningGradient2 = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetRunningGradient2', + request_serializer=lights__pb2.RunningGradient2Request.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetRunningGradient3 = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetRunningGradient3', + request_serializer=lights__pb2.RunningGradient3Request.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetRunningRainbow = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetRunningRainbow', + request_serializer=lights__pb2.RunningRainbowRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetSnake = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetSnake', + request_serializer=lights__pb2.SnakeRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetFire = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetFire', + request_serializer=lights__pb2.FireRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetSnowfall = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetSnowfall', + request_serializer=lights__pb2.SnowfallRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetFlash = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetFlash', + request_serializer=lights__pb2.FlashRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetLoad = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetLoad', + request_serializer=lights__pb2.LoadRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + self.SetCpuLoad = channel.unary_unary( + '/GSS2.LightsControl.Lights/SetCpuLoad', + request_serializer=lights__pb2.CpuLoadRequest.SerializeToString, + response_deserializer=lights__pb2.Empty.FromString, + _registered_method=True) + + +class LightsServicer(object): + """Missing associated documentation comment in .proto file.""" + + def Ping(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetOff(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetStaticColor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetStaticGradient2(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetStaticGradient3(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetStaticRainbow(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetBreathingColor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetBreathingGradient2(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetBreathingGradient3(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetBreathingRainbow(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetRunningColor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetRunningGradient2(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetRunningGradient3(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetRunningRainbow(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetSnake(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetFire(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetSnowfall(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetFlash(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetLoad(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetCpuLoad(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_LightsServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Ping': grpc.unary_unary_rpc_method_handler( + servicer.Ping, + request_deserializer=lights__pb2.Empty.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetOff': grpc.unary_unary_rpc_method_handler( + servicer.SetOff, + request_deserializer=lights__pb2.Empty.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetStaticColor': grpc.unary_unary_rpc_method_handler( + servicer.SetStaticColor, + request_deserializer=lights__pb2.StaticColorRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetStaticGradient2': grpc.unary_unary_rpc_method_handler( + servicer.SetStaticGradient2, + request_deserializer=lights__pb2.StaticGradient2Request.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetStaticGradient3': grpc.unary_unary_rpc_method_handler( + servicer.SetStaticGradient3, + request_deserializer=lights__pb2.StaticGradient3Request.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetStaticRainbow': grpc.unary_unary_rpc_method_handler( + servicer.SetStaticRainbow, + request_deserializer=lights__pb2.StaticRainbowRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetBreathingColor': grpc.unary_unary_rpc_method_handler( + servicer.SetBreathingColor, + request_deserializer=lights__pb2.BreathingColorRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetBreathingGradient2': grpc.unary_unary_rpc_method_handler( + servicer.SetBreathingGradient2, + request_deserializer=lights__pb2.BreathingGradient2Request.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetBreathingGradient3': grpc.unary_unary_rpc_method_handler( + servicer.SetBreathingGradient3, + request_deserializer=lights__pb2.BreathingGradient3Request.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetBreathingRainbow': grpc.unary_unary_rpc_method_handler( + servicer.SetBreathingRainbow, + request_deserializer=lights__pb2.BreathingRainbowRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetRunningColor': grpc.unary_unary_rpc_method_handler( + servicer.SetRunningColor, + request_deserializer=lights__pb2.RunningColorRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetRunningGradient2': grpc.unary_unary_rpc_method_handler( + servicer.SetRunningGradient2, + request_deserializer=lights__pb2.RunningGradient2Request.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetRunningGradient3': grpc.unary_unary_rpc_method_handler( + servicer.SetRunningGradient3, + request_deserializer=lights__pb2.RunningGradient3Request.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetRunningRainbow': grpc.unary_unary_rpc_method_handler( + servicer.SetRunningRainbow, + request_deserializer=lights__pb2.RunningRainbowRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetSnake': grpc.unary_unary_rpc_method_handler( + servicer.SetSnake, + request_deserializer=lights__pb2.SnakeRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetFire': grpc.unary_unary_rpc_method_handler( + servicer.SetFire, + request_deserializer=lights__pb2.FireRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetSnowfall': grpc.unary_unary_rpc_method_handler( + servicer.SetSnowfall, + request_deserializer=lights__pb2.SnowfallRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetFlash': grpc.unary_unary_rpc_method_handler( + servicer.SetFlash, + request_deserializer=lights__pb2.FlashRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetLoad': grpc.unary_unary_rpc_method_handler( + servicer.SetLoad, + request_deserializer=lights__pb2.LoadRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + 'SetCpuLoad': grpc.unary_unary_rpc_method_handler( + servicer.SetCpuLoad, + request_deserializer=lights__pb2.CpuLoadRequest.FromString, + response_serializer=lights__pb2.Empty.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'GSS2.LightsControl.Lights', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('GSS2.LightsControl.Lights', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class Lights(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def Ping(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/Ping', + lights__pb2.Empty.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetOff(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetOff', + lights__pb2.Empty.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetStaticColor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetStaticColor', + lights__pb2.StaticColorRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetStaticGradient2(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetStaticGradient2', + lights__pb2.StaticGradient2Request.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetStaticGradient3(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetStaticGradient3', + lights__pb2.StaticGradient3Request.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetStaticRainbow(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetStaticRainbow', + lights__pb2.StaticRainbowRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetBreathingColor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetBreathingColor', + lights__pb2.BreathingColorRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetBreathingGradient2(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetBreathingGradient2', + lights__pb2.BreathingGradient2Request.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetBreathingGradient3(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetBreathingGradient3', + lights__pb2.BreathingGradient3Request.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetBreathingRainbow(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetBreathingRainbow', + lights__pb2.BreathingRainbowRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetRunningColor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetRunningColor', + lights__pb2.RunningColorRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetRunningGradient2(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetRunningGradient2', + lights__pb2.RunningGradient2Request.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetRunningGradient3(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetRunningGradient3', + lights__pb2.RunningGradient3Request.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetRunningRainbow(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetRunningRainbow', + lights__pb2.RunningRainbowRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetSnake(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetSnake', + lights__pb2.SnakeRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetFire(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetFire', + lights__pb2.FireRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetSnowfall(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetSnowfall', + lights__pb2.SnowfallRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetFlash(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetFlash', + lights__pb2.FlashRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetLoad(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetLoad', + lights__pb2.LoadRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SetCpuLoad(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/GSS2.LightsControl.Lights/SetCpuLoad', + lights__pb2.CpuLoadRequest.SerializeToString, + lights__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/GSS2.LightsControl/Server/requirements.txt b/GSS2.LightsControl/Server/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..e1692f640d94f448a5f2156fadfabfc81bf3bac8 GIT binary patch literal 222 zcmY+8Q3}E^5Jca%(4#1=LhDZtQ?0dvBq2>jFRxBFAeNA1lbLzDpLa)3$3SG}Mq + + + net10.0 + enable + enable + dotnet-GSS2.Test-191ece0a-855d-46ec-9ecb-44378fc3db83 + + + + + + + + + + diff --git a/GSS2.Test/Program.cs b/GSS2.Test/Program.cs new file mode 100644 index 0000000..134ba9d --- /dev/null +++ b/GSS2.Test/Program.cs @@ -0,0 +1,66 @@ +using System.Data; +using GSS2.Core.Analysis.AmineContent.Database; +using GSS2.Core.Hardware; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Protocols.Configuration; + +namespace GSS2.Test; + +public class Program +{ + public static void Main(string[] args) + { + var builder = Host.CreateApplicationBuilder(args); + + builder.Services.AddDbContext( + options => + { + var databasePath = builder.Configuration.GetConnectionString("AmineContentCalibrationDatabasePath"); + options.UseSqlite($"Data Source={databasePath}"); + }, + ServiceLifetime.Singleton + ); + builder.Services.AddSingleton( + serviceProvider => + { + var configuration = serviceProvider.GetService(); + if (configuration is null) + throw new Exception("Cannot get configuration"); + var serviceConfiguration = configuration.GetValue("Lights"); + if (serviceConfiguration is null) + throw new InvalidConfigurationException("Cannot get configuration (Lights)"); + var logger = serviceProvider.GetService>(); + if (logger is null) + throw new Exception("Cannot get logger"); + return new LightsService(logger, serviceConfiguration); + } + ); + builder.Services.AddSingleton( + serviceProvider => + { + var configuration = serviceProvider.GetService(); + if (configuration is null) + throw new Exception("Cannot get configuration"); + var serviceConfiguration = configuration.GetValue("TemperatureHumidity"); + if (serviceConfiguration is null) + throw new InvalidConfigurationException("Cannot get configuration (TemperatureHumidity)"); + var logger = serviceProvider.GetService>(); + if (logger is null) + throw new Exception("Cannot get logger"); + return new TemperatureHumidityService(logger, serviceConfiguration); + } + ); + + builder.Services.AddHostedService(); + + var host = builder.Build(); + + using (var scope = host.Services.CreateScope()) + { + var context = scope.ServiceProvider.GetRequiredService(); + context.Database.Migrate(); + } + + host.Run(); + } +} diff --git a/GSS2.Test/Worker.cs b/GSS2.Test/Worker.cs new file mode 100644 index 0000000..0588663 --- /dev/null +++ b/GSS2.Test/Worker.cs @@ -0,0 +1,30 @@ +using System.Drawing; +using GSS2.Core.Analysis.AmineContent.Database; +using GSS2.Core.Hardware; + +namespace GSS2.Test; + +public class Worker( + ILogger logger, + AmineContentCalibrationContext calibrationContext, + LightsService lightsService, + TemperatureHumidityService temperatureHumidity +) : BackgroundService +{ + private readonly ILogger _logger = logger; + private readonly AmineContentCalibrationContext _calibrationContext = calibrationContext; + private readonly LightsService _lightsService = lightsService; + private readonly TemperatureHumidityService _temperatureHumidity = temperatureHumidity; + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + _ = _lightsService.Run(cancellationToken); + await _lightsService.SnowfallAsync(0.3, 1, Color.Aqua, cancellationToken); + while (!cancellationToken.IsCancellationRequested) + { + var (temperature, humidity) = await _temperatureHumidity.MeasureAsync(); + _logger.LogInformation("Temperature: {}, RelativeHumidity: {}", temperature, humidity); + await Task.Delay(5000); + } + } +} diff --git a/GSS2.Test/appsettings.json b/GSS2.Test/appsettings.json new file mode 100644 index 0000000..d0ccc75 --- /dev/null +++ b/GSS2.Test/appsettings.json @@ -0,0 +1,33 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "ConnectionStrings": { + "AmineContentCalibrationDatabasePath": "AmineContentCalibration.db" + }, + "Hardware": { + "Illuminator": { + "WhitePwmPin": 13, + "Uv254PwmPin": 18, + "Uv365PwmPin": 12, + "WhiteRelayPin": 16, + "Uv254RelayPin": 15, + "Uv365RelayPin": 20, + "FanPin": 21, + "PwmFrequency": 200, + "MaxWhitePwmDuty": 1.0, + "MaxUv254PwmDuty": 1.0, + "MaxUv365PwmDuty": 1.0 + }, + "TemperatureHumidity": { + "Bus": 0, + "Address": 68 + }, + "Lights": { + "ServerAddress": "localhost:50051" + } + } +} \ No newline at end of file diff --git a/GranuSightSoftware2.sln b/GranuSightSoftware2.sln new file mode 100644 index 0000000..d2a5f97 --- /dev/null +++ b/GranuSightSoftware2.sln @@ -0,0 +1,70 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.Core", "GSS2.Core\GSS2.Core.csproj", "{C691BFCC-A6EF-4331-B7C4-E264441BEE0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.Test", "GSS2.Test\GSS2.Test.csproj", "{94F2F407-23DC-4945-AE67-B33C540DEF28}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GSS2.LightsControl", "GSS2.LightsControl", "{B8CA8CAF-03B6-2A4B-B2EF-758B3B2ADED2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CSharpClient", "CSharpClient", "{478CE71E-3653-8F33-D0F9-CF70B2F74279}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GSS2.LightsControl.CSharpClient", "GSS2.LightsControl\CSharpClient\GSS2.LightsControl.CSharpClient.csproj", "{D0130B90-6ECA-49CA-A126-EFEFF481FC74}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Debug|x64.ActiveCfg = Debug|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Debug|x64.Build.0 = Debug|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Debug|x86.ActiveCfg = Debug|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Debug|x86.Build.0 = Debug|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Release|Any CPU.Build.0 = Release|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Release|x64.ActiveCfg = Release|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Release|x64.Build.0 = Release|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Release|x86.ActiveCfg = Release|Any CPU + {C691BFCC-A6EF-4331-B7C4-E264441BEE0D}.Release|x86.Build.0 = Release|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Debug|x64.ActiveCfg = Debug|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Debug|x64.Build.0 = Debug|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Debug|x86.ActiveCfg = Debug|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Debug|x86.Build.0 = Debug|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Release|Any CPU.Build.0 = Release|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Release|x64.ActiveCfg = Release|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Release|x64.Build.0 = Release|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Release|x86.ActiveCfg = Release|Any CPU + {94F2F407-23DC-4945-AE67-B33C540DEF28}.Release|x86.Build.0 = Release|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Debug|x64.ActiveCfg = Debug|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Debug|x64.Build.0 = Debug|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Debug|x86.ActiveCfg = Debug|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Debug|x86.Build.0 = Debug|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|Any CPU.Build.0 = Release|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x64.ActiveCfg = Release|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x64.Build.0 = Release|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x86.ActiveCfg = Release|Any CPU + {D0130B90-6ECA-49CA-A126-EFEFF481FC74}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {478CE71E-3653-8F33-D0F9-CF70B2F74279} = {B8CA8CAF-03B6-2A4B-B2EF-758B3B2ADED2} + {D0130B90-6ECA-49CA-A126-EFEFF481FC74} = {478CE71E-3653-8F33-D0F9-CF70B2F74279} + EndGlobalSection +EndGlobal