/
methods.go
1041 lines (932 loc) · 35.6 KB
/
methods.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package servo
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"chromiumos/tast/common/xmlrpc"
"chromiumos/tast/errors"
"chromiumos/tast/testing"
)
// A StringControl contains the name of a gettable/settable Control which takes a string value.
type StringControl string
// These are the Servo controls which can be get/set with a string value.
const (
ActiveChgPort StringControl = "active_chg_port"
ActiveDUTController StringControl = "active_dut_controller"
ArbKey StringControl = "arb_key"
ArbKeyConfig StringControl = "arb_key_config"
DUTVoltageMV StringControl = "dut_voltage_mv"
DownloadImageToUSBDev StringControl = "download_image_to_usb_dev"
ECActiveCopy StringControl = "ec_active_copy"
FWWPState StringControl = "fw_wp_state"
ImageUSBKeyDev StringControl = "image_usbkey_dev"
ImageUSBKeyDirection StringControl = "image_usbkey_direction"
ImageUSBKeyPwr StringControl = "image_usbkey_pwr"
LidOpen StringControl = "lid_open"
PowerState StringControl = "power_state"
Type StringControl = "servo_type"
UARTCmd StringControl = "servo_v4_uart_cmd"
UARTCmdV4p1 StringControl = "servo_v4p1_uart_cmd"
Watchdog StringControl = "watchdog"
WatchdogAdd StringControl = "watchdog_add"
WatchdogRemove StringControl = "watchdog_remove"
PDCommunication StringControl = "servo_pd_comm"
// DUTConnectionType was previously known as V4Type ("servo_v4_type")
DUTConnectionType StringControl = "root.dut_connection_type"
// PDRole was previously known as V4Role ("servo_v4_role")
PDRole StringControl = "servo_pd_role"
)
// A BoolControl contains the name of a gettable/settable Control which takes a boolean value.
type BoolControl string
// These are the Servo controls which can be get/set with a boolean value.
const (
ChargerAttached BoolControl = "charger_attached"
)
// An IntControl contains the name of a gettable/settable Control which takes an integer value.
type IntControl string
// These are the Servo controls which can be get/set with an integer value.
const (
BatteryChargeMAH IntControl = "battery_charge_mah"
BatteryCurrentMA IntControl = "ppvar_vbat_ma"
BatteryFullChargeMAH IntControl = "battery_full_charge_mah"
BatteryVoltageMV IntControl = "ppvar_vbat_mv"
VolumeDownHold IntControl = "volume_down_hold" // Integer represents a number of milliseconds.
VolumeUpHold IntControl = "volume_up_hold" // Integer represents a number of milliseconds.
VolumeUpDownHold IntControl = "volume_up_down_hold" // Integer represents a number of milliseconds.
)
// A FloatControl contains the name of a gettable/settable Control which takes a floating-point value.
type FloatControl string
// These are the Servo controls with floating-point values.
const (
BatteryTemperatureCelsius FloatControl = "battery_tempc"
VBusVoltage FloatControl = "vbus_voltage"
)
// A OnOffControl accepts either "on" or "off" as a value.
type OnOffControl string
// These controls accept only "on" and "off" as values.
const (
CCDKeepaliveEn OnOffControl = "ccd_keepalive_en"
CCDState OnOffControl = "ccd_state"
DTSMode OnOffControl = "servo_dts_mode"
RecMode OnOffControl = "rec_mode"
USBKeyboard OnOffControl = "init_usb_keyboard"
I2CMuxEn OnOffControl = "i2c_mux_en"
ColdReset OnOffControl = "cold_reset"
)
// An OnOffValue is a string value that would be accepted by an OnOffControl.
type OnOffValue string
// These are the values used by OnOff controls.
const (
Off OnOffValue = "off"
On OnOffValue = "on"
)
// A KeypressControl is a special type of Control which can take either a numerical value or a KeypressDuration.
type KeypressControl StringControl
// These are the Servo controls which can be set with either a numerical value or a KeypressDuration.
const (
CtrlD KeypressControl = "ctrl_d"
CtrlS KeypressControl = "ctrl_s"
CtrlU KeypressControl = "ctrl_u"
CtrlEnter KeypressControl = "ctrl_enter"
Ctrl KeypressControl = "ctrl_key"
Enter KeypressControl = "enter_key"
Refresh KeypressControl = "refresh_key"
CtrlRefresh KeypressControl = "ctrl_refresh_key"
ImaginaryKey KeypressControl = "imaginary_key"
SysRQX KeypressControl = "sysrq_x"
PowerKey KeypressControl = "power_key"
Pwrbutton KeypressControl = "pwr_button"
USBEnter KeypressControl = "usb_keyboard_enter_key"
)
// A KeypressDuration is a string accepted by a KeypressControl.
type KeypressDuration string
// These are string values that can be passed to a KeypressControl.
const (
DurTab KeypressDuration = "tab"
DurPress KeypressDuration = "press"
DurShortPress KeypressDuration = "short_press"
DurLongPress KeypressDuration = "long_press"
)
// Dur returns a custom duration that can be passed to KeypressWithDuration
func Dur(dur time.Duration) KeypressDuration {
return KeypressDuration(fmt.Sprintf("%f", dur.Seconds()))
}
// A FWWPStateValue is a string accepted by the FWWPState control.
type FWWPStateValue string
// These are the string values that can be passed to the FWWPState control.
const (
FWWPStateOff FWWPStateValue = "force_off"
FWWPStateOn FWWPStateValue = "force_on"
)
// A LidOpenValue is a string accepted by the LidOpen control.
type LidOpenValue string
// These are the string values that can be passed to the LidOpen control.
const (
LidOpenYes LidOpenValue = "yes"
LidOpenNo LidOpenValue = "no"
)
// A PowerStateValue is a string accepted by the PowerState control.
type PowerStateValue string
// These are the string values that can be passed to the PowerState control.
const (
PowerStateCR50Reset PowerStateValue = "cr50_reset"
PowerStateOff PowerStateValue = "off"
PowerStateOn PowerStateValue = "on"
PowerStateRec PowerStateValue = "rec"
PowerStateRecForceMRC PowerStateValue = "rec_force_mrc"
PowerStateReset PowerStateValue = "reset"
PowerStateWarmReset PowerStateValue = "warm_reset"
)
// A USBMuxState indicates whether the servo's USB mux is on, and if so, which direction it is powering.
type USBMuxState string
// These are the possible states of the USB mux.
const (
USBMuxOff USBMuxState = "off"
USBMuxDUT USBMuxState = "dut_sees_usbkey"
USBMuxHost USBMuxState = "servo_sees_usbkey"
)
// A PDRoleValue is a string that would be accepted by the PDRole control.
type PDRoleValue string
// These are the string values that can be passed to PDRole.
const (
PDRoleSnk PDRoleValue = "snk"
PDRoleSrc PDRoleValue = "src"
// PDRoleNA indicates a non-v4 servo.
PDRoleNA PDRoleValue = "n/a"
)
// A DUTConnTypeValue is a string that would be returned by the DUTConnectionType control.
type DUTConnTypeValue string
// These are the string values that can be returned by DUTConnectionType
const (
DUTConnTypeA DUTConnTypeValue = "type-a"
DUTConnTypeC DUTConnTypeValue = "type-c"
// DUTConnTypeNA indicates a non-v4 servo.
DUTConnTypeNA DUTConnTypeValue = "n/a"
)
// A WatchdogValue is a string that would be accepted by WatchdogAdd & WatchdogRemove control.
type WatchdogValue string
// These are the string watchdog type values that can be passed to WatchdogAdd & WatchdogRemove.
const (
WatchdogCCD WatchdogValue = "ccd"
WatchdogMain WatchdogValue = "main"
)
// DUTController is the active controller on a dual mode servo.
type DUTController string
// Parameters that can be passed to SetActiveDUTController().
const (
DUTControllerC2D2 DUTController = "c2d2"
DUTControllerCCD DUTController = "ccd_cr50"
DUTControllerCCDGSC DUTController = "ccd_gsc"
DUTControllerServoMicro DUTController = "servo_micro"
)
// ServoKeypressDelay comes from hdctools/servo/drv/keyboard_handlers.py.
// It is the minimum time interval between 'press' and 'release' keyboard events.
const ServoKeypressDelay = 100 * time.Millisecond
// HasControl determines whether the Servo being used supports the given control.
func (s *Servo) HasControl(ctx context.Context, ctrl string) (bool, error) {
err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("doc", ctrl))
// If the control exists, doc() should return with no issue.
if err == nil {
return true, nil
}
// If the control doesn't exist, then doc() should return a fault.
if _, isFault := err.(xmlrpc.FaultError); isFault {
return false, nil
}
// A non-fault error indicates that something went wrong.
return false, err
}
// Echo calls the Servo echo method.
func (s *Servo) Echo(ctx context.Context, message string) (string, error) {
var val string
err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("echo", message), &val)
return val, err
}
// PowerNormalPress calls the Servo power_normal_press method.
func (s *Servo) PowerNormalPress(ctx context.Context) (bool, error) {
var val bool
err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("power_normal_press"), &val)
return val, err
}
// SetActChgPort enables a charge port on fluffy.
func (s *Servo) SetActChgPort(ctx context.Context, port string) error {
return s.SetString(ctx, ActiveChgPort, port)
}
// DUTVoltageMV reads the voltage present on the DUT port on fluffy.
func (s *Servo) DUTVoltageMV(ctx context.Context) (string, error) {
return s.GetString(ctx, DUTVoltageMV)
}
// GetServoVersion gets the version of Servo being used.
func (s *Servo) GetServoVersion(ctx context.Context) (string, error) {
if s.version != "" {
return s.version, nil
}
err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("get_version"), &s.version)
return s.version, err
}
// IsServoV4 determines whether the Servo being used is v4.
func (s *Servo) IsServoV4(ctx context.Context) (bool, error) {
version, err := s.GetServoVersion(ctx)
if err != nil {
return false, errors.Wrap(err, "determining servo version")
}
return strings.HasPrefix(version, "servo_v4"), nil
}
// GetDUTConnectionType gets the type of connection between the Servo and the DUT.
// If Servo is not V4, returns DUTConnTypeNA.
func (s *Servo) GetDUTConnectionType(ctx context.Context) (DUTConnTypeValue, error) {
if s.dutConnType != "" {
return s.dutConnType, nil
}
if isV4, err := s.IsServoV4(ctx); err != nil {
return "", errors.Wrap(err, "determining whether servo is v4")
} else if !isV4 {
s.dutConnType = DUTConnTypeNA
return s.dutConnType, nil
}
connType, err := s.GetString(ctx, DUTConnectionType)
if err != nil {
return "", err
}
s.dutConnType = DUTConnTypeValue(connType)
return s.dutConnType, nil
}
// GetString returns the value of a specified control.
func (s *Servo) GetString(ctx context.Context, control StringControl) (string, error) {
var value string
if err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("get", string(control)), &value); err != nil {
return "", errors.Wrapf(err, "getting value for servo control %q", control)
}
return value, nil
}
// GetStringTimeout returns the value of a specified control with a custom timeout.
func (s *Servo) GetStringTimeout(ctx context.Context, control StringControl, timeout time.Duration) (string, error) {
var value string
if err := s.xmlrpc.Run(
ctx,
xmlrpc.NewCallTimeout(
"get",
timeout,
string(control)),
&value); err != nil {
return "", errors.Wrapf(err, "getting value for servo control %q", control)
}
return value, nil
}
// GetServoSerials returns a map of servo serial numbers. Interesting map keys are "ccd", "main", "servo_micro", but there are others also.
func (s *Servo) GetServoSerials(ctx context.Context) (map[string]string, error) {
value := make(map[string]string)
if err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("get_servo_serials"), &value); err != nil {
return map[string]string{}, errors.Wrap(err, "getting servo serials")
}
return value, nil
}
// GetCCDSerial returns the serial number of the CCD interface.
func (s *Servo) GetCCDSerial(ctx context.Context) (string, error) {
value, err := s.GetServoSerials(ctx)
if err != nil {
return "", err
}
ccdSerial, ok := value["ccd"]
if ok {
return ccdSerial, nil
}
servoType, err := s.GetServoType(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to get servo type")
}
// SuzyQ reports as ccd_cr50, and doesn't have an interface named ccd.
if servoType == "ccd_cr50" {
ccdSerial, ok := value["main"]
if ok {
return ccdSerial, nil
}
}
return "", errors.Errorf("no ccd serial in %q", value)
}
// GetBool returns the boolean value of a specified control.
func (s *Servo) GetBool(ctx context.Context, control BoolControl) (bool, error) {
var value bool
if err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("get", string(control)), &value); err != nil {
return false, errors.Wrapf(err, "getting value for servo control %q", control)
}
return value, nil
}
// GetChargerAttached returns the boolean value to indicate whether charger is attached.
func (s *Servo) GetChargerAttached(ctx context.Context) (bool, error) {
return s.GetBool(ctx, ChargerAttached)
}
// LidOpenState checks whether DUT's lid is open or closed, and returns yes/no.
func (s *Servo) LidOpenState(ctx context.Context) (string, error) {
return s.GetString(ctx, LidOpen)
}
// parseUint extracts a hex number from `value` at `*index+1` that is exactly `bits` in length.
// `bits` must be power of 2.
// `*index` will be moved to the end of the extracted runes.
func parseUint(value []rune, index *int, bits int) (rune, error) {
chars := bits / 4
endIndex := *index + chars
if endIndex >= len(value) {
return 0, errors.Errorf("unparsable escape sequence `\\%s`", string(value[*index:]))
}
char, err := strconv.ParseUint(string(value[*index+1:endIndex+1]), 16, bits)
if err != nil {
return 0, errors.Wrapf(err, "unparsable escape sequence `\\%s`", string(value[*index:endIndex+1]))
}
*index += chars
return rune(char), nil
}
// parseQuotedStringInternal returns a new string with the quotes and escaped chars from `value` removed, moves `*index` to the index of the closing quote rune.
func parseQuotedStringInternal(value []rune, index *int) (string, error) {
if *index >= len(value) {
return "", errors.Errorf("unexpected end of string at %d in %s", *index, string(value))
}
// The first char should always be a ' or "
quoteChar := value[*index]
if quoteChar != '\'' && quoteChar != '"' {
return "", errors.Errorf("unexpected string char %c at index %d in %s", quoteChar, *index, string(value))
}
(*index)++
var current strings.Builder
for ; *index < len(value); (*index)++ {
c := value[*index]
if c == quoteChar {
break
} else if c == '\\' {
(*index)++
if *index >= len(value) {
return "", errors.New("unparsable escape sequence \\")
}
switch value[*index] {
case '"', '\'', '\\':
current.WriteRune(value[*index])
case 'r':
current.WriteRune('\r')
case 'n':
current.WriteRune('\n')
case 't':
current.WriteRune('\t')
case 'x':
r, err := parseUint(value, index, 8)
if err != nil {
return "", err
}
current.WriteRune(r)
case 'u':
r, err := parseUint(value, index, 16)
if err != nil {
return "", err
}
current.WriteRune(r)
case 'U':
r, err := parseUint(value, index, 32)
if err != nil {
return "", err
}
current.WriteRune(r)
default:
return "", errors.Errorf("unexpected escape sequence \\%c at index %d in %s", value[*index], *index, string(value))
}
} else {
current.WriteRune(c)
}
}
return current.String(), nil
}
// parseStringListInternal parses `value` as a possibly nested list of strings, each quoted and separated by commas. Moves `*index` to the index of the closing ] rune.
func parseStringListInternal(value []rune, index *int) ([]interface{}, error) {
var result []interface{}
if *index >= len(value) {
return nil, errors.Errorf("unexpected end of string at %d in %s", *index, string(value))
}
// The first char should always be a [ or (, as it might be a list or a tuple.
if value[*index] != '[' && value[*index] != '(' {
return nil, errors.Errorf("unexpected list char %c at index %d in %s", value[*index], *index, string(value))
}
(*index)++
for ; *index < len(value); (*index)++ {
c := value[*index]
switch c {
case '[', '(':
sublist, err := parseStringListInternal(value, index)
if err != nil {
return nil, err
}
result = append(result, sublist)
case '\'', '"':
substr, err := parseQuotedStringInternal(value, index)
if err != nil {
return nil, err
}
result = append(result, substr)
case ',', ' ':
// Ignore this char
case ']', ')':
return result, nil
default:
return nil, errors.Errorf("unexpected list char %c at index %d in %s", c, *index, string(value))
}
}
return nil, errors.Errorf("unexpected end of string at %d in %s", *index, string(value))
}
// ParseStringList parses `value` as a possibly nested list of strings, each quoted and separated by commas.
func ParseStringList(value string) ([]interface{}, error) {
index := 0
return parseStringListInternal([]rune(value), &index)
}
// ParseQuotedString returns a new string with the quotes and escaped chars from `value` removed.
func ParseQuotedString(value string) (string, error) {
index := 0
return parseQuotedStringInternal([]rune(value), &index)
}
// GetStringList parses the value of a control as an encoded list
func (s *Servo) GetStringList(ctx context.Context, control StringControl) ([]interface{}, error) {
v, err := s.GetString(ctx, control)
if err != nil {
return nil, err
}
return ParseStringList(v)
}
// ConvertToStringArrayArray takes a stringList from GetStringList and converts it to [][]string
func ConvertToStringArrayArray(ctx context.Context, stringList []interface{}) ([][]string, error) {
var ret [][]string
for i, x := range stringList {
switch t := x.(type) {
case string:
ret = append(ret, []string{t})
case []string:
ret = append(ret, t)
case []interface{}:
var strings []string
for _, y := range t {
strings = append(strings, fmt.Sprint(y))
}
ret = append(ret, strings)
default:
return nil, errors.Errorf("unexpected type %T at index %d", x, i)
}
}
return ret, nil
}
// GetQuotedString parses the value of a control as a quoted string
func (s *Servo) GetQuotedString(ctx context.Context, control StringControl) (string, error) {
v, err := s.GetString(ctx, control)
if err != nil {
return "", err
}
return ParseQuotedString(v)
}
// SetString sets a Servo control to a string value.
func (s *Servo) SetString(ctx context.Context, control StringControl, value string) error {
// Servo's Set method returns a bool stating whether the call succeeded or not.
// This is redundant, because a failed call will return an error anyway.
// So, we can skip unpacking the output.
if err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("set", string(control), value)); err != nil {
return errors.Wrapf(err, "setting servo control %q to %q", control, value)
}
return nil
}
// SetStringTimeout sets a Servo control to a string value.
func (s *Servo) SetStringTimeout(ctx context.Context, control StringControl, value string, timeout time.Duration) error {
// Servo's Set method returns a bool stating whether the call succeeded or not.
// This is redundant, because a failed call will return an error anyway.
// So, we can skip unpacking the output.
if err := s.xmlrpc.Run(ctx, xmlrpc.NewCallTimeout("set", timeout, string(control), value)); err != nil {
return errors.Wrapf(err, "setting servo control %q to %q", control, value)
}
return nil
}
// SetStringList sets a Servo control to a list of string values.
func (s *Servo) SetStringList(ctx context.Context, control StringControl, values []string) error {
value := "["
for i, part := range values {
if i > 0 {
value += ", "
}
// Escape \ and '
part = strings.ReplaceAll(part, `\`, `\\`)
part = strings.ReplaceAll(part, `'`, `\'`)
// Surround by '
value += "'" + part + "'"
}
value += "]"
return s.SetString(ctx, control, value)
}
// SetInt sets a Servo control to an integer value.
func (s *Servo) SetInt(ctx context.Context, control IntControl, value int) error {
if err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("set", string(control), value)); err != nil {
return errors.Wrapf(err, "setting servo control %q to %d", control, value)
}
return nil
}
// GetInt returns the integer value of a specified control.
func (s *Servo) GetInt(ctx context.Context, control IntControl) (int, error) {
var value int
if err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("get", string(control)), &value); err != nil {
return 0, errors.Wrapf(err, "getting value for servo control %q", control)
}
return value, nil
}
// GetBatteryChargeMAH returns the battery's charge in mAh.
func (s *Servo) GetBatteryChargeMAH(ctx context.Context) (int, error) {
return s.GetInt(ctx, BatteryChargeMAH)
}
// GetBatteryFullChargeMAH returns the battery's last full charge in mAh.
func (s *Servo) GetBatteryFullChargeMAH(ctx context.Context) (int, error) {
return s.GetInt(ctx, BatteryFullChargeMAH)
}
// GetFloat returns the floating-point value of a specified control.
func (s *Servo) GetFloat(ctx context.Context, control FloatControl) (float64, error) {
var value float64
if err := s.xmlrpc.Run(ctx, xmlrpc.NewCall("get", string(control)), &value); err != nil {
return 0, errors.Wrapf(err, "getting value for servo control %q", control)
}
return value, nil
}
// SetStringAndCheck sets a control to a specified value, and then verifies that it was set correctly.
// Unless you have a good reason to check, just use SetString.
func (s *Servo) SetStringAndCheck(ctx context.Context, control StringControl, value string) error {
if err := s.SetString(ctx, control, value); err != nil {
return err
}
if checkedValue, err := s.GetString(ctx, control); err != nil {
return err
} else if checkedValue != value {
return errors.Errorf("after attempting to set %s to %s, checked value was %s", control, value, checkedValue)
}
return nil
}
// KeypressWithDuration sets a KeypressControl to a KeypressDuration value.
func (s *Servo) KeypressWithDuration(ctx context.Context, control KeypressControl, value KeypressDuration) error {
// The default duration with SetString is 10s.
timeout := 10 * time.Second
// If duration is string (e.g. press, tab, long_press) don't parse as duration string.
if match := regexp.MustCompile(`\d+(.|\.\d+)?`).FindStringSubmatch(string(value)); match != nil {
out, err := time.ParseDuration(fmt.Sprintf("%ss", string(value)))
if err != nil {
return errors.Wrap(err, "parsing duration")
}
timeout = out + 1*time.Second
}
// Set timeout of keypress to make it doesn't timeout before keypress is complete.
return s.SetStringTimeout(ctx, StringControl(control), string(value), timeout)
}
// PressKey presses an arbitrary key for a KeypressDuration.
// This either uses EC keyboard emulation or the servo's emulated USB keyboard depending on the setting of USBKeyboard.
func (s *Servo) PressKey(ctx context.Context, key string, value KeypressDuration) error {
// The default duration with SetString is 10s.
timeout := 10 * time.Second
// If duration is string (e.g. press, tab, long_press) don't parse as duration string.
if match := regexp.MustCompile(`\d+(.|\.\d+)?`).FindStringSubmatch(string(value)); match != nil {
out, err := time.ParseDuration(fmt.Sprintf("%ss", string(value)))
if err != nil {
return errors.Wrap(err, "parsing duration")
}
timeout = out + 1*time.Second
}
if err := s.SetString(ctx, ArbKeyConfig, key); err != nil {
return errors.Wrapf(err, "failed to press key %q", key)
}
// Set timeout of keypress to make it doesn't timeout before keypress is complete.
return s.SetStringTimeout(ctx, ArbKey, string(value), timeout)
}
// GetUSBMuxState determines whether the servo USB mux is on, and if so, which direction it is pointed.
func (s *Servo) GetUSBMuxState(ctx context.Context) (USBMuxState, error) {
pwr, err := s.GetString(ctx, ImageUSBKeyPwr)
if err != nil {
return "", err
}
if pwr == string(USBMuxOff) {
return USBMuxOff, nil
}
direction, err := s.GetString(ctx, ImageUSBKeyDirection)
if err != nil {
return "", err
}
if direction != string(USBMuxDUT) && direction != string(USBMuxHost) {
return "", errors.Errorf("%q has an unknown value: %q", ImageUSBKeyDirection, direction)
}
return USBMuxState(direction), nil
}
// SetUSBMuxState switches the servo's USB mux to the specified power/direction state.
func (s *Servo) SetUSBMuxState(ctx context.Context, value USBMuxState) error {
if value == USBMuxOff {
return s.SetString(ctx, ImageUSBKeyPwr, string(value))
}
// Servod ensures the following:
// * The port is power cycled if it is changing directions
// * The port ends up in a powered state after this call
// * If facing the host side, the call only returns once a USB device is detected, or after a generous timeout (10s)
if err := s.SetStringTimeout(ctx, ImageUSBKeyDirection, string(value), 90*time.Second); err != nil {
return err
}
return nil
}
// SetPowerState sets the PowerState control.
// Because this is particularly disruptive, it is always logged.
// It can be slow, because some boards are configured to hold down the power button for 12 seconds.
func (s *Servo) SetPowerState(ctx context.Context, value PowerStateValue) error {
testing.ContextLogf(ctx, "Setting %q to %q", PowerState, value)
// Power states that reboot the EC can make servod exit or fail if the CCD watchdog is enabled.
switch value {
case PowerStateReset, PowerStateRec, PowerStateRecForceMRC, PowerStateCR50Reset, PowerStateWarmReset:
if err := s.WatchdogRemove(ctx, WatchdogCCD); err != nil {
return errors.Wrap(err, "remove ccd watchdog")
}
default:
// Do nothing
}
return s.SetStringTimeout(ctx, PowerState, string(value), 30*time.Second)
}
// SetFWWPState sets the FWWPState control.
// Because this is particularly disruptive, it is always logged.
func (s *Servo) SetFWWPState(ctx context.Context, value FWWPStateValue) error {
testing.ContextLogf(ctx, "Setting %q to %q", FWWPState, value)
return s.SetString(ctx, FWWPState, string(value))
}
// GetPDRole returns the servo's current PDRole (SNK or SRC), or PDRoleNA if Servo is not V4.
func (s *Servo) GetPDRole(ctx context.Context) (PDRoleValue, error) {
isV4, err := s.IsServoV4(ctx)
if err != nil {
return "", errors.Wrap(err, "determining whether servo is v4")
}
if !isV4 {
return PDRoleNA, nil
}
role, err := s.GetString(ctx, PDRole)
if err != nil {
return "", err
}
return PDRoleValue(role), nil
}
// SetPDRole sets the PDRole control for a servo v4.
// On a Servo version other than v4, this does nothing.
func (s *Servo) SetPDRole(ctx context.Context, newRole PDRoleValue) error {
// Determine the current PD role
currentRole, err := s.GetPDRole(ctx)
if err != nil {
return errors.Wrap(err, "getting current PD role")
}
// Save the initial PD role so we can restore it during servo.Close()
if s.initialPDRole == "" {
testing.ContextLogf(ctx, "Saving initial PDRole %q for later", currentRole)
s.initialPDRole = currentRole
}
// If not using a servo V4, then we can't set the PD Role
if currentRole == PDRoleNA {
testing.ContextLogf(ctx, "Skipping setting %q to %q on non-v4 servo", PDRole, newRole)
return nil
}
// If the current value is already the intended value,
// then don't bother resetting.
if currentRole == newRole {
testing.ContextLogf(ctx, "Skipping setting %q to %q, because that is the current value", PDRole, newRole)
return nil
}
return s.SetString(ctx, PDRole, string(newRole))
}
// SetOnOff sets an OnOffControl setting to the specified value.
func (s *Servo) SetOnOff(ctx context.Context, ctrl OnOffControl, value OnOffValue) error {
return s.SetString(ctx, StringControl(ctrl), string(value))
}
// ToggleOffOn turns a switch off and on again.
func (s *Servo) ToggleOffOn(ctx context.Context, ctrl OnOffControl) error {
if err := s.SetString(ctx, StringControl(ctrl), string(Off)); err != nil {
return err
}
if err := testing.Sleep(ctx, ServoKeypressDelay); err != nil {
return err
}
if err := s.SetString(ctx, StringControl(ctrl), string(On)); err != nil {
return err
}
return nil
}
// ToggleOnOff turns a switch on and off again.
func (s *Servo) ToggleOnOff(ctx context.Context, ctrl OnOffControl) error {
if err := s.SetString(ctx, StringControl(ctrl), string(On)); err != nil {
return err
}
if err := testing.Sleep(ctx, ServoKeypressDelay); err != nil {
return err
}
if err := s.SetString(ctx, StringControl(ctrl), string(Off)); err != nil {
return err
}
return nil
}
// GetOnOff gets an OnOffControl as a bool.
func (s *Servo) GetOnOff(ctx context.Context, ctrl OnOffControl) (bool, error) {
str, err := s.GetString(ctx, StringControl(ctrl))
if err != nil {
return false, err
}
switch str {
case string(On):
return true, nil
case string(Off):
return false, nil
}
return false, errors.Errorf("cannot convert %q to boolean", str)
}
// WatchdogAdd adds the specified watchdog to the servod instance.
func (s *Servo) WatchdogAdd(ctx context.Context, val WatchdogValue) error {
return s.SetString(ctx, WatchdogAdd, string(val))
}
// WatchdogRemove removes the specified watchdog from the servod instance.
// Servo.Close() will restore the watchdog.
func (s *Servo) WatchdogRemove(ctx context.Context, val WatchdogValue) error {
for _, wd := range s.removedWatchdogs {
if wd == val {
return nil
}
}
if val == WatchdogCCD {
// SuzyQ reports as ccd_cr50, and doesn't have a watchdog named CCD.
servoType, err := s.GetServoType(ctx)
if err != nil {
return errors.Wrap(err, "failed to get servo type")
}
// No need to remove CCD watchdog if there is no CCD.
if !s.hasCCD {
testing.ContextLog(ctx, "Skipping watchdog remove CCD, because there is no CCD")
return nil
}
if servoType == "ccd_cr50" {
val = WatchdogMain
}
}
testing.ContextLog(ctx, "Removing watchdog: ", val)
if err := s.SetString(ctx, WatchdogRemove, string(val)); err != nil {
return err
}
s.removedWatchdogs = append(s.removedWatchdogs, val)
// Removing the watchdog seems to take some time before it works.
if err := testing.Sleep(ctx, 4*time.Second); err != nil {
return err
}
return nil
}
// runUARTCommand runs the given command on the servo console.
func (s *Servo) runUARTCommand(ctx context.Context, cmd string) error {
cmdName := UARTCmd
// Servo v4p1 uses a different interface for UART commands, so check for that.
// TODO(b/194310192): Unify the interface name at servod.
if hasV4p1, err := s.HasControl(ctx, string(UARTCmdV4p1)); err != nil {
return errors.Wrapf(err, "failed to run HasControl for %s", string(UARTCmdV4p1))
} else if hasV4p1 {
cmdName = UARTCmdV4p1
}
return s.SetString(ctx, cmdName, cmd)
}
// RunUSBCDPConfigCommand executes the "usbc_action dp" command with the specified args on the servo
// console.
func (s *Servo) RunUSBCDPConfigCommand(ctx context.Context, args ...string) error {
args = append([]string{"usbc_action dp"}, args...)
cmd := strings.Join(args, " ")
return s.runUARTCommand(ctx, cmd)
}
// SetCC sets the CC line to the specified value.
func (s *Servo) SetCC(ctx context.Context, val OnOffValue) error {
cmd := "cc " + string(val)
return s.runUARTCommand(ctx, cmd)
}
// SetActiveDUTController sets the active controller on a dual mode v4 servo
func (s *Servo) SetActiveDUTController(ctx context.Context, adc DUTController) error {
return testing.Poll(ctx, func(ctx context.Context) error {
err := s.SetString(ctx, ActiveDUTController, string(adc))
if err != nil && !strings.Contains(err.Error(), "activeV4DeviceError") {
return testing.PollBreak(err)
}
return err
}, &testing.PollOptions{Timeout: 1 * time.Minute, Interval: 1 * time.Second})
}
// GetServoType gets the type of the servo.
func (s *Servo) GetServoType(ctx context.Context) (string, error) {
if s.servoType != "" {
return s.servoType, nil
}
servoType, err := s.GetString(ctx, Type)
if err != nil {
return "", err
}
hasCCD := strings.Contains(servoType, "ccd")
if !hasCCD {
if hasCCDState, err := s.HasControl(ctx, string(CCDState)); err != nil {
return "", errors.Wrap(err, "failed to check ccd_state control")
} else if hasCCDState {
ccdState, err := s.GetOnOff(ctx, CCDState)
if err != nil {
return "", errors.Wrap(err, "failed to get ccd_state")
}
hasCCD = ccdState
}
}
hasServoMicro := strings.Contains(servoType, string(DUTControllerServoMicro))
hasC2D2 := strings.Contains(servoType, string(DUTControllerC2D2))
isDualV4 := strings.Contains(servoType, "_and_")
if !hasCCD && !hasServoMicro && !hasC2D2 {
testing.ContextLogf(ctx, "Assuming %s is equivalent to servo_micro", servoType)
hasServoMicro = true
}
s.servoType = servoType
s.hasCCD = hasCCD
s.hasServoMicro = hasServoMicro
s.hasC2D2 = hasC2D2
s.isDualV4 = isDualV4
return s.servoType, nil
}
// GetPDCommunication returns the current value from servo_v4_PD_comm.
func (s *Servo) GetPDCommunication(ctx context.Context) (string, error) {
pdComm, err := s.GetString(ctx, PDCommunication)
if err != nil {
return "", errors.Wrap(err, "failed to get PD communication status")
}
return pdComm, nil
}
// RequireCCD verifies that the servo has a CCD connection, and switches to it for dual v4 servos.
func (s *Servo) RequireCCD(ctx context.Context) error {
servoType, err := s.GetServoType(ctx)
if err != nil {
return errors.Wrap(err, "failed to get servo type")
}
if !s.hasCCD {
return errors.Wrapf(err, "servo %s is not CCD", servoType)
}
if s.isDualV4 {
if err = s.SetActiveDUTController(ctx, DUTControllerCCD); err != nil {
if err = s.SetActiveDUTController(ctx, DUTControllerCCDGSC); err != nil {
return errors.Wrap(err, "failed to set active dut controller")
}
}
}
return nil
}
// HasCCD checks if the servo has a CCD connection.
func (s *Servo) HasCCD(ctx context.Context) (bool, error) {
_, err := s.GetServoType(ctx)
if err != nil {
return false, errors.Wrap(err, "failed to get servo type")
}
return s.hasCCD, nil
}
// PreferDebugHeader switches to the servo_micro or C2D2 for dual v4 servos, but doesn't fail on CCD only servos.
// Returns true if the servo has a debug header connection, false if it only has CCD.
func (s *Servo) PreferDebugHeader(ctx context.Context) (bool, error) {
_, err := s.GetServoType(ctx)
if err != nil {
return false, errors.Wrap(err, "failed to get servo type")
}
if s.isDualV4 {
if s.hasServoMicro {
if err = s.SetActiveDUTController(ctx, DUTControllerServoMicro); err != nil {
return false, errors.Wrap(err, "failed to set active dut controller")
}
return true, nil
} else if s.hasC2D2 {
if err = s.SetActiveDUTController(ctx, DUTControllerC2D2); err != nil {
return false, errors.Wrap(err, "failed to set active dut controller")