WIP: Initial commit for ESCL projects

This commit is contained in:
Ben Olden-Cooligan 2022-08-15 22:49:56 -04:00
parent 7d80389087
commit 13d8a3901e
22 changed files with 568 additions and 0 deletions

32
NAPS2.Escl.Client/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
Thumbs.db
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.sln.docstates
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
*.vssscc
$tf*/
publish/
bin/
temp/

View File

@ -0,0 +1,47 @@
using Zeroconf;
namespace NAPS2.Escl.Client;
public class DeviceServiceLocator
{
public async Task Something()
{
// TODO: ResolveContinuous is also an option
var results = await ZeroconfResolver.ResolveAsync(new[]
{
"_uscan._tcp",
"_uscans._tcp"
});
foreach (var r in results)
{
var ip = r.IPAddress;
foreach (var s in r.Services.Values)
{
var port = s.Port;
var name = s.Name;
var serviceName = s.ServiceName;
// TODO: Merge properties from multiple sets?
var props = s.Properties.FirstOrDefault();
if (props != null)
{
// var txtVers = props.GetValueOrDefault("txtvers"); // txt record version
// var adminUrl = props.GetValueOrDefault("adminurl"); // url to scanner config page
// var esclVersion = props.GetValueOrDefault("Vers"); // escl version e.g. "2.0"
// var thumbnail = props.GetValueOrDefault("representation"); // url to png or ico
// var urlBasePath = props.GetValueOrDefault("rs"); // no leading (or trailing) slash
// var scannerName = props.GetValueOrDefault("ty"); // human readable
// var note = props.GetValueOrDefault("note"); // supposed to be "scanner location", e.g. "Copy Room"
// // Note jpeg is better in that we can get one image at a time, but pdf does allow png quality potentially
// // Hopefully decent scanners can support png too
// // Also for the server we can definitely provide NAPS2-generated pdfs, which is kind of a cool idea for e.g. using from mobile
// var pdl = props.GetValueOrDefault("pdl"); // comma separated mime types supported "application/pdf,image/jpeg" at minimum
// var uuid = props.GetValueOrDefault("uuid"); // physical device id
// var colorSpace = props.GetValueOrDefault("cs"); // comma separated capabilites, "color,grayscale,binary"
// var source = props.GetValueOrDefault("is"); // "platen,adf,camera" platen = flatbed
// var duplex = props.GetValueOrDefault("duplex"); // "T"rue or "F"alse
//
}
}
}
}
}

View File

@ -0,0 +1,95 @@
using System.Xml.Linq;
namespace NAPS2.Escl.Client;
public class EsclHttpClient
{
private static readonly XNamespace ScanNs = XNamespace.Get("http://schemas.hp.com/imaging/escl/2011/05/03");
private static readonly XNamespace PwgNs = XNamespace.Get("http://www.pwg.org/schemas/2010/12/sm");
private readonly EsclService _service;
public EsclHttpClient(EsclService service)
{
_service = service;
}
public async Task<EsclCapabilities> GetCapabilities()
{
var doc = await DoRequest("ScannerCapabilities");
var root = doc.Root;
if (root?.Name != ScanNs + "ScannerCapabilities")
{
throw new InvalidOperationException("Unexpected root element: " + doc.Root?.Name);
}
var settingProfilesEl = root.Element(ScanNs + "SettingProfiles");
var settingProfiles = new Dictionary<string, EsclSettingProfile>();
if (settingProfilesEl != null)
{
foreach (var el in settingProfilesEl.Elements(ScanNs + "SettingProfile"))
{
ParseSettingProfile(el, settingProfiles);
}
}
return new EsclCapabilities
{
Version = root.Element(PwgNs + "Version")?.Value,
MakeAndModel = root.Element(PwgNs + "MakeAndModel")?.Value,
SerialNumber = root.Element(PwgNs + "SerialNumber")?.Value,
Uuid = root.Element(ScanNs + "UUID")?.Value,
AdminUri = root.Element(ScanNs + "AdminURI")?.Value,
IconUri = root.Element(ScanNs + "IconURI")?.Value
};
}
private EsclSettingProfile ParseSettingProfile(XElement element,
Dictionary<string, EsclSettingProfile> profilesDict)
{
var profileRef = element.Attribute("ref")?.Value;
if (profileRef != null)
{
return profilesDict[profileRef];
}
var profile = new EsclSettingProfile
{
Name = element.Attribute("name")?.Value,
ColorModes =
ParseEnumValues<EsclColorMode>(element.Element(ScanNs + "ColorModes")?.Elements(ScanNs + "ColorMode"))
};
if (profile.Name != null)
{
profilesDict[profile.Name] = profile;
}
return profile;
}
private List<T> ParseEnumValues<T>(IEnumerable<XElement>? elements) where T : struct
{
var list = new List<T>();
if (elements == null)
{
return list;
}
foreach (var el in elements)
{
if (Enum.TryParse<T>(el.Value, out var parsed))
{
list.Add(parsed);
}
}
return list;
}
private async Task<XDocument> DoRequest(string endpoint)
{
// TODO: We're supposed to reuse these, right?
var httpClient = new HttpClient();
var protocol = _service.Tls ? "https" : "http";
var url = $"{protocol}://{_service.Ip}:{_service.Port}/{_service.RootUrl}/{endpoint}";
var response = await httpClient.GetAsync(url);
// TODO: Handle status codes better
response.EnsureSuccessStatusCode();
var responseText = await response.Content.ReadAsStringAsync();
return XDocument.Parse(responseText);
}
}

