Skip to content

Commit

Permalink
added basic OTA support to the mobile app.
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcus10110 committed Oct 18, 2021
1 parent f062473 commit 31a4d95
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 79 deletions.
12 changes: 0 additions & 12 deletions DelSolClockApp/DelSolClockApp/AppShell.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,10 @@
</Shell.Resources>

<TabBar>
<!--<ShellContent Title="About" Icon="icon_about.png" Route="AboutPage" ContentTemplate="{DataTemplate local:AboutPage}" /> -->

<ShellContent Title="Status" Icon="icon_feed.png" ContentTemplate="{DataTemplate local:StatusPage}" />
<ShellContent Title="Navigate" Icon="icon_feed.png" ContentTemplate="{DataTemplate local:NavigationPage}" />
<ShellContent Title="Update" Icon="icon_feed.png" ContentTemplate="{DataTemplate local:UpdatePage}" />
<ShellContent Title="Logs" Icon="icon_feed.png" ContentTemplate="{DataTemplate local:LogPage}" />

</TabBar>

<!--
If you would like to navigate to this content you can do so by calling
await Shell.Current.GoToAsync("//LoginPage");
-->
<TabBar>
<ShellContent Route="LoginPage" ContentTemplate="{DataTemplate local:LoginPage}" />
</TabBar>


</Shell>
127 changes: 124 additions & 3 deletions DelSolClockApp/DelSolClockApp/Services/DelSolConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,39 @@ public class StatusChangedEventArgs : System.EventArgs
public CarStatus Status { get; set; }
}

public class ConnectedEventArgs : System.EventArgs
{
public string Version { get; set; }
}

public struct FwUpdateProgress
{
public double PercentComplete { get; set; }
public double SecondsRemaining { get; set; }
public double SpeedBps { get; set; }
public bool Started { get; set; }

}

public static readonly Guid DelSolVehicleServiceGuid = new Guid("8fb88487-73cf-4cce-b495-505a4b54b802");
public static readonly Guid DelSolStatusCharacteristicGuid = new Guid("40d527f5-3204-44a2-a4ee-d8d3c16f970e");
public static readonly Guid DelSolBatteryCharacteristicGuid = new Guid("5c258bb8-91fc-43bb-8944-b83d0edc9b43");

public static readonly Guid DelSolLocationServiceGuid = new Guid("61d33c70-e3cd-4b31-90d8-a6e14162fffd");
public static readonly Guid DelSolNavigationServiceGuid = new Guid("77f5d2b5-efa1-4d55-b14a-cc92b72708a0");

public static readonly Guid DelSolFirmwareServiceGuid = new Guid("69da0f2b-43a4-4c2a-b01d-0f11564c732b");
public static readonly Guid DelSolFirmwareWriteCharacteristicGuid = new Guid("7efc013a-37b7-44da-8e1c-06e28256d83b");
public static readonly Guid DelSolFirmwareVersionCharacteristicGuid = new Guid("a5c0d67a-9576-47ea-85c6-0347f8473cf3");

private IDevice DelSolDevice = null;
private ICharacteristic StatusCharacteristic = null;
private ICharacteristic WriteCharacteristic = null;
private IAdapter Adapter = null;
private bool Active = false;

public EventHandler<StatusChangedEventArgs> StatusChanged;
public EventHandler Connected;
public EventHandler<ConnectedEventArgs> Connected;
public EventHandler Disconnected;

private void StartAsyncTimer(TimeSpan period, Func<Task<bool>> callback)
Expand Down Expand Up @@ -133,6 +152,85 @@ public CarStatus GetStatus()
}
return ParseStatus(StatusCharacteristic.Value);
}

private TaskCompletionSource<string> WriteResultTask = null;

