- 投稿日:2021-01-12T21:38:35+09:00
Salck APIとを使用してC#でテキストファイルを投稿する
はじめに
C#でSlak APIを利用する際に、
Webhookを使用した記事を用いたメッセージ投稿のプログラムは
よくみるものの、
Tokenを使用したプログラムはあまり見られなかったので、
自分への備忘録も兼ねて記事を書きます。Slack APIについて
基本は使用したいAPIの公式ドキュメントを見てください。
そこに全ては書いてあります。
今回は"files.upload"を使用するので、
以下URL先のドキュメントをしっかりよむ。
https://api.slack.com/methods/files.uploadC#側のコード
以下、今回紹介するソース全文
//外部ファイルより、投稿したいChannelとTokenを取得 string channel = System.Environment.GetEnvironmentVariable("Channel"); string token = System.Environment.GetEnvironmentVariable("Token"); //パラメータにTokenとChannelをセット var parameters = new NameValueCollection(); parameters["token"] = token; parameters["channels"] = channel; var client = new WebClient(); client.QueryString = parameters; byte[] responseBytes = client.UploadFile("https://slack.com/api/files.upload",Filename); //結果を取得 String responseString = Encoding.UTF8.GetString(responseBytes); ReturnValue Return = JsonConvert.DeserializeObject<ReturnValue>(responseString); Console.WriteLine(Return);class ReturnValue { public bool ok { get; set; } public String error { get; set; } public SlackFile file { get; set; } } class SlackFile { public String id { get; set; } public String name { get; set; } }外部ファイルより値の取得
System.Environment.GetEnvironmentVariable()では
外部ファイル(今回はAzure Functionで作成したので”localsetting.json”から)
TokenとChannel(正確にはChannelID)から値を引っ張ってきます。
Tokenの取得は以下、
https://dev.classmethod.jp/articles/search-messages-with-slack-api/
ChannelIDの取得は以下を参考にするとわかりやすい
https://qiita.com/unsoluble_sugar/items/603e51106d9632f3ea4f//外部ファイルより、投稿したいChannelとTokenを取得 string channel = System.Environment.GetEnvironmentVariable("Channel"); string token = System.Environment.GetEnvironmentVariable("Token");APIに渡す引数のセット
次に、NameValueCollection()をもちいて、
先ほど取得した
"parameters"にTokenとchannels(Slack APIに渡す引数)をセットする。//パラメータにTokenとChannelをセット var parameters = new NameValueCollection(); parameters["token"] = token; parameters["channels"] = channel;クエリを作成し、WebClientで送信する
WebClientのクエリとして"parameters"をセットした後に、
"client.UploadFile"でSlack APIにクエリを投げかけます。
(変数"Filename"に投稿したいファイルパスが格納されています)byte[] responseBytes にはSlack APIからの返信がバイトで格納されます。
var client = new WebClient(); client.QueryString = parameters; byte[] responseBytes = client.UploadFile("https://slack.com/api/files.upload",Filename);結果をコンソール出力
最後に、送信結果をコンソールに出力すると、便利かも。
(okのパラメータがTrueなら投稿できているはず)//結果を取得 String responseString = Encoding.UTF8.GetString(responseBytes); ReturnValue Return = JsonConvert.DeserializeObject<ReturnValue>(responseString); Console.WriteLine(Return);最後に
今回で、Slcak APIについて色々わかってきたので、
他も触っていきたいと思う今日この頃
- 投稿日:2021-01-12T06:46:22+09:00
Unityで対話型音声認識アプリを作れるフレームワークをつくった
macOS, Windows両方で動く、Unityで対話型の音声認識アプリがすぐに作れるフレームワークを作りました。
リポジトリへのリンク:https://github.com/HassoPlattnerInstituteHCI/SpeechIOForUnity
Unity Package: https://github.com/HassoPlattnerInstituteHCI/SpeechIOForUnity/releases/tag/v1.0つかいかた
SpeechOut/SpeechIn
を宣言し、以下のように書くだけ。SpeechOut speechOut = new SpeechOut(); SpeechIn speechIn = new SpeechIn(OnRecognized); void Start(){ Dialog(); } async void Dialog(){ await speechOut.Speak("Hello!"); await speechIn.Listen(new string[] { "Hello", "Hi", "Hey" }); await speechOut.Speak("How are you doing?"); await speechIn.Listen(new string[] { "I'm fine", "Nah", "I'm Sick" }); //... }なぜつくったか
macOS, Windowsを使う学生のために、Unityを扱う授業で音声認識を使った対話型アプリを作るためのフレームワークがあればいいなと思ったので作りました。
主に非同期処理のasync/await
ベースで、逐次処理をわかりやすく書けるように、また速度や”間”などのチューニングも簡単にできるようにしました。授業ではmac, Windowsそれぞれを使う学生を同様に対応しないといけないのですが、Unityそのものは双方で動作する一方、ネイティブOSの機能を使おうとすると相互にコードを共有できない=他のチームやグループが作ったアプリを動作させられないことになるので、このようなコードを作りました。
授業のためとはいえ、一般的に使いやすいツールだと思います。
OSネイティブの音声認識・音声合成
macOS側
macOSネイティブのオフライン音声認識ツールとしては、
NSSpeechRecognizer
が、音声合成ツールとしては、コマンドラインツールとしてsay
があります。このうち、say
に関してはUnity上のSystem.Diagnostics.Process
から呼び出すことで実行できます(この点、Argsをいじるだけで声質とか速度を変えられるのでサイコー)一方の
NSSpeechRecognizer
はOSネイティブAPIとして提供されている機能なので、実行するためにはObjective-C
もしくはSwift
で記述されたコードから実行する必要があります。今回のフレームワークでは別途NSSpeechForUnityとして、
NSSpeechRecognizer
を呼び出すObjective-C
コードを書き、外部ライブラリとして.bundle
ファイルを書き出し、それをUnityのPluginとすることで実行しています。Windows側
逆にWindows側では
UnityEngine.Windows.Speech
なるモジュールがあり、音声認識に関してはUnityから直接実行できる一方、音声合成に関しては別途WindowsのネイティブAPI=SAPIを叩く必要がありました。コード内で別途WindowsVoiceProjectとして、Visual Studioから.dllをビルドできるプロジェクトを作り、その.dllをUnityのPluginにします。
以上2つのOSに関して独自にライブラリを書き出すことによって実現しましたが、Unity上で非同期の動作を実現させるため、それぞれの処理がきちんと終わったかどうかを常に監視するためのコードを書く必要があります。たとえばこのように、内部のStateを逐一変えて、Unity側から監視することで、UnityのAsync/Awaitが進行するように若干HardCodedな感じではありますが、リアルタイムアプリケーションのための非同期処理を実現しています。
応用
対話型アプリケーションのためとは言いましたが、VRアプリケーションなどのための目と手が離せない際のデバッグ(イベントが起こったときにどのフラグが立ったか喋って教えてくれる)や、視覚障害者向けアプリなど、様々な用途が考えられます。「デバッグのときになにか喋ってくれたら便利だな」とか、「アクセシビリティ機能の充実したアプリを作りたいな」などというときにはぜひ使ってみてください。
- 投稿日:2021-01-12T06:32:58+09:00
cscの作法 その46
概要
cscの作法、調べてみた。
intelHEXやってみた。参考にしたページ
http://nanoappli.com/blog/archives/3655
サンプルコード
using System; using System.Collections.Generic; using System.IO; using System.Text; public class HexFileParser { class HexFileData { private int offset; public int Offset { get { return offset; } set { offset = value; } } private int length; public int Length { get { return length; } set { length = value; } } private string recType; public string RecType { get { return recType; } set { recType = value; } } // レコード種別 2byte // 00 データレコード // 01 ファイル終了レコード // 02 拡張セグメントアドレスレコード // 03 スタートセグメントアドレスレコード // 04 拡張リニアアドレスレコード // 05 スタートリニアアドレスレコード private string data; public string Data { get { return data; } set { data = value; } } } public int StartAddr { get { return startAddr; } } public int EndAddr { get { return endAddr; } } public List<string>ParseErrorInfo { get { return parseErrorInfo; } } private string defaultValue; public string DefaultValue { get { return defaultValue; } set { defaultValue = value; } } private int startAddr; private int endAddr; private List<HexFileData> hexFileList = new List<HexFileData>(); private List<string> parseErrorInfo = new List<string>(); public HexFileParser() { DefaultValue = "00"; clearParseInfo(); } public void clearParseInfo() { startAddr = 0xFFFF; endAddr = 0x0000; parseErrorInfo.Clear(); hexFileList.Clear(); } public void parseLine(int lineNo, string inData) { string startMark; // 1byte スタートマーク(":"固定) string byteCount; // 2byte データ長 string offsetAddress; // 4byte データオフセット(開始位置) string recType; // 2byte レコード種別 00:データ string data; // variable データ string checkSum; // 2byte チェックサム if (inData.Length < 11) { parseErrorInfo.Add("行:" + lineNo + " レコード長が不正です"); return; } startMark = inData.Substring(0, 1); byteCount = inData.Substring(1, 2); offsetAddress = inData.Substring(3, 4); recType = inData.Substring(7, 2); data = inData.Substring(9, inData.Length - 11); checkSum = inData.Substring(inData.Length - 2); if (!startMark.Equals(":")) { parseErrorInfo.Add("行:" + lineNo + " スタートマークが':'では有りません[data=" + startMark + "]"); return; } if (!isHexaDecimal(byteCount)) { parseErrorInfo.Add("行:" + lineNo + " バイトカウントが16進文字列では有りません[data=" + byteCount + "]"); return; } if (!isHexaDecimal(offsetAddress)) { parseErrorInfo.Add("行:" + lineNo + " オフセットアドレスが16進文字列では有りません[data=" + offsetAddress + "]"); return; } if (!isHexaDecimal(recType)) { parseErrorInfo.Add("行:" + lineNo + " レコード種別が16進文字列では有りません[data=" + recType + "]"); return; } if (!recType.Equals("00") && !recType.Equals("01") && !recType.Equals("02") && !recType.Equals("03") && !recType.Equals("04") && !recType.Equals("05")) { parseErrorInfo.Add("行:" + lineNo + " レコード種別が00~05以外の値です[data=" + recType + "]"); return; } if (!isHexaDecimal(data)) { parseErrorInfo.Add("行:" + lineNo + " データが16進文字列では有りません[data=" + data + "]"); return; } if (!isHexaDecimal(checkSum)) { parseErrorInfo.Add("行:" + lineNo + " チェックサムが16進文字列では有りません[data=" + checkSum + "]"); return; } uint calcValue = 0; for (int loop = 1; loop < inData.Length - 2; loop +=2) { string curValue = inData.Substring(loop, 2); calcValue += (uint) Convert.ToInt32(curValue, 16); } calcValue = (~calcValue) + 1; // 2の補数を取る calcValue &= 0x000000FF; // 下位16ビット分だけを抽出 if (Convert.ToInt32(checkSum, 16) != calcValue) { parseErrorInfo.Add("行:" + lineNo + " チェックサムが一致しません[calc=" + calcValue.ToString() + ", data=" + checkSum + "]"); return; } int start = Convert.ToInt32(offsetAddress, 16); int len = Convert.ToInt32(byteCount, 16); storeValue(start, len, recType, data); } public string getValue(int address) { HexFileData targetData = null; foreach (HexFileData curData in hexFileList) { if (curData.Offset <= address && curData.Offset + curData.Length > address) { targetData = curData; break; } } if (targetData == null) { int wordLen = DefaultValue.Length / 2; // 1ワードの長さを求める int wordPos = address % wordLen; // 要求されたアドレスが、ワード中のどこにあたるかかのoffsetを求める return DefaultValue.Substring(wordPos * 2, 2); } int pos = address - targetData.Offset; return targetData.Data.Substring(pos * 2, 2); } private void storeValue(int start, int len, string recType, string data) { if (!recType.Equals("00")) { return; } if (start < startAddr) { startAddr = start; } if (start + len > endAddr) { endAddr = start + len; } HexFileData hexFileData = new HexFileData(); hexFileData.Offset = start; hexFileData.Length = len; hexFileData.RecType = recType; hexFileData.Data = data; hexFileList.Add(hexFileData); } private bool isHexaDecimal(string inData) { foreach (Char data in inData) { if ((data >= '0' && data <= '9') || (data >= 'A' && data <= 'F')) { continue; } return false; } return true; } } public class Hex0 { static void Main() { string fileName = "c:\\avr\\a0.hex"; HexFileParser parser = new HexFileParser(); //parser.DefaultValue = "FF3F"; using (StreamReader reader = new StreamReader(fileName)) { int lineNo = 0; while (true) { string line = reader.ReadLine(); if (line == null) { break; } parser.parseLine(lineNo, line); Console.WriteLine(line + Environment.NewLine); lineNo++; } } //txtHexDump.Text = ""; int loopCol = 0; int startAddr = parser.StartAddr; int endAddr = parser.EndAddr; for (int curAddr = startAddr; curAddr < endAddr; curAddr++) { string mCode = parser.getValue(curAddr); Console.WriteLine(mCode + " "); if (loopCol++ >= 16) { loopCol = 0; Console.WriteLine(Environment.NewLine); } } } }以上。
- 投稿日:2021-01-12T06:31:36+09:00
2つのObservableCollectionの双方向同期
概要
2つのObservableCollectionを双方向に同期させたいことがあります。
以下のGIFは、左にObservableCollection<int>
、右にObservableCollection<string>
がBindingされています。2つのObservableCollectionを双方向に同期させて、どちらを変更しても、もう片方に反映されるデモです。デモはWPFで作成しましたが、ObservableCollection自体はWPFには依存しないので、UWPでもコンソールでも使えます。
コード
2つのObservableCollectionを同期するために、両方の
CollectionChanged
イベントを購読して、相手側を変更しています。
無限ループを避けるため、ローカル変数のisChanging
で既に変更中かの状態を保持しています。public static class ObservableCollectionExtension { /// <summary> /// 指定したコレクションからコピーされた要素を格納するObservableCollectionを生成 /// </summary> public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> source) => new ObservableCollection<T>(source); /// <summary> /// 指定したObservableCollectionと双方向に同期したObservableCollectionを生成する /// </summary> public static ObservableCollection<TargetT> ToObservableCollctionSynced<SourceT, TargetT>(this ObservableCollection<SourceT> sources, Func<SourceT, TargetT> sourceToTarget, Func<TargetT, SourceT> targetToSource) { //sourcesの要素を変換したコレクションを生成 var targets = sources.Select(sourceToTarget).ToObservableCollection(); //2つのコレクションを同期させる SyncCollectionTwoWay(sources, targets, sourceToTarget, targetToSource); //同期済みのコレクションを返す return targets; } /// <summary> /// 2つのObservableCollectionを双方向に同期させる /// </summary> public static void SyncCollectionTwoWay<SourceT, TargetT>(ObservableCollection<SourceT> sources, ObservableCollection<TargetT> targets, Func<SourceT, TargetT> sourceToTarget, Func<TargetT, SourceT> targetToSource) { bool isChanging = false; //Source -> Target sources.CollectionChanged += (o, e) => ExcuteIfNotChanging(() => SyncByChangedEventArgs(sources, targets, sourceToTarget, e)); //Target -> Source targets.CollectionChanged += (o, e) => ExcuteIfNotChanging(() => SyncByChangedEventArgs(targets, sources, targetToSource, e)); //変更イベントループしてしまわないように、ローカル変数(isChanging)でチェック //ローカル変数(isChanging)にアクセスするため、ローカル関数で記述 void ExcuteIfNotChanging(Action action) { if (isChanging) return; isChanging = true; action.Invoke(); isChanging = false; } } private static void SyncByChangedEventArgs<OriginT, DestT>(ObservableCollection<OriginT> origin, ObservableCollection<DestT> dest, Func<OriginT, DestT> originToDest, NotifyCollectionChangedEventArgs originE) { switch (originE.Action) { case NotifyCollectionChangedAction.Add: if (originE.NewItems?[0] is OriginT addItem) dest.Insert(originE.NewStartingIndex, originToDest(addItem)); return; case NotifyCollectionChangedAction.Remove: if (originE.OldStartingIndex >= 0) dest.RemoveAt(originE.OldStartingIndex); return; case NotifyCollectionChangedAction.Replace: if (originE.NewItems?[0] is OriginT replaceItem) dest[originE.NewStartingIndex] = originToDest(replaceItem); return; case NotifyCollectionChangedAction.Move: dest.Move(originE.OldStartingIndex, originE.NewStartingIndex); return; case NotifyCollectionChangedAction.Reset: dest.Clear(); foreach (DestT item in origin.Select(originToDest)) dest.Add(item); return; } } }使用方法
使用方法は単純で元となるなるObservableCollectionから拡張メソッドで呼ぶだけです。その際に双方向の要素変換のデリゲートを引数に指定します。
ここではSource->Targetは数字を文字列にして固定文字列を足したもの、Target->Sourceは文字列の3文字目以降を数字に変換したもの、になっています。class MainWindowViewModel { public ObservableCollection<int> Sources { get; } = new ObservableCollection<int>(new[] { 10, 20, 30 }); public ObservableCollection<string> Targets { get; } public MainWindowViewModel() { Targets = Sources .ToObservableCollctionSynced( x => $"C:{x}", x => int.Parse(x.Substring(2))); } }デモではViewを直接変更するため、あえてコードビハインドで変更しています。
<Window x:Class="ObservableColletionSyncedTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ObservableColletionSyncedTest"> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <UniformGrid Columns="2"> <StackPanel> <Label HorizontalContentAlignment="Center" Content="Source" /> <Button Click="AddSourceButton_Click" Content="Add" /> <Button Click="RemoveSourceButton_Click" Content="Remove" /> <Button Click="ReplaceSourceButton_Click" Content="Replace" /> <Button Click="MoveSourceButton_Click" Content="Move" /> <Button Click="ClearSourceButton_Click" Content="Clear" /> <ListBox x:Name="sources" ItemsSource="{Binding Sources}" /> </StackPanel> <StackPanel> <Label HorizontalContentAlignment="Center" Content="Target" /> <Button Click="AddTargetButton_Click" Content="Add" /> <Button Click="RemoveTargetButton_Click" Content="Remove" /> <Button Click="ReplaceTargetButton_Click" Content="Replace" /> <Button Click="MoveTargetButton_Click" Content="Move" /> <Button Click="ClearTargetButton_Click" Content="Clear" /> <ListBox x:Name="targets" ItemsSource="{Binding Targets}" /> </StackPanel> </UniformGrid> </Window>public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } Random random = new Random(); ObservableCollection<string> targetItems => (targets.ItemsSource as ObservableCollection<string>); ObservableCollection<int> sourcesItems => (sources.ItemsSource as ObservableCollection<int>); private int CreateSourceValue() => random.Next(0, 99); private int GetRandomIndex<T>(Collection<T> collection) => random.Next(0, collection.Count); private void AddSourceButton_Click(object sender, RoutedEventArgs e) => sourcesItems.Add(CreateSourceValue()); private void AddTargetButton_Click(object sender, RoutedEventArgs e) => targetItems.Add($"A:{CreateSourceValue()}"); private void RemoveSourceButton_Click(object sender, RoutedEventArgs e) => sourcesItems.RemoveAt(GetRandomIndex(sourcesItems)); private void RemoveTargetButton_Click(object sender, RoutedEventArgs e) => targetItems.RemoveAt(GetRandomIndex(targetItems)); private void ReplaceSourceButton_Click(object sender, RoutedEventArgs e) => sourcesItems[GetRandomIndex(sourcesItems)] = CreateSourceValue(); private void ReplaceTargetButton_Click(object sender, RoutedEventArgs e) => targetItems[GetRandomIndex(targetItems)] = $"R:{CreateSourceValue()}"; private void Move<T>(ObservableCollection<T> collection) { int indexOld = GetRandomIndex(collection); int indexNew = GetRandomIndex(collection); collection.Move(indexOld, indexNew); } private void MoveSourceButton_Click(object sender, RoutedEventArgs e) => Move(sourcesItems); private void MoveTargetButton_Click(object sender, RoutedEventArgs e) => Move(targetItems); private void ClearSourceButton_Click(object sender, RoutedEventArgs e) => sourcesItems.Clear(); private void ClearTargetButton_Click(object sender, RoutedEventArgs e) => targetItems.Clear(); }注意点
デモではわかりやすくするため、ObservableCollectionを両方ともViewにBindingしていましたが、この用途なら、どちらかのListBoxに双方向の変換のConverterを挟んだほうがよいです。
実際はModel層とViewModel層のObservableCollectionを同期したい、といった用途が多いと思います。ColletionChangedイベントの購読を解除する方法は無いため、2つのObservableCollectionの寿命が違う場合はメモリリークします。
参考
http://nomoredeathmarch.hatenablog.com/entry/2019/03/02/180147
全体コード
以下の場所においておきます。
https://github.com/soi013/ObservableColletionSyncedTest/環境
VisualStudio 2019 Version 16.8.3
.NET Core 3.1
C#8
- 投稿日:2021-01-12T00:59:42+09:00
C#による音声の同期・非同期再生
はじめに
この記事は,C#による音声の同期・非同期再生についてまとめた記事である.
主にC#入門者である自分の知識整理のためにまとめたものである.同期再生・非同期再生とは
同期再生とは,音声の再生が終了まで他の処理を行わない再生方法である.
一方,非同期再生は,音声をバックグラウンドで再生する再生方法であるため,再生後も次の処理を行う.
音声を再生中に他の処理を行いたい場合,同期再生で再生してしまうと音声の再生のみ行われるが,非同期再生だと音声の再生と他の処理が同時に行える.同期再生
synchro.csstatic string Method1(){ //処理1を行う Thread.Sleep(3000); //処理2を行う return "hogehoge"; } static void Method2(){ //処理1を行う Thread.Sleep(3000); //処理2を行う } static void Main(string[] args){ string result = Method1(); Method2(); Console.WriteLine(result); Console.ReadLine(); }非同期再生
asynchro.csstatic async Task<string> Method1(){ //処理1を行う await Task.Delay(3000); //(2) //処理2を行う return "hogehoge"; } static void Method2(){ //処理1を行う Thread.Sleep(3000); //処理2を行う } static async Task Main(string[] args){ Task<string> task = Method1(); //(1) Method2(); //(3) Console.WriteLine(task.Result); //(4) Console.ReadLine(); }(1)
関数Method1に入る.
Task型かTask型のジェネリック.
Task型は、非同期メソッドの状態や戻り値を知ることができる.
<>は戻り値の型.(2)
同期からの変更点.
awaitは、Task型を返す非同期メソッドが終了するまで待つ(3000ms待つ、という非同期メソッド).
awaitが付いているメソッドが実行されると一旦そのメソッドを抜け、メインに戻る.
※awaitは戻り値がTask型のメソッドにしか付けることができない.
「Task.Delay」→引数で指定した秒数を待ち,その実行状態をTask型で返す非同期メソッド(3)
Method1を一旦抜けて実行される.
その間にもTask.Delay(3000)は別スレッドで実行中.
Task.Delay(3000)の処理が終わったら、Method2が途中でもMethod1に戻り、文字列を返す.(4)
asyncメソッドの戻り値はTask型であるため、Resultプロパティで戻り値を取得できる.おまけ
完全自分用、とりあえずC#での意味を書いてく
クラス
アプリ内の特定の機能を担うかたまり。
文字列や数字すべてのオブジェクトを普遍的に表現、操作できるような雛形のようなもの。
例えば、文字列ならstringというクラスで、数学的機能はMathというクラスで表現する。
Console.WriteLineやConsole.ReadLineはConsoleクラスの静的メソッド(オブジェクトを生成しなくてもクラスから直接呼び出せるメソッドのこと、クラスメソッドとも呼ぶ)である。
あとTaskもクラス。Task型。メソッド
データを操作するための道具。
クラスの動作や処理、振る舞いを表現するもの。
主に、クラスで管理されているデータ(フィールド)の値を操作するためのもの。
関数と似ているが、厳密には異なるものである。(違いについてはまた今度調べる)
フィールド(変数)とメソッドのことを総称してメンバーという。修飾子
public→すべてのクラスからアクセス可能
private→同じクラス内からのみアクセス可能(既定)
static→静的メンバーを指定