@@ -2,12 +2,14 @@ package cmd
22
33import (
44 "bufio"
5+ "encoding/json"
56 "fmt"
67 "os"
78 "path/filepath"
89 "sort"
910 "strings"
1011
12+ "github.com/alperen/sensorpanel/pkg/config"
1113 "github.com/alperen/sensorpanel/pkg/sensors"
1214 "github.com/spf13/cobra"
1315)
@@ -51,15 +53,30 @@ or configured in the config file.`,
5153 RunE : runSensorOpts ,
5254}
5355
56+ var sensorReadCmd = & cobra.Command {
57+ Use : "read [sensor_id...]" ,
58+ Short : "Read current sensor values" ,
59+ Long : `Read and display current values from all or specified sensors.
60+
61+ Examples:
62+ sensorpanel sensor read # Read all sensors
63+ sensorpanel sensor read cpu # Read only CPU sensor
64+ sensorpanel sensor read cpu memory # Read CPU and memory sensors
65+ sensorpanel sensor read --json # Output as JSON` ,
66+ RunE : runSensorRead ,
67+ }
68+
5469func init () {
5570 rootCmd .AddCommand (sensorCmd )
5671 sensorCmd .AddCommand (sensorListCmd )
5772 sensorCmd .AddCommand (sensorTypesCmd )
5873 sensorCmd .AddCommand (sensorCreateCmd )
5974 sensorCmd .AddCommand (sensorOptsCmd )
75+ sensorCmd .AddCommand (sensorReadCmd )
6076
6177 sensorListCmd .Flags ().BoolP ("available" , "a" , false , "Only show available sensors on this system" )
6278 sensorTypesCmd .Flags ().StringP ("output" , "o" , "" , "Output file path (default: stdout)" )
79+ sensorReadCmd .Flags ().BoolP ("json" , "j" , false , "Output as JSON" )
6380}
6481
6582func runSensorList (cmd * cobra.Command , args []string ) error {
@@ -489,6 +506,191 @@ func sensorToCamelCase(s string) string {
489506 return result
490507}
491508
509+ func runSensorRead (cmd * cobra.Command , args []string ) error {
510+ jsonOutput , _ := cmd .Flags ().GetBool ("json" )
511+
512+ // Load config from file to get sensor options
513+ cfg , err := config .Load ()
514+ if err != nil {
515+ // Fall back to default config if no config file
516+ cfg = & config.Config {}
517+ }
518+
519+ // Create sensor config with options from config file
520+ sensorConfig := sensors .DefaultConfig ()
521+ if cfg .SensorOptions != nil {
522+ sensorConfig .Options = make (map [string ]interface {})
523+ for k , v := range cfg .SensorOptions {
524+ sensorConfig .Options [k ] = v
525+ }
526+ }
527+
528+ collector := sensors .NewCollector (sensorConfig )
529+
530+ // Collect data
531+ var data map [string ]interface {}
532+
533+ if len (args ) == 0 {
534+ // Collect all sensors
535+ data = collector .CollectAll ()
536+ } else {
537+ // Collect specified sensors
538+ data = make (map [string ]interface {})
539+ for _ , id := range args {
540+ if sensorData , ok := collector .CollectByID (id ); ok {
541+ data [id ] = sensorData
542+ } else {
543+ fmt .Fprintf (os .Stderr , "Warning: sensor '%s' not available\n " , id )
544+ }
545+ }
546+ }
547+
548+ if len (data ) == 0 {
549+ fmt .Println ("No sensor data available." )
550+ return nil
551+ }
552+
553+ // Output as JSON
554+ if jsonOutput {
555+ jsonBytes , err := jsonMarshalIndent (data )
556+ if err != nil {
557+ return fmt .Errorf ("failed to marshal JSON: %w" , err )
558+ }
559+ fmt .Println (string (jsonBytes ))
560+ return nil
561+ }
562+
563+ // Pretty print output
564+ registry := sensors .GlobalRegistry ()
565+
566+ // Group sensors by category for display
567+ categories := make (map [string ][]string )
568+ for sensorID := range data {
569+ if p , ok := registry .Get (sensorID ); ok {
570+ cat := p .Meta ().Category
571+ categories [cat ] = append (categories [cat ], sensorID )
572+ }
573+ }
574+
575+ // Sort categories
576+ catNames := make ([]string , 0 , len (categories ))
577+ for cat := range categories {
578+ catNames = append (catNames , cat )
579+ }
580+ sort .Strings (catNames )
581+
582+ for _ , cat := range catNames {
583+ fmt .Printf ("\n [%s]\n " , strings .ToUpper (cat ))
584+
585+ sensorIDs := categories [cat ]
586+ sort .Strings (sensorIDs )
587+
588+ for _ , sensorID := range sensorIDs {
589+ sensorData := data [sensorID ]
590+ p , _ := registry .Get (sensorID )
591+ meta := p .Meta ()
592+
593+ fmt .Printf (" %s\n " , meta .Name )
594+
595+ // Handle map data (arrays like disk, network)
596+ if meta .IsArray {
597+ if mapData , ok := sensorData .(map [string ]interface {}); ok {
598+ // Array sensors store items in "_items" key
599+ if items , ok := mapData ["_items" ].([]map [string ]interface {}); ok {
600+ for _ , item := range items {
601+ // Use the array key field as the header
602+ keyValue := ""
603+ if v , ok := item [meta .ArrayKey ]; ok {
604+ keyValue = fmt .Sprintf ("%v" , v )
605+ }
606+ fmt .Printf (" [%s]\n " , keyValue )
607+ printSensorFields (meta .Fields , item , " " )
608+ }
609+ } else if items , ok := mapData ["_items" ].([]interface {}); ok {
610+ // Handle case where items are []interface{}
611+ for _ , item := range items {
612+ if itemMap , ok := item .(map [string ]interface {}); ok {
613+ keyValue := ""
614+ if v , ok := itemMap [meta .ArrayKey ]; ok {
615+ keyValue = fmt .Sprintf ("%v" , v )
616+ }
617+ fmt .Printf (" [%s]\n " , keyValue )
618+ printSensorFields (meta .Fields , itemMap , " " )
619+ }
620+ }
621+ }
622+ }
623+ } else {
624+ // Single sensor data
625+ if mapData , ok := sensorData .(map [string ]interface {}); ok {
626+ printSensorFields (meta .Fields , mapData , " " )
627+ }
628+ }
629+ }
630+ }
631+
632+ fmt .Println ()
633+ return nil
634+ }
635+
636+ func printSensorFields (fields []sensors.FieldDef , data map [string ]interface {}, indent string ) {
637+ for _ , field := range fields {
638+ value , ok := data [field .JSONName ]
639+ if ! ok {
640+ continue
641+ }
642+
643+ // Format value based on type and unit
644+ var formatted string
645+ switch v := value .(type ) {
646+ case float64 :
647+ if field .Unit == "%" {
648+ formatted = fmt .Sprintf ("%.1f%%" , v )
649+ } else if field .Unit == "°C" {
650+ formatted = fmt .Sprintf ("%.1f°C" , v )
651+ } else if field .Unit == "W" {
652+ formatted = fmt .Sprintf ("%.2f W" , v )
653+ } else if field .Unit == "V" {
654+ formatted = fmt .Sprintf ("%.3f V" , v )
655+ } else if field .Unit == "MHz" {
656+ formatted = fmt .Sprintf ("%.0f MHz" , v )
657+ } else if field .Unit == "RPM" {
658+ formatted = fmt .Sprintf ("%.0f RPM" , v )
659+ } else if field .Unit == "MB" {
660+ if v >= 1024 {
661+ formatted = fmt .Sprintf ("%.1f GB" , v / 1024 )
662+ } else {
663+ formatted = fmt .Sprintf ("%.0f MB" , v )
664+ }
665+ } else if field .Unit == "GB" {
666+ formatted = fmt .Sprintf ("%.1f GB" , v )
667+ } else if field .Unit == "B/s" {
668+ formatted = sensors .FormatBytesPerSec (v )
669+ } else if field .Unit == "bytes" {
670+ formatted = sensors .FormatBytes (v )
671+ } else {
672+ formatted = fmt .Sprintf ("%.2f" , v )
673+ }
674+ case string :
675+ formatted = v
676+ case bool :
677+ if v {
678+ formatted = "yes"
679+ } else {
680+ formatted = "no"
681+ }
682+ default :
683+ formatted = fmt .Sprintf ("%v" , v )
684+ }
685+
686+ fmt .Printf ("%s%-16s %s\n " , indent , field .Name + ":" , formatted )
687+ }
688+ }
689+
690+ func jsonMarshalIndent (v interface {}) ([]byte , error ) {
691+ return json .MarshalIndent (v , "" , " " )
692+ }
693+
492694func runSensorOpts (cmd * cobra.Command , args []string ) error {
493695 registry := sensors .GlobalRegistry ()
494696 options := registry .AllOptions ()
0 commit comments