├── CreateNugetPackage.bat ├── .github ├── FUNDING.yml └── workflows │ └── dotnet-core.yml ├── WpfApp ├── icon.ico ├── WpfApp.csproj └── Program.cs ├── docs └── example1.gif ├── WpfApp.Gui ├── Resources │ └── icon.png ├── App.xaml.cs ├── Views │ ├── Basics │ │ ├── PlcButton.xaml.cs │ │ ├── PlcVariable.xaml.cs │ │ ├── PlcSignalOkNok.xaml.cs │ │ ├── PlcSignalOnOff.xaml.cs │ │ ├── PlcErrorBar.xaml.cs │ │ ├── PlcErrorDetails.xaml.cs │ │ ├── PlcErrorDetails.xaml │ │ ├── PlcButton.xaml │ │ ├── PlcErrorBar.xaml │ │ ├── PlcVariable.xaml │ │ ├── PlcSignalOkNok.xaml │ │ ├── PlcSignalOnOff.xaml │ │ └── PlcUserControl.cs │ ├── Pages │ │ ├── GraphView.xaml.cs │ │ ├── MainView.xaml.cs │ │ ├── SettingsView.xaml.cs │ │ ├── GraphView.xaml │ │ ├── SettingsView.xaml │ │ └── MainView.xaml │ ├── MainWindow.xaml.cs │ └── MainWindow.xaml ├── Design │ ├── DesignSettingsProvider.cs │ ├── DesignPlcProvider.cs │ └── DesignPlcEventService.cs ├── GuiModuleCatalog.cs ├── ViewModels │ ├── MainViewModel.cs │ ├── SettingsViewModel.cs │ ├── Basics │ │ ├── PlcErrorDetailsViewModel.cs │ │ ├── PlcErrorBarViewModel.cs │ │ └── PlcVariableViewModel.cs │ ├── GraphViewModel.cs │ ├── ViewModelBase.cs │ └── MainWindowViewModel.cs ├── Converters │ ├── ConnectionStateToVisibilityConverter.cs │ ├── BoolToStyleConverter.cs │ ├── BoolToBrushConverter.cs │ ├── BoolToVisibilityConverter.cs │ ├── RoleToVisibilityConverter.cs │ └── MinimalRoleToVisibilityConverter.cs ├── Contents.resx ├── Contents.de.resx ├── Services │ └── PresentationService.cs ├── WpfApp.Gui.csproj ├── ViewModelLocator.cs ├── App.xaml ├── Contents.Designer.cs └── Contents.de.Designer.cs ├── WpfApp.Interfaces ├── Commons │ ├── IInitializable.cs │ ├── IInstanceCreator.cs │ └── IViewModelFactory.cs ├── Models │ ├── DatabaseObject.cs │ ├── User.cs │ └── PlcEvent.cs ├── Ui │ ├── IViewModel.cs │ └── IPresentationService.cs ├── Enums │ ├── Role.cs │ └── Severity.cs ├── Services │ ├── ISelectionService.cs │ ├── IPlcProvider.cs │ ├── ISettingsProvider.cs │ ├── IPlcEventLogService.cs │ ├── IDirectoryService.cs │ ├── IPlcEventService.cs │ ├── IUserService.cs │ └── IDatabaseService.cs ├── Settings │ ├── HardwareSetting.cs │ ├── ErrorSetting.cs │ ├── PlcSetting.cs │ ├── ErrorCodeDescription.cs │ ├── ApplicationSetting.cs │ ├── CultureSetting.cs │ ├── ErrorCodeSetting.cs │ └── SettingRoot.cs ├── Exceptions │ └── LoginFailedException.cs ├── Constants.cs ├── Extensions │ ├── DisposableExtensions.cs │ └── RandomExtensions.cs ├── WpfApp.Interfaces.csproj └── Hardware │ ├── IPlc.cs │ └── MockPlc.cs ├── .template.config └── template.json ├── WpfApp.Logic ├── Services │ ├── SelectionService.cs │ ├── PlcEventLogService.cs │ ├── DirectoryService.cs │ ├── PlcProvider.cs │ ├── SettingsService.cs │ ├── DatabaseService.cs │ ├── PlcEventService.cs │ └── UserService.cs ├── WpfApp.Logic.csproj ├── LogicModuleCatalog.cs └── Hardware │ ├── BeckhoffConversions.cs │ └── BeckhoffPlc.cs ├── TwincatWpfHMI.template.nuspec ├── LICENSE ├── WpfApp.sln ├── README.md └── .gitignore /CreateNugetPackage.bat: -------------------------------------------------------------------------------- 1 | nuget pack TwincatWpfHMI.template.nuspec -NoDefaultExcludes -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: fbarresi 4 | -------------------------------------------------------------------------------- /WpfApp/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbarresi/twincat-wpf-boilerplate/HEAD/WpfApp/icon.ico -------------------------------------------------------------------------------- /docs/example1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbarresi/twincat-wpf-boilerplate/HEAD/docs/example1.gif -------------------------------------------------------------------------------- /WpfApp.Gui/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbarresi/twincat-wpf-boilerplate/HEAD/WpfApp.Gui/Resources/icon.png -------------------------------------------------------------------------------- /WpfApp.Interfaces/Commons/IInitializable.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Interfaces.Commons 2 | { 3 | public interface IInitializable 4 | { 5 | void Init(); 6 | } 7 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Models/DatabaseObject.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Interfaces.Models 2 | { 3 | public class DatabaseObjectBase 4 | { 5 | public int Id { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Ui/IViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WpfApp.Interfaces.Commons; 3 | 4 | namespace WpfApp.Interfaces.Ui 5 | { 6 | public interface IViewModel : IDisposable, IInitializable 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Enums/Role.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Interfaces.Enums 2 | { 3 | public enum Role 4 | { 5 | Root = 0, 6 | Technician = 10, 7 | Service = 20, 8 | Operator = 30 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Gui/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace WpfApp.Gui 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Commons/IInstanceCreator.cs: -------------------------------------------------------------------------------- 1 | using Ninject.Parameters; 2 | 3 | namespace WpfApp.Interfaces.Commons 4 | { 5 | public interface IInstanceCreator 6 | { 7 | T CreateInstance(ConstructorArgument[] arguments); 8 | } 9 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcButton.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Gui.Views.Basics 2 | { 3 | public partial class PlcButton : PlcUserControl 4 | { 5 | public PlcButton() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Services/ISelectionService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WpfApp.Interfaces.Services 4 | { 5 | public interface ISelectionService 6 | { 7 | IObservable Current { get; } 8 | void Select(T step); 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcVariable.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Gui.Views.Basics 2 | { 3 | public partial class PlcVariable : PlcUserControl 4 | { 5 | public PlcVariable() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Services/IPlcProvider.cs: -------------------------------------------------------------------------------- 1 | using WpfApp.Interfaces.Hardware; 2 | 3 | namespace WpfApp.Interfaces.Services 4 | { 5 | public interface IPlcProvider 6 | { 7 | IPlc GetHardware(string name); 8 | IPlc GetHardware(); 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Settings/HardwareSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WpfApp.Interfaces.Settings 4 | { 5 | public class HardwareSetting 6 | { 7 | public List PlcSettings { get; set; } = new List(); 8 | } 9 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcSignalOkNok.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Gui.Views.Basics 2 | { 3 | public partial class PlcSignalOkNok : PlcUserControl 4 | { 5 | public PlcSignalOkNok() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcSignalOnOff.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Gui.Views.Basics 2 | { 3 | public partial class PlcSignalOnOff : PlcUserControl 4 | { 5 | public PlcSignalOnOff() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Services/ISettingsProvider.cs: -------------------------------------------------------------------------------- 1 | using WpfApp.Interfaces.Settings; 2 | 3 | namespace WpfApp.Interfaces.Services 4 | { 5 | public interface ISettingsProvider 6 | { 7 | SettingRoot SettingRoot { get; } 8 | void SaveSettings(); 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Settings/ErrorSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WpfApp.Interfaces.Settings 4 | { 5 | public class ErrorSetting 6 | { 7 | public List ErrorCodeSettings { get; set; } = new List(); 8 | } 9 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Enums/Severity.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Interfaces.Enums 2 | { 3 | public enum Severity 4 | { 5 | Fatal, 6 | Critical, 7 | Error, 8 | Warning, 9 | Info, 10 | Debug, 11 | Ignored, 12 | NoError 13 | } 14 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/GraphView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Pages 4 | { 5 | public partial class GraphView : UserControl 6 | { 7 | public GraphView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/MainView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Pages 4 | { 5 | public partial class MainView : UserControl 6 | { 7 | public MainView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Exceptions/LoginFailedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WpfApp.Interfaces.Exceptions 4 | { 5 | public class LoginFailedException : Exception 6 | { 7 | public LoginFailedException(string message): base(message) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcErrorBar.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Basics 4 | { 5 | public partial class PlcErrorBar : UserControl 6 | { 7 | public PlcErrorBar() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/SettingsView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Pages 4 | { 5 | public partial class SettingsView : UserControl 6 | { 7 | public SettingsView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Services/IPlcEventLogService.cs: -------------------------------------------------------------------------------- 1 | using WpfApp.Interfaces.Models; 2 | 3 | namespace WpfApp.Interfaces.Services 4 | { 5 | public interface IPlcEventLogService 6 | { 7 | void LogEvent(PlcEvent plcEvent); 8 | PlcEvent[] GetHistory(int elements, int page); 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcErrorDetails.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace WpfApp.Gui.Views.Basics 4 | { 5 | public partial class PlcErrorDetails : UserControl 6 | { 7 | public PlcErrorDetails() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Services/IDirectoryService.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Interfaces.Services 2 | { 3 | public interface IDirectoryService 4 | { 5 | string DatabaseFolder { get; } 6 | string SettingsFolder { get; } 7 | string LogsFolder { get; } 8 | string ApplicationFolder { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Services/IPlcEventService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WpfApp.Interfaces.Models; 3 | 4 | namespace WpfApp.Interfaces.Services 5 | { 6 | public interface IPlcEventService 7 | { 8 | public IObservable ActiveEvents { get; } 9 | public IObservable LatestEvent { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Settings/PlcSetting.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Interfaces.Settings 2 | { 3 | public class PlcSetting 4 | { 5 | public string Name { get; set; } = "Plc1"; 6 | public string AmsNetId { get; set; } = ""; 7 | public int Port { get; set; } = 852; 8 | public bool IsMock { get; set; } = false; 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace WpfApp.Interfaces 2 | { 3 | public class Constants 4 | { 5 | public static string ApplicationName => "WpfApp"; 6 | public static string LoggerDirectory => "Logs"; 7 | public static string DatabaseDirectory => "Data"; 8 | public static string SettingsDirectory => "Settings"; 9 | } 10 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Design/DesignSettingsProvider.cs: -------------------------------------------------------------------------------- 1 | using WpfApp.Interfaces.Services; 2 | using WpfApp.Interfaces.Settings; 3 | 4 | namespace WpfApp.Gui.Design 5 | { 6 | internal class DesignSettingsProvider : ISettingsProvider 7 | { 8 | public SettingRoot SettingRoot => new SettingRoot(); 9 | public void SaveSettings() 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Commons/IViewModelFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WpfApp.Interfaces.Ui; 3 | 4 | namespace WpfApp.Interfaces.Commons 5 | { 6 | public interface IViewModelFactory 7 | { 8 | T Create(); 9 | 10 | TVm CreateViewModel(T model); 11 | 12 | TVm CreateViewModel(); 13 | IViewModel CreateViewModel(Type viewModelType); 14 | } 15 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Settings/ErrorCodeDescription.cs: -------------------------------------------------------------------------------- 1 | using WpfApp.Interfaces.Enums; 2 | 3 | namespace WpfApp.Interfaces.Settings 4 | { 5 | public class ErrorCodeDescription 6 | { 7 | public object Value { get; set; } 8 | public string Description { get; set; } 9 | public Severity Severity { get; set; } 10 | public string LongDescription { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Settings/ApplicationSetting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WpfApp.Interfaces.Settings 4 | { 5 | public class ApplicationSetting 6 | { 7 | public string ToggleSignalName { get; set; } = "Utils.Toggle"; 8 | public string DoubleSignalName { get; set; } = "Utils.Random"; 9 | public TimeSpan Autologout { get; set; } = TimeSpan.FromMinutes(5); 10 | } 11 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Ui/IPresentationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WpfApp.Interfaces.Ui 4 | { 5 | public interface IPresentationService 6 | { 7 | void SwitchActiveViewModel(IViewModel viewModel); 8 | void SwitchActiveViewModel(Type viewModelType); 9 | void SwitchActiveViewModel() where T : IViewModel; 10 | IObservable ActiveViewModel { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Settings/CultureSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace WpfApp.Interfaces.Settings 4 | { 5 | public class CultureSetting 6 | { 7 | public CultureInfo SelectedCulture { get; set; } = CultureInfo.GetCultureInfo("en"); 8 | 9 | public CultureInfo[] SupportedCultures => 10 | new[] {CultureInfo.GetCultureInfo("en"), CultureInfo.GetCultureInfo("de")}; 11 | } 12 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using ControlzEx.Theming; 3 | using MahApps.Metro.Controls; 4 | 5 | namespace WpfApp.Gui.Views 6 | { 7 | /// 8 | /// Interaction logic for MainWindow.xaml 9 | /// 10 | public partial class MainWindow : MetroWindow 11 | { 12 | public MainWindow() 13 | { 14 | InitializeComponent(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "Federico Barresi", 4 | "classifications": [ 5 | "Common", "WPF" 6 | ], 7 | "preferNameDirectory": "true", 8 | "sourceName": "WpfApp", 9 | "identity": "TwincatWpfHMI.template", 10 | "name": "Twincat WPF HMI", 11 | "shortName": "tchmi", 12 | "tags": { 13 | "language": "C#", 14 | "type": "project" 15 | } 16 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Design/DesignPlcProvider.cs: -------------------------------------------------------------------------------- 1 | using WpfApp.Interfaces.Hardware; 2 | using WpfApp.Interfaces.Services; 3 | 4 | namespace WpfApp.Gui.Design 5 | { 6 | internal class DesignPlcProvider : IPlcProvider 7 | { 8 | public IPlc GetHardware(string name) 9 | { 10 | return new MockPlc(); 11 | } 12 | 13 | public IPlc GetHardware() 14 | { 15 | return new MockPlc(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Extensions/DisposableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Disposables; 3 | 4 | namespace WpfApp.Interfaces.Extensions 5 | { 6 | public static class DisposableExtensions 7 | { 8 | public static T AddDisposableTo(this T source, CompositeDisposable disposables) where T : IDisposable 9 | { 10 | disposables.Add((IDisposable) source); 11 | return source; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Design/DesignPlcEventService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using WpfApp.Interfaces.Models; 4 | using WpfApp.Interfaces.Services; 5 | 6 | namespace WpfApp.Gui.Design 7 | { 8 | internal class DesignPlcEventService : IPlcEventService 9 | { 10 | public IObservable ActiveEvents => Observable.Never(); 11 | public IObservable LatestEvent => Observable.Never(); 12 | } 13 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using WpfApp.Interfaces.Enums; 3 | 4 | namespace WpfApp.Interfaces.Models 5 | { 6 | public class User : DatabaseObjectBase 7 | { 8 | public string Name { get; set; } 9 | public string PasswordHash { get; set; } 10 | public List Roles { get; set; } 11 | 12 | bool HasRole(Role role) 13 | { 14 | return Roles?.Contains(role) ?? false; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /WpfApp.Gui/GuiModuleCatalog.cs: -------------------------------------------------------------------------------- 1 | using MahApps.Metro.Controls.Dialogs; 2 | using Ninject.Modules; 3 | using WpfApp.Gui.Services; 4 | using WpfApp.Interfaces.Ui; 5 | 6 | namespace WpfApp.Gui 7 | { 8 | public class GuiModuleCatalog : NinjectModule 9 | { 10 | public override void Load() 11 | { 12 | Bind().To().InSingletonScope(); 13 | Bind().To().InSingletonScope(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /WpfApp.Logic/Services/SelectionService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Subjects; 4 | using WpfApp.Interfaces.Services; 5 | 6 | namespace WpfApp.Logic.Services 7 | { 8 | public class SelectionService : ISelectionService 9 | { 10 | public IObservable Current => subject.AsObservable(); 11 | private readonly Subject subject = new Subject(); 12 | 13 | public void Select(T step) 14 | { 15 | subject.OnNext(step); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Models/PlcEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using WpfApp.Interfaces.Enums; 3 | 4 | namespace WpfApp.Interfaces.Models 5 | { 6 | public class PlcEvent : DatabaseObjectBase 7 | { 8 | public DateTime Timestamp { get; set; } 9 | public string Description { get; set; } 10 | public string LongDescription { get; set; } 11 | public Severity Severity { get; set; } 12 | public object Value { get; set; } 13 | public object ExpectedValue { get; set; } 14 | public string Source { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/WpfApp.Interfaces.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /WpfApp.Interfaces/Settings/ErrorCodeSetting.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using WpfApp.Interfaces.Enums; 3 | 4 | namespace WpfApp.Interfaces.Settings 5 | { 6 | public class ErrorCodeSetting 7 | { 8 | public string PlcName { get; set; } 9 | public string ErrorCodeAddress { get; set; } 10 | public List CodeDescriptions { get; set; } 11 | public bool IgnoreNotDescribedValues { get; set; } 12 | public object NoErrorValue { get; set; } 13 | public Severity DefaultSeverity { get; set; } = Severity.Warning; 14 | } 15 | } -------------------------------------------------------------------------------- /WpfApp.Logic/WpfApp.Logic.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /WpfApp.Interfaces/Services/IUserService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using WpfApp.Interfaces.Enums; 4 | using WpfApp.Interfaces.Models; 5 | 6 | namespace WpfApp.Interfaces.Services 7 | { 8 | public interface IUserService 9 | { 10 | IObservable CurrentUser { get; } 11 | void Login(string name, string password); 12 | bool AddUser(string name, string password, IEnumerable roles); 13 | bool UpdateUser(string name, string password, IEnumerable roles); 14 | bool RemoveUser(string name); 15 | void Logout(); 16 | 17 | } 18 | } -------------------------------------------------------------------------------- /WpfApp.Interfaces/Hardware/IPlc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using TwinCAT; 4 | 5 | namespace WpfApp.Interfaces.Hardware 6 | { 7 | public interface IPlc : IDisposable 8 | { 9 | public IObservable ConnectionState { get; } 10 | public IObservable CreateNotification(string variable); 11 | public IObservable CreateNotification(string variable, TimeSpan cycle); 12 | public Task Read(string variable); 13 | public Task Write(string variable, T value); 14 | IObservable CreateNotification(string variable); 15 | } 16 | } -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 3.1.101 20 | - name: Install dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --configuration Release --no-restore 24 | - name: Test 25 | run: dotnet test --no-restore --verbosity normal 26 | 27 | -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Linq; 2 | using ReactiveUI; 3 | using TwinCAT; 4 | using WpfApp.Gui.Design; 5 | using WpfApp.Interfaces.Services; 6 | using WpfApp.Interfaces.Settings; 7 | 8 | namespace WpfApp.Gui.ViewModels 9 | { 10 | public class MainViewModel : ViewModelBase 11 | { 12 | 13 | public MainViewModel() 14 | { 15 | Title = "Main View"; 16 | } 17 | protected override void Initialize() 18 | { 19 | Logger.Debug("Main view initialized!"); 20 | } 21 | 22 | } 23 | 24 | internal class DesignMainViewModel : MainViewModel 25 | { 26 | public DesignMainViewModel() : base() 27 | { 28 | Init(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /TwincatWpfHMI.template.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TwincatWpfHMI.template 5 | 1.0.10 6 | 7 | A TwinCAT HMI as a WPF application. 8 | 9 | https://github.com/fbarresi/twincat-wpf-boilerplate 10 | Federico Barresi 11 | 12 | 13 | 14 | MIT 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /WpfApp.Interfaces/Settings/SettingRoot.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WpfApp.Interfaces.Settings 4 | { 5 | public class SettingRoot 6 | { 7 | public ApplicationSetting ApplicationSetting { get; set; } = new ApplicationSetting(); 8 | public HardwareSetting HardwareSetting { get; set; } = new HardwareSetting(); 9 | public CultureSetting CultureSettings { get; set; } = new CultureSetting(); 10 | public ErrorSetting ErrorSetting { get; set; } = new ErrorSetting(); 11 | 12 | public void CreateDafaultSettings() // Default settings 13 | { 14 | ApplicationSetting = new ApplicationSetting(); 15 | HardwareSetting = new HardwareSetting(){PlcSettings = new List(){new PlcSetting{IsMock = true}} }; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcErrorDetails.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /WpfApp/WpfApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | netcoreapp3.1 6 | true 7 | icon.ico 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/ConnectionStateToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | using TwinCAT; 6 | 7 | namespace WpfApp.Gui.Converters 8 | { 9 | public class ConnectionStateToVisibilityConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value is ConnectionState) 14 | { 15 | return (ConnectionState) value == ConnectionState.Connected ? Visibility.Collapsed : Visibility.Visible; 16 | } 17 | 18 | return DependencyProperty.UnsetValue; 19 | 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | return DependencyProperty.UnsetValue; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/BoolToStyleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | using System.Windows.Media; 6 | 7 | namespace WpfApp.Gui.Converters 8 | { 9 | public class BoolToStyleConverter : IValueConverter 10 | { 11 | public Style IfTrue { get; set; } 12 | public Style IfFalse { get; set; } 13 | 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value is bool) 17 | { 18 | return (bool) value ? IfTrue : IfFalse; 19 | } 20 | 21 | return DependencyProperty.UnsetValue; 22 | 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | return DependencyProperty.UnsetValue; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/BoolToBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | using System.Windows.Media; 6 | 7 | namespace WpfApp.Gui.Converters 8 | { 9 | public class BoolToBrushConverter : IValueConverter 10 | { 11 | public Brush IfTrue { get; set; } = Brushes.Green; 12 | public Brush IfFalse { get; set; } = Brushes.Red; 13 | 14 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | if (value is bool) 17 | { 18 | return (bool) value ? IfTrue : IfFalse; 19 | } 20 | 21 | return DependencyProperty.UnsetValue; 22 | 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | return DependencyProperty.UnsetValue; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/BoolToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace WpfApp.Gui.Converters 7 | { 8 | public class BoolToVisibilityConverter : IValueConverter 9 | { 10 | public Visibility IfTrue { get; set; } = Visibility.Visible; 11 | public Visibility IfFalse { get; set; } = Visibility.Collapsed; 12 | 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if (value is bool) 16 | { 17 | return (bool) value ? IfTrue : IfFalse; 18 | } 19 | 20 | return DependencyProperty.UnsetValue; 21 | 22 | } 23 | 24 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 25 | { 26 | return DependencyProperty.UnsetValue; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Pages/GraphView.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /WpfApp.Interfaces/Services/IDatabaseService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace WpfApp.Interfaces.Services 6 | { 7 | public interface IDatabaseService 8 | { 9 | IEnumerable GetCollection(string name, Expression> filter, int skip = 0, int limit = -1) where T : new(); 10 | T InsertIntoCollection(string name, T obj) where T : new(); 11 | T UpdateIntoCollection(string name, T obj) where T : new(); 12 | bool RemoveFromCollection(string name, T obj) where T : new(); 13 | void IndexCollection(string name, Expression> index) where T : new(); 14 | int CountElementInCollection(string name) where T : new(); 15 | IEnumerable GetCollection(string name, Expression> filter, Expression> orderBy, bool orderByDescending, int skip = 0, int limit = -1) where T : new(); 16 | } 17 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/RoleToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Windows; 5 | using System.Windows.Data; 6 | using WpfApp.Interfaces.Enums; 7 | 8 | namespace WpfApp.Gui.Converters 9 | { 10 | public class RoleToVisibilityConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | var parsed = Enum.TryParse((string)parameter, out Role parsedRole); 15 | 16 | if (value is List && parsed) 17 | { 18 | return ((List) value).Contains(parsedRole) ? Visibility.Visible : Visibility.Collapsed; 19 | } 20 | 21 | return Visibility.Collapsed; 22 | 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | return DependencyProperty.UnsetValue; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Converters/MinimalRoleToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Windows; 6 | using System.Windows.Data; 7 | using WpfApp.Interfaces.Enums; 8 | 9 | namespace WpfApp.Gui.Converters 10 | { 11 | public class MinimalRoleToVisibilityConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | var parsed = Enum.TryParse((string)parameter, out Role parsedRole); 16 | 17 | if (value is List && parsed) 18 | { 19 | return ((List) value).Any(r => r <= parsedRole) ? Visibility.Visible : Visibility.Collapsed; 20 | } 21 | 22 | return Visibility.Collapsed; 23 | 24 | } 25 | 26 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 27 | { 28 | return DependencyProperty.UnsetValue; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /WpfApp.Logic/Services/PlcEventLogService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using WpfApp.Interfaces.Models; 4 | using WpfApp.Interfaces.Services; 5 | 6 | namespace WpfApp.Logic.Services 7 | { 8 | public class PlcEventLogService : IPlcEventLogService 9 | { 10 | private readonly IDatabaseService databaseService; 11 | private const string CollectionName = "EventArchive"; 12 | public PlcEventLogService(IDatabaseService databaseService) 13 | { 14 | this.databaseService = databaseService; 15 | } 16 | 17 | public void LogEvent(PlcEvent plcEvent) 18 | { 19 | databaseService.InsertIntoCollection(CollectionName, plcEvent); 20 | } 21 | 22 | public PlcEvent[] GetHistory(int elements, int page) 23 | { 24 | return databaseService.GetCollection(CollectionName, 25 | _ => true, 26 | e => e.Timestamp, 27 | true, 28 | page * elements, 29 | elements) 30 | .ToArray(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Federico Barresi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /WpfApp.Gui/ViewModels/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive; 2 | using System.Threading.Tasks; 3 | using ReactiveUI; 4 | using WpfApp.Gui.Design; 5 | using WpfApp.Interfaces.Extensions; 6 | using WpfApp.Interfaces.Services; 7 | using WpfApp.Interfaces.Settings; 8 | 9 | namespace WpfApp.Gui.ViewModels 10 | { 11 | public class SettingsViewModel : ViewModelBase 12 | { 13 | private readonly ISettingsProvider settingsProvider; 14 | public ApplicationSetting Setting { get; } 15 | 16 | public ReactiveCommand SaveSettings { get; set; } 17 | 18 | public SettingsViewModel(ApplicationSetting setting, ISettingsProvider settingsProvider) 19 | { 20 | this.settingsProvider = settingsProvider; 21 | Setting = setting; 22 | } 23 | protected override void Initialize() 24 | { 25 | SaveSettings = ReactiveCommand.CreateFromTask(Save).AddDisposableTo(Disposables); 26 | } 27 | 28 | private Task Save() 29 | { 30 | settingsProvider.SaveSettings(); 31 | return Task.FromResult(Unit.Default); 32 | } 33 | } 34 | 35 | internal class DesignSettingsViewModel : SettingsViewModel 36 | { 37 | public DesignSettingsViewModel() : base(new ApplicationSetting(), new DesignSettingsProvider()) 38 | { 39 | Init(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /WpfApp.Logic/Services/DirectoryService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.Extensions.Logging; 4 | using WpfApp.Interfaces; 5 | using WpfApp.Interfaces.Services; 6 | 7 | namespace WpfApp.Logic.Services 8 | { 9 | public class DirectoryService : IDirectoryService 10 | { 11 | public string DatabaseFolder { get; } 12 | public string SettingsFolder { get; } 13 | public string ApplicationFolder { get; } 14 | 15 | public DirectoryService() 16 | { 17 | var basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), 18 | Constants.ApplicationName); 19 | ApplicationFolder = basePath; 20 | 21 | DatabaseFolder = Path.Combine(basePath, Constants.DatabaseDirectory); 22 | SettingsFolder = Path.Combine(basePath, Constants.SettingsDirectory); 23 | LogsFolder = Path.Combine(basePath, Constants.LoggerDirectory); 24 | CreateDirectoryIfNotExists(DatabaseFolder); 25 | CreateDirectoryIfNotExists(SettingsFolder); 26 | CreateDirectoryIfNotExists(LogsFolder); 27 | } 28 | 29 | private void CreateDirectoryIfNotExists(string path) 30 | { 31 | if (!Directory.Exists(path)) 32 | { 33 | Directory.CreateDirectory(path); 34 | } 35 | } 36 | 37 | public string LogsFolder { get; } 38 | } 39 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Contents.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | text/microsoft-resx 11 | 12 | 13 | 1.3 14 | 15 | 16 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17 | 18 | 19 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 20 | 21 | 22 | Lamp 23 | 24 | 25 | Description 26 | 27 | 28 | Save 29 | 30 | 31 | Settings 32 | 33 | 34 | Button 35 | 36 | -------------------------------------------------------------------------------- /WpfApp.Interfaces/Extensions/RandomExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WpfApp.Interfaces.Extensions 4 | { 5 | public static class RandomExtensions 6 | { 7 | public static T GetData(this Random random) 8 | { 9 | var type = typeof(T); 10 | switch (Type.GetTypeCode(type)) 11 | { 12 | case TypeCode.Boolean: 13 | return (T)Convert.ChangeType(random.Next() % 2 == 0, type); 14 | case TypeCode.Char: 15 | case TypeCode.SByte: 16 | case TypeCode.Byte: 17 | case TypeCode.Int16: 18 | case TypeCode.UInt16: 19 | case TypeCode.Int32: 20 | case TypeCode.UInt32: 21 | case TypeCode.Int64: 22 | case TypeCode.UInt64: 23 | return (T)Convert.ChangeType(random.Next(), type); 24 | case TypeCode.Single: 25 | case TypeCode.Double: 26 | case TypeCode.Decimal: 27 | return (T)Convert.ChangeType(random.NextDouble(), type); 28 | case TypeCode.DateTime: 29 | return (T)Convert.ChangeType(DateTime.Now, type); 30 | case TypeCode.String: 31 | return (T)Convert.ChangeType(Guid.NewGuid().ToString(), type); 32 | default: 33 | return default(T); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /WpfApp.Gui/Contents.de.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | text/microsoft-resx 11 | 12 | 13 | 1.3 14 | 15 | 16 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 17 | 18 | 19 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 20 | 21 | 22 | Beschreibung 23 | 24 | 25 | Lampe 26 | 27 | 28 | Speichern 29 | 30 | 31 | Einstellungen 32 | 33 | 34 | Knopf 35 | 36 | -------------------------------------------------------------------------------- /WpfApp.Gui/Views/Basics/PlcButton.xaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | 18 |