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
実際に点灯しているようすです。
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ボタン
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で操作しているようすです。
Wi-Fi付きのArduino互換機 Spark Core (その1) - SIN@SAPPOROWORKSの覚書
Wi-Fi付きのArduino互換機 Spark Core (その2) - SIN@SAPPOROWORKSの覚書