From ec33cdc13e3a9f21672818a42b3f6da4cdf59469 Mon Sep 17 00:00:00 2001 From: misodengaku Date: Fri, 10 May 2019 15:24:27 +0900 Subject: [PATCH] Initial commit --- .gitignore | 348 +++++++ LICENSE | 201 ++++ README.md | 26 + SakuraIO/Client.cs | 300 ++++++ SakuraIO/Datastore.cs | 31 + SakuraIO/Message.cs | 117 +++ SakuraIO/Module.cs | 32 + SakuraIO/Project.cs | 30 + SakuraIO/Properties/AssemblyInfo.cs | 36 + SakuraIO/SakuraIO.csproj | 55 ++ SakuraIO/Service.cs | 20 + SakuraIOTest.sln | 36 + SakuraIOTest/App.config | 6 + SakuraIOTest/DatastoreForm.Designer.cs | 96 ++ SakuraIOTest/DatastoreForm.cs | 25 + SakuraIOTest/DatastoreForm.resx | 120 +++ SakuraIOTest/Form1.Designer.cs | 934 ++++++++++++++++++ SakuraIOTest/Form1.cs | 500 ++++++++++ SakuraIOTest/Form1.resx | 129 +++ SakuraIOTest/Program.cs | 22 + SakuraIOTest/Properties/AssemblyInfo.cs | 36 + SakuraIOTest/Properties/Resources.Designer.cs | 71 ++ SakuraIOTest/Properties/Resources.resx | 117 +++ SakuraIOTest/Properties/Settings.Designer.cs | 30 + SakuraIOTest/Properties/Settings.settings | 7 + SakuraIOTest/README.html | 0 SakuraIOTest/SakuraIOTest.csproj | 103 ++ images/sakuraio-test.png | Bin 0 -> 15536 bytes 28 files changed, 3428 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SakuraIO/Client.cs create mode 100644 SakuraIO/Datastore.cs create mode 100644 SakuraIO/Message.cs create mode 100644 SakuraIO/Module.cs create mode 100644 SakuraIO/Project.cs create mode 100644 SakuraIO/Properties/AssemblyInfo.cs create mode 100644 SakuraIO/SakuraIO.csproj create mode 100644 SakuraIO/Service.cs create mode 100644 SakuraIOTest.sln create mode 100644 SakuraIOTest/App.config create mode 100644 SakuraIOTest/DatastoreForm.Designer.cs create mode 100644 SakuraIOTest/DatastoreForm.cs create mode 100644 SakuraIOTest/DatastoreForm.resx create mode 100644 SakuraIOTest/Form1.Designer.cs create mode 100644 SakuraIOTest/Form1.cs create mode 100644 SakuraIOTest/Form1.resx create mode 100644 SakuraIOTest/Program.cs create mode 100644 SakuraIOTest/Properties/AssemblyInfo.cs create mode 100644 SakuraIOTest/Properties/Resources.Designer.cs create mode 100644 SakuraIOTest/Properties/Resources.resx create mode 100644 SakuraIOTest/Properties/Settings.Designer.cs create mode 100644 SakuraIOTest/Properties/Settings.settings create mode 100644 SakuraIOTest/README.html create mode 100644 SakuraIOTest/SakuraIOTest.csproj create mode 100644 images/sakuraio-test.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f255229 --- /dev/null +++ b/.gitignore @@ -0,0 +1,348 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4fa394 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# SakuraIOTest +sakura.ioの機能を試すためのWindows用ツールです。 + +![sakura.io Test](./images/sakuraio-test.png) + +# 試すことができる機能 +* 管理用API + * プロジェクト一覧の取得 + * 特定のプロジェクトに所属するモジュール一覧の取得 +* WebSocket連携 + * 通信モジュールへのメッセージ送信 + * 通信モジュールから送られてきたメッセージの受信 + +# ディレクトリ構成 +[SakuraIOTestディレクトリ](SakuraIOTest/)以下に本体のソースコード、 +[SakuraIOディレクトリ](SakuraIO/)以下にsakura.ioへのアクセスを行うライブラリのソースコードが格納されています。 + +# 動作環境 +Windows 10上で動作することを確認しています。 + +また、開発にはVisual Studio 2017 Communityを使用しています。 + +# 再頒布やサポートについて +本リポジトリに含まれるソースコードやビルド済みバイナリはApache License 2.0に基づき自由に使用・再頒布が可能です。 + +ただし、本リポジトリに含まれる内容について一切のサポート・保証等はありません。 \ No newline at end of file diff --git a/SakuraIO/Client.cs b/SakuraIO/Client.cs new file mode 100644 index 0000000..d8310f1 --- /dev/null +++ b/SakuraIO/Client.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.WebSockets; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SakuraIO +{ + public class Client + { + private string apiToken; + private string apiSecret; + private string wsToken; + private ClientWebSocket sakuraioSocket; + const int MessageBufferSize = 102400; + + public delegate void OnMessageHandler(ISakuraIOMessage message); + public delegate void OnConnectHandler(bool isSucceeded); + public delegate void OnCloseHandler(); + + private string getBasicAuthString() => Convert.ToBase64String(Encoding.UTF8.GetBytes(apiToken + ":" + apiSecret)); + + #region Property + public bool IsWebSocketOpened => sakuraioSocket != null && sakuraioSocket.State == WebSocketState.Open; + + public bool IsAPIAuthenticated { get; private set; } = false; + #endregion + + #region Constructor + public Client() { } + + public Client(string _apiToken, string _apiSecret) + { + SetAPIToken(_apiToken, _apiSecret); + } + #endregion + + #region Exceptions + public class APIException : Exception + { + public APIException(string message) : base(message) + { + } + } + public class WebSocketException : Exception + { + public WebSocketException(string message) : base(message) + { + } + } + #endregion + + public void SetAPIToken(string _apiToken, string _apiSecret) + { + apiToken = _apiToken; + apiSecret = _apiSecret; + } + + public void SetWebSocketToken(string _wsToken) => wsToken = _wsToken; + + public async Task AuthenticateAPI() + { + using (var client = new HttpClient()) + { + var request = new HttpRequestMessage(HttpMethod.Get, "https://api.sakura.io/v1/auth/"); + request.Headers.Add(@"Authorization", @"Basic " + getBasicAuthString()); + + var response = await client.SendAsync(request); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + IsAPIAuthenticated = false; + throw new Exception("API request failed"); + } + } + IsAPIAuthenticated = true; + return true; + } + + public async Task> GetModulesFromProjectID(int project_id) + { + using (var client = new HttpClient()) + { + var request = new HttpRequestMessage(HttpMethod.Get, string.Format("https://api.sakura.io/v1/modules/?project={0}", project_id)); + request.Headers.Add(@"Authorization", @"Basic " + getBasicAuthString()); + + var response = await client.SendAsync(request); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + throw new Exception("API request failed"); + } + + + var serializer = new DataContractJsonSerializer(typeof(List)); + var modules = (List)serializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(await response.Content.ReadAsStringAsync()))); + + return modules; + } + throw new NotImplementedException(); + } + + public async Task> GetProjects() + { + using (var client = new HttpClient()) + { + var request = new HttpRequestMessage(HttpMethod.Get, @"https://api.sakura.io/v1/projects/"); + request.Headers.Add(@"Authorization", @"Basic " + getBasicAuthString()); + + var response = await client.SendAsync(request); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + throw new Exception("API request failed"); + } + + + var serializer = new DataContractJsonSerializer(typeof(List)); + var projects = (List)serializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(await response.Content.ReadAsStringAsync()))); + + return projects; + } + } + + public async Task> GetServices() + { + + using (var client = new HttpClient()) + { + var request = new HttpRequestMessage(HttpMethod.Get, @"https://api.sakura.io/v1/services/"); + request.Headers.Add(@"Authorization", @"Basic " + getBasicAuthString()); + + var response = await client.SendAsync(request); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + throw new Exception("API request failed"); + } + + + var serializer = new DataContractJsonSerializer(typeof(List)); + var services = (List)serializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(await response.Content.ReadAsStringAsync()))); + return services; + } + } + + public async Task GetProjectIDFromServiceToken(string serviceToken) + { + var services = await GetServices(); + var service = services.Where(x => x.Token == serviceToken).Select(x => x).Single(); + + return service.ProjectID; + } + + public async Task> GetServicesFromProject(Project project) + { + var service_all = await GetServices(); + var services = service_all.Where(x => x.ProjectID == project.ID).ToList(); + + return services; + } + + public async Task> GetServicesFromProject(int project_id) + { + var service_all = await GetServices(); + var services = service_all.Where(x => x.ProjectID == project_id).ToList(); + + return services; + } + + public async Task CreateService(int project_id, string name, string type) + { + var parameters = new Dictionary() + { + {"name", name}, + {"type", type }, + {"project", project_id }, + }; + var request = new HttpRequestMessage(HttpMethod.Post, @"https://api.sakura.io/v1/services/"); + request.Headers.Add(@"Authorization", @"Basic " + getBasicAuthString()); + request.Content = new StringContent($"{{\"name\": \"{name}\", \"type\": \"{type}\", \"project\": {project_id}}}", Encoding.UTF8); + request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + using (var client = new HttpClient()) + { + var response = await client.SendAsync(request); + var resp_str = await response.Content.ReadAsStringAsync(); + Console.WriteLine(resp_str); + + var serializer = new DataContractJsonSerializer(typeof(Service)); + var service = (Service)serializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(resp_str))); + return service; + } + } + + private ChannelMessage DeserializeChannelMessage(string frameString) + { + try + { + var serializer = new DataContractJsonSerializer(typeof(ChannelMessage)); + // nullで刺さる + return (ChannelMessage)serializer.ReadObject(new MemoryStream(Encoding.UTF8.GetBytes(frameString))); + + } + catch (Exception) + { + return null; + } + } + + private string SerializeChannelMessage(ISakuraIOMessage message) + { + Type type; + + if (message.Type == "channels") + { + type = typeof(ChannelMessage); + } + else + { + throw new InvalidDataException("messageに含まれるtypeの値が不正です"); + } + + using (var ms = new MemoryStream()) + using (var sr = new StreamReader(ms)) + { + var serializer = new DataContractJsonSerializer(type); + serializer.WriteObject(ms, message); + ms.Position = 0; + return sr.ReadToEnd(); + } + } + + + public async Task ConnectWebSocket(OnMessageHandler onMessageHandler, OnConnectHandler onConnectHandler = null, OnCloseHandler onCloseHandler = null) + { + await Task.Run(async () => + { + if (!Guid.TryParse(wsToken, out _)) + { + throw new ArgumentException("tokenの値が不正です, UUID形式で入力してください"); + } + + sakuraioSocket = new ClientWebSocket(); + try + { + await sakuraioSocket.ConnectAsync(new Uri("wss://api.sakura.io/ws/v1/" + wsToken), CancellationToken.None); + } + catch (System.Net.WebSockets.WebSocketException) + { + throw new WebSocketException("WebSocketの接続に失敗しました"); + } + + // 特に指定がなければ呼ばない + onConnectHandler?.Invoke(true); + + while (sakuraioSocket.State == WebSocketState.Open) + { + var buff = new ArraySegment(new byte[MessageBufferSize]); + var ret = await sakuraioSocket.ReceiveAsync(buff, CancellationToken.None); + + if (!ret.EndOfMessage) + { + continue; + } + + var frameString = (new UTF8Encoding()).GetString(buff.Take(ret.Count).ToArray()); + Console.WriteLine(frameString); + + var cmsg = DeserializeChannelMessage(frameString); + if (cmsg == null) + { + continue; + } + + onMessageHandler(cmsg); + + } + onCloseHandler?.Invoke(); + }); + } + + public async Task SendWebSocket(ISakuraIOMessage msg) + { + + var jsonStr = SerializeChannelMessage(msg); + var buff = new ArraySegment((new UTF8Encoding()).GetBytes(jsonStr)); + Console.WriteLine(jsonStr); + + await sakuraioSocket.SendAsync(buff, WebSocketMessageType.Text, true, CancellationToken.None); + + } + + public async Task CloseWebSocket() + { + await sakuraioSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "connection closed by client", CancellationToken.None); + } + } +} diff --git a/SakuraIO/Datastore.cs b/SakuraIO/Datastore.cs new file mode 100644 index 0000000..89609ab --- /dev/null +++ b/SakuraIO/Datastore.cs @@ -0,0 +1,31 @@ +namespace SakuraIO +{ + class DatastoreClient + { + /// + /// Datastoreを叩くやつ + /// 現在未実装 + /// + + private string apiToken; + private string apiSecret; + + #region Constructor + public DatastoreClient() { } + + public DatastoreClient(string _apiToken, string _apiSecret) + { + SetAPIToken(_apiToken, _apiSecret); + } + #endregion + + + public void SetAPIToken(string _apiToken, string _apiSecret) + { + apiToken = _apiToken; + apiSecret = _apiSecret; + } + + + } +} diff --git a/SakuraIO/Message.cs b/SakuraIO/Message.cs new file mode 100644 index 0000000..9e88f4d --- /dev/null +++ b/SakuraIO/Message.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; + +namespace SakuraIO +{ + + public interface ISakuraIOMessage + { + string Module { get; set; } + string Type { get; set; } + string Datetime { get; set; } + } + + [DataContract] + public class ChannelMessage : ISakuraIOMessage + { + [DataMember(Name = "module")] + public string Module { get; set; } + [DataMember(Name = "type")] + public string Type { get; set; } + [DataMember(Name = "datetime")] + public string Datetime { get; set; } + [DataMember(Name = "payload")] + public Payload Payload { get; set; } + + public ChannelMessage() + { + Payload = new Payload(); + } + + public override string ToString() + { + + + return "[" + string.Join(", ", Payload.Channels.Select(x => $"{{{x.ToString()}}}")) + "]"; + } + } + + [DataContract] + public class Payload + { + [DataMember(Name = "channels")] + public List Channels { get; set; } + + public Payload() + { + Channels = new List(); + } + } + + [DataContract] + public class Channel + { + [DataMember(Name = "channel")] + public int ChannelID { get; set; } + [DataMember(Name = "type")] + public string Type { get; set; } + [DataMember(Name = "value")] + public object Value { get; set; } + public Int64 ValueInt + { + get + { + if (Type == "i" || Type == "I" || Type == "l" || Type == "L") + { + return Convert.ToInt64(Value); + } + return 0; + } + } + public double ValueFloat + { + get + { + if (Type == "d" || Type == "f") + { + return Convert.ToDouble(Value); + } + return 0.0; + } + } + /*public byte[] valueByte + { + get + { + if (type == "i" || type == "I") + { + return (int)value; + } + } + }*/ + + [DataMember(Name = "datetime")] + public string Datetime { get; set; } + + public dynamic GetValue() + { + if (Type == "i" || Type == "I" || Type == "l" || Type == "L") + { + return ValueInt; + } + if (Type == "d" || Type == "f") + { + return ValueFloat; + } + return null; + } + + public override string ToString() + { + return string.Format($"\"channel\": {ChannelID}, \"type\": {Type}, \"value\": {Value}"); + } + } +} diff --git a/SakuraIO/Module.cs b/SakuraIO/Module.cs new file mode 100644 index 0000000..9d042c9 --- /dev/null +++ b/SakuraIO/Module.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Text; + +namespace SakuraIO +{ + [DataContract] + public class Module + { + [DataMember(Name = "id")] + public string ID { get; set; } + [DataMember(Name = "name")] + public string Name { get; set; } + [DataMember(Name = "project")] + public int Project { get; set; } + [DataMember(Name = "is_online")] + public bool IsOnline { get; set; } + [DataMember(Name = "serial_number")] + public string SerialNumber { get; set; } + [DataMember(Name = "model")] + public string Model { get; set; } + [DataMember(Name = "firmware_version")] + public string FirmwareVersion { get; set; } + + public override string ToString() + { + return string.Format("{1} ({0})", ID, Name); + } + + } +} diff --git a/SakuraIO/Project.cs b/SakuraIO/Project.cs new file mode 100644 index 0000000..967041b --- /dev/null +++ b/SakuraIO/Project.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.Serialization; + +namespace SakuraIO +{ + [DataContract] + public class Project + { + [DataMember(Name = "id")] + public int ID { get; set; } + [DataMember(Name = "name")] + public string Name { get; set; } + [DataMember(Name = "options")] + public ProjectOptions Options { get; set; } + + public override string ToString() + { + return string.Format("{0}: \t{1}", ID, Name); + } + } + + [DataContract] + public class ProjectOptions + { + [DataMember(Name = "location")] + public bool Location { get; set; } + [DataMember(Name = "datastore")] + public string Datastore { get; set; } + } +} diff --git a/SakuraIO/Properties/AssemblyInfo.cs b/SakuraIO/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ca295ce --- /dev/null +++ b/SakuraIO/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 +// アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("SakuraIO")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SakuraIO")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから +// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("f1f3defa-bcc9-4d36-b71b-d6a44cd8d022")] + +// アセンブリのバージョン情報は次の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// Revision +// +// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます +// 以下のように '*' を使用します: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SakuraIO/SakuraIO.csproj b/SakuraIO/SakuraIO.csproj new file mode 100644 index 0000000..fa834dd --- /dev/null +++ b/SakuraIO/SakuraIO.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {F1F3DEFA-BCC9-4D36-B71B-D6A44CD8D022} + Library + Properties + SakuraIO + SakuraIO + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SakuraIO/Service.cs b/SakuraIO/Service.cs new file mode 100644 index 0000000..cc3a318 --- /dev/null +++ b/SakuraIO/Service.cs @@ -0,0 +1,20 @@ +using System.Runtime.Serialization; + +namespace SakuraIO +{ + [DataContract] + public class Service + { + [DataMember(Name = "id")] + public int ID { get; set; } + [DataMember(Name = "name")] + public string Name { get; set; } + [DataMember(Name = "type")] + public string Type { get; set; } + [DataMember(Name = "project")] + public int ProjectID { get; set; } + [DataMember(Name = "token")] + public string Token { get; set; } + } + +} diff --git a/SakuraIOTest.sln b/SakuraIOTest.sln new file mode 100644 index 0000000..f34e345 --- /dev/null +++ b/SakuraIOTest.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2003 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SakuraIOTest", "SakuraIOTest\SakuraIOTest.csproj", "{AE09B37F-E7EC-45E3-A546-BD0D8735E37C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SakuraIO", "SakuraIO\SakuraIO.csproj", "{F1F3DEFA-BCC9-4D36-B71B-D6A44CD8D022}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C8DA6AC0-DD66-43B3-B445-3AF807B2FCAF}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AE09B37F-E7EC-45E3-A546-BD0D8735E37C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE09B37F-E7EC-45E3-A546-BD0D8735E37C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE09B37F-E7EC-45E3-A546-BD0D8735E37C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE09B37F-E7EC-45E3-A546-BD0D8735E37C}.Release|Any CPU.Build.0 = Release|Any CPU + {F1F3DEFA-BCC9-4D36-B71B-D6A44CD8D022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1F3DEFA-BCC9-4D36-B71B-D6A44CD8D022}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1F3DEFA-BCC9-4D36-B71B-D6A44CD8D022}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1F3DEFA-BCC9-4D36-B71B-D6A44CD8D022}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6EC3A2BF-7007-428B-8AA6-0BDD5FA0C017} + EndGlobalSection +EndGlobal diff --git a/SakuraIOTest/App.config b/SakuraIOTest/App.config new file mode 100644 index 0000000..00bfd11 --- /dev/null +++ b/SakuraIOTest/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SakuraIOTest/DatastoreForm.Designer.cs b/SakuraIOTest/DatastoreForm.Designer.cs new file mode 100644 index 0000000..0dc608d --- /dev/null +++ b/SakuraIOTest/DatastoreForm.Designer.cs @@ -0,0 +1,96 @@ +namespace SakuraIOTest +{ + partial class DatastoreForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.listBox1 = new System.Windows.Forms.ListBox(); + this.button1 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // textBox1 + // + this.textBox1.Location = new System.Drawing.Point(12, 31); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(177, 19); + this.textBox1.TabIndex = 0; + this.textBox1.Text = "634d23be-b293-4589-b62b-e2269e666251"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(93, 12); + this.label1.TabIndex = 1; + this.label1.Text = "Datastore トークン"; + // + // listBox1 + // + this.listBox1.FormattingEnabled = true; + this.listBox1.ItemHeight = 12; + this.listBox1.Location = new System.Drawing.Point(256, 12); + this.listBox1.Name = "listBox1"; + this.listBox1.Size = new System.Drawing.Size(532, 424); + this.listBox1.TabIndex = 2; + // + // button1 + // + this.button1.Location = new System.Drawing.Point(12, 117); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(177, 23); + this.button1.TabIndex = 3; + this.button1.Text = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // DatastoreForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.button1); + this.Controls.Add(this.listBox1); + this.Controls.Add(this.label1); + this.Controls.Add(this.textBox1); + this.Name = "DatastoreForm"; + this.Text = "Datastore"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ListBox listBox1; + private System.Windows.Forms.Button button1; + } +} \ No newline at end of file diff --git a/SakuraIOTest/DatastoreForm.cs b/SakuraIOTest/DatastoreForm.cs new file mode 100644 index 0000000..5b79182 --- /dev/null +++ b/SakuraIOTest/DatastoreForm.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SakuraIOTest +{ + public partial class DatastoreForm : Form + { + public DatastoreForm() + { + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + //var url = "https://api.sakura.io/datastore/v1/channels?token=" + } + } +} diff --git a/SakuraIOTest/DatastoreForm.resx b/SakuraIOTest/DatastoreForm.resx new file mode 100644 index 0000000..29dcb1b --- /dev/null +++ b/SakuraIOTest/DatastoreForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SakuraIOTest/Form1.Designer.cs b/SakuraIOTest/Form1.Designer.cs new file mode 100644 index 0000000..6305d54 --- /dev/null +++ b/SakuraIOTest/Form1.Designer.cs @@ -0,0 +1,934 @@ +namespace SakuraIOTest +{ + partial class Form1 + { + /// + /// 必要なデザイナー変数です。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 使用中のリソースをすべてクリーンアップします。 + /// + /// マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows フォーム デザイナーで生成されたコード + + /// + /// デザイナー サポートに必要なメソッドです。このメソッドの内容を + /// コード エディターで変更しないでください。 + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.tokenBox = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.websocketConnectButton = new System.Windows.Forms.Button(); + this.jsonPreviewList = new System.Windows.Forms.ListBox(); + this.jsonMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); + this.jsonListCopyMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.statusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.typeBox = new System.Windows.Forms.ComboBox(); + this.channelNumBox = new System.Windows.Forms.NumericUpDown(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.valueBox = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.sendButton = new System.Windows.Forms.Button(); + this.label5 = new System.Windows.Forms.Label(); + this.targetModuleBox = new System.Windows.Forms.TextBox(); + this.getProjectListButton = new System.Windows.Forms.Button(); + this.label6 = new System.Windows.Forms.Label(); + this.apiTokenBox = new System.Windows.Forms.TextBox(); + this.apiSecretBox = new System.Windows.Forms.TextBox(); + this.label7 = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.projectListBox = new System.Windows.Forms.ComboBox(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.csvPreviewListView = new System.Windows.Forms.ListView(); + this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader4 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.csvListMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); + this.csvListCopyMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.exportRxButton = new System.Windows.Forms.Button(); + this.label10 = new System.Windows.Forms.Label(); + this.txQueueListView = new System.Windows.Forms.ListView(); + this.Channel = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.Type = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.Value = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.Datetime = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.exportTxButton = new System.Windows.Forms.Button(); + this.importTxButton = new System.Windows.Forms.Button(); + this.clearTxQueueButton = new System.Windows.Forms.Button(); + this.timeOffsetNumBox = new System.Windows.Forms.NumericUpDown(); + this.addTxDataButton = new System.Windows.Forms.Button(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.clearRxButton = new System.Windows.Forms.Button(); + this.keepaliveTypeCheckBox = new System.Windows.Forms.CheckBox(); + this.label12 = new System.Windows.Forms.Label(); + this.channelTypeCheckBox = new System.Windows.Forms.CheckBox(); + this.autoSetupWebSocketTokenButton = new System.Windows.Forms.Button(); + this.searchProjectFromWebSocketTokenButton = new System.Windows.Forms.Button(); + this.label11 = new System.Windows.Forms.Label(); + this.apiStateLabel = new System.Windows.Forms.Label(); + this.projectModuleList = new System.Windows.Forms.ComboBox(); + this.getModuleListButton = new System.Windows.Forms.Button(); + this.setTargetModuleButton = new System.Windows.Forms.Button(); + this.apiAuthButton = new System.Windows.Forms.Button(); + this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.button1 = new System.Windows.Forms.Button(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.linkLabel1 = new System.Windows.Forms.LinkLabel(); + this.jsonMenuStrip1.SuspendLayout(); + this.statusStrip1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.channelNumBox)).BeginInit(); + this.tabControl1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.csvListMenuStrip1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.timeOffsetNumBox)).BeginInit(); + this.groupBox3.SuspendLayout(); + this.groupBox4.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); + this.splitContainer2.Panel1.SuspendLayout(); + this.splitContainer2.Panel2.SuspendLayout(); + this.splitContainer2.SuspendLayout(); + this.SuspendLayout(); + // + // tokenBox + // + this.tokenBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tokenBox.Location = new System.Drawing.Point(127, 97); + this.tokenBox.Name = "tokenBox"; + this.tokenBox.Size = new System.Drawing.Size(217, 19); + this.tokenBox.TabIndex = 0; + // + // label1 + // + this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(8, 100); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(96, 12); + this.label1.TabIndex = 1; + this.label1.Text = "WebSocket Token"; + // + // websocketConnectButton + // + this.websocketConnectButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.websocketConnectButton.Location = new System.Drawing.Point(352, 95); + this.websocketConnectButton.Name = "websocketConnectButton"; + this.websocketConnectButton.Size = new System.Drawing.Size(125, 23); + this.websocketConnectButton.TabIndex = 2; + this.websocketConnectButton.Text = "接続"; + this.websocketConnectButton.UseVisualStyleBackColor = true; + this.websocketConnectButton.Click += new System.EventHandler(this.websocketConnectButton_Click); + // + // jsonPreviewList + // + this.jsonPreviewList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.jsonPreviewList.ContextMenuStrip = this.jsonMenuStrip1; + this.jsonPreviewList.FormattingEnabled = true; + this.jsonPreviewList.ItemHeight = 12; + this.jsonPreviewList.Location = new System.Drawing.Point(0, 0); + this.jsonPreviewList.Name = "jsonPreviewList"; + this.jsonPreviewList.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended; + this.jsonPreviewList.Size = new System.Drawing.Size(597, 136); + this.jsonPreviewList.TabIndex = 3; + // + // jsonMenuStrip1 + // + this.jsonMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.jsonListCopyMenuItem}); + this.jsonMenuStrip1.Name = "jsonMenuStrip1"; + this.jsonMenuStrip1.Size = new System.Drawing.Size(115, 26); + // + // jsonListCopyMenuItem + // + this.jsonListCopyMenuItem.Name = "jsonListCopyMenuItem"; + this.jsonListCopyMenuItem.Size = new System.Drawing.Size(114, 22); + this.jsonListCopyMenuItem.Text = "コピー(&C)"; + this.jsonListCopyMenuItem.Click += new System.EventHandler(this.jsonListCopyMenuItem_Click); + // + // statusStrip1 + // + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.statusLabel}); + this.statusStrip1.Location = new System.Drawing.Point(0, 539); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); + this.statusStrip1.Size = new System.Drawing.Size(1176, 22); + this.statusStrip1.TabIndex = 4; + this.statusStrip1.Text = "statusStrip1"; + // + // statusLabel + // + this.statusLabel.Name = "statusLabel"; + this.statusLabel.Size = new System.Drawing.Size(43, 17); + this.statusLabel.Text = "未接続"; + // + // typeBox + // + this.typeBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.typeBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.typeBox.FormattingEnabled = true; + this.typeBox.Items.AddRange(new object[] { + "int32", + "uint32", + "int64", + "uint64", + "float", + "double", + "byte[8]"}); + this.typeBox.Location = new System.Drawing.Point(118, 216); + this.typeBox.Name = "typeBox"; + this.typeBox.Size = new System.Drawing.Size(308, 20); + this.typeBox.TabIndex = 6; + // + // channelNumBox + // + this.channelNumBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.channelNumBox.Location = new System.Drawing.Point(118, 191); + this.channelNumBox.Maximum = new decimal(new int[] { + 127, + 0, + 0, + 0}); + this.channelNumBox.Name = "channelNumBox"; + this.channelNumBox.Size = new System.Drawing.Size(308, 19); + this.channelNumBox.TabIndex = 7; + // + // label2 + // + this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(7, 193); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(44, 12); + this.label2.TabIndex = 8; + this.label2.Text = "channel"; + // + // label3 + // + this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(8, 219); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(26, 12); + this.label3.TabIndex = 9; + this.label3.Text = "type"; + // + // valueBox + // + this.valueBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.valueBox.Location = new System.Drawing.Point(118, 243); + this.valueBox.Name = "valueBox"; + this.valueBox.Size = new System.Drawing.Size(308, 19); + this.valueBox.TabIndex = 10; + // + // label4 + // + this.label4.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(8, 245); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(32, 12); + this.label4.TabIndex = 11; + this.label4.Text = "value"; + // + // sendButton + // + this.sendButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.sendButton.Location = new System.Drawing.Point(433, 214); + this.sendButton.Name = "sendButton"; + this.sendButton.Size = new System.Drawing.Size(182, 23); + this.sendButton.TabIndex = 12; + this.sendButton.Text = "WebSocketで送信"; + this.sendButton.UseVisualStyleBackColor = true; + this.sendButton.Click += new System.EventHandler(this.sendButton_Click); + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(7, 21); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(98, 12); + this.label5.TabIndex = 13; + this.label5.Text = "送信先モジュールID"; + // + // targetModuleBox + // + this.targetModuleBox.Location = new System.Drawing.Point(129, 18); + this.targetModuleBox.Name = "targetModuleBox"; + this.targetModuleBox.Size = new System.Drawing.Size(188, 19); + this.targetModuleBox.TabIndex = 14; + // + // getProjectListButton + // + this.getProjectListButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.getProjectListButton.Enabled = false; + this.getProjectListButton.Location = new System.Drawing.Point(7, 124); + this.getProjectListButton.Name = "getProjectListButton"; + this.getProjectListButton.Size = new System.Drawing.Size(470, 23); + this.getProjectListButton.TabIndex = 15; + this.getProjectListButton.Text = "プロジェクト一覧取得"; + this.getProjectListButton.UseVisualStyleBackColor = true; + this.getProjectListButton.Click += new System.EventHandler(this.getProjectListButton_Click); + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(7, 20); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(58, 12); + this.label6.TabIndex = 16; + this.label6.Text = "API Token"; + // + // apiTokenBox + // + this.apiTokenBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.apiTokenBox.Location = new System.Drawing.Point(79, 17); + this.apiTokenBox.Name = "apiTokenBox"; + this.apiTokenBox.Size = new System.Drawing.Size(397, 19); + this.apiTokenBox.TabIndex = 17; + // + // apiSecretBox + // + this.apiSecretBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.apiSecretBox.Location = new System.Drawing.Point(79, 42); + this.apiSecretBox.Name = "apiSecretBox"; + this.apiSecretBox.Size = new System.Drawing.Size(397, 19); + this.apiSecretBox.TabIndex = 18; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(8, 45); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(38, 12); + this.label7.TabIndex = 19; + this.label7.Text = "Secret"; + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(8, 156); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(40, 12); + this.label8.TabIndex = 21; + this.label8.Text = "project"; + // + // label9 + // + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(8, 213); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(47, 12); + this.label9.TabIndex = 22; + this.label9.Text = "modules"; + // + // projectListBox + // + this.projectListBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.projectListBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.projectListBox.Enabled = false; + this.projectListBox.FormattingEnabled = true; + this.projectListBox.Location = new System.Drawing.Point(66, 153); + this.projectListBox.Name = "projectListBox"; + this.projectListBox.Size = new System.Drawing.Size(410, 20); + this.projectListBox.TabIndex = 23; + this.projectListBox.SelectedIndexChanged += new System.EventHandler(this.projectListBox_SelectedIndexChanged); + // + // tabControl1 + // + this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Location = new System.Drawing.Point(7, 18); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(612, 172); + this.tabControl1.TabIndex = 24; + // + // tabPage2 + // + this.tabPage2.Controls.Add(this.csvPreviewListView); + this.tabPage2.Location = new System.Drawing.Point(4, 22); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(604, 146); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "CSV"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // csvPreviewListView + // + this.csvPreviewListView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.csvPreviewListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeader1, + this.columnHeader2, + this.columnHeader3, + this.columnHeader4}); + this.csvPreviewListView.ContextMenuStrip = this.csvListMenuStrip1; + this.csvPreviewListView.FullRowSelect = true; + this.csvPreviewListView.Location = new System.Drawing.Point(0, 0); + this.csvPreviewListView.Name = "csvPreviewListView"; + this.csvPreviewListView.Size = new System.Drawing.Size(604, 143); + this.csvPreviewListView.TabIndex = 29; + this.csvPreviewListView.UseCompatibleStateImageBehavior = false; + this.csvPreviewListView.View = System.Windows.Forms.View.Details; + // + // columnHeader1 + // + this.columnHeader1.Text = "Channel"; + // + // columnHeader2 + // + this.columnHeader2.Text = "Type"; + // + // columnHeader3 + // + this.columnHeader3.Text = "Value"; + this.columnHeader3.Width = 113; + // + // columnHeader4 + // + this.columnHeader4.Text = "Datetime"; + this.columnHeader4.Width = 179; + // + // csvListMenuStrip1 + // + this.csvListMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.csvListCopyMenuItem}); + this.csvListMenuStrip1.Name = "csvListMenuStrip1"; + this.csvListMenuStrip1.Size = new System.Drawing.Size(115, 26); + // + // csvListCopyMenuItem + // + this.csvListCopyMenuItem.Name = "csvListCopyMenuItem"; + this.csvListCopyMenuItem.Size = new System.Drawing.Size(114, 22); + this.csvListCopyMenuItem.Text = "コピー(&C)"; + this.csvListCopyMenuItem.Click += new System.EventHandler(this.listCopyMenuItem_Click); + // + // tabPage1 + // + this.tabPage1.Controls.Add(this.jsonPreviewList); + this.tabPage1.Location = new System.Drawing.Point(4, 22); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3); + this.tabPage1.Size = new System.Drawing.Size(604, 146); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "JSON"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // exportRxButton + // + this.exportRxButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.exportRxButton.Enabled = false; + this.exportRxButton.Location = new System.Drawing.Point(297, 189); + this.exportRxButton.Name = "exportRxButton"; + this.exportRxButton.Size = new System.Drawing.Size(227, 23); + this.exportRxButton.TabIndex = 25; + this.exportRxButton.Text = "エクスポート"; + this.exportRxButton.UseVisualStyleBackColor = true; + // + // label10 + // + this.label10.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(8, 270); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(88, 12); + this.label10.TabIndex = 26; + this.label10.Text = "time offset (ms)"; + // + // txQueueListView + // + this.txQueueListView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txQueueListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.Channel, + this.Type, + this.Value, + this.Datetime}); + this.txQueueListView.FullRowSelect = true; + this.txQueueListView.Location = new System.Drawing.Point(7, 43); + this.txQueueListView.Name = "txQueueListView"; + this.txQueueListView.Size = new System.Drawing.Size(607, 139); + this.txQueueListView.TabIndex = 28; + this.txQueueListView.UseCompatibleStateImageBehavior = false; + this.txQueueListView.View = System.Windows.Forms.View.Details; + // + // Channel + // + this.Channel.Text = "Channel"; + // + // Type + // + this.Type.Text = "Type"; + // + // Value + // + this.Value.Text = "Value"; + // + // Datetime + // + this.Datetime.Text = "Datetime"; + this.Datetime.Width = 203; + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox2.Controls.Add(this.exportTxButton); + this.groupBox2.Controls.Add(this.importTxButton); + this.groupBox2.Controls.Add(this.label5); + this.groupBox2.Controls.Add(this.targetModuleBox); + this.groupBox2.Controls.Add(this.clearTxQueueButton); + this.groupBox2.Controls.Add(this.sendButton); + this.groupBox2.Controls.Add(this.timeOffsetNumBox); + this.groupBox2.Controls.Add(this.addTxDataButton); + this.groupBox2.Controls.Add(this.txQueueListView); + this.groupBox2.Controls.Add(this.typeBox); + this.groupBox2.Controls.Add(this.label2); + this.groupBox2.Controls.Add(this.label10); + this.groupBox2.Controls.Add(this.channelNumBox); + this.groupBox2.Controls.Add(this.label3); + this.groupBox2.Controls.Add(this.label4); + this.groupBox2.Controls.Add(this.valueBox); + this.groupBox2.Location = new System.Drawing.Point(3, 3); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(629, 294); + this.groupBox2.TabIndex = 29; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "送信メッセージキュー"; + // + // exportTxButton + // + this.exportTxButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.exportTxButton.Enabled = false; + this.exportTxButton.Location = new System.Drawing.Point(528, 240); + this.exportTxButton.Name = "exportTxButton"; + this.exportTxButton.Size = new System.Drawing.Size(87, 23); + this.exportTxButton.TabIndex = 33; + this.exportTxButton.Text = "エクスポート"; + this.exportTxButton.UseVisualStyleBackColor = true; + // + // importTxButton + // + this.importTxButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.importTxButton.Enabled = false; + this.importTxButton.Location = new System.Drawing.Point(433, 240); + this.importTxButton.Name = "importTxButton"; + this.importTxButton.Size = new System.Drawing.Size(87, 23); + this.importTxButton.TabIndex = 32; + this.importTxButton.Text = "インポート"; + this.importTxButton.UseVisualStyleBackColor = true; + // + // clearTxQueueButton + // + this.clearTxQueueButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.clearTxQueueButton.Location = new System.Drawing.Point(433, 265); + this.clearTxQueueButton.Name = "clearTxQueueButton"; + this.clearTxQueueButton.Size = new System.Drawing.Size(182, 23); + this.clearTxQueueButton.TabIndex = 31; + this.clearTxQueueButton.Text = "送信メッセージキューをクリア"; + this.clearTxQueueButton.UseVisualStyleBackColor = true; + this.clearTxQueueButton.Click += new System.EventHandler(this.clearTxQueueButton_Click); + // + // timeOffsetNumBox + // + this.timeOffsetNumBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.timeOffsetNumBox.Location = new System.Drawing.Point(118, 268); + this.timeOffsetNumBox.Maximum = new decimal(new int[] { + 10000000, + 0, + 0, + 0}); + this.timeOffsetNumBox.Name = "timeOffsetNumBox"; + this.timeOffsetNumBox.Size = new System.Drawing.Size(308, 19); + this.timeOffsetNumBox.TabIndex = 30; + // + // addTxDataButton + // + this.addTxDataButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.addTxDataButton.Location = new System.Drawing.Point(433, 188); + this.addTxDataButton.Name = "addTxDataButton"; + this.addTxDataButton.Size = new System.Drawing.Size(182, 23); + this.addTxDataButton.TabIndex = 29; + this.addTxDataButton.Text = "データをキューに追加"; + this.addTxDataButton.UseVisualStyleBackColor = true; + this.addTxDataButton.Click += new System.EventHandler(this.addTxDataButton_Click); + // + // groupBox3 + // + this.groupBox3.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox3.Controls.Add(this.exportRxButton); + this.groupBox3.Controls.Add(this.tabControl1); + this.groupBox3.Controls.Add(this.clearRxButton); + this.groupBox3.Location = new System.Drawing.Point(3, 303); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.Size = new System.Drawing.Size(629, 218); + this.groupBox3.TabIndex = 27; + this.groupBox3.TabStop = false; + this.groupBox3.Text = "受信したメッセージ"; + // + // clearRxButton + // + this.clearRxButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.clearRxButton.Location = new System.Drawing.Point(7, 189); + this.clearRxButton.Name = "clearRxButton"; + this.clearRxButton.Size = new System.Drawing.Size(284, 23); + this.clearRxButton.TabIndex = 26; + this.clearRxButton.Text = "受信データクリア"; + this.clearRxButton.UseVisualStyleBackColor = true; + this.clearRxButton.Click += new System.EventHandler(this.clearRxButton_Click); + // + // keepaliveTypeCheckBox + // + this.keepaliveTypeCheckBox.AutoSize = true; + this.keepaliveTypeCheckBox.Enabled = false; + this.keepaliveTypeCheckBox.Location = new System.Drawing.Point(98, 326); + this.keepaliveTypeCheckBox.Name = "keepaliveTypeCheckBox"; + this.keepaliveTypeCheckBox.Size = new System.Drawing.Size(72, 16); + this.keepaliveTypeCheckBox.TabIndex = 41; + this.keepaliveTypeCheckBox.Text = "keepalive"; + this.keepaliveTypeCheckBox.UseVisualStyleBackColor = true; + // + // label12 + // + this.label12.AutoSize = true; + this.label12.Location = new System.Drawing.Point(8, 308); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(132, 12); + this.label12.TabIndex = 40; + this.label12.Text = "受信対象のメッセージタイプ"; + // + // channelTypeCheckBox + // + this.channelTypeCheckBox.AutoSize = true; + this.channelTypeCheckBox.Checked = true; + this.channelTypeCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; + this.channelTypeCheckBox.Enabled = false; + this.channelTypeCheckBox.Location = new System.Drawing.Point(10, 326); + this.channelTypeCheckBox.Name = "channelTypeCheckBox"; + this.channelTypeCheckBox.Size = new System.Drawing.Size(69, 16); + this.channelTypeCheckBox.TabIndex = 39; + this.channelTypeCheckBox.Text = "channels"; + this.channelTypeCheckBox.UseVisualStyleBackColor = true; + // + // autoSetupWebSocketTokenButton + // + this.autoSetupWebSocketTokenButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.autoSetupWebSocketTokenButton.Location = new System.Drawing.Point(7, 177); + this.autoSetupWebSocketTokenButton.Name = "autoSetupWebSocketTokenButton"; + this.autoSetupWebSocketTokenButton.Size = new System.Drawing.Size(470, 23); + this.autoSetupWebSocketTokenButton.TabIndex = 38; + this.autoSetupWebSocketTokenButton.Text = "WebSocket Token自動設定"; + this.autoSetupWebSocketTokenButton.UseVisualStyleBackColor = true; + this.autoSetupWebSocketTokenButton.Click += new System.EventHandler(this.autoSetupWebSocketTokenButton_Click); + // + // searchProjectFromWebSocketTokenButton + // + this.searchProjectFromWebSocketTokenButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.searchProjectFromWebSocketTokenButton.Location = new System.Drawing.Point(7, 282); + this.searchProjectFromWebSocketTokenButton.Name = "searchProjectFromWebSocketTokenButton"; + this.searchProjectFromWebSocketTokenButton.Size = new System.Drawing.Size(469, 23); + this.searchProjectFromWebSocketTokenButton.TabIndex = 36; + this.searchProjectFromWebSocketTokenButton.Text = "WebSocket Tokenからプロジェクト取得"; + this.searchProjectFromWebSocketTokenButton.UseVisualStyleBackColor = true; + this.searchProjectFromWebSocketTokenButton.Click += new System.EventHandler(this.searchProjectFromWebSocketTokenButton_Click); + // + // label11 + // + this.label11.AutoSize = true; + this.label11.Location = new System.Drawing.Point(8, 71); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(53, 12); + this.label11.TabIndex = 31; + this.label11.Text = "API state"; + // + // apiStateLabel + // + this.apiStateLabel.AutoSize = true; + this.apiStateLabel.Location = new System.Drawing.Point(77, 71); + this.apiStateLabel.Name = "apiStateLabel"; + this.apiStateLabel.Size = new System.Drawing.Size(41, 12); + this.apiStateLabel.TabIndex = 32; + this.apiStateLabel.Text = "未認証"; + // + // projectModuleList + // + this.projectModuleList.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.projectModuleList.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.projectModuleList.Enabled = false; + this.projectModuleList.FormattingEnabled = true; + this.projectModuleList.Location = new System.Drawing.Point(66, 208); + this.projectModuleList.Name = "projectModuleList"; + this.projectModuleList.Size = new System.Drawing.Size(205, 20); + this.projectModuleList.TabIndex = 33; + // + // getModuleListButton + // + this.getModuleListButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.getModuleListButton.Enabled = false; + this.getModuleListButton.Location = new System.Drawing.Point(279, 206); + this.getModuleListButton.Name = "getModuleListButton"; + this.getModuleListButton.Size = new System.Drawing.Size(198, 23); + this.getModuleListButton.TabIndex = 34; + this.getModuleListButton.Text = "モジュール一覧取得"; + this.getModuleListButton.UseVisualStyleBackColor = true; + this.getModuleListButton.Click += new System.EventHandler(this.getModuleListButton_Click); + // + // setTargetModuleButton + // + this.setTargetModuleButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.setTargetModuleButton.Enabled = false; + this.setTargetModuleButton.Location = new System.Drawing.Point(7, 234); + this.setTargetModuleButton.Name = "setTargetModuleButton"; + this.setTargetModuleButton.Size = new System.Drawing.Size(470, 23); + this.setTargetModuleButton.TabIndex = 35; + this.setTargetModuleButton.Text = "送信対象モジュールとして設定"; + this.setTargetModuleButton.UseVisualStyleBackColor = true; + this.setTargetModuleButton.Click += new System.EventHandler(this.setTargetModuleButton_Click); + // + // apiAuthButton + // + this.apiAuthButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.apiAuthButton.Location = new System.Drawing.Point(390, 66); + this.apiAuthButton.Name = "apiAuthButton"; + this.apiAuthButton.Size = new System.Drawing.Size(87, 23); + this.apiAuthButton.TabIndex = 37; + this.apiAuthButton.Text = "認証"; + this.apiAuthButton.UseVisualStyleBackColor = true; + this.apiAuthButton.Click += new System.EventHandler(this.apiAuthButton_Click); + // + // groupBox4 + // + this.groupBox4.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox4.Controls.Add(this.linkLabel1); + this.groupBox4.Controls.Add(this.button1); + this.groupBox4.Controls.Add(this.keepaliveTypeCheckBox); + this.groupBox4.Controls.Add(this.label12); + this.groupBox4.Controls.Add(this.label6); + this.groupBox4.Controls.Add(this.channelTypeCheckBox); + this.groupBox4.Controls.Add(this.getProjectListButton); + this.groupBox4.Controls.Add(this.tokenBox); + this.groupBox4.Controls.Add(this.apiTokenBox); + this.groupBox4.Controls.Add(this.label1); + this.groupBox4.Controls.Add(this.websocketConnectButton); + this.groupBox4.Controls.Add(this.autoSetupWebSocketTokenButton); + this.groupBox4.Controls.Add(this.searchProjectFromWebSocketTokenButton); + this.groupBox4.Controls.Add(this.apiSecretBox); + this.groupBox4.Controls.Add(this.apiAuthButton); + this.groupBox4.Controls.Add(this.label7); + this.groupBox4.Controls.Add(this.label8); + this.groupBox4.Controls.Add(this.setTargetModuleButton); + this.groupBox4.Controls.Add(this.label9); + this.groupBox4.Controls.Add(this.getModuleListButton); + this.groupBox4.Controls.Add(this.projectListBox); + this.groupBox4.Controls.Add(this.projectModuleList); + this.groupBox4.Controls.Add(this.label11); + this.groupBox4.Controls.Add(this.apiStateLabel); + this.groupBox4.Location = new System.Drawing.Point(3, 3); + this.groupBox4.Name = "groupBox4"; + this.groupBox4.Size = new System.Drawing.Size(491, 518); + this.groupBox4.TabIndex = 41; + this.groupBox4.TabStop = false; + this.groupBox4.Text = "API関連設定"; + // + // button1 + // + this.button1.Enabled = false; + this.button1.Location = new System.Drawing.Point(9, 380); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(335, 23); + this.button1.TabIndex = 42; + this.button1.Text = "Datastore tools"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // splitContainer2 + // + this.splitContainer2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.splitContainer2.Location = new System.Drawing.Point(14, 12); + this.splitContainer2.Name = "splitContainer2"; + // + // splitContainer2.Panel1 + // + this.splitContainer2.Panel1.Controls.Add(this.groupBox4); + this.splitContainer2.Panel1MinSize = 0; + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.groupBox2); + this.splitContainer2.Panel2.Controls.Add(this.groupBox3); + this.splitContainer2.Panel2MinSize = 0; + this.splitContainer2.Size = new System.Drawing.Size(1148, 524); + this.splitContainer2.SplitterDistance = 505; + this.splitContainer2.SplitterWidth = 5; + this.splitContainer2.TabIndex = 42; + // + // linkLabel1 + // + this.linkLabel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.linkLabel1.AutoSize = true; + this.linkLabel1.Location = new System.Drawing.Point(273, 71); + this.linkLabel1.Name = "linkLabel1"; + this.linkLabel1.Size = new System.Drawing.Size(111, 12); + this.linkLabel1.TabIndex = 43; + this.linkLabel1.TabStop = true; + this.linkLabel1.Text = "API Token取得ページ"; + this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1176, 561); + this.Controls.Add(this.splitContainer2); + this.Controls.Add(this.statusStrip1); + this.MinimumSize = new System.Drawing.Size(800, 300); + this.Name = "Form1"; + this.Text = "sakura.io Test"; + this.jsonMenuStrip1.ResumeLayout(false); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.channelNumBox)).EndInit(); + this.tabControl1.ResumeLayout(false); + this.tabPage2.ResumeLayout(false); + this.csvListMenuStrip1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.timeOffsetNumBox)).EndInit(); + this.groupBox3.ResumeLayout(false); + this.groupBox4.ResumeLayout(false); + this.groupBox4.PerformLayout(); + this.splitContainer2.Panel1.ResumeLayout(false); + this.splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); + this.splitContainer2.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox tokenBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button websocketConnectButton; + private System.Windows.Forms.ListBox jsonPreviewList; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel statusLabel; + private System.Windows.Forms.ComboBox typeBox; + private System.Windows.Forms.NumericUpDown channelNumBox; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox valueBox; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Button sendButton; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TextBox targetModuleBox; + private System.Windows.Forms.Button getProjectListButton; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.TextBox apiTokenBox; + private System.Windows.Forms.TextBox apiSecretBox; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.ComboBox projectListBox; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.Button exportRxButton; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.ListView txQueueListView; + private System.Windows.Forms.ColumnHeader Channel; + private System.Windows.Forms.ColumnHeader Type; + private System.Windows.Forms.ColumnHeader Value; + private System.Windows.Forms.ColumnHeader Datetime; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.Label apiStateLabel; + private System.Windows.Forms.ComboBox projectModuleList; + private System.Windows.Forms.Button getModuleListButton; + private System.Windows.Forms.Button setTargetModuleButton; + private System.Windows.Forms.Button searchProjectFromWebSocketTokenButton; + private System.Windows.Forms.Button apiAuthButton; + private System.Windows.Forms.Button autoSetupWebSocketTokenButton; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Button addTxDataButton; + private System.Windows.Forms.NumericUpDown timeOffsetNumBox; + private System.Windows.Forms.Button clearTxQueueButton; + private System.Windows.Forms.Button clearRxButton; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.ListView csvPreviewListView; + private System.Windows.Forms.ColumnHeader columnHeader1; + private System.Windows.Forms.ColumnHeader columnHeader2; + private System.Windows.Forms.ColumnHeader columnHeader3; + private System.Windows.Forms.ColumnHeader columnHeader4; + private System.Windows.Forms.ContextMenuStrip csvListMenuStrip1; + private System.Windows.Forms.ToolStripMenuItem csvListCopyMenuItem; + private System.Windows.Forms.ContextMenuStrip jsonMenuStrip1; + private System.Windows.Forms.ToolStripMenuItem jsonListCopyMenuItem; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.CheckBox channelTypeCheckBox; + private System.Windows.Forms.CheckBox keepaliveTypeCheckBox; + private System.Windows.Forms.SplitContainer splitContainer2; + private System.Windows.Forms.Button importTxButton; + private System.Windows.Forms.Button exportTxButton; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.LinkLabel linkLabel1; + } +} + diff --git a/SakuraIOTest/Form1.cs b/SakuraIOTest/Form1.cs new file mode 100644 index 0000000..f3a900c --- /dev/null +++ b/SakuraIOTest/Form1.cs @@ -0,0 +1,500 @@ +using SakuraIO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; +using static System.Windows.Forms.ListViewItem; + +namespace SakuraIOTest +{ + + + public partial class Form1 : Form + { + Client client = new Client(); + List ProjectList; + List ModuleList; + + public Form1() + { + InitializeComponent(); + typeBox.SelectedIndex = 0; + } + + #region UI制御関連 + private void EnableWebSocketUIControls() + { + //sendButton.Enabled = true; + } + + private void EnableAPIClientControls() + { + getProjectListButton.Enabled = true; + getModuleListButton.Enabled = false; + setTargetModuleButton.Enabled = false; + projectModuleList.Items.Clear(); + projectModuleList.Enabled = false; + projectListBox.Items.Clear(); + projectListBox.Enabled = false; + } + + private void listBox2_SelectedIndexChanged(object sender, EventArgs e) + { + targetModuleBox.Text = projectModuleList.SelectedItem.ToString(); + } + + private void setTargetModuleButton_Click(object sender, EventArgs e) + { + var targetModule = ModuleList[projectModuleList.SelectedIndex]; + targetModuleBox.Text = targetModule.ID; + MessageBox.Show(string.Format("送信先モジュールIDを以下に設定しました\nID: {0}\nモジュール名: {1}", targetModule.ID, targetModule.Name), + "送信先設定完了", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + + private void jsonListCopyMenuItem_Click(object sender, EventArgs e) + { + var lv = jsonPreviewList.SelectedItems.Cast().Aggregate((a, b) => string.Format("{0}, {1}", a, b)); + Clipboard.SetText(string.Format("[{0}]", lv)); + } + + + private void clearRxButton_Click(object sender, EventArgs e) + { + csvPreviewListView.Items.Clear(); + jsonPreviewList.Items.Clear(); + } + + private void button1_Click(object sender, EventArgs e) + { + var form = new DatastoreForm(); + form.Show(); + } + + private void projectListBox_SelectedIndexChanged(object sender, EventArgs e) + { + projectModuleList.Items.Clear(); + projectModuleList.Enabled = false; + setTargetModuleButton.Enabled = false; + } + + private void clearTxQueueButton_Click(object sender, EventArgs e) + { + var result = MessageBox.Show("送信メッセージキューの内容をすべて削除しますか?", + "確認", + MessageBoxButtons.YesNo, + MessageBoxIcon.Exclamation); + + if (result == DialogResult.No) + { + return; + } + + txQueueListView.Items.Clear(); + } + + private void listCopyMenuItem_Click(object sender, EventArgs e) + { + var lv = csvPreviewListView.SelectedItems.Cast(); + try + { + var items = lv.Select(x => x.SubItems.Cast().Select(y => y.Text).Aggregate((a, b) => string.Format("{0}, {1}", a, b))) + .Aggregate((a, b) => string.Format("{0}\n{1}", a, b)); + Clipboard.SetText(items); + } + catch (InvalidOperationException) + { + return; + } + } + #endregion + + private async void websocketConnectButton_Click(object sender, EventArgs e) + { + if (client.IsWebSocketOpened) + { + // close + await client.CloseWebSocket(); + statusLabel.Text = "接続を切断しました"; + websocketConnectButton.Text = "接続"; + return; + } + client.SetWebSocketToken(tokenBox.Text); + statusLabel.Text = "接続試行中"; + try + { + await client.ConnectWebSocket(msg => + { + Invoke((MethodInvoker)delegate + { + // channelsメッセージを受信したらListViewに追加 + if (channelTypeCheckBox.Checked && msg.Type == "channels") + { + + jsonPreviewList.Items.Add(msg.ToString()); + jsonPreviewList.TopIndex = jsonPreviewList.Items.Count - 1; + + if (msg.Type == "channels") + { + var cmsg = (ChannelMessage)msg; + + foreach (var channel in cmsg.Payload.Channels) + { + var listItem = new ListViewItem(new string[] { + channel.ChannelID.ToString(), channel.Type, channel.GetValue().ToString(), channel.Datetime }); + csvPreviewListView.Items.Add(listItem); + } + + } + } + // TODO: ToStringで刺さるのを直す + //else if (keepaliveTypeCheckBox.Checked && msg.Type == "keepalive") + //{ + // jsonPreviewList.Items.Add(msg.ToString()); + // jsonPreviewList.TopIndex = jsonPreviewList.Items.Count - 1; + //} + + }); + + }, issuccessed => + { + Invoke((MethodInvoker)delegate + { + statusLabel.Text = "通信中"; + websocketConnectButton.Text = "切断"; + EnableWebSocketUIControls(); + }); + }); + + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "WebSocket エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private async void sendButton_Click(object sender, EventArgs e) + { + + if (!client.IsWebSocketOpened) + { + MessageBox.Show("\"WebSocket設定\"でWebSocketトークンを入力し、接続を行ってください", + "WebSocketが接続されていません", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + return; + } + + // 送信先 + ChannelMessage channelMessage = new ChannelMessage(); + channelMessage.Module = targetModuleBox.Text; + channelMessage.Datetime = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzzz"); + channelMessage.Type = "channels"; + + var channels = txQueueListView.Items.Cast() + .Select((x) => + { + return new Channel + { + ChannelID = int.Parse(x.SubItems[0].Text), + Type = typeNameToIndicator(x.SubItems[1].Text), + Value = x.SubItems[2].Text, + Datetime = x.SubItems[3].Text + }; + }) + .ToList(); + + + + channelMessage.Payload.Channels.AddRange(channels); + + + statusLabel.Text = "メッセージ送信中"; + await client.SendWebSocket(channelMessage); + + statusLabel.Text = "メッセージ送信完了"; + jsonPreviewList.Items.Add("------- send message"); + } + + private async void getModuleListButton_Click(object sender, EventArgs e) + { + if (!CheckAPIAuthenticated()) + { + return; + } + + if (projectListBox.SelectedIndex == -1 || projectListBox.Items.Count == 0) + { + MessageBox.Show("プロジェクトが選択されていません", + "プロジェクトを選択してください", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return; + } + var targetProject = ProjectList[projectListBox.SelectedIndex]; + + statusLabel.Text = "モジュール検索中"; + ModuleList = await client.GetModulesFromProjectID(targetProject.ID); + statusLabel.Text = "完了"; + projectModuleList.Items.Clear(); + projectModuleList.Items.AddRange(ModuleList.ToArray()); + if (ModuleList.Count() == 0) + { + MessageBox.Show("プロジェクトに登録されているモジュールが見つかりませんでした", + "プロジェクトにモジュールが登録されていません", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + return; + } + projectModuleList.SelectedIndex = 0; + projectModuleList.Enabled = true; + setTargetModuleButton.Enabled = true; + } + + private async void apiAuthButton_Click(object sender, EventArgs e) + { + try + { + client.SetAPIToken(apiTokenBox.Text, apiSecretBox.Text); + await client.AuthenticateAPI(); + apiStateLabel.Text = "認証済み"; + EnableAPIClientControls(); + } + catch + { + MessageBox.Show("APIの認証に失敗しました API TokenとSecretを確認してください", "API認証失敗", MessageBoxButtons.OK, MessageBoxIcon.Stop); + } + + } + + private async void getProjectListButton_Click(object sender, EventArgs e) + { + if (!CheckAPIAuthenticated()) + { + return; + } + + statusLabel.Text = "プロジェクト取得中"; + client.SetAPIToken(apiTokenBox.Text, apiSecretBox.Text); + + ProjectList = await client.GetProjects(); + projectListBox.Items.Clear(); + projectListBox.Items.AddRange(ProjectList.ToArray()); + projectListBox.SelectedIndex = 0; + projectListBox.Enabled = true; + getModuleListButton.Enabled = true; + projectModuleList.Enabled = false; + setTargetModuleButton.Enabled = false; + statusLabel.Text = "完了"; + } + + private async void autoSetupWebSocketTokenButton_Click(object sender, EventArgs e) + { + if (!CheckAPIAuthenticated()) + { + return; + } + + if (ProjectList == null || ProjectList.Count() == 0) + { + MessageBox.Show("プロジェクトが見つかりませんでした", + "プロジェクトが見つかりませんでした", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + return; + } + + var result = MessageBox.Show("自動設定を行いますか?\n\n● 送信対象モジュールを含むプロジェクトに関連付いているWebSocket連携サービスのうち1つを自動選択し、本プログラム内で使用できるよう設定します\n● プロジェクト内にWebSocket連携サービスが含まれない場合、自動的にWebSocket連携サービスを作成します", + "自動設定確認", + MessageBoxButtons.YesNo); + + if (result == DialogResult.No) + { + return; + } + + var project = ProjectList[projectListBox.SelectedIndex]; + var websocket_services = (await client.GetServicesFromProject(project)).Where(x => x.Type == "websocket").ToList(); + if (websocket_services.Count() > 0) + { + // WebSocket連携サービスが見つかったので適当に選んで使う + tokenBox.Text = websocket_services[0].Token; + MessageBox.Show("以下の連携サービスを使用するように設定しました\n" + string.Format("名前: {0}\nToken: {1}", websocket_services[0].Name, websocket_services[0].Token)); + return; + } + + // 連携サービスが見つからなかったので自動作成する + var resp = await client.CreateService(project.ID, "sakura.io Testで自動設定された連携サービス", "websocket"); + tokenBox.Text = resp.Token; + MessageBox.Show("連携サービスを作成しました\n" + $"連携サービス名: {resp.Name}\nWebSocket Token: {resp.Token}\n\n作成した連携サービスのトークンをWebSocket Token欄にコピーしました"); + + } + + private void addTxDataButton_Click(object sender, EventArgs e) + { + // TimeSpanは100ns単位 + var datetime = DateTime.Now - new TimeSpan((long)(timeOffsetNumBox.Value) * 10000); + + if (valueBox.Text.Length == 0) + { + return; + } + + switch (typeBox.SelectedText) + { + case "float": + if (!float.TryParse(valueBox.Text, out float _)) + { + MessageBox.Show("valueが単精度浮動小数点数ではありません"); + return; + } + break; + case "double": + if (!double.TryParse(valueBox.Text, out double _)) + { + MessageBox.Show("valueが倍精度浮動小数点数ではありません"); + return; + } + break; + case "int32": + if (!int.TryParse(valueBox.Text, out int _)) + { + MessageBox.Show("valueが32bit 符号あり整数ではありません"); + return; + } + break; + case "uint32": + if (!uint.TryParse(valueBox.Text, out uint _)) + { + MessageBox.Show("valueが32bit 符号なし整数ではありません"); + return; + } + break; + case "int64": + if (!long.TryParse(valueBox.Text, out long _)) + { + MessageBox.Show("valueが64bit 符号あり整数ではありません"); + return; + } + break; + case "uint64": + if (!ulong.TryParse(valueBox.Text, out ulong _)) + { + MessageBox.Show("valueが64bit 符号なし整数ではありません"); + return; + } + break; + case "byte[8]": + MessageBox.Show("未実装です"); + return; + } + + var txData = new ListViewItem(new string[] { + channelNumBox.Value.ToString(), typeBox.Text, valueBox.Text, datetime.ToString("yyyy-MM-ddTHH:mm:ss.fffzzzz") }); + txQueueListView.Items.Add(txData); + } + + private async void searchProjectFromWebSocketTokenButton_Click(object sender, EventArgs e) + { + if (!CheckAPIAuthenticated()) + { + return; + } + + var result = MessageBox.Show("WebSocket Tokenを基にプロジェクト検索を行いますか?", + "自動設定確認", + MessageBoxButtons.YesNo, + MessageBoxIcon.Information); + + if (result == DialogResult.No) + { + return; + } + + statusLabel.Text = "プロジェクト取得中"; + client.SetAPIToken(apiTokenBox.Text, apiSecretBox.Text); + + ProjectList = await client.GetProjects(); + projectListBox.Items.Clear(); + projectListBox.Items.AddRange(ProjectList.ToArray()); + projectListBox.Enabled = true; + getModuleListButton.Enabled = true; + projectModuleList.Enabled = false; + setTargetModuleButton.Enabled = false; + + try + { + var tokenID = await client.GetProjectIDFromServiceToken(tokenBox.Text); + var project = ProjectList.Where(x => x.ID == tokenID).Single(); + projectListBox.SelectedIndex = projectListBox.Items.IndexOf(project); + MessageBox.Show("以下のプロジェクトが見つかりました:\n" + string.Format("{0}: {1}", project.ID, project.Name), + "検索完了", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + catch (InvalidOperationException) + { + projectListBox.SelectedIndex = 0; + MessageBox.Show("WebSocket Tokenが所属しているプロジェクトが見つかりませんでした\n以下のような問題がないか確認してもう一度試してください\n\n● WebSocket Tokenの入力ミス\n● WebSocket TokenとAPI Tokenが別アカウントのものである", + "WebSocket Tokenが所属しているプロジェクトが見つかりませんでした", + MessageBoxButtons.OK, + MessageBoxIcon.Stop); + } + + statusLabel.Text = "完了"; + } + + #region utils + + /// + /// APIの認証状態をチェックして認証されていなければダイアログを表示 + /// + /// 認証済みか否か + private bool CheckAPIAuthenticated() + { + if (!client.IsAPIAuthenticated) + { + MessageBox.Show("API認証が行われていません\nAPI TokenとSecretを入力し、「認証」ボタンを押して認証を行ってから再度試してください", + "API認証が行われていません", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + return false; + } + + return true; + } + + private string typeNameToIndicator(string typename) + { + switch (typename) + { + case "int32": + return "i"; + case "uint32": + return "I"; + case "int64": + return "l"; + case "uint64": + return "L"; + case "float": + return "f"; + case "double": + return "d"; + } + return ""; + } + #endregion + + private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + if (MessageBox.Show("ブラウザでAPI Token取得ページを開きますか?\n\nトークンをまだ作成していない場合、「APIキー追加」をクリックし、権限を「全権限」に設定したトークンを作成してください。\nログインを要求された場合、sakura.ioを使用している会員IDでログインを行うと一覧が確認できます。", "トークン取得", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.No) + { + return; + } + System.Diagnostics.Process.Start("https://secure.sakura.ad.jp/iot/console/#/apikeys"); + } + } + + +} diff --git a/SakuraIOTest/Form1.resx b/SakuraIOTest/Form1.resx new file mode 100644 index 0000000..60d5054 --- /dev/null +++ b/SakuraIOTest/Form1.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 283, 17 + + + 17, 17 + + + 133, 17 + + \ No newline at end of file diff --git a/SakuraIOTest/Program.cs b/SakuraIOTest/Program.cs new file mode 100644 index 0000000..899571c --- /dev/null +++ b/SakuraIOTest/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace SakuraIOTest +{ + static class Program + { + /// + /// アプリケーションのメイン エントリ ポイントです。 + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/SakuraIOTest/Properties/AssemblyInfo.cs b/SakuraIOTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d862fa4 --- /dev/null +++ b/SakuraIOTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 +// アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更してください。 +[assembly: AssemblyTitle("SakuraIOTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SakuraIOTest")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから +// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("ae09b37f-e7ec-45e3-a546-bd0d8735e37c")] + +// アセンブリのバージョン情報は次の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// Revision +// +// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます +// 既定値にすることができます: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SakuraIOTest/Properties/Resources.Designer.cs b/SakuraIOTest/Properties/Resources.Designer.cs new file mode 100644 index 0000000..df2b646 --- /dev/null +++ b/SakuraIOTest/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// このコードはツールによって生成されました。 +// ランタイム バージョン:4.0.30319.42000 +// +// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 +// コードが再生成されるときに損失したりします +// +//------------------------------------------------------------------------------ + +namespace SakuraIOTest.Properties +{ + + + /// + /// ローカライズされた文字列などを検索するための、厳密に型指定されたリソース クラスです。 + /// + // このクラスは StronglyTypedResourceBuilder クラスが ResGen + // または Visual Studio のようなツールを使用して自動生成されました。 + // メンバーを追加または削除するには、.ResX ファイルを編集して、/str オプションと共に + // ResGen を実行し直すか、または VS プロジェクトをリビルドします。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// このクラスで使用されるキャッシュされた ResourceManager インスタンスを返します。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SakuraIOTest.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// すべてについて、現在のスレッドの CurrentUICulture プロパティをオーバーライドします + /// 現在のスレッドの CurrentUICulture プロパティをオーバーライドします。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/SakuraIOTest/Properties/Resources.resx b/SakuraIOTest/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/SakuraIOTest/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SakuraIOTest/Properties/Settings.Designer.cs b/SakuraIOTest/Properties/Settings.Designer.cs new file mode 100644 index 0000000..c3b5c1a --- /dev/null +++ b/SakuraIOTest/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SakuraIOTest.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/SakuraIOTest/Properties/Settings.settings b/SakuraIOTest/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/SakuraIOTest/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SakuraIOTest/README.html b/SakuraIOTest/README.html new file mode 100644 index 0000000..e69de29 diff --git a/SakuraIOTest/SakuraIOTest.csproj b/SakuraIOTest/SakuraIOTest.csproj new file mode 100644 index 0000000..d707781 --- /dev/null +++ b/SakuraIOTest/SakuraIOTest.csproj @@ -0,0 +1,103 @@ + + + + + Debug + AnyCPU + {AE09B37F-E7EC-45E3-A546-BD0D8735E37C} + WinExe + SakuraIOTest + SakuraIOTest + v4.6.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + Form + + + DatastoreForm.cs + + + Form + + + Form1.cs + + + + + DatastoreForm.cs + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {f1f3defa-bcc9-4d36-b71b-d6a44cd8d022} + SakuraIO + + + + \ No newline at end of file diff --git a/images/sakuraio-test.png b/images/sakuraio-test.png new file mode 100644 index 0000000000000000000000000000000000000000..1c166723cb60fe7d3277a91002c1c22f288ad73f GIT binary patch literal 15536 zcmeIZcQjn>yFV;N1QF3i4QBL`2$HBlls)?By^B$!2SFl1LbPr4&LE?=5Iqu|5To}L zJ%}=h{@b4CSu=%R78^=TM|OG2{cu86rkq>1Y%<6 zpB7?b78V2q9<+4N=xL!JdU|?(etraEVPaxo1O#UU7DZwfMWR-<;*aZPoSS4^nh2hb zS%`&MScEBgw-O{b(r2}Zg%yb>HxeXw5hM>06wMMeu84*8ix!JA%F@Yt*oXP4Y%0S-RNo%Al8u_)T~EBoe|cV;(>x0^i+zDX0pKpo9Q8O1~u zCPt8Xi#+|-jS?7HEhlLMCrOx?MWn-(NC%=A=c}OW)g%v+WKZHu-)oovGH6ak78W15 zNxFEi4m`L!ZD(2ZlDr~};zJ~5Gf3T$NZXM_+nYu=kV)SkCN`LTa|FZCUu3aaWO4NI z#?dE+J@Vvx z)X75T$wJoYO6loZ-R5S|$B!S|+uQs4`UVCDruw^vhlfW-M#jd*re=pGCMKq4XQ!v9 zr#5DL7uWhXH>WmMrZ(_XXDd@@(C69N?9kc9?B-_w_wU~q78Vv47gtwT*Vfk7*Vi{T zHh%y9eYUZ3wt+u8TiM*)+}_?k{Ih>{hCezwf|{PQ%>VaajY&ft&?<weZ5M2L! z{&%6%^_>m$kls_lz*F1R&eO-z-Im~qCCbH9(8bpCmWbdTL9r8c;$;GY2Q*;TD`KNS8qrW}O;l6HfwY^P!hnM@xBgvER4+$d)6=qLslw zD8z}_2E+WUpPr@qm$;U^-!CoQC|xbwm~GhK_t$kSd%st@>QVp8r*J@OKVu*7EvGyr z*>vLkDkd}=^Gf789FB40=3$GJ2Z&Kj2!!%aO{5${0UAF-;z%jCp#sN-mYqzNo$LfH zYMcR3Ib)yl;Y*o6m(EU)&hEqEHl(gsM(=k=KfBo>bVTtM9tNIHu>SEm+jBjMlzGCv zqQdG|YpXrGYwf$3J}1CBdL~taj*-23j}ADrJ<||)pS`&0kCWLw3EE+7`CfL`EDEYW zJ)lZ>Zhm$_=%-?SANSvzJN_sUG_wu!|9a7Y{FimG@e{~ z-{*b$jexmbsR5b&b-&k$!|k1=vy&P3&Dx-))XXFL!&leZjDWbjov2YTaHQopWe&gk z`>d&Dsi@`9rw$XS|7^+UWF6{iVQ%*Vg%13r`OJQuIu7&uu)=(+Cb3zcL`qSw>6n2_c4|eX!Y~uE%HR{{bMVj_U zyp7N#yIU%{Cb9U10jZ$VW9t?j@_dZVqftct=K6-~X{5~0f|kRK{l+D}z%NCAa%bBl zTILUk%ud@RKnJ0KU`iRo9~) z^W7aeT;klbouHPZyr9z{={<62>4A?c@reP)2Xmj(shyR1gOIS_p}R?*j3;2UOwg$h z{#$L+vD;}Y#ZB^~4N=R;xZ*#(*MNR={7%pYU4WJ^7yQ+9+~?YcC{bdz{X8`K2JRLb z9n(d|I#gW3zNiBq1@Q!xuTg*!n7Iv3paZis?u;mzrCe$wt?E7T!yyfd*K{Q>q+M!c>Sa|p+NfEeC9N| zSTRFRtSJdM|El0!kVMyoV}dTgY-@g0qvBVBIWuY2YTj$T;fEeL77y@L|C>-o3wnFf zh+Kd<2lamcMS3`YXGR?;$uYnu`6!&rOht9%h%84NsQ(5J7z?!NQR|>2LF2wAnp{X?dix}Uy3DE~FGj_bBf20@%$+s8&hU}x&`n2}T4rrCE&bqEtu75I;Z)^8 zx&@la63}YnMyyEsXKN?azWyU=ns&Pz8v{R8c*{x~oA*dMNO_38{dc{ReYn!<<5qi% ztUV<;IkoEnB6Jn4)$;4zxwy$V6UmXoSkz6Dg&TAeBR4 z6u_tLO1Oou6Kzzs5Awu*7wB}nbwSANq|)Nb_ft(|lSw(-Siq+Vnfr)r{`X?Ak|Ty| z-4WO{Jf!S@vKY#YV8az3So=1OCwAI>r$3a!1^}x5jW)V=m`6MI(}{zyU7^X@M5OS| z@Mk@Ef81w#^Vhx2TAsx+GREoILV`~lNbxJQjp23OIqo8uw`GN5X}4xK6sse_jb1po z5ged_STuxVn}&3!1=g9U5zlnL2xO^H%+IEcebGK?)q0J+`Kk?8E^=>!HE{}>xVU2f27gwF?;swQ1Iqqxa?ZeLPl`R#t6dap zC}HlEW=&+Jgfn9D&K?i`0(?D~6Ar%@40>OHGv7o^5~4kAxKVVFSY%^N9|B5?F5qNl zWh}?PqOwQDC*11}w7ijId5U62OmgzU-g9*Qd_%IaFDzBB)i!V8P{{~Y956E zshpvuFrC=9%guDzLfj~!YW@{1Z}hN|P$4hbcyaj)u=EA!lCK5Ke8yiN%00bEZbLdp z%mu`00IUI-;R0+@jcy~9FZ&krb%fQ089hG-)fg_5A|^>o5UW-xX_UF6mKWGjdtF!F z=#3f~8R>M%p|1HMOtT_1m%lQitLakWMPAJ55CUqNC1c7d(o8y(5KY_{5nil=!Ri}(@5v`oZ!lUDrC+F@;r%gJn74IAm&Niif8=}?t4a#iNruYG zw%T3U0-Z$ppH-gpNbkPmlS%U*Y{qyEn}!k&gH|zp2o2{)LwrIUv@bOdGF%%?-Ckh! zZe|{M{#tn!QWBIPSAF$UY zwZkhEz8mA7cIrsuf-Pz)VEv(#eBcL;SD2cfj%P_W3*M_X`wOvfxT67Vg;2Hh_8rR# zGUL*IN0SMkV36_&BQTj#f-qcu! zq~&vc$+f=}v?`PAQ5ccFB^5~tBc=O!wVJ~;%=e9yO^@CJ|5d4x=1pC@VtrvAx@kZ= z=9&-kOFzVnD}|GRmfbg> zP)_M{i{>YD*LokFm2VVvO_x$%sbnV@{!XtVOf#0+@Oj91xNNYlcF$~%1UAXxAQz~A zI>WXr4{Vw|WXWTXzvrCK^sKPhT_d!R%YP#+h zY|Nq+C~c)_VE2@55;Bb8YI2i=%&@0qjh3bh$LTtlHbzrf+xN%xwTaaqglEF@3XJPr zXeHle$OSJR7W%iW+1sj-Nph*9ZAtIFJkV4I z@pC^q+$Pm%>LZ>cP77R9E|-_*uPSxr_TtS}Bc&>w4VJ*NWhNjTcHK6fa0v`#eg07; zV0Ql{Q|jLF^A#RYcC%SQ0r}gQ@g>cOPIv_n!=|qgGsr?8zq`>U2pZk?ozDKm#|V>9 ztH^oU;OKu1mZ?2qK&1E1+YGN*HsjqyJo;DpcdeO1n@J3+f{se z?FOoI8K$S-?1^x3Xnfqex_oiv5>n4k;lHRtKYhf$PX3&{~&5zhh{Qz5jc4>!*Q9im&2`nqj1 zT^d%4kxZTa7vP@q5O_FP0lfLnw5vraNf)DpRbEg_*1>)7ObpKR zetx|d9`A*!&4uY8*rKohKkRB~szfe4c`3JYHa!a?<*ouycp1DF+5dk0Tp@42R=7Mvh?wM9R|#J@_+T40rgW}A%)Dd#IwxauT@6di4a<|1 zGRI1kZ5!y&o`idJq#G{+E<^TVXuTKQ?TU3bUFBI5`*7^EAR9PencJJYM&C;_Z(Cqw z9Z)T@iv{3!ZoVV8BfqIa*iPBKJ*i*{r>G8krlrqK7>C<4ORB?}vaH zAs~1p2MkmKxMP1UeCmyO}5>fFa1_TE>h@T76LG1YgU&-0?1IcA7h?K&Ms zmNV*3d^PWhnrl?OY1b8CDEC(uXhrSmLUV3+&^lyQ^rZ`WcS0`A+tqy}m+ol9{F&93 zNH9P*A7-a}t?hZ=b1D3FK1LBHxX{+m_XTCGBL6lGF@J26$+sEnaaN``{$IbA3`;6V zsUZqQ=_(R2Zg)rCctMWj#F09~CpLlbcg0T{d4_yt`xhbCO`1Xz{+7}Q>6z=x#t7HF zN5R8W_eu)pm3w>MfQF&sfS@?myP9L{g#K>O6CEE6g{xnMY1g96eGG~&4@BEYAaTrQ2PZi`8DZtn;Hz@lNt*vhejB{iC zR;-Z7N*07)LWvJf>aVD9Kn~~*$i_U*#>_Ws;;J-}?&kIJ_|S2AB+{MLFCH&A{+qF$ zXpaIi(C1>TiOYsqR1OFW2KTPQnMoiX{ND=1R2*cSxW?KuoL_?1+r5Exq0(4KIY24& zzdGD3intXa_}1JpVUM{vlC8y;Tl9UIZ*h!^;T#Rb5Yg?T-ng+dCrG?;wwkiM@FlWh&6p*!@p;qp?vi|P$~Su6z|y-Dzk0_`a>_Cq)t>j& zdGIoqx6Pm$c4!vE1z+Y=@HVvDAcG3nV7i6f!Ey?Wz43R6rY}Lgle6=fq*U;!=&2`U z!L_c9++j$VV?7_04U47rZm&hNC5txle?79Vo1NS6o4E*aAB!x`DQRVqtSo{?ETQWH zy@-(uJQVVuloqv`xtgdG>8-0EF1l%oQ~&Q#{NLfdQzCl$qJIq;LYz7k4)=KuUMGaF zQ$WAa)k@qenvfRF#>mS8{;&R9!L%0gi1?Oau+-x#aOQA-ghNnjXj<665W(M_o5&#-Ux!IV9A@Ycg~Vk~Y>`LBAl^Ru zc}HbS3RN-=+j&5|Saii!T-*KYGB`GC^;e}8btAV&Jv@fETT3ZG7cnPAQDr?HA5o;b z?-!;#jJyXSKqtNm+EqHmvURr-nQQi64#RnqL(^l1HK8v7-CF-MQnQanL#Wi&BI%=RBwZ z*a@}Dh{{ZCQMPZo%{`1MuwIrZ78daA0vxU%3YIRk^X*aamYMKk18lG7Y}*VbI`UWM zMMS;J!kCtu;qIPQMdzIf#dy5NXlQKa@R&7jQxPUAJGdr06y$8rzjANaL9BanMc=;& zVHF8*wQw=}l?Z)l4oTE;U%L;E=eAmIz(8)i9hFX(WI-;gkvb&95SU@w3sp*2=Iz>C zIe`gyFMM!{YqrNa4wddNS$8ta0Sy-Mm=&WNq&sKqw7wHJWpQKXL8jGD5kG&?DHnEf z!W6i&lWV@&g-fFxcYY|Be^{F{LAsN!O^B%@NQkoGs|cOqkHg*!33zQnx>;a`gW{{| zOVbp2#EL>bg#lr?NqrYZLZuv-&r#_pZ zA7iY)H3g)qXjQ~nMJK{4V7g!1(vg?HM651|N9!o6l_&v3--KH1EF%2a6THHN z7P6`;Nm3QvO&LIHErk##;DX2dRF`XB;fYzg4w@>1HHRL!d$UjFrH=|kn%$By9t?(M zY2l4#nY^wKoo?NPGd~{!XDMT=fts_coIz#DJk4j+{38U_mY9nIE2~bt)hkq_+2>|% zAV((H_r&YuLZ5<6FzV{LwZroZnH|_bn_C+SX4odm%s9WIfW2=a|0=qNrH4xQwg}O9 z_OhP~US&HNDW>2a?pm>f*xU}_%*F3GZv6CHnQe&rJquzUsX9A^i06bANdo^l^D z{jsFK{#EjNyo+HObjpyW0X!BuRs##w-HqsdhKpYk8%N~2In7s;g@NF=3^1el**dLA zt^Mi8NFUt&l`V3BgS(A53BIg-Cb~)ykiWtQZw4v-Re@k#zmYsV@uod_MhH z&9^3h&qa_&&?vGZkNG!w1nH#RvoTY0mX>@;{YA6Z;S(mTC4!ChSv7FeppqG^<)6mmyGR=Qfj)RI}u0)Sp1&bcb1s zYUzD#qkiaq^TK=TTIH0k$B~Ur#MlnK7e={4F`uO4UiB5{K_Yg^DdRg_RE5p%W`S*p zwDAX<-O2U^M>NOYS>aJwYuF!H{i4Dg~r*k17alU+Gz7eW&~JCq&)m%kKzn*e+1 z^(*JxV*Jm6OeU*-B{oRcZ*Z?nUxH(*g}C7M%Qhqi^JKka@<6S-5O8wwE1g-~vn9ix zU3feV@+oCkRS3CEj`L%fz;(@M)Mow>59(7f2M+{Tx!4x^s7lS_b22-QC*~~$(4KsB zz^ahR$qyQ#vO7E{3}0(5x9Ao8qIFjQ+%5^EiXJ+BzdNXLHB+tU#A91SyF!O{`T4tX z0<*hHwFrmk4b~SrlUGH`SSDj~Z`)T-Bd!%0a;@N#o7tbb{M>O&Jb{haGv^i-w#%7_ zqm;}_WCk+89%Ex3FXbZ)){#`u<8jo)fNv2dF;-fmO~os%z!})pPJmVYdfY-Jp<<-{ z-YR~^@zt_v8w+UG6+crKjnTJ?$7~Z0Hs5ABx>&~;Er2={*<@}0)3bwB>pK{;UZ0m7 zUCzcL8e$ZS2i?dZ)1n7BxLSf~DI8|RzAn3kabL>hG=6_si~b@6>d4%QIOU-&u_Cz? zj6~PfU55bHBDn4YXX@%8LVR6(gphdwgHRL8zRhXpVj{r1%w?OIM8~}aCyw|kXCBix ziV9aKI#-hba^3WeJP^URJKJ|Uek=@>$Z>^AzUUz#N2Kg@Pn&c~!JCBlZjS|J8CztA zS3E=#8{&%I_}#*qzD+cXJ`&!y-tjW8jL;}JzE&j1A;x+Gon|R(?t^r{^BCqOwBQ%B zCIand9D9r zvvctmtSRkSr0Z^uF12vW#q&CFT^Uz6ihSAx#|&u@cQc%^EC7W`kVjU}fIzsA!cEB_ zi4Orw?d*Z|rKf+HYY`ag-Z;_p0-+;oJ%l&eNz`$Ef$EaOov9EAs8>!j`m(bMLr@3@ z$x$!kYcZw+3Fd{lft2=wg5D?fKhhredysMS5KkuP>^_-5=<<(UaHJkonL+k8`d{UI zHmS(OvXC0o7ti92SU$y=}F>B(a*Ca4dCBu+<1z7N%jV z)zM-}-N4#>ZCY^$^}!LI*4TIW%4kilW`d^ z8kPB)Pzo|CE~Rzi!C00neMYOwW0l<|E=rE>hpB_b7}xc4i@EZi2gWNfSereL+2$Dn z5Y7`KRu4i(ZMeZ#Wuwy_jJQ&atM^5C6HQK7fb~0Us`Z5?ZYlbNL;<#u3g1hE3{P*A zd7LF^;!5~q@X3*iyyOx)5V)|o96~;riV55)QmeQqZ^X{%9+_o?2Hv|2Dg}FYINm&Q z{Jj3b1jl!liX#F&9s2H`l-Z2lvW&zGT*j!d=CnZg5_Ss7jg5RVOw>A*16RiO=0?MO zj%D*tjBy_@8VupwZz3bhm2o|>rZ8PNn=S0%Q8YKotzoEd&z}87PJ*LqlGPjK=ja$s z6*k^#cwAj%B>q#V# z+_eE-v;jNzdYp%PPurTjrxzDr2*MELncMh0E*OUrQ|f!Zj%XlEixGi zpgd`%FGH?`m#dE&(Bu<};oe_-h>Y9m_AhyYL@s}~&p%athE1e_h1+Kj0WQkvRYjtQ0_p-7R{uxK_Tz4#QBI^%@g)9#^ zRsdNlk0?JWk2UNN9Ei&yGdlbcKX6rpL8eL-Nv8M+Fuag*w-&B*hFNC92ynHj~g$)a&DGT5s<~j5>Rh3EHVL1;IKFvQ&P0fX7qFa9K zk1JMt@k{S@2tlD)xV-`yZ1B?|8sBQjjdk{mROPhVH~iA1>!imDRp%Q$ZH>(jrUns7 zYueR`(GLvJJ@=#AP=VCY5PWcjd$C+!(L;vq#3&-P&FFBNfeMT`)#u4$ywvHgqW`Ac-EE(HFmU zAL6KfWe2;3FSF}@v=W7@-YAcn_HAnzV0`jmh0(P!R{FF6ay%=PaocJIHqA?7Ah*hP z>h#LCYDF9J+K`tvG+bJoCn*sk!(9jN4DQMVQ|Jym1P!cWC3Ij(-8%7$2HFCZc4p2$ zZ3fw((Mow%#2FHF0`c4Rrv!Er(H4^-=kSZvxsz{r`vYcg0NJfu!8HZpICb+HKE}2U z7;koW1@0AxwV$LA=L@1e@1oHriJ|aw4QxJs?}n)*YyF*2%+}-CupoCc%`;Ph0k2^h zMDzq&XZN(4kEc%Ri!!f%{w2nk@@CO^exlWM%oA%JlCS^=@(PDZgku8 zmt0~(1RvH8PTyg>w^^i%hg1O5K3d1ch=D&kLCu0j+>lylh_FjEmikte5z%FH3JyvkIPrQ(GXol(_EQiv zieGXF1T7d2=Iv(edWU;>MGxH5~z#Zm1TGd6?yp}cS)?e4S-*w+mHHV z5#Iianmby)(of>4tzpH+6(Vu8p6*i22Sdyt86M zJ&LMooTl1cEhPjL>saA3r{;g_Az9{EgE+0a`RcyHMr?jICi2eyJl&Zb`@EdqY680= zhh0xVIDQR}xv*KNngx~eVI$>|40yv#3f!mk9s1T&tOMNX#FWE@#twsxR)tDw_6Sd? zl@=rJS%)qyssG7Ji>vkvJbt63fy^A~o20n@0LSU`gB{rA-ArtFrXUa<*k>vA8m6^) zMV+6j9aG@eN&gmNsHMIP^Pt&kIWR}>6YNbTtObc769k9Nd;;#+UZxlDqbhsqmG_5R z*~L+~;w$-Bf}UT_g$Hn~3?PmO=i)U==)&mQ1}CeU#?pRxglw!t@D>}gJFn0+6i7b} zTw}s*H<{e7^1Q4LAXw5PVDGhryRME^f7kNA=)fAYntyM`K=4hB2DQg5mAe=J)2*mz#AikO2RE?ZvtJw@LeRTAV0VMivssJz%Y zfb^{Tg3|kHZ^7_Uw%O8Gf*s!>yv!pT^FhTSVPCV$PUlV^o8OI?M}}69r41xo{H~ri zWyc0w3fDr`#nN6xG0O6;b-}MHAXz19IrA6_ZLQtpaA%byO!DSo^(P2RP!55fYa_4qG8_IjchBIr(F%DXG4H9DIWjM}vL< zkZKiykvTs75GU=Q@X9wKR9r|RhZ?l*Ll`lYJ?3{;^>^PMZmN2OJ6yt~ENfmovcRm~ zkENbVMB-{bDB}hK^~|2QRe$(dm+&suxGh{c>JAr{^@pLfLU_H}5qUQ|KzEx1t4qs` zdT9b%3Hmc?gB3Sw5)jqG+l5W%czYrem~9o2QUsmGXa)gTr6C7dbWbz*BUS5743)fC zJXq7N0G`g12mE^cefceBM=P${Sh^Q0fx4Odi?Kt=?lCYR3#3A_GI!j~>L|@0|D?(^00_SeWu@-182ISaB?*pl7Og!HjsK)~&Ltcx_hN}&f_t40 zfZYfoUwUp-7~~LXOg<$xj%s?HD)Y@-@F0c<6{d-N30^^0-{Ho}0+$ez^yuL%%mujf zHH3pE?l~xS1un{sdI&JS1R2mGkp8GJKpyLbem3vc)Y6zzfSuJ-E0ukWE(!G@Zy{{? zp%L&-|JfgvEX>Pt9b}#KP~f6)Nx_an_37&M$Lv>s4OAsFNCy|xY>->Zxt{z6(|k)WGw1%Oet{{u8ky_5G7nZ7 zB=?9cdUm-e`!8Df260g|G>^$=NQk8;#|@97B-?zPu@wnNQ!sl_|V@&rmudh zPMMnfC2AE}?RBN9Sy4%~&EQKOo0X_5YH$KdUX00uyoLE6MK-+)A)S#*LPE0~Q;LP! zG~}0Klz4O;CEF4@-`!{eEB+MQ6xUW(K?z!H>43w_Hv7aW@71b*lPskEuGceTg!_E? zKoOeE^h0E$X#b93d zi7#yTVmyD1z{A35=tXn%P5Q@cw9^p5ivXi9#;PhxZ?sR=->8lLPK|?VLw_ z^MR^`uuSQ!%<>zSRMzZs;TZ$!Au)toe`PQDzgF84jA_#_^mP$ft1x_R38iDnrzGl- z_eV#`viptqxbyju$i$}$sYm>EApG>TfZ`XAF~_%*V41i9v+=|Pl=m4k3U9AC7|gAi z{a!<-mI|dU#FYfnG;>I<^+MnT}WVv&QE2uYGHNzDB{YLz@$X` z%MX#;7lyL3@2~e=if4`(VJ{LXTg16IhTHROqm+u3e0iWL(W)R=AXMU?ZPdh7j0>(1 zbq9`n_+3GW(@H2GYE{yq(a`a)G34Lf!xv6JoR5Q;fJF|sHjac+CmQaoD~H!QCTg4H zNIL`@CuK^CPsM7lgr28s1m5Z%9B92)LQxVDUB7B*#TEawypXo&OBFw2y`TV;^@ zEBALrQX>bsJC_>fbH&1){bFs?VdGX`ybh&^#$=Z@b*fi&rQP+(A@+{f6$@^-okmIA zKuxSteOx#y1tUv2+%=5Aznb3v1RibQ273lkV{z2EWV6^GV)IOaMz8o^?+fsPesl6p z;XW^5fjt}<96}Pl;~g9`fNqWO=U3OIU+iI6cbj~vffHeYPIj5;nw)}7?TLdQ)yvi% zu-&MzajRiTpQe5fG41)V^mWN_%zLN$CB4ZeN7W;70vR$0(_Mm?>FcRPhYg}f7gPwGs<0NjhI!PR`haz zw!DCP%<^u{pqSP$&sQ|j`|ZdQN4ANscbgwN5!Zs*qa9VGP`~SH9C}fz^@ydYyP={1 zGf%N>hJy~4{|nC$BM^337y((J`x?3$&drnj0<5_Vhu>Yh#WO+fuRZ`-`QyF~%1!N$ zDm+0}%QC{lOq^DUAfeSqAXUu);h1v)M`H4{Vi0bu=jTXeE-I`wm?7P z4AQP#9n5xq*!VU*O^$wCNm3eT*F8Bv!Ul_Z6Cngf>g&6``JqbL(EEl~fviK0tgU3rJw}fi1xu@o$5h+-5yKp@TvaMuvBZ)=?DJZR1weJNA!q z*BluhPhEv1djT{HjCE>goL{c~1w6^G^dRgR;;@lzbk&L-K4P)bZX|_mX?H9$%Ap6l zKa2gXVL!twS5~)shm|5`Cs>1Su|~8Woo6%sWUHYU9WX5`p-A0Ol7Qky=1Lt%RJ!)8 zPbedK#T%%|v)}yZ#(RHN{;q9tKZy=Wjmo(l#ihXA!;ZLqoQ z&e~_ctyZN@>49I+WrS}SjLa;xAud{442{;&#O-BaFu`CA5gbnAuY8EdW8~H2xz=qO z3mbatk}(5L{K_SOkKLe|CK3sG^79x6>@3Z;Vaw@JljsHzgY5<7yP{pC1PWO^=fj+Ibu3NhCuL&0e;4%fn3gn(Er>he~8Mm zz|>0+gn}`afA{AH1`}z?N*mNU=mJG~_u)Lgp8qvj{y&m>^pp`BubZyI#pGF7{o5TE zVenTXkmmj$?$`ZuZRzaMRTOlTOblh!|8w!~->XVI@Dq;~+Mi=9zoj&eorYheADzNM A6#xJL literal 0 HcmV?d00001