Created
September 1, 2024 19:42
-
-
Save lwsrbrts/cc02314a7ff203897061da8c7dffbab0 to your computer and use it in GitHub Desktop.
An example PowerShell script to obtain data from the Ecoflow HTTP API. Requires signing up for a developer account.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Get a timestamp that's in a format suitable for the Ecoflow HTTP API. | |
function Get-Timestamp { | |
return [int64](([datetime]::UtcNow - [datetime]'1970-01-01').TotalMilliseconds) | |
} | |
# Get a nonce (random variable) that's in a format suitable for the Ecoflow HTTP API. | |
function Get-Nonce { | |
return (Get-Random -Minimum 100000 -Maximum 999999).ToString() | |
} | |
# Flatten a hashtable in to a single layer suitable for processing by Format-SigningString. | |
function Merge-Hashtable { | |
param ( | |
[hashtable]$Hashtable, | |
[string]$Prefix = '' | |
) | |
if ($hashtable -eq $null) { return $null } | |
$result = @{} | |
foreach ($key in $Hashtable.Keys) { | |
$value = $Hashtable[$key] | |
$newKey = if ($Prefix) { "$Prefix.$key" } else { $key } | |
if ($value -is [hashtable]) { | |
$result += Merge-Hashtable -Hashtable $value -Prefix $newKey | |
} | |
elseif ($value -is [array]) { | |
for ($i = 0; $i -lt $value.Length; $i++) { | |
$result["$newKey[$i]"] = $value[$i] | |
} | |
} | |
else { | |
$result[$newKey] = $value | |
} | |
} | |
return $result | |
} | |
# Format the signing string (the thing we must sign) in to a format suitable for the Ecoflow API. | |
# This could be parameters from a provided hashtable, parameters from the HTTP GET URL | |
# but never both _OR_ it could be none of these, but we still need to return a suitable signing string. | |
function Format-SigningString { | |
param ( | |
[hashtable]$params, | |
[string]$url_params, | |
[string]$EcoflowAccessKey, | |
[string]$nonce, | |
[string]$timestamp | |
) | |
# Initialize the signable string | |
$signableString = "" | |
if ($params) { | |
# If there are parameters, sort them by key (name) in ASCII order and concatenate them into a string | |
$sortedParams = ($params.GetEnumerator() | Sort-Object Name | ForEach-Object { | |
"$($_.Key)=$($_.Value)" | |
}) -join "&" | |
# Build the signable string with sorted parameters | |
$signableString = "$sortedParams&accessKey=$EcoflowAccessKey&nonce=$nonce×tamp=$timestamp" | |
} | |
elseif ($url_params) { | |
# If no parameters were provided but URL parameters exist, use them | |
$signableString = "$url_params&accessKey=$EcoflowAccessKey&nonce=$nonce×tamp=$timestamp" | |
} | |
else { | |
# If neither parameters nor URL parameters are provided, build a signable string with just the basic keys | |
$signableString = "accessKey=$EcoflowAccessKey&nonce=$nonce×tamp=$timestamp" | |
} | |
return $signableString | |
} | |
# We sign the request (technically the stringToSign) using HMAC-SHA256. | |
function Get-Signature { | |
param ( | |
[string]$stringToSign, | |
[string]$access_key, | |
[string]$secret_key, | |
[string]$nonce, | |
[string]$timestamp | |
) | |
# Create HMAC-SHA256 signature | |
$hmac = New-Object System.Security.Cryptography.HMACSHA256 | |
$hmac.Key = [System.Text.Encoding]::UTF8.GetBytes($secret_key) | |
$signBytes = $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($stringToSign)) | |
$signature = -join ($signBytes | ForEach-Object { "{0:x2}" -f $_ }) | |
return $signature | |
} | |
function Get-HttpData { | |
# We use some parameter sets to ensure that we get the variables we need during execution of a | |
# particular RequestType. We need the secret, access and api host each time for every request. | |
[CmdletBinding(DefaultParameterSetName = 'DeviceList')] | |
param ( | |
[Parameter(ParameterSetName = 'SpecificQuota', Mandatory = $true)] | |
[Parameter(ParameterSetName = 'GetAllQuota', Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DeviceList', Mandatory = $true)] | |
[ValidateSet("GetAllQuota", "DeviceList", "SpecificQuota")] | |
[string]$RequestType, | |
[Parameter(ParameterSetName = 'GetAllQuota', Mandatory = $true)] | |
[string]$DeviceSerialNo, | |
[Parameter(ParameterSetName = 'SpecificQuota', Mandatory = $true)] | |
[hashtable]$params, | |
[Parameter(ParameterSetName = 'SpecificQuota', Mandatory = $true)] | |
[Parameter(ParameterSetName = 'GetAllQuota', Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DeviceList', Mandatory = $true)] | |
[string]$EcoflowAccessKey, | |
[Parameter(ParameterSetName = 'SpecificQuota', Mandatory = $true)] | |
[Parameter(ParameterSetName = 'GetAllQuota', Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DeviceList', Mandatory = $true)] | |
[string]$EcoflowSecretKey, | |
[Parameter(ParameterSetName = 'SpecificQuota', Mandatory = $true)] | |
[Parameter(ParameterSetName = 'GetAllQuota', Mandatory = $true)] | |
[Parameter(ParameterSetName = 'DeviceList', Mandatory = $true)] | |
[string]$EcoflowApiHost | |
) | |
$nonce = Get-Nonce | |
$timestamp = Get-Timestamp | |
# base URL for querying the HTTP API | |
$baseUrl = "$EcoflowApiHost/iot-open/sign/device/" | |
# Concatenate the base URL with the additional required path based on the RequestType | |
$reqTarget = switch ($RequestType) { | |
'GetAllQuota' { "quota/all?sn=$DeviceSerialNo" } # GET but needs the serial including and signing! | |
'DeviceList' { 'list' } # GET but just needs the basic signing information. | |
'SpecificQuota' { 'quota' } # POST but needs the body parameters flattening and then signing. | |
Default {} | |
} | |
$url = $baseUrl + $reqTarget | |
# If there are URL parameters (like in a GetAllQuota request, these must be included in the signing key) | |
$urlParameters = $url -split "[?]" -like "*=*" | |
# Here we format the signing string - we're not signing it, just formatting it. Part of that means flattening (Merge-Hashtable) the params, if included. | |
# One or both of -params or -url_params will be $null, depending on the request type. | |
$signingString = Format-SigningString -params (Merge-Hashtable $params) -url_params $urlParameters -EcoflowAccessKey $EcoflowAccessKey -nonce $nonce -timestamp $timestamp | |
# Let's sign everything for the request. | |
$sign = Get-Signature -stringToSign $signingString -access_key $EcoflowAccessKey -secret_key $EcoflowSecretKey -nonce $nonce -timestamp $timestamp | |
$headers = @{ | |
"accessKey" = $EcoflowAccessKey | |
"nonce" = $nonce | |
"timestamp" = $timestamp | |
"sign" = $sign | |
} | |
# Send the request, depending on the type as either a POST or a GET. The headers are needed whatever type of request we're sending. | |
if ($RequestType -in @('DeviceList', 'GetAllQuota')) { | |
return Invoke-RestMethod -Uri $url -Method Get -Headers $headers | |
} | |
else { | |
# To send a POST request to this API, we must define the Content-Type header as...well...that ---> | |
return Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body ($params | ConvertTo-Json) -ContentType 'application/json;charset=UTF-8' | |
} | |
} | |
####################### | |
# | |
# Everything above just declares some functions and does nothing unless called. | |
# Now we can run some functions and get some data... | |
# Everything below are EXAMPLES. Don't just run the script expecting it to read your mind. | |
# | |
####################### | |
# There are THREE request types requiring different sets of data that are mandatory per their request type. | |
# DeviceList -RequestType <string> -EcoflowAccessKey <string> -EcoflowSecretKey <string> -EcoflowApiHost <string> [<CommonParameters>] | |
# SpecificQuota -params <hashtable> -RequestType <string> -EcoflowAccessKey <string> -EcoflowSecretKey <string> -EcoflowApiHost <string> [<CommonParameters>] | |
# GetAllQuota -RequestType <string> -DeviceSerialNo <string> -EcoflowAccessKey <string> -EcoflowSecretKey <string> -EcoflowApiHost <string> [<CommonParameters>] | |
# To see what parameters are needed for each of the request types (the same as the output above), use the following. | |
(Get-Command Get-HttpData).ParameterSets | | |
Select-Object -Property @{n = 'ParameterSetName'; e = { $_.name } }, | |
@{n = 'Parameters'; e = { $_.ToString() } } | |
# The $EF_API_HOST is provided in the Developer Portal @ https://developer-eu.ecoflow.com/us/document/generalInfo | |
$EF_API_HOST = "https://SOMETHING.ecoflow.com" # Could be https://api-e.ecoflow.com or https://api-a.ecoflow.com for example. | |
# These are the AccessKey and SecretKey which you must generate @ https://developer-eu.ecoflow.com/us/security | |
$EF_ACCESS_KEY = "the access key from the dev portal" | |
$EF_SECRET_KEY = "the secret key associated with the above access key" | |
# Now let's GET some STUFF - this is just for getting data, not setting it but all the same principles apply. | |
# Get a list of all the devices associated with this err, developer(?!), account. | |
$devices = Get-HttpData -RequestType DeviceList -EcoflowAccessKey $EF_ACCESS_KEY -EcoflowSecretKey $EF_SECRET_KEY -EcoflowApiHost $EF_API_HOST | |
# Use some fancier code to determine these from the $devices variable. We're just doing it by product type here. | |
$SHP_SERIAL = $devices.data | Where-Object { $_.productName -eq "Smart Home Panel" } | Select-Object -ExpandProperty sn | |
# We can still use product type here, where we know we have one result. | |
$WAVE_SERIAL = $devices.data | Where-Object { $_.productName -eq "WAVE 2" } | Select-Object -ExpandProperty sn | |
# I have two Delta Pro. So, if we were using the same approach as above (product type), that would be a mistake because we'd | |
# see two results (an array), which the function won't accept so we use the device name instead. | |
# This could be expanded to be both product type and device name of course but you get the idea. | |
$DP_SERIAL = $devices.data | Where-Object { $_.deviceName -eq "Delta Pro 1" } | Select-Object -ExpandProperty sn | |
# Get all the data available from the API for the Smart Home Panel device based on the serial number we derived from the device list. | |
$shp = Get-HttpData -RequestType 'GetAllQuota' -DeviceSerialNo $SHP_SERIAL -EcoflowAccessKey $EF_ACCESS_KEY -EcoflowSecretKey $EF_SECRET_KEY -EcoflowApiHost $EF_API_HOST | |
# Get all the data available from the API for the Wave 2 device. | |
$wave = Get-HttpData -RequestType 'GetAllQuota' -DeviceSerialNo $WAVE_SERIAL -EcoflowAccessKey $EF_ACCESS_KEY -EcoflowSecretKey $EF_SECRET_KEY -EcoflowApiHost $EF_API_HOST | |
# Get all the data available from the API for the Delta Pro device named "Delta Pro 1". | |
$delta_pro = Get-HttpData -RequestType 'GetAllQuota' -DeviceSerialNo $DP_SERIAL -EcoflowAccessKey $EF_ACCESS_KEY -EcoflowSecretKey $EF_SECRET_KEY -EcoflowApiHost $EF_API_HOST | |
# Get some more specific API data (using HTTP POST), like EPS mode status for a Smart Home Panel, not just EVERYTHING like we have above. | |
# Note that the serial number is included in the request body parameters and isn't specified as part of the request. | |
# This whole hashtable gets flattened and must be signed as though it's a string of URL parameters. | |
$params = @{ | |
sn = $SHP_SERIAL # We need to set this here because we aren't using the URL parameters. | |
params = @{ | |
quotas = @('epsModeInfo.eps', 'epsModeInfo.cmdSet', 'epsModeInfo.id') | |
} | |
} | |
$eps = Get-HttpData -RequestType SpecificQuota -params $params -EcoflowAccessKey $EF_ACCESS_KEY -EcoflowSecretKey $EF_SECRET_KEY -EcoflowApiHost $EF_API_HOST | |
if ($eps.data.'epsModeInfo.eps') {Write-Output "EPS mode is ENABLED on the device: $($params.sn)"} | |
else {Write-Output "EPS mode is DISABLED on the device: $($params.sn)"} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment