ESP側の話の続きです
Andoroid
まあウインドウズでも動いちゃったんですが
開発環境はVisual Studio2022
.NET MAUI
です
つーても実証実験のタメに作っただけなので、C#でTCPクライアントを作って、パケットを送りつけるというとても雑なアプリです
このページ見た人は「何だよ俺でも出来るじゃん」と思うでしょう
出来上がったイメージはこんな感じ
ソリューションを作る
Visual Studioで.NET MAUIのアプリを作って下さい。
今回はTcpClientMauiというnamespaceにしましたが、そこはご自由に
firmwareの用意
まあ何は無くてもファームウェアの用意です。
こいつを送り込んでやります。
つーても私の場合は他のアプリをいくつか作ってるので、その中から適当に選択したアプリのfirmware.binを使いました
ESP32のファームを作るのにVS Code上のplatform IOを使ってる人はプロジェクトフォルダの下の
.pio/buildの中を引っかき回せば見つかります。(adruinoはどこにあるんだろ・・・)
それをZIPで圧縮してVisual Studio のさっき作ってプロジェクトのResourcesの下のRAWにドラッグします
そして、大抵は勝手にプロパティのビルドアクションがMauiAssetになってるのだけどなってなかった場合はMauiAssetにしよう
コーディング
後はコーディング
ZipHelperクラス(ZipHelper.cs)
namespace TcpClientMaui
{
internal class ZipHelper
{
public static byte[] ExtractZipFromMemory(byte[] zipData)
{
using (var zipStream = new MemoryStream(zipData))
using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Read))
{
// ここで必要なエントリを選択して解凍します
// 例: 最初のエントリを取得
var entry = archive.Entries[0];
using (var entryStream = entry.Open())
using (var memoryStream = new MemoryStream())
{
entryStream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
}
}
さっきリソースフォルダにZipファイルを放り込んだのですが、それをメモリに展開するためのクラスです
引数にZipのバイナリデータを与えると、戻り値に解凍したバイナリイメージが返却されます
TCPClientクラス(TcpClientClass.cs)
namespace TcpClientMaui
{
internal class TcpClientClass
{
string _ipadr;
int _port;
TcpClient _client;
NetworkStream _stream;
bool _isRunning = false;
public TcpClientClass(string ipadr, int port)
{
_ipadr = ipadr;
_port = port;
}
public void SendData(byte[] data)
{
if (_stream != null)
{
_stream.Write(data, 0, data.Length);
}
}
public void Disconnect()
{
if (_stream != null)
{
_stream.Close();
_stream.Dispose();
}
if (_client != null)
{
_client.Close();
}
if (_isRunning)
{
_isRunning = false;
}
}
/// <summary>
/// 接続
/// </summary>
public void Connect()
{
try
{
// 接続
_client = new TcpClient(_ipadr, _port);
_stream = _client.GetStream();// ネットワークストリーム取得
_stream.ReadTimeout = 10000;
_stream.WriteTimeout = 10000;
_isRunning = true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
if (_stream != null)
{
_stream.Close();
_stream.Dispose();
}
if (_client != null)
{
_client.Close();
}
if (_isRunning)
{
_isRunning = false;
}
}
}
}
}
ESP32側で待ち受けしているTCPサーバに
- 接続するconnect()メソッド
- 切断するdisconnect()メソッド
- データを送信するSendData()メソッド
を記述します(しまったいらないからって受信イベントを作るの忘れてた)
メインページ(MainPage.xaml)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TcpClientMaui.MainPage">
<ScrollView>
<VerticalStackLayout
Padding="30,0"
Spacing="25">
<Image
Source="dotnet_bot.png"
HeightRequest="185"
Aspect="AspectFit"
SemanticProperties.Description="dot net bot in a race car number eight" />
<Label
Text="Hello, World!"
Style="{StaticResource Headline}"
SemanticProperties.HeadingLevel="Level1" />
<Label
Text="Welcome to .NET Multi-platform App UI"
Style="{StaticResource SubHeadline}"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I" />
<Entry x:Name="TxtIp" Text="192.168.4.1" />
<Entry x:Name="TxtPort" Text="80" Keyboard="Numeric" />
<Button
x:Name="ConnectBtn"
Text="接続"
SemanticProperties.Hint="サーバに接続します"
Clicked="OnConnect"
HorizontalOptions="Fill" />
<Button
x:Name="DisconnectBtn"
Text="切断"
SemanticProperties.Hint="サーバから切断します"
Clicked="OnDisconnect"
HorizontalOptions="Fill" />
<Button
x:Name="SendBtn"
Text="送信"
SemanticProperties.Hint="サーバにメッセージを送信します"
Clicked="OnSend"
HorizontalOptions="Fill" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
何も難しいことはないかと。
.NET Mauiアプリを作ると出来上がるテンプレートに
IPアドレスとポートを記述するテキストボックス、接続、切断、送信の各ボタンを置いただけです
メインページ(MainPage.xaml.cs)
using System.IO;
using System.Net.Sockets;
using System.Text;
namespace TcpClientMaui;
public partial class MainPage : ContentPage
{
TcpClientClass _tcpClient;
/// <summary>
/// コンストラクタ
/// </summary>
public MainPage()
{
InitializeComponent();
}
/// <summary>
/// プラットフォームによってはリソースフォルダからリードしたstreamの長さが取得できないので、いったんアプリケーションフォルダにコピーしてからリードする
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public async Task CopyFileToAppDataDirectory(string filename)
{
// バンドルされたファイルを開く
using Stream inputStream = await FileSystem.Current.OpenAppPackageFileAsync(filename);
// 出力ファイル名を作成
string targetFile = Path.Combine(FileSystem.Current.AppDataDirectory, filename);
// ファイルをAppDataDirectoryにコピー
using FileStream outputStream = File.Create(targetFile);
await inputStream.CopyToAsync(outputStream);
}
private async Task<byte[]> Fileread()
{
// .NEt MAUI Resourcesフォルダのバイナリデータ(xx.zip)をリードして
// バイトデータを返す
await CopyFileToAppDataDirectory("firmware.zip");
byte[] data = null;
using(FileStream stream = File.OpenRead(Path.Combine(FileSystem.Current.AppDataDirectory, "firmware.zip")))
{
data = new byte[stream.Length];
await stream.ReadAsync(data, 0, data.Length);
}
// Windowsの場合は以下のようにリソースフォルダからリードすればいい
//byte[] data = null;
////using var stream = await FileSystem.OpenAppPackageFileAsync("firmware.zip");
//using var stream = await FileSystem.Current.OpenAppPackageFileAsync("firmware.zip");
//data = new byte[stream.Length];
//await stream.ReadAsync(data, 0, data.Length);
return data;
}
/// <summary>
/// 接続
/// </summary>
/// <param name="sender">送信元</param>
/// <param name="e">イベント</param>
private void OnConnect(object sender, EventArgs e)
{
_tcpClient = new TcpClientClass(TxtIp.Text, int.Parse(TxtPort.Text));
_tcpClient.Connect();
}
/// <summary>
/// 切断
/// </summary>
private void OnDisconnect(object sender, EventArgs e)
{
// _tcpClient.Disconnect();
}
private async void OnSend(object sender, EventArgs e)
{
byte[] data = await Fileread();
byte[] zipData = ZipHelper.ExtractZipFromMemory(data);
if (data != null)
{
_tcpClient.SendData(zipData);
}
}
}
接続と切断は接続して切断するだけ
おおう、切断がコメントになってるぜ・・・
送信について(OnSend()メソッド)
まあここが肝です(肝と言っても解凍したデータをそのまま送信関数にぶち込んでるだけなんだけど)
- 最初にfileread()メソッドでZIPファイルをデータとしてメモリに取り込みます
とは言え・・実はFilereadメソッド中で変なことをしているのはAndroid等では直接streamに取り込むとサイズがわからないので、いったんリソースからファイルにコピーしてからメモリにリードしています - ZipHelperクラスで解凍
- 送信!
まあこんな感じでしょうか
これでESP32にファームを送信出来ます
確認したのは
AndroidとWindows11です。
使い方
最初にWi-FiでESP32のアクセスポイントに接続
そしたら、作ったアプリで接続→送信。↑のはWindows11でで試しているところ。
Andoridエミュレータ上からでも接続出来ました。実機の場合は実機のWi-fi接続の画面から接続して、それからアプリを動作させてください
改造
改造というか・・・ あくまで実証実験用に作ったのでとんでもなく雑に作っています。以下の点に突っ込み無用でお願いします
- データ送信サイズ
受信側のESP32はこんなただのパケット送信でどうやって終了を検知するのだろう?
そうです、このアプリはファームのサイズを固定値で決め打ちしています。
受信側のESP32に受信サイズが定数として記述されているのはこのためです。つまり決まったサイズを受信したら、更新完了と判定しているわけです。
当たり前ですが実アプリではこんなことしません。 - 一発で送信完了
Sendでファームを1回だけ送信してるけど大丈夫なの?
大丈夫な訳ないですね。
通常は数キロバイト毎に分割して送信します。そしてその中で電文としてこのパケットは何番目なのかとか、最終パケットだよとか通知します。 - 受信がないけど?
本来は必要です。ESP側で失敗したときは返信電文で「失敗したからロールバックしたぜ。もう送ってくんな」とか送り返します。 - エラートラップは?
実証アプリつってんだろ
最後に
OTAやるぜって意気込んだ割に普通の手抜きTCPクライアントでした
おしまい
コメント
コメントを投稿