fritzbox-smarthome 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 430136a0736fded1a9e2cf308cdeb00129e94514259915306146fc65be4fb04a
4
- data.tar.gz: 8755e3f921c2c815b1e6d38d3372bcfff068e6a8aa7a47785d07213585e67101
3
+ metadata.gz: 87ca99bd2b9c7f0ab8cc157255593d9211b16e168f838e3a7b643dd273b541e2
4
+ data.tar.gz: c9d862aa7dfae2b02cb16694e2caf8595397fbdc990e84fb0539159b1a5dfee6
5
5
  SHA512:
6
- metadata.gz: 94ac858e01f25649f9171df8cf685658b4a4e17f5c230a1394569c37d3052444ed1b155d8eb9a58d61fc056eb471347ee5c0c163b0a777651a8abceac4a3c76a
7
- data.tar.gz: 198756b6e8a1d7d2cfcf886e26f5e2efd3d75eb655d2533e94074c936b42da531d58147ceba13e3a54b5e621f66fe99f56fe5652cbdd6c2b64294a095fa5e22e
6
+ metadata.gz: de120146d37f15a50a43c0ba89c19cee46f91131a4765a01d518cedf897ecaa053269c6cae7bb5a6ccbdd35bd48904571722c6bafcd475d28e92ce4ff636d63c
7
+ data.tar.gz: 62629678cefbef554aa83ff465a3086661c0100075375723fdefe0ab655816b441da4c17aabba5c8d9462172cd559d6d64461ca584ed496c4c4769f5e815f9ea
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.1.2
1
+ 3.1.3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.6.0
4
+
5
+ * Add support for `Actor.find_by!(ain:)` and `Actor#reload`
6
+
7
+ ## v0.5.0
8
+
9
+ * Unify on/off interface
10
+ * Provides `#active?` and `#toggle!` methods
11
+ * Adds new functionality to `Switch`
12
+ * Replacing own implementation in `Lightbulb`
13
+
3
14
  ## v0.4.0
4
15
 
5
16
  * Add support for Fritz!DECT lightbulbs
data/README.md CHANGED
@@ -5,6 +5,7 @@ Ruby client library to interface with Smarthome features of your FritzBox.
5
5
  Currently implemented actor types:
6
6
 
7
7
  * Heater
8
+ * Lightbulb
8
9
  * SmokeDetector
9
10
  * Switch
10
11
 
@@ -40,6 +41,12 @@ actors = Fritzbox::Smarthome::Actor.all
40
41
  # Get all actors of type Heater
41
42
  heaters = Fritzbox::Smarthome::Heater.all
42
43
  heaters.last.update_hkr_temp_set(BigDecimal('21.5'))