public async Task<bool> UpdateFirmware(System.IO.Stream bin, Action<FwUpdateProgress> percent_update_cb)
{
if (!IsConnected) throw new Exception("unable to update firmware, not connected");
if (WriteCharacteristic == null) throw new Exception("write characteristic is missing");

DateTime upload_start = DateTime.Now;
long total_size = bin.Length;
const int max_length = 512; // matches firmware.
byte[] buffer = new byte[max_length];
int read = 0;
long bytes_written = 0;
do
{
read = await bin.ReadAsync(buffer, 0, max_length);

byte[] write_buffer = new byte[read];
Array.Copy(buffer, write_buffer, read);
WriteResultTask = new TaskCompletionSource<string>();
var write_start = DateTime.Now;
var write_success = await WriteCharacteristic.WriteAsync(write_buffer);
if (!write_success)
{
throw new Exception("failed to write firmware, write failed");
}
bytes_written += read;
var write_success_time = DateTime.Now - write_start;
string result = await WriteResultTask.Task;
var response_time = DateTime.Now - write_start;
var elapsed_time = DateTime.Now - upload_start;



if (result == "continue")
{
double percent_done = 100 * (double)bytes_written / (double)total_size;
Logger.WriteLine($"wrote {read}, total {bytes_written}. {percent_done}%");
double average_bps = (double)bytes_written / elapsed_time.TotalSeconds;
double seconds_remaining = (total_size - bytes_written) / average_bps;
Logger.WriteLine($"{Math.Round(average_bps)}bps. write time: {write_success_time.TotalSeconds}, response time: {response_time.TotalSeconds} elapsed: {Math.Round(elapsed_time.TotalSeconds)}");
FwUpdateProgress update = new FwUpdateProgress { SpeedBps = average_bps, PercentComplete = percent_done, SecondsRemaining = seconds_remaining, Started = true };
percent_update_cb(update);
}
else if (result == "success")
{
Logger.WriteLine("upload success!");
return true;
}
else if (result == "error")
{
Logger.WriteLine($"FW upload error after section of {read}, total {bytes_written}");
return false;
}
else
{
throw new Exception($"could not understand response: \"{result}\"");
}

} while (read > 0);
throw new Exception($"reached the end of the FW update file, but did not get a finished response. total: {bytes_written}");
}

private void WriteCharacteristic_ValueUpdated(object sender, Plugin.BLE.Abstractions.EventArgs.CharacteristicUpdatedEventArgs e)
{

string value = Encoding.Default.GetString(e.Characteristic.Value);
if (WriteResultTask != null && !WriteResultTask.Task.IsCompleted)
{
Logger.WriteLine($"write characteristic notified: {value}, setting result.");
WriteResultTask.SetResult(value);
}
else
{
Logger.WriteLine($"Value changed without a valid task: {value}");
}
}

private async Task Service()
{

Expand Down Expand Up @@ -186,7 +284,30 @@ private async Task Service()
StatusCharacteristic.ValueUpdated += Status_characteristic_ValueUpdated;
await StatusCharacteristic.StartUpdatesAsync();
Logger.WriteLine($"Initial Status: {System.Text.Encoding.Default.GetString(StatusCharacteristic.Value)}");
Connected?.Invoke(this, new EventArgs());

// Firmware Update
var firmware_service = await DelSolDevice.GetServiceAsync(DelSolFirmwareServiceGuid);
if (firmware_service == null)
{
throw new Exception("Firmware Serivce missing");
}
var version_characteristic = await firmware_service.GetCharacteristicAsync(DelSolFirmwareVersionCharacteristicGuid);
if (version_characteristic == null)
{
throw new Exception("version characteristic missing");
}
String version = System.Text.Encoding.Default.GetString(await version_characteristic.ReadAsync());
Logger.WriteLine($"current FW version: {version}");

WriteCharacteristic = await firmware_service.GetCharacteristicAsync(DelSolFirmwareWriteCharacteristicGuid);
if (WriteCharacteristic == null)
{
throw new Exception("write characteristic missing");
}
WriteCharacteristic.ValueUpdated += WriteCharacteristic_ValueUpdated;
await WriteCharacteristic.StartUpdatesAsync();

Connected?.Invoke(this, new ConnectedEventArgs { Version = version });
StatusChangedEventArgs args = new StatusChangedEventArgs();
args.Status = ParseStatus(StatusCharacteristic.Value);
StatusChanged?.Invoke(this, args);
Expand All @@ -206,7 +327,7 @@ private CarStatus ParseStatus(byte[] value)
{
CarStatus status = new CarStatus();
if (value == null) return null;
String str = System.Text.Encoding.Default.GetString(value);
String str = Encoding.Default.GetString(value);
var items = str.Split(new char[] { ',' });
if (items.Length != 5) return null;
status.RearWindow = int.Parse(items[0]) != 0;
Expand Down
24 changes: 0 additions & 24 deletions DelSolClockApp/DelSolClockApp/ViewModels/LoginViewModel.cs

This file was deleted.

14 changes: 0 additions & 14 deletions DelSolClockApp/DelSolClockApp/Views/LoginPage.xaml

This file was deleted.

21 changes: 0 additions & 21 deletions DelSolClockApp/DelSolClockApp/Views/LoginPage.xaml.cs

This file was deleted.

6 changes: 3 additions & 3 deletions DelSolClockApp/DelSolClockApp/Views/UpdatePage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<StackLayout Padding="10" x:DataType="{x:Type local:ReleaseBrowser+Release}">
<StackLayout Orientation="Horizontal" x:DataType="{x:Type local:ReleaseBrowser+Release}">
<StackLayout Padding="10">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
Expand All @@ -30,7 +30,7 @@
</StackLayout>
<Button HorizontalOptions="EndAndExpand" VerticalOptions="Center" Text="Upload"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:UpdateViewModel}}, Path=UploadRelease}"
CommandParameter="{Binding .}"></Button>
CommandParameter="{Binding .}" ></Button>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
Expand Down
56 changes: 54 additions & 2 deletions DelSolClockApp/DelSolClockApp/Views/UpdatePage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

