読者です 読者をやめる 読者になる 読者になる

SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

Wi-Fi付きのArduino互換機 Spark Core (その5) Xamarin.Formsクライアントによるマトリックス操作

1 ダイナミック点灯

 ダイナミック点灯とは、1行ごとに点灯の処理を行い、それを高速で繰り返すことで、残像で全行が表示されているように見せることです。

 ここで、1行の表示を処理しているのは、void disp(int r,String dat) です。第1パラメータは、表示対象の行です。そして、第2パラメータは、列のOn/OFFを指定するデータです。例えば、datに"f0"を指定すると、ビットに直して"11110000"なので、左から●●●●○○○○のように点灯します。

 以下は、Arduinoのコードですが、このdisp()をloop()の中で、全行分繰り返しています。

int row[8]={A7,A6,A5,A4,A3,A2,A1,A0};
int col[8]={D7,D6,D5,D4,D3,D2,D1,D0};

//点灯対象の文字列データ
String datStr="f0f0f0f08f8f8f8f";

void setup() {
    for(int i=0;i<8;i++){
        //D0..D7の初期化
        pinMode(col[i], OUTPUT); 
        digitalWrite(col[i],1);//HIGHで初期化
        //A0..A7の初期化
        pinMode(row[i], OUTPUT); 
        digitalWrite(row[i],0); //LOWで初期化
    }
}

void loop() {
    for(int i=0;i<8;i++){
        //2文字づつ切り取って、列ごと点灯する
        disp(i,datStr.substring(i*2,i*2 + 2));
    }
}

//文字列(2文字)を数値化する
//"ff" -> 0xff   "1c" -> 0x1c
int toHex(String str){
    char c = str[0];
    int h = ('a' <= c && c <= 'f') ? (c - 'a') + 10 : (c - '0');
    c = str[1];
    int l = ('a' <= c && c <= 'f') ? (c - 'a') + 10 : (c - '0');
    return (h<<4 | l)&0xff;
}

void disp(int r,String dat){
    int d = toHex(dat);//文字列の数値化
    digitalWrite(row[r],0); //当該行の消灯
    for(int c=0;c<8;c++){
        int sw = 1;//HIGHで初期化
        if (((d >> c) & 1)==1) {
            sw = 0;//LOWで点灯
        }
        digitalWrite(col[7-c],sw);
    }
    digitalWrite(row[r],1); //当該行の点灯
    delay(3);
    digitalWrite(row[r],0); //当該行の消灯
}

元となるデータが、String datStr="f0f0f0f08f8f8f8f" であるため、LEDは次のように表示されます。

●●●●○○○○ <- f0
●●●●○○○○ <- f0
●●●●○○○○ <- f0
●●●●○○○○ <- f0
●○○○●●●● <- 8f
●○○○●●●● <- 8f
●○○○●●●● <- 8f
●○○○●●●● <- 8f


実際に点灯しているようすです。

f:id:furuya02:20150223060532j:plain

2 WebAPIの公開

続いて、(その2) で紹介したWebAPIを使用して、この元データであるdatStrを外部から変更できるようにします。

追加したArduinoのコードは、下記のとおりです。

int func(String sw); //WebAPIの型宣

void setup() {
    //・・・省略・・・
    Spark.function("func", func); //WebAPIの公開
}

//公開されたAPI
int func(String s){
    str = s;
    return 1;
}

3 LEDボタン

f:id:furuya02:20150223061751j:plain:w300:left
WebAPIをコールするクライアントは、iPhone6用として決め打ちなのですが、今後の拡張が容易なように、Xamarin.Formsを使用しました。

クライアントでは、LED(赤い丸)をタッチすると、LEDがON/OFFするようになっていますが、この赤い丸(LEDクラス)は、BoxViewを継承して作成しました。
LEDクラスは、レンダラーを記述することで、プラットホーム側で、丸く描画し、併せて影をおきました。

BoxViewを継承したLEDクラスは次のとおりです。

public class Led:BoxView{
    bool _sw;
    public bool Sw {
        set {
            _sw = value;
            Color = _sw ? Color.FromRgba(224, 33, 0, 255) : Color.FromRgba(255, 100, 100, 100);
            OnPropertyChanged(propertyName: "Color");
        }
        get { return _sw; }
    }

    public Led() {
        WidthRequest = 45;
        HeightRequest = 45;
        Sw = false;
    }
}

LEDクラスには、Swというプロパティがあり、これがON/OFFを表現しています。
Swプロパティをセットした時は、同時にColorプロパティが変更され、レンダラークラスにも通知されるようになっています。

レンダラー側のコードは、下記のとおりです。

[assembly: ExportRenderer(typeof(Led), typeof(LedRenderer))]
namespace MatrixLed.iOS
{
    class LedRenderer : BoxRenderer{
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName == "Color"){
                SetNeedsDisplay();      
            }
        }

        public override void Draw(CGRect rect) {
            //base.Draw(rect);

            var led = (Led)Element;         
            using (var context = UIGraphics.GetCurrentContext()){   

                var shadowSize = 3;
                var blur = shadowSize;
                var radius = 20;

                context.SetFillColor(led.Color.ToCGColor());     
                var bounds = Bounds.Inset(2,2); 
                context.AddPath(CGPath.FromRoundedRect(bounds, radius, radius));
                context.SetShadow(new SizeF(shadowSize, shadowSize), blur);
                context.DrawPath(CGPathDrawingMode.Fill);
            }
        }
    }
}

デフォルトの描画を無効とし、「影」及び「丸」を自前で描画しています。
また、再描画は、プロパティ「Color」が変更されたときに、 SetNeedsDisplay()をコールし、間接的にDraw()を呼び出しています。

4 メイン処理

続いて、メインの処理です。

 LEDクラスは、16個生成しAbsoluteLayoutレイアウトで配置しています。また、タッチの検出は、TapGestureRecognizerクラスを LEDクラス(BoxView)のGestureRecognizersに追加することで行っています。

public class App : Application {
    public App() {
        MainPage = new MyPage();
    }

    //・・・省略・・・
}

class MyPage : ContentPage {
    public MyPage() {
        var ar = new List<Led>();

        BackgroundImage = "back.png";//背景画像

        var absoluteLayout = new AbsoluteLayout();

        //送信文字の表示用ラベル
        var label = new Label{
            Text="0x0000000000000000",
            FontFamily = "HelveticaNeue-Thin",
            FontSize = 30,
            TextColor = Color.White,
            BackgroundColor = Color.FromRgba(0,0,0,70),
            WidthRequest = 375,//iPhone6
            HeightRequest = 100,
            XAlign = TextAlignment.Center,
            YAlign = TextAlignment.Center
        };
        absoluteLayout.Children.Add(label, new Point(0, 667-90));

        //タッチイベントの処理
        var gr = new TapGestureRecognizer(); 
        gr.Tapped += (s, e) => {
            var led = (Led) s;
                
            //ON/OFFの反転
            led.Sw = !led.Sw;

            //LEDのON/OFFデータをを文字列として取得する
            var dataStr = GetDataStr(ar);
          
            //ラベルへの表示
            label.Text = "0x" + dataStr;

            //WebAPIのコール
            Api(dataStr);

        };

        //LEDの生成と表示
        const int top = 100;
        const int left = 3;
        const int width = 46;
        for (var y = 0; y < 8; y++){
            for (var x = 0; x < 8; x++) {
                var led = new Led();
                ar.Add(led);
                led.GestureRecognizers.Add(gr); //タッチイベントの検知を追加
                absoluteLayout.Children.Add(led, new Point(left + x*width, top + y*width));
            }
        }

        Content = absoluteLayout;
    }
     // ・・・省略・・・
}

5 WebAPI

最後にWebAPIをコールするコードです。
トリガーは、タッチ時ですが、コールの要領は、(その2)で紹介したものと同じです。

    //タッチイベントの処理
    gr.Tapped += (s, e) => {
        var led = (Led) s;
                
        //LEDのON/OFFデータをを文字列として取得する
        var dataStr = GetDataStr(ar);
          
        //WebAPIのコール
        Api(dataStr);

    };


    //WebAPIのコール
    async void Api(String daataStr){
        var httpClient = new HttpClient();
        const string deviceId = "53ff6xxxxxxxxxxxxxx2567";//デバイスID
        const string accessToken = "50179axxxxxxxxxxxxxxxxxxxxxxxxxxx5413e";//アクセストークン
        const string funcName = "func";//WebAPIのファンクション名
        var data = new FormUrlEncodedContent(new Dictionary<string, string>{
                {"access_token", accessToken},
                {"params", daataStr}
            });
        //URLはデバイスIDとファンクション名で決まる
        var url = string.Format("https://api.spark.io/v1/devices/{0}/{1}", deviceId, funcName);
        await httpClient.PostAsync(url, data);
    }

    //LEDのON/OFFデータをを文字列として取得する
    private string GetDataStr(List<Led> ar) {
        var sb = new StringBuilder();
        for (var r = 0; r < 8; r++) {
            var dat = 0;
            for (var c = 0; c < 8; c++) {
                var index = r*8 + c;
                if (ar[index].Sw) {
                    dat |= 1 << (7 - c);
                }
            }
            sb.Append(Convert.ToString(dat, 16));
        }
        return sb.ToString();
    }

}



Githubにおいた全コードです。

furuya02/Xamarin.Forms.MatrixLed · GitHub

iPhoneで操作しているようすです。
f:id:furuya02:20150223061645j:plain






Wi-Fi付きのArduino互換機 Spark Core (その1) - SIN@SAPPOROWORKSの覚書


Wi-Fi付きのArduino互換機 Spark Core (その2) - SIN@SAPPOROWORKSの覚書


Wi-Fi付きのArduino互換機 Spark Core (その3) - SIN@SAPPOROWORKSの覚書


Wi-Fi付きのArduino互換機 Spark Core (その4) - SIN@SAPPOROWORKSの覚書