View File

@ -0,0 +1,9 @@
namespace NAPS2.Escl.Client;
public class EsclService
{
public string Ip { get; init; }
public int Port { get; init; }
public bool Tls { get; init; }
public string RootUrl { get; init; }
}

View File

@ -0,0 +1,5 @@
// https://sergiopedri.medium.com/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;
public static class IsExternalInit {}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462;netstandard2.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Zeroconf" Version="3.6.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NAPS2.Escl\NAPS2.Escl.csproj" />
</ItemGroup>
</Project>

32
NAPS2.Escl.Server/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
Thumbs.db
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.sln.docstates
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
*.vssscc
$tf*/
publish/
bin/
temp/

View File

@ -0,0 +1,34 @@
using System.Xml.Linq;
using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;
namespace NAPS2.Escl.Server;
public class EsclApiController : WebApiController
{
private static readonly XNamespace ScanNs = XNamespace.Get("http://schemas.hp.com/imaging/escl/2011/05/03");
private static readonly XNamespace PwgNs = XNamespace.Get("http://www.pwg.org/schemas/2010/12/sm");
private readonly EsclServerConfig _serverConfig;
public EsclApiController(EsclServerConfig serverConfig)
{
_serverConfig = serverConfig;
}
[Route(HttpVerbs.Get, "/ScannerCapabilities")]
public async Task GetScannerCapabilities()
{
var caps = _serverConfig.Capabilities;
var doc =
new XDocument(
new XElement(ScanNs + "ScannerCapabilities",
new XElement(PwgNs + "Version", caps.Version), // TODO: Probably hard code version or something
new XElement(PwgNs + "MakeAndModel", caps.MakeAndModel),
new XElement(PwgNs + "SerialNumber", caps.SerialNumber)
));
using var writer = new StreamWriter(HttpContext.OpenResponseStream());
await writer.WriteAsync(doc.ToString());
}
}

View File

@ -0,0 +1,41 @@
using EmbedIO;
using EmbedIO.WebApi;
namespace NAPS2.Escl.Server;
public class EsclServer : IDisposable
{
private readonly EsclServerConfig _serverConfig;
private WebServer? _server;
public EsclServer(EsclServerConfig serverConfig)
{
_serverConfig = serverConfig;
}
public void Start()
{
if (_server != null)
{
throw new InvalidOperationException();
}
var url = "http://localhost:9898/";
_server = new WebServer(o => o
.WithMode(HttpListenerMode.EmbedIO)
.WithUrlPrefix(url))
.WithWebApi("/escl", m => m.WithController(() => new EsclApiController(_serverConfig)));
_server.StateChanged += ServerOnStateChanged;
// TODO: This might block on tasks, maybe copy impl but async
_server.Start();
}
private void ServerOnStateChanged(object sender, WebServerStateChangedEventArgs e)
{
}
public void Dispose()
{
_server?.Dispose();
}
}

View File

@ -0,0 +1,6 @@
namespace NAPS2.Escl.Server;
public class EsclServerConfig
{
public EsclCapabilities Capabilities { get; init; }
}

View File

@ -0,0 +1,5 @@
// https://sergiopedri.medium.com/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;
public static class IsExternalInit {}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462;netstandard2.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EmbedIO" Version="3.4.3" />
<PackageReference Include="Zeroconf" Version="3.6.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NAPS2.Escl\NAPS2.Escl.csproj" />
</ItemGroup>
</Project>

32
NAPS2.Escl.Tests/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
Thumbs.db
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.sln.docstates
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
*.vssscc
$tf*/
publish/
bin/
temp/

View File

