package fritzbox import ( "reflect" "strings" "testing" ) // restOverview trimmed from a FRITZ!Box 6670 on FRITZ!OS 8.25, names and // AINs replaced. The meter's multimeter state is "unknown" as it was there. const restFixture = `{ "devices": [ {"UID": "10000 0000001", "name": "Radiator", "productName": "FRITZ!Smart Thermo 301", "firmwareVersion": "05.34", "isConnected": true, "batteryState": "known", "batteryValue": 40, "isBatteryLow": false}, {"UID": "10000 0000002", "name": "Desk", "productName": "FRITZ!Smart Energy 200", "firmwareVersion": "04.32", "isConnected": true, "batteryState": "unknown"}, {"UID": "10000 0000003", "name": "Meter", "productName": "FRITZ!Smart Energy 250", "firmwareVersion": "03.76", "isConnected": true, "batteryState": "known", "batteryValue": 90, "isBatteryLow": false}, {"UID": "10000 0000004", "name": "Lamp", "productName": "FRITZ!Smart Light 500", "firmwareVersion": "34.10", "isConnected": true, "batteryState": "unknown"}, {"UID": "10000 0000005", "name": "Balcony", "productName": "FRITZ!Smart Control 350", "firmwareVersion": "03.58", "isConnected": true, "batteryState": "known", "batteryValue": 15, "isBatteryLow": true} ], "units": [ {"ain": "10000 0000001", "deviceUid": "10000 0000001", "name": "Radiator", "isConnected": true, "isGroupUnit": false, "unitType": "avmThermostat", "interfaces": { "temperatureInterface": {"state": "valid", "celsius": 22}, "thermostatInterface": {"state": "valid", "setPointTemperature": {"celsius": 16, "mode": "temperature"}, "comfortTemperature": {"celsius": 21, "mode": "temperature"}, "reducedTemperature": {"celsius": 16, "mode": "temperature"}, "windowOpenMode": {"enabled": false}, "boost": {"enabled": true}, "nextChange": {"changeTime": 1790827200, "temperatureChange": {"celsius": 21, "mode": "temperature"}}}}}, {"ain": "10000 0000002", "deviceUid": "10000 0000002", "name": "Desk", "isConnected": true, "isGroupUnit": false, "unitType": "avmPlugSocket", "interfaces": { "multimeterInterface": {"state": "valid", "power": 74950, "energy": 40346, "voltage": 230256}, "onOffInterface": {"state": "valid", "active": true}, "temperatureInterface": {"state": "valid", "celsius": 25.5}}}, {"ain": "10000 0000003-1", "deviceUid": "10000 0000003", "name": "Meter", "isConnected": true, "isGroupUnit": false, "unitType": "avmMeter", "interfaces": { "multimeterInterface": {"state": "unknown"}, "smartmeterInterface": {"state": "valid"}}}, {"ain": "10000 0000004-1", "deviceUid": "10000 0000004", "name": "Lamp", "isConnected": true, "isGroupUnit": false, "unitType": "dimmableColorBulb", "interfaces": { "colorControlInterface": {"state": "valid", "colorTemperature": 4700}, "levelControlInterface": {"state": "valid", "level": 80}, "onOffInterface": {"state": "valid", "active": false}}}, {"ain": "10000 0000005-1", "deviceUid": "10000 0000005", "name": "Balcony", "isConnected": true, "isGroupUnit": false, "unitType": "doorOpenCloseDetector", "interfaces": { "alertInterface": {"state": "valid", "alerts": ["open"], "lastAlertTime": 1790787056}}}, {"ain": "grp1", "name": "All lamps", "isConnected": true, "isGroupUnit": true, "unitType": "dimmableColorBulb", "interfaces": {"onOffInterface": {"state": "valid", "active": false}}} ] }` // The same devices as AHA reports them, with the meter's total that the REST // API leaves out. const ahaTwin = ` 1Radiator 400220 32324201 179082720042 1Desk 11 2302567495040346255 1Meter900 1Meter 313500065281 1Lamp 1Lamp 020480 4700 1Balcony151 1Balcony 51311790787056 ` func TestRESTMatchesAHA(t *testing.T) { rest, err := parseREST([]byte(restFixture)) if err != nil { t.Fatal(err) } aha, err := parseDevices([]byte(ahaTwin)) if err != nil { t.Fatal(err) } if len(rest) != len(aha) { t.Fatalf("REST has %d devices, AHA %d; the group unit must not count", len(rest), len(aha)) } byAIN := map[string]Device{} for _, d := range aha { byAIN[d.AIN] = d } for _, r := range rest { a, ok := byAIN[r.AIN] if !ok { t.Errorf("REST AIN %s is not one AHA knows", r.AIN) continue } if r.Name == "Meter" { if r.EnergyKWh != nil { t.Error("an unknown multimeter state became a reading") } a.EnergyKWh = nil } if !reflect.DeepEqual(r, a) { t.Errorf("%s differs\nREST %+v\nAHA %+v", r.Name, r, a) } } if got := Missing(aha, rest); strings.Join(got, "; ") != "Meter: energy" { t.Errorf("REST lacks %q, want only the meter's energy", got) } if got := Missing(rest, aha); len(got) != 0 { t.Errorf("AHA lacks %q", got) } } func TestRESTThermostatModes(t *testing.T) { body := strings.Replace(restFixture, `"setPointTemperature": {"celsius": 16, "mode": "temperature"}`, `"setPointTemperature": {"mode": "off"}`, 1) devices, err := parseREST([]byte(body)) if err != nil { t.Fatal(err) } d := devices[0] if d.TargetC != nil || d.Thermostat == nil || !d.Thermostat.Off { t.Errorf("an off thermostat reads %v, %+v", d.TargetC, d.Thermostat) } }