44
+
45
+ # Get a specific actor via it's AIN
46
+ actor = Fritzbox::Smarthome::Actor.find_by!(ain: '0815 4711')
47
+
48
+ # Reload the data of an already fetched actor
49
+ actor.reload
43
50
  ```
44
51
 
45
52
  ## Development
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
- f.match(%r{^(test|spec|features)/})
17
+ f.match(%r{^(test|spec|features|examples)/})
18
18
  end
19
19
  spec.bindir = 'exe'
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -4,36 +4,63 @@ module Fritzbox
4
4
  include ActiveModel::Model
5
5
 
6
6
  attr_accessor \
7
- :id,
8
- :type,
9
- :ain,
10
- :present,
11
- :name,
12
- :manufacturer,
13
- :group_members
7
+ :id,
8
+ :type,
9
+ :ain,
10
+ :present,
11
+ :name,
12
+ :manufacturer,
13
+ :group_members
14
+
15
+ ResourceNotFound = Class.new(RuntimeError)
14
16
 
15
17
  class << self
16
18
  def all(types: ['group', 'device'])
17
- response = get(command: 'getdevicelistinfos')
18
- xml = nori.parse(response.body)
19
+ xml = parse(get(command: 'getdevicelistinfos'))
20
+
19
21
  Array.wrap(types.map { |type| xml.dig('devicelist', type) }.flatten).compact.map do |data|
20
22
  klass = Actor.descendants.find { |k| k.match?(data) } || Actor
21
23
  self.in?([klass, Actor]) ? klass.new_from_api(data) : nil
22
24
  end.compact
23
25
  end
24
26
 
27
+ def find_by!(ain: nil)
28
+ data = parse(get(command: 'getdeviceinfos', ain: ain)).fetch('device')
29
+ klass = Actor.descendants.find { |k| k.match?(data) } || Actor
30
+
31
+ instance = klass.new(ain: ain)
32
+ instance.assign_from_api(data)
33
+ instance
34
+ rescue KeyError
35
+ raise ResourceNotFound, "Unable to find actor with ain='#{ain}'"
36
+ end
37
+
25
38
  def new_from_api(data)
26
- new(
27
- id: data.dig('@id').to_s,
28
- type: data.dig('groupinfo').present? ? :group : :device,
29
- ain: data.dig('@identifier').to_s,
30
- present: data.dig('present') == '1',
31
- name: (data.dig('name') || data.dig('@productname')).to_s,
32
- manufacturer: (data.dig('manufacturer') || data.dig('@manufacturer')).to_s,
33
- group_members: data.dig('groupinfo', 'members').to_s.split(',').presence
34
- )
39
+ instance = new
40
+ instance.assign_from_api(data)
41
+ instance
35
42
  end
36
43
  end
44
+
45
+ def assign_from_api(data)
46
+ assign_attributes(
47
+ id: data.dig('@id').to_s,
48
+ type: data.dig('groupinfo').present? ? :group : :device,
49
+ ain: data.dig('@identifier').to_s,
50
+ present: data.dig('present') == '1',
51
+ name: (data.dig('name') || data.dig('@productname')).to_s,
52
+ manufacturer: (data.dig('manufacturer') || data.dig('@manufacturer')).to_s,
53
+ group_members: data.dig('groupinfo', 'members').to_s.split(',').presence
54
+ )
55
+ end
56
+
57
+ def reload
58
+ xml = parse(get(command: 'getdeviceinfos', ain: ain))
59
+ assign_from_api(xml.fetch('device'))
60
+ self
61
+ rescue KeyError
62
+ raise ResourceNotFound, "Unable to reload actor with ain='#{ain}'"
63
+ end
37
64
  end
38
65
  end
39
66
  end
@@ -14,19 +14,19 @@ module Fritzbox
14
14
  def match?(data)
15
15
  data.key?('hkr')
16
16
  end
17
+ end
17
18
 
18
- def new_from_api(data)
19
- instance = super
20
- instance.assign_attributes(
21
- battery: data.dig('battery').to_i,
22
- batterylow: data.dig('batterylow').to_i,
23
- hkr_temp_is: data.dig('hkr', 'tist').to_i * 0.5,
24
- hkr_temp_set: data.dig('hkr', 'tsoll').to_i * 0.5,
25
- hkr_next_change_period: Time.at(data.dig('hkr', 'nextchange', 'endperiod').to_i),
26
- hkr_next_change_temp: data.dig('hkr', 'nextchange', 'tchange').to_i * 0.5
27
- )
28
- instance
29
- end
19
+ def assign_from_api(data)
20
+ super(data)
21
+
22
+ assign_attributes(
23
+ battery: data.dig('battery').to_i,
24
+ batterylow: data.dig('batterylow').to_i,
25
+ hkr_temp_is: data.dig('hkr', 'tist').to_i * 0.5,
26
+ hkr_temp_set: data.dig('hkr', 'tsoll').to_i * 0.5,
27
+ hkr_next_change_period: Time.at(data.dig('hkr', 'nextchange', 'endperiod').to_i),
28
+ hkr_next_change_temp: data.dig('hkr', 'nextchange', 'tchange').to_i * 0.5
29
+ )
30
30
  end
31
31
 
32
32
  def update_hkr_temp_set(value)
@@ -4,32 +4,12 @@ module Fritzbox
4
4
  module Smarthome
5
5
  class Lightbulb < Actor
6
6
 
7
- attr_accessor \
8
- :simpleonoff_state
7
+ include Properties::SimpleOnOff
9
8
 
10
9
  class << self
11
10
  def match?(data)
12
11
  data.fetch('@productname', '') =~ /FRITZ!DECT 5\d{2}/i
13
12
  end
14
-
15
- def new_from_api(data)
16
- instance = super
17
- instance.assign_attributes(
18
- simpleonoff_state: data.dig('simpleonoff', 'state').to_i,
19
- )
20
- instance
21
- end
22
- end
23
-
24
- def active?
25
- simpleonoff_state == 1
26
- end
27
-
28
- def toggle!
29
- value = active? ? 0 : 1
30
- response = self.class.get(command: 'setsimpleonoff', ain: ain, onoff: value)
31
-
32
- response.ok? && @simpleonoff_state = value
33
13
  end
34
14
  end
35
15
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fritzbox
4
+ module Smarthome
5
+ module Properties
6
+ # Defines a common interface/behaviour for actors with the "simpleonoff" state.
7
+ # The including class is expected to have an `ain` attribute defined.
8
+ module SimpleOnOff
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ attr_accessor :simpleonoff_state
13
+ end
14
+
15
+ module ClassMethods
16
+ def new_from_api(data)
17
+ instance = defined?(super) ? super : new
18
+ instance.simpleonoff_state = data.dig('simpleonoff', 'state').to_i
19
+ instance
20
+ end
21
+ end
22
+
23
+ # @return [Boolean]
24
+ def active?
25
+ simpleonoff_state.to_s == "1"
26
+ end
27
+
28
+ # Makes a request to the Fritzbox and set the current instance's active state.
29
+ #
30
+ # The instance state is kept in memory and not checked with the Fritzbox state. It is
31
+ # possible that the device is switched on/off through other means.
32
+ #
33
+ # @example
34
+ # lightbulb.active?
35
+ # # => true
36
+ # lightbulb.toggle!
37
+ # # => 0
38
+ # lightbulb.active?
39
+ # # => false
40
+ # @return [false, Integer] Returns the new on/off state or false when the request
41
+ # was unsuccessful
42
+ # @raise [ArgumentError] if the including class does not respond to `#ain`
43
+ def toggle!
44
+ raise ArgumentError, "Attribute `ain` is missing on #{inspect}" unless respond_to?(:ain)
45
+ value = active? ? 0 : 1
46
+ response =
47
+ Fritzbox::Smarthome::Resource.get(command: 'setsimpleonoff', ain: ain, onoff: value)
48
+
49
+ response.ok? && @simpleonoff_state = value
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fritzbox/smarthome/properties/simple_on_off'
4
+
5
+ module Fritzbox
6
+ module Smarthome
7
+ module Properties
8
+ end
9
+ end
10
+ end
@@ -17,6 +17,10 @@ module Fritzbox
17
17
  HTTParty.get(url, **httparty_options)
