Skip to content

Instantly share code, notes, and snippets.

@lwsrbrts
Created September 1, 2024 19:42
Show Gist options
  • Save lwsrbrts/cc02314a7ff203897061da8c7dffbab0 to your computer and use it in GitHub Desktop.
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.
# 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&timestamp=$timestamp"
}
elseif ($url_params) {
# If no parameters were provided but URL parameters exist, use them
$signableString = "$url_params&accessKey=$EcoflowAccessKey&nonce=$nonce&timestamp=$timestamp"
}
else {
# If neither parameters nor URL parameters are provided, build a signable string with just the basic keys
$signableString = "accessKey=$EcoflowAccessKey&nonce=$nonce&timestamp=$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