blob: 08776b3481b46c967ed80bbdb1dd978c4059ab8b [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package cellular
import (
"context"
"math"
"time"
"github.com/golang/protobuf/ptypes/empty"
"go.chromium.org/tast-tests/cros/remote/cellular/callbox/manager"
"go.chromium.org/tast-tests/cros/services/cros/cellular"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/testing"
"go.chromium.org/tast/core/testing/cellularconst"
"go.chromium.org/tast/core/testing/hwdep"
)
const (
// strengthErrorMargin is the allowable margin of error for signal strength percent.
strengthErrorMargin = 1.0
// rsrpErrorMargin is the allowable margin of error to use while waiting for RSRP to update to requested value in dBm.
rsrpErrorMargin = 3.0
)
// rxSignalPower fetches the current signal power received at the dut in dBm.
type rxSignalPower func(context.Context, cellular.RemoteCellularServiceClient) (float64, error)
// signalBarTest is a single test case in an signal bar test.
type signalBarTest struct {
maxPower float64
minPower float64
stepSize float64
stepCount int
fetchPower rxSignalPower
callboxOpts *manager.ConfigureCallboxRequestBody
}
func init() {
testing.AddTest(&testing.Test{
Func: AssertUISignalQuality,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Decrease downlink power on the callbox and verifies that the signal strength reflected on ui proportionally",
Contacts: []string{"chromeos-cellular-team@google.com", "srikanthkumar@google.com"},
BugComponent: "b:167157", // ChromeOS > Platform > Connectivity > Cellular
Attr: []string{"group:cellular", "cellular_callbox", "cellular_cmw_callbox", "cellular_e2e"},
ServiceDeps: []string{"tast.cros.cellular.RemoteCellularService"},
SoftwareDeps: []string{"chrome"},
Fixture: "callboxManagedFixture",
// FM101 only reports RSSI to MM (b/274882743), this test relies on RSRP values.
HardwareDeps: hwdep.D(hwdep.SkipOnCellularModemType(cellularconst.ModemTypeFM101)),
Timeout: 15 * time.Minute,
Params: []testing.Param{
{
Name: "lte",
Val: signalBarTest{
maxPower: -88,
minPower: -128,
// decrease by ~20%, smaller step sizes may not be accurately resolvable if they're not at least 2*rsrpErrorMargin
stepSize: -8,
stepCount: 3,
fetchPower: fetchLteRSRPValue,
callboxOpts: &manager.ConfigureCallboxRequestBody{
Hardware: manager.CallboxHardwareCMW,
CellularType: manager.CellularTechnologyLTE,
Parameters: []manager.CellConfiguration{
manager.NewLteCellConfiguration(),
},
},
},
},
},
})
}
func AssertUISignalQuality(ctx context.Context, s *testing.State) {
tc := s.Param().(signalBarTest)
tf := s.FixtValue().(*manager.TestFixture)
dutConn := s.DUT().Conn()
if err := tf.ConnectToCallbox(ctx, dutConn, tc.callboxOpts); err != nil {
s.Fatal("Failed to initialize cellular connection: ", err)
}
// get initial power set on the callbox
// NOTE: callbox power is in RS EPRE while lte is reported by the modem in RSRP, the two should be nearly identical in this scenario
rxResp, err := tf.CallboxManagerClient.FetchRxPower(ctx, &manager.FetchRxPowerRequestBody{})
if err != nil {
s.Fatal("Failed to fetch callbox downlink power: ", err)
}
pReq := rxResp.Power
// wait for received power at the DUT to update
pMeas, err := waitForSignalPower(ctx, tc, tf.RemoteCellularClient, pReq)
if err != nil {
s.Fatal("Failed to wait for requested power: ", err)
}
serviceResp, err := tf.RemoteCellularClient.QueryService(ctx, &empty.Empty{})
if err != nil {
s.Fatal("Failed to get cellular service properties: ", err)
}
strength := serviceResp.Strength
calibrationOffset := pReq - pMeas
s.Logf("Starting power: %f dBm, strength: %d %%, incrementing by %f dBm", pMeas, strength, tc.stepSize)
for i := 0; i < tc.stepCount; i++ {
pReq += tc.stepSize
req := &manager.ConfigureRxPowerRequestBody{Power: manager.NewRxPower(pReq + calibrationOffset)}
if err := tf.CallboxManagerClient.ConfigureRxPower(ctx, req); err != nil {
s.Fatal("Failed to change callbox uplink power: ", err)
}
powerOld := pMeas
pMeas, err = waitForSignalPower(ctx, tc, tf.RemoteCellularClient, pReq)
if err != nil {
s.Fatal("Failed to wait for requested power: ", err)
}
calibrationOffset = pReq + calibrationOffset - pMeas
// calculate expected decrease in signal strength
sDiffExpected := 100 * (powerOld - pMeas) / (tc.maxPower - tc.minPower)
serviceResp, err := tf.RemoteCellularClient.QueryService(ctx, &empty.Empty{})
if err != nil {
s.Fatal("Failed to get cellular service properties: ", err)
}
sDiff := float64(strength - serviceResp.Strength)
strength = serviceResp.Strength
var expectedCount int32
expectedCount = barCount(ctx, float64(strength))
s.Logf("Power: %f, strength: %d %%, offset: %f dBm", pMeas, strength, calibrationOffset)
resultedCount, err := getShillBasedBarCnt(ctx, tf.RemoteCellularClient)
if err != nil {
s.Log("Failed to get shill based cellular signal bar count: ", err)
}
s.Log("Getting signal bar count from UI")
signalResp, err := tf.RemoteCellularClient.QuerySignalBars(ctx, &empty.Empty{})
if err != nil {
s.Fatal("Failed to get cellular signal bar count: ", err)
}
uiBarCnt := int32(signalResp.Count)
s.Logf("Signal bar count want: %d, got shill based count: %d, ui based count: %d", expectedCount, resultedCount, uiBarCnt)
if uiBarCnt <= 0 {
s.Fatalf("Cellular network not having any signal: %d", uiBarCnt)
}
if expectedCount != uiBarCnt || expectedCount != resultedCount {
s.Fatal("Failed to match signal bar on ui")
}
if math.Abs(sDiffExpected-sDiff) > strengthErrorMargin {
s.Fatalf("Failed to change signal strength, want: %f+/-%f%%, got: %f%%", sDiffExpected, strengthErrorMargin, sDiff)
}
}
}
// getShillBasedBarCnt fetches the RSRP for the current LTE signal strngth and maps expected signal bars.
func getShillBasedBarCnt(ctx context.Context, client cellular.RemoteCellularServiceClient) (int32, error) {
resp, err := client.QueryLTESignal(ctx, &empty.Empty{})
if err != nil {
return 0, err
}
var min, max, clampedSignalQuality, signalPercent float64 = -128, -88, 0, 0
clampedSignalQuality = math.Min(math.Max(resp.Rsrp, min), max)
signalPercent = (clampedSignalQuality - min) * 100 / (max - min)
testing.ContextLogf(ctx, "clampedSignalQuality: %f,signalPercent: %f , max: %f, min: %f, Rsrp: %f", clampedSignalQuality, signalPercent, max, min, resp.Rsrp)
return barCount(ctx, signalPercent), nil
}
// barCount maps shill signal strength with number of signal bars on UI.
func barCount(ctx context.Context, strength float64) int32 {
// Chrome OS UI uses signal quality values set by this method to draw
// network icons. UI code maps |quality| to number of bars as follows:
// [1-25] 1 bar, [26-50] 2 bars, [51-75] 3 bars and [76-100] 4 bars.
// -128->-88 rsrp scales to UI quality of 0->100, used for 4G
// -115->-89 rscp scales to UI quality of 0->100, used for 3G
// -105->-83 rssi scales to UI quality of 0->100, used for other tech
return (int32(strength) + 24) / 25
}
// fetchLteRSRPValue fetches the RSRP for the current LTE signal.
func fetchLteRSRPValue(ctx context.Context, client cellular.RemoteCellularServiceClient) (float64, error) {
resp, err := client.QueryLTESignal(ctx, &empty.Empty{})
if err != nil {
return 0, err
}
return resp.Rsrp, nil
}
// waitForSignalPower waits for the received power at the DUT to update within some margin of the requested value.
func waitForSignalPower(ctx context.Context, test signalBarTest, client cellular.RemoteCellularServiceClient, pTarget float64) (float64, error) {
var pMeas float64
var err error
if err = testing.Poll(ctx, func(ctx context.Context) error {
if pMeas, err = test.fetchPower(ctx, client); err != nil {
return errors.Wrap(err, "failed to get DUT Rx signal properties")
}
if math.Abs(pTarget-pMeas) > rsrpErrorMargin {
return errors.Errorf("waiting for DUT Rx power to reach %f, got %f", pTarget, pMeas)
}
return nil
}, &testing.PollOptions{Interval: time.Second}); err != nil {
return 0, errors.Wrap(err, "failed to wait for uplink power to reach the requested value")
}
return pMeas, nil
}