18
18
  end
19
19
 
20
+ def parse(response)
21
+ nori.parse(response.body)
22
+ end
23
+
20
24
  private
21
25
 
22
26
  delegate :config, to: Smarthome
@@ -52,6 +56,8 @@ module Fritzbox
52
56
  @nori ||= Nori.new
53
57
  end
54
58
  end
59
+
60
+ delegate :get, :parse, to: :class
55
61
  end
56
62
  end
57
63
  end
@@ -10,15 +10,15 @@ module Fritzbox
10
10
  def match?(data)
11
11
  data.key?('alert')
12
12
  end
13
+ end
13
14
 
14
- def new_from_api(data)
15
- instance = super
16
- instance.assign_attributes(
17
- alert_state: data.dig('alert', 'state').to_i,
18
- last_alert: Time.at(data.dig('alert', 'lastalertchgtimestamp').to_i)
19
- )
20
- instance
21
- end
15
+ def assign_from_api(data)
16
+ super(data)
17
+
18
+ assign_attributes(
19
+ alert_state: data.dig('alert', 'state').to_i,
20
+ last_alert: Time.at(data.dig('alert', 'lastalertchgtimestamp').to_i)
21
+ )
22
22
  end
23
23
  end
24
24
  end
@@ -2,12 +2,13 @@ module Fritzbox
2
2
  module Smarthome
