├── .editorconfig
├── .gitignore
├── Common.props
├── LICENSE.md
├── NuGet.config
├── README.md
├── README.zh_cn.md
├── Sandwych.SmartConfig.Demo.sln
├── Sandwych.SmartConfig.sln
├── appveyor.yml
├── assets
├── paypal_button.svg
└── wechat_qrcode.png
├── demo
├── Sandwych.SmartConfig.AndroidDemoApp
│ ├── Assets
│ │ └── AboutAssets.txt
│ ├── DeviceListViewAdapter.cs
│ ├── DeviceListViewHolder.cs
│ ├── MainActivity.cs
│ ├── Properties
│ │ ├── AndroidManifest.xml
│ │ └── AssemblyInfo.cs
│ ├── Resources
│ │ ├── AboutResources.txt
│ │ ├── Resource.designer.cs
│ │ ├── layout
│ │ │ ├── activity_main.xml
│ │ │ ├── content_main.xml
│ │ │ └── device_list_row.xml
│ │ ├── menu
│ │ │ └── menu_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ └── Sandwych.SmartConfig.AndroidDemoApp.csproj
└── Sandwych.SmartConfig.CliDemoApp
│ ├── Program.cs
│ └── Sandwych.SmartConfig.CliDemoApp.csproj
├── src
└── Sandwych.SmartConfig
│ ├── AbstractSmartConfigProvider.cs
│ ├── Airkiss
│ ├── AirkissOptionNames.cs
│ ├── AirkissSmartConfigProvider.cs
│ ├── AirkissWellknownConstants.cs
│ └── Protocol
│ │ ├── AirkissDevicePacketInterpreter.cs
│ │ ├── AirkissMagicCodeFrameEncoder.cs
│ │ ├── AirkissPrefixCodeFrameEncoder.cs
│ │ ├── AirkissProcedureEncoder.cs
│ │ └── AirkissSeqEntryFrameEncoder.cs
│ ├── Constants.cs
│ ├── DeviceDiscoveredEventArgs.cs
│ ├── Directory.Build.props
│ ├── Esptouch
│ ├── EspOptionNames.cs
│ ├── EspSmartConfigProvider.cs
│ ├── EspWellKnownConstants.cs
│ └── Protocol
│ │ ├── EspDatumFrameEncoder.cs
│ │ ├── EspDevicePacketInterpreter.cs
│ │ └── EspProcedureEncoder.cs
│ ├── ISmartConfigContextFactory.cs
│ ├── ISmartConfigDevice.cs
│ ├── ISmartConfigJob.cs
│ ├── ISmartConfigProvider.cs
│ ├── Networking
│ ├── DatagramBroadcaster.cs
│ ├── DatagramReceiveResult.cs
│ ├── DatagramReceiver.cs
│ ├── DefaultDatagramClient.cs
│ ├── IDatagramBroadcaster.cs
│ ├── IDatagramClient.cs
│ └── IDatagramReceiver.cs
│ ├── Protocol
│ ├── FrameDataConverter.cs
│ ├── IDevicePacketInterpreter.cs
│ ├── IProcedureEncoder.cs
│ └── Segment.cs
│ ├── Sandwych.SmartConfig.csproj
│ ├── SmartConfigArguments.cs
│ ├── SmartConfigContext.cs
│ ├── SmartConfigDevice.cs
│ ├── SmartConfigExtensions.cs
│ ├── SmartConfigJob.cs
│ ├── SmartConfigStarter.cs
│ ├── SmartConfigTimerEventArgs.cs
│ ├── SmartConfigTimerEventHandler.cs
│ ├── StandardOptionNames.cs
│ └── Util
│ ├── AsyncExtensions.cs
│ ├── BytesHelper.cs
│ ├── Crc.cs
│ ├── LinqExtensions.cs
│ └── TaskExtensions.cs
└── test
└── Sandwych.SmartConfig.Test
├── Airkiss
└── Protocol
│ ├── AirkissDevicePacketInterpreterTests.cs
│ ├── AirkissMagicCodeFrameEncoderTests.cs
│ ├── AirkissPrefixCodeFrameEncoderTests.cs
│ ├── AirkissProcedureEncoderTests.cs
│ └── AirkissSeqEntryFrameEncoderTests.cs
├── Esptouch
└── Protocol
│ ├── EspDatumFrameEncoderTests.cs
│ └── EspProcedureEncoderTests.cs
├── Sandwych.SmartConfig.Test.csproj
└── Util
├── BytesHelperTests.cs
├── Crc8Tests.cs
└── LinqExtensions.cs
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Don't use tabs for indentation.
7 | [*]
8 | indent_style = space
9 | # (Please don't specify an indent_size here; that has too many unintended consequences.)
10 |
11 | # Code files
12 | [*.{cs,csx,vb,vbx}]
13 | indent_size = 4
14 | insert_final_newline = true
15 | charset = utf-8-bom
16 |
17 | # XML project files
18 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
19 | indent_size = 2
20 |
21 | # XML config files
22 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
23 | indent_size = 2
24 |
25 | # JSON files
26 | [*.json]
27 | indent_size = 2
28 |
29 | # Powershell files
30 | [*.ps1]
31 | indent_size = 2
32 |
33 | # Shell script files
34 | [*.sh]
35 | end_of_line = lf
36 | indent_size = 2
37 |
38 | # Dotnet code style settings:
39 | [*.{cs,vb}]
40 |
41 | # Sort using and Import directives with System.* appearing first
42 | dotnet_sort_system_directives_first = true
43 | dotnet_separate_import_directive_groups = false
44 | # Avoid "this." and "Me." if not necessary
45 | dotnet_style_qualification_for_field = false:refactoring
46 | dotnet_style_qualification_for_property = false:refactoring
47 | dotnet_style_qualification_for_method = false:refactoring
48 | dotnet_style_qualification_for_event = false:refactoring
49 |
50 | # Use language keywords instead of framework type names for type references
51 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
52 | dotnet_style_predefined_type_for_member_access = true:suggestion
53 |
54 | # Suggest more modern language features when available
55 | dotnet_style_object_initializer = true:suggestion
56 | dotnet_style_collection_initializer = true:suggestion
57 | dotnet_style_coalesce_expression = true:suggestion
58 | dotnet_style_null_propagation = true:suggestion
59 | dotnet_style_explicit_tuple_names = true:suggestion
60 |
61 | # Whitespace options
62 | dotnet_style_allow_multiple_blank_lines_experimental = false
63 |
64 | # Non-private static fields are PascalCase
65 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
66 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
67 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
68 |
69 | dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
70 | dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
71 | dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
72 |
73 | dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
74 |
75 | # Non-private readonly fields are PascalCase
76 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
77 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
78 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
79 |
80 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
81 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
82 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
83 |
84 | dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
85 |
86 | # Constants are PascalCase
87 | dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
88 | dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
89 | dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
90 |
91 | dotnet_naming_symbols.constants.applicable_kinds = field, local
92 | dotnet_naming_symbols.constants.required_modifiers = const
93 |
94 | dotnet_naming_style.constant_style.capitalization = pascal_case
95 |
96 | # Static fields are camelCase and start with s_
97 | dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
98 | dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
99 | dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
100 |
101 | dotnet_naming_symbols.static_fields.applicable_kinds = field
102 | dotnet_naming_symbols.static_fields.required_modifiers = static
103 |
104 | dotnet_naming_style.static_field_style.capitalization = camel_case
105 | dotnet_naming_style.static_field_style.required_prefix = s_
106 |
107 | # Instance fields are camelCase and start with _
108 | dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
109 | dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
110 | dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
111 |
112 | dotnet_naming_symbols.instance_fields.applicable_kinds = field
113 |
114 | dotnet_naming_style.instance_field_style.capitalization = camel_case
115 | dotnet_naming_style.instance_field_style.required_prefix = _
116 |
117 | # Locals and parameters are camelCase
118 | dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
119 | dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
120 | dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
121 |
122 | dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
123 |
124 | dotnet_naming_style.camel_case_style.capitalization = camel_case
125 |
126 | # Local functions are PascalCase
127 | dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
128 | dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
129 | dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
130 |
131 | dotnet_naming_symbols.local_functions.applicable_kinds = local_function
132 |
133 | dotnet_naming_style.local_function_style.capitalization = pascal_case
134 |
135 | # By default, name items with PascalCase
136 | dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
137 | dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
138 | dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
139 |
140 | dotnet_naming_symbols.all_members.applicable_kinds = *
141 |
142 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
143 |
144 | file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.\nSee the LICENSE file in the project root for more information.
145 |
146 | # RS0016: Only enable if API files are present
147 | dotnet_public_api_analyzer.require_api_files = true
148 |
149 | # CSharp code style settings:
150 | [*.cs]
151 | # Newline settings
152 | csharp_new_line_before_open_brace = all
153 | csharp_new_line_before_else = true
154 | csharp_new_line_before_catch = true
155 | csharp_new_line_before_finally = true
156 | csharp_new_line_before_members_in_object_initializers = true
157 | csharp_new_line_before_members_in_anonymous_types = true
158 | csharp_new_line_between_query_expression_clauses = true
159 |
160 | # Indentation preferences
161 | csharp_indent_block_contents = true
162 | csharp_indent_braces = false
163 | csharp_indent_case_contents = true
164 | csharp_indent_case_contents_when_block = true
165 | csharp_indent_switch_labels = true
166 | csharp_indent_labels = flush_left
167 |
168 | # Whitespace options
169 | csharp_style_allow_embedded_statements_on_same_line_experimental = false
170 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
171 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
172 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false
173 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false
174 |
175 | # Prefer "var" everywhere
176 | csharp_style_var_for_built_in_types = true:suggestion
177 | csharp_style_var_when_type_is_apparent = true:suggestion
178 | csharp_style_var_elsewhere = true:suggestion
179 |
180 | # Prefer method-like constructs to have a block body
181 | csharp_style_expression_bodied_methods = false:none
182 | csharp_style_expression_bodied_constructors = false:none
183 | csharp_style_expression_bodied_operators = false:none
184 |
185 | # Prefer property-like constructs to have an expression-body
186 | csharp_style_expression_bodied_properties = true:none
187 | csharp_style_expression_bodied_indexers = true:none
188 | csharp_style_expression_bodied_accessors = true:none
189 |
190 | # Suggest more modern language features when available
191 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
192 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
193 | csharp_style_inlined_variable_declaration = true:suggestion
194 | csharp_style_throw_expression = true:suggestion
195 | csharp_style_conditional_delegate_call = true:suggestion
196 | csharp_style_prefer_extended_property_pattern = true:suggestion
197 |
198 | # Space preferences
199 | csharp_space_after_cast = false
200 | csharp_space_after_colon_in_inheritance_clause = true
201 | csharp_space_after_comma = true
202 | csharp_space_after_dot = false
203 | csharp_space_after_keywords_in_control_flow_statements = true
204 | csharp_space_after_semicolon_in_for_statement = true
205 | csharp_space_around_binary_operators = before_and_after
206 | csharp_space_around_declaration_statements = do_not_ignore
207 | csharp_space_before_colon_in_inheritance_clause = true
208 | csharp_space_before_comma = false
209 | csharp_space_before_dot = false
210 | csharp_space_before_open_square_brackets = false
211 | csharp_space_before_semicolon_in_for_statement = false
212 | csharp_space_between_empty_square_brackets = false
213 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
214 | csharp_space_between_method_call_name_and_opening_parenthesis = false
215 | csharp_space_between_method_call_parameter_list_parentheses = false
216 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
217 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
218 | csharp_space_between_method_declaration_parameter_list_parentheses = false
219 | csharp_space_between_parentheses = false
220 | csharp_space_between_square_brackets = false
221 |
222 | # Blocks are allowed
223 | csharp_prefer_braces = true:silent
224 | csharp_preserve_single_line_blocks = true
225 | csharp_preserve_single_line_statements = true
226 |
227 | # IDE0060: Remove unused parameter
228 | dotnet_diagnostic.IDE0060.severity = warning
229 |
230 | [src/{Compilers,ExpressionEvaluator,Scripting}/**Test**/*.{cs,vb}]
231 |
232 | # IDE0060: Remove unused parameter
233 | dotnet_diagnostic.IDE0060.severity = none
234 |
235 | [src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}]
236 |
237 | # IDE0011: Add braces
238 | csharp_prefer_braces = when_multiline:warning
239 | # NOTE: We need the below severity entry for Add Braces due to https://github.com/dotnet/roslyn/issues/44201
240 | dotnet_diagnostic.IDE0011.severity = warning
241 |
242 | # IDE0040: Add accessibility modifiers
243 | dotnet_diagnostic.IDE0040.severity = warning
244 |
245 | # IDE0052: Remove unread private member
246 | dotnet_diagnostic.IDE0052.severity = warning
247 |
248 | # IDE0059: Unnecessary assignment to a value
249 | dotnet_diagnostic.IDE0059.severity = warning
250 |
251 | # CA1012: Abstract types should not have public constructors
252 | dotnet_diagnostic.CA1012.severity = warning
253 |
254 | # CA1822: Make member static
255 | dotnet_diagnostic.CA1822.severity = warning
256 |
257 | # Prefer "var" everywhere
258 | dotnet_diagnostic.IDE0007.severity = warning
259 | csharp_style_var_for_built_in_types = true:warning
260 | csharp_style_var_when_type_is_apparent = true:warning
261 | csharp_style_var_elsewhere = true:warning
262 |
263 | # csharp_style_allow_embedded_statements_on_same_line_experimental
264 | dotnet_diagnostic.IDE2001.severity = warning
265 |
266 | # csharp_style_allow_blank_lines_between_consecutive_braces_experimental
267 | dotnet_diagnostic.IDE2002.severity = warning
268 |
269 | # csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental
270 | dotnet_diagnostic.IDE2004.severity = warning
271 |
272 | # csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental
273 | dotnet_diagnostic.IDE2005.severity = warning
274 |
275 | # csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental
276 | dotnet_diagnostic.IDE2006.severity = warning
277 |
278 | [src/{VisualStudio}/**/*.{cs,vb}]
279 | # CA1822: Make member static
280 | # There is a risk of accidentally breaking an internal API that partners rely on though IVT.
281 | dotnet_code_quality.CA1822.api_surface = private
282 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Visual Studio (>=2015) project-specific, machine local files
5 | .vs/
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.sln.docstates
11 | *.userprefs
12 |
13 | # ignore Xamarin.Android Resource.Designer.cs files
14 | **/*.Droid/**/[Rr]esource.[Dd]esigner.cs
15 | **/*.Android/**/[Rr]esource.[Dd]esigner.cs
16 | **/Android/**/[Rr]esource.[Dd]esigner.cs
17 | **/Droid/**/[Rr]esource.[Dd]esigner.cs
18 |
19 | # Xamarin Components
20 | Components/
21 |
22 | # Build results
23 | [Dd]ebug/
24 | [Dd]ebugPublic/
25 | [Rr]elease/
26 | x64/
27 | build/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | #NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | *_i.c
46 | *_p.c
47 | *_i.h
48 | *.ilk
49 | *.meta
50 | *.obj
51 | *.pch
52 | *.pdb
53 | *.pgc
54 | *.pgd
55 | *.rsp
56 | *.sbr
57 | *.tlb
58 | *.tli
59 | *.tlh
60 | *.tmp
61 | *.tmp_proj
62 | *.log
63 | *.vspscc
64 | *.vssscc
65 | .builds
66 | *.pidb
67 | *.svclog
68 | *.scc
69 |
70 | # Chutzpah Test files
71 | _Chutzpah*
72 |
73 | # Visual C++ cache files
74 | ipch/
75 | *.aps
76 | *.ncb
77 | *.opensdf
78 | *.sdf
79 | *.cachefile
80 |
81 | # Visual Studio profiler
82 | *.psess
83 | *.vsp
84 | *.vspx
85 |
86 | # TFS 2012 Local Workspace
87 | $tf/
88 |
89 | # Guidance Automation Toolkit
90 | *.gpState
91 |
92 | # ReSharper is a .NET coding add-in
93 | _ReSharper*/
94 | *.[Rr]e[Ss]harper
95 | *.DotSettings.user
96 |
97 | # JustCode is a .NET coding addin-in
98 | .JustCode
99 |
100 | # TeamCity is a build add-in
101 | _TeamCity*
102 |
103 | # DotCover is a Code Coverage Tool
104 | *.dotCover
105 |
106 | # NCrunch
107 | *.ncrunch*
108 | _NCrunch_*
109 | .*crunch*.local.xml
110 |
111 | # MightyMoose
112 | *.mm.*
113 | AutoTest.Net/
114 |
115 | # Web workbench (sass)
116 | .sass-cache/
117 |
118 | # Installshield output folder
119 | [Ee]xpress/
120 |
121 | # DocProject is a documentation generator add-in
122 | DocProject/buildhelp/
123 | DocProject/Help/*.HxT
124 | DocProject/Help/*.HxC
125 | DocProject/Help/*.hhc
126 | DocProject/Help/*.hhk
127 | DocProject/Help/*.hhp
128 | DocProject/Help/Html2
129 | DocProject/Help/html
130 |
131 | # Click-Once directory
132 | publish/
133 |
134 | # Publish Web Output
135 | *.[Pp]ublish.xml
136 | *.azurePubxml
137 |
138 | # NuGet Packages Directory
139 | packages/
140 | *.nuget.targets
141 | *.lock.json
142 | *.nuget.props
143 |
144 | ## TODO: If the tool you use requires repositories.config uncomment the next line
145 | #!packages/repositories.config
146 |
147 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
148 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented)
149 | !packages/build/
150 |
151 | # Windows Azure Build Output
152 | csx/
153 | *.build.csdef
154 |
155 | # Windows Store app package directory
156 | AppPackages/
157 |
158 | # Others
159 | sql/
160 | *.Cache
161 | ClientBin/
162 | [Ss]tyle[Cc]op.*
163 | ~$*
164 | *~
165 | *.dbmdl
166 | *.dbproj.schemaview
167 | *.pfx
168 | *.publishsettings
169 | node_modules/
170 | .DS_Store
171 | *.bak
172 |
173 | # RIA/Silverlight projects
174 | Generated_Code/
175 |
176 | # Backup & report files from converting an old project file to a newer
177 | # Visual Studio version. Backup files are not needed, because we have git ;-)
178 | _UpgradeReport_Files/
179 | Backup*/
180 | UpgradeLog*.XML
181 | UpgradeLog*.htm
182 |
183 | # SQL Server files
184 | *.mdf
185 | *.ldf
186 |
187 | # Business Intelligence projects
188 | *.rdl.data
189 | *.bim.layout
190 | *.bim_*.settings
191 |
192 | # Microsoft Fakes
193 | FakesAssemblies/
194 |
195 | .vscode/settings.json
196 |
197 |
--------------------------------------------------------------------------------
/Common.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Li 'oldrev' Wei
5 | Li Wei
6 | true
7 | portable
8 | false
9 | false
10 | false
11 | false
12 | false
13 | false
14 | false
15 | true
16 | 0
17 | 1.1.1.$(BuildNumber)
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2014 Sandwych.SmartConfig contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://ci.appveyor.com/project/oldrev/sandwych-smartconfig/branch/master)
2 | [](https://www.nuget.org/packages/Sandwych.SmartConfig)
3 |
4 | # Sandwych.SmartConfig
5 |
6 | Sandwych.SmartConfig is a pure C# implementation of various WiFi SmartConfig protocols that build from scratch.
7 |
8 | TD;LR: If you working on a Xamarin mobile app to deal with WiFi-capability IoT devices, you may need this library.
9 |
10 | English | [简体中文](README.zh_cn.md)
11 |
12 | ## Getting Started
13 |
14 | ## Features
15 |
16 | * A .NET Standard class library, works on both Xamarin and desktop.
17 | * No third-party library referenced.
18 | * Supported protocols: WeChat's AirKiss and Espressif's ESPTouch.
19 | * Clean architecture, easy to learn and add your own protocol.
20 | * IoC container friendly.
21 |
22 | ## Getting Started
23 |
24 | ### Prerequisites
25 |
26 | * Microsoft Visual Studio 2019
27 | * DocFX for API documents generation (Optional)
28 |
29 | ### Supported Platforms
30 |
31 | * .NET Standard 2.0+
32 |
33 | ### Installation
34 |
35 | Install Sandwych.SmartConfig to your project by [NuGet](https://www.nuget.org/packages/Sandwych.SmartConfig) then you're good to go.
36 |
37 | ## Examples
38 |
39 | ### Usage
40 |
41 | ```csharp
42 |
43 | var provider = new EspSmartConfigProvider();
44 | var ctx = provider.CreateContext();
45 |
46 | ctx.DeviceDiscoveredEvent += (s, e) => {
47 | Console.WriteLine("Found device: IP={0} MAC={1}", e.Device.IPAddress, e.Device.MacAddress);
48 | };
49 |
50 | var scArgs = new SmartConfigArguments()
51 | {
52 | Ssid = "YourWiFiSSID",
53 | Bssid = PhysicalAddress.Parse("10-10-10-10-10-10"),
54 | Password = "YourWiFiPassword",
55 |
56 | // Your local IP address of WiFi network. It's important for using multiple network interfaces
57 | // See CliDemoApp for details.
58 | LocalAddress = IPAddress.Parse("192.168.1.10")
59 | };
60 |
61 | // Do the SmartConfig job
62 | using (var job = new SmartConfigJob(TimeSpan.FromSeconds(100))) // Set the timeout to 100 seconds
63 | {
64 | await job.ExecuteAsync(ctx, scArgs);
65 | }
66 |
67 | ```
68 |
69 | Or much simpler if you perfer the callback style:
70 |
71 | ```csharp
72 |
73 | await SmartConfigStarter.StartAsync(args,
74 | onDeviceDiscovered: (s, e) => Console.WriteLine("Found device: IP={0} MAC={1}", e.Device.IPAddress, e.Device.MacAddress));
75 |
76 | ```
77 |
78 | ### The Demo Android App
79 |
80 | APK Download: WIP
81 |
82 | ## Donation
83 |
84 | If this project is useful to you, you can buy me a beer:
85 |
86 | [](https://www.paypal.me/oldrev)
87 |
88 | ## Contributiors
89 |
90 | * **Li "oldrev" Wei** - *Init work and the main maintainer* - [oldrev](https://github.com/oldrev)
91 |
92 | ## License
93 |
94 | Licensed under the MIT License. Copyright © Sandwych.SmartConfig Contributors.
95 |
96 | See [LICENSE.md](LICENSE.md) for details.
97 |
98 | ## Credits
99 |
100 | * Espressif EsptouchForAndroid: https://github.com/EspressifApp/EsptouchForAndroid
101 |
--------------------------------------------------------------------------------
/README.zh_cn.md:
--------------------------------------------------------------------------------
1 | [](https://ci.appveyor.com/project/oldrev/sandwych-smartconfig)
2 | [](https://www.nuget.org/packages/Sandwych.SmartConfig)
3 |
4 | # Sandwych.SmartConfig
5 |
6 | 一个从零开始实现的微信 AirKiss 和乐鑫 ESPTouch 的 WiFi SmartConfig 配网功能库。
7 |
8 | [English](README.md) | 简体中文
9 |
10 |
11 | ## 特性
12 |
13 | * 使用纯 C# 和 .NET BCL 的 `UDPClient` 类实现,不依赖设备厂家或其他的第三方包,可同时用于 Xamarin 手机 App 或桌面;
14 | * 目前支持的协议:AirKiss、ESPTouch;
15 | * 配网速度快,兼容性好;
16 | * 设计清晰可扩展,可自行增加其他协议,也可参考本项目编写其他语言的 AirKiss 和 ESPTouch 实现;
17 | * IoC 容器友好;
18 | * 纯异步设计
19 |
20 | 一般来说,如果你想用 .NET 的 Xamarin 开发涉及 WiFi 物联网设备的手机 App,那 Sandwych.SmartConfig 是你必须会用到的。
21 |
22 | ## 快速上手
23 |
24 |
25 | ### 前置需求
26 |
27 | * Microsoft Visual Studio 2019(免费社区版即可)
28 | * DocFX 用于生成 API 文档(可选)
29 |
30 | ### 支持平台
31 |
32 | * .NET Standard 2.0+
33 |
34 | ### 安装
35 |
36 | 把 Sandwych.SmartConfig 通过 **[NuGet](https://www.nuget.org/packages/Sandwych.SmartConfig)** 安装到你的项目中即可使用。
37 |
38 | ## 例子
39 |
40 | ### 简单调用本库进行配网
41 |
42 | 以 ESPTouch 协议为例:
43 |
44 | ```csharp
45 |
46 | var provider = new EspSmartConfigProvider();
47 | var ctx = provider.CreateContext();
48 |
49 | // 设备通过 UDP 广播回报 IP 以后引发此事件,注意同一个设备 IP 只会触发一次
50 | ctx.DeviceDiscoveredEvent += (s, e) => {
51 | // 这里如果涉及更新 GUI 的话需要同步到主线程
52 | Console.WriteLine("Found device: IP={0} MAC={1}", e.Device.IPAddress, e.Device.MacAddress);
53 | };
54 |
55 | // 设置配网参数
56 | var scArgs = new SmartConfigArguments()
57 | {
58 | Ssid = "YourWiFiSSID", // 路由器 SSID
59 | Bssid = PhysicalAddress.Parse("10-10-10-10-10-10"), // WiFi 路由器 BSSID,ESPTouch 协议需要,Airkiss 协议不需要
60 | Password = "YourWiFiPassword", // WiFi 路由器密码
61 |
62 | // 手机/PC 的本机 IP,如果有多个网卡,这里必须是 WiFi 网络的本机 IP
63 | // 获取本机 WiFi IP 可参见 CliDemoApp 演示程序
64 | LocalAddress = IPAddress.Parse("192.168.1.10")
65 | };
66 |
67 | // 调用 SmartConfigJob 进行实际的配网
68 | using (var job = new SmartConfigJob(TimeSpan.FromSeconds(100))) // 设置最长配网时间 100秒
69 | {
70 | await job.ExecuteAsync(ctx, scArgs);
71 | }
72 |
73 | ```
74 |
75 | 使用回调风格更加简单:
76 |
77 | ```csharp
78 |
79 | await SmartConfigStarter.StartAsync(args,
80 | onDeviceDiscovered: (s, e) => Console.WriteLine("Found device: IP={0} MAC={1}", e.Device.IPAddress, e.Device.MacAddress));
81 |
82 | ```
83 |
84 | ### 使用例子程序
85 |
86 | 本项目包括了一个通用的 Xamarin.Android 配网程序作为例子,可自行编译运行,也可直接下载编译好的 `.APK` 安装测试:
87 |
88 | APK 下载地址: TODO
89 |
90 | ## 支持本项目
91 |
92 | 假如本项目对你有用,可以考虑请我喝杯啤酒:
93 |
94 | 微信打赏二维码:
95 |
96 | 
97 |
98 |
99 | * BTW 如果你需要在本项目贡献者处留名,打赏时请备注。
100 |
101 | 当然,也非常欢迎你测试提交 bug、贡献代码、帮其他用户解决问题、编写文档,这些都是金钱不能衡量的。
102 |
103 | ## 贡献者
104 |
105 | * **李维** - *初始开发及现维护者* - [oldrev](https://github.com/oldrev)
106 |
107 | ## 授权协议
108 |
109 | 版权所有 © Sandwych.SmartConfig 贡献者。
110 |
111 | 本项目使用 MIT 协议授权,详情见: [LICENSE.md](LICENSE.md)。
112 |
113 | ## 致谢
114 |
115 | * 乐鑫 EsptouchForAndroid: https://github.com/EspressifApp/EsptouchForAndroid
116 |
--------------------------------------------------------------------------------
/Sandwych.SmartConfig.Demo.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29926.136
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandwych.SmartConfig", "src\Sandwych.SmartConfig\Sandwych.SmartConfig.csproj", "{147CCAF3-F2DC-4D48-ACC5-7082D7DE7BF6}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandwych.SmartConfig.AndroidDemoApp", "demo\Sandwych.SmartConfig.AndroidDemoApp\Sandwych.SmartConfig.AndroidDemoApp.csproj", "{4B7AA7C1-C490-4E29-A8B5-AF6E42F55F99}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandwych.SmartConfig.CliDemoApp", "demo\Sandwych.SmartConfig.CliDemoApp\Sandwych.SmartConfig.CliDemoApp.csproj", "{5CBEA915-C15F-4074-8850-ACB0B9EB94F8}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {147CCAF3-F2DC-4D48-ACC5-7082D7DE7BF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {147CCAF3-F2DC-4D48-ACC5-7082D7DE7BF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {147CCAF3-F2DC-4D48-ACC5-7082D7DE7BF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {147CCAF3-F2DC-4D48-ACC5-7082D7DE7BF6}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {4B7AA7C1-C490-4E29-A8B5-AF6E42F55F99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {4B7AA7C1-C490-4E29-A8B5-AF6E42F55F99}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {4B7AA7C1-C490-4E29-A8B5-AF6E42F55F99}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
25 | {4B7AA7C1-C490-4E29-A8B5-AF6E42F55F99}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {4B7AA7C1-C490-4E29-A8B5-AF6E42F55F99}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {4B7AA7C1-C490-4E29-A8B5-AF6E42F55F99}.Release|Any CPU.Deploy.0 = Release|Any CPU
28 | {5CBEA915-C15F-4074-8850-ACB0B9EB94F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {5CBEA915-C15F-4074-8850-ACB0B9EB94F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {5CBEA915-C15F-4074-8850-ACB0B9EB94F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {5CBEA915-C15F-4074-8850-ACB0B9EB94F8}.Release|Any CPU.Build.0 = Release|Any CPU
32 | EndGlobalSection
33 | GlobalSection(SolutionProperties) = preSolution
34 | HideSolutionNode = FALSE
35 | EndGlobalSection
36 | GlobalSection(ExtensibilityGlobals) = postSolution
37 | SolutionGuid = {2DE497AB-6AEF-4839-B02E-F8ADECFE4027}
38 | EndGlobalSection
39 | EndGlobal
40 |
--------------------------------------------------------------------------------
/Sandwych.SmartConfig.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.4.33213.308
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandwych.SmartConfig", "src\Sandwych.SmartConfig\Sandwych.SmartConfig.csproj", "{E851B517-D7A0-417E-BCB9-492081B26E6F}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandwych.SmartConfig.Test", "test\Sandwych.SmartConfig.Test\Sandwych.SmartConfig.Test.csproj", "{3022E137-2800-4BC5-962A-190E7DB47E3A}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3F6D0B71-67C9-4BA3-8C15-63DC63D36612}"
11 | ProjectSection(SolutionItems) = preProject
12 | .editorconfig = .editorconfig
13 | Common.props = Common.props
14 | LICENSE.md = LICENSE.md
15 | README.md = README.md
16 | README.zh_cn.md = README.zh_cn.md
17 | EndProjectSection
18 | EndProject
19 | Global
20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
21 | Debug|Any CPU = Debug|Any CPU
22 | Release|Any CPU = Release|Any CPU
23 | EndGlobalSection
24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
25 | {E851B517-D7A0-417E-BCB9-492081B26E6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {E851B517-D7A0-417E-BCB9-492081B26E6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {E851B517-D7A0-417E-BCB9-492081B26E6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {E851B517-D7A0-417E-BCB9-492081B26E6F}.Release|Any CPU.Build.0 = Release|Any CPU
29 | {3022E137-2800-4BC5-962A-190E7DB47E3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {3022E137-2800-4BC5-962A-190E7DB47E3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {3022E137-2800-4BC5-962A-190E7DB47E3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {3022E137-2800-4BC5-962A-190E7DB47E3A}.Release|Any CPU.Build.0 = Release|Any CPU
33 | EndGlobalSection
34 | GlobalSection(SolutionProperties) = preSolution
35 | HideSolutionNode = FALSE
36 | EndGlobalSection
37 | GlobalSection(ExtensibilityGlobals) = postSolution
38 | SolutionGuid = {C19C547C-046A-4F18-B697-8B4E43F6438B}
39 | EndGlobalSection
40 | EndGlobal
41 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Visual Studio 2019
2 |
3 | # Do not build feature branch with open Pull Requests
4 | skip_branch_with_pr: true
5 | branches:
6 | only:
7 | - master
8 | - dev
9 | - release
10 |
11 | init:
12 | - git config --global core.autocrlf true
13 | install:
14 | - ps: $env:BuildNumber= $env:APPVEYOR_BUILD_NUMBER
15 | - ps: $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = true
16 | - ps: $env:NUGET_XMLDOC_MODE = "skip"
17 | - ps: $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
18 | - ps: $IsReleaseBranch = ($env:APPVEYOR_REPO_BRANCH -eq "master" -Or $env:APPVEYOR_REPO_BRANCH -eq "release")
19 | build_script:
20 | # Ensure we are not using the myget feed for dependencies
21 | - dotnet --version
22 | - dotnet pack Sandwych.SmartConfig.sln -c Release
23 | test_script:
24 | - dotnet test .\test\Sandwych.SmartConfig.Test\Sandwych.SmartConfig.Test.csproj -c Release
25 | artifacts:
26 | - path: 'src\**\*.nupkg'
27 | deploy:
28 | - provider: NuGet
29 | on:
30 | branch: release
31 | server: https://www.nuget.org/api/v2/package
32 | api_key:
33 | secure: 76dWrCJVCm1+cBO+WmjmZIvpAw7sf416N/UJWHMbshmC7BOBabuC4Mm6PP2MWdGk
34 | skip_symbols: true
35 | artifact: /.*\.nupkg/
36 | - provider: NuGet
37 | on:
38 | branch: master
39 | server: https://www.nuget.org/api/v2/package
40 | api_key:
41 | secure: 76dWrCJVCm1+cBO+WmjmZIvpAw7sf416N/UJWHMbshmC7BOBabuC4Mm6PP2MWdGk
42 | skip_symbols: true
43 | artifact: /.*\.nupkg/
44 |
--------------------------------------------------------------------------------
/assets/paypal_button.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/wechat_qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/assets/wechat_qrcode.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Assets/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories) and given a Build Action of "AndroidAsset".
3 |
4 | These files will be deployed with your package and will be accessible using Android's
5 | AssetManager, like this:
6 |
7 | public class ReadAsset : Activity
8 | {
9 | protected override void OnCreate (Bundle bundle)
10 | {
11 | base.OnCreate (bundle);
12 |
13 | InputStream input = Assets.Open ("my_asset.txt");
14 | }
15 | }
16 |
17 | Additionally, some Android functions will automatically load asset files:
18 |
19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/DeviceListViewAdapter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | using Android.App;
7 | using Android.Content;
8 | using Android.OS;
9 | using Android.Runtime;
10 | using Android.Views;
11 | using Android.Widget;
12 |
13 | namespace Sandwych.SmartConfig.AndroidDemoApp
14 | {
15 | public class DeviceListViewAdapter : BaseAdapter
16 | {
17 | private readonly IList _devices;
18 |
19 | public DeviceListViewAdapter(IList devices)
20 | {
21 | _devices = devices;
22 | }
23 |
24 | public override ISmartConfigDevice this[int position] => _devices[position];
25 |
26 | public override int Count => throw new NotImplementedException();
27 |
28 | public override long GetItemId(int position) => position;
29 |
30 | public override View GetView(int position, View convertView, ViewGroup parent)
31 | {
32 | var view = convertView;
33 |
34 | if (view == null)
35 | {
36 | view = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.device_list_row, parent, false);
37 |
38 | /*
39 | var image = view.FindViewById(Resource.Id.photoImageView);
40 | var name = view.FindViewById(Resource.Id.ipad);
41 | var department = view.FindViewById(Resource.Id.departmentTextView);
42 |
43 | view.Tag = new DeviceListViewHolder()
44 | {
45 | Image = image,
46 | Name = name,
47 | Department = department
48 | };
49 | var holder = (DeviceListViewHolder)view.Tag;
50 | holder.Photo.SetImageDrawable(ImageManager.Get(parent.Context, users[position].ImageUrl));
51 | holder.Name.Text = users[position].Name;
52 | holder.Department.Text = users[position].Department;
53 | return view;
54 | */
55 | }
56 |
57 | return view;
58 | }
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/DeviceListViewHolder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | using Android.App;
7 | using Android.Content;
8 | using Android.OS;
9 | using Android.Runtime;
10 | using Android.Views;
11 | using Android.Widget;
12 |
13 | namespace Sandwych.SmartConfig.AndroidDemoApp
14 | {
15 | public class DeviceListViewHolder
16 | {
17 | public ImageView Image { get; set; }
18 | public TextView IPAddressView { get; set; }
19 | public TextView MacAddressView { get; set; }
20 |
21 | }
22 | }
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Android.App;
4 | using Android.OS;
5 | using Android.Runtime;
6 | using Android.Support.Design.Widget;
7 | using Android.Support.V7.App;
8 | using Android.Views;
9 | using Android.Widget;
10 |
11 | namespace Sandwych.SmartConfig.AndroidDemoApp
12 | {
13 | [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
14 | public class MainActivity : AppCompatActivity
15 | {
16 | private readonly List _devices = new List();
17 | private ListView _deviceListView;
18 |
19 | protected override void OnCreate(Bundle savedInstanceState)
20 | {
21 | base.OnCreate(savedInstanceState);
22 | Xamarin.Essentials.Platform.Init(this, savedInstanceState);
23 | SetContentView(Resource.Layout.activity_main);
24 |
25 | Android.Support.V7.Widget.Toolbar toolbar = FindViewById(Resource.Id.toolbar);
26 | SetSupportActionBar(toolbar);
27 |
28 | _deviceListView = this.FindViewById(Resource.Id.deviceListView);
29 | _deviceListView.Adapter = new DeviceListViewAdapter(_devices);
30 |
31 | FloatingActionButton fab = FindViewById(Resource.Id.fab);
32 | fab.Click += FabOnClick;
33 | }
34 |
35 | public override bool OnCreateOptionsMenu(IMenu menu)
36 | {
37 | MenuInflater.Inflate(Resource.Menu.menu_main, menu);
38 | return true;
39 | }
40 |
41 | public override bool OnOptionsItemSelected(IMenuItem item)
42 | {
43 | int id = item.ItemId;
44 | if (id == Resource.Id.action_settings)
45 | {
46 | return true;
47 | }
48 |
49 | return base.OnOptionsItemSelected(item);
50 | }
51 |
52 | private void FabOnClick(object sender, EventArgs eventArgs)
53 | {
54 | View view = (View) sender;
55 | Snackbar.Make(view, "Replace with your own action", Snackbar.LengthLong)
56 | .SetAction("Action", (Android.Views.View.IOnClickListener)null).Show();
57 | }
58 | public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
59 | {
60 | Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
61 |
62 | base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
63 | }
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Properties/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 | using Android.App;
5 |
6 | // General Information about an assembly is controlled through the following
7 | // set of attributes. Change these attribute values to modify the information
8 | // associated with an assembly.
9 | [assembly: AssemblyTitle("Sandwych.SmartConfig.AndroidDemoApp")]
10 | [assembly: AssemblyDescription("")]
11 | [assembly: AssemblyConfiguration("")]
12 | [assembly: AssemblyCompany("")]
13 | [assembly: AssemblyProduct("Sandwych.SmartConfig.AndroidDemoApp")]
14 | [assembly: AssemblyCopyright("Copyright © 2018")]
15 | [assembly: AssemblyTrademark("")]
16 | [assembly: AssemblyCulture("")]
17 | [assembly: ComVisible(false)]
18 |
19 | // Version information for an assembly consists of the following four values:
20 | //
21 | // Major Version
22 | // Minor Version
23 | // Build Number
24 | // Revision
25 | [assembly: AssemblyVersion("1.0.0.0")]
26 | [assembly: AssemblyFileVersion("1.0.0.0")]
27 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/AboutResources.txt:
--------------------------------------------------------------------------------
1 | Images, layout descriptions, binary blobs and string dictionaries can be included
2 | in your application as resource files. Various Android APIs are designed to
3 | operate on the resource IDs instead of dealing with images, strings or binary blobs
4 | directly.
5 |
6 | For example, a sample Android app that contains a user interface layout (main.xml),
7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
8 | would keep its resources in the "Resources" directory of the application:
9 |
10 | Resources/
11 | drawable/
12 | icon.png
13 |
14 | layout/
15 | main.xml
16 |
17 | values/
18 | strings.xml
19 |
20 | In order to get the build system to recognize Android resources, set the build action to
21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but
22 | instead operate on resource IDs. When you compile an Android application that uses resources,
23 | the build system will package the resources for distribution and generate a class called "R"
24 | (this is an Android convention) that contains the tokens for each one of the resources
25 | included. For example, for the above Resources layout, this is what the R class would expose:
26 |
27 | public class R {
28 | public class drawable {
29 | public const int icon = 0x123;
30 | }
31 |
32 | public class layout {
33 | public const int main = 0x456;
34 | }
35 |
36 | public class strings {
37 | public const int first_string = 0xabc;
38 | public const int second_string = 0xbcd;
39 | }
40 | }
41 |
42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
43 | to reference the layout/main.xml file, or R.strings.first_string to reference the first
44 | string in the dictionary file values/strings.xml.
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
18 |
24 |
29 |
34 |
39 |
40 |
41 |
42 |
45 |
51 |
58 |
59 |
60 |
63 |
69 |
76 |
77 |
78 |
81 |
89 |
90 |
91 |
92 |
93 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/layout/device_list_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
16 |
24 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oldrev/sandwych-smartconfig/c7cf59d4e21ef450f7b06abbebe137d40d1eb162/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2c3e50
4 | #1B3147
5 | #3498db
6 |
7 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2C3E50
4 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WiFi SmartConfig Demo App
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Resources/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.AndroidDemoApp/Sandwych.SmartConfig.AndroidDemoApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {4B7AA7C1-C490-4E29-A8B5-AF6E42F55F99}
9 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
10 | {84dd83c5-0fe3-4294-9419-09e7c8ba324f}
11 | Library
12 | Properties
13 | Sandwych.SmartConfig.AndroidDemoApp
14 | Sandwych.SmartConfig.AndroidDemoApp
15 | 512
16 | True
17 | True
18 | Resources\Resource.designer.cs
19 | Resource
20 | Off
21 | false
22 | v9.0
23 | Properties\AndroidManifest.xml
24 | Resources
25 | Assets
26 | true
27 | true
28 | Xamarin.Android.Net.AndroidClientHandler
29 |
30 |
31 | True
32 | portable
33 | False
34 | bin\Debug\
35 | DEBUG;TRACE
36 | prompt
37 | 4
38 | True
39 | None
40 | False
41 |
42 |
43 | True
44 | portable
45 | True
46 | bin\Release\
47 | TRACE
48 | prompt
49 | 4
50 | true
51 | False
52 | SdkOnly
53 | True
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Designer
78 |
79 |
80 | Designer
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 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | Designer
118 |
119 |
120 |
121 |
122 | {147ccaf3-f2dc-4d48-acc5-7082d7de7bf6}
123 | Sandwych.SmartConfig
124 |
125 |
126 |
127 |
134 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.CliDemoApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 | using System.Net.NetworkInformation;
5 | using System.Net.Sockets;
6 | using System.Threading.Tasks;
7 |
8 | using Sandwych.SmartConfig;
9 | using Sandwych.SmartConfig.Esptouch;
10 |
11 | namespace Sandwych.SmartConfig.CliDemoApp
12 | {
13 | class Program
14 | {
15 | private static NetworkInterface FindFirstWifiInterfaceOrDefault()
16 | {
17 | var adapters = NetworkInterface.GetAllNetworkInterfaces();
18 | return adapters.Where(
19 | x => x.NetworkInterfaceType == NetworkInterfaceType.Wireless80211
20 | && x.OperationalStatus == OperationalStatus.Up
21 | && !x.IsReceiveOnly
22 | ).FirstOrDefault();
23 | }
24 |
25 | private static IPAddress GetIPv4AddressOrDefault(NetworkInterface ni)
26 | {
27 | return ni.GetIPProperties()
28 | .UnicastAddresses
29 | .Where(x => x.Address.AddressFamily == AddressFamily.InterNetwork)
30 | .Select(x => x.Address)
31 | .FirstOrDefault();
32 | }
33 |
34 | static async Task Main(string[] args)
35 | {
36 | Console.WriteLine("******** ESPTouch SmartConfig Demo/Utility ********");
37 | if (args.Length != 3)
38 | {
39 | ShowUsage();
40 | return -1;
41 | }
42 |
43 | var wifiInterface = FindFirstWifiInterfaceOrDefault();
44 | if (wifiInterface == null)
45 | {
46 | Console.WriteLine("Cannot find any available WiFi adapter.");
47 | return -1;
48 | }
49 | Console.WriteLine("WiFi interface: {0}", wifiInterface.Name);
50 |
51 | var localAddress = GetIPv4AddressOrDefault(wifiInterface);
52 | if(localAddress == null)
53 | {
54 | Console.WriteLine("Cannot find IPv4 address for WiFi interface: {0}", wifiInterface.Name);
55 | return -1;
56 | }
57 | Console.WriteLine("Local address: {0}", localAddress);
58 |
59 | var provider = new EspSmartConfigProvider();
60 | var ctx = provider.CreateContext();
61 |
62 | ctx.DeviceDiscoveredEvent += (s, e) =>
63 | {
64 | Console.WriteLine("Found device: IP={0} MAC={1}", e.Device.IPAddress, e.Device.MacAddress);
65 | };
66 |
67 | var scArgs = new SmartConfigArguments()
68 | {
69 | Ssid = args[0],
70 | Bssid = PhysicalAddress.Parse(args[1].ToUpperInvariant().Replace(':', '-')),
71 | Password = args[2],
72 | LocalAddress = localAddress
73 | };
74 |
75 | // Do the SmartConfig job
76 | using (var job = new SmartConfigJob(TimeSpan.FromSeconds(20))) // Set the timeout to 20 seconds
77 | {
78 | job.Elapsed += Job_Elapsed;
79 |
80 | await job.ExecuteAsync(ctx, scArgs);
81 | }
82 |
83 | Console.WriteLine("SmartConfig finished.");
84 | return 0;
85 | }
86 |
87 | private static void ShowUsage()
88 | {
89 | Console.WriteLine("\nUSAGE:");
90 | Console.WriteLine("sccli.exe ");
91 | Console.WriteLine("\tAP SSID:\tThe SSID of your WiFi AP.");
92 | Console.WriteLine("\tAP BSSID:\tThe BSSID(MAC) of your WiFi AP, like '10-10-10-10-10-10' or '10:10:10:10:10:10'");
93 | Console.WriteLine("\tAP Password:\tThe password of your WiFi AP.");
94 | Console.WriteLine("\nTIPS:");
95 | Console.WriteLine("\tOn Windows you can get BSSID by using command 'netsh wlan show interfaces'");
96 | }
97 |
98 | private static void Job_Elapsed(object sender, SmartConfigTimerEventArgs e)
99 | {
100 | Console.WriteLine("Doing SmartConfig, Time remaining: {0}", e.LeftTime);
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/demo/Sandwych.SmartConfig.CliDemoApp/Sandwych.SmartConfig.CliDemoApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | sccli
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/AbstractSmartConfigProvider.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using System.Collections.Generic;
3 |
4 | namespace Sandwych.SmartConfig
5 | {
6 | public abstract class AbstractSmartConfigProvider : ISmartConfigProvider
7 | {
8 | public abstract string Name { get; }
9 |
10 | public abstract IDevicePacketInterpreter CreateDevicePacketInterpreter();
11 |
12 | public SmartConfigContext CreateContext()
13 | {
14 | var ctx = new SmartConfigContext(this);
15 | foreach(var e in this.GetDefaultOptions())
16 | {
17 | ctx.Options[e.key] = e.value;
18 | }
19 | return ctx;
20 | }
21 |
22 | public abstract IProcedureEncoder CreateProcedureEncoder();
23 |
24 | public abstract IEnumerable<(string key, object value)> GetDefaultOptions();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Airkiss/AirkissOptionNames.cs:
--------------------------------------------------------------------------------
1 | namespace Sandwych.SmartConfig.Airkiss
2 | {
3 | public static class AirkissOptionNames
4 | {
5 | public const string RandomNumber = "airkiss.random";
6 | public const string PrefixCodeTimeout = "airkiss.prefix_timeout";
7 | public const string MagicCodeTimeout = "airkiss.magic_timeout";
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Airkiss/AirkissSmartConfigProvider.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Airkiss.Protocol;
2 | using Sandwych.SmartConfig.Protocol;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace Sandwych.SmartConfig.Airkiss
7 | {
8 | public class AirkissSmartConfigProvider : AbstractSmartConfigProvider
9 | {
10 | public override string Name => "Airkiss";
11 |
12 | public override IDevicePacketInterpreter CreateDevicePacketInterpreter() => new AirkissDevicePacketInterpreter();
13 |
14 | public override IEnumerable<(string key, object value)> GetDefaultOptions()
15 | {
16 | yield return (StandardOptionNames.BroadcastingTargetPort, 10001); // The port to broadcast doesn't matter
17 | yield return (StandardOptionNames.ListeningPort, 10000);
18 | yield return (StandardOptionNames.FrameInterval, TimeSpan.Zero);
19 | yield return (StandardOptionNames.SegmentInterval, TimeSpan.FromMilliseconds(5));
20 | yield return (StandardOptionNames.GuideCodeTimeout, TimeSpan.FromSeconds(2));
21 |
22 | yield return (AirkissOptionNames.MagicCodeTimeout, TimeSpan.FromMilliseconds(500));
23 | yield return (AirkissOptionNames.PrefixCodeTimeout, TimeSpan.FromMilliseconds(500));
24 |
25 | var randomValue = (byte)(Environment.TickCount % 256);
26 | yield return (AirkissOptionNames.RandomNumber, randomValue);
27 | }
28 |
29 | public override IProcedureEncoder CreateProcedureEncoder()
30 | => new AirkissProcedureEncoder();
31 |
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Airkiss/AirkissWellknownConstants.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Sandwych.SmartConfig.Airkiss
6 | {
7 | public class AirkissWellknownConstants
8 | {
9 | public static IReadOnlyList GuideCodes { get; } = new ushort[] { 1, 2, 3, 4 };
10 | public const int DevicePacketLength = 7;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Airkiss/Protocol/AirkissDevicePacketInterpreter.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net.NetworkInformation;
6 | using System.Text;
7 |
8 | namespace Sandwych.SmartConfig.Airkiss.Protocol
9 | {
10 | public class AirkissDevicePacketInterpreter : IDevicePacketInterpreter
11 | {
12 | public PhysicalAddress ParseMacAddress(byte[] packet)
13 | {
14 | var macSpan = new ArraySegment(packet, 1, 6);
15 | return new PhysicalAddress(macSpan.ToArray());
16 | }
17 |
18 | public bool Validate(SmartConfigContext context, byte[] packet)
19 | {
20 | var randomValue = context.GetOption(AirkissOptionNames.RandomNumber);
21 | return (packet.Length == AirkissWellknownConstants.DevicePacketLength && packet[0] == randomValue);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Airkiss/Protocol/AirkissMagicCodeFrameEncoder.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using Sandwych.SmartConfig.Util;
3 | using System.Collections.Generic;
4 |
5 | namespace Sandwych.SmartConfig.Airkiss.Protocol
6 | {
7 | public static class AirkissMagicCodeFrameEncoder
8 | {
9 |
10 | public static IEnumerable Encode(int totalLength, byte ssidCrc)
11 | {
12 | ushort[] frames = new ushort[4];
13 | var blen = (byte)totalLength;
14 | var firstFrame = BytesHelper.CombineUshort(0x00, blen.Bisect().high);
15 | frames[0] = firstFrame != 0 ? firstFrame : (ushort)0x08;
16 | frames[1] = BytesHelper.CombineUshort(0x01, blen.Bisect().low);
17 | frames[2] = BytesHelper.CombineUshort(0x02, ssidCrc.Bisect().high);
18 | frames[3] = BytesHelper.CombineUshort(0x03, ssidCrc.Bisect().low);
19 | return frames;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Airkiss/Protocol/AirkissPrefixCodeFrameEncoder.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using Sandwych.SmartConfig.Util;
3 | using System.Collections.Generic;
4 |
5 | namespace Sandwych.SmartConfig.Airkiss.Protocol
6 | {
7 | public static class AirkissPrefixCodeFrameEncoder
8 | {
9 | public static IEnumerable Encode(int passwordLength)
10 | {
11 | var frames = new ushort[4];
12 | var blen = (byte)passwordLength;
13 | var lenCrc8 = Crc8.ComputeOnceOnly(blen);
14 | frames[0] = BytesHelper.CombineUshort(0x04, blen.Bisect().high);
15 | frames[1] = BytesHelper.CombineUshort(0x05, blen.Bisect().low);
16 | frames[2] = BytesHelper.CombineUshort(0x06, lenCrc8.Bisect().high);
17 | frames[3] = BytesHelper.CombineUshort(0x07, lenCrc8.Bisect().low);
18 | return frames;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Airkiss/Protocol/AirkissProcedureEncoder.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using Sandwych.SmartConfig.Util;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | namespace Sandwych.SmartConfig.Airkiss.Protocol
9 | {
10 | public struct AirkissProcedureEncoder : IProcedureEncoder
11 | {
12 | public IEnumerable Encode(SmartConfigContext context, SmartConfigArguments args)
13 | {
14 | var frameInterval = context.GetOption(StandardOptionNames.FrameInterval);
15 |
16 | var builder = new List(128);
17 |
18 | var ssid = Encoding.UTF8.GetBytes(args.Ssid);
19 | var ssidCrc8 = Crc8.ComputeOnceOnly(ssid);
20 | var password = args.Password != null ? Encoding.UTF8.GetBytes(args.Password) : Constants.EmptyBuffer;
21 |
22 | // Guide Segment
23 | var guidePeriod = context.GetOption(StandardOptionNames.GuideCodeTimeout);
24 | builder.Add(new Segment(AirkissWellknownConstants.GuideCodes, frameInterval, guidePeriod));
25 |
26 | // Magic Code Segment
27 | var magicCodeFrames = AirkissMagicCodeFrameEncoder.Encode(password.Length + ssid.Length + 1, ssidCrc8);
28 | var magicCodeTimeout = context.GetOption(AirkissOptionNames.MagicCodeTimeout);
29 | builder.Add(new Segment(magicCodeFrames, frameInterval, magicCodeTimeout));
30 |
31 | // Prefix Code Segment
32 | var prefixCodeFrames = AirkissPrefixCodeFrameEncoder.Encode(password.Length);
33 | var prefixCodeTimeout = context.GetOption(AirkissOptionNames.PrefixCodeTimeout);
34 | builder.Add(new Segment(prefixCodeFrames, frameInterval, prefixCodeTimeout));
35 |
36 | // Data(password/random/ssid) Segment
37 | var randValue = context.GetOption(AirkissOptionNames.RandomNumber);
38 | var buf = password.Append(randValue).Concat(ssid).ToList();
39 | var dataFrames = new List(buf.Count * 2);
40 | var index = 0;
41 | foreach (var bytes in buf.Partition(4))
42 | {
43 | var seqEntryFrames = AirkissSeqEntryFrameEncoder.Encode(index, bytes);
44 | dataFrames.AddRange(seqEntryFrames);
45 | index++;
46 | }
47 | builder.Add(new Segment(dataFrames, frameInterval, TimeSpan.FromSeconds(4)));
48 |
49 | return builder;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Airkiss/Protocol/AirkissSeqEntryFrameEncoder.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using Sandwych.SmartConfig.Util;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace Sandwych.SmartConfig.Airkiss.Protocol
7 | {
8 | public static class AirkissSeqEntryFrameEncoder
9 | {
10 | public static IEnumerable Encode(int index, IEnumerable bytes)
11 | {
12 | var frames = new List(2 + bytes.Count());
13 |
14 | var crc = new Crc8();
15 | crc.Update((byte)(index & 0x7F));
16 | crc.Update(bytes);
17 |
18 | frames.Add((ushort)(0x80 | (crc.Value & 0x7F)));
19 | frames.Add((ushort)(0x80 | index));
20 |
21 | foreach (var b in bytes)
22 | {
23 | frames.Add((ushort)(0x100 | b));
24 | }
25 | return frames;
26 | }
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Constants.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Sandwych.SmartConfig
6 | {
7 | internal class Constants
8 | {
9 | public static readonly byte[] EmptyBuffer = new byte[] { };
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/DeviceDiscoveredEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Sandwych.SmartConfig
4 | {
5 | public class DeviceDiscoveredEventArgs : EventArgs
6 | {
7 | public DeviceDiscoveredEventArgs(ISmartConfigDevice device)
8 | {
9 | this.Device = device;
10 | }
11 |
12 | public ISmartConfigDevice Device { get; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sandwych.SmartConfig
5 | Sandwych.SmartConfig
6 | Pure C# Implementation of Various WiFi SmartConfig Protocols: AirKiss and ESPTouch
7 | Sandwych.SmartConfig
8 | smartconfig, airkiss, esptouch, esp8266, esp32, xamarin
9 | https://github.com/oldrev/sandwych-smartconfig
10 | © Li 'oldrev' Wei. All rights reserved.
11 | MIT
12 | true
13 | true
14 | true
15 | snupkg
16 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
17 |
18 |
19 | true
20 | true
21 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Esptouch/EspOptionNames.cs:
--------------------------------------------------------------------------------
1 | namespace Sandwych.SmartConfig.Esptouch
2 | {
3 | public static class EspOptionNames
4 | {
5 | public const string DatumPeriodTimeout = "esp.datum_period";
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Esptouch/EspSmartConfigProvider.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Esptouch.Protocol;
2 | using Sandwych.SmartConfig.Protocol;
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | namespace Sandwych.SmartConfig.Esptouch
7 | {
8 | public class EspSmartConfigProvider : AbstractSmartConfigProvider
9 | {
10 | public override string Name => "Esptouch";
11 |
12 | public override IDevicePacketInterpreter CreateDevicePacketInterpreter()
13 | => new EspDevicePacketInterpreter();
14 |
15 | public override IEnumerable<(string key, object value)> GetDefaultOptions()
16 | {
17 | yield return (StandardOptionNames.BroadcastingTargetPort, 7001);
18 | yield return (StandardOptionNames.ListeningPort, 18266);
19 | yield return (StandardOptionNames.FrameInterval, TimeSpan.Zero);
20 | yield return (StandardOptionNames.SegmentInterval, TimeSpan.FromMilliseconds(8));
21 | yield return (StandardOptionNames.GuideCodeTimeout, TimeSpan.FromSeconds(2));
22 | yield return (EspOptionNames.DatumPeriodTimeout, TimeSpan.FromSeconds(4));
23 | }
24 |
25 | public override IProcedureEncoder CreateProcedureEncoder()
26 | => new EspProcedureEncoder();
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Esptouch/EspWellKnownConstants.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Sandwych.SmartConfig.Esptouch
5 | {
6 | public static class EspWellKnownConstants
7 | {
8 |
9 | public static IReadOnlyList GuideCodes { get; } = new ushort[] { 515, 514, 513, 512 };
10 |
11 | public const String EspTouchVersion = "v0.3.7.2";
12 |
13 | public const byte EspDevicePacketMagic = 0x18;
14 | public const int EspDevicePacketLength = 11;
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Esptouch/Protocol/EspDatumFrameEncoder.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using Sandwych.SmartConfig.Util;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Net;
6 | using System.Text;
7 |
8 | namespace Sandwych.SmartConfig.Esptouch.Protocol
9 | {
10 | public sealed class EspDatumFrameEncoder
11 | {
12 | public const int ExtraHeaderLength = 5;
13 | public const ushort ExtraLength = 40;
14 | public const int FramesPerByte = 3;
15 |
16 | private List _framesBuilder = new List(128);
17 |
18 | public int DataCodeCount => _framesBuilder.Count / FramesPerByte;
19 |
20 | public IEnumerable Encode(SmartConfigContext ctx, SmartConfigArguments args)
21 | {
22 | // Data = total len(1 byte) + apPwd len(1 byte) + SSID CRC(1 byte) +
23 | // BSSID CRC(1 byte) + TOTAL XOR(1 byte)+ ipAddress(4 byte) + apPwd + apSsid apPwdLen <=
24 | // 105 at the moment
25 |
26 | var senderIPAddress = args.LocalAddress.GetAddressBytes();
27 |
28 | var passwordBytes = args.Password != null ? Encoding.ASCII.GetBytes(args.Password) : Constants.EmptyBuffer;
29 |
30 | var ssid = Encoding.UTF8.GetBytes(args.Ssid);
31 | var ssidCrc8 = Crc8.ComputeOnceOnly(ssid);
32 |
33 | var bssid = args.Bssid?.GetAddressBytes() ?? Constants.EmptyBuffer;
34 | var bssidCrc8 = Crc8.ComputeOnceOnly(bssid);
35 |
36 | var totalLength = (byte)(ExtraHeaderLength + senderIPAddress.Length + passwordBytes.Length + ssid.Length);
37 |
38 | byte totalXor = ComputeTotalXor(senderIPAddress, passwordBytes, ssid, ssidCrc8, bssidCrc8, totalLength);
39 |
40 | _framesBuilder.Clear();
41 |
42 | this.DoEncode(totalLength, (byte)passwordBytes.Length,
43 | ssidCrc8, bssidCrc8, totalXor, senderIPAddress, passwordBytes, ssid, bssid);
44 | return _framesBuilder;
45 | }
46 |
47 | private static byte ComputeTotalXor(
48 | byte[] senderIPAddress, byte[] passwordBytes, byte[] ssid, byte ssidCrc8, byte bssidCrc8, byte totalLength)
49 | {
50 | byte totalXor = 0;
51 | totalXor ^= (byte)totalLength;
52 | totalXor ^= (byte)passwordBytes.Length;
53 | totalXor ^= (byte)ssidCrc8;
54 | totalXor ^= (byte)bssidCrc8;
55 | foreach (var b in senderIPAddress)
56 | {
57 | totalXor ^= b;
58 | }
59 | foreach (var b in passwordBytes)
60 | {
61 | totalXor ^= b;
62 | }
63 | foreach (var b in ssid)
64 | {
65 | totalXor ^= b;
66 | }
67 |
68 | return totalXor;
69 | }
70 |
71 | public static IEnumerable ByteToFrames(int index, byte b)
72 | {
73 | if (index > byte.MaxValue || index < 0)
74 | {
75 | throw new ArgumentOutOfRangeException(nameof(index));
76 | }
77 |
78 | var crc = new Crc8();
79 | crc.Update(b);
80 | crc.Update((byte)index);
81 | var first = (crc.Value & 0xF0) | ((b >> 4) & 0x0F);
82 | var seq = (0x100 | index);
83 | var last = ((crc.Value << 4) & 0xF0) | (b & 0x0F);
84 |
85 | yield return (ushort)(first + ExtraLength);
86 | yield return (ushort)(seq + ExtraLength);
87 | yield return (ushort)(last + ExtraLength);
88 | }
89 |
90 | private void AppendByte(byte b)
91 | {
92 | this.AppendByte(this.DataCodeCount, b);
93 | }
94 |
95 | private void AppendByte(int frameIndex, byte b)
96 | {
97 | var fs = ByteToFrames(frameIndex, b);
98 | _framesBuilder.AddRange(fs);
99 | }
100 |
101 | private void AppendBytes(IEnumerable bytes)
102 | {
103 | foreach (var b in bytes)
104 | {
105 | AppendByte(b);
106 | }
107 | }
108 |
109 | private void DoEncode(
110 | byte totalLength,
111 | byte passwordLength,
112 | byte ssidCrc8,
113 | byte bssidCrc8,
114 | byte totalXor,
115 | byte[] senderIpAddress,
116 | byte[] stationPassword,
117 | byte[] ssid,
118 | byte[] bssid)
119 | {
120 | this.AppendByte(totalLength);
121 | this.AppendByte(passwordLength);
122 | this.AppendByte(ssidCrc8);
123 | this.AppendByte(bssidCrc8);
124 | this.AppendByte(totalXor);
125 | this.AppendBytes(senderIpAddress);
126 | this.AppendBytes(stationPassword);
127 | this.AppendBytes(ssid);
128 |
129 | var bssidInsertIndex = ExtraHeaderLength;
130 | for (var i = 0; i < bssid.Length; i++)
131 | {
132 | var frameIndex = totalLength + i;
133 | var byteValue = bssid[i];
134 | var fs = ByteToFrames(frameIndex, byteValue);
135 | if (bssidInsertIndex >= this.DataCodeCount)
136 | {
137 | _framesBuilder.AddRange(fs);
138 | }
139 | else
140 | {
141 | _framesBuilder.InsertRange(bssidInsertIndex * FramesPerByte, fs);
142 | }
143 | bssidInsertIndex += 4;
144 | }
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Esptouch/Protocol/EspDevicePacketInterpreter.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net.NetworkInformation;
6 | using System.Text;
7 |
8 | namespace Sandwych.SmartConfig.Esptouch.Protocol
9 | {
10 | public sealed class EspDevicePacketInterpreter : IDevicePacketInterpreter
11 | {
12 | public PhysicalAddress ParseMacAddress(byte[] packet)
13 | {
14 | var macSpan = new ArraySegment(packet, 1, 6);
15 | return new PhysicalAddress(macSpan.ToArray());
16 | }
17 |
18 | public bool Validate(SmartConfigContext context, byte[] packet)
19 | {
20 | return packet.Length == EspWellKnownConstants.EspDevicePacketLength && packet[0] == EspWellKnownConstants.EspDevicePacketMagic;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Esptouch/Protocol/EspProcedureEncoder.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace Sandwych.SmartConfig.Esptouch.Protocol
6 | {
7 | public sealed class EspProcedureEncoder : IProcedureEncoder
8 | {
9 | public IEnumerable Encode(SmartConfigContext context, SmartConfigArguments args)
10 | {
11 | var guideTimeout = context.GetOption(StandardOptionNames.GuideCodeTimeout);
12 | var datumTimeout = context.GetOption(EspOptionNames.DatumPeriodTimeout);
13 | var frameInterval = context.GetOption(StandardOptionNames.FrameInterval);
14 |
15 | var datumEncoder = new EspDatumFrameEncoder();
16 | var segFrames = new Segment[]
17 | {
18 | new Segment(EspWellKnownConstants.GuideCodes, frameInterval, guideTimeout),
19 | new Segment(datumEncoder.Encode(context, args), frameInterval, datumTimeout)
20 | };
21 | return segFrames;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/ISmartConfigContextFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Sandwych.SmartConfig
6 | {
7 | public interface ISmartConfigContextFactory
8 | {
9 | SmartConfigContext CreateContext();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/ISmartConfigDevice.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.NetworkInformation;
3 |
4 | namespace Sandwych.SmartConfig
5 | {
6 | public interface ISmartConfigDevice
7 | {
8 | PhysicalAddress MacAddress { get; }
9 | IPAddress IPAddress { get; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/ISmartConfigJob.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace Sandwych.SmartConfig
6 | {
7 | public interface ISmartConfigJob : IDisposable
8 | {
9 | TimeSpan Timeout { get; }
10 |
11 | Task ExecuteAsync(SmartConfigContext context,
12 | SmartConfigArguments args,
13 | CancellationToken cancelToken);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/ISmartConfigProvider.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 |
3 | namespace Sandwych.SmartConfig
4 | {
5 | public interface ISmartConfigProvider : ISmartConfigContextFactory
6 | {
7 | string Name { get; }
8 |
9 | IProcedureEncoder CreateProcedureEncoder();
10 |
11 | IDevicePacketInterpreter CreateDevicePacketInterpreter();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Networking/DatagramBroadcaster.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Protocol;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Net.Sockets;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace Sandwych.SmartConfig.Networking
11 | {
12 | public class DatagramBroadcaster : IDatagramBroadcaster
13 | {
14 | private IDatagramClient _broadcastingSocket;
15 | private bool _isStarted = false;
16 | private IPEndPoint _broadcastTarget;
17 |
18 | public DatagramBroadcaster()
19 | {
20 | _broadcastingSocket = new DefaultDatagramClient();
21 | }
22 |
23 | public DatagramBroadcaster(IDatagramClient client)
24 | {
25 | _broadcastingSocket = client;
26 | }
27 |
28 | public async Task BroadcastAsync(SmartConfigContext context, SmartConfigArguments args, CancellationToken cancelToken)
29 | {
30 | if (_isStarted)
31 | {
32 | throw new InvalidOperationException("Already started");
33 | }
34 |
35 | try
36 | {
37 | this._isStarted = true;
38 |
39 | _broadcastingSocket.Bind(new IPEndPoint(args.LocalAddress, 0));
40 | _broadcastTarget = new IPEndPoint(IPAddress.Broadcast, context.GetOption(StandardOptionNames.BroadcastingTargetPort));
41 | var encoder = context.Provider.CreateProcedureEncoder();
42 | var segments = encoder.Encode(context, args);
43 | var broadcastBuffer = this.CreateBroadcastBuffer(segments.SelectMany(x => x.Frames));
44 |
45 | await this.BroadcastProcedureAsync(context, segments, broadcastBuffer, cancelToken);
46 | }
47 | finally
48 | {
49 | _isStarted = false;
50 | }
51 | }
52 |
53 | private async Task BroadcastProcedureAsync(
54 | SmartConfigContext context,
55 | IEnumerable segments,
56 | byte[] broadcastBuffer,
57 | CancellationToken userCancelToken)
58 | {
59 | var segmentInterval = context.GetOption(StandardOptionNames.SegmentInterval);
60 | while (true)
61 | {
62 | userCancelToken.ThrowIfCancellationRequested();
63 |
64 | foreach (var segment in segments)
65 | {
66 | userCancelToken.ThrowIfCancellationRequested();
67 |
68 | if (segment.BroadcastingMaxTimes > 0)
69 | {
70 | await this.BroadcastSegmentByTimesAsync(context, segment, broadcastBuffer, userCancelToken);
71 | }
72 | else
73 | {
74 | await this.BroadcastSegmentUntilAsync(
75 | context, segment, broadcastBuffer, userCancelToken);
76 | }
77 | if (segmentInterval > TimeSpan.Zero)
78 | {
79 | await Task.Delay(segmentInterval, userCancelToken);
80 | }
81 | }
82 |
83 | if (segmentInterval > TimeSpan.Zero)
84 | {
85 | await Task.Delay(segmentInterval, userCancelToken);
86 | }
87 | }
88 | }
89 |
90 | private async Task BroadcastSegmentUntilAsync(
91 | SmartConfigContext context, Segment segment, byte[] broadcastBuffer, CancellationToken token)
92 | {
93 | var segmentInterval = context.GetOption(StandardOptionNames.SegmentInterval);
94 | var endTime = TimeSpan.FromMilliseconds(Environment.TickCount) + segment.BroadcastingPeriod;
95 | while ((TimeSpan.FromMilliseconds(Environment.TickCount) <= endTime) && !token.IsCancellationRequested)
96 | {
97 | await this.BroadcastSingleSegmentAsync(segment, broadcastBuffer, segmentInterval, token);
98 | }
99 | }
100 |
101 | private async Task BroadcastSegmentByTimesAsync(
102 | SmartConfigContext context, Segment segment, byte[] broadcastBuffer, CancellationToken token)
103 | {
104 | var segmentInterval = context.GetOption(StandardOptionNames.FrameInterval);
105 | for (int i = 0; i < segment.BroadcastingMaxTimes; i++)
106 | {
107 | token.ThrowIfCancellationRequested();
108 | await this.BroadcastSingleSegmentAsync(segment, broadcastBuffer, segmentInterval, token);
109 | }
110 | }
111 |
112 | private async Task BroadcastSingleSegmentAsync(
113 | Segment segment, byte[] broadcastBuffer, TimeSpan segmentInterval, CancellationToken token)
114 | {
115 | token.ThrowIfCancellationRequested();
116 | foreach (var frame in segment.Frames)
117 | {
118 | token.ThrowIfCancellationRequested();
119 | await _broadcastingSocket.SendAsync(broadcastBuffer, frame, this._broadcastTarget);
120 | if (segment.FrameInterval > TimeSpan.Zero)
121 | {
122 | await Task.Delay(segment.FrameInterval, token);
123 | }
124 | }
125 | if (segmentInterval > TimeSpan.Zero)
126 | {
127 | await Task.Delay(segmentInterval, token);
128 | }
129 | }
130 |
131 | public byte[] CreateBroadcastBuffer(IEnumerable frames)
132 | {
133 | var maxLength = frames.Max();
134 | var bytes = new byte[maxLength];
135 | for (int i = 0; i < bytes.Length; i++)
136 | {
137 | bytes[i] = (byte)'1';
138 | }
139 | return bytes.ToArray();
140 | }
141 |
142 | #region IDisposable Support
143 | private bool _isDisposed = false; // To detect redundant calls
144 |
145 | public void Close()
146 | {
147 | this.Dispose();
148 | }
149 |
150 | protected virtual void Dispose(bool disposing)
151 | {
152 | if (!_isDisposed)
153 | {
154 | if (disposing)
155 | {
156 | _broadcastingSocket.Dispose();
157 | }
158 | _isDisposed = true;
159 | }
160 | }
161 |
162 | ~DatagramBroadcaster()
163 | {
164 | this.Dispose(false);
165 | }
166 |
167 | // This code added to correctly implement the disposable pattern.
168 | public void Dispose()
169 | {
170 | if (_isStarted)
171 | {
172 | throw new InvalidOperationException("Already started.");
173 | }
174 | this.Dispose(true);
175 | GC.SuppressFinalize(this);
176 | }
177 | #endregion
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Networking/DatagramReceiveResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Text;
5 |
6 | namespace Sandwych.SmartConfig.Networking
7 | {
8 | public struct DatagramReceiveResult
9 | {
10 | public byte[] Buffer { get; }
11 | public IPEndPoint RemoteEndPoint { get; }
12 |
13 | public DatagramReceiveResult(byte[] buffer, IPEndPoint remote)
14 | {
15 | this.Buffer = buffer;
16 | this.RemoteEndPoint = remote;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Networking/DatagramReceiver.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Esptouch;
2 | using Sandwych.SmartConfig.Util;
3 | using System;
4 | using System.Net;
5 | using System.Net.Sockets;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using System.Collections.Concurrent;
9 |
10 | namespace Sandwych.SmartConfig.Networking
11 | {
12 | public class DatagramReceiver : IDatagramReceiver
13 | {
14 | private readonly IDatagramClient _listeningSocket;
15 | private readonly ConcurrentDictionary _reportedDeviceIPAddresses = new ConcurrentDictionary();
16 |
17 | private bool _isStarted = false;
18 |
19 | public DatagramReceiver()
20 | {
21 | _listeningSocket = new DefaultDatagramClient();
22 | }
23 |
24 | public DatagramReceiver(IDatagramClient client)
25 | {
26 | _listeningSocket = client;
27 | }
28 |
29 | public async Task ListenAsync(
30 | SmartConfigContext context, IPAddress localAddress, CancellationToken cancelToken)
31 | {
32 | if (_isStarted)
33 | {
34 | throw new InvalidOperationException("Already started.");
35 | }
36 |
37 | _isStarted = true;
38 | try
39 | {
40 | this.SetupSocket(context, localAddress);
41 | _reportedDeviceIPAddresses.Clear();
42 | await this.ListenUntilCancelledAsync(context).WithCancellation(cancelToken);
43 | }
44 | finally
45 | {
46 | _isStarted = false;
47 | }
48 | }
49 |
50 | private void SetupSocket(SmartConfigContext context, IPAddress localAddress)
51 | {
52 | var listeningPort = context.GetOption(StandardOptionNames.ListeningPort);
53 | _listeningSocket.Bind(new IPEndPoint(localAddress, listeningPort));
54 | }
55 |
56 | private async Task ListenUntilCancelledAsync(SmartConfigContext context)
57 | {
58 | var interpreter = context.Provider.CreateDevicePacketInterpreter();
59 | while (true)
60 | {
61 | var result = await _listeningSocket.ReceiveAsync();
62 | if (result.Buffer != null && result.Buffer.Length > 0)
63 | {
64 | var remoteAddress = result.RemoteEndPoint.Address;
65 | if (interpreter.Validate(context, result.Buffer) && _reportedDeviceIPAddresses.TryAdd(remoteAddress, 0))
66 | {
67 | var mac = interpreter.ParseMacAddress(result.Buffer);
68 | context.ReportDevice(new SmartConfigDevice(mac, remoteAddress));
69 | }
70 | }
71 | }
72 | }
73 |
74 | public void Close()
75 | {
76 | this.Dispose();
77 | }
78 |
79 | #region IDisposable Support
80 |
81 | private bool _isDisposed = false; // To detect redundant calls
82 |
83 | protected virtual void Dispose(bool disposing)
84 | {
85 | if (!_isDisposed)
86 | {
87 | if (disposing)
88 | {
89 | _listeningSocket.Dispose();
90 | }
91 | _isDisposed = true;
92 | }
93 | }
94 |
95 | ~DatagramReceiver()
96 | {
97 | Dispose(false);
98 | }
99 |
100 | // This code added to correctly implement the disposable pattern.
101 | public void Dispose()
102 | {
103 | if (_isStarted)
104 | {
105 | throw new InvalidOperationException("Already started.");
106 | }
107 | this.Dispose(true);
108 | GC.SuppressFinalize(this);
109 | }
110 |
111 | #endregion
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Networking/DefaultDatagramClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Net.NetworkInformation;
7 | using System.Net.Sockets;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace Sandwych.SmartConfig.Networking
12 | {
13 | public class DefaultDatagramClient : IDatagramClient
14 | {
15 | private readonly UdpClient _udp;
16 |
17 | public DefaultDatagramClient()
18 | {
19 | _udp = new UdpClient(AddressFamily.InterNetwork);
20 | _udp.EnableBroadcast = true;
21 | }
22 |
23 | public void Bind(IPEndPoint localEndPoint)
24 | {
25 | _udp.Client.Bind(localEndPoint);
26 | }
27 |
28 | public async Task SendAsync(byte[] datagram, int bytes, IPEndPoint target)
29 | {
30 | await _udp.SendAsync(datagram, bytes, target);
31 | }
32 |
33 | public async Task ReceiveAsync()
34 | {
35 | var result = await _udp.ReceiveAsync();
36 | return new DatagramReceiveResult(result.Buffer, result.RemoteEndPoint);
37 | }
38 |
39 | public void Dispose()
40 | {
41 | _udp.Dispose();
42 | }
43 |
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Networking/IDatagramBroadcaster.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace Sandwych.SmartConfig.Networking
6 | {
7 | public interface IDatagramBroadcaster : IDisposable
8 | {
9 | Task BroadcastAsync(
10 | SmartConfigContext context, SmartConfigArguments args, CancellationToken cancelToken);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Networking/IDatagramClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Sandwych.SmartConfig.Networking
8 | {
9 | public interface IDatagramClient : IDisposable
10 | {
11 | void Bind(IPEndPoint localEndPoint);
12 | Task SendAsync(byte[] datagram, int bytes, IPEndPoint target);
13 |
14 | Task ReceiveAsync();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Networking/IDatagramReceiver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace Sandwych.SmartConfig.Networking
7 | {
8 | public interface IDatagramReceiver : IDisposable
9 | {
10 | Task ListenAsync(
11 | SmartConfigContext context, IPAddress localAddress, CancellationToken cancelToken);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Protocol/FrameDataConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Sandwych.Esptouch.Protocol
5 | {
6 | public static class FrameDataConverter
7 | {
8 |
9 | public static IReadOnlyList ValueToFilledByteArray(int value)
10 | {
11 | if (value > 0x1FF || value < 0)
12 | {
13 | throw new ArgumentOutOfRangeException(nameof(value));
14 | }
15 |
16 | var result = new List(value);
17 | for (int i = 0; i < value; i++)
18 | {
19 | result[i] = (byte)'1';
20 | }
21 | return result;
22 | }
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Protocol/IDevicePacketInterpreter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net.NetworkInformation;
4 | using System.Text;
5 |
6 | namespace Sandwych.SmartConfig.Protocol
7 | {
8 | public interface IDevicePacketInterpreter
9 | {
10 | bool Validate(SmartConfigContext context, byte[] packet);
11 |
12 | PhysicalAddress ParseMacAddress(byte[] packet);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Protocol/IProcedureEncoder.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Sandwych.SmartConfig.Protocol
4 | {
5 | public interface IProcedureEncoder
6 | {
7 | IEnumerable Encode(SmartConfigContext context, SmartConfigArguments args);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Protocol/Segment.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Sandwych.SmartConfig.Protocol
5 | {
6 | public struct Segment
7 | {
8 | public IEnumerable Frames { get; }
9 | public TimeSpan FrameInterval { get; }
10 | public TimeSpan BroadcastingPeriod { get; }
11 | public int BroadcastingMaxTimes { get; }
12 |
13 | public Segment(
14 | IEnumerable frames,
15 | TimeSpan frameInterval,
16 | int broadcastingMaxTimes)
17 | {
18 | this.Frames = frames;
19 | this.FrameInterval = frameInterval;
20 | this.BroadcastingPeriod = TimeSpan.MaxValue;
21 | this.BroadcastingMaxTimes = broadcastingMaxTimes;
22 | }
23 |
24 | public Segment(
25 | IEnumerable frames,
26 | TimeSpan frameInterval,
27 | TimeSpan broadcastingPeriod)
28 | {
29 | this.Frames = frames;
30 | this.FrameInterval = frameInterval;
31 | this.BroadcastingPeriod = broadcastingPeriod;
32 | this.BroadcastingMaxTimes = 0;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/Sandwych.SmartConfig.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/SmartConfigArguments.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Net.NetworkInformation;
5 | using System.Text;
6 |
7 | namespace Sandwych.SmartConfig
8 | {
9 | public class SmartConfigArguments
10 | {
11 | public string Password { get; set; } = string.Empty;
12 | public string Ssid { get; set; }
13 | public PhysicalAddress Bssid { get; set; }
14 | public IPAddress LocalAddress { get; set; }
15 | public bool? IsHiddenSsid { get; set; } = null;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/SmartConfigContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Sandwych.SmartConfig
5 | {
6 | public class SmartConfigContext
7 | {
8 | public IDictionary Options { get; } = new Dictionary();
9 |
10 | public T GetOption(string name) => (T)this.Options[name];
11 |
12 | public void SetOption(string name, T value)
13 | {
14 | this.Options[name] = value;
15 | }
16 |
17 | public ISmartConfigProvider Provider { get; internal set; }
18 |
19 | public SmartConfigContext(ISmartConfigProvider provider)
20 | {
21 | this.Provider = provider;
22 | }
23 |
24 | public event EventHandler DeviceDiscoveredEvent;
25 |
26 | public void ReportDevice(ISmartConfigDevice device)
27 | {
28 | this.DeviceDiscoveredEvent?.Invoke(this, new DeviceDiscoveredEventArgs(device));
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/SmartConfigDevice.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Net.NetworkInformation;
5 | using System.Text;
6 |
7 | namespace Sandwych.SmartConfig
8 | {
9 | public class SmartConfigDevice : ISmartConfigDevice
10 | {
11 | public PhysicalAddress MacAddress { get; }
12 | public IPAddress IPAddress { get; }
13 |
14 | public SmartConfigDevice(PhysicalAddress mac, IPAddress ip)
15 | {
16 | this.MacAddress = mac;
17 | this.IPAddress = ip;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/SmartConfigExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace Sandwych.SmartConfig
8 | {
9 | public static class SmartConfigExtensions
10 | {
11 | public static async Task ExecuteAsync(
12 | this ISmartConfigJob self, SmartConfigContext context, SmartConfigArguments args)
13 | {
14 | await self.ExecuteAsync(context, args, CancellationToken.None);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/SmartConfigJob.cs:
--------------------------------------------------------------------------------
1 | using Sandwych.SmartConfig.Networking;
2 | using System;
3 | using System.Net;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Sandwych.SmartConfig.Util;
7 | using System.Timers;
8 | using System.ComponentModel;
9 |
10 | #if DEBUG
11 | using System.Diagnostics;
12 | #endif
13 |
14 | namespace Sandwych.SmartConfig
15 | {
16 | public class SmartConfigJob : ISmartConfigJob
17 | {
18 | private bool _isStarted = false;
19 | private readonly IDatagramBroadcaster _broadcaster = new DatagramBroadcaster();
20 | private readonly IDatagramReceiver _receiver = new DatagramReceiver();
21 |
22 | public static TimeSpan TimeInterval { get; } = TimeSpan.FromSeconds(1);
23 |
24 | private System.Timers.Timer _timer = new System.Timers.Timer(TimeInterval.TotalMilliseconds);
25 |
26 | private CancellationTokenSource _timerCts = null;
27 |
28 | public event SmartConfigTimerEventHandler Elapsed;
29 |
30 | public TimeSpan Timeout { get; }
31 | public TimeSpan ExecutedTime { get; private set; } = TimeSpan.Zero;
32 | public TimeSpan LeftTime => this.Timeout - this.ExecutedTime;
33 |
34 | public SmartConfigJob() : this(TimeSpan.FromSeconds(60))
35 | {
36 | }
37 |
38 | public SmartConfigJob(TimeSpan timeout)
39 | {
40 | this.Timeout = timeout;
41 |
42 | this._timer.Elapsed += Timer_Elapsed;
43 | }
44 |
45 | private void Timer_Elapsed(object sender, ElapsedEventArgs e)
46 | {
47 | this.ExecutedTime = ExecutedTime.Add(TimeSpan.FromMilliseconds(_timer.Interval));
48 | this.Elapsed?.Invoke(this, new SmartConfigTimerEventArgs(this.Timeout, this.ExecutedTime));
49 | if (this.LeftTime <= TimeSpan.Zero)
50 | {
51 | if (_timer.Enabled)
52 | {
53 | _timer.Stop();
54 | }
55 |
56 | _timerCts?.Cancel();
57 | }
58 | }
59 |
60 | public async Task ExecuteAsync(SmartConfigContext context, SmartConfigArguments args, CancellationToken externalCancelToken)
61 | {
62 | if (_isStarted)
63 | {
64 | throw new InvalidOperationException("Already started");
65 | }
66 |
67 | this.ExecutedTime = TimeSpan.Zero;
68 | _isStarted = true;
69 | _timerCts = new CancellationTokenSource();
70 | var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(externalCancelToken, _timerCts.Token);
71 | try
72 | {
73 | _timer.Start();
74 | this.Elapsed?.Invoke(this, new SmartConfigTimerEventArgs(this.Timeout, this.ExecutedTime));
75 |
76 | var broadcastingTask = _broadcaster.BroadcastAsync(context, args, linkedCts.Token).CancelOnFaulted(linkedCts);
77 | var receivingTask = _receiver.ListenAsync(context, args.LocalAddress, linkedCts.Token).CancelOnFaulted(linkedCts);
78 | await Task.WhenAll(broadcastingTask, receivingTask);
79 | }
80 | catch (OperationCanceledException ocex)
81 | {
82 | if (externalCancelToken.IsCancellationRequested)
83 | {
84 | throw ocex;
85 | }
86 | }
87 | catch
88 | {
89 | linkedCts.Cancel();
90 | throw;
91 | }
92 | finally
93 | {
94 | if (_timer.Enabled)
95 | {
96 | _timer.Stop();
97 | }
98 |
99 | linkedCts.Dispose();
100 |
101 | _timerCts.Dispose();
102 | _timerCts = null;
103 |
104 | ExecutedTime = TimeSpan.Zero;
105 | _isStarted = false;
106 | }
107 | }
108 |
109 |
110 | #region IDisposable Support
111 | private bool _isDisposed = false; // To detect redundant calls
112 |
113 | public void Close()
114 | {
115 | this.Dispose();
116 | }
117 |
118 | protected virtual void Dispose(bool disposing)
119 | {
120 | if (!_isDisposed)
121 | {
122 | if (disposing)
123 | {
124 | _timer.Dispose();
125 | _receiver.Dispose();
126 | _broadcaster.Dispose();
127 | }
128 | _isDisposed = true;
129 | }
130 | }
131 |
132 | ~SmartConfigJob()
133 | {
134 | this.Dispose(false);
135 | }
136 |
137 | public void Dispose()
138 | {
139 | if (_isStarted)
140 | {
141 | throw new InvalidOperationException("Already started");
142 | }
143 | this.Dispose(true);
144 | GC.SuppressFinalize(this);
145 | }
146 | #endregion
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/src/Sandwych.SmartConfig/SmartConfigStarter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace Sandwych.SmartConfig
8 | {
9 | public static class SmartConfigStarter
10 | {
11 | public static async Task StartAsync(SmartConfigArguments args,
12 | CancellationToken cancelToken,
13 | Action