├── .gitattributes
├── .gitignore
├── CoAPExplorer.sln
├── Notes.md
├── Readme.md
├── Tests
└── CoAPExplorer.WPF.Tests
│ ├── CoAPExplorer.WPF.Tests.csproj
│ ├── ConverterTests.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ └── packages.config
├── Tools
└── Database
│ ├── CoapExplorerContext.cs
│ ├── Database.csproj
│ ├── Migrations
│ ├── 20180516004057_Initial.Designer.cs
│ ├── 20180516004057_Initial.cs
│ └── CoapExplorerContextModelSnapshot.cs
│ └── Program.cs
├── appveyor.yml
├── nuget.config
└── src
├── CoAPExplorer.WPF
├── App.config
├── App.xaml
├── App.xaml.cs
├── CoAPExplorer.WPF.csproj
├── Consts.cs
├── Controls
│ ├── AppBar.cs
│ ├── Behaviors
│ │ └── TextFieldBehavior.cs
│ ├── CoapOptionsList.xaml
│ ├── CoapOptionsList.xaml.cs
│ ├── DeviceListView.cs
│ ├── FilterOption.xaml
│ ├── FilterOption.xaml.cs
│ ├── NavigationDrawer.cs
│ └── NavigationItem.cs
├── Converters
│ ├── BoolToVisibilityConverter.cs
│ ├── CoapExplorerIconConverter.cs
│ ├── CoapMessageCodeToStringConverter.cs
│ ├── CoapOptionTypeToNameConverter.cs
│ ├── HextoAsciiConverter.cs
│ ├── InvertBooleanConverter.cs
│ ├── RelativeDateTimeConverter.cs
│ └── UIElementVisibilityToUnsetValueConverter.cs
├── Dialogs
│ ├── NewDeviceViewDialog.xaml
│ └── NewDeviceViewDialog.xaml.cs
├── Extensions
│ └── DependencyObjectExtensions.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── MockViewModels
│ ├── DeviceListMock.cs
│ ├── DeviceNavigationViewModel.cs
│ ├── DeviceViewModel.cs
│ ├── MockCoapOptionsList.cs
│ ├── MockHomeView.cs
│ └── NavigationViewModel.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Resources
│ ├── JSONFormat.xml
│ ├── LinkFormat.xml
│ └── logo.ico
├── Services
│ ├── AvalonEditTextMarkerService.cs
│ └── CoapFormatHighlightManager.cs
├── Themes
│ ├── AppBar.xaml
│ ├── DeviceListView.xaml
│ └── NavigationDrawer.xaml
└── Views
│ ├── DeviceNavigationView.xaml
│ ├── DeviceNavigationView.xaml.cs
│ ├── DeviceView.xaml
│ ├── DeviceView.xaml.cs
│ ├── HomeView.xaml
│ ├── HomeView.xaml.cs
│ ├── MessageRequestView.xaml
│ ├── MessageRequestView.xaml.cs
│ ├── MessageResponseView.xaml
│ ├── MessageResponseView.xaml.cs
│ ├── NavigationView.xaml
│ ├── NavigationView.xaml.cs
│ ├── RecentDevicesView.xaml
│ ├── RecentDevicesView.xaml.cs
│ ├── SearchView.xaml
│ └── SearchView.xaml.cs
└── CoAPExplorer
├── App.cs
├── CoAPExplorer.csproj
├── CoapIcon.cs
├── Database
├── CoapExplorerContext.cs
├── CoapOptionSerialiser.cs
└── Migrations
│ ├── 20180516004057_Initial.Designer.cs
│ ├── 20180516004057_Initial.cs
│ └── CoapExplorerContextModelSnapshot.cs
├── EndpointType.cs
├── Extensions
├── CoapMessageExtensions.cs
├── EnumExtensions.cs
└── ReactiveLoggerExtensions.cs
├── FormattedTextException.cs
├── Models
├── Device.cs
├── DeviceResource.cs
├── Message.cs
├── NavigationItem.cs
├── RequestFilter.cs
└── ToastNotification.cs
├── Properties
└── AssemblyInfo.cs
├── Services
├── CoapEndpointFactory.cs
├── CoapService.cs
├── DiscoveryService.cs
├── IDiscoveryService.cs
└── MyDebugLogger.cs
├── Utils
├── CoapPayloadFormatConverter.cs
└── StringEscape.cs
└── ViewModels
├── DeviceNavigationViewModel.cs
├── DeviceViewModel.cs
├── HomeViewModel.cs
├── MessageViewModel.cs
├── NavigationViewModel.cs
├── NewDeviceViewModel.cs
├── RecentDevicesViewModel.cs
└── SearchViewModel.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 | *.[Dd]esigner.cs
198 |
199 | # Since there are multiple workflows, uncomment next line to ignore bower_components
200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
201 | #bower_components/
202 |
203 | # RIA/Silverlight projects
204 | Generated_Code/
205 |
206 | # Backup & report files from converting an old project file
207 | # to a newer Visual Studio version. Backup files are not needed,
208 | # because we have git ;-)
209 | _UpgradeReport_Files/
210 | Backup*/
211 | UpgradeLog*.XML
212 | UpgradeLog*.htm
213 |
214 | # SQL Server files
215 | *.mdf
216 | *.ldf
217 |
218 | # Business Intelligence projects
219 | *.rdl.data
220 | *.bim.layout
221 | *.bim_*.settings
222 |
223 | # Microsoft Fakes
224 | FakesAssemblies/
225 |
226 | # GhostDoc plugin setting file
227 | *.GhostDoc.xml
228 |
229 | # Node.js Tools for Visual Studio
230 | .ntvs_analysis.dat
231 |
232 | # Visual Studio 6 build log
233 | *.plg
234 |
235 | # Visual Studio 6 workspace options file
236 | *.opt
237 |
238 | # Visual Studio LightSwitch build output
239 | **/*.HTMLClient/GeneratedArtifacts
240 | **/*.DesktopClient/GeneratedArtifacts
241 | **/*.DesktopClient/ModelManifest.xml
242 | **/*.Server/GeneratedArtifacts
243 | **/*.Server/ModelManifest.xml
244 | _Pvt_Extensions
245 |
246 | # Paket dependency manager
247 | .paket/paket.exe
248 | paket-files/
249 |
250 | # FAKE - F# Make
251 | .fake/
252 |
253 | # JetBrains Rider
254 | .idea/
255 | *.sln.iml
256 |
257 | # CodeRush
258 | .cr/
259 |
260 | # Python Tools for Visual Studio (PTVS)
261 | __pycache__/
262 | *.pyc
--------------------------------------------------------------------------------
/CoAPExplorer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2036
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoAPExplorer.WPF", "src\CoAPExplorer.WPF\CoAPExplorer.WPF.csproj", "{9BEC4D6B-5BF2-4893-9E1E-17882B13D5E6}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02E79D79-5DE2-4FD2-921B-643147B0FDA7}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoAPExplorer", "src\CoAPExplorer\CoAPExplorer.csproj", "{A18BDC5C-947F-4ACB-9413-F85E78D2704E}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F2D4903E-1237-405F-B41A-C94931BAECEE}"
13 | ProjectSection(SolutionItems) = preProject
14 | .gitignore = .gitignore
15 | appveyor.yml = appveyor.yml
16 | Notes.md = Notes.md
17 | nuget.config = nuget.config
18 | Readme.md = Readme.md
19 | EndProjectSection
20 | EndProject
21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{6E6E4765-D2A5-43F8-BD2C-669AF6DCC78D}"
22 | EndProject
23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Database", "Tools\Database\Database.csproj", "{B1BD0B92-5936-4E98-B13E-CD5D5D239A04}"
24 | EndProject
25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9C82C172-B4A0-4C94-ADB3-ED7CB18C8439}"
26 | EndProject
27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoAPExplorer.WPF.Tests", "Tests\CoAPExplorer.WPF.Tests\CoAPExplorer.WPF.Tests.csproj", "{A6D994B5-DC66-466D-A460-F5042EDA4D5F}"
28 | EndProject
29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Media", "Media", "{C3297368-6E89-4E86-8386-79807CC31156}"
30 | EndProject
31 | Global
32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
33 | Debug|Any CPU = Debug|Any CPU
34 | Release|Any CPU = Release|Any CPU
35 | EndGlobalSection
36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
37 | {9BEC4D6B-5BF2-4893-9E1E-17882B13D5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {9BEC4D6B-5BF2-4893-9E1E-17882B13D5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {9BEC4D6B-5BF2-4893-9E1E-17882B13D5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {9BEC4D6B-5BF2-4893-9E1E-17882B13D5E6}.Release|Any CPU.Build.0 = Release|Any CPU
41 | {A18BDC5C-947F-4ACB-9413-F85E78D2704E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {A18BDC5C-947F-4ACB-9413-F85E78D2704E}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {A18BDC5C-947F-4ACB-9413-F85E78D2704E}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {A18BDC5C-947F-4ACB-9413-F85E78D2704E}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {B1BD0B92-5936-4E98-B13E-CD5D5D239A04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {B1BD0B92-5936-4E98-B13E-CD5D5D239A04}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {B1BD0B92-5936-4E98-B13E-CD5D5D239A04}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {B1BD0B92-5936-4E98-B13E-CD5D5D239A04}.Release|Any CPU.Build.0 = Release|Any CPU
49 | {A6D994B5-DC66-466D-A460-F5042EDA4D5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {A6D994B5-DC66-466D-A460-F5042EDA4D5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {A6D994B5-DC66-466D-A460-F5042EDA4D5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {A6D994B5-DC66-466D-A460-F5042EDA4D5F}.Release|Any CPU.Build.0 = Release|Any CPU
53 | EndGlobalSection
54 | GlobalSection(SolutionProperties) = preSolution
55 | HideSolutionNode = FALSE
56 | EndGlobalSection
57 | GlobalSection(NestedProjects) = preSolution
58 | {9BEC4D6B-5BF2-4893-9E1E-17882B13D5E6} = {02E79D79-5DE2-4FD2-921B-643147B0FDA7}
59 | {A18BDC5C-947F-4ACB-9413-F85E78D2704E} = {02E79D79-5DE2-4FD2-921B-643147B0FDA7}
60 | {B1BD0B92-5936-4E98-B13E-CD5D5D239A04} = {6E6E4765-D2A5-43F8-BD2C-669AF6DCC78D}
61 | {A6D994B5-DC66-466D-A460-F5042EDA4D5F} = {9C82C172-B4A0-4C94-ADB3-ED7CB18C8439}
62 | {C3297368-6E89-4E86-8386-79807CC31156} = {F2D4903E-1237-405F-B41A-C94931BAECEE}
63 | EndGlobalSection
64 | GlobalSection(ExtensibilityGlobals) = postSolution
65 | SolutionGuid = {F1A27755-63B4-411F-A681-40D8C089EBA6}
66 | EndGlobalSection
67 | EndGlobal
68 |
--------------------------------------------------------------------------------
/Notes.md:
--------------------------------------------------------------------------------
1 | ## Discovery
2 | - Networks
3 | - Broadcast (i.e. 255.255.255.255)
4 | - Multicast IPv4/IPv6
5 | - `/.well-known/core`
6 | - Filter parameters (rt=)
7 | - CoRE resource Directory
8 | - See draft-ietf-core-resource-directory
9 | - Save last search urls
10 | ---
11 |
12 | - Investigate User Input Validation
13 |
14 | Create a App class that will be inherited by the Xamarin.Forms App class.
15 | - sets up viewmodels and reactive views
16 | - other DI stuff
17 |
18 | Coap.Net
19 | - If your message has options set, but forgot to set the Code to anyhting other than None, there's no indication of failure or forgotten field. Maybe log it? or be explicit?
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # CoAP Explorer
2 |
3 | [](https://ci.appveyor.com/project/NZSmartie/coapexplorer/branch/master)
4 |
5 | Work in Progress App for interacting with CoAP devices. Soon to be cross platform, for now, is targeting Windows.
6 |
7 | Thanks To:
8 | - [ReactiveUI](https://github.com/reactiveui/ReactiveUI/) - Reactive Style UI
9 | - [ReactiveUI.Routing](https://github.com/KallynGowdy/ReactiveUI.Routing) - Better cross platform routing library for ReactiveUI
10 | - [Material Deisgn Toolkit](https://github.com/ButchersBoy/MaterialDesignInXamlToolkit) Google's Material Design for WIndows Presentation Framework
11 | - [AvalonEdit](https://github.com/icsharpcode/AvalonEdit) - Text Highlighter for WPF
12 | - [CoAP.Net](https://github.com/NZSmartie/CoAP.Net/) - My very own CoAP library
13 |
14 | Latest nightly builds for Windows can be downloaded straight from AppVeyor - https://ci.appveyor.com/project/NZSmartie/coapexplorer/build/artifacts
15 |
16 | ## Goals
17 |
18 | - Cross Platform
19 | - Using the same concepts from Xamarin Apps, the core functionality is in the schared project (Targeting .Net Standard)
20 | - Device Discovery
21 | - [X] UDP Multicast Discovery.
22 | - [ ] TODO: Suport more transports.
23 | - Fully functioal message editor
24 | - Support various content types
25 | - Saving message requests or responses
26 | - Easy UI
27 |
28 | ## Screen Grabs
29 |
30 |
--------------------------------------------------------------------------------
/Tests/CoAPExplorer.WPF.Tests/CoAPExplorer.WPF.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Debug
7 | AnyCPU
8 | {A6D994B5-DC66-466D-A460-F5042EDA4D5F}
9 | Library
10 | Properties
11 | CoAPExplorer.WPF.Tests
12 | CoAPExplorer.WPF.Tests
13 | v4.7.1
14 | 512
15 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
16 | 15.0
17 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
18 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
19 | False
20 | UnitTest
21 |
22 |
23 |
24 |
25 |
26 | true
27 | full
28 | false
29 | bin\Debug\
30 | DEBUG;TRACE
31 | prompt
32 | 4
33 | latest
34 |
35 |
36 | pdbonly
37 | true
38 | bin\Release\
39 | TRACE
40 | prompt
41 | 4
42 | latest
43 |
44 |
45 |
46 | ..\..\packages\NUnit.3.10.1\lib\net45\nunit.framework.dll
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {9bec4d6b-5bf2-4893-9e1e-17882b13d5e6}
61 | CoAPExplorer.WPF
62 |
63 |
64 |
65 |
66 |
67 |
68 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Tests/CoAPExplorer.WPF.Tests/ConverterTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using CoAPExplorer.WPF.Converters;
4 | using NUnit.Framework;
5 |
6 |
7 | namespace CoAPExplorer.WPF.Tests
8 | {
9 | [TestFixture]
10 | public class ConverterTests
11 | {
12 | [TestCase(new byte[] { 0x12, 0x34 }, 2, ExpectedResult = "12 34")]
13 | [TestCase(new byte[] { 0x12, 0x34 }, 4, ExpectedResult = "12 34 ")]
14 | [TestCase(new byte[] { 0x1}, 4, ExpectedResult = "1")]
15 | [TestCase(new byte[] { 0x12 }, 4, ExpectedResult = "12 ")]
16 | [TestCase(new byte[] { }, 4, ExpectedResult = "")]
17 | [TestCase(new byte[] { 0x0 }, 1, ExpectedResult = "0")]
18 | [TestCase(new byte[] { 0x15, 0x1F }, 1, ExpectedResult = "15")]
19 | public string HexToAsciiConverter_Convert(byte[] data, int maxBytes)
20 | {
21 | var converter = new HextoAsciiConverter();
22 |
23 | return converter.Convert(data, typeof(string), maxBytes, CultureInfo.CurrentCulture);
24 | }
25 |
26 | [TestCase("12 34", 2, ExpectedResult = new byte[] { 0x12, 0x34 })]
27 | [TestCase("12 34 ", 4, ExpectedResult = new byte[] { 0x12, 0x34 })]
28 | [TestCase("1", 4, ExpectedResult = new byte[] { 0x1 })]
29 | [TestCase("12 ", 4, ExpectedResult = new byte[] { 0x12 })]
30 | [TestCase("", 4, ExpectedResult = new byte[] { })]
31 | [TestCase("0", 1, ExpectedResult = new byte[] { 0x0 })]
32 | [TestCase("15 1F", 1, ExpectedResult = new byte[] { 0x15 })]
33 | public byte[] HexToAsciiConverter_ConvertBack(string data, int maxBytes)
34 | {
35 | var converter = new HextoAsciiConverter();
36 |
37 | return converter.ConvertBack(data, typeof(string), maxBytes, CultureInfo.CurrentCulture);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Tests/CoAPExplorer.WPF.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("CoAPExplorer.WPF.Tests")]
6 | [assembly: AssemblyDescription("")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("")]
9 | [assembly: AssemblyProduct("CoAPExplorer.WPF.Tests")]
10 | [assembly: AssemblyCopyright("Copyright © 2018")]
11 | [assembly: AssemblyTrademark("")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | [assembly: ComVisible(false)]
15 |
16 | [assembly: Guid("a6d994b5-dc66-466d-a460-f5042eda4d5f")]
17 |
18 | // [assembly: AssemblyVersion("1.0.*")]
19 | [assembly: AssemblyVersion("1.0.0.0")]
20 | [assembly: AssemblyFileVersion("1.0.0.0")]
21 |
--------------------------------------------------------------------------------
/Tests/CoAPExplorer.WPF.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Tools/Database/CoapExplorerContext.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.Models;
2 | using CoAPExplorer.Services;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Design;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 |
9 | namespace CoAPExplorer.Database
10 | {
11 | public class CoapExplorerContextFactory : IDesignTimeDbContextFactory
12 | {
13 | public CoapExplorerContext CreateDbContext(string[] args)
14 | {
15 | var optionsBuilder = new DbContextOptionsBuilder();
16 | optionsBuilder.UseSqlite("Filename=something.db", b => b.MigrationsAssembly("Database"));
17 |
18 | return new CoapExplorerContext(optionsBuilder.Options);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tools/Database/Database.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 | CoAPExplorer.Database
7 | latest
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Tools/Database/Migrations/20180516004057_Initial.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using CoAPExplorer;
3 | using CoAPExplorer.Database;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage;
9 | using Microsoft.EntityFrameworkCore.Storage.Internal;
10 | using System;
11 |
12 | namespace CoAPExplorer.Database.Migrations
13 | {
14 | [DbContext(typeof(CoapExplorerContext))]
15 | [Migration("20180516004057_Initial")]
16 | partial class Initial
17 | {
18 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
19 | {
20 | #pragma warning disable 612, 618
21 | modelBuilder
22 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011");
23 |
24 | modelBuilder.Entity("CoAPExplorer.Models.Device", b =>
25 | {
26 | b.Property("Id")
27 | .ValueGeneratedOnAdd();
28 |
29 | b.Property("EndpointType");
30 |
31 | b.Property("IsFavourite");
32 |
33 | b.Property("LastSeen");
34 |
35 | b.Property("Name");
36 |
37 | b.Property("_dbAddress")
38 | .HasColumnName("Address");
39 |
40 | b.HasKey("Id");
41 |
42 | b.ToTable("Devices");
43 | });
44 |
45 | modelBuilder.Entity("CoAPExplorer.Models.DeviceResource", b =>
46 | {
47 | b.Property("Id")
48 | .ValueGeneratedOnAdd();
49 |
50 | b.Property("DeviceId");
51 |
52 | b.Property("Name");
53 |
54 | b.Property("_dbContentFormat")
55 | .HasColumnName("ContentFormat");
56 |
57 | b.Property("_dbUrl")
58 | .HasColumnName("Url");
59 |
60 | b.HasKey("Id");
61 |
62 | b.HasIndex("DeviceId");
63 |
64 | b.ToTable("DeviceResource");
65 | });
66 |
67 | modelBuilder.Entity("CoAPExplorer.Models.Message", b =>
68 | {
69 | b.Property("Id")
70 | .ValueGeneratedOnAdd();
71 |
72 | b.Property("Payload");
73 |
74 | b.Property("_dbCode")
75 | .HasColumnName("Code");
76 |
77 | b.Property("_dbContentFormat")
78 | .HasColumnName("ContentFormat");
79 |
80 | b.Property("_dbOptions")
81 | .HasColumnName("Options");
82 |
83 | b.Property("_dbUrl")
84 | .HasColumnName("Url");
85 |
86 | b.HasKey("Id");
87 |
88 | b.ToTable("RecentMessages");
89 | });
90 |
91 | modelBuilder.Entity("CoAPExplorer.Models.DeviceResource", b =>
92 | {
93 | b.HasOne("CoAPExplorer.Models.Device", "Device")
94 | .WithMany("KnownResources")
95 | .HasForeignKey("DeviceId");
96 | });
97 | #pragma warning restore 612, 618
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Tools/Database/Migrations/20180516004057_Initial.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace CoAPExplorer.Database.Migrations
6 | {
7 | public partial class Initial : Migration
8 | {
9 | protected override void Up(MigrationBuilder migrationBuilder)
10 | {
11 | migrationBuilder.CreateTable(
12 | name: "Devices",
13 | columns: table => new
14 | {
15 | Id = table.Column(nullable: false)
16 | .Annotation("Sqlite:Autoincrement", true),
17 | EndpointType = table.Column(nullable: false),
18 | IsFavourite = table.Column(nullable: false),
19 | LastSeen = table.Column(nullable: false),
20 | Name = table.Column(nullable: true),
21 | Address = table.Column(nullable: true)
22 | },
23 | constraints: table =>
24 | {
25 | table.PrimaryKey("PK_Devices", x => x.Id);
26 | });
27 |
28 | migrationBuilder.CreateTable(
29 | name: "RecentMessages",
30 | columns: table => new
31 | {
32 | Id = table.Column(nullable: false)
33 | .Annotation("Sqlite:Autoincrement", true),
34 | Payload = table.Column(nullable: true),
35 | Code = table.Column(nullable: true),
36 | ContentFormat = table.Column(nullable: true),
37 | Options = table.Column(nullable: true),
38 | Url = table.Column(nullable: true)
39 | },
40 | constraints: table =>
41 | {
42 | table.PrimaryKey("PK_RecentMessages", x => x.Id);
43 | });
44 |
45 | migrationBuilder.CreateTable(
46 | name: "DeviceResource",
47 | columns: table => new
48 | {
49 | Id = table.Column(nullable: false)
50 | .Annotation("Sqlite:Autoincrement", true),
51 | DeviceId = table.Column(nullable: true),
52 | Name = table.Column(nullable: true),
53 | ContentFormat = table.Column(nullable: true),
54 | Url = table.Column(nullable: true)
55 | },
56 | constraints: table =>
57 | {
58 | table.PrimaryKey("PK_DeviceResource", x => x.Id);
59 | table.ForeignKey(
60 | name: "FK_DeviceResource_Devices_DeviceId",
61 | column: x => x.DeviceId,
62 | principalTable: "Devices",
63 | principalColumn: "Id",
64 | onDelete: ReferentialAction.Restrict);
65 | });
66 |
67 | migrationBuilder.CreateIndex(
68 | name: "IX_DeviceResource_DeviceId",
69 | table: "DeviceResource",
70 | column: "DeviceId");
71 | }
72 |
73 | protected override void Down(MigrationBuilder migrationBuilder)
74 | {
75 | migrationBuilder.DropTable(
76 | name: "DeviceResource");
77 |
78 | migrationBuilder.DropTable(
79 | name: "RecentMessages");
80 |
81 | migrationBuilder.DropTable(
82 | name: "Devices");
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Tools/Database/Migrations/CoapExplorerContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using CoAPExplorer;
3 | using CoAPExplorer.Database;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage;
9 | using Microsoft.EntityFrameworkCore.Storage.Internal;
10 | using System;
11 |
12 | namespace CoAPExplorer.Database.Migrations
13 | {
14 | [DbContext(typeof(CoapExplorerContext))]
15 | partial class CoapExplorerContextModelSnapshot : ModelSnapshot
16 | {
17 | protected override void BuildModel(ModelBuilder modelBuilder)
18 | {
19 | #pragma warning disable 612, 618
20 | modelBuilder
21 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011");
22 |
23 | modelBuilder.Entity("CoAPExplorer.Models.Device", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd();
27 |
28 | b.Property("EndpointType");
29 |
30 | b.Property("IsFavourite");
31 |
32 | b.Property("LastSeen");
33 |
34 | b.Property("Name");
35 |
36 | b.Property("_dbAddress")
37 | .HasColumnName("Address");
38 |
39 | b.HasKey("Id");
40 |
41 | b.ToTable("Devices");
42 | });
43 |
44 | modelBuilder.Entity("CoAPExplorer.Models.DeviceResource", b =>
45 | {
46 | b.Property("Id")
47 | .ValueGeneratedOnAdd();
48 |
49 | b.Property("DeviceId");
50 |
51 | b.Property("Name");
52 |
53 | b.Property("_dbContentFormat")
54 | .HasColumnName("ContentFormat");
55 |
56 | b.Property("_dbUrl")
57 | .HasColumnName("Url");
58 |
59 | b.HasKey("Id");
60 |
61 | b.HasIndex("DeviceId");
62 |
63 | b.ToTable("DeviceResource");
64 | });
65 |
66 | modelBuilder.Entity("CoAPExplorer.Models.Message", b =>
67 | {
68 | b.Property("Id")
69 | .ValueGeneratedOnAdd();
70 |
71 | b.Property("Payload");
72 |
73 | b.Property("_dbCode")
74 | .HasColumnName("Code");
75 |
76 | b.Property("_dbContentFormat")
77 | .HasColumnName("ContentFormat");
78 |
79 | b.Property("_dbOptions")
80 | .HasColumnName("Options");
81 |
82 | b.Property("_dbUrl")
83 | .HasColumnName("Url");
84 |
85 | b.HasKey("Id");
86 |
87 | b.ToTable("RecentMessages");
88 | });
89 |
90 | modelBuilder.Entity("CoAPExplorer.Models.DeviceResource", b =>
91 | {
92 | b.HasOne("CoAPExplorer.Models.Device", "Device")
93 | .WithMany("KnownResources")
94 | .HasForeignKey("DeviceId");
95 | });
96 | #pragma warning restore 612, 618
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Tools/Database/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Database
4 | {
5 | class Program
6 | {
7 | static void Main(string[] args)
8 | {
9 | Console.WriteLine("Hello World!");
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 0.0.1-pre{build}
2 | image: Visual Studio 2017
3 |
4 | configuration: Release
5 |
6 | before_build:
7 | - nuget restore
8 |
9 | build:
10 | verbosity: minimal
11 |
12 | test: off
13 |
14 | artifacts:
15 | - path: src/CoAPExplorer.WPF/bin/$(configuration)/
16 | name: Windows Binaries
17 |
18 |
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/App.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 |
10 | using CoAPNet;
11 | using CoAPNet.Udp;
12 | using Microsoft.EntityFrameworkCore;
13 | using ReactiveUI;
14 | using Splat;
15 |
16 | using CoAPExplorer.Database;
17 | using CoAPExplorer.Extensions;
18 | using CoAPExplorer.Services;
19 | using CoAPExplorer.ViewModels;
20 | using CoAPExplorer.WPF.Views;
21 | using CoAPExplorer.WPF.Dialogs;
22 |
23 | namespace CoAPExplorer.WPF
24 | {
25 | ///
26 | /// Interaction logic for App.xaml
27 | ///
28 | public partial class App : Application
29 | {
30 | private readonly CoAPExplorer.App _coapExplorer;
31 | private readonly CoapExplorerContext _database;
32 |
33 | public static CoAPExplorer.App CoapExplorer => (Current as App)._coapExplorer;
34 |
35 | public const string DatabaseName = "database.db";
36 |
37 | public App()
38 | {
39 | // Inistalise our application's data directory
40 | var applicationPath = new DirectoryInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CoAPExplorer"));
41 | if (!applicationPath.Exists)
42 | applicationPath.Create();
43 |
44 | // Shared application class that is used in other platforms.
45 | _coapExplorer = new CoAPExplorer.App(applicationPath.FullName);
46 |
47 | // TODO: Make this configurable? as to make this application portable?
48 | var databasePath = Path.Combine(applicationPath.FullName, DatabaseName);
49 | _database = new CoapExplorerContext(databasePath);
50 | _database.Database.Migrate();
51 |
52 | // Register Services
53 | _coapExplorer.Locator.RegisterConstant(_coapExplorer);
54 | _coapExplorer.Locator.RegisterConstant(_database);
55 |
56 | // Register Views
57 | _coapExplorer.Locator.Register>(() => new HomeView());
58 | _coapExplorer.Locator.Register>(() => new RecentDevicesView());
59 | _coapExplorer.Locator.Register>(() => new SearchView());
60 | _coapExplorer.Locator.Register>(() => new DeviceView());
61 | _coapExplorer.Locator.Register>(() => new NavigationView());
62 | _coapExplorer.Locator.Register>(() => new DeviceNavigationView());
63 |
64 | // Dialogs
65 | _coapExplorer.Locator.Register>(() => new NewDeviceViewDialog());
66 |
67 | //_coapExplorer.Services
68 | // .RegisterConstant(new CoapContext(databasePath));
69 | }
70 |
71 | protected override void OnExit(ExitEventArgs e)
72 | {
73 | _database.Dispose();
74 | base.OnExit(e);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Consts.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.WPF.Converters;
2 | using CoAPNet;
3 | using CoAPNet.Options;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 |
8 | namespace CoAPExplorer.WPF
9 | {
10 | public class Consts
11 | {
12 | public static IReadOnlyList> MessageCodes { get; } = new List>
13 | (
14 | new[]
15 | {
16 | Tuple.Create( "None",CoapMessageCode.None),
17 | Tuple.Create( "Proxying Not Supported",CoapMessageCode. ProxyingNotSupported),
18 | Tuple.Create( "Gateway Timeout",CoapMessageCode. GatewayTimeout),
19 | Tuple.Create( "Service Unavailable",CoapMessageCode. ServiceUnavailable),
20 | Tuple.Create( "Bad Gateway",CoapMessageCode. BadGateway),
21 | Tuple.Create( "Not Implemented",CoapMessageCode. NotImplemented),
22 | Tuple.Create( "Internal Server Error",CoapMessageCode. InternalServerError),
23 | Tuple.Create( "Unsupported Content Format",CoapMessageCode. UnsupportedContentFormat),
24 | Tuple.Create( "Precondition Failed",CoapMessageCode. PreconditionFailed),
25 | Tuple.Create( "Request Entity Incomplete",CoapMessageCode. RequestEntityIncomplete),
26 | Tuple.Create( "Not Acceptable",CoapMessageCode. NotAcceptable),
27 | Tuple.Create( "Method Not Allowed",CoapMessageCode. MethodNotAllowed),
28 | Tuple.Create( "Not Found",CoapMessageCode. NotFound),
29 | Tuple.Create( "Forbidden",CoapMessageCode. Forbidden),
30 | Tuple.Create( "Request Entity Too Large",CoapMessageCode. RequestEntityTooLarge),
31 | Tuple.Create( "Unauthorized",CoapMessageCode. Unauthorized),
32 | Tuple.Create( "Bad Option",CoapMessageCode. BadOption),
33 | Tuple.Create( "Post",CoapMessageCode. Post),
34 | Tuple.Create( "Put",CoapMessageCode. Put),
35 | Tuple.Create( "Delete",CoapMessageCode. Delete),
36 | Tuple.Create( "Created",CoapMessageCode. Created),
37 | Tuple.Create( "Deleted",CoapMessageCode. Deleted),
38 | Tuple.Create( "Get",CoapMessageCode. Get),
39 | Tuple.Create( "Changed",CoapMessageCode. Changed),
40 | Tuple.Create( "Content",CoapMessageCode. Content),
41 | Tuple.Create( "Continue",CoapMessageCode. Continue),
42 | Tuple.Create( "Bad Request",CoapMessageCode. BadRequest),
43 | Tuple.Create( "Valid",CoapMessageCode. Valid),
44 | }
45 | .Select(t => Tuple.Create($"{t.Item2} - {t.Item1.ToUpper()}", t.Item2))
46 | );
47 |
48 | private static IReadOnlyList> _requestMessageCodes;
49 | public static IReadOnlyList> RequestMessageCodes
50 | => _requestMessageCodes ?? (_requestMessageCodes = MessageCodes.Where(t => t.Item2.IsRequest()).OrderBy(t => t.Item2.Detail).ToList());
51 |
52 | public static IReadOnlyList> ContentTypes { get; } = new List>
53 | (
54 | new[]
55 | {
56 | ContentFormatType.TextPlain,
57 | ContentFormatType.ApplicationLinkFormat,
58 | ContentFormatType.ApplicationXml,
59 | ContentFormatType.ApplicationOctetStream,
60 | ContentFormatType.ApplicationExi,
61 | ContentFormatType.ApplicationJson,
62 | ContentFormatType.ApplicationCbor
63 | }
64 | .Select(cf => Tuple.Create($"{cf.Value} - {cf.Name}", cf))
65 | .Prepend(Tuple.Create("(none)", default(ContentFormatType)))
66 | );
67 |
68 | public static IReadOnlyList> CoapOptionTypes { get; } = new List>
69 | {
70 | Tuple.Create("URI Host",typeof(UriHost)),
71 | Tuple.Create("URI Port",typeof(UriPort)),
72 | Tuple.Create("URI Path",typeof(UriPath)),
73 | Tuple.Create("URI Query",typeof(UriQuery)),
74 | Tuple.Create("Proxy URI",typeof(ProxyUri)),
75 | Tuple.Create("Proxy Scheme",typeof(ProxyScheme)),
76 | Tuple.Create("Location Path",typeof(LocationPath)),
77 | Tuple.Create("Location Query",typeof(LocationQuery)),
78 | Tuple.Create("Content Format",typeof(ContentFormat)),
79 | Tuple.Create("Accept",typeof(Accept)),
80 | Tuple.Create("Max Age",typeof(MaxAge)),
81 | Tuple.Create("ETag",typeof(ETag)),
82 | Tuple.Create("Size1",typeof(Size1)),
83 | Tuple.Create("Size2",typeof(Size2)),
84 | Tuple.Create("If Match",typeof(IfMatch)),
85 | Tuple.Create("If None Match",typeof(IfNoneMatch)),
86 | Tuple.Create("Block1",typeof(Block1)),
87 | Tuple.Create("Block2",typeof(Block2)),
88 | };
89 |
90 | public static IReadOnlyList CoapBlockSupportedSizes { get; } = CoAPNet.Options.BlockBase.SupportedBlockSizes;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Controls/Behaviors/TextFieldBehavior.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Input;
9 |
10 | namespace CoAPExplorer.WPF.Controls.Behaviors
11 | {
12 | public enum TrippleClickBehavior
13 | {
14 | None,
15 | SelectAll
16 | }
17 |
18 | public static class TextFieldBehavior
19 | {
20 | public static readonly DependencyProperty TripleClickBehaviorProperty = DependencyProperty.RegisterAttached(
21 | "TripleClickBehavior", typeof(TrippleClickBehavior), typeof(TextFieldBehavior), new PropertyMetadata(TrippleClickBehavior.None, OnPropertyChanged));
22 |
23 | private static void OnPropertyChanged(DependencyObject element, DependencyPropertyChangedEventArgs eventArgs)
24 | {
25 | if (element is TextBox textBox)
26 | {
27 | var behavior = (TrippleClickBehavior)eventArgs.NewValue;
28 | if (behavior != TrippleClickBehavior.None)
29 | {
30 | textBox.PreviewMouseLeftButtonDown += OnTextBoxMouseDown;
31 | }
32 | else
33 | {
34 | textBox.PreviewMouseLeftButtonDown -= OnTextBoxMouseDown;
35 | }
36 | }
37 | }
38 |
39 | private static void OnTextBoxMouseDown(object sender, MouseButtonEventArgs eventArgs)
40 | {
41 | if (eventArgs.ClickCount != 3)
42 | return;
43 |
44 | switch (GetTripleClickBehavior(sender as DependencyObject))
45 | {
46 | case TrippleClickBehavior.SelectAll:
47 | ((TextBox)sender).SelectAll();
48 | break;
49 | }
50 | }
51 |
52 | public static void SetTripleClickBehavior(DependencyObject element, TrippleClickBehavior value)
53 | {
54 | element.SetValue(TripleClickBehaviorProperty, value);
55 | }
56 |
57 | public static TrippleClickBehavior GetTripleClickBehavior(DependencyObject element)
58 | {
59 | return (TrippleClickBehavior)element.GetValue(TripleClickBehaviorProperty);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Controls/CoapOptionsList.xaml.cs:
--------------------------------------------------------------------------------
1 | using CoAPNet;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Windows.Shapes;
17 |
18 | namespace CoAPExplorer.WPF.Controls
19 | {
20 | ///
21 | /// Interaction logic for CoapOptionsList.xaml
22 | ///
23 | public partial class CoapOptionsList : UserControl
24 | {
25 | public static readonly DependencyProperty OptionsProperty = DependencyProperty.Register(
26 | nameof(Options), typeof(ObservableCollection), typeof(CoapOptionsList), new PropertyMetadata(null));
27 |
28 | public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
29 | nameof(IsReadOnly), typeof(bool), typeof(CoapOptionsList), new PropertyMetadata(false));
30 |
31 | public ObservableCollection Options
32 | {
33 | get => GetValue(OptionsProperty) as ObservableCollection;
34 | set => SetValue(OptionsProperty, value);
35 | }
36 |
37 | public bool IsReadOnly
38 | {
39 | get => (bool)GetValue(IsReadOnlyProperty);
40 | set => SetValue(IsReadOnlyProperty, value);
41 | }
42 |
43 | public CoapOptionsList()
44 | {
45 | InitializeComponent();
46 |
47 | DataContext = this;
48 | }
49 |
50 | private void AddButton_Click(object sender, RoutedEventArgs e)
51 | {
52 | if (Options is null)
53 | return;
54 |
55 | Options.Add(new CoAPNet.Options.Accept());
56 | }
57 |
58 | private void DeleteButton_Click(object sender, RoutedEventArgs e)
59 | {
60 | if (Options is null)
61 | return;
62 |
63 | var option = (sender as Control)?.DataContext as CoapOption;
64 | if (option == null)
65 | return;
66 |
67 | Options.Remove(option);
68 | }
69 |
70 | private void OptionTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
71 | {
72 | if (e.AddedItems == null)
73 | return;
74 |
75 | var option = (sender as Control)?.DataContext as CoapOption;
76 | if (option == null)
77 | return;
78 |
79 | var newOptionType = e.AddedItems.Cast>().Single().Item2;
80 | if (option.GetType() == newOptionType)
81 | return;
82 |
83 | var newOption = Activator.CreateInstance(newOptionType) as CoapOption;
84 |
85 | var index = Options.IndexOf(option);
86 | Options.Remove(option);
87 | Options.Insert(index, newOption);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Controls/DeviceListView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 |
6 | using ReactiveUI;
7 |
8 | using CoAPExplorer.ViewModels;
9 |
10 | namespace CoAPExplorer.WPF.Controls
11 | {
12 | public partial class DeviceListView : ListView
13 | {
14 | public static readonly DependencyProperty DevicesProperty = DependencyProperty.Register(
15 | "Devices", typeof(IReactiveCollection), typeof(DeviceListView), new PropertyMetadata(default(List)));
16 |
17 | public IReactiveCollection Devices
18 | {
19 | get => (IReactiveCollection)GetValue(DevicesProperty);
20 | set => SetValue(DevicesProperty, value);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Controls/FilterOption.xaml:
--------------------------------------------------------------------------------
1 |
10 |
12 |
13 | Resource Type (rt=)
14 | Interface Type (if=)
15 | ...
16 |
17 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Controls/FilterOption.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using MaterialDesignThemes.Wpf;
16 |
17 | namespace CoAPExplorer.WPF.Controls
18 | {
19 | ///
20 | /// Interaction logic for FilterOption.xaml
21 | ///
22 | public partial class FilterOption : UserControl
23 | {
24 | public const PackIconKind Add = PackIconKind.Plus;
25 | public const PackIconKind Remove = PackIconKind.Minus;
26 |
27 | public FilterOption()
28 | {
29 | InitializeComponent();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Controls/NavigationDrawer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Controls;
7 |
8 | namespace CoAPExplorer.WPF.Controls
9 | {
10 | public class NavigationDrawer : ListView
11 | {
12 | public NavigationDrawer()
13 | {
14 | SelectionMode = SelectionMode.Single;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Controls/NavigationItem.cs:
--------------------------------------------------------------------------------
1 | using MaterialDesignThemes.Wpf;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 |
10 | namespace CoAPExplorer.WPF.Controls
11 | {
12 | public class NavigationItem : HeaderedContentControl
13 | {
14 | public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
15 | nameof(Icon), typeof(object), typeof(NavigationItem));
16 |
17 | public object Icon { get => GetValue(IconProperty); set => SetValue(IconProperty, value); }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Converters/BoolToVisibilityConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Data;
9 |
10 | namespace CoAPExplorer.WPF.Converters
11 | {
12 | public class BoolToVisibilityConverter : IValueConverter
13 | {
14 | public Visibility WhenTrue { get; set; } = Visibility.Visible;
15 |
16 | public Visibility WhenFalse { get; set; } = Visibility.Collapsed;
17 |
18 | object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
19 | {
20 | if (value is bool isVisible)
21 | return Convert(isVisible);
22 |
23 | return null;
24 | }
25 |
26 | object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
27 | {
28 | if (value is Visibility visibility)
29 | return ConvertBack(visibility);
30 |
31 | return false;
32 | }
33 |
34 | Visibility Convert(bool value)
35 | {
36 | return value ? WhenTrue : WhenFalse;
37 | }
38 |
39 | bool ConvertBack(Visibility value)
40 | {
41 | return value == WhenTrue ? true : false;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Converters/CoapExplorerIconConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 | using MaterialDesignThemes.Wpf;
5 |
6 | namespace CoAPExplorer.WPF.Converters
7 | {
8 | public class CoapExplorerIconConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | if(targetType != typeof(PackIconKind))
13 | throw new NotSupportedException();
14 |
15 | if (value is CoapExplorerIcon icon)
16 | {
17 | switch (icon)
18 | {
19 | case CoapExplorerIcon.None:
20 | return null;
21 | case CoapExplorerIcon.Settings:
22 | return PackIconKind.Settings;
23 | case CoapExplorerIcon.Search:
24 | return PackIconKind.Magnify;
25 | case CoapExplorerIcon.Favouriate:
26 | return PackIconKind.Star;
27 | case CoapExplorerIcon.Recent:
28 | return PackIconKind.History;
29 | default:
30 | #if DEBUG
31 | return PackIconKind.EmoticonPoop;
32 | #else
33 | return null;
34 | #endif
35 | }
36 | }
37 |
38 | return null;
39 | }
40 |
41 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
42 | {
43 | throw new NotImplementedException();
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Converters/CoapMessageCodeToStringConverter.cs:
--------------------------------------------------------------------------------
1 | using CoAPNet;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows.Data;
9 |
10 | namespace CoAPExplorer.WPF.Converters
11 | {
12 | public class CoapMessageCodeToStringConverter : IValueConverter
13 | {
14 | object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
15 | => value is CoapMessageCode ? Convert((CoapMessageCode)value) : string.Empty;
16 |
17 | public string Convert(CoapMessageCode value)
18 | {
19 | return Consts.MessageCodes.SingleOrDefault(c => c.Item2 == value)?.Item1 ?? string.Empty;
20 | }
21 |
22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
23 | {
24 | throw new NotImplementedException();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Converters/CoapOptionTypeToNameConverter.cs:
--------------------------------------------------------------------------------
1 | using CoAPNet;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows.Data;
9 |
10 | namespace CoAPExplorer.WPF.Converters
11 | {
12 | public class CoapOptionTypeToNameConverter : IValueConverter
13 | {
14 | object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
15 | => Convert(value as CoapOption);
16 |
17 | object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
18 | => ConvertBack(value as Type);
19 |
20 | public static string Convert(CoapOption option)
21 | {
22 | if (option is null)
23 | return string.Empty;
24 |
25 | var name = Consts.CoapOptionTypes.SingleOrDefault(p => p.Item2 == option.GetType()).Item1
26 | ?? option.GetType().Name;
27 |
28 | return name;
29 | }
30 |
31 | public static CoapOption ConvertBack(Type optionType)
32 | {
33 | if (optionType is null)
34 | return null;
35 |
36 | return Activator.CreateInstance(optionType) as CoapOption;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Converters/HextoAsciiConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows.Data;
8 |
9 | namespace CoAPExplorer.WPF.Converters
10 | {
11 | public class HextoAsciiConverter : IValueConverter
12 | {
13 | object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
14 | {
15 | if (!(value is IEnumerable bytes))
16 | return string.Empty;
17 |
18 | if (!(parameter is int maxBytes))
19 | maxBytes = 8;
20 |
21 | return Convert(value as byte[], targetType, maxBytes, culture);
22 | }
23 |
24 | object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
25 | => ConvertBack((string) value, targetType, (int)parameter, culture);
26 |
27 | public string Convert(byte[] value, Type targetType, int maxBytes, CultureInfo culture)
28 | {
29 | var length = Math.Min(maxBytes, value?.Length ?? 0);
30 | if (length == 0)
31 | return string.Empty;
32 |
33 | var sb = new StringBuilder();
34 |
35 | sb.Append(string.Join(" ", value.Take(length - 1).Select(b => b.ToString("X2"))));
36 |
37 | if (length > 1)
38 | sb.Append(" ");
39 |
40 | var last = value.ElementAt(length-1);
41 | if (last < 0x10)
42 | sb.Append(last.ToString("X"));
43 | else if (length < maxBytes)
44 | sb.Append(last.ToString("X2"))
45 | .Append(" ");
46 | else
47 | sb.Append(last.ToString("X2"));
48 |
49 |
50 | return sb.ToString();
51 | }
52 |
53 | public byte[] ConvertBack(string value, Type targetType, int maxBytes, CultureInfo culture)
54 | {
55 | var hexChars = "abcdefABCDEF0123456789";
56 |
57 | var length = Math.Max(maxBytes, (value.Length + 1) / 2);
58 | var arr = new List();
59 |
60 | var hex = "";
61 | int count = 0;
62 | foreach (var c in value)
63 | {
64 | if (hexChars.Contains(c))
65 | hex += c;
66 |
67 |
68 | if (hex.Length == 2 || !char.IsLetterOrDigit(c))
69 | {
70 | if(!string.IsNullOrWhiteSpace(hex))
71 | arr.Add(System.Convert.ToByte(hex,16));
72 |
73 | if (maxBytes == arr.Count)
74 | break;
75 |
76 | hex = "";
77 | count++;
78 | }
79 | }
80 |
81 | if (maxBytes != arr.Count && !string.IsNullOrWhiteSpace(hex))
82 | arr.Add(System.Convert.ToByte(hex, 16));
83 |
84 | return arr.ToArray();
85 | }
86 |
87 | private static int GetHexVal(char hex)
88 | {
89 | int val = (int)hex;
90 | return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Converters/InvertBooleanConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Data;
9 |
10 | namespace CoAPExplorer.WPF.Converters
11 | {
12 | public class InvertBooleanConverter : IValueConverter
13 | {
14 | object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
15 | {
16 | return (value is bool)
17 | ? !(bool)value
18 | : false;
19 | }
20 |
21 | object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
22 | {
23 | return (value is bool)
24 | ? !(bool)value
25 | : false;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Converters/RelativeDateTimeConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.Windows.Data;
6 |
7 | namespace CoAPExplorer.WPF.Converters
8 | {
9 | public class RelativeDateTimeConverter : IValueConverter
10 | {
11 | private const int _minute = 60;
12 | private const int _hour = 60 * _minute;
13 | private const int _day = 24 * _hour;
14 |
15 | private readonly Dictionary _thresholds = new Dictionary()
16 | {
17 | { _minute, "just now" },
18 | { _minute * 2, "a minute ago" },
19 | { 45 * _minute, "{0} minutes ago" },
20 | { 120 * _minute, "an hour ago" },
21 | { _day, "{0} hours ago" },
22 | { _day* 2, "yesterday" },
23 | { _day* 30, "{0} days ago" },
24 | };
25 |
26 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
27 | {
28 | if (value is DateTime relative)
29 | {
30 | if (relative == DateTime.MinValue)
31 | return "never";
32 |
33 | long since = (DateTime.Now.Ticks - relative.Ticks) / 10000000;
34 |
35 | var threshold = _thresholds.FirstOrDefault(t => since < t.Key);
36 | if (string.IsNullOrEmpty(threshold.Value))
37 | return relative.ToShortDateString();
38 |
39 | TimeSpan timeSpan = new TimeSpan((DateTime.Now.Ticks - relative.Ticks));
40 |
41 | return string.Format(threshold.Value,
42 | timeSpan.Days > 365 ? timeSpan.Days / 365 : (timeSpan.Days > 0 ? timeSpan.Days : (timeSpan.Hours > 0 ? timeSpan.Hours : (timeSpan.Minutes > 0 ? timeSpan.Minutes : (timeSpan.Seconds > 0 ? timeSpan.Seconds : 0)))));
43 |
44 | }
45 |
46 | #if DEBUG
47 | return $"Could not cast {value?.GetType()} to {nameof(DateTime)}";
48 | #else
49 | return "";
50 | #endif
51 | }
52 |
53 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
54 | {
55 | throw new NotImplementedException();
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Converters/UIElementVisibilityToUnsetValueConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace CoAPExplorer.WPF.Converters
7 | {
8 | public class UIElementVisibilityToUnsetValueConverter : IValueConverter
9 | {
10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
11 | {
12 | var element = parameter as UIElement;
13 | if (element == null)
14 | throw new ArgumentException($"Invalid parameter [{(parameter?.GetType()?.ToString() ?? "null")}], expecting [{nameof(UIElement)}]", nameof(parameter));
15 |
16 | if(!element.IsVisible)
17 | return DependencyProperty.UnsetValue;
18 |
19 | return value;
20 | }
21 |
22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
23 | {
24 | throw new NotImplementedException();
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Dialogs/NewDeviceViewDialog.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Add an Existing Device
16 |
17 |
18 |
19 |
20 |
23 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Dialogs/NewDeviceViewDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reactive;
4 | using System.Reactive.Disposables;
5 | using System.Reactive.Linq;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Input;
9 |
10 | using MaterialDesignThemes.Wpf;
11 | using ReactiveUI;
12 |
13 | using CoAPExplorer.ViewModels;
14 | using CoAPExplorer.Services;
15 |
16 | namespace CoAPExplorer.WPF.Dialogs
17 | {
18 | ///
19 | /// Interaction logic for NewDeviceViewDialog.xaml
20 | ///
21 | public partial class NewDeviceViewDialog : UserControl, IViewFor
22 | {
23 | public NewDeviceViewDialog()
24 | {
25 | InitializeComponent();
26 |
27 | this.WhenActivated(disposables =>
28 | {
29 | ViewModel.AddDeviceCommand
30 | .Subscribe(p => DialogHost.CloseDialogCommand.Execute(p, this))
31 | .DisposeWith(disposables);
32 |
33 | this.BindCommand(ViewModel, vm => vm.AddDeviceCommand, v => v.AddButton)
34 | .DisposeWith(disposables);
35 |
36 | this.Bind(ViewModel, vm => vm.Name, v => v.NameTextBox.Text)
37 | .DisposeWith(disposables);
38 |
39 | this.Bind(ViewModel, vm => vm.Address, v => v.AddressTextBox.Text)
40 | .DisposeWith(disposables);
41 | });
42 | }
43 |
44 | public NewDeviceViewModel ViewModel { get; set; }
45 | object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as NewDeviceViewModel; }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Extensions/DependencyObjectExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Media;
8 |
9 | namespace CoAPExplorer.WPF.Extensions
10 | {
11 | public static class DependencyObjectExtensions
12 | {
13 | public static DependencyObject GetParent(this DependencyObject element)
14 | {
15 | if (element == null)
16 | return null;
17 |
18 | DependencyObject parent = null;
19 |
20 | if (element is ContentElement ce)
21 | {
22 | parent = ContentOperations.GetParent(ce);
23 | if (parent == null && ce is FrameworkContentElement fce)
24 | parent = fce.Parent;
25 |
26 | }else if (element is FrameworkElement fe)
27 | {
28 | parent = fe.Parent;
29 | }
30 |
31 | if (parent != null)
32 | return parent;
33 |
34 | return VisualTreeHelper.GetParent(element);
35 | }
36 |
37 | public static IEnumerable GetChildren(this DependencyObject element)
38 | {
39 | if (element == null)
40 | yield break;
41 |
42 | var count = VisualTreeHelper.GetChildrenCount(element);
43 |
44 | for (var i = 0; i < count; i++)
45 | yield return VisualTreeHelper.GetChild(element, i);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Linq;
3 | using System.Windows;
4 | using System.Windows.Input;
5 |
6 | using MaterialDesignThemes.Wpf;
7 | using ReactiveUI;
8 | using Splat;
9 |
10 | using CoAPExplorer.ViewModels;
11 |
12 | namespace CoAPExplorer.WPF
13 | {
14 | ///
15 | /// Interaction logic for MainWindow.xaml
16 | ///
17 | public partial class MainWindow : Window, IScreen
18 | {
19 | public RoutingState Router { get; }
20 |
21 | public SnackbarMessageQueue ToastMessageQueue { get; }
22 |
23 | public MainWindow()
24 | {
25 | ToastMessageQueue = new SnackbarMessageQueue();
26 |
27 | InitializeComponent();
28 |
29 | // reactiveUI.Router stuff
30 | Router = new RoutingState();
31 |
32 | Router.CurrentViewModel.Subscribe(viewModel =>
33 | {
34 | if (viewModel == null)
35 | {
36 | Frame.Content = null;
37 | return;
38 | }
39 |
40 | var viewLocator = App.CoapExplorer.Locator.GetService();
41 | var view = viewLocator.ResolveView(viewModel);
42 | view.ViewModel = viewModel;
43 | Frame.Navigate(view);
44 | });
45 |
46 | Router.NavigateAndReset.Execute(new HomeViewModel(this))
47 | .Subscribe();
48 |
49 | App.CoapExplorer.ToastNotifications.Subscribe(toast =>
50 | {
51 | var toastMessage = new SnackbarMessage { Content = toast.Message };
52 |
53 | if(toast.Actions.Count == 0)
54 | ToastMessageQueue.Enqueue(toastMessage, true);
55 | else
56 | ToastMessageQueue.Enqueue(
57 | toastMessage,
58 | toast.Actions[0].Label.ToUpper(), p => ((ICommand)toast.Actions[0].Command).Execute(p),
59 | null,
60 | false, true);
61 |
62 | });
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/MockViewModels/DeviceListMock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using CoAPExplorer.Models;
4 | using CoAPExplorer.ViewModels;
5 |
6 | namespace CoAPExplorer.WPF.MockViewModels
7 | {
8 | public class DeviceListMock
9 | {
10 | public ObservableCollection Devices { get; }
11 |
12 | public DeviceListMock()
13 | {
14 | Devices = new ObservableCollection
15 | {
16 | new DeviceViewModel(new Device
17 | {
18 | Name = "Garage Door",
19 | Address = new Uri("coap://192.168.x.x/"),
20 | LastSeen = DateTime.Now.AddMinutes(-123456)
21 | }),
22 | new DeviceViewModel(new Device{
23 | Name = "Weather Station",
24 | Address = new Uri("coap://192.168.x.x/"),
25 | LastSeen = DateTime.Now.AddMinutes(-1),
26 | IsFavourite = true,
27 | }),
28 | new DeviceViewModel(new Device{
29 | Name = "Pet Food Station",
30 | Address = new Uri("coap://192.168.x.x"),
31 | LastSeen = DateTime.Now.AddMinutes(-12)
32 | })
33 | };
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/MockViewModels/DeviceNavigationViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace CoAPExplorer.WPF.MockViewModels
8 | {
9 | public class DeviceNavigationViewModel : CoAPExplorer.ViewModels.DeviceNavigationViewModel
10 | {
11 | public DeviceNavigationViewModel()
12 | :base(new DeviceViewModel())
13 | { }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/MockViewModels/DeviceViewModel.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.Models;
2 | using CoAPExplorer.Services;
3 | using CoAPNet;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace CoAPExplorer.WPF.MockViewModels
11 | {
12 | public class DeviceViewModel : CoAPExplorer.ViewModels.DeviceViewModel
13 | {
14 | public DeviceViewModel()
15 | : this(null)
16 | { }
17 |
18 | public DeviceViewModel(Device device)
19 | : base(device ?? new Device
20 | {
21 | Name = "Some Device",
22 | Endpoint = CoapEndpointFactory.GetEndpoint(new Uri("coap://182.168.x.x/")),
23 | Address = new Uri("coap://182.168.x.x/"),
24 | LastSeen = DateTime.Now,
25 | IsFavourite = false,
26 | KnownResources = new System.Collections.ObjectModel.Collection
27 | {
28 | new DeviceResource {Url = new Uri("/.well-known/core", UriKind.RelativeOrAbsolute)},
29 | new DeviceResource {Url = new Uri("/doors", UriKind.RelativeOrAbsolute)},
30 | new DeviceResource {Url = new Uri("/doors/garage", UriKind.RelativeOrAbsolute)},
31 | new DeviceResource {Url = new Uri("/doors/entrance", UriKind.RelativeOrAbsolute)},
32 | new DeviceResource {Url = new Uri("/lights", UriKind.RelativeOrAbsolute)},
33 | new DeviceResource {Url = new Uri("/lights/desklamp", UriKind.RelativeOrAbsolute)},
34 | new DeviceResource {Url = new Uri("/lights/lounge1", UriKind.RelativeOrAbsolute)},
35 | new DeviceResource {Url = new Uri("/lights/lounge2", UriKind.RelativeOrAbsolute)},
36 | new DeviceResource {Url = new Uri("/lights/lounge3", UriKind.RelativeOrAbsolute)},
37 | new DeviceResource {Url = new Uri("/lights/lounge4", UriKind.RelativeOrAbsolute)},
38 | new DeviceResource {Url = new Uri("/lights/lounge4", UriKind.RelativeOrAbsolute)},
39 | },
40 | })
41 | {
42 | Message = new Message
43 | {
44 | MessageId = 1234,
45 | Token = new byte[] { 0x01, 0x02, 0x03, 0x04 },
46 |
47 | Url = new Uri("/some/resource", UriKind.RelativeOrAbsolute),
48 | Code = CoapMessageCode.Get,
49 | //Type = CoapMessageType.Confirmable,
50 |
51 | ContentFormat = CoAPNet.Options.ContentFormatType.ApplicationJson,
52 | Payload = Encoding.UTF8.GetBytes("{\r\n\t\"test\": 1234, \r\n\t\"emoji\": \"🦆\", \r\n\t\"zero\": \0\r\n}")
53 | };
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/MockViewModels/MockCoapOptionsList.cs:
--------------------------------------------------------------------------------
1 | using CoAPNet;
2 | using CoAPNet.Options;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace CoAPExplorer.WPF.MockViewModels
11 | {
12 | public class MockCoapOptionsList
13 | {
14 | public ObservableCollection Options { get; }
15 | = new ObservableCollection
16 | {
17 | new UriHost("localhost"),
18 | new UriPort(1234),
19 | new UriPath("path"),
20 | new UriQuery("ct=0"),
21 | new ProxyUri(),
22 | new ProxyScheme(),
23 | new LocationPath(),
24 | new LocationQuery(),
25 | new ContentFormat(ContentFormatType.ApplicationJson),
26 | new Accept(ContentFormatType.ApplicationJson),
27 | new MaxAge(3600),
28 | new ETag(new byte[]{12,34,56,78 }),
29 | new Size1(1234),
30 | new Size2(1234),
31 | new IfMatch(),
32 | new IfNoneMatch(),
33 | new Block1(),
34 | new Block2(1,128,true),
35 | };
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/MockViewModels/MockHomeView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | using ReactiveUI;
8 |
9 | using CoAPExplorer.ViewModels;
10 |
11 | namespace CoAPExplorer.WPF.MockViewModels
12 | {
13 | public class MockHomeView : HomeViewModel
14 | {
15 | public MockHomeView()
16 | {
17 | RecentDevices = new RecentDevicesViewModel
18 | {
19 | Devices = new DeviceListMock().Devices
20 | };
21 |
22 | Search = new SearchViewModel()
23 | {
24 | Devices = new ReactiveList(RecentDevices.Devices)
25 | };
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/MockViewModels/NavigationViewModel.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Media.Media3D;
2 | using CoAPExplorer.Models;
3 | using MaterialDesignThemes.Wpf;
4 | using ReactiveUI;
5 |
6 | namespace CoAPExplorer.WPF.MockViewModels
7 | {
8 | public class NavigationViewModel : CoAPExplorer.ViewModels.NavigationViewModel
9 | {
10 | public NavigationViewModel()
11 | {
12 | NavigationItems = new ReactiveList(new[]
13 | {
14 | new NavigationItem {Name = "Favourites", Icon = CoapExplorerIcon.Favouriate},
15 | new NavigationItem {Name = "Search", Icon = CoapExplorerIcon.Search},
16 | new NavigationItem {Name = "Settings", Icon = CoapExplorerIcon.Settings}
17 | });
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("CoAPExplorer")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("CoAPExplorer")]
15 | [assembly: AssemblyCopyright("Copyright © 2018")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CoAPExplorer.WPF.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CoAPExplorer.WPF.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace CoAPExplorer.WPF.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Resources/JSONFormat.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | false
15 | null
16 | true
17 | NaN
18 | Infinity
19 |
20 |
21 | //
22 |
23 |
24 | /\*
25 | \*/
26 |
27 |
28 |
29 | /
30 | /
31 |
32 |
33 |
34 |
35 |
36 | "
37 | "
38 |
39 |
40 |
41 |
42 |
43 | '
44 | '
45 |
46 |
47 |
48 |
49 | \b0[xX][0-9a-fA-F]+|(\b\d+(\.[0-9]+)?|\.[0-9]+)([eE][+-]?[0-9]+)?
50 |
51 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Resources/LinkFormat.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | <
12 | >
13 |
14 |
15 |
16 | ;
17 | ,
18 |
19 |
20 |
21 | "
22 | "
23 |
24 |
25 |
26 |
27 |
28 |
29 | [0-9.]+
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Resources/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NZSmartie/CoAPExplorer/13b95b7cf493f539da29b26f0c86856487922187/src/CoAPExplorer.WPF/Resources/logo.ico
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Services/CoapFormatHighlightManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Xml;
9 | using CoAPNet.Options;
10 | using ICSharpCode.AvalonEdit.Highlighting;
11 | using ICSharpCode.AvalonEdit.Highlighting.Xshd;
12 |
13 | namespace CoAPExplorer.WPF.Services
14 | {
15 | public class CoapFormatHighlightingManager : IHighlightingDefinitionReferenceResolver
16 | {
17 | public static readonly CoapFormatHighlightingManager Default
18 | = new CoapFormatHighlightingManager();
19 |
20 | private readonly Dictionary> _registeredHighlighting
21 | = new Dictionary>();
22 |
23 | public CoapFormatHighlightingManager()
24 | {
25 | Register(ContentFormatType.ApplicationJson, "/Resources/JSONFormat.xml");
26 | Register(ContentFormatType.ApplicationLinkFormat, "/Resources/LinkFormat.xml");
27 | }
28 |
29 | public void Register(ContentFormatType contentFormat, string resourceName)
30 | {
31 | _registeredHighlighting.Add(contentFormat, new Lazy(() => LoadHighlighting(resourceName)));
32 | }
33 |
34 | public IHighlightingDefinition GetDefinition(string name)
35 | {
36 | return null;
37 | }
38 |
39 | public IHighlightingDefinition GetDefinition(ContentFormatType contentFormat)
40 | {
41 | if (contentFormat is null)
42 | return null;
43 |
44 | if (!_registeredHighlighting.TryGetValue(contentFormat, out var definition))
45 | return null;
46 |
47 | try
48 | {
49 | return definition.Value;
50 |
51 | }
52 | catch (HighlightingDefinitionInvalidException ex)
53 | {
54 | throw new InvalidOperationException($"The highlighting for '{contentFormat}' is invalid.", ex);
55 | }
56 | }
57 |
58 | private IHighlightingDefinition LoadHighlighting(string resourceName)
59 | {
60 | XshdSyntaxDefinition xshd;
61 |
62 | var resourceInfo = Application.GetResourceStream(new Uri(resourceName, UriKind.Relative));
63 |
64 | using (var s = resourceInfo.Stream)
65 | {
66 | using (XmlTextReader reader = new XmlTextReader(s))
67 | {
68 | // in release builds, skip validating the built-in highlightings
69 | xshd = HighlightingLoader.LoadXshd(reader);
70 | }
71 | }
72 | return HighlightingLoader.Load(xshd, this);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Themes/AppBar.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
36 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Themes/DeviceListView.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
78 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/DeviceNavigationView.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
47 |
50 |
51 |
52 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/DeviceNavigationView.xaml.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.ViewModels;
2 | using ReactiveUI;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Reactive.Disposables;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Data;
12 | using System.Windows.Documents;
13 | using System.Windows.Input;
14 | using System.Windows.Media;
15 | using System.Windows.Media.Imaging;
16 | using System.Windows.Navigation;
17 | using System.Windows.Shapes;
18 |
19 | namespace CoAPExplorer.WPF.Views
20 | {
21 | ///
22 | /// Interaction logic for DeviceNavigationView.xaml
23 | ///
24 | public partial class DeviceNavigationView : UserControl, IViewFor
25 | {
26 | public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
27 | nameof(IsOpen), typeof(bool), typeof(DeviceNavigationView), new PropertyMetadata(true));
28 |
29 | public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(
30 | nameof(ViewModel), typeof(DeviceNavigationViewModel), typeof(DeviceNavigationView), new PropertyMetadata(null));
31 |
32 | public DeviceNavigationViewModel ViewModel { get => GetValue(ViewModelProperty) as DeviceNavigationViewModel; set => SetValue(ViewModelProperty, value); }
33 | object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as DeviceNavigationViewModel; }
34 |
35 | public bool IsOpen
36 | {
37 | get => (bool)GetValue(IsOpenProperty);
38 | set => SetValue(IsOpenProperty, value);
39 | }
40 |
41 | public DeviceNavigationView()
42 | {
43 | InitializeComponent();
44 |
45 | //SetBinding(DataContextProperty, new Binding(nameof(ViewModel)) { Mode = BindingMode.OneWay });
46 | this.WhenActivated(disposables =>
47 | {
48 | this.WhenAnyValue(t => t.ViewModel).Subscribe(NewViewModel => DataContext = NewViewModel).DisposeWith(disposables);
49 |
50 | this.BindCommand(ViewModel,
51 | vm => vm.RefreshResourcesCommand,
52 | v => v.RefreshButton,
53 | vm => vm.Device)
54 | .DisposeWith(disposables);
55 |
56 | this.OneWayBind(ViewModel, vm => vm.Resources, v => v.ResourceList.ItemsSource)
57 | .DisposeWith(disposables);
58 |
59 | this.Bind(ViewModel, vm => vm.SelectedResource, v => v.ResourceList.SelectedItem)
60 | .DisposeWith(disposables);
61 | });
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/DeviceView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Linq;
4 | using System.Reactive.Disposables;
5 | using System.Reactive.Linq;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 |
15 | using CoAPNet;
16 | using CoAPNet.Options;
17 | using ReactiveUI;
18 |
19 | using CoAPExplorer.Models;
20 | using CoAPExplorer.ViewModels;
21 | using CoAPExplorer.WPF.Converters;
22 |
23 | namespace CoAPExplorer.WPF.Views
24 | {
25 | ///
26 | /// Interaction logic for DeviceView.xaml
27 | ///
28 | public partial class DeviceView : UserControl, IViewFor
29 | {
30 | private static readonly BooleanToVisibilityConverter _visibilityConverter = new BooleanToVisibilityConverter();
31 |
32 | private ReactiveCommand NavigateCommand;
33 |
34 | public DeviceView()
35 | {
36 | InitializeComponent();
37 |
38 | NavigateCommand = ReactiveCommand.CreateFromObservable(
39 | () => ViewModel.HostScreen.Router.NavigateBack.Execute(),
40 | this.WhenAnyValue(x => x.ViewModel).Select(x => x != null && x.HostScreen != null));
41 |
42 | NavigateBackButton.Command = NavigateCommand;
43 |
44 | this.WhenActivated(disposables =>
45 | {
46 | this.Bind(ViewModel, vm => vm.Message, v => v.Url.SelectedItem,
47 | this.WhenAnyValue(v => v.Url.SelectedItem).Where(i => i != null))
48 | .DisposeWith(disposables);
49 |
50 | this.OneWayBind(ViewModel, vm => vm.RecentMessages, v => v.Url.ItemsSource)
51 | .DisposeWith(disposables);
52 |
53 | this.BindCommand(ViewModel, vm => vm.SendCommand, v => v.SendButton, vm => vm.Message)
54 | .DisposeWith(disposables);
55 |
56 | this.BindCommand(ViewModel, vm => vm.StopSendingCommand, v => v.StopButton)
57 | .DisposeWith(disposables);
58 |
59 | this.BindCommand(ViewModel, vm=> vm.DuplicateMessageCommand, v => v.DuplicateMessageButton,
60 | ViewModel.WhenAnyValue(vm => vm.Message))
61 | .DisposeWith(disposables);
62 |
63 | this.OneWayBind(ViewModel,
64 | vm => vm.IsSending,
65 | v => v.StopButton.Visibility,
66 | x => _visibilityConverter.Convert(x, typeof(Visibility), null, CultureInfo.CurrentCulture))
67 | .DisposeWith(disposables);
68 |
69 | this.OneWayBind(ViewModel,
70 | vm => vm.IsSending,
71 | v => v.SendButton.Visibility,
72 | x => _visibilityConverter.Convert(!x, typeof(Visibility), null, CultureInfo.CurrentCulture))
73 | .DisposeWith(disposables);
74 |
75 | this.Bind(ViewModel, vm => vm.MessageViewModel, v => v.MessageRequest.ViewModel)
76 | .DisposeWith(disposables);
77 |
78 | this.OneWayBind(ViewModel,
79 | vm => vm.HostScreen.Router.NavigationStack,
80 | v => v.NavigateBackButton.Visibility,
81 | stack => stack.Any() ? Visibility.Visible : Visibility.Collapsed);
82 |
83 | this.OneWayBind(ViewModel, vm => vm.Navigation, v => v.DeviceNavigation.ViewModel)
84 | .DisposeWith(disposables);
85 |
86 | ViewModel.SendCommand
87 | .Subscribe(response =>
88 | {
89 | MessageResponse.ViewModel = new MessageViewModel(response);
90 | MessageTabControl.SelectedItem = ReponseTab;
91 | })
92 | .DisposeWith(disposables);
93 |
94 | Observable.Merge(Url.Events().KeyUp.Where(k => k.Key == Key.Enter).Select(_ => false),
95 | Url.Events().LostKeyboardFocus.Select(_ => false))
96 | .Subscribe(_ => CreateMessage());
97 |
98 | });
99 | }
100 |
101 | private void CreateMessage()
102 | {
103 | if (ViewModel == null)
104 | return;
105 |
106 | if (Url.SelectedItem == null)
107 | {
108 | var message = ViewModel.Message.Clone();
109 |
110 | if(Uri.TryCreate(Url.Text, UriKind.RelativeOrAbsolute, out var url))
111 | message.Url = url;
112 |
113 | ViewModel.Message = message;
114 | }
115 | }
116 |
117 | public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
118 | nameof(ViewModel), typeof(DeviceViewModel), typeof(DeviceView), new PropertyMetadata(null));
119 |
120 | public DeviceViewModel ViewModel { get => GetValue(ViewModelProperty) as DeviceViewModel; set => SetValue(ViewModelProperty, value); }
121 | object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as DeviceViewModel; }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/HomeView.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 🤷
42 |
43 |
44 |
45 |
46 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/HomeView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Disposables;
3 | using System.Reactive.Linq;
4 | using System.Windows.Controls;
5 |
6 | using ReactiveUI;
7 | using Splat;
8 |
9 | using CoAPExplorer.Models;
10 | using CoAPExplorer.ViewModels;
11 |
12 | namespace CoAPExplorer.WPF.Views
13 | {
14 | ///
15 | /// Interaction logic for HomeView.xaml
16 | ///
17 | public partial class HomeView : Page, IViewFor
18 | {
19 | public IScreen Router { get; }
20 |
21 | public HomeViewModel ViewModel { get; set; }
22 |
23 | public HomeView(IScreen router = null)
24 | {
25 | InitializeComponent();
26 |
27 | this.WhenActivated(disposables =>
28 | {
29 | this.WhenAnyValue(x => x.ViewModel)
30 | .Subscribe(vm => DataContext = vm)
31 | .DisposeWith(disposables);
32 | });
33 | }
34 |
35 |
36 | object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as HomeViewModel; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/MessageResponseView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Reactive.Linq;
4 | using System.Reactive.Disposables;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 |
17 | using ReactiveUI;
18 |
19 | using CoAPExplorer.Models;
20 | using CoAPExplorer.ViewModels;
21 | using CoAPExplorer.WPF.Converters;
22 | using System.Text.RegularExpressions;
23 | using System.Linq;
24 | using CoAPExplorer.WPF.Services;
25 |
26 | namespace CoAPExplorer.WPF.Views
27 | {
28 |
29 | ///
30 | /// Interaction logic for MessageResponseView.xaml
31 | ///
32 | public partial class MessageResponseView : UserControl, IViewFor
33 | {
34 | private CompositeDisposable _viewModelDisposables;
35 |
36 | private static readonly HextoAsciiConverter _hextoAsciiConverter = new HextoAsciiConverter();
37 |
38 | public MessageResponseView()
39 | {
40 | InitializeComponent();
41 |
42 | this.WhenActivated(disposables =>
43 | {
44 | this.WhenAnyValue(v => v.ViewModel)
45 | .Subscribe(NewViewModel =>
46 | {
47 | _viewModelDisposables?.Dispose();
48 | _viewModelDisposables = new CompositeDisposable();
49 |
50 | if (NewViewModel == null)
51 | return;
52 |
53 | this.OneWayBind(NewViewModel, vm => vm.MessageId, v => v.MessageIdTextBox.Text)
54 | .DisposeWith(_viewModelDisposables);
55 |
56 | this.OneWayBind(NewViewModel, vm => vm.Token, v => v.MessageToken.Text,
57 | x => _hextoAsciiConverter.Convert(x, typeof(string), 8, CultureInfo.CurrentCulture))
58 | .DisposeWith(_viewModelDisposables);
59 |
60 | this.OneWayBind(NewViewModel, vm => vm.Code, v => v.MessageCodeTextBox.Text,
61 | x => Consts.MessageCodes.SingleOrDefault(c => c.Item2 == x)?.Item1 ?? x.ToString())
62 | .DisposeWith(_viewModelDisposables);
63 |
64 | this.OneWayBind(NewViewModel, vm => vm.ContentFormat, v => v.ContentTypeTextBox.Text,
65 | x => Consts.ContentTypes.SingleOrDefault(c => c.Item2?.Value == x.Value)?.Item1 ?? $"{x.Value} - (unknown)")
66 | .DisposeWith(_viewModelDisposables);
67 |
68 | this.Bind(NewViewModel, vm => vm.Options, v => v.OptionsList.Options)
69 | .DisposeWith(_viewModelDisposables);
70 |
71 | this.OneWayBind(NewViewModel, vm => vm.Payload, v => v.MessageTextBox.Text)
72 | .DisposeWith(_viewModelDisposables);
73 |
74 | this.WhenAnyValue(v => v.DisplayUnicode.IsSelected)
75 | .InvokeCommand(NewViewModel, vm => vm.EscapePayload)
76 | .DisposeWith(_viewModelDisposables);
77 |
78 | this.OneWayBind(NewViewModel, vm => vm.FormattedPayload, v => v.FormattedTextEditor.Text)
79 | .DisposeWith(_viewModelDisposables);
80 |
81 | NewViewModel.WhenAnyValue(vm => vm.ContentFormat)
82 | .Select(cf => CoapFormatHighlightingManager.Default.GetDefinition(cf))
83 | .Subscribe(d => FormattedTextEditor.SyntaxHighlighting = d)
84 | .DisposeWith(_viewModelDisposables);
85 | })
86 | .DisposeWith(disposables);
87 | });
88 |
89 | }
90 |
91 | public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
92 | nameof(ViewModel), typeof(MessageViewModel), typeof(MessageResponseView), new PropertyMetadata(null));
93 |
94 | public MessageViewModel ViewModel { get => GetValue(ViewModelProperty) as MessageViewModel; set => SetValue(ViewModelProperty, value); }
95 | object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as MessageViewModel; }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/NavigationView.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
23 |
24 |
25 |
27 |
28 |
29 |
30 |
33 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/NavigationView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Linq;
4 | using System.Reactive;
5 | using System.Reactive.Disposables;
6 | using System.Reactive.Linq;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 |
10 | using CoAPExplorer.Models;
11 | using CoAPExplorer.ViewModels;
12 | using ReactiveUI;
13 |
14 | namespace CoAPExplorer.WPF.Views
15 | {
16 | ///
17 | /// Interaction logic for NavigationView.xaml
18 | ///
19 | public partial class NavigationView : UserControl, IViewFor
20 | {
21 | public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
22 | nameof(IsOpen), typeof(bool), typeof(NavigationView), new PropertyMetadata(true));
23 |
24 | public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
25 | nameof(ViewModel), typeof(NavigationViewModel), typeof(NavigationView), new PropertyMetadata(null));
26 |
27 | public bool IsOpen
28 | {
29 | get => (bool)GetValue(IsOpenProperty);
30 | set => SetValue(IsOpenProperty, value);
31 | }
32 |
33 |
34 | public NavigationViewModel ViewModel { get => GetValue(ViewModelProperty) as NavigationViewModel; set => SetValue(ViewModelProperty, value); }
35 | object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as NavigationViewModel; }
36 |
37 | public NavigationView()
38 | {
39 | InitializeComponent();
40 |
41 | //CollapseNavigation = ReactiveCommand.Create(() => IsOpen = false);
42 |
43 | #if DEBUG
44 | if (DesignerProperties.GetIsInDesignMode(this))
45 | IsOpen = true;
46 | #endif
47 |
48 | this.WhenActivated(disposables =>
49 | {
50 | this.Bind(ViewModel, vm => vm.IsOpen, v => v.IsOpen)
51 | .DisposeWith(disposables);
52 |
53 | this.OneWayBind(ViewModel, vm => vm.NavigationItems, v => v.NaigationList.ItemsSource)
54 | .DisposeWith(disposables);
55 |
56 | this.Bind(ViewModel, vm => vm.SelectedNavigationItem, v => v.NaigationList.SelectedItem)
57 | .DisposeWith(disposables);
58 |
59 | //Observable.FromEventPattern(
60 | // h => NaigationList.SelectionChanged += h,
61 | // h => NaigationList.SelectionChanged -= h)
62 | // .SelectMany(x => x.EventArgs.AddedItems.Cast())
63 | // .Select(n => Observable.Return(Unit.Default)
64 | // .InvokeCommand(n.Command)
65 | // .DisposeWith(disposables))
66 | // .Subscribe()
67 | // .DisposeWith(disposables);
68 | });
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/RecentDevicesView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive;
3 | using System.Reactive.Linq;
4 | using System.Linq;
5 | using System.Windows.Controls;
6 | using System.Reactive.Disposables;
7 |
8 | using MaterialDesignThemes.Wpf;
9 | using ReactiveUI;
10 |
11 | using CoAPExplorer.ViewModels;
12 | using System.Windows;
13 | using System.Windows.Input;
14 |
15 | namespace CoAPExplorer.WPF.Views
16 | {
17 | ///
18 | /// Interaction logic for RecentDevicesView.xaml
19 | ///
20 | public partial class RecentDevicesView : UserControl, IViewFor
21 | {
22 | private static readonly DependencyProperty SearchCommandProperty = DependencyProperty.Register(
23 | nameof(SearchCommand), typeof(ReactiveCommand), typeof(RecentDevicesView), new PropertyMetadata(default(ReactiveCommand)));
24 |
25 | private ReactiveCommand SearchCommand { get => GetValue(SearchCommandProperty) as ReactiveCommand; set => SetValue(SearchCommandProperty, value); }
26 |
27 | private static readonly DependencyProperty CloseSearchCommandProperty = DependencyProperty.Register(
28 | nameof(CloseSearchCommand), typeof(ReactiveCommand), typeof(RecentDevicesView), new PropertyMetadata(default(ReactiveCommand)));
29 |
30 | private ReactiveCommand CloseSearchCommand { get => GetValue(CloseSearchCommandProperty) as ReactiveCommand; set => SetValue(CloseSearchCommandProperty, value); }
31 |
32 | public RecentDevicesView()
33 | {
34 | InitializeComponent();
35 |
36 | SearchCommand = ReactiveCommand.Create(() =>
37 | {
38 | AppBarTransistioner.SelectedItem = SearchTransistionState;
39 | SearchTextBox.Focus();
40 | });
41 |
42 | CloseSearchCommand = ReactiveCommand.Create(() =>
43 | {
44 | if (ViewModel != null)
45 | ViewModel.SearchTerms = string.Empty;
46 |
47 | MaterialDesignThemes.Wpf.Transitions.Transitioner.MoveFirstCommand.Execute(Unit.Default, SearchTextBox);
48 | });
49 |
50 | this.WhenActivated(disposables =>
51 | {
52 | this.OneWayBind(ViewModel, vm => vm.FilteredDevices, v => v.DeviceListView.ItemsSource)
53 | .DisposeWith(disposables);
54 |
55 | this.WhenAnyValue(v => v.DeviceListView.SelectedItem)
56 | .Select(x => x as DeviceViewModel)
57 | .Where(x => x != null)
58 | .InvokeCommand(this, v => v.ViewModel.OpenDeviceCommand)
59 | .DisposeWith(disposables);
60 |
61 | this.WhenAnyValue(x => x.ViewModel)
62 | .Where(x => x != null)
63 | .Subscribe(vm => vm.AddDeviceCommand.Subscribe(ndvm =>
64 | {
65 | var view = ViewLocator.Current.ResolveView(ndvm);
66 | view.ViewModel = ndvm;
67 |
68 | DialogHost.Show(view);
69 | })
70 | .DisposeWith(disposables))
71 | .DisposeWith(disposables);
72 |
73 | this.Bind(ViewModel, vm => vm.SearchTerms, v => v.SearchTextBox.Text)
74 | .DisposeWith(disposables);
75 |
76 | this.SearchTextBox.Events()
77 | .KeyUp.Where(k => k.Key == Key.Enter)
78 | .Select(_ => Unit.Default).InvokeCommand(this, v => v.ViewModel.NavigateToUriCommand)
79 | .DisposeWith(disposables);
80 |
81 | this.SearchTextBox.Events()
82 | .KeyUp.Where(k => k.Key == Key.Escape)
83 | .Select(_ => Unit.Default).InvokeCommand(this, v => v.CloseSearchCommand)
84 | .DisposeWith(disposables);
85 |
86 | this.BindCommand(ViewModel, vm => vm.NavigateToUriCommand, v => v.NavigateToButton)
87 | .DisposeWith(disposables);
88 |
89 | this.OneWayBind(ViewModel,
90 | vm => vm.IsSearchValidUri,
91 | v => v.NavigateToButton.Visibility,
92 | x => x ? Visibility.Visible : Visibility.Collapsed)
93 | .DisposeWith(disposables);
94 |
95 | this.BindCommand(ViewModel, vm => vm.AddDeviceCommand, v => v.AddButton, nameof(AddButton.ToggleCheckedContentClick))
96 | .DisposeWith(disposables);
97 | });
98 | }
99 |
100 | public readonly static DependencyProperty ViewModelProperty = DependencyProperty.Register(
101 | nameof(ViewModel), typeof(RecentDevicesViewModel), typeof(RecentDevicesView), new PropertyMetadata(null));
102 |
103 | public RecentDevicesViewModel ViewModel { get => (RecentDevicesViewModel)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); }
104 |
105 | object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as RecentDevicesViewModel; }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/CoAPExplorer.WPF/Views/SearchView.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Reactive;
6 | using System.Reactive.Disposables;
7 | using System.Reactive.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using System.Windows;
11 | using System.Windows.Controls;
12 | using System.Windows.Data;
13 | using System.Windows.Documents;
14 | using System.Windows.Input;
15 | using System.Windows.Media;
16 | using System.Windows.Media.Imaging;
17 | using System.Windows.Navigation;
18 | using System.Windows.Shapes;
19 | using CoAPExplorer.Models;
20 | using CoAPExplorer.ViewModels;
21 | using ReactiveUI;
22 |
23 | namespace CoAPExplorer.WPF.Views
24 | {
25 | ///
26 | /// Interaction logic for SearchView.xaml
27 | ///
28 | public partial class SearchView : UserControl, IViewFor
29 | {
30 |
31 | public SearchView()
32 | {
33 | InitializeComponent();
34 |
35 | this.WhenActivated((CompositeDisposable disposables) =>
36 | {
37 | var visibilityConverter = new BooleanToVisibilityConverter();
38 |
39 | this.OneWayBind(ViewModel, vm => vm.Devices, v => v.DeviceList.ItemsSource)
40 | .DisposeWith(disposables);
41 |
42 | this.WhenAnyValue(v => v.DeviceList.SelectedItem)
43 | .Select(x => x as DeviceViewModel)
44 | .Where(x => x != null)
45 | .InvokeCommand(this, v => v.ViewModel.OpenDeviceCommand)
46 | .DisposeWith(disposables);
47 |
48 | this.Bind(ViewModel, vm => vm.SearchUrl, v => v.SearchUrl.Text)
49 | .DisposeWith(disposables);
50 |
51 | this.OneWayBind(ViewModel,
52 | vm => vm.IsSearching,
53 | v => v.GoButton.Visibility,
54 | x => visibilityConverter.Convert(!x, typeof(Visibility), null, CultureInfo.CurrentCulture))
55 | .DisposeWith(disposables);
56 |
57 | this.BindCommand(ViewModel, vm => vm.SearchCommand, v => v.GoButton)
58 | .DisposeWith(disposables);
59 |
60 |
61 | this.OneWayBind(ViewModel,
62 | vm => vm.IsSearching,
63 | v => v.StopButton.Visibility,
64 | x => visibilityConverter.Convert(x, typeof(Visibility), null, CultureInfo.CurrentCulture))
65 | .DisposeWith(disposables);
66 |
67 | this.BindCommand(ViewModel, vm => vm.StopCommand, v => v.StopButton)
68 | .DisposeWith(disposables);
69 |
70 | this.OneWayBind(ViewModel,
71 | vm => vm.IsSearching,
72 | v => v.SearchProgress.Visibility,
73 | x => visibilityConverter.Convert(x, typeof(Visibility), null, CultureInfo.CurrentCulture))
74 | .DisposeWith(disposables);
75 |
76 | this.OneWayBind(ViewModel, vm => vm.IsSearching, v => v.FilterPanel.IsEnabled, x => !x)
77 | .DisposeWith(disposables);
78 | });
79 | }
80 |
81 | #region IViewFor Boilerplate
82 |
83 | public readonly static DependencyProperty ViewModelProperty = DependencyProperty.Register(
84 | nameof(ViewModel), typeof(SearchViewModel), typeof(SearchView), new PropertyMetadata(null));
85 |
86 | object IViewFor.ViewModel { get => ViewModel; set => ViewModel = value as SearchViewModel; }
87 |
88 | public SearchViewModel ViewModel { get => (SearchViewModel)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); }
89 |
90 | #endregion
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/App.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Reactive;
6 | using System.Reactive.Linq;
7 | using System.Reactive.Subjects;
8 | using System.Text;
9 |
10 | using CoAPNet;
11 | using CoAPNet.Udp;
12 | using ReactiveUI;
13 | using Splat;
14 |
15 | using CoAPExplorer.Extensions;
16 | using CoAPExplorer.Models;
17 | using CoAPExplorer.Services;
18 | using CoAPExplorer.ViewModels;
19 |
20 |
21 | namespace CoAPExplorer
22 | {
23 | public class App
24 | {
25 | public string DataPath { get; }
26 |
27 | private ISubject _toastNotifications
28 | = new Subject();
29 |
30 | public IObservable ToastNotifications => _toastNotifications;
31 |
32 | public IMutableDependencyResolver Locator => global::Splat.Locator.CurrentMutable;
33 |
34 | ///
35 | /// Logs the exception to the applications log directory and invokes the exception event for displaying to the user.
36 | ///
37 | ///
38 | public static void LogException(Exception exception)
39 | {
40 | if (exception == null)
41 | return;
42 |
43 | var app = Splat.Locator.Current.GetService();
44 |
45 | // TODO: Fire an event which will display a toast of the recently received exception.
46 | // TODO: Create a view (seperate window?) for viewing all exceptions
47 |
48 | var logPath = new DirectoryInfo(Path.Combine(app.DataPath, "logs"));
49 |
50 | if (!logPath.Exists)
51 | logPath.Create();
52 |
53 | var timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH-mm-ss", System.Globalization.CultureInfo.InvariantCulture);
54 | var baseFileName = $"{timestamp}-Error-{exception.GetType().Name}";
55 |
56 | var attempt = 1;
57 | var filename = Path.Combine(app.DataPath, "logs", $"{baseFileName}.log");
58 | while(File.Exists(filename))
59 | filename = Path.Combine(app.DataPath, "logs", $"{baseFileName}.{attempt++}.log");
60 |
61 | using (var log = new StreamWriter(filename, false, Encoding.UTF8))
62 | log.Write(exception.ToString());
63 |
64 | var message = "An error has occured.";
65 | #if DEBUG
66 | // Only display exception details in the UI for debug builds
67 | message = $"{exception.GetType().Name}: {exception.Message}.";
68 | #endif
69 | app._toastNotifications.OnNext(new ToastNotification(message, ToastNotificationType.Error, ("Show", OpenLogFile(filename))));
70 | }
71 |
72 | private static ReactiveCommand OpenLogFile(string filename)
73 | {
74 | return ReactiveCommand.Create(() =>
75 | {
76 | var process = Process.Start(filename);
77 | });
78 | }
79 |
80 | public App(string dataPath)
81 | {
82 | DataPath = dataPath;
83 |
84 | #if DEBUG
85 | // Debug logging
86 | Locator.RegisterConstant(new MyDebugLogger { Level = LogLevel.Debug });
87 | #endif
88 |
89 | // Register logger for all require generic uses of Microsoft.Extensions.Logging.ILogger
90 | //services.RegisterLogger()
91 | // .RegisterLogger()
92 | // .RegisterLogger();
93 |
94 | // Ensure coap related schemas are supported
95 | CoapStyleUriParser.Register();
96 |
97 | // App-wide services
98 | Locator
99 | .RegisterLogger();
100 |
101 | Locator.Register(() => new DiscoveryService());
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/CoAPExplorer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | latest
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/CoapIcon.cs:
--------------------------------------------------------------------------------
1 | namespace CoAPExplorer
2 | {
3 | public enum CoapExplorerIcon
4 | {
5 | None,
6 | Settings,
7 | Search,
8 | Favouriate,
9 | Recent
10 | }
11 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer/Database/CoapExplorerContext.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.Models;
2 | using Microsoft.EntityFrameworkCore;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | namespace CoAPExplorer.Database
8 | {
9 | public class CoapExplorerContext : DbContext
10 | {
11 | private readonly string _databasePath;
12 |
13 | public DbSet Devices { get; set; }
14 |
15 | public DbSet RecentMessages { get; set; }
16 |
17 | public string DatabasePath => _databasePath;
18 |
19 | public CoapExplorerContext(DbContextOptions options)
20 | :base(options)
21 | { }
22 |
23 | public CoapExplorerContext(string databasePath)
24 | {
25 | _databasePath = databasePath;
26 | }
27 |
28 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
29 | {
30 | if(!string.IsNullOrEmpty(_databasePath))
31 | optionsBuilder.UseSqlite($"Filename={_databasePath}");
32 |
33 | base.OnConfiguring(optionsBuilder);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Database/CoapOptionSerialiser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using CoAPNet;
5 | using Newtonsoft.Json;
6 | using Newtonsoft.Json.Linq;
7 | using Newtonsoft.Json.Serialization;
8 |
9 | namespace CoAPExplorer.Database
10 | {
11 | //[JsonConverter(typeof(StringFlagEnumConverter))]
12 | public class CoapOptionConverter : JsonConverter
13 | {
14 | // TODO: Support custom option factory
15 | private readonly CoAPNet.Options.OptionFactory _factory = CoAPNet.Options.OptionFactory.Default;
16 |
17 | public override CoapOption ReadJson(JsonReader reader, Type objectType, CoapOption existingValue, bool hasExistingValue, JsonSerializer serializer)
18 | {
19 | if (reader.TokenType == JsonToken.Null)
20 | return null;
21 |
22 | if (reader.TokenType != JsonToken.StartObject)
23 | return null;
24 |
25 | byte[] data = new byte[] { };
26 | int? number = 0;
27 |
28 | reader.Read();
29 |
30 | while (reader.TokenType != JsonToken.EndObject)
31 | {
32 | if (reader.Path.EndsWith(".n"))
33 | number = reader.ReadAsInt32();
34 | else if(reader.Path.EndsWith(".d"))
35 | data = reader.ReadAsBytes();
36 | else
37 | reader.Read();
38 |
39 | reader.Read();
40 | }
41 |
42 | return _factory.Create(number.Value, data);
43 | }
44 |
45 | public override void WriteJson(JsonWriter writer, CoapOption value, JsonSerializer serializer)
46 | {
47 | if (value == null)
48 | {
49 | writer.WriteNull();
50 | return;
51 | }
52 |
53 | writer.WriteStartObject();
54 |
55 | writer.WritePropertyName("n");
56 | writer.WriteValue(value.OptionNumber);
57 |
58 | writer.WritePropertyName("d");
59 | writer.WriteValue(value.GetBytes());
60 |
61 | writer.WriteEndObject();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Database/Migrations/20180516004057_Initial.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using CoAPExplorer;
3 | using CoAPExplorer.Database;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage;
9 | using Microsoft.EntityFrameworkCore.Storage.Internal;
10 | using System;
11 |
12 | namespace CoAPExplorer.Database.Migrations
13 | {
14 | [DbContext(typeof(CoapExplorerContext))]
15 | [Migration("20180516004057_Initial")]
16 | partial class Initial
17 | {
18 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
19 | {
20 | #pragma warning disable 612, 618
21 | modelBuilder
22 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011");
23 |
24 | modelBuilder.Entity("CoAPExplorer.Models.Device", b =>
25 | {
26 | b.Property("Id")
27 | .ValueGeneratedOnAdd();
28 |
29 | b.Property("EndpointType");
30 |
31 | b.Property("IsFavourite");
32 |
33 | b.Property("LastSeen");
34 |
35 | b.Property("Name");
36 |
37 | b.Property("_dbAddress")
38 | .HasColumnName("Address");
39 |
40 | b.HasKey("Id");
41 |
42 | b.ToTable("Devices");
43 | });
44 |
45 | modelBuilder.Entity("CoAPExplorer.Models.DeviceResource", b =>
46 | {
47 | b.Property("Id")
48 | .ValueGeneratedOnAdd();
49 |
50 | b.Property("DeviceId");
51 |
52 | b.Property("Name");
53 |
54 | b.Property("_dbContentFormat")
55 | .HasColumnName("ContentFormat");
56 |
57 | b.Property("_dbUrl")
58 | .HasColumnName("Url");
59 |
60 | b.HasKey("Id");
61 |
62 | b.HasIndex("DeviceId");
63 |
64 | b.ToTable("DeviceResource");
65 | });
66 |
67 | modelBuilder.Entity("CoAPExplorer.Models.Message", b =>
68 | {
69 | b.Property("Id")
70 | .ValueGeneratedOnAdd();
71 |
72 | b.Property("Payload");
73 |
74 | b.Property("_dbCode")
75 | .HasColumnName("Code");
76 |
77 | b.Property("_dbContentFormat")
78 | .HasColumnName("ContentFormat");
79 |
80 | b.Property("_dbOptions")
81 | .HasColumnName("Options");
82 |
83 | b.Property("_dbUrl")
84 | .HasColumnName("Url");
85 |
86 | b.HasKey("Id");
87 |
88 | b.ToTable("RecentMessages");
89 | });
90 |
91 | modelBuilder.Entity("CoAPExplorer.Models.DeviceResource", b =>
92 | {
93 | b.HasOne("CoAPExplorer.Models.Device", "Device")
94 | .WithMany("KnownResources")
95 | .HasForeignKey("DeviceId");
96 | });
97 | #pragma warning restore 612, 618
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Database/Migrations/20180516004057_Initial.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace CoAPExplorer.Database.Migrations
6 | {
7 | public partial class Initial : Migration
8 | {
9 | protected override void Up(MigrationBuilder migrationBuilder)
10 | {
11 | migrationBuilder.CreateTable(
12 | name: "Devices",
13 | columns: table => new
14 | {
15 | Id = table.Column(nullable: false)
16 | .Annotation("Sqlite:Autoincrement", true),
17 | EndpointType = table.Column(nullable: false),
18 | IsFavourite = table.Column(nullable: false),
19 | LastSeen = table.Column(nullable: false),
20 | Name = table.Column(nullable: true),
21 | Address = table.Column(nullable: true)
22 | },
23 | constraints: table =>
24 | {
25 | table.PrimaryKey("PK_Devices", x => x.Id);
26 | });
27 |
28 | migrationBuilder.CreateTable(
29 | name: "RecentMessages",
30 | columns: table => new
31 | {
32 | Id = table.Column(nullable: false)
33 | .Annotation("Sqlite:Autoincrement", true),
34 | Payload = table.Column(nullable: true),
35 | Code = table.Column(nullable: true),
36 | ContentFormat = table.Column(nullable: true),
37 | Options = table.Column(nullable: true),
38 | Url = table.Column(nullable: true)
39 | },
40 | constraints: table =>
41 | {
42 | table.PrimaryKey("PK_RecentMessages", x => x.Id);
43 | });
44 |
45 | migrationBuilder.CreateTable(
46 | name: "DeviceResource",
47 | columns: table => new
48 | {
49 | Id = table.Column(nullable: false)
50 | .Annotation("Sqlite:Autoincrement", true),
51 | DeviceId = table.Column(nullable: true),
52 | Name = table.Column(nullable: true),
53 | ContentFormat = table.Column(nullable: true),
54 | Url = table.Column(nullable: true)
55 | },
56 | constraints: table =>
57 | {
58 | table.PrimaryKey("PK_DeviceResource", x => x.Id);
59 | table.ForeignKey(
60 | name: "FK_DeviceResource_Devices_DeviceId",
61 | column: x => x.DeviceId,
62 | principalTable: "Devices",
63 | principalColumn: "Id",
64 | onDelete: ReferentialAction.Restrict);
65 | });
66 |
67 | migrationBuilder.CreateIndex(
68 | name: "IX_DeviceResource_DeviceId",
69 | table: "DeviceResource",
70 | column: "DeviceId");
71 | }
72 |
73 | protected override void Down(MigrationBuilder migrationBuilder)
74 | {
75 | migrationBuilder.DropTable(
76 | name: "DeviceResource");
77 |
78 | migrationBuilder.DropTable(
79 | name: "RecentMessages");
80 |
81 | migrationBuilder.DropTable(
82 | name: "Devices");
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Database/Migrations/CoapExplorerContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using CoAPExplorer;
3 | using CoAPExplorer.Database;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage;
9 | using Microsoft.EntityFrameworkCore.Storage.Internal;
10 | using System;
11 |
12 | namespace CoAPExplorer.Database.Migrations
13 | {
14 | [DbContext(typeof(CoapExplorerContext))]
15 | partial class CoapExplorerContextModelSnapshot : ModelSnapshot
16 | {
17 | protected override void BuildModel(ModelBuilder modelBuilder)
18 | {
19 | #pragma warning disable 612, 618
20 | modelBuilder
21 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011");
22 |
23 | modelBuilder.Entity("CoAPExplorer.Models.Device", b =>
24 | {
25 | b.Property("Id")
26 | .ValueGeneratedOnAdd();
27 |
28 | b.Property("EndpointType");
29 |
30 | b.Property("IsFavourite");
31 |
32 | b.Property("LastSeen");
33 |
34 | b.Property("Name");
35 |
36 | b.Property("_dbAddress")
37 | .HasColumnName("Address");
38 |
39 | b.HasKey("Id");
40 |
41 | b.ToTable("Devices");
42 | });
43 |
44 | modelBuilder.Entity("CoAPExplorer.Models.DeviceResource", b =>
45 | {
46 | b.Property("Id")
47 | .ValueGeneratedOnAdd();
48 |
49 | b.Property("DeviceId");
50 |
51 | b.Property("Name");
52 |
53 | b.Property("_dbContentFormat")
54 | .HasColumnName("ContentFormat");
55 |
56 | b.Property("_dbUrl")
57 | .HasColumnName("Url");
58 |
59 | b.HasKey("Id");
60 |
61 | b.HasIndex("DeviceId");
62 |
63 | b.ToTable("DeviceResource");
64 | });
65 |
66 | modelBuilder.Entity("CoAPExplorer.Models.Message", b =>
67 | {
68 | b.Property("Id")
69 | .ValueGeneratedOnAdd();
70 |
71 | b.Property("Payload");
72 |
73 | b.Property("_dbCode")
74 | .HasColumnName("Code");
75 |
76 | b.Property("_dbContentFormat")
77 | .HasColumnName("ContentFormat");
78 |
79 | b.Property("_dbOptions")
80 | .HasColumnName("Options");
81 |
82 | b.Property("_dbUrl")
83 | .HasColumnName("Url");
84 |
85 | b.HasKey("Id");
86 |
87 | b.ToTable("RecentMessages");
88 | });
89 |
90 | modelBuilder.Entity("CoAPExplorer.Models.DeviceResource", b =>
91 | {
92 | b.HasOne("CoAPExplorer.Models.Device", "Device")
93 | .WithMany("KnownResources")
94 | .HasForeignKey("DeviceId");
95 | });
96 | #pragma warning restore 612, 618
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/EndpointType.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace CoAPExplorer
4 | {
5 | public enum EndpointType
6 | {
7 | [Display(Name = "None")]
8 | None,
9 | [Display(Name = "UDP")]
10 | Udp
11 | }
12 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer/Extensions/CoapMessageExtensions.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.Models;
2 | using CoAPNet;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.Linq;
7 | using System.Text;
8 |
9 | namespace CoAPExplorer.Extensions
10 | {
11 | internal static class CoapMessageExtensions
12 | {
13 | public static CoapMessage ToCoapMessage(this Message message)
14 | {
15 | var coapMessage = new CoapMessage
16 | {
17 | Id = message.MessageId,
18 | Token = message.Token ?? new byte[] { },
19 | Code = message.Code,
20 | Type = CoapMessageType.Confirmable,
21 | };
22 | coapMessage.SetUri(message.Url, UriComponents.PathAndQuery);
23 |
24 | if ((message.Code.IsRequest()) && message.ContentFormat != null)
25 | {
26 | coapMessage.Options.Add(new CoAPNet.Options.ContentFormat(message.ContentFormat));
27 | coapMessage.Payload = message.Payload;
28 | }
29 |
30 | foreach (var option in message.Options)
31 | coapMessage.Options.Add(option);
32 |
33 | return coapMessage;
34 | }
35 |
36 | public static Message ToMessage(this CoapMessage coapMessage)
37 | {
38 | var contentTypeOption
39 | = coapMessage.Options.FirstOrDefault(o => o.OptionNumber == CoapRegisteredOptionNumber.ContentFormat)
40 | as CoAPNet.Options.ContentFormat;
41 |
42 | var message = new Message
43 | {
44 | MessageId = coapMessage.Id,
45 | Token = coapMessage.Token,
46 | Code = coapMessage.Code,
47 |
48 | ContentFormat = contentTypeOption?.MediaType,
49 |
50 | Url = coapMessage.GetUri(),
51 | Payload = coapMessage.Payload,
52 | };
53 |
54 | message.Options = new ObservableCollection(
55 | coapMessage.Options.Where(o => o.OptionNumber != CoapRegisteredOptionNumber.ContentFormat));
56 |
57 | return message;
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Extensions/EnumExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.Reflection;
5 | using System.Text;
6 |
7 | namespace CoAPExplorer.Extensions
8 | {
9 | public static class EnumExtensions
10 | {
11 | public static string GetDisplayValue(this Enum value)
12 | {
13 | var fieldInfo = value.GetType().GetField(value.ToString());
14 |
15 | var descriptionAttributes = fieldInfo.GetCustomAttributes(
16 | typeof(DisplayAttribute), false) as DisplayAttribute[];
17 |
18 | if (descriptionAttributes[0].ResourceType != null)
19 | {
20 | foreach (PropertyInfo staticProperty in descriptionAttributes[0].ResourceType.GetProperties(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
21 | {
22 | if (staticProperty.PropertyType == typeof(System.Resources.ResourceManager))
23 | {
24 | System.Resources.ResourceManager resourceManager = (System.Resources.ResourceManager)staticProperty.GetValue(null, null);
25 | return resourceManager.GetString(descriptionAttributes[0].Name);
26 | }
27 | }
28 |
29 | return descriptionAttributes[0].Name; // Fallback with the key name
30 | }
31 |
32 | if (descriptionAttributes == null) return string.Empty;
33 | return (descriptionAttributes.Length > 0) ? descriptionAttributes[0].Name : value.ToString();
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/FormattedTextException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace CoAPExplorer
4 | {
5 | public class FormattedTextException : Exception
6 | {
7 | public FormattedTextException(string message, int line, int offset)
8 | : base(message)
9 | {
10 | Line = line;
11 | Offset = offset;
12 | }
13 |
14 | public FormattedTextException(string message, int line, int offset, Exception innerException)
15 | : base(message, innerException)
16 | {
17 | Line = line;
18 | Offset = offset;
19 | }
20 |
21 | public int Line { get; }
22 |
23 | public int Offset { get; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Models/Device.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.Services;
2 | using CoAPNet;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.ComponentModel.DataAnnotations;
7 | using System.ComponentModel.DataAnnotations.Schema;
8 | using System.Threading.Tasks;
9 |
10 | namespace CoAPExplorer.Models
11 | {
12 | public class Device
13 | {
14 | private ICoapEndpoint _endpoint;
15 | private Uri _address = null;
16 |
17 | [Key]
18 | public int Id { get; set; }
19 |
20 | [NotMapped]
21 | public ICoapEndpoint Endpoint
22 | {
23 | get => _endpoint ?? (_endpoint = CoapEndpointFactory.GetEndpoint(Address));
24 | set => _endpoint = value;
25 | }
26 |
27 | public ICollection KnownResources { get; set; }
28 | = new ObservableCollection();
29 |
30 | public EndpointType EndpointType { get; set; }
31 |
32 | public bool IsFavourite { get; set; }
33 |
34 | public string Name { get; set; } = string.Empty;
35 |
36 | [NotMapped]
37 | public Uri Address { get => _address ?? (_address = Endpoint.BaseUri); set => _address = value; }
38 | public DateTime LastSeen { get; set; } = DateTime.MinValue;
39 |
40 | [Column(nameof(Address))]
41 | public string _dbAddress
42 | {
43 | get => Address?.ToString();
44 | set
45 | {
46 | if (value == null)
47 | {
48 | Address = null;
49 | return;
50 | }
51 |
52 | Address = new Uri(value, UriKind.Absolute);
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer/Models/DeviceResource.cs:
--------------------------------------------------------------------------------
1 | using CoAPNet.Options;
2 | using System;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.ComponentModel.DataAnnotations.Schema;
5 |
6 | namespace CoAPExplorer.Models
7 | {
8 | public class DeviceResource
9 | {
10 | [Key]
11 | public int Id { get; set; }
12 |
13 | public Device Device { get; set; }
14 |
15 | [NotMapped]
16 | public Uri Url { get; set; }
17 |
18 | public string Name { get; set; }
19 |
20 | [NotMapped]
21 | public ContentFormatType ContentFormat { get; set; } = null;
22 |
23 | [Column(nameof(Url))]
24 | public string _dbUrl
25 | {
26 | get => Url?.ToString();
27 | set
28 | {
29 | if (value == null)
30 | {
31 | Url = null;
32 | return;
33 | }
34 |
35 | Url = new Uri(value, UriKind.RelativeOrAbsolute);
36 | }
37 | }
38 |
39 | [Column(nameof(ContentFormat))]
40 | public string _dbContentFormat
41 | {
42 | get => ContentFormat != null ? $"{ContentFormat.Value} - {ContentFormat}" : null;
43 | set
44 | {
45 | if (value == null)
46 | {
47 | ContentFormat = null;
48 | return;
49 | }
50 |
51 | var p = value.Split(new[] { " - " }, StringSplitOptions.RemoveEmptyEntries);
52 | ContentFormat = new ContentFormatType(int.Parse(p[0]), p[1]);
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer/Models/Message.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.Database;
2 | using CoAPExplorer.Extensions;
3 | using CoAPNet;
4 | using CoAPNet.Options;
5 | using ReactiveUI;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Collections.ObjectModel;
9 | using System.ComponentModel.DataAnnotations;
10 | using System.ComponentModel.DataAnnotations.Schema;
11 | using System.IO;
12 | using System.Linq;
13 | using System.Runtime.Serialization;
14 | using System.Text;
15 |
16 | namespace CoAPExplorer.Models
17 | {
18 | public class Message
19 | {
20 | private byte[] _token;
21 | private byte[] _payload;
22 | private Stream _payloadStream;
23 |
24 | [Key]
25 | public int Id { get; set; }
26 |
27 | [NotMapped]
28 | public int MessageId { get; set; }
29 |
30 |
31 | [NotMapped]
32 | public byte[] Token
33 | {
34 | get => _token;
35 | set
36 | {
37 | if ((value?.Length ?? 0) > 8)
38 | throw new ArgumentException($"{nameof(Token)} may have no moer than 8 bytes");
39 | _token = value;
40 | }
41 | }
42 |
43 | [NotMapped]
44 | public CoapMessageCode Code { get; set; } = CoapMessageCode.None;
45 |
46 | [NotMapped]
47 | public Uri Url { get; set; } = new Uri(string.Empty, UriKind.RelativeOrAbsolute);
48 |
49 | [NotMapped]
50 | public IList Options { get; set; } = new List();
51 |
52 | [NotMapped]
53 | public ContentFormatType ContentFormat { get; set; } = null;
54 |
55 | public byte[] Payload
56 | {
57 | get => _payload;
58 | set
59 | {
60 | if (_payloadStream != null)
61 | throw new InvalidOperationException($"Please set {nameof(PayloadStream)} to null before assigning to {nameof(Payload)}");
62 | _payload = value;
63 | }
64 | }
65 |
66 | [NotMapped]
67 | public Stream PayloadStream
68 | {
69 | get => _payloadStream;
70 | set
71 | {
72 | if (_payload != null)
73 | throw new InvalidOperationException($"Please set {nameof(Payload)} to null before assigning to {nameof(PayloadStream)}");
74 | _payloadStream = value;
75 | }
76 | }
77 |
78 | [Column(nameof(Code))]
79 | public string _dbCode
80 | {
81 | get => Code.ToString() ?? "0.00";
82 | set
83 | {
84 | var p = value.Split('.');
85 | Code = new CoapMessageCode(int.Parse(p[0]), int.Parse(p[1]));
86 | }
87 | }
88 |
89 | [Column(nameof(ContentFormat))]
90 | public string _dbContentFormat
91 | {
92 | get => ContentFormat != null ? $"{ContentFormat.Value} - {ContentFormat}" : null;
93 | set
94 | {
95 | if (value == null)
96 | {
97 | ContentFormat = null;
98 | return;
99 | }
100 |
101 | var p = value.Split(new[] { " - " }, StringSplitOptions.RemoveEmptyEntries);
102 | ContentFormat = new ContentFormatType(int.Parse(p[0]), p[1]);
103 | }
104 | }
105 |
106 | [Column(nameof(Url))]
107 | public string _dbUrl
108 | {
109 | get => Url?.ToString();
110 | set
111 | {
112 | if (value == null)
113 | {
114 | Url = null;
115 | return;
116 | }
117 |
118 | Url = new Uri(value, UriKind.RelativeOrAbsolute);
119 | }
120 | }
121 |
122 | [Column(nameof(Options))]
123 | public string _dbOptions
124 | {
125 | get => Newtonsoft.Json.JsonConvert.SerializeObject(Options, new CoapOptionConverter());
126 | set
127 | {
128 | var options = Newtonsoft.Json.JsonConvert.DeserializeObject>(value, new CoapOptionConverter());
129 | Options = new ObservableCollection(options);
130 | }
131 | }
132 |
133 | public override bool Equals(object obj)
134 | {
135 | if (obj is Message message)
136 | {
137 | if (message.Url != Url)
138 | return false;
139 | if (message.Code != Code)
140 | return false;
141 | if (message.Options.Any(o => !Options.Contains(o)))
142 | return false;
143 | if (message.Payload != Payload)
144 | return false;
145 | return true;
146 | }
147 | return base.Equals(obj);
148 | }
149 |
150 | public override int GetHashCode()
151 | {
152 | // Well, there aren't any immutable properties... so this will do.
153 | return 12425;
154 | }
155 |
156 | public override string ToString()
157 | {
158 | return this.ToCoapMessage().ToString();
159 | }
160 |
161 | public Message Clone()
162 | {
163 | return new Message
164 | {
165 | MessageId = MessageId,
166 | Token = Token?.Clone() as byte[],
167 | Url = Url,
168 | Code = Code,
169 | ContentFormat = ContentFormat,
170 | Options = new List(Options),
171 | Payload = Payload?.Clone() as byte[],
172 | };
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Models/NavigationItem.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 |
3 | namespace CoAPExplorer.Models
4 | {
5 | public class NavigationItem
6 | {
7 | public string Name { get; set; }
8 |
9 | public ReactiveCommand Command { get; set; }
10 |
11 | public CoapExplorerIcon Icon { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer/Models/RequestFilter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace CoAPExplorer.Models
6 | {
7 | public class RequestFilter
8 | {
9 | public string Key { get; set; }
10 | public string Value { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Models/ToastNotification.cs:
--------------------------------------------------------------------------------
1 | using ReactiveUI;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace CoAPExplorer.Models
7 | {
8 | public enum ToastNotificationType
9 | {
10 | Debug,
11 | Information,
12 | Warning,
13 | Error,
14 | }
15 |
16 | public readonly struct ToastNotification
17 | {
18 | public readonly string Message;
19 |
20 | public readonly ToastNotificationType Type;
21 |
22 | public readonly IReadOnlyList<(string Label, ReactiveCommand Command)> Actions;
23 |
24 | public ToastNotification(string message, ToastNotificationType type = ToastNotificationType.Information, params (string Label, ReactiveCommand Command)[] actions)
25 | {
26 | Message = message;
27 |
28 | Type = type;
29 |
30 | Actions = actions;
31 | }
32 |
33 | public ToastNotification(string message, ToastNotificationType type = ToastNotificationType.Information, params Tuple[] actions)
34 | {
35 | Message = message;
36 |
37 | Type = type;
38 |
39 | Actions = actions.Select(t => (t.Item1, t.Item2)).ToList();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly:InternalsVisibleTo("Database")]
--------------------------------------------------------------------------------
/src/CoAPExplorer/Services/CoapEndpointFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Text;
5 |
6 | using CoAPNet;
7 | using CoAPNet.Udp;
8 | using System.Linq;
9 |
10 | namespace CoAPExplorer.Services
11 | {
12 |
13 | public class CoapEndpointFactory
14 | {
15 | static CoapEndpointFactory()
16 | {
17 | CoapStyleUriParser.Register();
18 | }
19 |
20 | public static Uri CreateUriFromAddress(string address)
21 | {
22 | if (!address.Contains("://"))
23 | address = "coap://" + address;
24 |
25 | return new Uri(address, UriKind.Absolute);
26 | }
27 |
28 | public static ICoapEndpoint GetEndpoint(Uri address, bool defaultEndpoint = true)
29 | {
30 | if (address == null)
31 | throw new ArgumentNullException(nameof(address));
32 |
33 | if(address.Scheme == "coap")
34 | {
35 | int port = address.IsDefaultPort && defaultEndpoint ? Coap.Port : 0;
36 | return new CoapUdpEndPoint(ParseIPEndpoint(address.Host, port));
37 | }
38 |
39 | return new CoapEndpoint();
40 | }
41 |
42 | public static IPEndPoint ParseIPEndpoint(string endpointstring, int defaultport = -1)
43 | {
44 | if (string.IsNullOrEmpty(endpointstring)
45 | || endpointstring.Trim().Length == 0)
46 | {
47 | throw new ArgumentException("Endpoint descriptor may not be empty.");
48 | }
49 |
50 | if (defaultport != -1 &&
51 | (defaultport < IPEndPoint.MinPort
52 | || defaultport > IPEndPoint.MaxPort))
53 | {
54 | throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
55 | }
56 |
57 | string[] values = endpointstring.Split(new char[] { ':' });
58 | IPAddress ipaddy;
59 | int port = -1;
60 |
61 | //check if we have an IPv6 or ports
62 | if (values.Length <= 2) // ipv4 or hostname
63 | {
64 | if (values.Length == 1)
65 | //no port is specified, default
66 | port = defaultport;
67 | else
68 | port = GetPort(values[1]);
69 |
70 | //try to use the address as IPv4, otherwise get hostname
71 | if (!IPAddress.TryParse(values[0], out ipaddy))
72 | ipaddy = ResolveHost(values[0]);
73 | }
74 | else if (values.Length > 2) //ipv6
75 | {
76 | //could [a:b:c]:d
77 | if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
78 | {
79 | string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
80 | ipaddy = IPAddress.Parse(ipaddressstring);
81 | port = GetPort(values[values.Length - 1]);
82 | }
83 | else //[a:b:c] or a:b:c
84 | {
85 | ipaddy = IPAddress.Parse(endpointstring);
86 | port = defaultport;
87 | }
88 | }
89 | else
90 | {
91 | throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
92 | }
93 |
94 | if (port == -1)
95 | throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
96 |
97 | return new IPEndPoint(ipaddy, port);
98 | }
99 |
100 | private static int GetPort(string p)
101 | {
102 | int port;
103 |
104 | if (!int.TryParse(p, out port)
105 | || port < IPEndPoint.MinPort
106 | || port > IPEndPoint.MaxPort)
107 | {
108 | throw new FormatException(string.Format("Invalid end point port '{0}'", p));
109 | }
110 |
111 | return port;
112 | }
113 |
114 | private static IPAddress ResolveHost(string p)
115 | {
116 | var hosts = Dns.GetHostAddresses(p);
117 |
118 | if (hosts == null || hosts.Length == 0)
119 | throw new ArgumentException(string.Format("Host not found: {0}", p));
120 |
121 | return hosts[0];
122 | }
123 | }
124 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer/Services/CoapService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reactive.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | using CoAPNet;
9 | using Splat;
10 |
11 | using CoAPExplorer.Extensions;
12 | using CoAPExplorer.Models;
13 | using System.IO;
14 |
15 | namespace CoAPExplorer.Services
16 | {
17 | public class CoapService
18 | {
19 | private readonly CoapClient _coapClient;
20 | private readonly ICoapEndpoint _endpoint;
21 |
22 | public CoapService(Device targetDevice)
23 | : this(new UriBuilder { Scheme = targetDevice.Address.Scheme, Host = "0.0.0.0", Port = 0 }.Uri)
24 | { }
25 |
26 | public CoapService(string scheme)
27 | :this(new UriBuilder { Scheme = scheme, Host = "0.0.0.0", Port = 0 }.Uri)
28 | { }
29 |
30 | public CoapService(Uri listenAddress)
31 | {
32 | _endpoint = CoapEndpointFactory.GetEndpoint(listenAddress);
33 | _coapClient = new CoapClient(_endpoint);
34 | }
35 |
36 | public IObservable SendMessage(Message message, ICoapEndpoint endpoint)
37 | {
38 | if (message is null)
39 | return Observable.Empty();
40 |
41 | var coapMessage = message.ToCoapMessage();
42 | var messageContext = coapMessage.CreateBlockWiseContext(_coapClient);
43 |
44 | return Observable.Create(observer =>
45 | {
46 | var cts = new CancellationTokenSource();
47 | Task.Run(async () =>
48 | {
49 | try
50 | {
51 | if (coapMessage.IsBlockWise())
52 | {
53 | using (var writer = new CoapBlockStreamWriter(messageContext, endpoint))
54 | await message.PayloadStream.CopyToAsync(writer, writer.BlockSize);
55 | }
56 | else
57 | {
58 | var id = await _coapClient.SendAsync(coapMessage, endpoint, cts.Token);
59 | messageContext = new CoapBlockWiseContext(_coapClient, coapMessage, await _coapClient.GetResponseAsync(id, cts.Token));
60 | }
61 |
62 | var response = messageContext.Response.ToMessage();
63 |
64 | if (messageContext.Response.IsBlockWise())
65 | {
66 | var memoryStream = new MemoryStream();
67 |
68 | using (var reader = new CoapBlockStreamReader(messageContext, endpoint))
69 | reader.CopyTo(memoryStream);
70 |
71 | response.Payload = memoryStream.ToArray();
72 | }
73 |
74 | observer.OnNext(response);
75 |
76 | observer.OnCompleted();
77 | }
78 | catch(Exception ex)
79 | {
80 | observer.OnError(ex);
81 | }
82 | });
83 |
84 | return cts.Cancel;
85 | });
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Services/IDiscoveryService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using CoAPExplorer.Models;
4 |
5 | namespace CoAPExplorer.Services
6 | {
7 | public interface IDiscoveryService
8 | {
9 | IObservable DiscoverDevices();
10 | IObservable DiscoverResources(Device device);
11 | void SetFilters(IEnumerable filters);
12 | void SetRequstUrl(string Url);
13 | void SetTimeout(TimeSpan timeout);
14 | }
15 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer/Services/MyDebugLogger.cs:
--------------------------------------------------------------------------------
1 | #define DEBUG
2 |
3 | using Splat;
4 | using System.ComponentModel;
5 |
6 | namespace CoAPExplorer.Services
7 | {
8 | public class MyDebugLogger : ILogger
9 | {
10 | public LogLevel Level { get; set; }
11 |
12 | public void Write([Localizable(false)] string message, LogLevel logLevel)
13 | {
14 | if ((int)logLevel < (int)Level) return;
15 | System.Diagnostics.Debug.WriteLine(message);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/CoAPExplorer/Utils/CoapPayloadFormatConverter.cs:
--------------------------------------------------------------------------------
1 | using CoAPExplorer.Models;
2 | using CoAPNet.Options;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 |
9 | namespace CoAPExplorer.Utils
10 | {
11 | public class CoapPayloadFormater
12 | {
13 | public static string Format(byte[] payload, ContentFormatType contentFormat)
14 | {
15 | if (payload == null || contentFormat == null)
16 | return string.Empty;
17 |
18 | if(payload.Length == 0)
19 | return string.Empty;
20 |
21 | try
22 | {
23 | if (contentFormat.Value == ContentFormatType.TextPlain.Value)
24 | return Encoding.UTF8.GetString(payload);
25 |
26 | if (contentFormat.Value == ContentFormatType.ApplicationJson.Value)
27 | return JToken.Parse(Encoding.UTF8.GetString(payload)).ToString(Newtonsoft.Json.Formatting.Indented);
28 |
29 | // TODO: Don't simply replace all commas with new line. Need to be context aware to ensure we're not splitting a link format in two.
30 | if (contentFormat.Value == ContentFormatType.ApplicationLinkFormat.Value)
31 | return Encoding.UTF8.GetString(payload).Replace(",", ",\r\n");
32 | }
33 | catch(Exception ex)
34 | {
35 | System.Diagnostics.Debug.WriteLine(ex);
36 | #if DEBUG
37 | return ex.ToString();
38 | #endif
39 | }
40 |
41 | return string.Empty;
42 | }
43 |
44 | public static byte[] RemoveFormat(string payload, ContentFormatType contentFormat)
45 | {
46 | if (string.IsNullOrEmpty(payload) || contentFormat == null)
47 | return null;
48 |
49 | try
50 | {
51 |
52 | if (contentFormat.Value == ContentFormatType.TextPlain.Value)
53 | return Encoding.UTF8.GetBytes(payload);
54 |
55 | if (contentFormat.Value == ContentFormatType.ApplicationJson.Value)
56 | return Encoding.UTF8.GetBytes(JToken.Parse(payload).ToString());
57 |
58 | if (contentFormat.Value == ContentFormatType.ApplicationLinkFormat.Value)
59 | return Encoding.UTF8.GetBytes(payload.Replace("\n,",",").Replace("\r",""));
60 | }
61 | catch (JsonReaderException ex)
62 | {
63 | throw new FormattedTextException(ex.Message, ex.LineNumber, ex.LinePosition, ex);
64 | }
65 |
66 | return null;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/Utils/StringEscape.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 |
9 | namespace CoAPExplorer.Utils
10 | {
11 | public static class StringEscape
12 | {
13 | public static string Escape(string value)
14 | {
15 | Func escapeChar = c =>
16 | {
17 | switch (c)
18 | {
19 | case '\\': return @"\\";
20 | case '\0': return @"\0";
21 | case '\a': return @"\a";
22 | case '\b': return @"\b";
23 | case '\f': return @"\f";
24 | case '\n': return @"\n";
25 | case '\r': return @"\r";
26 | case '\t': return @"\t";
27 | case '\v': return @"\v";
28 | }
29 | if ((int)c <= 255)
30 | return "\\x" + ((int)c).ToString("x2");
31 | return "\\u" + ((int)c).ToString("x4");
32 | };
33 |
34 | StringBuilder sb = new StringBuilder();
35 | for (int i = 0; i < value.Length; i++)
36 | {
37 | var c = value[i];
38 | char[] controlChars = new[] { '\\', '0', 'a', 'b', 'f', 'l', 'n', 'r', 't', 'v', 'x', 'u', 'U' };
39 |
40 | if (char.IsControl(c) && !char.IsWhiteSpace(c))
41 | sb.Append(escapeChar(c));
42 | else if (c == '\\' && controlChars.Contains(value[i + 1]))
43 | sb.Append(@"\\");
44 | else if (char.IsSurrogatePair(value, i))
45 | sb.Append("\\U" + char.ConvertToUtf32(value, i++).ToString("x8"));
46 | else if (c > 127)
47 | sb.Append("\\u" + ((int)c).ToString("x4"));
48 | else
49 | sb.Append(c);
50 | }
51 |
52 | return sb.ToString();
53 | }
54 |
55 | public static string Unescape(string value)
56 | {
57 | return Regex.Replace(value, @"\\(?:(\\|0|a|b|f|n|r|t|v)|U([a-fA-F0-9]{8})|u([a-fA-F0-9]{4})|x([a-fA-F0-9]{1,4}))",
58 | matches => {
59 | string match = matches.Groups[1].Success ? matches.Groups[1].Value // control characters
60 | : matches.Groups[2].Success ? matches.Groups[2].Value // 8-digit unicode sequence
61 | : matches.Groups[3].Success ? matches.Groups[3].Value // 4-digit unicdoe sequence
62 | : matches.Groups[4].Success ? matches.Groups[4].Value // 1 to 4-digit hex sequence
63 | : throw new NotImplementedException($"Regex issue in {nameof(StringEscape)}");
64 |
65 | if (matches.Groups[1].Success)
66 | {
67 | switch (match)
68 | {
69 | case @"\": return "\\";
70 | case @"0": return "\0";
71 | case @"a": return "\a";
72 | case @"b": return "\b";
73 | case @"f": return "\f";
74 | case @"n": return "\n";
75 | case @"r": return "\r";
76 | case @"t": return "\t";
77 | case @"v": return "\v";
78 | default:
79 | throw new NotImplementedException($"Unsupported escape character ({match})");
80 | }
81 | }
82 | return (char.ConvertFromUtf32(int.Parse(match, NumberStyles.HexNumber)));
83 | });
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/ViewModels/DeviceNavigationViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Reactive.Disposables;
6 | using System.Reactive.Linq;
7 | using System.Text;
8 |
9 | using ReactiveUI;
10 | using Splat;
11 |
12 | using CoAPExplorer.Database;
13 | using CoAPExplorer.Models;
14 | using CoAPExplorer.Services;
15 |
16 | namespace CoAPExplorer.ViewModels
17 | {
18 | public class DeviceNavigationViewModel : ReactiveObject, ISupportsActivation
19 | {
20 | private bool _isOpen = true;
21 | private IDiscoveryService _discoveryService;
22 | private readonly CoapExplorerContext _context;
23 | private bool _pendingClearResources = false;
24 | private DeviceViewModel _device;
25 | public DeviceResource _selectedResource;
26 |
27 | public bool IsOpen { get => _isOpen; set => this.RaiseAndSetIfChanged(ref _isOpen, value); }
28 |
29 | public Device Device => _device.Device;
30 |
31 | public IReactiveDerivedList Resources =>
32 | _device.Resources.CreateDerivedCollection(x => x, orderer: (a, b) => Uri.Compare(a.Url, b.Url, UriComponents.Path, UriFormat.Unescaped, StringComparison.InvariantCultureIgnoreCase));
33 |
34 | public DeviceResource SelectedResource { get => _selectedResource; set => this.RaiseAndSetIfChanged(ref _selectedResource, value); }
35 |
36 | public string Name => _device.Name;
37 |
38 | public string Address => _device.Address;
39 |
40 | public ViewModelActivator Activator { get; } = new ViewModelActivator();
41 |
42 | public ReactiveCommand RefreshResourcesCommand;
43 |
44 | public DeviceNavigationViewModel(DeviceViewModel device)
45 | {
46 | _device = device;
47 | _discoveryService = Locator.Current.GetService();
48 | _context = Locator.Current.GetService();
49 |
50 | _device.PropertyChanged += DevicePropertyChanged;
51 |
52 | RefreshResourcesCommand = ReactiveCommand.CreateFromObservable(d =>
53 | {
54 | _pendingClearResources = true;
55 | return _discoveryService.DiscoverResources(d).Do(_ => { }, async () => await _context.SaveChangesAsync());
56 | });
57 |
58 | RefreshResourcesCommand.Subscribe(resource =>
59 | {
60 | if (_pendingClearResources)
61 | _device.Device.KnownResources.Clear();
62 |
63 | _pendingClearResources = false;
64 | _device.Device.KnownResources.Add(resource);
65 | });
66 |
67 | this.WhenActivated(disposables =>
68 | {
69 | RefreshResourcesCommand
70 | .ThrownExceptions
71 | .Subscribe(ex => App.LogException(ex))
72 | .DisposeWith(disposables);
73 | });
74 | }
75 |
76 | private void DevicePropertyChanged(object sender, PropertyChangedEventArgs e)
77 | {
78 | switch (e.PropertyName)
79 | {
80 | case nameof(_device.Name):
81 | this.RaisePropertyChanged(nameof(Name));
82 | break;
83 | case nameof(_device.Address):
84 | this.RaisePropertyChanged(nameof(Address));
85 | break;
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/ViewModels/HomeViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | using ReactiveUI;
5 | using Splat;
6 |
7 | namespace CoAPExplorer.ViewModels
8 | {
9 | public class HomeViewModel : ReactiveObject, IRoutableViewModel
10 | {
11 | private SearchViewModel _search;
12 | private RecentDevicesViewModel _recentDevices;
13 |
14 | public RecentDevicesViewModel RecentDevices
15 | {
16 | get => _recentDevices ?? (_recentDevices = new RecentDevicesViewModel(HostScreen));
17 | set => _recentDevices = value;
18 | }
19 |
20 | public SearchViewModel Search
21 | {
22 | get => _search ?? (_search = new SearchViewModel(HostScreen));
23 | set => _search = value;
24 | }
25 |
26 | public IScreen HostScreen { get; }
27 |
28 | public string UrlPathSegment => "";
29 |
30 | public HomeViewModel(IScreen screen = null)
31 | {
32 | HostScreen = screen ?? Locator.Current.GetService();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/ViewModels/NavigationViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reactive;
4 | using System.Reactive.Linq;
5 | using System.Text;
6 | using CoAPExplorer.Models;
7 | using ReactiveUI;
8 |
9 | namespace CoAPExplorer.ViewModels
10 | {
11 | public class NavigationViewModel : ReactiveObject
12 | {
13 | private bool _isOpen = false;
14 | private NavigationItem _selectedNavigationItem;
15 |
16 | public bool IsOpen { get => _isOpen; set => this.RaiseAndSetIfChanged(ref _isOpen, value); }
17 |
18 | public ReactiveList NavigationItems { get; set; }
19 |
20 | public NavigationItem SelectedNavigationItem
21 | {
22 | get => _selectedNavigationItem;
23 | set
24 | {
25 | //if (_selectedNavigationItem == value)
26 | // return;
27 | _selectedNavigationItem = null;
28 |
29 | if(value?.Command != null)
30 | Observable.Return(Unit.Default).InvokeCommand(value.Command);
31 |
32 | this.RaisePropertyChanged(nameof(SelectedNavigationItem));
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/ViewModels/NewDeviceViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive;
5 | using System.Text;
6 |
7 | using ReactiveUI;
8 | using Splat;
9 |
10 | using CoAPExplorer.Models;
11 | using CoAPExplorer.Services;
12 | using CoAPExplorer.Extensions;
13 | using CoAPExplorer.Database;
14 |
15 | namespace CoAPExplorer.ViewModels
16 | {
17 | public class NewDeviceViewModel : ReactiveObject
18 | {
19 | private string _name;
20 | private string _address;
21 | private readonly CoapExplorerContext _dbContext;
22 | private readonly IScreen _screen;
23 |
24 | public string Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); }
25 |
26 | public string Address { get => _address; set => this.RaiseAndSetIfChanged(ref _address, value); }
27 |
28 | public ReactiveCommand AddDeviceCommand { get; }
29 |
30 | public NewDeviceViewModel(IScreen screen = null)
31 | {
32 | _dbContext = Locator.Current.GetService();
33 |
34 | _screen = screen ?? Locator.Current.GetService();
35 |
36 | AddDeviceCommand = ReactiveCommand.CreateFromTask(async () =>
37 | {
38 | // TODO: User Input Validation
39 | // TODO: Can re add another devie with the same Endpoint address?
40 | var address = CoapEndpointFactory.CreateUriFromAddress(Address);
41 | var device = new Device
42 | {
43 | Name = Name,
44 | Address = address,
45 | Endpoint = CoapEndpointFactory.GetEndpoint(address),
46 | };
47 |
48 | _screen.Router.Navigate.Execute(new DeviceViewModel(device, _screen))
49 | .Subscribe();
50 |
51 | if (_dbContext != null)
52 | {
53 | await _dbContext.Devices.AddAsync(device);
54 | await _dbContext.SaveChangesAsync();
55 | }
56 | });
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/ViewModels/RecentDevicesViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Reactive;
6 | using System.Reactive.Disposables;
7 | using System.Reactive.Linq;
8 | using System.Text;
9 |
10 | using Microsoft.EntityFrameworkCore;
11 | using Splat;
12 | using ReactiveUI;
13 |
14 | using CoAPExplorer.Database;
15 | using CoAPExplorer.Models;
16 | using CoAPExplorer.Services;
17 |
18 | namespace CoAPExplorer.ViewModels
19 | {
20 | public class RecentDevicesViewModel : ReactiveObject, ISupportsActivation
21 | {
22 | private readonly CoapExplorerContext _dbContext;
23 | private readonly IScreen _screen;
24 | private ObservableAsPropertyHelper _isSearchValidUri = ObservableAsPropertyHelper.Default();
25 | private string _searchTerms;
26 | private ObservableCollection _devices;
27 | private IReactiveDerivedList _filteredDevices;
28 |
29 | public ReactiveCommand OpenDeviceCommand { get; }
30 |
31 | public ReactiveCommand RemoveDeviceCommand { get; }
32 |
33 | public ReactiveCommand NavigateToUriCommand { get; }
34 |
35 | public string SearchTerms { get => _searchTerms; set => this.RaiseAndSetIfChanged(ref _searchTerms, value); }
36 |
37 | public bool IsSearchValidUri => _isSearchValidUri.Value;
38 |
39 | public ReactiveCommand AddDeviceCommand { get; }
40 |
41 | public ObservableCollection Devices
42 | {
43 | get => _devices ?? (_devices = new ObservableCollection(_dbContext.Devices.Include(x => x.KnownResources).Select(d => new DeviceViewModel(d, _screen))));
44 | set => _devices = value;
45 | }
46 |
47 | public IReactiveDerivedList FilteredDevices => _filteredDevices ?? (_filteredDevices = Devices.CreateDerivedCollection(x => x, FilterDevicesBySearhTerms, signalReset: this.WhenAnyValue(vm => vm.SearchTerms)));
48 |
49 | public ViewModelActivator Activator { get; } = new ViewModelActivator();
50 |
51 | public RecentDevicesViewModel(IScreen screen = null, CoapExplorerContext context = null)
52 | {
53 | _screen = screen ?? Locator.Current.GetService();
54 | _dbContext = context ?? Locator.Current.GetService();
55 |
56 | AddDeviceCommand = ReactiveCommand.Create(_ =>
57 | {
58 | System.Diagnostics.Debug.WriteLine("Creating new device dialog thingy");
59 | return new NewDeviceViewModel(screen);
60 | });
61 |
62 | RemoveDeviceCommand = ReactiveCommand.CreateFromTask(async device =>
63 | {
64 | _dbContext.Devices.Remove(device);
65 | _devices.Remove(_devices.Single(dvm => dvm.Device.Id == device.Id));
66 | await _dbContext.SaveChangesAsync();
67 | });
68 |
69 | OpenDeviceCommand = ReactiveCommand.Create(device => Observable.Return(Unit.Default).InvokeCommand(device.OpenCommand));
70 |
71 | NavigateToUriCommand = ReactiveCommand.CreateFromObservable(() =>
72 | {
73 | if (!Uri.TryCreate(SearchTerms, UriKind.Absolute, out var uri))
74 | return Observable.Empty();
75 |
76 | var deviceViewModel = new DeviceViewModel(new Device { Address = uri }, screen);
77 |
78 | return screen.Router.Navigate.Execute(deviceViewModel);
79 | }, this.WhenAnyValue(vm => vm.IsSearchValidUri));
80 |
81 | this.WhenActivated(disposables =>
82 | {
83 | _isSearchValidUri = this.WhenAnyValue(vm => vm.SearchTerms)
84 | .Select(x => Uri.TryCreate(x, UriKind.Absolute, out _))
85 | .ToProperty(this, vm => vm.IsSearchValidUri)
86 | .DisposeWith(disposables);
87 | });
88 | }
89 |
90 | private bool FilterDevicesBySearhTerms(DeviceViewModel device)
91 | {
92 | if (IsSearchValidUri)
93 | return false;
94 |
95 | if (string.IsNullOrEmpty(SearchTerms))
96 | return true;
97 |
98 | var search = SearchTerms.ToLowerInvariant();
99 |
100 | if (device.Name.ToLowerInvariant().Contains(search) ||
101 | device.Address.ToLowerInvariant().Contains(search))
102 | return true;
103 |
104 | // TODO: Add more places to search (i.e. hostname, history, resources)
105 |
106 | return false;
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/CoAPExplorer/ViewModels/SearchViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 | using System.Reactive;
6 | using System.Reactive.Linq;
7 | using System.Reactive.Disposables;
8 | using System.Threading.Tasks;
9 |
10 | using CoAPNet;
11 | using ReactiveUI;
12 | using Splat;
13 |
14 | using CoAPExplorer.Models;
15 | using CoAPExplorer.Services;
16 |
17 | namespace CoAPExplorer.ViewModels
18 | {
19 | public class SearchViewModel : ReactiveObject, ISupportsActivation
20 | {
21 | private string _searchUrl = "/.well-known/core";
22 | private ObservableAsPropertyHelper _isSearching;
23 | private readonly IDiscoveryService _discoveryService;
24 | private ReactiveList _devices;
25 |
26 | public IScreen Router { get; }
27 |
28 | public List Filters { get; set; }
29 |
30 | public ReactiveCommand AddFilter { get; }
31 |
32 | public ReactiveCommand RemoveFilter { get; }
33 |
34 | public ReactiveCommand SearchCommand { get; }
35 |
36 | public ReactiveCommand StopCommand { get; }
37 |
38 | public ReactiveCommand OpenDeviceCommand { get; }
39 |
40 | public bool IsSearching => _isSearching.Value;
41 |
42 | public ViewModelActivator Activator { get; } = new ViewModelActivator();
43 |
44 | public ReactiveList Devices
45 | {
46 | get => _devices ?? (_devices = new ReactiveList());
47 | set => _devices = value;
48 | }
49 |
50 | public string SearchUrl
51 | {
52 | get { return _searchUrl; }
53 | set { this.RaiseAndSetIfChanged(ref _searchUrl, value); }
54 | }
55 |
56 | public SearchViewModel(IScreen router = null)
57 | {
58 | Router = router ?? Locator.Current.GetService();
59 |
60 | _discoveryService = Locator.Current.GetService();
61 |
62 | Filters = new List();
63 |
64 | AddFilter = ReactiveCommand.Create(() => Filters.Add(new RequestFilter()));
65 | RemoveFilter = ReactiveCommand.Create(f => Filters.Remove(f));
66 |
67 | SearchCommand = ReactiveCommand
68 | .CreateFromObservable(
69 | () => SearchDevices().TakeUntil(StopCommand));
70 |
71 | StopCommand = ReactiveCommand.Create(
72 | () => { },
73 | SearchCommand.IsExecuting);
74 |
75 | //OpenDeviceCommand = ReactiveCommand.CreateFromObservable(device => device.OpenCommand);
76 | OpenDeviceCommand = ReactiveCommand.Create(device => System.Diagnostics.Debug.WriteLine($"we got a device {device.Name}"));
77 |
78 |
79 | this.WhenActivated(disposables =>
80 | {
81 | _isSearching = SearchCommand.IsExecuting
82 | .Select(x => x)
83 | .ToProperty(this, x => x.IsSearching, false)
84 | .DisposeWith(disposables);
85 |
86 | SearchCommand.Select(d => new DeviceViewModel(d, router)).Subscribe(device => Devices.Add(device))
87 | .DisposeWith(disposables);
88 |
89 | // Catch and handle all exceptions produced by observables.
90 | Observable.Merge(StopCommand.ThrownExceptions, SearchCommand.ThrownExceptions,
91 | AddFilter.ThrownExceptions, RemoveFilter.ThrownExceptions)
92 | .Subscribe(ex => App.LogException(ex))
93 | .DisposeWith(disposables);
94 | });
95 | }
96 |
97 | public IObservable SearchDevices()
98 | {
99 | _discoveryService.SetRequstUrl(SearchUrl);
100 | _discoveryService.SetFilters(Filters);
101 | return _discoveryService.DiscoverDevices();
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------