diff --git a/.gitignore b/.gitignore
index aa202a2..f6a27f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,34 +1,243 @@
-/PebbleSharp.Core/bin
-/PebbleSharp.Core/obj
-/PebbleSharp.Core.Tests/bin
-/PebbleSharp.Core.Tests/obj
-/PebbleSharp.Net45/bin
-/PebbleSharp.Net45/obj
-/PebbleSharp.Net45.Tests/bin/Debug
-/PebbleSharp.WinRT/bin/Debug
-/PebbleSharp.WinRT/obj/Debug
-/PebbleSharp.Net45.Tests/obj/Debug
-/PebbleSharp.Mobile/PebbleSharp.Mobile.WinPhone
-/PebbleSharp.Mobile/PebbleSharp.Mobile.iOS
-/PebbleSharp.Mobile/PebbleSharp.Mobile.Android/obj/Debug
-/Demo/PebbleSharp.WPF
-/PebbleSharp.Driod/obj/Debug
-/PebbleSharp.iOS/obj/Debug
-/PebbleSharp.Mobile/PebbleSharp.Mobile
-/PebbleSharp.sln.DotSettings.user
-/PebbleSharp.v12.suo
-/PebbleSharp.Universal/PebbleSharp.Universal.Windows/bin
-/PebbleSharp.Universal/PebbleSharp.Universal.Windows/obj
-/PebbleSharp.Universal/PebbleSharp.Universal.WindowsPhone/bin
-/PebbleSharp.Universal/PebbleSharp.Universal.WindowsPhone/obj
-/PebbleSharp.WinRT.Tests/bin
-/PebbleSharp.WinRT.Tests/obj
-/PebbleSharp.Universal/PebbleSharp.Universal.WindowsPhone/PebbleSharp.Universal.WindowsPhone.csproj.user
-/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Windows/bin
-/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Windows/obj
-/Demo/PebbleSharp.Universal/PebbleSharp.Universal.WindowsPhone/bin
-/Demo/PebbleSharp.Universal/PebbleSharp.Universal.WindowsPhone/obj
-/Demo/PebbleSharp.Universal/PebbleSharp.Universal.WindowsPhone/PebbleSharp.Universal.WindowsPhone.csproj.user
-/PebbleCmd/bin/Debug
-/PebbleCmd/obj/Debug
-/PebbleSharp.Core.Tests/PebbleSharp.Core.Tests.csproj.user
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Microsoft Azure ApplicationInsights config file
+ApplicationInsights.config
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.imlobj
\ No newline at end of file
diff --git a/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Shared/ViewModels/AppsViewModel.cs b/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Shared/ViewModels/AppsViewModel.cs
index 84b45c7..df283f7 100644
--- a/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Shared/ViewModels/AppsViewModel.cs
+++ b/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Shared/ViewModels/AppsViewModel.cs
@@ -29,7 +29,7 @@ public override async Task RefreshAsync()
private async Task LoadAppsAsync()
{
- var appBankContents = await Pebble.GetAppbankContentsAsync();
+ var appBankContents = await Pebble.InstallClient.GetAppbankContentsAsync();
Apps.Clear();
if (appBankContents != null && appBankContents.AppBank != null && appBankContents.AppBank.Apps != null)
{
diff --git a/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Windows/PebbleSharp.Universal.Windows.csproj b/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Windows/PebbleSharp.Universal.Windows.csproj
index 516090b..c99cd29 100644
--- a/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Windows/PebbleSharp.Universal.Windows.csproj
+++ b/Demo/PebbleSharp.Universal/PebbleSharp.Universal.Windows/PebbleSharp.Universal.Windows.csproj
@@ -104,11 +104,11 @@
-
+
{ae0f69ff-97c2-4e55-a3c9-b73e68668dda}
PebbleSharp.Core
-
+
{c6f33ca9-eed4-4553-ae74-cd77e6bf3d75}
PebbleSharp.WinRT
diff --git a/Demo/PebbleSharp.WPF/Messages/PebbleDisconnected.cs b/Demo/PebbleSharp.WPF/Messages/PebbleDisconnected.cs
new file mode 100644
index 0000000..269673f
--- /dev/null
+++ b/Demo/PebbleSharp.WPF/Messages/PebbleDisconnected.cs
@@ -0,0 +1,21 @@
+using System;
+using PebbleSharp.Core;
+
+namespace PebbleSharp.WPF.Messages
+{
+ public class PebbleDisconnected
+ {
+ private readonly Pebble _pebble;
+
+ public PebbleDisconnected( Pebble pebble )
+ {
+ if (pebble == null) throw new ArgumentNullException("pebble");
+ _pebble = pebble;
+ }
+
+ public Pebble Pebble
+ {
+ get { return _pebble; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demo/PebbleSharp.WPF/PebbleSharp.WPF.csproj b/Demo/PebbleSharp.WPF/PebbleSharp.WPF.csproj
index 0a85a13..fb80187 100644
--- a/Demo/PebbleSharp.WPF/PebbleSharp.WPF.csproj
+++ b/Demo/PebbleSharp.WPF/PebbleSharp.WPF.csproj
@@ -70,6 +70,7 @@
App.xaml
Code
+
@@ -100,7 +101,6 @@
-
Code
diff --git a/Demo/PebbleSharp.WPF/ViewModels/PebbleAppsViewModel.cs b/Demo/PebbleSharp.WPF/ViewModels/PebbleAppsViewModel.cs
index 953300e..7349898 100644
--- a/Demo/PebbleSharp.WPF/ViewModels/PebbleAppsViewModel.cs
+++ b/Demo/PebbleSharp.WPF/ViewModels/PebbleAppsViewModel.cs
@@ -4,6 +4,7 @@
using System.Windows.Input;
using GalaSoft.MvvmLight.Command;
using Microsoft.Win32;
+using PebbleSharp.Core;
using PebbleSharp.Core.Bundles;
using PebbleSharp.WPF.Messages;
using PebbleSharp.Net45;
@@ -58,7 +59,7 @@ private async Task LoadAppsAsync()
return;
Loading = true;
- var appBankContents = await _pebble.GetAppbankContentsAsync();
+ var appBankContents = await _pebble.InstallClient.GetAppbankContentsAsync();
_apps.Clear();
if ( appBankContents.Success )
foreach ( var app in appBankContents.AppBank.Apps )
@@ -91,12 +92,13 @@ private async void OnInstallApp()
var bundle = new AppBundle();
using (var zip = new Zip())
{
- bundle.Load(openDialog.OpenFile(), zip);
+ zip.Open(openDialog.OpenFile());
+ bundle.Load(zip,_pebble.Firmware.HardwarePlatform.GetSoftwarePlatform());
}
if ( _pebble.IsAlive == false )
return;
- await _pebble.InstallAppAsync( bundle );
+ await _pebble.InstallClient.InstallAppAsync( bundle );
await LoadAppsAsync();
}
}
diff --git a/Demo/PebbleSharp.WPF/ViewModels/PebbleInfoViewModel.cs b/Demo/PebbleSharp.WPF/ViewModels/PebbleInfoViewModel.cs
index a9002b6..101f45c 100644
--- a/Demo/PebbleSharp.WPF/ViewModels/PebbleInfoViewModel.cs
+++ b/Demo/PebbleSharp.WPF/ViewModels/PebbleInfoViewModel.cs
@@ -101,12 +101,13 @@ private async void OnUpdateFirmware()
var bundle = new FirmwareBundle();
using (var zip = new Zip())
{
- bundle.Load(openDialog.OpenFile(), zip);
+ zip.Open(openDialog.OpenFile());
+ bundle.Load(zip,_pebble.Firmware.HardwarePlatform.GetSoftwarePlatform());
}
if (_pebble.IsAlive == false)
return;
- await _pebble.InstallFirmwareAsync(bundle);
+ await _pebble.InstallClient.InstallFirmwareAsync(bundle);
}
}
diff --git a/PebbleCmd/AppMessageTest.pbw b/PebbleCmd/AppMessageTest.pbw
new file mode 100644
index 0000000..8b268c2
Binary files /dev/null and b/PebbleCmd/AppMessageTest.pbw differ
diff --git a/PebbleCmd/PebbleCmd.csproj b/PebbleCmd/PebbleCmd.csproj
index 69615f2..f397065 100644
--- a/PebbleCmd/PebbleCmd.csproj
+++ b/PebbleCmd/PebbleCmd.csproj
@@ -50,6 +50,9 @@
+
+ PreserveNewest
+
diff --git a/PebbleCmd/Program.cs b/PebbleCmd/Program.cs
index c970c02..4beaa3c 100644
--- a/PebbleCmd/Program.cs
+++ b/PebbleCmd/Program.cs
@@ -4,7 +4,10 @@
using System.Threading.Tasks;
using PebbleSharp.Core;
using PebbleSharp.Core.Responses;
+using System.IO;
using PebbleSharp.Net45;
+using PebbleSharp.Core.Bundles;
+using PebbleSharp.Core.AppMessage;
namespace PebbleCmd
{
@@ -29,6 +32,7 @@ private static async Task ShowPebbles()
if ( result >= 0 && result < pebbles.Count )
{
var selectedPebble = pebbles[result];
+ selectedPebble.RegisterCallback(ReceiveAppMessage);
Console.WriteLine( "Connecting to Pebble " + selectedPebble.PebbleID );
await selectedPebble.ConnectAsync();
Console.WriteLine( "Connected" );
@@ -44,7 +48,9 @@ private static async Task ShowPebbleMenu( Pebble pebble )
"Set Current Time",
"Get Firmware Info",
"Send Ping",
- "Media Commands" );
+ "Media Commands",
+ "Install App",
+ "Send App Message");
while ( true )
{
switch ( menu.ShowMenu() )
@@ -72,8 +78,127 @@ private static async Task ShowPebbleMenu( Pebble pebble )
case 5:
ShowMediaCommands( pebble );
break;
+ case 6:
+ InstallApp(pebble);
+ break;
+ case 7:
+ SendAppMessage(pebble);
+ break;
+ }
+ }
+ }
+
+ private static string SelectApp()
+ {
+ string exePath = System.Reflection.Assembly.GetExecutingAssembly ().CodeBase;
+ //TODO: there has to be a better way to come up with a canonical path, but this combo seems to work on both windows and 'nix
+ if (exePath.StartsWith("file:"))
+ {
+ exePath = exePath.Substring(5);
+ }
+ if (exePath.StartsWith("///"))
+ {
+ exePath = exePath.Substring(3);
+ }
+ string exeDir = Path.GetDirectoryName (exePath);
+ var dir = new DirectoryInfo (exeDir);
+ var files = dir.GetFiles ("*.pbw");
+
+ if (files.Any())
+ {
+ if (files.Count() == 1)
+ {
+ return files.Single().FullName;
+ }
+ else
+ {
+ var fileMenu = new Menu(files.Select(x => x.Name).ToArray());
+ int index = fileMenu.ShowMenu();
+ return files[index].FullName;
+ }
+ }
+ else
+ {
+ Console.WriteLine("No .pbw files found");
+ return null;
+ }
+ }
+
+ private static void InstallApp(Pebble pebble)
+ {
+ var progress = new Progress(pv => Console.WriteLine(pv.ProgressPercentage + " " + pv.Message));
+
+ string appPath = SelectApp();
+
+ if (!string.IsNullOrEmpty(appPath) && File.Exists(appPath))
+ {
+ using (var stream = new FileStream(appPath, FileMode.Open))
+ {
+ using (var zip = new Zip())
+ {
+ zip.Open(stream);
+ var bundle = new AppBundle();
+ stream.Position = 0;
+ bundle.Load(zip,pebble.Firmware.HardwarePlatform.GetSoftwarePlatform());
+ pebble.InstallClient.InstallAppAsync(bundle, progress).Wait();
+
+ //for firmware v3, launch is done as part of the install
+ //Console.WriteLine("App Installed, launching...");
+ //var uuid=new UUID(bundle.AppInfo.UUID);
+ //pebble.LaunchApp(uuid);
+ //Console.WriteLine ("Launched");
+ }
}
}
+ else
+ {
+ Console.WriteLine("No .pbw");
+ }
+ }
+
+ private static void SendAppMessage(Pebble pebble)
+ {
+ string uuidAppPath = SelectApp();
+
+ if (!string.IsNullOrEmpty(uuidAppPath) && File.Exists(uuidAppPath))
+ {
+ using (var stream = new FileStream(uuidAppPath, FileMode.Open))
+ {
+ using (var zip = new Zip())
+ {
+ zip.Open(stream);
+ var bundle = new AppBundle();
+ stream.Position = 0;
+ bundle.Load(zip,pebble.Firmware.HardwarePlatform.GetSoftwarePlatform());
+
+ System.Console.Write("Enter Message:");
+ var messageText = System.Console.ReadLine();
+
+ //format a message
+ var rand = new Random().Next();
+ AppMessagePacket message = new AppMessagePacket();
+ message.Command = (byte)Command.Push;
+ message.Values.Add(new AppMessageUInt32() { Key=0,Value = (uint)rand });
+ message.Values.Add(new AppMessageString() { Key=1,Value = messageText });
+ message.ApplicationId = bundle.AppMetadata.UUID;
+ message.TransactionId = 255;
+
+
+ //send it
+ Console.WriteLine("Sending Status "+rand+" to " + bundle.AppMetadata.UUID.ToString());
+ var task = pebble.SendApplicationMessage(message);
+ task.Wait();
+ Console.WriteLine("Response received");
+ }
+ }
+
+
+
+ }
+ else
+ {
+ Console.WriteLine("No .pbw");
+ }
}
private static void ShowMediaCommands( Pebble pebble )
@@ -108,5 +233,10 @@ private static void DisplayResult( T result, Func successData )
Console.WriteLine( result.ErrorDetails.ToString() );
}
}
+
+ private static void ReceiveAppMessage(AppMessagePacket response)
+ {
+ System.Console.WriteLine("Recieved App Message");
+ }
}
}
diff --git a/PebbleSharp.Core.Tests/AbstractTests/BaseCrc32Tests.cs b/PebbleSharp.Core.Tests/AbstractTests/BaseCrc32Tests.cs
index d21766b..746025d 100644
--- a/PebbleSharp.Core.Tests/AbstractTests/BaseCrc32Tests.cs
+++ b/PebbleSharp.Core.Tests/AbstractTests/BaseCrc32Tests.cs
@@ -15,7 +15,7 @@ public abstract class BaseCrc32Tests
protected void RunGeneratesCorrectChecksumForApp()
{
var bundle = new AppBundle();
- bundle.Load(ResourceManager.GetAppBundle(), GetZip());
+ bundle.Load(GetZip(),SoftwarePlatform.UNKNOWN);
Assert.AreEqual(bundle.Manifest.Application.CRC, Crc32.Calculate(bundle.App));
}
@@ -23,7 +23,7 @@ protected void RunGeneratesCorrectChecksumForApp()
protected void RunGeneratesCorrectChecksumForFirmware()
{
var bundle = new FirmwareBundle();
- bundle.Load(ResourceManager.GetFirmwareBundle(), GetZip());
+ bundle.Load(GetZip(),SoftwarePlatform.UNKNOWN);
Assert.AreEqual(bundle.Manifest.Firmware.CRC, Crc32.Calculate(bundle.Firmware));
}
diff --git a/PebbleSharp.Core.Tests/AbstractTests/BasePebbleBundleTests.cs b/PebbleSharp.Core.Tests/AbstractTests/BasePebbleBundleTests.cs
index 7b91500..6979aad 100644
--- a/PebbleSharp.Core.Tests/AbstractTests/BasePebbleBundleTests.cs
+++ b/PebbleSharp.Core.Tests/AbstractTests/BasePebbleBundleTests.cs
@@ -18,7 +18,9 @@ protected void RunCanLoadInformationFromAppBundle()
{
Stream testBundle = ResourceManager.GetAppBundle();
var bundle = new AppBundle();
- bundle.Load( testBundle, GetZip() );
+ var zip = GetZip();
+ zip.Open(testBundle);
+ bundle.Load(zip,SoftwarePlatform.APLITE );
var manifest = bundle.Manifest;
Assert.IsNotNull( manifest );
@@ -40,7 +42,6 @@ protected void RunCanLoadInformationFromAppBundle()
Assert.AreEqual( (uint)0, bundle.AppMetadata.IconResourceID );
Assert.AreEqual( (uint)552, bundle.AppMetadata.Offset );
Assert.AreEqual( (uint)2, bundle.AppMetadata.RelocationListItemCount );
- Assert.AreEqual( (uint)3860, bundle.AppMetadata.RelocationListStart );
Assert.AreEqual( 3, bundle.AppMetadata.SDKMajorVersion );
Assert.AreEqual( 1, bundle.AppMetadata.SDKMinorVersion );
Assert.AreEqual( "3.1", bundle.AppMetadata.SDKVersion );
@@ -57,7 +58,9 @@ protected void RunCanLoadInformationFromFirmwareBundle()
Stream testBundle = ResourceManager.GetFirmwareBundle();
var bundle = new FirmwareBundle();
- bundle.Load( testBundle, GetZip() );
+ var zip = GetZip();
+ zip.Open(testBundle);
+ bundle.Load( zip,SoftwarePlatform.APLITE );
Assert.IsNotNull( bundle.Firmware );
diff --git a/PebbleSharp.Core.Tests/PebbleSharp.Core.Tests.csproj b/PebbleSharp.Core.Tests/PebbleSharp.Core.Tests.csproj
index e8d1edc..3a8e0e5 100644
--- a/PebbleSharp.Core.Tests/PebbleSharp.Core.Tests.csproj
+++ b/PebbleSharp.Core.Tests/PebbleSharp.Core.Tests.csproj
@@ -58,7 +58,6 @@
-
diff --git a/PebbleSharp.Core.Tests/PebbleTests.cs b/PebbleSharp.Core.Tests/PebbleTests.cs
index d310842..d5c3616 100644
--- a/PebbleSharp.Core.Tests/PebbleTests.cs
+++ b/PebbleSharp.Core.Tests/PebbleTests.cs
@@ -32,7 +32,7 @@ public async Task InstallFirmwareAsyncRequiresBundle()
var bluetoothConnection = new Mock();
var pebble = new TestPebble( bluetoothConnection.Object, TEST_PEBBLE_ID );
- await pebble.InstallFirmwareAsync( null );
+ await pebble.InstallClient.InstallFirmwareAsync( null );
}
[TestMethod]
@@ -75,7 +75,7 @@ public async Task InstallFirmwareAsyncTest()
var pebble = new TestPebble( bluetoothConnection.Object, TEST_PEBBLE_ID );
- bool success = await pebble.InstallFirmwareAsync( bundle.Object );
+ bool success = await pebble.InstallClient.InstallFirmwareAsync( bundle.Object );
Assert.IsTrue( success );
bluetoothConnection.Verify();
}
diff --git a/PebbleSharp.Core/AppInfo.cs b/PebbleSharp.Core/AppInfo.cs
new file mode 100644
index 0000000..2d78c3f
--- /dev/null
+++ b/PebbleSharp.Core/AppInfo.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Runtime.Serialization;
+
+namespace PebbleSharp.Core
+{
+ [DataContract]
+ public class AppInfo
+ {
+ //does exist in sdk3
+ [DataMember(Name = "versionCode", IsRequired = false)]
+ public int VersionCode { get; private set; }
+
+ [DataMember(Name = "sdkVersion", IsRequired = true)]
+ public string SdkVersion { get; private set; }
+
+ //[DataMember(Name = "capabilities", IsRequired = true)]
+ //public string[] Capabilities { get; private set; }
+
+ [DataMember(Name = "shortName", IsRequired = true)]
+ public string ShortName { get; private set; }
+
+ /// The manifest for the resources contained in this bundle.
+ //resources
+
+ //appKeys
+ //[DataMember(Name = "appKeys", IsRequired = true)]
+ //public Dictionary AppKeys { get; set; }
+
+ [DataMember(Name = "uuid", IsRequired = true)]
+ public string UUID { get; private set; }
+
+ [DataMember(Name = "versionLabel", IsRequired = true)]
+ public string VersionLabel { get; private set; }
+
+ [DataMember(Name = "longName", IsRequired = true)]
+ public string LongName { get; private set; }
+
+ //watchapp
+
+ [DataMember(Name = "projectType", IsRequired = true)]
+ public string ProjectType { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/PebbleSharp.Core/AppMessage/AppMessagePacket.cs b/PebbleSharp.Core/AppMessage/AppMessagePacket.cs
new file mode 100644
index 0000000..599baed
--- /dev/null
+++ b/PebbleSharp.Core/AppMessage/AppMessagePacket.cs
@@ -0,0 +1,490 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using PebbleSharp.Core.Responses;
+
+namespace PebbleSharp.Core.AppMessage
+{
+ //modeled after https://github.com/pebble/libpebble2/blob/master/libpebble2/services/appmessage.py
+ [Endpoint(Endpoint.ApplicationMessage)]
+ public class AppMessagePacket: ResponseBase
+ {
+ //TODO: the key names are in the manifest.... do we need them for anything?
+
+ //byte layout is as follows
+ //command (always 1?) byte
+ //transactionid byte
+ //uuid byte[16]
+ //tuplecount byte
+ //tuple
+ // k uint key
+ // t byte type
+ // l ushort length
+
+ //1:250:34:162:123:154:11:7:71:175:173:135:178:194:147:5:186:182:1:0:0:0:0:2:1:0:1
+ //sample message
+ //1: command
+ //250: transaction id
+ //34:162:123:154:11:7:71:175:173:135:178:194:147:5:186:182: //uuid
+ //1: //tuple count
+ //0:0:0:0: //first tuple, key
+ //2: //type
+ //1:0: //length
+ //1 //value
+
+ //sample outbound message
+ //1:
+ //0:
+ //11:7:71:175:173:135:178:194:147:5:186:182:255:255:255:255:
+ //1:
+ //0:0:0:0:
+ //2:
+ //4:0:
+ //87:195:209:30:
+
+ public byte Command { get; set; }
+
+ public byte TransactionId { get; set; }
+ public UUID ApplicationId { get; set; }
+
+ public AppMessagePacket()
+ {
+ Values = new List();
+ }
+
+ public AppMessagePacket(byte[] bytes)
+ {
+ Load(bytes);
+ }
+
+ protected override void Load(byte[] bytes)
+ {
+ Values = new List();
+ int index = 0;
+ var command = bytes[index];
+ index++;
+
+ Command = command;
+
+ TransactionId = bytes[index];
+ index++;
+
+ ApplicationId = new UUID(bytes.Skip(index).Take(16).ToArray());
+ index += 16;
+
+ int tupleCount = bytes[index];
+ index++;
+
+ for (int i = 0; i < tupleCount; i++)
+ {
+ uint k;
+ byte t;
+ ushort l;
+
+ k = BitConverter.ToUInt32(bytes, index);
+ index += 4;
+
+ t = bytes[index];
+ index++;
+
+ l = BitConverter.ToUInt16(bytes, index);
+ index += 2;
+
+ IAppMessageDictionaryEntry entry = null;
+ if (t == (byte)PackedType.Bytes)
+ {
+ entry = new AppMessageBytes() { Value = bytes.Skip(index).Take(l).ToArray() };
+ }
+ else if (t == (byte)PackedType.Signed)
+ {
+ if (l == 1)
+ {
+ entry = new AppMessageInt8() { Value = Convert.ToSByte(bytes[index]) };
+ }
+ else if (l == 2)
+ {
+ entry = new AppMessageInt16() { Value = BitConverter.ToInt16(bytes, index) };
+ }
+ else if (l == 4)
+ {
+ entry = new AppMessageInt32() { Value = BitConverter.ToInt32(bytes, index) };
+ }
+ else
+ {
+ throw new PebbleException("Invalid signed integer length");
+ }
+ }
+ else if (t == (byte)PackedType.String)
+ {
+ entry = new AppMessageString() { Value = System.Text.Encoding.UTF8.GetString(bytes, index, l) };
+ }
+ else if (t == (byte)PackedType.Unsigned)
+ {
+ if (l == 1)
+ {
+ entry = new AppMessageUInt8() { Value = bytes[index] };
+ }
+ else if (l == 2)
+ {
+ entry = new AppMessageUInt16() { Value = BitConverter.ToUInt16(bytes, index) };
+ }
+ else if (l == 4)
+ {
+ entry = new AppMessageUInt32() { Value = BitConverter.ToUInt32(bytes, index) };
+ }
+ else
+ {
+ throw new PebbleException("Invalid signed integer length");
+ }
+ }
+ else
+ {
+ throw new PebbleException("Unknown tuple type");
+ }
+ index += l;
+ entry.Key = k;
+ Values.Add(entry);
+ }
+ }
+
+ public IList Values { get; set; }
+
+ public byte[] GetBytes()
+ {
+ if (Values != null && Values.Any())
+ {
+ var bytes = new List();
+ bytes.Add(Command);
+ bytes.Add(TransactionId);
+ bytes.AddRange(ApplicationId.Data);
+ bytes.Add((byte)Values.Count);
+ foreach (var tuple in Values)
+ {
+ bytes.AddRange(tuple.PackedBytes);
+ }
+ return bytes.ToArray();
+ }
+ else
+ {
+ return new byte[0];
+ }
+ }
+ }
+
+ public interface IAppMessageDictionaryEntry
+ {
+ uint Key { get; set; }
+ PackedType PackedType { get; }
+ ushort Length { get; }
+ byte[] ValueBytes { get; set; }
+ byte[] PackedBytes { get; }
+ }
+
+ public enum Command:byte
+ {
+ Push=1
+ }
+
+ public enum PackedType:byte
+ {
+ Bytes=0,
+ String =1,
+ Unsigned =2,
+ Signed = 3
+ }
+
+ public abstract class AppMessageDictionaryEntry : IAppMessageDictionaryEntry
+ {
+ public uint Key { get; set; }
+ public abstract PackedType PackedType { get; }
+
+ public virtual ushort Length
+ {
+ get { return (ushort) System.Runtime.InteropServices.Marshal.SizeOf(typeof(T)); }
+ }
+
+ public virtual T Value { get; set; }
+ public abstract byte[] ValueBytes { get; set; }
+
+ public byte[] PackedBytes
+ {
+ get
+ {
+ var bytes = new List();
+ bytes.AddRange(BitConverter.GetBytes(Key));
+ bytes.Add((byte)PackedType);
+ bytes.AddRange(BitConverter.GetBytes(Length));
+ bytes.AddRange(ValueBytes);
+ return bytes.ToArray();
+ }
+ }
+
+ public new virtual string ToString()
+ {
+ return System.Convert.ToString(Value);
+ }
+ }
+
+ public class AppMessageUInt8 : AppMessageDictionaryEntry
+ {
+ public override PackedType PackedType
+ {
+ get { return PackedType.Unsigned; }
+ }
+
+ public override byte[] ValueBytes
+ {
+ get { return new byte[] {Value}; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ else if (value.Length == Length)
+ {
+ Value = value[0];
+ }
+ else
+ {
+ throw new PebbleException("Incorrect # of bytes");
+ }
+ }
+ }
+ }
+
+ public class AppMessageUInt16 : AppMessageDictionaryEntry
+ {
+ public override PackedType PackedType
+ {
+ get { return PackedType.Unsigned; }
+ }
+
+ public override byte[] ValueBytes
+ {
+ get { return BitConverter.GetBytes(Value); }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ else if (value.Length == Length)
+ {
+ Value = BitConverter.ToUInt16(value,0);
+ }
+ else
+ {
+ throw new PebbleException("Incorrect # of bytes");
+ }
+ }
+ }
+ }
+
+ public class AppMessageUInt32 : AppMessageDictionaryEntry
+ {
+ public override PackedType PackedType
+ {
+ get { return PackedType.Unsigned; }
+ }
+
+ public override byte[] ValueBytes
+ {
+ get { return BitConverter.GetBytes(Value); }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ else if (value.Length == Length)
+ {
+ Value = BitConverter.ToUInt32(value, 0);
+ }
+ else
+ {
+ throw new PebbleException("Incorrect # of bytes");
+ }
+ }
+ }
+ }
+
+ public class AppMessageInt8 : AppMessageDictionaryEntry
+ {
+ public override PackedType PackedType
+ {
+ get { return PackedType.Signed; }
+ }
+
+ public override byte[] ValueBytes
+ {
+ get { return new byte[] { Convert.ToByte(Value) }; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ else if (value.Length == Length)
+ {
+ Value = Convert.ToSByte(value);
+ }
+ else
+ {
+ throw new PebbleException("Incorrect # of bytes");
+ }
+ }
+ }
+ }
+
+ public class AppMessageInt16 : AppMessageDictionaryEntry
+ {
+ public override PackedType PackedType
+ {
+ get { return PackedType.Signed; }
+ }
+
+ public override byte[] ValueBytes
+ {
+ get { return BitConverter.GetBytes(Value); }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ else if (value.Length == Length)
+ {
+ Value = BitConverter.ToInt16(value, 0);
+ }
+ else
+ {
+ throw new PebbleException("Incorrect # of bytes");
+ }
+ }
+ }
+ }
+
+ public class AppMessageInt32 : AppMessageDictionaryEntry
+ {
+ public override PackedType PackedType
+ {
+ get { return PackedType.Signed; }
+ }
+
+ public override byte[] ValueBytes
+ {
+ get { return BitConverter.GetBytes(Value); }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ else if (value.Length == Length)
+ {
+ Value = BitConverter.ToInt32(value, 0);
+ }
+ else
+ {
+ throw new PebbleException("Incorrect # of bytes");
+ }
+ }
+ }
+ }
+
+ public class AppMessageString : AppMessageDictionaryEntry
+ {
+ public override PackedType PackedType
+ {
+ get { return PackedType.String; }
+ }
+
+ public override ushort Length
+ {
+ get { return (ushort)ValueBytes.Length; }
+ }
+
+ public override string Value
+ {
+ get
+ {
+ return base.Value;
+ }
+ set
+ {
+ if (value != null && value.EndsWith("\0"))
+ {
+ base.Value = value.Substring(0, value.Length - 1);
+ }
+ else
+ {
+ base.Value = value;
+ }
+ }
+ }
+
+ public override byte[] ValueBytes
+ {
+ get
+ {
+ return System.Text.UTF8Encoding.UTF8.GetBytes(Value+"\0");
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ else if (value.Length <= ushort.MaxValue)
+ {
+ Value = System.Text.UTF8Encoding.UTF8.GetString(value,0,value.Length);
+ }
+ else
+ {
+ throw new OverflowException("Specified string is too large for length to fit in a ushort");
+ }
+ }
+ }
+ }
+
+ public class AppMessageBytes : AppMessageDictionaryEntry
+ {
+ public override PackedType PackedType
+ {
+ get { return PackedType.Bytes; }
+ }
+
+ public override ushort Length
+ {
+ get { return (ushort)Value.Length; }
+ }
+
+ public override byte[] ValueBytes
+ {
+ get { return Value; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+ else if (value.Length <= ushort.MaxValue)
+ {
+ Value = value;
+ }
+ else
+ {
+ throw new OverflowException("Specified array is too large for length to fit in a ushort");
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ return BitConverter.ToString(Value).Replace("-", "").ToLower();
+ }
+ }
+}
diff --git a/PebbleSharp.Core/ApplicationManifest.cs b/PebbleSharp.Core/ApplicationManifest.cs
index 14d016b..e0f9c46 100644
--- a/PebbleSharp.Core/ApplicationManifest.cs
+++ b/PebbleSharp.Core/ApplicationManifest.cs
@@ -12,7 +12,7 @@ public struct ApplicationManifest
public string Filename { get; private set; }
/// The firmware version required to run this application.
- [DataMember(Name = "reqFwVer", IsRequired = true)]
+ [DataMember(Name = "reqFwVer", IsRequired = false)]
public int RequiredFirmwareVersion { get; private set; }
/// The time at which the application binary was created. (?)
diff --git a/PebbleSharp.Core/ApplicationMetadata.cs b/PebbleSharp.Core/ApplicationMetadata.cs
index 72928f2..79cde3f 100644
--- a/PebbleSharp.Core/ApplicationMetadata.cs
+++ b/PebbleSharp.Core/ApplicationMetadata.cs
@@ -23,6 +23,27 @@ public string StructVersion
get { return string.Format("{0}.{1}", StructMajorVersion, StructMinorVersion); }
}
+ /*
+ source: https://github.com/pebble/libpebble2/blob/d9ecce4a345f31217fb510f2f4e840f7cdda235b/libpebble2/util/bundle.py
+ python struct packing: https://docs.python.org/2/library/struct.html
+ STRUCT_DEFINITION = [
+ '8s', # header = char[8]
+ '2B', # struct version = byte[2]
+ '2B', # sdk version = byte[2]
+ '2B', # app version =byte[2]
+ 'H', # size = ushort
+ 'I', # offset = uint
+ 'I', # crc = uint
+ '32s', # app name = char[32]
+ '32s', # company name = char[32]
+ 'I', # icon resource id = uint
+ 'I', # symbol table address = uint
+ 'I', # flags = uint
+ 'I', # num relocation list entries = uint
+ '16s' # uuid = char[16]
+ ]
+ */
+
// The data as stored in the binary
[Serializable(Order = 0, Size = 8)]
public string Header { get; set; }
@@ -54,11 +75,11 @@ public string StructVersion
public uint SymbolTableAddress { get; set; }
[Serializable(Order = 14)]
public uint Flags { get; set; }
+ //[Serializable(Order = 15)]
+ //public uint RelocationListStart { get; set; }
[Serializable(Order = 15)]
- public uint RelocationListStart { get; set; }
- [Serializable(Order = 16)]
public uint RelocationListItemCount { get; set; }
- [Serializable(Order = 17)]
+ [Serializable(Order = 16)]
public UUID UUID { get; set; }
public override string ToString()
diff --git a/PebbleSharp.Core/Apps/AppFetchRequestPacket.cs b/PebbleSharp.Core/Apps/AppFetchRequestPacket.cs
new file mode 100644
index 0000000..5b60ca1
--- /dev/null
+++ b/PebbleSharp.Core/Apps/AppFetchRequestPacket.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Linq;
+using PebbleSharp.Core.Responses;
+namespace PebbleSharp.Core
+{
+ [Endpoint(Endpoint.AppFetch)]
+ public class AppFetchRequestPacket:ResponseBase
+ {
+ public byte Command { get; set; }
+ public UUID UUID { get; set; }
+ public int AppId { get; set; }
+
+ public AppFetchRequestPacket()
+ {
+ }
+
+ protected override void Load(byte[] payload)
+ {
+ Command = payload[0];
+ UUID = new UUID(payload.Skip(1).Take(16).ToArray());
+
+
+ //this packet is defined as little endian, which is slightly abnormal since
+ //it is coming from the pebble
+ //(most packets from he pebble are big endian / network endian)
+ //TODO: refactor Util conversions to respect per packet endian attributes
+ if (BitConverter.IsLittleEndian)
+ {
+ AppId = BitConverter.ToInt32(payload, 17);
+ }
+ else
+ {
+ AppId = BitConverter.ToInt32(payload.Skip(16).Take(4).ToArray().Reverse().ToArray(),0);
+ }
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/Apps/AppFetchResponsePacket.cs b/PebbleSharp.Core/Apps/AppFetchResponsePacket.cs
new file mode 100644
index 0000000..7ba66c1
--- /dev/null
+++ b/PebbleSharp.Core/Apps/AppFetchResponsePacket.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using PebbleSharp.Core.Responses;
+namespace PebbleSharp.Core
+{
+ [Endpoint(Endpoint.AppFetch)]
+ public class AppFetchResponsePacket
+ {
+ public byte Command { get; set; }
+ public AppFetchStatus Response { get; set; }
+
+ public AppFetchResponsePacket()
+ {
+ Command = 1;
+ }
+
+ public byte[] GetBytes()
+ {
+ return new byte[]{Command,(byte)Response};
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/Apps/AppRunStatePacket.cs b/PebbleSharp.Core/Apps/AppRunStatePacket.cs
new file mode 100644
index 0000000..a42f954
--- /dev/null
+++ b/PebbleSharp.Core/Apps/AppRunStatePacket.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using PebbleSharp.Core.Responses;
+namespace PebbleSharp.Core
+{
+ [Endpoint( Endpoint.AppRunState)]
+ public class AppRunStatePacket
+ {
+ public AppRunState Command { get; set; }
+ public UUID UUID { get; set; }
+
+ public AppRunStatePacket()
+ {
+ }
+
+ public byte[] GetBytes()
+ {
+ var bytes = new List();
+ bytes.Add((byte)Command);
+ if (Command == AppRunState.Start || Command == AppRunState.Stop)
+ {
+ bytes.AddRange(UUID.Data);
+ }
+ return bytes.ToArray();
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/BlobDB/AppMetaData.cs b/PebbleSharp.Core/BlobDB/AppMetaData.cs
new file mode 100644
index 0000000..4b6a087
--- /dev/null
+++ b/PebbleSharp.Core/BlobDB/AppMetaData.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using PebbleSharp.Core.Bundles;
+
+namespace PebbleSharp.Core.BlobDB
+{
+ public class AppMetaData
+ {
+ public UUID UUID { get; set; }
+ public UInt32 Flags { get; set; }
+ public UInt32 Icon { get; set; }
+ public byte AppVersionMajor { get; set; }
+ public byte AppVersionMinor { get; set; }
+ public byte SdkVersionMajor { get; set; }
+ public byte SdkVersionMinor { get; set; }
+ public byte AppFaceBackgroundColor { get; set; }
+ public byte AppFaceTemplateId { get; set; }
+ public string Name { get; set; }/*Fixed length 96*/
+
+ public byte[] GetBytes()
+ {
+ var bytes = new List();
+ bytes.AddRange(this.UUID.Data);
+ bytes.AddRange(BitConverter.GetBytes(Flags));
+ bytes.AddRange(BitConverter.GetBytes(Icon));
+ bytes.Add(AppVersionMajor);
+ bytes.Add(AppVersionMinor);
+ bytes.Add(SdkVersionMajor);
+ bytes.Add(SdkVersionMinor);
+ bytes.Add(AppFaceBackgroundColor);
+ bytes.Add(AppFaceTemplateId);
+ var name = Name;
+
+ //TODO: build "fixed" type strings into pebblesharp core
+ if (name.Length > 96)
+ {
+ name = name.Substring(0, 96);
+ }
+ name = name.PadRight(96, '\0');
+
+ var nameBytes = Util.GetBytes(name, false);
+
+ bytes.AddRange(nameBytes);
+ return bytes.ToArray();
+ }
+
+ public static AppMetaData FromAppBundle(AppBundle bundle, byte appFaceTemplateId = 0, byte appFaceBackgroundColor = 0)
+ {
+ var meta = new PebbleSharp.Core.BlobDB.AppMetaData();
+ meta.AppFaceTemplateId = appFaceTemplateId;
+ meta.AppFaceBackgroundColor = appFaceBackgroundColor;
+ meta.AppVersionMajor = bundle.AppMetadata.AppMajorVersion;
+ meta.AppVersionMinor = bundle.AppMetadata.AppMinorVersion;
+ meta.SdkVersionMajor = bundle.AppMetadata.SDKMajorVersion;
+ meta.SdkVersionMinor = bundle.AppMetadata.SDKMinorVersion;
+ meta.Flags = bundle.AppMetadata.Flags;
+ meta.Icon = bundle.AppMetadata.IconResourceID;
+ meta.UUID = bundle.AppMetadata.UUID;
+ meta.Name = bundle.AppMetadata.AppName;
+
+ return meta;
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/BlobDB/BlobDBClient.cs b/PebbleSharp.Core/BlobDB/BlobDBClient.cs
new file mode 100644
index 0000000..c590f94
--- /dev/null
+++ b/PebbleSharp.Core/BlobDB/BlobDBClient.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Threading.Tasks;
+
+namespace PebbleSharp.Core.BlobDB
+{
+ public class BlobDBClient
+ {
+ private Pebble _pebble;
+ private static Random _random;
+
+ static BlobDBClient()
+ {
+ _random = new Random();
+ }
+
+ internal BlobDBClient(Pebble pebble)
+ {
+ _pebble = pebble;
+ }
+
+ public async Task Insert(BlobDatabase database, byte[] key, byte[] value)
+ {
+ var insertCommand = new BlobDBCommandPacket()
+ {
+ Token = GenerateToken(),
+ Database = database,
+ Command = BlobCommand.Insert,
+ Key = key,
+ Value = value
+ };
+ return await Send(insertCommand);
+ }
+ public async Task Delete(BlobDatabase database, byte[] key)
+ {
+ var deleteCommand = new BlobDBCommandPacket()
+ {
+ Token = GenerateToken(),
+ Database = database,
+ Command = BlobCommand.Delete,
+ Key = key,
+ };
+ return await Send(deleteCommand);
+ }
+ public async Task Clear(BlobDatabase database)
+ {
+ var clearCommand = new BlobDBCommandPacket()
+ {
+ Token = GenerateToken(),
+ Database = database,
+ Command = BlobCommand.Clear
+ };
+ return await Send(clearCommand);
+ }
+ private async Task Send(BlobDBCommandPacket command)
+ {
+ return await _pebble.SendBlobDBMessage(command);
+ }
+ public ushort GenerateToken()
+ {
+ //this is how libpebble2 does it...random.randrange(1, 2**16 - 1, 1)
+ return (ushort)_random.Next(1, (2 ^ 16) - 1);
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/BlobDB/BlobDBCommandPacket.cs b/PebbleSharp.Core/BlobDB/BlobDBCommandPacket.cs
new file mode 100644
index 0000000..efb0b0d
--- /dev/null
+++ b/PebbleSharp.Core/BlobDB/BlobDBCommandPacket.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using PebbleSharp.Core;
+using PebbleSharp.Core.Responses;
+
+namespace PebbleSharp.Core.BlobDB
+{
+ [Endpoint(Endpoint.BlobDB)]
+ public class BlobDBCommandPacket
+ {
+ public BlobCommand Command {get;set;}
+ public ushort Token { get; set; }
+ public BlobDatabase Database { get; set; }
+
+ //only used for insert and delete commands
+ public byte[] Key { get; set; }
+ public byte KeyLength
+ {
+ get
+ {
+ return (byte)Key.Length;
+ }
+ }
+
+ //only used for insert
+ public byte[] Value { get; set; }
+ public ushort ValueLength
+ {
+ get
+ {
+ return (ushort)Value.Length;
+ }
+ }
+
+ public byte[] GetBytes()
+ {
+ var bytes = new List();
+ bytes.Add((byte)Command);
+ bytes.AddRange(BitConverter.GetBytes(Token));
+ bytes.Add((byte)Database);
+ if (Command == BlobCommand.Insert || Command == BlobCommand.Delete)
+ {
+ bytes.Add(KeyLength);
+ bytes.AddRange(Key);
+ if (Command == BlobCommand.Insert)
+ {
+ bytes.AddRange(BitConverter.GetBytes(ValueLength));
+ bytes.AddRange(Value);
+ }
+ }
+ return bytes.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/PebbleSharp.Core/BlobDB/BlobDBResponsePacket.cs b/PebbleSharp.Core/BlobDB/BlobDBResponsePacket.cs
new file mode 100644
index 0000000..a09c91a
--- /dev/null
+++ b/PebbleSharp.Core/BlobDB/BlobDBResponsePacket.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using PebbleSharp.Core;
+using PebbleSharp.Core.Responses;
+
+namespace PebbleSharp.Core.BlobDB
+{
+ [Endpoint(Endpoint.BlobDB)]
+ public class BlobDBResponsePacket :ResponseBase
+ {
+ public ushort Token { get; private set;}
+ public BlobStatus Response { get; private set; }
+
+ public byte[] Payload { get; private set; }
+ //token = Uint16()
+ //response = Uint8(enum=BlobStatus)
+
+ protected override void Load( byte[] payload )
+ {
+ if (payload.Length == 0)
+ {
+ SetError("BlobDB Command failed");
+ }
+ Payload = payload;
+ Token = BitConverter.ToUInt16(payload, 0);
+ Response = (BlobStatus)payload[2];
+ }
+ }
+
+
+}
+
diff --git a/PebbleSharp.Core/BlobDB/TimelineAction.cs b/PebbleSharp.Core/BlobDB/TimelineAction.cs
new file mode 100644
index 0000000..c20dae5
--- /dev/null
+++ b/PebbleSharp.Core/BlobDB/TimelineAction.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+namespace PebbleSharp.Core
+{
+ public class TimelineAction
+ {
+ public enum TimelineActionType:byte
+ {
+ AncsDismiss = 0x01,
+ Generic = 0x02,
+ Response = 0x03,
+ Dismiss = 0x04,
+ HTTP = 0x05,
+ Snooze = 0x06,
+ OpenWatchapp = 0x07,
+ Empty = 0x08,
+ Remove = 0x09,
+ OpenPin = 0x0a,
+ }
+ public byte ActionId { get; set; }
+ public TimelineActionType ActionType { get; set; }
+ public byte AttributeCount
+ {
+ get
+ {
+ if (Attributes != null)
+ {
+ return (byte)Attributes.Count;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+ public IList Attributes { get; set; }
+
+ public TimelineAction()
+ {
+
+ }
+
+ public byte[] GetBytes()
+ {
+ var bytes = new List();
+ bytes.Add(ActionId);
+ bytes.Add((byte)ActionType);
+ bytes.Add(AttributeCount);
+ if (Attributes != null)
+ {
+ foreach (var attribute in Attributes)
+ {
+ bytes.AddRange(attribute.GetBytes());
+ }
+ }
+ return bytes.ToArray();
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/BlobDB/TimelineAttribute.cs b/PebbleSharp.Core/BlobDB/TimelineAttribute.cs
new file mode 100644
index 0000000..af83525
--- /dev/null
+++ b/PebbleSharp.Core/BlobDB/TimelineAttribute.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+namespace PebbleSharp.Core
+{
+ public class TimelineAttribute
+ {
+ public byte AttributeId { get; set; }
+ public ushort Length
+ {
+ get
+ {
+ if (Content != null)
+ {
+ return (ushort)Content.Length;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+ public byte[] Content { get; set; }
+ public TimelineAttribute()
+ {
+ }
+
+ public byte[] GetBytes()
+ {
+ var bytes = new List();
+ bytes.Add(AttributeId);
+ bytes.AddRange(BitConverter.GetBytes(Length));
+ bytes.AddRange(Content);
+ return bytes.ToArray();
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/BlobDB/TimelineItem.cs b/PebbleSharp.Core/BlobDB/TimelineItem.cs
new file mode 100644
index 0000000..74eed12
--- /dev/null
+++ b/PebbleSharp.Core/BlobDB/TimelineItem.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+
+namespace PebbleSharp.Core
+{
+ public class TimelineItem
+ {
+ public enum TimelineItemType:byte
+ {
+ Notification=1,
+ Pin=2,
+ Reminder=3
+ }
+
+ public UUID ItemId { get; set; }
+ public UUID ParentId { get; set; }
+ public DateTime TimeStamp { get; set; }
+ public ushort Duration { get; set; }
+ public TimelineItemType ItemType { get; set; }
+ public ushort Flags { get; set; }
+ public byte Layout { get; set; }
+ public ushort DataLength { get; private set; }
+ public byte AttributeCount
+ {
+ get
+ {
+ if (Attributes != null)
+ {
+ return (byte)Attributes.Count;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+ public byte ActionCount
+ {
+ get
+ {
+ if (Actions != null)
+ {
+ return (byte)Actions.Count;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+ public IList Attributes { get; set; }
+ public IList Actions { get; set; }
+
+ public TimelineItem()
+ {
+ }
+
+ public byte[] GetBytes()
+ {
+ var bytes = new List();
+ bytes.AddRange(ItemId.Data);
+ bytes.AddRange(ParentId.Data);
+ bytes.AddRange(BitConverter.GetBytes(Util.GetTimestampFromDateTime(this.TimeStamp)));
+ bytes.AddRange(BitConverter.GetBytes(Duration));
+ bytes.Add((byte)this.ItemType);
+ bytes.AddRange(BitConverter.GetBytes(this.Flags));
+ bytes.Add(Layout);
+
+ var attributeBytes = new List();
+ if (Attributes != null)
+ {
+ foreach (var attribute in Attributes)
+ {
+ attributeBytes.AddRange(attribute.GetBytes());
+ }
+ }
+
+ var actionBytes = new List();
+ if (Actions != null)
+ {
+ foreach (var action in Actions)
+ {
+ actionBytes.AddRange(action.GetBytes());
+ }
+ }
+
+ this.DataLength = (ushort)(attributeBytes.Count + actionBytes.Count);
+ bytes.AddRange(BitConverter.GetBytes(DataLength));
+ bytes.Add(AttributeCount);
+ bytes.Add(ActionCount);
+ bytes.AddRange(attributeBytes);
+ bytes.AddRange(actionBytes);
+ return bytes.ToArray();
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/Bundles/AppBundle.cs b/PebbleSharp.Core/Bundles/AppBundle.cs
index 367fc7d..2358849 100644
--- a/PebbleSharp.Core/Bundles/AppBundle.cs
+++ b/PebbleSharp.Core/Bundles/AppBundle.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using PebbleSharp.Core.Serialization;
+using System.Runtime.Serialization.Json;
namespace PebbleSharp.Core.Bundles
{
@@ -9,13 +10,14 @@ public class AppBundle : BundleBase
public byte[] App { get; private set; }
public ApplicationMetadata AppMetadata { get; private set; }
-
+ public PebbleSharp.Core.AppInfo AppInfo { get; private set; }
+
protected override void LoadData( IZip zip )
{
if ( string.IsNullOrWhiteSpace( Manifest.Application.Filename ) )
- throw new InvalidOperationException( "Bundle does not contain pebble app" );
+ throw new PebbleException("Bundle does not contain pebble app");
- using ( Stream binStream = zip.OpenEntryStream( Manifest.Application.Filename ) )
+ using ( Stream binStream = zip.OpenEntryStream( this.PlatformSubdirectory()+Manifest.Application.Filename ) )
{
if ( binStream == null )
throw new Exception( string.Format( "App file {0} not found in archive", Manifest.Application.Filename ) );
@@ -23,6 +25,15 @@ protected override void LoadData( IZip zip )
App = Util.GetBytes( binStream );
AppMetadata = BinarySerializer.ReadObject( App );
+ }
+ //note, appinfo.json is NOT under the platform subdir
+ using (Stream appinfoStream = zip.OpenEntryStream("appinfo.json"))
+ {
+ if (appinfoStream != null)
+ {
+ var serializer = new DataContractJsonSerializer(typeof(PebbleSharp.Core.AppInfo));
+ AppInfo = (PebbleSharp.Core.AppInfo)serializer.ReadObject(appinfoStream);
+ }
}
}
diff --git a/PebbleSharp.Core/Bundles/BundleBase.cs b/PebbleSharp.Core/Bundles/BundleBase.cs
index 4f44f40..7c59f78 100644
--- a/PebbleSharp.Core/Bundles/BundleBase.cs
+++ b/PebbleSharp.Core/Bundles/BundleBase.cs
@@ -10,35 +10,40 @@ public abstract class BundleBase
public virtual bool HasResources { get; private set; }
public BundleManifest Manifest { get; private set; }
public virtual byte[] Resources { get; private set; }
+ public SoftwarePlatform Platform { get; private set;}
protected abstract void LoadData(IZip zip);
+ protected string PlatformSubdirectory()
+ {
+ var platformSubdirectory = (Platform == SoftwarePlatform.UNKNOWN ? "" : Platform.ToString().ToLower()+"/");
+ return platformSubdirectory;
+ }
+
+ private BundleManifest LoadManifest(IZip zip)
+ {
+ using (var manifestStream = zip.OpenEntryStream(PlatformSubdirectory() + "manifest.json"))
+ {
+ var serializer = new DataContractJsonSerializer(typeof(BundleManifest));
+ return (BundleManifest)serializer.ReadObject(manifestStream);
+ }
+ }
+
///
/// Create a new PebbleBundle from a .pwb file and parse its metadata.
///
/// The stream to the bundle.
/// The zip library implementation.
- public void Load(Stream bundle, IZip zip)
+ public void Load(IZip zip, SoftwarePlatform platform)
{
- //TODO: This needs to be refactored, probably put into a Load method
- if (false == zip.Open(bundle))
- throw new InvalidOperationException("Failed to open pebble bundle");
-
- using (Stream manifestStream = zip.OpenEntryStream("manifest.json"))
- {
- if (manifestStream == null)
- {
- throw new InvalidOperationException("manifest.json not found in archive - not a valid Pebble bundle.");
- }
- var serializer = new DataContractJsonSerializer(typeof(BundleManifest));
- Manifest = (BundleManifest)serializer.ReadObject(manifestStream);
- }
+ Platform = platform;
+ Manifest = LoadManifest (zip);
HasResources = (Manifest.Resources.Size != 0);
if (HasResources)
{
- using (Stream resourcesBinary = zip.OpenEntryStream(Manifest.Resources.Filename))
+ using (Stream resourcesBinary = zip.OpenEntryStream(PlatformSubdirectory()+Manifest.Resources.Filename))
{
if (resourcesBinary == null)
throw new PebbleException("Could not find resource entry in the bundle");
diff --git a/PebbleSharp.Core/Bundles/FirmwareBundle.cs b/PebbleSharp.Core/Bundles/FirmwareBundle.cs
index 6380116..167c287 100644
--- a/PebbleSharp.Core/Bundles/FirmwareBundle.cs
+++ b/PebbleSharp.Core/Bundles/FirmwareBundle.cs
@@ -10,7 +10,7 @@ public class FirmwareBundle : BundleBase
protected override void LoadData(IZip zip)
{
if (string.IsNullOrWhiteSpace(Manifest.Firmware.Filename))
- throw new InvalidOperationException("Bundle does not contain firmware");
+ throw new PebbleException("Bundle does not contain firmware");
using (Stream binStream = zip.OpenEntryStream(Manifest.Firmware.Filename))
{
diff --git a/PebbleSharp.Core/Enums.cs b/PebbleSharp.Core/Enums.cs
index fc07815..fbd3e71 100644
--- a/PebbleSharp.Core/Enums.cs
+++ b/PebbleSharp.Core/Enums.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace PebbleSharp.Core
{
@@ -17,61 +18,44 @@ public enum MediaControl : byte
SendNowPlaying = 9
}
- ///
- /// Endpoints (~"commands") used by Pebble to indicate particular instructions
- /// or instruction types.
- ///
- public enum Endpoint : ushort
- {
- Firmware = 1,
- Time = 11,
- FirmwareVersion = 16,
- PhoneVersion = 17,
- SystemMessage = 18,
- MusicControl = 32,
- PhoneControl = 33,
- ApplicationMessage = 48,
- Launcher = 49,
- AppCustomize = 50,
- Logs = 2000,
- Ping = 2001,
- LogDump = 2002,
- Reset = 2003,
- App = 2004,
- Mfg = 2004,
- AppLogs = 2006,
- Notification = 3000,
- Resource = 4000,
- SysReg = 5000,
- FctReg = 5001,
- AppManager = 6000,
- RunKeeper = 7000,
- PutBytes = 48879,
- DataLog = 6778,
- CoreDump = 9000,
- MaxEndpoint = 65535 //ushort.MaxValue
- }
+ ///
+ /// Endpoints (~"commands") used by Pebble to indicate particular instructions
+ /// or instruction types.
+ ///
+ public enum Endpoint : ushort
+ {
+ Firmware = 1,
+ Time = 11,
+ FirmwareVersion = 16,
+ PhoneVersion = 17,
+ SystemMessage = 18,
+ MusicControl = 32,
+ PhoneControl = 33,
+ ApplicationMessage = 48,
+ Launcher = 49,
+ AppCustomize = 50,
+ AppRunState = 52,
+ Logs = 2000,
+ Ping = 2001,
+ LogDump = 2002,
+ Reset = 2003,
+ App = 2004,
+ Mfg = 2004,
+ AppLogs = 2006,
+ Notification = 3000,
+ Resource = 4000,
+ SysReg = 5000,
+ FctReg = 5001,
+ AppManager = 6000,
+ AppFetch = 6001,
+ RunKeeper = 7000,
+ PutBytes = 48879,
+ DataLog = 6778,
+ CoreDump = 9000,
+ BlobDB = 45531,//0xb1db
+ MaxEndpoint = 65535, //ushort.MaxValue
- [Flags]
- public enum RemoteCaps : uint
- {
- Unknown = 0,
- IOS = 1,
- Android = 2,
- OSX = 3,
- Linux = 4,
- Windows = 5,
- Telephony = 16,
- SMS = 32,
- GPS = 64,
- BTLE = 128,
- // 240? No, that doesn't make sense. But it's apparently true.
- CameraFront = 240,
- CameraRear = 256,
- Accelerometer = 512,
- Gyro = 1024,
- Compass = 2048
- }
+ }
public enum LogLevel
{
@@ -84,11 +68,164 @@ public enum LogLevel
Verbose = 250
}
- public enum AppMessage : byte
+ public enum AppMessageCommand : byte
{
Push = 0x01,
Request = 0x02,
Ack = 0xFF,
Nack = 0x7F
}
+
+ public enum BlobDatabase:byte
+ {
+ Test = 0,
+ Pin = 1,
+ App = 2,
+ Reminder = 3,
+ Notification = 4
+ }
+
+ public enum BlobStatus:byte
+ {
+ Success = 0x01,
+ GeneralFailure = 0x02,
+ InvalidOperation = 0x03,
+ InvalidDatabaseID = 0x04,
+ InvalidData = 0x05,
+ KeyDoesNotExist = 0x06,
+ DatabaseFull = 0x07,
+ DataStale = 0x08
+ }
+
+ public enum BlobCommand:byte
+ {
+ Insert=0x01,
+ Delete=0x04,
+ Clear=0x05,
+ }
+
+ public enum AppRunState : byte
+ {
+ Start=0x01,
+ Stop=0x02,
+ Request=0x03,
+ }
+
+ public enum AppFetchStatus : byte
+ {
+ Start = 0x01,
+ Busy = 0x02,
+ InvalidUUID = 0x03,
+ NoData = 0x04
+ }
+
+
+ public enum TransferType : byte
+ {
+ Firmware = 1,
+ Recovery = 2,
+ SysResources = 3,
+ Resources = 4,
+ Binary = 5,
+ File=6,
+ Worker=7
+ }
+
+ public enum SystemMessage : byte
+ {
+ FirmwareAvailible = 0,
+ FirmwareStart = 1,
+ FirmwareComplete = 2,
+ FirmwareFail = 3,
+ FirmwareUpToDate = 4,
+ FirmwareOutOfDate = 5,
+ BluetoothStartDiscoverable = 6,
+ BluetoothEndDiscoverable = 7
+ }
+
+ public enum PutBytesType : byte
+ {
+ Init=0x01,
+ Put=0x02,
+ Commit=0x03,
+ Abort=0x04,
+ Install=0x05,
+ }
+
+ public enum PutBytesResult : byte
+ {
+ Ack=0x01,
+ Nack=0x02
+ }
+
+ public enum ResetCommand
+ {
+ Reset = 0x00,
+ DumpCore = 0x01,
+ FactoryReset = 0x02,
+ PRF = 0x03
+ }
+
+ public enum Hardware:byte
+ {
+ UNKNOWN = 0,
+ TINTIN_EV1 = 1,
+ TINTIN_EV2 = 2,
+ TINTIN_EV2_3 = 3,
+ TINTIN_EV2_4 = 4,
+ TINTIN_V1_5 = 5,
+ BIANCA = 6,
+ SNOWY_EVT2 = 7,
+ SNOWY_DVT = 8,
+ SPALDING_EVT = 9,
+ BOBBY_SMILES = 10,
+ SPALDING = 11,
+ TINTIN_BB = 0xFF,
+ TINTIN_BB2 = 0xFE,
+ SNOWY_BB = 0xFD,
+ SNOWY_BB2 = 0xFC,
+ SPALDING_BB2 = 0xFB,
+ }
+
+ //note that the names of these values are important, .ToString()
+ //is used to locate the correct platform specific subdirectory in appbundles
+ public enum SoftwarePlatform : byte
+ {
+ UNKNOWN,
+ APLITE,
+ BASALT,
+ CHALK
+ }
+
+ public static class HardwareHelpers
+ {
+ private static Dictionary Platforms;
+
+ static HardwareHelpers()
+ {
+ Platforms = new Dictionary();
+ Platforms.Add(Hardware.UNKNOWN, SoftwarePlatform.UNKNOWN);
+ Platforms.Add(Hardware.TINTIN_EV1, SoftwarePlatform.APLITE);
+ Platforms.Add(Hardware.TINTIN_EV2, SoftwarePlatform.APLITE);
+ Platforms.Add(Hardware.TINTIN_EV2_3, SoftwarePlatform.APLITE);
+ Platforms.Add(Hardware.TINTIN_EV2_4, SoftwarePlatform.APLITE);
+ Platforms.Add(Hardware.TINTIN_V1_5, SoftwarePlatform.APLITE);
+ Platforms.Add(Hardware.BIANCA, SoftwarePlatform.APLITE);
+ Platforms.Add(Hardware.SNOWY_EVT2, SoftwarePlatform.BASALT);
+ Platforms.Add(Hardware.SNOWY_DVT, SoftwarePlatform.BASALT);
+ Platforms.Add(Hardware.BOBBY_SMILES, SoftwarePlatform.BASALT);
+ Platforms.Add(Hardware.SPALDING_EVT, SoftwarePlatform.CHALK);
+ Platforms.Add(Hardware.SPALDING, SoftwarePlatform.CHALK);
+ Platforms.Add(Hardware.TINTIN_BB, SoftwarePlatform.APLITE);
+ Platforms.Add(Hardware.TINTIN_BB2, SoftwarePlatform.APLITE);
+ Platforms.Add(Hardware.SNOWY_BB, SoftwarePlatform.BASALT);
+ Platforms.Add(Hardware.SNOWY_BB2, SoftwarePlatform.BASALT);
+ Platforms.Add(Hardware.SPALDING_BB2, SoftwarePlatform.CHALK);
+ }
+
+ public static SoftwarePlatform GetSoftwarePlatform(this Hardware hardware)
+ {
+ return Platforms[hardware];
+ }
+ }
}
\ No newline at end of file
diff --git a/PebbleSharp.Core/FirmwareVersion.cs b/PebbleSharp.Core/FirmwareVersion.cs
index 67b7576..1920580 100644
--- a/PebbleSharp.Core/FirmwareVersion.cs
+++ b/PebbleSharp.Core/FirmwareVersion.cs
@@ -1,11 +1,13 @@
using System;
+using System.Collections;
+using System.Collections.Generic;
namespace PebbleSharp.Core
{
public class FirmwareVersion
{
public FirmwareVersion( DateTime timestamp, string version, string commit,
- bool isRecovery, byte hardwarePlatform, byte metadataVersion )
+ bool isRecovery, Hardware hardwarePlatform, byte metadataVersion )
{
Timestamp = timestamp;
Version = version;
@@ -19,9 +21,27 @@ public FirmwareVersion( DateTime timestamp, string version, string commit,
public string Version { get; private set; }
public string Commit { get; private set; }
public bool IsRecovery { get; private set; }
- public byte HardwarePlatform { get; private set; }
+ public Hardware HardwarePlatform { get; private set; }
public byte MetadataVersion { get; private set; }
+ public IList ParseVersionComponents()
+ {
+ var components = new List();
+ if (!string.IsNullOrWhiteSpace(Version))
+ {
+ string cleanedVersion = Version.Replace("v", "");
+ foreach (var component in cleanedVersion.Split(new char[] {'.', '-'}, StringSplitOptions.RemoveEmptyEntries))
+ {
+ int v;
+ if (int.TryParse(component, out v))
+ {
+ components.Add(v);
+ }
+ }
+ }
+ return components;
+ }
+
public override string ToString()
{
const string format = "Version {0}, commit {1} ({2})\n"
diff --git a/PebbleSharp.Core/Install/InstallClient.cs b/PebbleSharp.Core/Install/InstallClient.cs
new file mode 100644
index 0000000..804c219
--- /dev/null
+++ b/PebbleSharp.Core/Install/InstallClient.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Threading.Tasks;
+using System.Linq;
+using System.Collections.Generic;
+using PebbleSharp.Core.Responses;
+using PebbleSharp.Core.Bundles;
+using PebbleSharp.Core.BlobDB;
+
+namespace PebbleSharp.Core.Install
+{
+ public class InstallClient
+ {
+ private Pebble _pebble;
+
+ internal InstallClient(Pebble pebble)
+ {
+ _pebble = pebble;
+ }
+
+ public async Task InstallAppAsync( AppBundle bundle, IProgress progress = null )
+ {
+ IList versionComponents = _pebble.Firmware.ParseVersionComponents();
+ if (versionComponents[0] < 3)
+ {
+ await InstallAppLegacyV2 (bundle, progress);
+ }
+ else
+ {
+ await InstallAppAsyncV3 (bundle, progress);
+ }
+ }
+
+ private async Task InstallAppAsyncV3(AppBundle bundle,IProgress progress)
+ {
+ //https://github.com/pebble/libpebble2/blob/master/libpebble2/services/install.py
+
+ var meta = AppMetaData.FromAppBundle(bundle);
+
+ var bytes = meta.GetBytes();
+ var result = await _pebble.BlobDBClient.Delete(BlobDatabase.App, meta.UUID.Data);
+
+ result = await _pebble.BlobDBClient.Insert(BlobDatabase.App, meta.UUID.Data, bytes);
+
+ if (result.Response == BlobStatus.Success)
+ {
+ var startPacket = new AppRunStatePacket();
+ startPacket.Command = AppRunState.Start;
+ startPacket.UUID = meta.UUID;
+ //app_fetch = self._pebble.send_and_read(AppRunState(data=AppRunStateStart(uuid=app_uuid)), AppFetchRequest)
+
+ var runStateResult = await _pebble.SendMessageAsync(Endpoint.AppRunState, startPacket.GetBytes());
+
+ if (!runStateResult.Success)
+ {
+ throw new PebbleException("Pebble replied invalid run state");
+ }
+
+ if (!meta.UUID.Equals(runStateResult.UUID))
+ {
+ var response = new AppFetchResponsePacket();
+ response.Response = AppFetchStatus.InvalidUUID;
+ await _pebble.SendMessageNoResponseAsync(Endpoint.AppFetch, response.GetBytes());
+ throw new PebbleException("The pebble requested the wrong UUID");
+ }
+
+ var putBytesResponse = await _pebble.PutBytesClient.PutBytes(bundle.App, TransferType.Binary, appInstallId:(uint)runStateResult.AppId);
+ if (!putBytesResponse)
+ {
+ throw new PebbleException("Putbytes failed");
+ }
+
+ if (bundle.HasResources)
+ {
+ putBytesResponse = await _pebble.PutBytesClient.PutBytes(bundle.Resources, TransferType.Resources, appInstallId:(uint)runStateResult.AppId);
+ if (!putBytesResponse)
+ {
+ throw new PebbleException("Putbytes failed");
+ }
+ }
+
+ //TODO: add worker to manifest and transfer it if necassary
+ //if (bundle.HasWorker)
+ //{
+ //await PutBytesV3(bundle.Worker, TransferType.Worker, runStateResult.AppId);
+ //}
+
+ }
+ else
+ {
+ throw new PebbleException("BlobDB Insert Failed");
+ }
+ }
+
+ private async Task InstallAppLegacyV2(AppBundle bundle, IProgress progress= null)
+ {
+ if ( bundle == null )
+ throw new ArgumentNullException( "bundle" );
+
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Removing previous install(s) of the app if they exist", 1 ) );
+ ApplicationMetadata metaData = bundle.AppMetadata;
+ UUID uuid = metaData.UUID;
+
+ AppbankInstallResponse appbankInstallResponse = await RemoveAppByUUID( uuid );
+ if ( appbankInstallResponse.Success == false )
+ return;
+
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Getting current apps", 20 ) );
+ AppbankResponse appBankResult = await GetAppbankContentsAsync();
+
+ if ( appBankResult.Success == false )
+ throw new PebbleException( "Could not obtain app list; try again" );
+ AppBank appBank = appBankResult.AppBank;
+
+ byte firstFreeIndex = 1;
+ foreach ( App app in appBank.Apps )
+ if ( app.Index == firstFreeIndex )
+ firstFreeIndex++;
+ if ( firstFreeIndex == appBank.Size )
+ throw new PebbleException( "All app banks are full" );
+
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Transferring app to Pebble", 40 ) );
+
+ if ( await _pebble.PutBytesClient.PutBytes( bundle.App, TransferType.Binary,index:firstFreeIndex ) == false )
+ throw new PebbleException( "Failed to send application binary pebble-app.bin" );
+
+ if ( bundle.HasResources )
+ {
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Transferring app resources to Pebble", 60 ) );
+ if ( await _pebble.PutBytesClient.PutBytes( bundle.Resources, TransferType.Resources,index:firstFreeIndex ) == false )
+ throw new PebbleException( "Failed to send application resources app_resources.pbpack" );
+ }
+
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Adding app", 80 ) );
+ await AddApp( firstFreeIndex );
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Done", 100 ) );
+ }
+
+
+
+ public async Task InstallFirmwareAsync( FirmwareBundle bundle, IProgress progress = null )
+ {
+ if ( bundle == null ) throw new ArgumentNullException( "bundle" );
+
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Starting firmware install", 1 ) );
+ if ( ( await _pebble.SendSystemMessageAsync( SystemMessage.FirmwareStart ) ).Success == false )
+ {
+ return false;
+ }
+
+ if ( bundle.HasResources )
+ {
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Transfering firmware resources", 25 ) );
+ if ( await _pebble.PutBytesClient.PutBytes( bundle.Resources, TransferType.SysResources,index:0 ) == false )
+ {
+ return false;
+ }
+ }
+
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Transfering firmware", 50 ) );
+ if ( await _pebble.PutBytesClient.PutBytes( bundle.Firmware, TransferType.Firmware,index:0 ) == false )
+ {
+ return false;
+ }
+
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Completing firmware install", 75 ) );
+ bool success = ( await _pebble.SendSystemMessageAsync( SystemMessage.FirmwareComplete ) ).Success;
+
+ if ( progress != null )
+ progress.Report( new ProgressValue( "Done installing firmware", 100 ) );
+
+ return success;
+ }
+
+ public async Task RemoveAppByUUID( UUID uuid )
+ {
+ byte[] data = Util.CombineArrays( new byte[] { 2 }, uuid.Data );
+ return await _pebble.SendMessageAsync( Endpoint.AppManager, data );
+ }
+
+ public async Task AddApp( byte index )
+ {
+ byte[] data = Util.CombineArrays( new byte[] { 3 }, Util.GetBytes( (uint)index ) );
+ await _pebble.SendMessageNoResponseAsync( Endpoint.AppManager, data );
+ }
+
+ public async Task GetAppbankContentsAsync()
+ {
+ return await _pebble.SendMessageAsync( Endpoint.AppManager, new byte[] { 1 } );
+ }
+ }
+}
+
diff --git a/PebbleSharp.Core/Pebble.cs b/PebbleSharp.Core/Pebble.cs
index 178f387..5bf157e 100644
--- a/PebbleSharp.Core/Pebble.cs
+++ b/PebbleSharp.Core/Pebble.cs
@@ -3,9 +3,13 @@
using System.Diagnostics;
using System.Globalization;
using System.Linq;
+using System.Text;
using System.Threading.Tasks;
using PebbleSharp.Core.Bundles;
+using PebbleSharp.Core.AppMessage;
using PebbleSharp.Core.Responses;
+using PebbleSharp.Core.BlobDB;
+using PebbleSharp.Core.Install;
namespace PebbleSharp.Core
{
@@ -16,19 +20,15 @@ namespace PebbleSharp.Core
///
public abstract class Pebble
{
- public enum SessionCaps : uint
- {
- GAMMA_RAY = 0x80000000
- }
-
- public const byte PEBBLE_CLIENT_VERSION = 2;
-
- private readonly PebbleProtocol _PebbleProt;
+ public FirmwareVersion Firmware { get; private set;}
+ private readonly PebbleProtocol _PebbleProt;
private readonly Dictionary> _callbackHandlers;
private readonly ResponseManager _responseManager = new ResponseManager();
- private uint _RemoteCaps = (uint)( RemoteCaps.Telephony | RemoteCaps.SMS | RemoteCaps.Android );
- private uint _SessionCaps = (uint)SessionCaps.GAMMA_RAY;
+
+ public BlobDBClient BlobDBClient { get; private set;}
+ public PutBytesClient PutBytesClient { get; private set;}
+ public InstallClient InstallClient { get; private set;}
///
/// Create a new Pebble
@@ -40,15 +40,18 @@ public enum SessionCaps : uint
///
protected Pebble( IBluetoothConnection connection, string pebbleId )
{
- ResponseTimeout = TimeSpan.FromSeconds( 5 );
+ ResponseTimeout = TimeSpan.FromSeconds( 5 );
PebbleID = pebbleId;
+ BlobDBClient = new BlobDBClient(this);
+ PutBytesClient = new PutBytesClient(this);
+ InstallClient = new InstallClient(this);
_callbackHandlers = new Dictionary>();
_PebbleProt = new PebbleProtocol( connection );
_PebbleProt.RawMessageReceived += RawMessageReceived;
- RegisterCallback( OnApplicationMessageReceived );
+ RegisterCallback( OnApplicationMessageReceived );
}
///
@@ -68,25 +71,6 @@ public IBluetoothConnection Connection
public TimeSpan ResponseTimeout { get; set; }
- ///
- /// Set the capabilities you want to tell the Pebble about.
- /// Should be called before connecting.
- ///
- ///
- ///
- public void SetCaps( uint? sessionCap = null, uint? remoteCaps = null )
- {
- if ( sessionCap != null )
- {
- _SessionCaps = (uint)sessionCap;
- }
-
- if ( remoteCaps != null )
- {
- _RemoteCaps = (uint)remoteCaps;
- }
- }
-
///
/// Connect with the Pebble.
///
@@ -104,20 +88,21 @@ public async Task ConnectAsync()
if ( response != null )
{
- byte[] prefix = { PEBBLE_CLIENT_VERSION, 0xFF, 0xFF, 0xFF, 0xFF };
- byte[] session = Util.GetBytes( _SessionCaps );
- byte[] remote = Util.GetBytes( _RemoteCaps );
-
- byte[] msg = Util.CombineArrays( prefix, session, remote );
- //\x01\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x002
- await SendMessageNoResponseAsync( Endpoint.PhoneVersion, msg );
+ var message = new AppVersionResponse();
+ await SendMessageNoResponseAsync( Endpoint.PhoneVersion, message.GetBytes() );
IsAlive = true;
+
+ //get the firmware details, we'll need to know the platform and version for possible future actions
+ var firmwareResponse = await this.GetFirmwareVersionAsync();
+ this.Firmware = firmwareResponse.Firmware;
}
else
{
Disconnect();
}
- }
+ }
+
+
///
/// Disconnect from the Pebble, if a connection existed.
@@ -247,93 +232,7 @@ public async Task BadPingAsync()
return await SendMessageAsync( Endpoint.Ping, cookie );
}
- public async Task InstallAppAsync( AppBundle bundle, IProgress progress = null )
- {
- if ( bundle == null )
- throw new ArgumentNullException( "bundle" );
-
- if ( progress != null )
- progress.Report( new ProgressValue( "Removing previous install(s) of the app if they exist", 1 ) );
- ApplicationMetadata metaData = bundle.AppMetadata;
- UUID uuid = metaData.UUID;
-
- AppbankInstallResponse appbankInstallResponse = await RemoveAppByUUID( uuid );
- if ( appbankInstallResponse.Success == false )
- return;
-
- if ( progress != null )
- progress.Report( new ProgressValue( "Getting current apps", 20 ) );
- AppbankResponse appBankResult = await GetAppbankContentsAsync();
-
- if ( appBankResult.Success == false )
- throw new PebbleException( "Could not obtain app list; try again" );
- AppBank appBank = appBankResult.AppBank;
-
- byte firstFreeIndex = 1;
- foreach ( App app in appBank.Apps )
- if ( app.Index == firstFreeIndex )
- firstFreeIndex++;
- if ( firstFreeIndex == appBank.Size )
- throw new PebbleException( "All app banks are full" );
-
- if ( progress != null )
- progress.Report( new ProgressValue( "Transferring app to Pebble", 40 ) );
-
- if ( await PutBytes( bundle.App, firstFreeIndex, TransferType.Binary ) == false )
- throw new PebbleException( "Failed to send application binary pebble-app.bin" );
-
- if ( bundle.HasResources )
- {
- if ( progress != null )
- progress.Report( new ProgressValue( "Transferring app resources to Pebble", 60 ) );
- if ( await PutBytes( bundle.Resources, firstFreeIndex, TransferType.Resources ) == false )
- throw new PebbleException( "Failed to send application resources app_resources.pbpack" );
- }
-
- if ( progress != null )
- progress.Report( new ProgressValue( "Adding app", 80 ) );
- await AddApp( firstFreeIndex );
- if ( progress != null )
- progress.Report( new ProgressValue( "Done", 100 ) );
- }
-
- public async Task InstallFirmwareAsync( FirmwareBundle bundle, IProgress progress = null )
- {
- if ( bundle == null ) throw new ArgumentNullException( "bundle" );
-
- if ( progress != null )
- progress.Report( new ProgressValue( "Starting firmware install", 1 ) );
- if ( ( await SendSystemMessageAsync( SystemMessage.FirmwareStart ) ).Success == false )
- {
- return false;
- }
-
- if ( bundle.HasResources )
- {
- if ( progress != null )
- progress.Report( new ProgressValue( "Transfering firmware resources", 25 ) );
- if ( await PutBytes( bundle.Resources, 0, TransferType.SysResources ) == false )
- {
- return false;
- }
- }
-
- if ( progress != null )
- progress.Report( new ProgressValue( "Transfering firmware", 50 ) );
- if ( await PutBytes( bundle.Firmware, 0, TransferType.Firmware ) == false )
- {
- return false;
- }
-
- if ( progress != null )
- progress.Report( new ProgressValue( "Completing firmware install", 75 ) );
- bool success = ( await SendSystemMessageAsync( SystemMessage.FirmwareComplete ) ).Success;
-
- if ( progress != null )
- progress.Report( new ProgressValue( "Done installing firmware", 100 ) );
-
- return success;
- }
+
public async Task GetFirmwareVersionAsync()
{
@@ -349,15 +248,6 @@ public async Task GetTimeAsync()
return await SendMessageAsync( Endpoint.Time, new byte[] { 0 } );
}
- ///
- /// Fetch the contents of the Appbank.
- ///
- ///
- public async Task GetAppbankContentsAsync()
- {
- return await SendMessageAsync( Endpoint.AppManager, new byte[] { 1 } );
- }
-
///
/// Remove an app from the Pebble, using an App instance retrieved from the Appbank.
///
@@ -372,13 +262,13 @@ public async Task RemoveAppAsync( App app )
return await SendMessageAsync( Endpoint.AppManager, msg );
}
- private async Task SendSystemMessageAsync( SystemMessage message )
+ public async Task SendSystemMessageAsync( SystemMessage message )
{
byte[] data = { 0, (byte)message };
return await SendMessageAsync( Endpoint.SystemMessage, data );
}
- private async Task SendMessageAsync( Endpoint endpoint, byte[] payload )
+ public async Task SendMessageAsync( Endpoint endpoint, byte[] payload )
where T : class, IResponse, new()
{
return await Task.Run( () =>
@@ -412,7 +302,7 @@ private async Task SendMessageAsync( Endpoint endpoint, byte[] payload )
} );
}
- private Task SendMessageNoResponseAsync( Endpoint endpoint, byte[] payload )
+ public Task SendMessageNoResponseAsync( Endpoint endpoint, byte[] payload )
{
return Task.Run( () =>
{
@@ -435,101 +325,74 @@ private Task SendMessageNoResponseAsync( Endpoint endpoint, byte[] payload )
private void RawMessageReceived( object sender, RawMessageReceivedEventArgs e )
{
- Debug.WriteLine( "Received {0} message: {1}", (Endpoint)e.Endpoint, BitConverter.ToString( e.Payload ) );
+ var endpoint = (Endpoint)e.Endpoint;
+ IResponse response = _responseManager.HandleResponse( endpoint, e.Payload );
- IResponse response = _responseManager.HandleResponse( (Endpoint)e.Endpoint, e.Payload );
+ if (response != null)
+ {
+ if (e.Endpoint == (ushort)Endpoint.PhoneVersion)
+ {
+ var message = new AppVersionResponse();
+ SendMessageNoResponseAsync(Endpoint.PhoneVersion, message.GetBytes()).Wait();
+ }
- if ( response != null )
- {
- //Check for callbacks
- List callbacks;
- if ( _callbackHandlers.TryGetValue( response.GetType(), out callbacks ) )
- {
- foreach ( CallbackContainer callback in callbacks )
- callback.Invoke( response );
- }
- }
- }
+ //Check for callbacks
+ List callbacks;
+ if (_callbackHandlers.TryGetValue(response.GetType(), out callbacks))
+ {
+ foreach (CallbackContainer callback in callbacks)
+ callback.Invoke(response);
+ }
+
+ }
- private void OnApplicationMessageReceived( ApplicationMessageResponse response )
- {
- SendMessageNoResponseAsync( Endpoint.ApplicationMessage, new byte[] { 0xFF, response.TID } );
}
- public override string ToString()
+ public async Task SendBlobDBMessage(BlobDBCommandPacket command)
+ {
+ //TODO: I'm not sure we should assume that the first blobdb response we get is the one that
+ //corresponds to this request, we probably need to do extra work here to match up the token
+ var bytes = command.GetBytes();
+
+ return await SendMessageAsync(Endpoint.BlobDB,bytes );
+ }
+
+ public async Task SendApplicationMessage(AppMessagePacket data)
{
- return string.Format( "Pebble {0} on {1}", PebbleID, Connection );
+ //DebugMessage(data.GetBytes());
+ return await SendMessageAsync(Endpoint.ApplicationMessage, data.GetBytes());
}
- private async Task RemoveAppByUUID( UUID uuid )
+ //self._pebble.send_packet(AppRunState(data=AppRunStateStart(uuid=app_uuid)))
+ public async Task LaunchApp(UUID uuid)
{
- byte[] data = Util.CombineArrays( new byte[] { 2 }, uuid.Data );
- return await SendMessageAsync( Endpoint.AppManager, data );
+ var data = new AppMessagePacket();
+ data.ApplicationId = uuid;
+ data.Command = (byte)Command.Push;
+ data.TransactionId = 1;
+ data.Values.Add(new AppMessageUInt8() { Key=1,Value = 1 });//this one is key 0, doesn't actually do anything
+
+ await SendMessageNoResponseAsync(Endpoint.Launcher, data.GetBytes());
}
- private async Task PutBytes( byte[] binary, byte index, TransferType transferType )
+ private void OnApplicationMessageReceived( AppMessagePacket response )
{
- byte[] length = Util.GetBytes( binary.Length );
+ SendMessageNoResponseAsync( Endpoint.ApplicationMessage, new byte[] { 0xFF, response.Values!=null ? response.TransactionId :(byte)0} );
+ }
- //Get token
- byte[] header = Util.CombineArrays( new byte[] { 1 }, length, new[] { (byte)transferType, index } );
-
- var rawMessageArgs = await SendMessageAsync( Endpoint.PutBytes, header );
- if ( rawMessageArgs.Success == false )
- return false;
-
- byte[] tokenResult = rawMessageArgs.Response;
- byte[] token = tokenResult.Skip( 1 ).ToArray();
-
- const int BUFFER_SIZE = 2000;
- //Send at most 2000 bytes at a time
- for ( int i = 0; i <= binary.Length / BUFFER_SIZE; i++ )
- {
- byte[] data = binary.Skip( BUFFER_SIZE * i ).Take( BUFFER_SIZE ).ToArray();
- byte[] dataHeader = Util.CombineArrays( new byte[] { 2 }, token, Util.GetBytes( data.Length ) );
- var result = await SendMessageAsync( Endpoint.PutBytes, Util.CombineArrays( dataHeader, data ) );
- if ( result.Success == false )
- {
- await AbortPutBytesAsync( token );
- return false;
- }
- }
-
- //Send commit message
- uint crc = Crc32.Calculate( binary );
- byte[] crcBytes = Util.GetBytes( crc );
- byte[] commitMessage = Util.CombineArrays( new byte[] { 3 }, token, crcBytes );
- var commitResult = await SendMessageAsync( Endpoint.PutBytes, commitMessage );
- if ( commitResult.Success == false )
+ private void DebugMessage(byte[] bytes)
+ {
+ StringBuilder payloadDebugger = new StringBuilder();
+ foreach (var b in bytes)
{
- await AbortPutBytesAsync( token );
- return false;
+ payloadDebugger.Append(string.Format("{0}:", b));
}
-
- //Send complete message
- byte[] completeMessage = Util.CombineArrays( new byte[] { 5 }, token );
- var completeResult = await SendMessageAsync( Endpoint.PutBytes, completeMessage );
- if ( completeResult.Success == false )
- {
- await AbortPutBytesAsync( token );
- }
- return completeResult.Success;
}
- private async Task AbortPutBytesAsync( byte[] token )
- {
- if ( token == null ) throw new ArgumentNullException( "token" );
-
- byte[] data = Util.CombineArrays( new byte[] { 4 }, token );
-
- return await SendMessageAsync( Endpoint.PutBytes, data );
- }
-
- private async Task AddApp( byte index )
+ public override string ToString()
{
- byte[] data = Util.CombineArrays( new byte[] { 3 }, Util.GetBytes( (uint)index ) );
- await SendMessageNoResponseAsync( Endpoint.AppManager, data );
+ return string.Format( "Pebble {0} on {1}", PebbleID, Connection );
}
private class CallbackContainer
@@ -557,25 +420,10 @@ public void Invoke( IResponse response )
}
}
- private enum TransferType : byte
- {
- Firmware = 1,
- Recovery = 2,
- SysResources = 3,
- Resources = 4,
- Binary = 5
- }
+ public void Reset(ResetCommand command)
+ {
+ _PebbleProt.SendMessage((ushort)Endpoint.Reset, new byte[] { (byte)command });
+ }
- private enum SystemMessage : byte
- {
- FirmwareAvailible = 0,
- FirmwareStart = 1,
- FirmwareComplete = 2,
- FirmwareFail = 3,
- FirmwareUpToDate = 4,
- FirmwareOutOfDate = 5,
- BluetoothStartDiscoverable = 6,
- BluetoothEndDiscoverable = 7
- }
}
}
\ No newline at end of file
diff --git a/PebbleSharp.Core/PebbleSharp.Core.csproj b/PebbleSharp.Core/PebbleSharp.Core.csproj
index e88aded..47dc1a4 100644
--- a/PebbleSharp.Core/PebbleSharp.Core.csproj
+++ b/PebbleSharp.Core/PebbleSharp.Core.csproj
@@ -36,6 +36,7 @@
+
@@ -59,7 +60,7 @@
-
+
@@ -67,7 +68,6 @@
-
@@ -76,6 +76,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+