@ -0,0 +1,34 @@
using NAPS2.Escl.Client;
using NAPS2.Escl.Server;
using Xunit;
namespace NAPS2.Escl.Tests;
public class ClientServerTests
{
[Fact]
public async Task ClientServer()
{
using var server = new EsclServer(new EsclServerConfig
{
Capabilities = new EsclCapabilities()
{
Version = "2.0",
MakeAndModel = "HP Blah",
SerialNumber = "123abc"
}
});
server.Start();
var client = new EsclHttpClient(new EsclService
{
Ip = "localhost",
Port = 9898,
RootUrl = "escl",
Tls = false
});
var caps = await client.GetCapabilities();
Assert.Equal("2.0", caps.Version);
Assert.Equal("HP Blah", caps.MakeAndModel);
Assert.Equal("123abc", caps.SerialNumber);
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0-preview-20220726-02" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NAPS2.Escl.Client\NAPS2.Escl.Client.csproj" />
<ProjectReference Include="..\NAPS2.Escl.Server\NAPS2.Escl.Server.csproj" />
</ItemGroup>
</Project>

32
NAPS2.Escl/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
Thumbs.db
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.sln.docstates
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
*.vssscc
$tf*/
publish/
bin/
temp/

View File

@ -0,0 +1,11 @@
namespace NAPS2.Escl;
public class EsclCapabilities
{
public string? Version { get; init; }
public string? MakeAndModel { get; init; }
public string? SerialNumber { get; init; }
public string? Uuid { get; init; }
public string? AdminUri { get; init; }
public string? IconUri { get; init; }
}

View File

@ -0,0 +1,10 @@
namespace NAPS2.Escl;
public enum EsclColorMode
{
BlackAndWhite1,
Grayscale8,
Grayscale16,
RGB24,
RGB48
}

View File

@ -0,0 +1,7 @@
namespace NAPS2.Escl;
public class EsclSettingProfile
{
public string? Name { get; init; }
public List<EsclColorMode> ColorModes { get; init; } = new();
}

View File

@ -0,0 +1,5 @@
// https://sergiopedri.medium.com/enabling-and-using-c-9-features-on-older-and-unsupported-runtimes-ce384d8debb
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;
public static class IsExternalInit {}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462;netstandard2.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Zeroconf" Version="3.6.11" />
</ItemGroup>
</Project>

View File

@ -56,6 +56,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Images.Mac", "NAPS2.I
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Lib.WinForms.Tests", "NAPS2.Lib.WinForms.Tests\NAPS2.Lib.WinForms.Tests.csproj", "{00CDDEBC-192B-45A1-AFD0-51C19DB6B17F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Escl", "NAPS2.Escl\NAPS2.Escl.csproj", "{7D99D58A-990F-4F64-A0C6-DF699F097655}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Escl.Server", "NAPS2.Escl.Server\NAPS2.Escl.Server.csproj", "{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Escl.Client", "NAPS2.Escl.Client\NAPS2.Escl.Client.csproj", "{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Escl.Tests", "NAPS2.Escl.Tests\NAPS2.Escl.Tests.csproj", "{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -295,6 +303,54 @@ Global
{00CDDEBC-192B-45A1-AFD0-51C19DB6B17F}.Standalone|Any CPU.Build.0 = Debug|Any CPU
{00CDDEBC-192B-45A1-AFD0-51C19DB6B17F}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{00CDDEBC-192B-45A1-AFD0-51C19DB6B17F}.Tools|Any CPU.Build.0 = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.DebugLang|Any CPU.ActiveCfg = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.DebugLang|Any CPU.Build.0 = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.InstallerEXE|Any CPU.ActiveCfg = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.InstallerEXE|Any CPU.Build.0 = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.InstallerMSI|Any CPU.ActiveCfg = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.InstallerMSI|Any CPU.Build.0 = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.Standalone|Any CPU.ActiveCfg = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.Standalone|Any CPU.Build.0 = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{7D99D58A-990F-4F64-A0C6-DF699F097655}.Tools|Any CPU.Build.0 = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.DebugLang|Any CPU.ActiveCfg = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.DebugLang|Any CPU.Build.0 = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.InstallerEXE|Any CPU.ActiveCfg = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.InstallerEXE|Any CPU.Build.0 = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.InstallerMSI|Any CPU.ActiveCfg = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.InstallerMSI|Any CPU.Build.0 = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.Standalone|Any CPU.ActiveCfg = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.Standalone|Any CPU.Build.0 = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{892CE6E0-CBB3-47F8-9BEA-815B28D6A8EB}.Tools|Any CPU.Build.0 = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.DebugLang|Any CPU.ActiveCfg = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.DebugLang|Any CPU.Build.0 = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.InstallerEXE|Any CPU.ActiveCfg = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.InstallerEXE|Any CPU.Build.0 = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.InstallerMSI|Any CPU.ActiveCfg = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.InstallerMSI|Any CPU.Build.0 = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.Standalone|Any CPU.ActiveCfg = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.Standalone|Any CPU.Build.0 = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{B1A0D82E-898E-454F-9B9D-05E9447F3DE1}.Tools|Any CPU.Build.0 = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.DebugLang|Any CPU.ActiveCfg = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.DebugLang|Any CPU.Build.0 = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.InstallerEXE|Any CPU.ActiveCfg = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.InstallerEXE|Any CPU.Build.0 = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.InstallerMSI|Any CPU.ActiveCfg = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.InstallerMSI|Any CPU.Build.0 = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Standalone|Any CPU.ActiveCfg = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Standalone|Any CPU.Build.0 = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Tools|Any CPU.ActiveCfg = Debug|Any CPU
{ECD69481-CD4D-4EEC-A3BA-612DB29F13B3}.Tools|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE