SQLite で LINQ を使う

と或るところで、さらっと答えてしまったら間違っていたので、仕切り直しがてらに進呈致します。どうやら、昔、どこかでオンメモリの設定で使っていてその知識がそのままだったようです。ファイルベースで動いていますね、SQLiteは。後述しますが、接続文字列のところで「DataSource=sample.db」のようにファイル名ではなくて「DataSource=:memory:」にするとオンメモリのDBとして動作します。

System.Data.SQLite を使う

SQLiteの本家サイトから落としてもいいのですが、手っ取り早く NuGet で落とします。

.NET Core 用には System.Data.SQLite.Core があるのと、Windows IoT Core のような UWP アプリで内部でデータベースを扱うのに SQLite 一択となるので、一度使ってみるといいかもしれません。NuGet には「Microsoft.Data.SQLite」もあるので、多少乱立ぎみなのかも。

SQLiteにテーブルを作る

実験用に users テーブルを作ります。SQL文は、一度 PupSQLite で作ったものから吐き出しています。

Pup’s Atelier-Software
https://www.eonet.ne.jp/~pup/software.html

オンメモリにする場合は、コネクションをクローズするたびに無くなってしまう(揮発性)なので、アプリが起動したときにコネクションを開いて、終わるときに閉じるという具合でしょう。

{
    string sql = @"
CREATE TABLE [users] (
[id] VARCHAR(256),
[username] VARCHAR(256) NOT NULL,
[email] VARCHAR(256),
[birthday] DATETIME,
[age] INTEGER,
[memo1] VARCHAR(256),
[memo2] VARCHAR(256),
[memo3] VARCHAR(256),
[memo4] VARCHAR(256),
[memo5] VARCHAR(256),
[memo6] VARCHAR(256),
[memo7] VARCHAR(256),
[memo8] VARCHAR(256),
[memo9] VARCHAR(256),
[memo10] VARCHAR(256),
PRIMARY KEY(id)
);";
    var cn = new SQLiteConnection("DataSource=" + db_file);
    var cmd = new SQLiteCommand(sql, cn);
    cn.Open();
    cmd.ExecuteNonQuery();
    cn.Close();
    textMsg.Text = "Users テーブルを作成しました";
}

Memo1 から Memo10 のデータは、ファイルを巨大にするためのダミーデータ用です。このテーブルで作ると10万件で、500MB ぐらいのファイルができあがります。

Userクラスを作る

LINQ で使えるように Entity クラスを作ります。
カラムの型は指定しなくても、うまくマッピングできています。Table属性とColumn属性のNameを指定すればokです。

using System.Data.Linq.Mapping;

[Table(Name ="users")]
class User
{
    [Column(Name ="id", IsPrimaryKey = true )]
    public string Id { get; set; }
    [Column(Name = "username", CanBeNull = false)]
    public string UserName { get; set; }
    [Column(Name = "email",CanBeNull = true)]
    public string Email { get; set; }
    [Column(Name = "birthday", CanBeNull = true)]
    public DateTime? Birthday { get; set; }
    [Column(Name = "age" ,CanBeNull = true)]
    public int? Age { get; set; }

    [Column(Name = "memo1")]
    public string Memo1 { get; set; }
    [Column(Name = "memo2")]
    public string Memo2 { get; set; }
    [Column(Name = "memo3")]
    public string Memo3 { get; set; }
    [Column(Name = "memo4")]
    public string Memo4 { get; set; }
    [Column(Name = "memo5")]
    public string Memo5 { get; set; }
    [Column(Name = "memo6")]
    public string Memo6 { get; set; }
    [Column(Name = "memo7")]
    public string Memo7 { get; set; }
    [Column(Name = "memo8")]
    public string Memo8 { get; set; }
    [Column(Name = "memo9")]
    public string Memo9 { get; set; }
    [Column(Name = "memo10")]
    public string Memo10 { get; set; }
}

検索用の LINQ

System.Data.Linqを参照設定しておいて、DataContextクラスを使います。テーブルを参照するときは、GetTableメソッドで指定のテーブルを取得。

private void clickCount(object sender, RoutedEventArgs e)
{
    var sw = new Stopwatch();
    sw.Start();
    var cn = new SQLiteConnection("DataSource=" + db_file);
    var context = new DataContext(cn);
    var users = context.GetTable<User>();
    int count = users.Count();

    textMsg.Text = $"{count} 件のデータがあります";
    textTime.Text = $"{sw.ElapsedMilliseconds} msec";
}

すると、LINQ が使えるようになるので、where文などを使って条件を指定できるようになります。

private void clickAge(object sender, RoutedEventArgs e)
{
    var sw = new Stopwatch();
    sw.Start();
    var cn = new SQLiteConnection("DataSource=" + db_file);
    var context = new DataContext(cn);
    var users = context.GetTable<User>();
    var q = from t in users
            where 40 <= t.Age && t.Age < 50
            select t;
    var count = q.Count();
    sw.Stop();
    textMsg.Text = $"{count} 件のデータがあります";
    textTime.Text = $"{sw.ElapsedMilliseconds} msec";
}

10万件のデータを検索したところ(インデックスなし)、300 mesc ちょっとで返って来ます。

メモリの具合はどうかというと、Working set の private が 30 MB 程度。このとき sample.db ファイルの大きさは 500MB 程度あるので、ファイルアクセスをしています。年齢で検索するために clickAge を呼び出した瞬間に HDD アクセスが大量発生してます。

となると検索スピードは HDD/SSD のスピードに依るということですね。

データ作成

データ作成をする INSERT は意外と遅いです。ロジックが多少複雑だというのもあるんでしょうが、1000件作るのに7秒程度かかります。オンメモリにすると 300msec 程度なので insert スピードが 20倍ぐらい違います。

private void clickCreateData(object sender, RoutedEventArgs e)
{
    var cn = new SQLiteConnection("DataSource=" + db_file); //  ";SyncMode=off;JournalMode=Memory");
    var context = new DataContext(cn);
    var users = context.GetTable<User>();
    _rnd = new Random();
    int max = int.Parse(textSize.Text);
    var sw = new Stopwatch();
    sw.Start();
    for ( int i=1; i<=max; i++ )
    {
        users.InsertOnSubmit(makeUser());
        if ( i % 100 == 0 )
        {
            context.SubmitChanges();
            Debug.WriteLine($"{i} 件 挿入...");
        }
    }
    context.SubmitChanges();
    sw.Stop();
    textMsg.Text = $"{max} 件のデータを挿入しました";
    textTime.Text = $"{sw.ElapsedMilliseconds} msec";
}

private User makeUser()
{
    var user = new User();
    user.Id = Guid.NewGuid().ToString("D");
    user.UserName = createName();
    user.Email = createEmail(user.UserName);
    user.Birthday = null;
    user.Age = _rnd.Next(10, 100);
    user.Memo1 = createMemo();
    user.Memo2 = createMemo();
    user.Memo3 = createMemo();
    user.Memo4 = createMemo();
    user.Memo5 = createMemo();
    user.Memo6 = createMemo();
    user.Memo7 = createMemo();
    user.Memo8 = createMemo();
    user.Memo9 = createMemo();
    user.Memo10 = createMemo();

    return user;
}
private string createName()
{
    // xxxxxx xxxxxx
    var name = "Aaaaaa Aaaaaa";
    var ch = name.ToCharArray();
    for (int i = 0; i < ch.Length; i++) {
        if (ch[i] == ' ') continue;
        ch[i] = (char)(ch[i] + _rnd.Next(26));
    }
    return new string(ch);
}
private string createEmail( string username )
{
    // xxxxxx xxxxxx
    var name = username.Split(' ')[0];
    return name + "@mail.com";
}
private string createMemo(int size = 256)
{
    var ch = new char[size];
    for (int i = 0; i < size; i++)
    {
        ch[i] = (char)('A' + _rnd.Next(26));
    }
    return new string(ch);
}

機会を作って、.NET Core のほうの SQLite も試してみるということで。

サンプルコード

サンプルコードはこちら
https://1drv.ms/u/s!AmXmBbuizQkXgfsUYttAgWYXnhiogw

参考先

C# で SQLite を便利に使うサンプルコード(LINQ to SQLite) – 翔星 Be ランド日記
http://shinta0806be.ldblog.jp/archives/9084539.html

In-Memory Databases
https://www.sqlite.org/inmemorydb.html

 

カテゴリー: SQLite | 1件のコメント

ASP.NET Core MVC の Web API で JSON 形式のデータを扱う

Web API の JSON 形式に関しては、

Building Your First Web API with ASP.NET Core MVC and Visual Studio ? ASP.NET documentation
https://docs.asp.net/en/latest/tutorials/first-web-api.html

に詳しい解説があります。
が、クライアント側が書いていないので、先の XML 形式と同じように WFP アプリでクライアントを書いていきます。

送受信の形式

  • WPF アプリで JSON 形式で送信
  • ASP.NET Core Web API で JSON 形式で返信

することを考える。データはいちいち JSON 形式に直すのは面倒なので、C# のクラスから Newtonsoft.Json.JsonSerializer を使ってシリアライズ/デシリアライズをする。

Web API 側の設定

XML 形式の場合は Formatters を追加したが、JSON の場合はもともとロードされているので不要。
JsonOutputFormatter と JsonInputFormatter が初期値で使われている。

Web API の PeopleController クラスを作る

Modelクラスである Person クラスを作っておく。

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

JsonSerializer は、IEnumerable<Person> もでシリアライズしてくれるので、XML 形式のような People クラスは不要で、そのまま使える。

コントローラーを作るときに「Entitiy Frameworkを使用したアクションがあるAPIコントローラー」を選べば、CURD機能のAPIが、ずらっと出力される。

このまま動くので改変しなくてよい。

[Produces("application/json")]
[Route("api/People")]
public class PeopleController : Controller
{
    private readonly ApplicationDbContext _context;

    public PeopleController(ApplicationDbContext context)
    {
        _context = context;
    }

    // GET: api/People
    [HttpGet]
    public IEnumerable<Person> GetPerson()
    {
        return _context.Person;
    }

    // GET: api/People/5
    [HttpGet("{id}")]
    public async Task<IActionResult> GetPerson([FromRoute] int id)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Person person = await _context.Person.SingleOrDefaultAsync(m => m.Id == id);

        if (person == null)
        {
            return NotFound();
        }

        return Ok(person);
    }

    // PUT: api/People/5
    [HttpPut("{id}")]
    public async Task<IActionResult> PutPerson([FromRoute] int id, [FromBody] Person person)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (id != person.Id)
        {
            return BadRequest();
        }

        _context.Entry(person).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!PersonExists(id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        // return NoContent();
        return await GetPerson(person.Id);
    }

    // POST: api/People
    [HttpPost]
    public async Task<IActionResult> PostPerson([FromBody] Person person)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        _context.Person.Add(person);
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateException)
        {
            if (PersonExists(person.Id))
            {
                return new StatusCodeResult(StatusCodes.Status409Conflict);
            }
            else
            {
                throw;
            }
        }

        return CreatedAtAction("GetPerson", new { id = person.Id }, person);
    }

    // DELETE: api/People/5
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeletePerson([FromRoute] int id)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Person person = await _context.Person.SingleOrDefaultAsync(m => m.Id == id);
        if (person == null)
        {
            return NotFound();
        }

        _context.Person.Remove(person);
        await _context.SaveChangesAsync();

        return Ok(person);
    }

    private bool PersonExists(int id)
    {
        return _context.Person.Any(e => e.Id == id);
    }
    /// <summary>
    /// 受け口を POST に変換する
    /// </summary>

    [HttpPost("{id}")]
    [Route("Edit/{id}")]
    public async Task<IActionResult> Edit([FromRoute] int id, [FromBody] Person person)
    {
        return await PutPerson(id, person);
    }
    [HttpPost]
    [Route("Create")]
    public async Task<IActionResult> Create([FromBody] Person person)
    {
        return await PostPerson(person);
    }
}

クライアントからの表示の都合上 PutPerson の戻り値を変えている。
あと、POST 形式だけで通るように、Edit と Create を追加している。

WPF クライアントを作る

XML 形式のときと同じように、Person クラスだけを作る。
JsonSerializer が使えるように、Newtonsoft.Json を NuGet で参照設定させておく。

namespace ClientJson
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private async void clickGet(object sender, RoutedEventArgs e)
        {
            var hc = new HttpClient();
            // hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var res = await hc.GetAsync("http://localhost:5000/api/people");
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;

            var js = new Newtonsoft.Json.JsonSerializer();
            var jr = new Newtonsoft.Json.JsonTextReader( new System.IO.StringReader(str));
            var items = js.Deserialize<IEnumerable<Person>>(jr);
            textPerson.Text = "";
            foreach (var item in items)
            {
                textPerson.Text += $"{item.Id} {item.Name} {item.Age} n";
            }
        }

        private async void clickGetById(object sender, RoutedEventArgs e)
        {
            int id = 2;
            var hc = new HttpClient();
            // hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var res = await hc.GetAsync($"http://localhost:5000/api/people/{id}");
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;

            var js = new Newtonsoft.Json.JsonSerializer();
            var jr = new Newtonsoft.Json.JsonTextReader(new System.IO.StringReader(str));
            var item = js.Deserialize<Person>(jr);
            textPerson.Text = $"{item.Id} {item.Name} {item.Age}";
        }

        private async void clickPutById(object sender, RoutedEventArgs e)
        {
            var person = new Person() { Id = 2, Name = "update person", Age = 99 };
            var js = new Newtonsoft.Json.JsonSerializer();
            var sw = new System.IO.StringWriter();
            js.Serialize(sw, person);
            var hc = new HttpClient();
            // hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var json = sw.ToString();
            var cont = new StringContent(json, Encoding.UTF8, "application/json");
            var res = await hc.PutAsync($"http://localhost:5000/api/people/{person.Id}", cont);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;

            var jr = new Newtonsoft.Json.JsonTextReader(new System.IO.StringReader(str));
            var item = js.Deserialize<Person>(jr);
            textPerson.Text = $"{item.Id} {item.Name} {item.Age}";
        }

        private async void clickPost(object sender, RoutedEventArgs e)
        {
            var person = new Person() { Id = 0, Name = "new person", Age = 88 };
            var js = new Newtonsoft.Json.JsonSerializer();
            var sw = new System.IO.StringWriter();
            js.Serialize(sw, person);
            var hc = new HttpClient();
            // hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var json = sw.ToString();
            var cont = new StringContent(json, Encoding.UTF8, "application/json");
            var res = await hc.PostAsync($"http://localhost:5000/api/people", cont);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;

            var jr = new Newtonsoft.Json.JsonTextReader(new System.IO.StringReader(str));
            var item = js.Deserialize<Person>(jr);
            textPerson.Text = $"{item.Id} {item.Name} {item.Age}";
        }

        private void clickDeleteById(object sender, RoutedEventArgs e)
        {
        }

        private async void clickCreate(object sender, RoutedEventArgs e)
        {
            var person = new Person() { Id = 0, Name = "new person", Age = 88 };
            var js = new Newtonsoft.Json.JsonSerializer();
            var sw = new System.IO.StringWriter();
            js.Serialize(sw, person);
            var hc = new HttpClient();
            // hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var json = sw.ToString();
            var cont = new StringContent(json, Encoding.UTF8, "application/json");
            var res = await hc.PostAsync($"http://localhost:5000/api/people/Create", cont);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;

            var jr = new Newtonsoft.Json.JsonTextReader(new System.IO.StringReader(str));
            var item = js.Deserialize<Person>(jr);
            textPerson.Text = $"{item.Id} {item.Name} {item.Age}";
        }

        private async void clickEdit(object sender, RoutedEventArgs e)
        {
            var person = new Person() { Id = 2, Name = "edit person", Age = 99 };
            var js = new Newtonsoft.Json.JsonSerializer();
            var sw = new System.IO.StringWriter();
            js.Serialize(sw, person);
            var hc = new HttpClient();
            // hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            var json = sw.ToString();
            var cont = new StringContent(json, Encoding.UTF8, "application/json");
            var res = await hc.PostAsync($"http://localhost:5000/api/people/Edit/{person.Id}", cont);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;

            var jr = new Newtonsoft.Json.JsonTextReader(new System.IO.StringReader(str));
            var item = js.Deserialize<Person>(jr);
            textPerson.Text = $"{item.Id} {item.Name} {item.Age}";
        }
    }
}
namespace SampleWebApiXml.Models
{
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
}
  • Web APIの戻り値形式が、デフォルトでJSONなので、 hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/json”)); は、設定しなくてもよい。
  • Person クラスと JSON 形式は、JsonSerializer.Serialize と Deserializeを使えばよい。
  • これも、日本語を通すためにはきちんと UTF8 エンコードが必要かも。

でもって、うまく動くと dotnet run でサーバーを起動、WPF クライアントから JSON 形式で送受信ができるようになる。ここまで、できるようになれば、クライアントを Javascript や Ruby に切り替えたり、サーバー側を CakePHP で切り替えて相互に動かせるようになる。

サンプルコード

動作できるサンプルコードはこちら
https://1drv.ms/u/s!AmXmBbuizQkXgfsRYiYpmZzonGIBZw

カテゴリー: ASP.NET | ASP.NET Core MVC の Web API で JSON 形式のデータを扱う はコメントを受け付けていません

ASP.NET Core MVC の Web API で XML 形式のデータを扱う

ASP.NET Core の Web API は標準で JSON 形式を扱うようになっているので、XML 形式を扱おうとすると苦労します…というか、苦労したのでメモ書き。

送受信の形式

Web API を POST で送信する場合 Body に何の形式を使うのか、というのと、受信に何の形式を使うのか、で組み合わせがある。

送信側
– フォーム形式 application/x-www-form-urlencoded
– JSON 形式 application/json
– XML 形式 application/xml あるいは text/xml

受信側
– JSON 形式 application/json
– XML 形式 application/xml あるいは text/xml

で、最近はブラウザ経由で JSON 形式で送受信することが多いので、そっちの情報は比較的多いのだが、XML 形式の情報がない。というか、WCF がそれを担っていたのだけど、WCF 自体が廃盤になっている。
なので、試験的に

– WPF アプリで XML 形式で送信
– ASP.NET Core Web API で XML 形式で返す

ということが考える。

テストプロジェクトを作る

ASP.NET Core Web Applicaiton(.NET Core) を使う.

Web API 側の設定

project.json に

"Microsoft.AspNetCore.Mvc.Formatters.Xml": "1.0.0"

を加える。

Setup.cs の Setup.ConfigureServices に XmlSerializerOutputFormatter と XmlSerializerInputFormatter を追加する。

services.AddMvc();
// add XML output formatter
services.Configure<Microsoft.AspNetCore.Mvc.MvcOptions>(
    options => {
        options.OutputFormatters.Add(
            new Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter());
        options.InputFormatters.Add(
            new Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerInputFormatter());
    });

OutputFormatters がアウトプット用で、InputFormattersがインプット用なので、フォーム形式で受けてXML形式で返す場合には、OutputFormattersだけでよい。
XML形式には、XmlDataContractSerializerOutputFormatter もあるの。これはクライアントと形式を揃える必要がある。じゃないとデシリアライズができない。

これで ASP.NET Core 側の XML 形式で送受信する設定は完了。

Web API の PeopleController クラスを作ってみる

Modelクラスである Person クラスを作っておく。

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

#if false
// これでは XMLデシリアライズできない
public class People : List<Person> { }
#endif

public class People
{
    // プロパティにして List 化すると通る
    public List<Person> Items { get; set; }
}

実は、List<Person> を使いたいのだが、XML形式でシリアライズするときに ArrayOfPerson のように変換されてデシリアライズでうまくいかない。仕方がないので、People クラスのように、中身に List を含んだラップクラスを作る。

この Person クラスからコードファーストでデータベースを作った後、PeopleController クラスを作る。

 [Produces(&quot;application/xml&quot;)]
 [Route(&quot;api/People&quot;)]
public class PeopleController : Controller
{
    private readonly ApplicationDbContext _context;

    public PeopleController(ApplicationDbContext context)
    {
        _context = context;
    }

    // GET: api/People
    [HttpGet]
#if false
	public IEnumerable<Person> GetPerson()
	{
		return _context.Person;
	}
#else
    public async Task<People> GetPerson()
    {
        var people = new People();
        people.Items = new List<Person>();
        await _context.Person.ForEachAsync(p => people.Items.Add(p));
        return people;
    }
#endif
    // GET: api/People/5
    [HttpGet(&quot;{id}&quot;)]
    public async Task<IActionResult> GetPerson([FromRoute] int id)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Person person = await _context.Person.SingleOrDefaultAsync(m => m.Id == id);

        if (person == null)
        {
            return NotFound();
        }

        return Ok(person);
    }

    // PUT: api/People/5
    [HttpPut(&quot;{id}&quot;)]
    public async Task<IActionResult> PutPerson([FromRoute] int id, [FromBody] Person person)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (id != person.Id)
        {
            return BadRequest();
        }

        _context.Entry(person).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!PersonExists(id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        // return NoContent();
        // return CreatedAtAction(&quot;GetPerson&quot;, new { id = person.Id }, person);
        return await GetPerson(person.Id);
    }

    // POST: api/People
    [HttpPost]
    public async Task<IActionResult> PostPerson([FromBody] Person person)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        _context.Person.Add(person);
        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateException)
        {
            if (PersonExists(person.Id))
            {
                return new StatusCodeResult(StatusCodes.Status409Conflict);
            }
            else
            {
                throw;
            }
        }

        return CreatedAtAction(&quot;GetPerson&quot;, new { id = person.Id }, person);
    }

    // DELETE: api/People/5
    [HttpDelete(&quot;{id}&quot;)]
    public async Task<IActionResult> DeletePerson([FromRoute] int id)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        Person person = await _context.Person.SingleOrDefaultAsync(m => m.Id == id);
        if (person == null)
        {
            return NotFound();
        }

        _context.Person.Remove(person);
        await _context.SaveChangesAsync();

        return Ok(person);
    }

    private bool PersonExists(int id)
    {
        return _context.Person.Any(e => e.Id == id);
    }

    /// <summary>
    /// 受け口を POST に変換する
    /// </summary>

    [HttpPost(&quot;{id}&quot;)]
    [Route(&quot;Edit/{id}&quot;)]
    public async Task<IActionResult> Edit([FromRoute] int id, [FromBody] Person person)
    {
        return await PutPerson(id, person);
    }
    [HttpPost]
    [Route(&quot;Create&quot;)]
    public async Task<IActionResult> Create([FromBody] Person person)
    {
        return await PostPerson(person);
    }

}

Entity Framework から取得するように修正してあるので、ややこしくなっているけど、ASP.NET Core MVC のスキャフォールディングと合わせるように、Create や Update で通るようにしてある。
フォーム形式の場合は、Bind 属性で値を取るが、JSONやXML形式の場合は FromBody 属性でバインドする。このあたり、ちょっと混乱しているような気がする。

PeopleController クラスの Produces 属性は、デフォルトで返す Content-type を指定するらしい。

WPF クライアントを作る

本来ならば、Person, People クラスをアセンブリで共有するほうがいいのだが、ASP.NET Core は .NET Core のライブラリで、WPF クライアントは .NET Framework のライブラリなので共有できない。が、XML形式やJSON形式でシリアライズしてやり取りするだけなので、クラス名やプロパティ名だけ合わせておけばよい。

namespace ClientXml
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void clickGet(object sender, RoutedEventArgs e)
        {
            var hc = new HttpClient();
            hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/xml&quot;));
            var res = await hc.GetAsync(&quot;http://localhost:5000/api/people&quot;);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;
#if false
            // ArrayOfPerson は取れない
            var xs = new System.Xml.Serialization.XmlSerializer(typeof(IEnumerable<Person>));
            var items = xs.Deserialize(new System.IO.StringReader(str)) as IEnumerable<Person>;
            textPerson.Text = &quot;&quot;;
            foreach ( var item in items )
            {
                textPerson.Text += $&quot;{item.Id} {item.Name} {item.Age} n&quot;;
            }
#endif
            var xs = new System.Xml.Serialization.XmlSerializer(typeof(People));
            var people = xs.Deserialize(new System.IO.StringReader(str)) as People;
            textPerson.Text = &quot;&quot;;
            foreach (var item in people.Items)
            {
                textPerson.Text += $&quot;{item.Id} {item.Name} {item.Age} n&quot;;
            }
        }

        private async void clickGetById(object sender, RoutedEventArgs e)
        {
            int id = 2;
            var hc = new HttpClient();
            hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/xml&quot;));
            var res = await hc.GetAsync($&quot;http://localhost:5000/api/people/{id}&quot;);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;
            var xs = new System.Xml.Serialization.XmlSerializer(typeof(Person));
            var item = xs.Deserialize(new System.IO.StringReader(str)) as Person;
            textPerson.Text = $&quot;{item.Id} {item.Name} {item.Age}&quot;;
        }

        private async void clickPutById(object sender, RoutedEventArgs e)
        {
            var person = new Person() { Id = 2, Name = &quot;update person&quot;, Age = 99 };
            var xs = new System.Xml.Serialization.XmlSerializer(typeof(Person));
            var hc = new HttpClient();
            hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/xml&quot;));
            var sw = new System.IO.StringWriter();
            // 先頭の <?xml ... をカットする
            var settings = new System.Xml.XmlWriterSettings() { OmitXmlDeclaration = true, Encoding = Encoding.UTF8 };
            var xw = System.Xml.XmlWriter.Create(sw, settings);
            xs.Serialize(xw, person);
            var xml = sw.ToString();
            var cont = new StringContent(xml, Encoding.UTF8, &quot;application/xml&quot;);
            var res = await hc.PutAsync($&quot;http://localhost:5000/api/people/{person.Id}&quot;, cont);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;
            var item = xs.Deserialize(new System.IO.StringReader(str)) as Person;
            textPerson.Text = $&quot;{item.Id} {item.Name} {item.Age}&quot;;
        }

        private async void clickPost(object sender, RoutedEventArgs e)
        {
            var person = new Person() { Id = 0, Name = &quot;new person&quot;, Age = 88 };
            var xs = new System.Xml.Serialization.XmlSerializer(typeof(Person));
            var hc = new HttpClient();
            hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/xml&quot;));
            var sw = new System.IO.StringWriter();
            var settings = new System.Xml.XmlWriterSettings() { OmitXmlDeclaration = true, Encoding = Encoding.UTF8 };
            var xw = System.Xml.XmlWriter.Create(sw, settings);
            xs.Serialize(xw, person);
            var xml = sw.ToString();
            var cont = new StringContent(xml, Encoding.UTF8, &quot;application/xml&quot;);
            var res = await hc.PostAsync($&quot;http://localhost:5000/api/people&quot;, cont);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;
            var item = xs.Deserialize(new System.IO.StringReader(str)) as Person;
            textPerson.Text = $&quot;{item.Id} {item.Name} {item.Age}&quot;;
        }

        private void clickDeleteById(object sender, RoutedEventArgs e)
        {
        }

        private async void clickCreate(object sender, RoutedEventArgs e)
        {
            var person = new Person() { Id = 0, Name = &quot;new person&quot;, Age = 88 };
            var xs = new System.Xml.Serialization.XmlSerializer(typeof(Person));
            var hc = new HttpClient();
            hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/xml&quot;));
            var sw = new System.IO.StringWriter();
            var settings = new System.Xml.XmlWriterSettings() { OmitXmlDeclaration = true, Encoding = Encoding.UTF8 };
            var xw = System.Xml.XmlWriter.Create(sw, settings);
            xs.Serialize(xw, person);
            var xml = sw.ToString();
            var cont = new StringContent(xml, Encoding.UTF8, &quot;application/xml&quot;);
            var res = await hc.PostAsync($&quot;http://localhost:5000/api/people/Create&quot;, cont);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;
            var item = xs.Deserialize(new System.IO.StringReader(str)) as Person;
            textPerson.Text = $&quot;{item.Id} {item.Name} {item.Age}&quot;;
        }

        private async void clickEdit(object sender, RoutedEventArgs e)
        {
            var person = new Person() { Id = 2, Name = &quot;update person&quot;, Age = 99 };
            var xs = new System.Xml.Serialization.XmlSerializer(typeof(Person));
            var hc = new HttpClient();
            hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(&quot;application/xml&quot;));
            var sw = new System.IO.StringWriter();
            // 先頭の <?xml ... をカットする
            var settings = new System.Xml.XmlWriterSettings() { OmitXmlDeclaration = true, Encoding = Encoding.UTF8 };
            var xw = System.Xml.XmlWriter.Create(sw, settings);
            xs.Serialize(xw, person);
            var xml = sw.ToString();
            var cont = new StringContent(xml, Encoding.UTF8, &quot;application/xml&quot;);
            var res = await hc.PostAsync($&quot;http://localhost:5000/api/people/Edit/{person.Id}&quot;, cont);
            var str = await res.Content.ReadAsStringAsync();
            textXml.Text = str;
            var item = xs.Deserialize(new System.IO.StringReader(str)) as Person;
            textPerson.Text = $&quot;{item.Id} {item.Name} {item.Age}&quot;;
        }
    }
}
namespace SampleWebApiXml.Models
{
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
    public class People
    {
        public List<Person> Items { get; set; }
    }
}

試行錯誤した結果を載せているので、これが一番良いというわけではない。
いくつか、ポイントがあるのでざっと解説をしておくと、

– XML 形式で受信するために hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(“application/xml”)); を付ける。HTTP プロトコルの Accept ヘッダに受信する形式を指定しておくと、Web API が Accept にあわせて送ってくれる。
– POST や PUT で送信するときに、StringContent(xml, Encoding.UTF8, “application/xml”); のように Content-type を指定する。これを指定しないと、Web API 側で XML データとして認識してくれない。
– XmlSerializer は、何故か UTF16 でシリアライズするので、先頭の <?xml … を取り除くために、System.Xml.XmlWriterSettings で設定をする。ASCII 文字でしかテストしていないが、日本語を通す場合はきちんと string 型から UTF8 エンコードをしたほうがいいかもしれない.

うまく実行できると、こんな風にサーバ側を dotnet run で動かして、WPF アプリから送受信できるようになる。

きちっとした説明は別の機会にでも…

サンプルコード

動作できるサンプルコードはこちら
https://1drv.ms/u/s!AmXmBbuizQkXgfsQuu9Ly1NjTHa6nQ

最初に dotnet ef database update を使ってデータベースを作らないといけないかも。

カテゴリー: ASP.NET | ASP.NET Core MVC の Web API で XML 形式のデータを扱う はコメントを受け付けていません

micro:bit 互換機で夏休みの自由研究を

毎度のことながら夏休みの自由研究に親は悩まされるのですが、今年の夏は(建前上ながら)大丈夫です。と、言いますか、子供の自由研究めあてに micro:bit 互換機のモニターに応募して当たりましたので、そのまま有り難く自由研究に使わせて頂きました。micro:bit互換機の試作機モニターへのご応募ありがとうございました【スイッチサイエンスチャンネル】 ちなみに、互換機は MFT2016(今週末の土日です)に販売されるそうなので、MFT2016にて、micro:bit互換機「chibi:bit」のテスト版を販売します | スイッチサイエンス マガジン こっちのほうも是非。

micro:bit とは

BBC が英国の小学生向けに無償配布した電子工作キットです。BBC micro:bit : home から辿ると micro:bit で何ができそうかが分かります。中身が mbed なので、たぶんインターネット上の mbed 環境でも作れるはずですが、子供用に Scratch に似たような Block Editor を使ってプログラミングをします。

image

ブラウザ上で、ぽちぽちとブロックを重ねるのと、「run」ボタンを押すと左に出てるシミュレーターで動かすことができます。なので、1回ごとに実機に転送しなくてよいのです。けど、試行錯誤的にはこれで十分だけど、傾きセンサーとか音を鳴らすとかの動きの実際の動きまでは試せないので、やっぱり実機への転送は必須です。

まったく宣伝されてませんが、実は Block Editor は Microsoft が作っています(日本で話題になってないだけですかね?)。

image

また、使ってはいないのですが、JavaScript や python も使えるのでがりがり組みたい場合(?)は、そっちのほうがいいかもしれません。

まあ、小6の自由研究にいきなりプログラムコードを…と言っても敷居が高すぎるので、ぽちぽちと並べて作ります。このあたり Scratch に喰い付く子であれば子供だけでも進むんでしょうが、そのあたりは「自由研究」として、説明しながら手を出しながらという具合で進めます。

自由研究の様子

30分ほど micro:bit の解説をして、ぽちぽちとプログラミング状態に突入。ほんとうは、加速度センサーとか電子ブザーとか付ければいいんですが、準備もあるし pin の説明をするのもアレなので、A, B ボタンと、シェイクで LED を光らせるところだけやります。

image

5×5 の LED を光らせるのは簡単になっています。show leds のブロックを使うか、文字列(アルファベットだけど)を流れるように表示する show string のブロックを使います。サンプルコードには30分とか書いてあるけど、最初にやるとそれなりに時間がかかるし工作も入ると1日では済みそうもないので、そこは手抜きで(苦笑)。

変数を使ってとか、if文を使って、とかあれこれやると時間が掛かりそうなので、A/B ボタンを押したときの処理だけ羅列した状態です。LED の配置が連なっているだけでもアニメーションっぽく動きます。途中の pause はなくても、一定時間 LED は光る仕様のようなので省略。

実はループを使っていると、途中でボタンを押しても反応しないんですよね…、ってのが分かったので途中で反応するように組み替えてもいいんですが、それはまあ「自由研究」の範疇を超えるので、また時間があったらということで。

image

micro:bit はパソコンから micro USBケーブルで電源供給するか、コネクタに接続するかなんですが、ひとまず携帯用のモバイル電源で光るのでお試しでやってみます。後から micro USB 経由で電池から供給できるように改造します。そうしないと学校に持って行けないし。

image

単体で持って行っても何やら分からないので、micro:bit の解説と、ちょっとだけ仕様っぽいものを書いておきます。裏には先に書いた Block Editor の画面キャプチャを印刷して貼り付けておしまい。micro:bit の説明から開始して2時間弱で「自由研究」の終了です。まあ、この時間で自由研究にしていいかどうかはさておき、夏休みの自由研究の素材としてはよいかなと。

これからの micro:bit

ひとまず、手元の micro:bit 互換機は、夏休みの自由研究の作品として学校に持って行ってしまうので帰ってきたら、ということになりますが、せっかく BLE もついているし、加速度センサーと磁気センサーもついているので、このあたりは試しておきたいですね。あと、圧電ブザーを鳴らすとかワニクリップを使って定番っぽいものをやっておく予定。

カテゴリー: 雑談 | micro:bit 互換機で夏休みの自由研究を はコメントを受け付けていません

.NETラボ勉強会 in 仙台でロボットアームを動かしたよ

.NETラボ 勉強会 2016年6月 in 仙台 | .NETラボ まで手持ちのロボットアームを持って行って1枠話した来たので、そのひとり反省会。みなさまありがとうございました。

仙台での写真を撮り忘れてしまったのですが、

こんな材料を使って

こんなものを動かします。Raspberry Pi – Arduino – Robot Arm というややこしい感じになっていますが、Windows IoT Core in Raspberry Pi から直接は細かくサーボを制御できないので、Arduino を噛まします。Raspberry Pi – Arduino 間はシリアル通信なので、実は PC – Arduino で USB ケーブル接続でも可能なのですが…そこは Windows IoT Core を無理矢理使うということで。この部分をコマンド制御にしておくことで、ワタクシ的には ROS への練習とか PLEN2 への練習とかを兼ねています。あと、かつてロボットアームでスマホゲームを動かしたかったので、それも兼ねて。

プレゼン資料

Windows IoT Core and Robot Arm

[slideshare id=63470614&doc=windowsiotcore-160627045432]

デモ用のコード

https://1drv.ms/u/s!AmXmBbuizQkXgflgZRXM-PnmEla5ng

デモについて

ここ1年ぐらいは、LEGO EV3 を持って行ったり、Raspberry Pi を持って行って Windows IoT Core で Lチカしてみたりする訳ですが、IoT 自体の説明時間とデモの説明時間のバランスが難しいです。普通の PC の場合、キーボードとモニタで完結することが多いので、自分としてはデモで「モニタの外側」に出ることを意識してやってる…んですが、自分でやってても「だから何?」の疑問が付きまといます。なんか、ソフトウェア技術的にすごいものと組み合わせてあれこれとハードを動かせれば「すごい」感じがするんでしょうが(まだできないけど)、でもそれだと「すごい」だけで終わっちゃうしなー、という感じ。

今回のロボットアームの件で言えば、

  • 適当なモーションを作って、暫く動きっぱなしでもよかったかも。
    • 1コマンド打ち込みだけだと、6軸同時に制御する、ってのがわかりづらい。
  • やっぱり、Raspberry Pi から大型モニタに出したほうがよい。
    • HDMI がないところが多いので、VGA変換ケーブルを買って試しておく。
  • 説明なしで、コントローラはタブレット(Xamarin)で良いかも。
    • あまり説明なしだと「何かのおもちゃが動いているだけ」になるけど、ポイントだけ解説するとか。
  • 当日、配線するのは結構大変なので(大抵、事前に30分ぐらいしか取れない)、そこだけは基盤にしないと駄目かも。

 

ソフトウェア屋さんがハードを触るときの最初のハードル

西村さん と話して、盛り上がったのでメモ的に

  • ハード特有の用語がわからん。そもそも、ブレッドボードが何か?とか。
    • これは、ソフトウェアの専門用語と同じだから、電子工作のはじめて本で覚えるしかないかな。私はオライリーの「Arduino をはじめよう」が最初の一冊です。
  • 電子部品は何処で買うのかわからない。
    • 昔は、秋葉原に行けばあったけど(今でもあるけど、数が減ったから探しあたる必要があり)、地方の人はどうしているんだろう。むしろ通販だから前よりよくなった?
  • 電子部品を何を買うのかわからない。
    • いさぎよく、最初は「初心者キット」(ちょっと割高だけど)を買うのがベター
    • その後、古めの電子工作の本を買ったのだけど、電子部品の番号が今は売っていなくて互換品を自分で探す必要あり。
  • だいたい2か月頑張れば、次に進める気がする。
    • プログラミングと同じで「慣れ」が必要かなと。キーボードじゃなくて、ブレットボードとジャンプワイヤーで手を動かすので、そっちの方面の「慣れ」が必要。
  • Raspberry Pi, Arduino で得意分野が違うので、両方やっておいたほうが良い。
    • Raspberry Pi だと画像処理とか通信処理とかが楽。
    • Arduino だとマイクロ秒単位の細かい制御ができる。
    • ブラウザ経由でLチカできる、ってのもアリだけど、直接ハードを触って「どんなことが起きているのか」を知っておいたほうがいいかな。

ロボットアームのその先

実験的に自前シリアル通信を作ったけど、Firmata を使うか ROS にしたいですよね。少しずつそれっぽい感じにする予定。ロボットアームからのフィードバックが必要なので、ポテンショメータの値の取得と、加速度センサーの追加、あと Raspberry Pi 側からカメラを引っ張ってきて画像解析できるように、ってところまでが目標。

カテゴリー: Win IoT | .NETラボ勉強会 in 仙台でロボットアームを動かしたよ はコメントを受け付けていません

PLEN2 が到着して初期設定まで

本当は自作ロボットがいいのですが、チートして Kickstarter 経由で PLEN2 を購入しました。日本では Kibidango  のほうが先なのですが、組み立てキットの早期募集が終わっていたので値段が同じぐらいだった Kickstarter のほうに応募。3D プリンタは持っていないのですが、せめて組み立てるということで。発送は日本郵便できました。

開封

元になった PLEN よりもちょっと小さ目です。身長20cm位なので、サーボも小さいですね。バッテリーは充電式の単四x5なもので動かします。胸の部分に充電器、背中の部分に Arduino が乗っている状態になります。

開けて中身を確認したものの、途中で水圧式のロボットアームの組み立てをしているので間があいていますが、組み立て自体はだいたい半日仕事じゃないかなと。特にロボットの組み立てが始めての場合、サーボの基準値とかはめ込みの方向とかあれこれ悩むのでちょっと時間が掛かります。

組み立て

Tutorials [PLEN Playground – Wiki]
http://plen.jp/playground/wiki/tutorials/index.html

に従って、Firmware のインストールからスタートします。バッテリーは既に充電済みなので、即つなげて試すことができました。Arduino にインストールした後に、サーボを繋いでスイッチを入れてピニオンギアにはめ込んで、を18回繰り返します。サーボが18個ありますからね。

サーボに電源を入れた状態で、

な感じで十字の軸を揃えます。揃えますが、きっちりと揃う訳ではありません。どうしてもサーボの基準点とピニオンギアの歯の山とのずれがあるので、ちょっとだけずれます。

この基準点は後でアプリを使って調節していくので、だいたい揃えておけばokです。

ひとまず、こんな感じまで完成させて、電源を入れない状態で立つぐらいまで仕上げます。ロボットアームだと、アームの部分の自重で電源を入れないと関節部分(サーボ部分)で曲がってしまうのですが、PLEN2 の場合は、足裏に重心が乗っかる感じなので、電源が入らない状態でもそのまま立てます。

サーボの原点位置を調節する

背中のスイッチ(Arduino 側とサーボ電源の両方)に電源を入れた状態だと、先のサーボの原点位置がずれているので、ちょっとずれた感じになります。私の場合は、なんか前傾姿勢になっていて、倒れそうな感じになっているし、足もちょっとがに股っぽくなります。実際、このまま歩きのモーションを入れると倒れます(苦笑)。

Tuning Up Home Positions [PLEN Playground – Wiki]
http://plen.jp/playground/wiki/tutorials/plen2/tune

にある ControlServer を使ってサーボの基準点を調節します。Windows の場合は、ControlServer.exe を起動して、PLENUtilities.url でブラウザから調節します。

こんな風なコンソールのサーバーが立ち上がって USB ケーブル経由で PLEN2 のサーボを調節します。

image

ブラウザのほうで、調節したいサーボの番号(位置)をクリックして、Angle をマウスで動かします。このとき PELN2 の実機が動くのでまっすぐになるようにします。調節が終わったら、Home ボタンを押して、そのサーボの設定を PLEN2 に送ります。これをすべての関節分(18回)繰り返します。現状のバージョンでは、サーボの1と13が逆になっています。モーションは問題ないので、ここの表示だけの問題かなと。

image

サーボから角度が送られてくるわけではなく、一方的にサーボ側に角度を通知するだけなので、再び Reset ボタンを押して0度を送ってから調節します。0度から大きく外れてしまった場合は、サーボを外してピニオンギアへのはめ込みをやり直したほうがよさそうです。単位が1/10 deg.(度)なので、歯車1個分(300位?)を目安にすればいいでしょう。

調節するときは、手足がまっすぐになるように、寝かせた状態か手でつりさげた状態でやるとやりやすいです。

ジーっと音がしなくなるまで調節する

サーボの位置を調節しているときに「ジー」という音が鳴ることがあります。これはサーボが鳴っているのですが、ただ立っているだけなのに鳴っているのはうるさいので、音がでなくなるように調節します。

何故、音が鳴るかというと、理由が2パターンあります。

  1. サーボが位置を変えようとする。
  2. 角度が限界までいっているので、それ以上回せない。
  3. けれども、位置設定まで動かそうとするので、モーターがジーっと鳴る。

 

  1. サーボの位置を設定する。
  2. 歯車と重心位置の関係で、ちょっとだけ行き過ぎる。
  3. サーボが位置を戻そうとするが、重さの関係でぎりぎり戻らない。
  4. でもってモーターがジーっと鳴る。

のパターンです。

最初のパターンは可動域よりも大きく廻し過ぎるときで、PLEN2 の場合、股(4,16)、膝(6,18)、足首(8,20)のところで音が鳴りやすくなっています。股の可動域が PLEN 狭すぎるような気がするので、ここは 3D プリンタで要調節かなと思います。

後者の重心が関わる場合は、サーボの角度の分解能に関わるものなので、完全に調節するのは無理です。PELN2 を立たせた状態で、音が鳴らないように膝などを調節します。歯車の遊びの部分があるので、ここは実機調節になります。

既存のモーションを試してみる

iPhone か Android のアプリを入れて既存のモーションを動かしてみます。いきなり、歩かせるとまだバランスが悪くて(微調節が必要な状態)ばたっと倒れますが、踊りのほうはなんとか動きます。モーションが PELN 用なのか、サッカーのモーションを PELN2 に適用すると倒れます(苦笑)。ここが股関節と足首のサーボの可動域の問題かなと。

 

 

 

自分でモーションを作る

自前のモーションは http://plen.jp/playground/motion-editor/ で作れます。可動範囲の制限がないので、コツがいるのですが、まあ、そのあたりはおいおいに。

できあがったモーションは JSON 形式で保存されるので、角度などをエディタで変更できます。

PLEN2 へのアップロードは Releases · plenprojectcompany/plen-MotionInstaller_Win を使って USB 経由で送信します。Arudino 側のスロットに割り当てる方式なので、モーションの空きスロットに転送します(あるいは上書き)。

PLEN2 の各コードは MIT ライセンスで github で配布されているので、自前で修正が可能です。モーションの実行は、ストアアプリ版がないので、プロトコルを見て自前で作ろうかなと思っているところです。

 

 

そんな訳で、ひとまず PLEN2 の組み立てからちょっと動かすところまで。これから先は自前でアプリを作るところからスタート。

  • Windows アプリでモーションの転送
  • Xamarin で手足を個別に動かすツール(モーションの作成用)
  • 加速度センサー(AccelerationGyroSensor)を使った制御(あるのかな?)

な感じです。

カテゴリー: PLEN2 | PLEN2 が到着して初期設定まで はコメントを受け付けていません

Windows IoT Core にリモートで接続する

世の中?は、dotnetConf で盛り上がっていますが、Windows IoT Core のリモート接続の方法を残しておきます。実は、まだ Inside Preview の状態でしか使えないのだけど、Closed Loop Control, Remote Sensors and Remote UX on RPi3 – Hackster.io のサンプルを見ると既に3月からできていたわけで、つまりは //Build/ 2016 の頃からできました、って話ですね。Build で Windows IoT Core の話があったかな…というのは後で調べます。

Remote Display Experience – Windows IoT

Remote display experience

そんな訳で、3か月遅れで試してます。が、結論から言えば、Windows 10 Inside Preview (現状では Slow Ring)を使うのと、Visual Studio で作成する UWP のバージョンが微妙に低いのと、Windows IoT Core 自身のバージョンが微妙に高いのとの兼ね合いで、画面のリモート接続はできるけど、リモートデバッグができません。つまりは Visual Studio からデプロイできないので、なんか変な感じで食い違っていますが、まあ、夏の正式リリースまでには揃えられるかと。というか、揃わないと困る。

Raspberry Pi 3 に Inside Preview 版を入れる

Windows IoT Core の Inside 版は Get Started – Windows IoT から、Raspberry Pi 3 の NOOBS 版からインストールするか、https://www.microsoft.com/en-us/software-download/windowsiot から直接ダウンロードします。どうやら、現時点で RPi3 対応は Inside Preview 扱いなので(正式リリース版ではない)どちらの方法でも 、14342 が使えます。NOOBS 版のほうは、微妙にサイズが大きくなって SD カードは 16 MB が必要です。

リモート接続を有効にする

ブラウザから http://miniwinpc:8080/ で接続すると「Remote」というメニューが増えています。このチェックを入れると、リモート接続が有効になります。

image

リモートアクセスのアプリを入れる

https://www.microsoft.com/en-us/store/apps/iot-remote-client/9nblggh5mnxz から Windows IoT Remote Client をインストールします。

無事接続できると、こんな風に Raspberry Pi 上のモニタが表示できます。マウスでぽちぽちすることもできるし、タッチパネルがあればタッチ操作ができます。

image

ただし、全画面転送しているらしくて妙に遅かったりします。現時点では解像度を 800×600 に落とすと使えるぐらいのスピードになります。

 

 

 

以下は、リモートデバッグしようと思ってあれこれやった結果です。

 

Windows のほうも Inside Preview 版にする

Windows IoT Core のアプリは UWP なのですが、このターゲットバージョンを揃えないといけません。さっき、14342 を入れたのだから、Windows 10 PC のほうも、14342 か、それ以上にします。現状では Slow Ring にして Windows Update すると自動的に入ります。

業務 PC に入れるのは難なので、Hyper-V 上に Windows 10 の環境を作ってバージョンを揃えています。

Windows 10 SDK を入れる

ホーム ページ – Windows Insider Program に参加して、プレビュー版の Windows 10 SDK をインストールします。実は、ここのバージョンが 14332 なので、PC や Windows IoT Core のバージョン 14342 よりも低いんですよね。たぶん、このおかげで Windows IoT Core に対してリモートデバッグができない状態なのだと思います。

でもって、Windows 10 SDK 14332 を入れた状態で Visual Studio からデプロイしようとすると、

image

2>DEP0800 : 必要なフレームワーク “C:\Program Files (x86)\Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.NET.CoreRuntime\1.0\.\AppX\ARM\Microsoft.NET.CoreRuntime.1.0.appx” のインストールに失敗しました。

のようなエラーが発生します。このエラー自体は、StackOverflow でよく見かけるのですが、解決できていません。大抵の回答は、SD カードの焼き直しなんだけど、どうもバージョン違いのような気がする。

アプリのデプロイ自体は、ブラウザから「Apps」→「Install app」で、appx ファイルをアップロードすれば ok です。このあたりはストアアプリと同じなので、デバッグはできませんが、ひとまずアップロードして Windows IoT Core 上で動かすことはできます。

image

そんな訳で、中途半端ですが、ひとまずリモート接続ができるところまで。

カテゴリー: Win IoT | Windows IoT Core にリモートで接続する はコメントを受け付けていません

WordPress に XSS が埋め込まれてから復旧まで

ざっと記録として残しておきます。

5/30 に悪質サイトに登録される

仕事上、執筆やらプログラミングの折りには自分のサイトを Google で検索するのが多いのですが、5/30 の朝に検索すると悪質サイトとして登録されていました。

Google ウェブマスターからはこんな感じ。

image

なんぞ、クラッキングされたのか?と思ったものの、直で moonmile.net からアクセスして当 blog にアクセスすると大丈夫。でも、Google 検索をさせると駄目という状態で。なんら表面上は大丈夫なように見えるんですが、妙にアクセスが遅いのが気になるところでした。ページにアクセスしようとすると、windows defender が警告を発する状態です。

ソースコードが保存できない

表面上、何もないということは XSS を仕組まれたか何かなので、ソースコードを見ます。Firefox ではガードされて見れなかったので、ひとまず Edge で見てエディタに貼り付けて保存しようとすると、windows defender が警告を出します。ってことは、コード自身に何かがある訳ですよね。

そんな訳でちょっとずつ HTML コードを削っていくと、妙な javascript が head タグにくっついているのを発見。それを削ると保存できるので、どうやらここが原因のようです。コード自体は公開できないのですが(悪質サイトになってしまうし)、kicrea[.]it に情報を送っています。

こんなスクリプトが wp_head() の後ろに埋め込まれます。IP のところは怪しいサイトに誘導されています。

image

wordpress の header.php を見る

google の Search Console https://www.google.com/webmasters/tools/home?hl=ja 名にたくさんのページが改竄されています。URL を見るとRSSまで改竄されているのと、先の head タグに書きもまれているので、どうやらヘッダ部分があやしいと推測します。

案の定 header.php に XSS が仕込まれているので削除します。

問題は、ここの blog のテーマだけでなく使っていないテーマの header.php や、相乗りしている header.php にも改竄がはいっていたことです。アクセスログを見ても wordpress からログインした形跡がないし(admin は消し去っています)、そもそも使っていないテーマの header.php を改竄すること自体おかしいし、ファイルの日付を見ると変更されていないので、どうやらターミナルコンソールのほうで何かあった感じ。

フォルダをリネームして google へ再申請

ひとまず、Google 検索で悪質サイトが出続けるのも困るので、フォルダをリネームして、Google に再申請します。元ページを削除した旨を連絡して、まっさらな index.html だけおきます。再申請のチェックは 12時間ぐらい掛かるので、まあ、早めに手を打っておこうかと。

たぶん、共有サーバーだから?

いくつかのサブドメインで運用している wordpress の header.php もやられてしまったので、これも修正します。当然、それらのドメイン(dotnetlab.net や openccpm.com)も悪質サイト扱いになっているので、同じように再申請しておきます。

このブログが乗っているのは、共有サーバーなのでたぶん100名ぐらいが同時に使っているんですよね。改竄された形跡から考えると、wordpress 経由とは考えづらく、ターミナルのパスワードが盗られたとも考えにくいので、共有の他のユーザーから漏れたのかなと。ただし、header.php のパーミッションは 644 -rw-r–r– なので、以前ロリポップ等でやられてしまったようなグループからは外れていると思うんですけどね。ルート権限からユーザーのパスワードが抜かれてしまったのかもしれません(ホスト側から連絡がないのでわからず)。

ひとまず、ターミナルへのパスワードを変更して、header.php を手作業で修正して様子見という状態です。2日間経って元に戻る気配はないので、ここで対処は終了。現時点では google から以前通りアクセスができます。

これ、個人の相乗り wordpress だったからよかったけど、商用サイトとかアフリエイトサイトとかだったら復旧が大変だったろうなと。

カテゴリー: トラブルシューティング | WordPress に XSS が埋め込まれてから復旧まで はコメントを受け付けていません

和風ペンタとブルーノートの比較の続き

和風ペンタトニックとブルーノートは酷似している | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/7893

の続き。タブ譜だけでは分かりづらいので、実際に弾いてみます。ブルーノートを弾いていてもちょっと和風ペンタっぽくなるのは私が日本人だから、ってことで勘弁してもらって、実際弾いてみるこんな感じになる。

和風ペンタとブルーノートの比較

ヨナ抜き(ファとシ)を覚えると大抵の童謡/民謡がこの音に乗っているのが分かる。ただし、完全に乗っているわけではなくてちょいちょいアクセントをつけるのがミソなんだけど、基本はヨナ抜きになる。サザエさんのオープニングもそうだし、ソーラン節もそうだし、植木等のスーダラ節もヨナ抜きになっている。実際に弾いてみると解るけど、ギターでヨナ抜きで上がり下がりをしていると無限に曲が沸いてくる、というか人工無能的にランダムに音を出したとしても和風ペンタに乗っている限りなんとなく曲に聴こえるから不思議だ。

で、ヨナ抜きのスケールをちょっとずらしたのがブルーノートになる。基本は同じスケールになるので、日本人にはヨナ抜きもブルーノートも時に同じように聴こえる。基音が違うので、そこだけ強調するのと、それっぽいリズム(和風ペンタの場合は音頭とか演歌風に弾く、ブルーノートの場合はブルース風にチョーキングを加える)になると、途端にそうなるから不思議だ。

動画の後ろのほうでは、ひとつの曲(っぽいもの)に和風ペンタとブルーノートを混ぜている。強引に混ぜるものだから途中でコードが変わったように聴こえるけども、実際は同じスケールを上がり下がりしているだけになる。

ちなみに、ブルースケールも、どんな風に弾いても大丈夫な感じなのは和風ペンタに似ている。

オレオレブルース G開放弦で – YouTube

ライトン・ホプキンズとジョン・リー・フッカーの真似事をしてみる。

ほんものはこっち

Baby, Please Don’t Go – Lightnin’ Hopkins – YouTube

Lightnin Hopkins – Lightnins Blues – YouTube

https://www.youtube.com/watch?v=DCSacpEIYWY

カテゴリー: ギター | 和風ペンタとブルーノートの比較の続き はコメントを受け付けていません

zo-3 のボリュームを交換する

yahoo! オークションで底値 2,400円也で手に入れた zo-3 なのですが、内蔵スピーカーから音が鳴ったり鳴らなかったりします。ボリュームを動かしてちまちまラジオのチューニングみたいなことをやると、がりがりという音と共に音が鳴るようになったりするのですが、ちょっと動かすとすぐに鳴らなくなります。どうやら、増幅回路んほうは大丈夫そうなので、ボリューム(ポテンショメーター)の交換をします。

image

amazon で見ると、エレキギターのボリュームスイッチは 500 ~ 2,000円で売っているようなのですが、そんな高価なのはいらないので秋月の 10kΩ のポテンショメータ 30円也を使います(どうやら、25kΩらしいんだけど、実測すると 10kΩちょっとだったので、手元にあるものでいいや、ということで)。

小型ボリューム 10KΩB: パーツ一般 秋月電子通商 電子部品 ネット通販
http://akizukidenshi.com/catalog/g/gP-00246/

裏側にある 9V 電池ボックスを取り外すとボリュームで使っているポテンショメータが見えます。これは交換した後に写したものです。

image

元についていたのがこれ。

  • ピックアップの線
  • 回路への線
  • コネクタケーブルへの線
  • グランド

が入ってくるので、まぜこぜになりますが。元のボリュームを参考にして配線&半田付け。一回、白い線を間違えて「あれ、鳴らん…」と焦ったは内緒です。テスターを持ち出してあれこれ調べました。

image

zo-3 の良いところは、スピーカーが内蔵されているところでアンプが無くても鳴らせるところです。定価だと 3万円ぐらい。中古だと1万円位で買えます。オークションだと 5,000円前後まで下がりますが塗装剥げや鳴らなかったりするのが曲者です。

回路はアナログ増幅回路だけ(だと思う)ので、適当なエフェクトを自作できるかなと思ったり思わなかったり。スペース的には RasPi Zero か Arduino Nano あたりが入りますね。いっそのことスピーカーを外してしまって、液晶モニタを埋め込むのもアリかもしれません。

image

この zo-3 が底値で買えたのは(競合が誰もいなかった)、弦がなかったのとナットが無かったからですね、たぶん。ネックの先のほうで弦を支えているのがナットです。…って、私も交換したことはなかった(交換するものとは思わなかった)ので、いくつか調べて池袋のギター屋さんで買いました。いくつか専用の器具がいるらしいのですが、面倒なので鉄やすりと糸のこで調節しています。ちょっと、削りが甘くて(疲れてしまって)弦高が高めなのですが、まあブルースを弾く分にはいいか、というのでそのままです。

image

そんな訳で、きれいに音がなるようになった zo-3 です。

指板のすれは、以前使っていた人のものです。きれいに12フレットまですれているので、きちんと弾き込んで使っていたものですね。フレットは減っている感じですが、まあ構わない。ネックの反りはないので、たぶん弦を外して、どこかでナットが外れてしまったまましまい込んでしまったのかなと。

ひとまず、ライトニン・ホプキンスとジョン・リー・フッカーの真似事からスタート。和風ペンタとブルーノートの組み合わせは後日。

カテゴリー: ギター | zo-3 のボリュームを交換する はコメントを受け付けていません