3
3
  class Switch < Actor
4
4
 
5
+ include Properties::SimpleOnOff
6
+
5
7
  attr_accessor \
6
8
  :switch_state,
7
9
  :switch_mode,
8
10
  :switch_lock,
9
11
  :switch_devicelock,
10
- :simpleonoff_state,
11
12
  :powermeter_voltage,
12
13
  :powermeter_power,
13
14
  :powermeter_energy,
@@ -18,23 +19,22 @@ module Fritzbox
18
19
  def match?(data)
19
20
  data.key?('switch')
20
21
  end
22
+ end
21
23
 
22
- def new_from_api(data)
23
- instance = super
24
- instance.assign_attributes(
25
- switch_state: data.dig('switch', 'state').to_i,
26
- switch_mode: data.dig('switch', 'mode').to_s,
27
- switch_lock: data.dig('switch', 'lock').to_i,
28
- switch_devicelock: data.dig('switch', 'devicelock').to_i,
29
- simpleonoff_state: data.dig('simpleonoff', 'state').to_i,
30
- powermeter_voltage: data.dig('powermeter', 'voltage').to_i,
31
- powermeter_power: data.dig('powermeter', 'power').to_i,
32
- powermeter_energy: data.dig('powermeter', 'energy').to_i,
33
- temperature_celsius: data.dig('temperature', 'celsius').to_i,
34
- temperature_offset: data.dig('temperature', 'offset').to_i
35
- )
36
- instance
37
- end
24
+ def assign_from_api(data)
25
+ super(data)
26
+
27
+ assign_attributes(
28
+ switch_state: data.dig('switch', 'state').to_i,
29
+ switch_mode: data.dig('switch', 'mode').to_s,
30
+ switch_lock: data.dig('switch', 'lock').to_i,
31
+ switch_devicelock: data.dig('switch', 'devicelock').to_i,
32
+ powermeter_voltage: data.dig('powermeter', 'voltage').to_i,
33
+ powermeter_power: data.dig('powermeter', 'power').to_i,
34
+ powermeter_energy: data.dig('powermeter', 'energy').to_i,
35
+ temperature_celsius: data.dig('temperature', 'celsius').to_i,
36
+ temperature_offset: data.dig('temperature', 'offset').to_i
37
+ )
38
38
  end
39
39
  end
40
40
  end
@@ -1,5 +1,5 @@
1
1
  module Fritzbox
2
2
  module Smarthome
3
- VERSION = '0.4.0'.freeze
3
+ VERSION = '0.6.0'.freeze
4
4
  end
5
5
  end
@@ -5,6 +5,7 @@ require 'nori'
5
5
 
6
6
  require 'fritzbox/smarthome/version'
7
7
  require 'fritzbox/smarthome/null_logger'
8
+ require 'fritzbox/smarthome/properties'
8
9
  require 'fritzbox/smarthome/resource'
9
10
  require 'fritzbox/smarthome/actor'
10
11
  require 'fritzbox/smarthome/heater'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fritzbox-smarthome
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Klaus Meyer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-15 00:00:00.000000000 Z
11
+ date: 2022-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -173,6 +173,8 @@ files:
173
173
  - lib/fritzbox/smarthome/heater.rb
174
174
  - lib/fritzbox/smarthome/lightbulb.rb
175
175
  - lib/fritzbox/smarthome/null_logger.rb
176
+ - lib/fritzbox/smarthome/properties.rb
177
+ - lib/fritzbox/smarthome/properties/simple_on_off.rb
176
178
  - lib/fritzbox/smarthome/resource.rb
177
179
  - lib/fritzbox/smarthome/smoke_detector.rb
178
180
  - lib/fritzbox/smarthome/switch.rb