Expand Down Expand Up @@ -52,17 +53,31 @@ public static async Task<List<Release>> FetchReleases()
}
public class UpdateViewModel : BaseViewModel
{
static readonly HttpClient Client = new HttpClient();

private DelSolConnection.FwUpdateProgress fwProgress = new DelSolConnection.FwUpdateProgress();
public ObservableCollection<ReleaseBrowser.Release> Releases { get; }

public Command RefreshCommand { get; }

public Command<ReleaseBrowser.Release> UploadRelease { get; }

public DelSolConnection.FwUpdateProgress FwProgress
{
get { return fwProgress; }
set
{
SetProperty(ref fwProgress, value);
}
}

public UpdateViewModel()
{
fwProgress.Started = false;
Releases = new ObservableCollection<ReleaseBrowser.Release>();
RefreshCommand = new Command(async () =>
{
Logger.WriteLine("Refreshing...");
IsBusy = true;
try
{
Expand All @@ -88,6 +103,43 @@ public UpdateViewModel()
UploadRelease = new Command<ReleaseBrowser.Release>(async (ReleaseBrowser.Release release) =>
{
Logger.WriteLine($"uploading {release.name}...");
// download the *.bin file!
System.IO.Stream fw_stream;
try
{
var bin_url = release.assets.First(x => x.name.EndsWith(".bin")).browser_download_url;
Logger.WriteLine($"downloading {bin_url}");
var response = await Client.GetAsync(bin_url);
response.EnsureSuccessStatusCode();
fw_stream = await response.Content.ReadAsStreamAsync();
Logger.WriteLine("response stream ready. Uploading...");
}
catch (Exception ex)
{
Logger.WriteLine($"failed to download fw image");
Logger.WriteLine(ex.Message);
return;
}

try
{
var app = App.Current as App;
var del_sol = app.DelSol;
if (!del_sol.IsConnected)
{
throw new Exception("Del Sol BLE not connected");
}
var result = await del_sol.UpdateFirmware(fw_stream, (progress) => { FwProgress = progress; });
if (!result)
{
throw new Exception("UpdateFirmware returned false");
}
}
catch (Exception ex)
{
Logger.WriteLine($"Error uploading FW: {ex.Message}");
}

});
}

Expand All @@ -103,7 +155,7 @@ public UpdatePage()
InitializeComponent();
BindingContext = _viewModel = new UpdateViewModel();
}

protected override void OnAppearing()
{
base.OnAppearing();
Expand All @@ -113,6 +165,6 @@ protected override void OnAppearing()
// setting IsBusy will cause the RefreshView to start refreshing.
_viewModel.IsBusy = true;
}

}
}

0 comments on commit 31a4d95

Please sign in to comment.