visfleet-hysteresis_filters 0.1.7

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.
@@ -0,0 +1,14 @@
1
+ # -*- ruby -*-
2
+ require 'rubygems'
3
+ require './lib/hysteresis_filters.rb'
4
+
5
+ desc "Test the hysteresis_filters plugin using rspec"
6
+ Spec::Rake::SpecTask.new do |t|
7
+ t.warning = true
8
+ t.pattern = 'spec/**/*.rb'
9
+ end
10
+
11
+
12
+ Rake::GemPackageTask.new(spec) do |pkg|
13
+ pkg.need_tar = true
14
+ end
@@ -0,0 +1,114 @@
1
+ require 'pp'
2
+
3
+ #
4
+ # This Module provides a set of triggers which exhibit the Hysteresis property. Each trigger can be
5
+ # used by itself or chained togeather to make more complex behaviours. See rspecs for example uses
6
+ #
7
+ module HysteresisFilters
8
+
9
+ class DecisionFilter
10
+ attr_accessor :boolean_state
11
+
12
+ def initialize
13
+ self.reset
14
+ end
15
+
16
+ def reset
17
+ @boolean_state = false
18
+ end
19
+
20
+ end
21
+
22
+ #
23
+ # A Schmitt Trigger is a 'greather than' test against a two value threshold. When the trigger is in a false state the
24
+ # the input value must be greater than the 'upper threshold' beforing firing. However when in the true state
25
+ # the value must dip below the lower_threshold before changing to false. Schmitt Triggers are good for filtering
26
+ # out oscillations around a threshold. See http://en.wikipedia.org/wiki/Schmitt_trigger
27
+ #
28
+ class SchmittTrigger < DecisionFilter
29
+
30
+ def greater_than?(value, lower_threshold, upper_threshold)
31
+ threshold = (@boolean_state) ? lower_threshold : upper_threshold
32
+ if value > threshold
33
+ return @boolean_state = true
34
+ else
35
+ return @boolean_state = false
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ #
42
+ # A Trigger than only fires when transitioning between states. In other words the
43
+ # trigger will return true when the input state changes from false to true, or
44
+ # vice versa. Useful in cases where you only want to do something the first time
45
+ # an event happens (such as a Speed Alert email).
46
+ #
47
+ class TransitionTrigger < DecisionFilter
48
+
49
+ def transitioning?(state)
50
+ if @boolean_state != state
51
+ @boolean_state = state
52
+ return true
53
+ else
54
+ return false
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ #
61
+ # A Trigger that only fires after the input state has been continously in that state
62
+ # for 'count' times. If the input state changes within the last 'count' inputs then
63
+ # the internal counter is reset. For example, only Fire a Speed Alert email after
64
+ # been overspeeding continously for 30 seconds.
65
+ #
66
+ # The Trigger also implements the method first_occurrence. This can be used to
67
+ # retrospectively retrieve the first occurrence of the given 'obj' parameter
68
+ # where the trigger first started to be true.
69
+ #
70
+ class ToleranceTrigger < DecisionFilter
71
+
72
+ attr_reader :first_occurrence, :count, :value
73
+
74
+ def state?(state, count, obj)
75
+ # only set @value if it's nil (i.e. before trigger has switched)
76
+ @value ||= obj
77
+
78
+ # state unchanged
79
+ if @boolean_state == state
80
+ @value = obj
81
+ # reset
82
+ @count = 0
83
+ return @boolean_state
84
+ # state changed
85
+ else
86
+ @count += 1
87
+ # if first time
88
+ if @count == 1
89
+ @possible_first_occurrence = obj
90
+ end
91
+ # long enough? then switch over
92
+ if @count >= count
93
+ @value = obj
94
+ @boolean_state = state
95
+ @first_occurrence = @possible_first_occurrence
96
+ @count = 0
97
+ return @boolean_state
98
+ else
99
+ return @boolean_state
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ def reset
106
+ @first_occurrence = nil
107
+ @count = 0
108
+ super
109
+ end
110
+
111
+ end
112
+
113
+
114
+ end
@@ -0,0 +1,63 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe 'Motion Start-Stop' do
5
+
6
+ before do
7
+ @tt = HysteresisFilters::TransitionTrigger.new
8
+ @tt.reset
9
+ @st = HysteresisFilters::ToleranceTrigger.new
10
+ @st.reset
11
+ @sct = HysteresisFilters::SchmittTrigger.new
12
+ @sct.reset
13
+ end
14
+
15
+ it "should filter motion start and stops" do
16
+ # GPS drift
17
+ 8.times do
18
+ dist = rand(15)
19
+ in_motion?(dist).should be_false
20
+ end
21
+
22
+ # False starts
23
+ in_motion?(19).should be_false
24
+ in_motion?(19).should be_false
25
+ in_motion?(3).should be_false
26
+ in_motion?(19).should be_false
27
+ in_motion?(3).should be_false
28
+
29
+ # Going for real now
30
+ in_motion?(19, 'a').should be_false
31
+ in_motion?(19, 'b').should be_false
32
+ in_motion?(20, 'c').should be_false
33
+ in_motion?(21, 'd').should be_true
34
+ in_motion?(17, 'e').should be_true
35
+ @st.first_occurrence.should == 'a'
36
+
37
+ # False stops
38
+ in_motion?(19).should be_true
39
+ in_motion?(3).should be_true
40
+ in_motion?(4).should be_true
41
+ in_motion?(3).should be_true
42
+ in_motion?(10).should be_true
43
+
44
+ # Stopping for real now
45
+ in_motion?(11).should be_true
46
+ in_motion?(3).should be_true
47
+ in_motion?(3).should be_true
48
+ in_motion?(4).should be_true
49
+ in_motion?(3).should be_false
50
+ in_motion?(15).should be_false
51
+ in_motion?(0).should be_false
52
+
53
+ end
54
+
55
+ end
56
+
57
+ def in_motion?(dist, obj = nil)
58
+ @sct.greater_than?(dist, 5, 15)
59
+ return @sct.boolean_state = @st.state?(@sct.boolean_state, 4, obj)
60
+ end
61
+
62
+
63
+
@@ -0,0 +1,26 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe HysteresisFilters::SchmittTrigger do
5
+
6
+ before do
7
+ @st = HysteresisFilters::SchmittTrigger.new
8
+ @st.reset
9
+ end
10
+
11
+ it "should return when greater than thresholds" do
12
+ 0.upto(15) do |i|
13
+ @st.greater_than?(i, 5, 15).should be_false
14
+ end
15
+ 16.upto(30) do |i|
16
+ @st.greater_than?(i, 5, 15).should be_true
17
+ end
18
+ 30.downto(6) do |i|
19
+ @st.greater_than?(i, 5, 15).should be_true
20
+ end
21
+ 5.downto(-5) do |i|
22
+ @st.greater_than?(i, 5, 15).should be_false
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,81 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe 'Speed Alert' do
5
+
6
+ before do
7
+ @tt = HysteresisFilters::TransitionTrigger.new
8
+ @tt.reset
9
+ @tolt = HysteresisFilters::ToleranceTrigger.new
10
+ @tolt.reset
11
+ @sct = HysteresisFilters::SchmittTrigger.new
12
+ @sct.reset
13
+ end
14
+
15
+ it "should only fire when 1) overspeeding is starting, 2) higher than upper threshold (schmitt), 3) has been speeding for 5 consecutive seconds" do
16
+
17
+ # Normal driving
18
+ 100.times do
19
+ speed = 80 + rand(20)
20
+ overspeed_started?(speed).should be_false
21
+ end
22
+
23
+ # Not quite speeding
24
+ overspeed_started?(90).should be_false
25
+ overspeed_started?(100).should be_false
26
+ overspeed_started?(110).should be_false
27
+ overspeed_started?(95).should be_false
28
+ overspeed_started?(91).should be_false
29
+
30
+ # Speeding now
31
+ overspeed_started?(111, 'a').should be_false
32
+ overspeed_started?(110, 'b').should be_false
33
+ overspeed_started?(100, 'c').should be_false
34
+ overspeed_started?(97, 'd').should be_false
35
+ # Speed alert fires!!!
36
+ overspeed_started?(97, 'e').should be_true
37
+ @tolt.first_occurrence.should == 'a'
38
+
39
+ # Continuing to speed, but not re-triggering
40
+ overspeed_started?(97).should be_false
41
+ overspeed_started?(110).should be_false
42
+ overspeed_started?(100).should be_false
43
+ overspeed_started?(97).should be_false
44
+ overspeed_started?(100).should be_false
45
+
46
+ # Slowing down, but not long enough to reset trigger
47
+ overspeed_started?(93).should be_false
48
+ overspeed_started?(90).should be_false
49
+
50
+ # Speeding again
51
+ overspeed_started?(101).should be_false
52
+ overspeed_started?(110).should be_false
53
+
54
+ # Slowing down. Now trigger is reset
55
+ overspeed_started?(93).should be_false
56
+ overspeed_started?(90).should be_false
57
+ overspeed_started?(82).should be_false
58
+ overspeed_started?(80).should be_false
59
+ overspeed_started?(97).should be_false
60
+
61
+ # Speeding again. This time fire.
62
+ overspeed_started?(101).should be_false
63
+ overspeed_started?(110).should be_false
64
+ overspeed_started?(100).should be_false
65
+ overspeed_started?(97).should be_false
66
+ # Speed alert fires!!!
67
+ overspeed_started?(97).should be_true
68
+
69
+ end
70
+
71
+ end
72
+
73
+ def overspeed_started?(speed, obj = nil)
74
+ @result = @sct.greater_than?(speed, 95, 100)
75
+ @result = @tolt.state?(@sct.boolean_state, 5, obj)
76
+ @tt.transitioning?(@result) and @result
77
+ end
78
+
79
+
80
+
81
+
@@ -0,0 +1,22 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe HysteresisFilters::ToleranceTrigger do
5
+
6
+ before do
7
+ @st = HysteresisFilters::ToleranceTrigger.new
8
+ @st.reset
9
+ end
10
+
11
+ it "should return true only after condition has been true >10 times" do
12
+ 0.upto(30) do |i|
13
+ if i < 24
14
+ @st.state?(i > 14, 10, i).should be_false
15
+ else
16
+ @st.state?(i > 14, 10, i).should be_true
17
+ @st.first_occurrence.should == 15
18
+ end
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,28 @@
1
+ require 'pp'
2
+ require 'lib/hysteresis_filters'
3
+
4
+ describe HysteresisFilters::TransitionTrigger do
5
+
6
+ before do
7
+ @tt = HysteresisFilters::TransitionTrigger.new
8
+ @tt.reset
9
+ end
10
+
11
+ it "should return true transitioning between states" do
12
+ 0.upto(30) do |i|
13
+ if i != 16
14
+ @tt.transitioning?(i > 15).should be_false
15
+ else
16
+ @tt.transitioning?(i > 15).should be_true
17
+ end
18
+ end
19
+ 30.downto(-5) do |i|
20
+ if i != 15
21
+ @tt.transitioning?(i > 15).should be_false
22
+ else
23
+ @tt.transitioning?(i > 15).should be_true
24
+ end
25
+ end
26
+ end
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: visfleet-hysteresis_filters
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.7
5
+ platform: ruby
6
+ authors:
7
+ - VisFleet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-09 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A collection of simple decision filters which have the Hysteresis property
17
+ email: info@visfleet.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.txt
24
+ files:
25
+ - lib/hysteresis_filters.rb
26
+ - Rakefile
27
+ - README.txt
28
+ - spec/motion_start_spec.rb
29
+ - spec/schmitt_trigger_spec.rb
30
+ - spec/speed_alert_spec.rb
31
+ - spec/tolerance_trigger_spec.rb
32
+ - spec/transition_trigger_spec.rb
33
+ has_rdoc: true
34
+ homepage:
35
+ post_install_message:
36
+ rdoc_options: []
37
+
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ requirements: []
53
+
54
+ rubyforge_project:
55
+ rubygems_version: 1.2.0
56
+ signing_key:
57
+ specification_version: 2
58
+ summary: "A collection of simple decision filters which have the Hysteresis property. The filters provide simple boolean logic but with a little added fuzzyness. The fuzzyness is based on what states a filter has previously been through (i.e. Hysteresis). These filters are useful when you want to: smooth away brief changes in state (e.g. Tolerance filter), make your logic robust against flipping between states (e.g. Schmitt filter), or only want to know when something has changed (e.g. Transition filter)."
59
+ test_files: []
60
+