adam6050 0.1.3 → 0.1.4
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 +4 -4
 - data/.rubocop.yml +1 -0
 - data/Gemfile.lock +1 -1
 - data/README.md +10 -2
 - data/adam6050.gemspec +8 -1
 - data/lib/adam6050/handler/login.rb +9 -1
 - data/lib/adam6050/handler/read.rb +16 -1
 - data/lib/adam6050/handler/status.rb +5 -1
 - data/lib/adam6050/handler/write.rb +2 -1
 - data/lib/adam6050/password.rb +10 -0
 - data/lib/adam6050/server.rb +33 -2
 - data/lib/adam6050/session.rb +22 -5
 - data/lib/adam6050/version.rb +1 -1
 - metadata +7 -3
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 2c831e1aa753967132fff58891039d680b8affbc
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 27d7aad6b04f7085c8b059e744434fea7939ddb0
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: aeba0631241b8976c771d32c62e1c9d9ed2d299e6d57b335c01529241e16daf449ee1004294f399d9ad3b2617ba4300baeea29e861c3ae4cff8ebeb6e4758013
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 28e971677d1e7befbdec55f875e86f288c62266218a0694506bb17f72b133fe85379ab5eb989ff2512d489df2f72f9365c2f8f932d48164754876f010494b765
         
     | 
    
        data/.rubocop.yml
    CHANGED
    
    
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -1,11 +1,15 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            #  
     | 
| 
      
 1 
     | 
    
         
            +
            # ADAM6050
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            [](https://badge.fury.io/rb/vissen-input)
         
     | 
| 
       4 
4 
     | 
    
         
             
            [](https://travis-ci.org/seblindberg/ruby-adam6050)
         
     | 
| 
       5 
5 
     | 
    
         
             
            [](http://inch-ci.org/github/seblindberg/ruby-adam6050)
         
     | 
| 
       6 
6 
     | 
    
         
             
            [](http://www.rubydoc.info/gems/adam6050/)
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
      
 8 
     | 
    
         
            +
            
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            This library implements a server that emulates the functionality of the network connected Advantech ADAM-6050 digital IO module. Specifically the UDP protocol that the unit speaks has been reverse engineered. Since I don't have an actual device to test with the response messages from the server may differ from what they should be. It all works well enough for interfacing with Synology Surveillance Station which is the original intent.
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            More information about the module can be found on the Advantech [product page](http://www.advantech.com/products/a67f7853-013a-4b50-9b20-01798c56b090/adam-6050/mod_b009c4b4-4b7c-4736-b16f-241978245e6a) which among other things links to its manual.
         
     | 
| 
       9 
13 
     | 
    
         | 
| 
       10 
14 
     | 
    
         
             
            ## Installation
         
     | 
| 
       11 
15 
     | 
    
         | 
| 
         @@ -34,6 +38,10 @@ server.run do |state, prev_state| 
     | 
|
| 
       34 
38 
     | 
    
         
             
            end
         
     | 
| 
       35 
39 
     | 
    
         
             
            ```
         
     | 
| 
       36 
40 
     | 
    
         | 
| 
      
 41 
     | 
    
         
            +
            ## TODO
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            -[ ] Improve the reliability of the server tests that involve socket connections. Hard coded delays are no good.
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
       37 
45 
     | 
    
         
             
            ## Development
         
     | 
| 
       38 
46 
     | 
    
         | 
| 
       39 
47 
     | 
    
         
             
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         
     | 
    
        data/adam6050.gemspec
    CHANGED
    
    | 
         @@ -12,7 +12,14 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
              spec.summary       = 'Server implementation of the ADAM-6050 IO module.'
         
     | 
| 
       14 
14 
     | 
    
         
             
              spec.description   = 'This library implements a server that emulates the ' \
         
     | 
| 
       15 
     | 
    
         
            -
                                   ' 
     | 
| 
      
 15 
     | 
    
         
            +
                                   'functionality of the network connected Advantech ' \
         
     | 
| 
      
 16 
     | 
    
         
            +
                                   'ADAM-6050 digital IO module. Specifically the UDP ' \
         
     | 
| 
      
 17 
     | 
    
         
            +
                                   'protocol that the unit speaks has been reverse ' \
         
     | 
| 
      
 18 
     | 
    
         
            +
                                   "engineered. Since I don't have an actual device to " \
         
     | 
| 
      
 19 
     | 
    
         
            +
                                   'test with the response messages from the server may ' \
         
     | 
| 
      
 20 
     | 
    
         
            +
                                   'differ from what they should be. It all works well ' \
         
     | 
| 
      
 21 
     | 
    
         
            +
                                   'enough for interfacing with Synology Surveillance ' \
         
     | 
| 
      
 22 
     | 
    
         
            +
                                   'Station which is the original intent.'
         
     | 
| 
       16 
23 
     | 
    
         
             
              spec.homepage      = 'https://github.com/seblindberg/ruby-adam6050'
         
     | 
| 
       17 
24 
     | 
    
         
             
              spec.license       = 'MIT'
         
     | 
| 
       18 
25 
     | 
    
         | 
| 
         @@ -3,6 +3,10 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            module ADAM6050
         
     | 
| 
       4 
4 
     | 
    
         
             
              module Handler
         
     | 
| 
       5 
5 
     | 
    
         
             
                # Allows senders to login.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # I have so far not been able to find any documentation around this feature.
         
     | 
| 
      
 8 
     | 
    
         
            +
                # It is therefore almost certain that the response in case of an incorrect
         
     | 
| 
      
 9 
     | 
    
         
            +
                # password is wrong.
         
     | 
| 
       6 
10 
     | 
    
         
             
                class Login
         
     | 
| 
       7 
11 
     | 
    
         
             
                  include Handler
         
     | 
| 
       8 
12 
     | 
    
         | 
| 
         @@ -18,8 +22,12 @@ module ADAM6050 
     | 
|
| 
       18 
22 
     | 
    
         | 
| 
       19 
23 
     | 
    
         
             
                  # @param  msg [String] the incomming message.
         
     | 
| 
       20 
24 
     | 
    
         
             
                  # @param  state [Integer] the current state.
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # @param  session [Session] the current session.
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @param  sender [Socket::UDPSource] the UDP client.
         
     | 
| 
       21 
27 
     | 
    
         
             
                  #
         
     | 
| 
       22 
     | 
    
         
            -
                  # @return [ 
     | 
| 
      
 28 
     | 
    
         
            +
                  # @return [Integer] the next state (always unchanged).
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # @return [String] a reply. The reply is either '>01' or '?' depending on
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #   if the login attempt was successful or not.
         
     | 
| 
       23 
31 
     | 
    
         
             
                  def handle(msg, state, session, sender)
         
     | 
| 
       24 
32 
     | 
    
         
             
                    return state, '?' unless @password == msg[6..-1].chomp!
         
     | 
| 
       25 
33 
     | 
    
         | 
| 
         @@ -3,6 +3,20 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            module ADAM6050
         
     | 
| 
       4 
4 
     | 
    
         
             
              module Handler
         
     | 
| 
       5 
5 
     | 
    
         
             
                # Allows registed senders to read the state.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # From the manual:
         
     | 
| 
      
 8 
     | 
    
         
            +
                #   Name        Read Channel Status
         
     | 
| 
      
 9 
     | 
    
         
            +
                #   Description This command requests that the specified ADAM-6000 module
         
     | 
| 
      
 10 
     | 
    
         
            +
                #               return the status of its digital input channels.
         
     | 
| 
      
 11 
     | 
    
         
            +
                #   Syntax      #01C\r
         
     | 
| 
      
 12 
     | 
    
         
            +
                #   Response    !0100(data)(data)(data)(data)\r
         
     | 
| 
      
 13 
     | 
    
         
            +
                #               (data) a 2-character hexadecimal value representing the
         
     | 
| 
      
 14 
     | 
    
         
            +
                #               values of the digital input module.
         
     | 
| 
      
 15 
     | 
    
         
            +
                #
         
     | 
| 
      
 16 
     | 
    
         
            +
                # TODO: The manual clearly states that onlyt the status of the input
         
     | 
| 
      
 17 
     | 
    
         
            +
                #       channels should be included in the response. There are however
         
     | 
| 
      
 18 
     | 
    
         
            +
                #       examples out there that also include the output.
         
     | 
| 
      
 19 
     | 
    
         
            +
                #
         
     | 
| 
       6 
20 
     | 
    
         
             
                class Read
         
     | 
| 
       7 
21 
     | 
    
         
             
                  include Handler
         
     | 
| 
       8 
22 
     | 
    
         | 
| 
         @@ -10,7 +24,8 @@ module ADAM6050 
     | 
|
| 
       10 
24 
     | 
    
         
             
                  MESSAGE_PREAMBLE = '$016'
         
     | 
| 
       11 
25 
     | 
    
         | 
| 
       12 
26 
     | 
    
         
             
                  # @param  state [Integer] the current state.
         
     | 
| 
       13 
     | 
    
         
            -
                  # @return [ 
     | 
| 
      
 27 
     | 
    
         
            +
                  # @return [Integer] the next state (always unchanged).
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # @return [String] the reply.
         
     | 
| 
       14 
29 
     | 
    
         
             
                  def handle(_, state, *)
         
     | 
| 
       15 
30 
     | 
    
         
             
                    # From the manual:
         
     | 
| 
       16 
31 
     | 
    
         
             
                    #   The first 2-character portion of the response (exclude the "!"
         
     | 
| 
         @@ -3,6 +3,9 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            module ADAM6050
         
     | 
| 
       4 
4 
     | 
    
         
             
              module Handler
         
     | 
| 
       5 
5 
     | 
    
         
             
                # Allows registed senders to read the IO status.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # I have so far not been able to find any documentation around this feature.
         
     | 
| 
      
 8 
     | 
    
         
            +
                # The meaning of the rely is therefore currently unknown.
         
     | 
| 
       6 
9 
     | 
    
         
             
                class Status
         
     | 
| 
       7 
10 
     | 
    
         
             
                  include Handler
         
     | 
| 
       8 
11 
     | 
    
         | 
| 
         @@ -11,7 +14,8 @@ module ADAM6050 
     | 
|
| 
       11 
14 
     | 
    
         | 
| 
       12 
15 
     | 
    
         
             
                  # @param  msg [String] the incomming message.
         
     | 
| 
       13 
16 
     | 
    
         
             
                  # @param  state [Integer] the current state.
         
     | 
| 
       14 
     | 
    
         
            -
                  # @return [ 
     | 
| 
      
 17 
     | 
    
         
            +
                  # @return [Integer] the next state (always unchanged).
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # @return [String] the reply.
         
     | 
| 
       15 
19 
     | 
    
         
             
                  def handle(msg, state, *)
         
     | 
| 
       16 
20 
     | 
    
         
             
                    reply =
         
     | 
| 
       17 
21 
     | 
    
         
             
                      if msg == MESSAGE_PREAMBLE + "\r"
         
     | 
| 
         @@ -23,7 +23,8 @@ module ADAM6050 
     | 
|
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
                  # @param  msg [String] the incomming message.
         
     | 
| 
       25 
25 
     | 
    
         
             
                  # @param  state [Integer] the current state.
         
     | 
| 
       26 
     | 
    
         
            -
                  # @return [ 
     | 
| 
      
 26 
     | 
    
         
            +
                  # @return [Integer] the next state (always unchanged).
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # @return [String] the reply.
         
     | 
| 
       27 
28 
     | 
    
         
             
                  def handle(msg, state, *)
         
     | 
| 
       28 
29 
     | 
    
         
             
                    channel, value = parse msg
         
     | 
| 
       29 
30 
     | 
    
         
             
                    next_state = if msg[3] == '1'
         
     | 
    
        data/lib/adam6050/password.rb
    CHANGED
    
    | 
         @@ -15,6 +15,7 @@ module ADAM6050 
     | 
|
| 
       15 
15 
     | 
    
         
             
              #   password = Password.new
         
     | 
| 
       16 
16 
     | 
    
         
             
              #
         
     | 
| 
       17 
17 
     | 
    
         
             
              #   password == 'anything' # => true
         
     | 
| 
      
 18 
     | 
    
         
            +
              #
         
     | 
| 
       18 
19 
     | 
    
         
             
              class Password
         
     | 
| 
       19 
20 
     | 
    
         
             
                # Format errors should be raised whenever a plain text password longer than
         
     | 
| 
       20 
21 
     | 
    
         
             
                # 8 characters is passed. Note that only ascii characters are supported.
         
     | 
| 
         @@ -41,6 +42,15 @@ module ADAM6050 
     | 
|
| 
       41 
42 
     | 
    
         | 
| 
       42 
43 
     | 
    
         
             
                private
         
     | 
| 
       43 
44 
     | 
    
         | 
| 
      
 45 
     | 
    
         
            +
                # Transforms a plain text password into an 8 character string recognised by
         
     | 
| 
      
 46 
     | 
    
         
            +
                # the ADAM-6050. The algorithm, if you can even call it that, used to
         
     | 
| 
      
 47 
     | 
    
         
            +
                # perform the transformation was found by trial and error.
         
     | 
| 
      
 48 
     | 
    
         
            +
                #
         
     | 
| 
      
 49 
     | 
    
         
            +
                # @raise [FormatError] if the plain text password is longer than 8
         
     | 
| 
      
 50 
     | 
    
         
            +
                #   characters.
         
     | 
| 
      
 51 
     | 
    
         
            +
                #
         
     | 
| 
      
 52 
     | 
    
         
            +
                # @param  plain [String] the plain text version of the password.
         
     | 
| 
      
 53 
     | 
    
         
            +
                # @return [String] the obfuscated, 8 character password.
         
     | 
| 
       44 
54 
     | 
    
         
             
                def obfuscate(plain)
         
     | 
| 
       45 
55 
     | 
    
         
             
                  codepoints = plain.codepoints
         
     | 
| 
       46 
56 
     | 
    
         | 
    
        data/lib/adam6050/server.rb
    CHANGED
    
    | 
         @@ -15,6 +15,7 @@ module ADAM6050 
     | 
|
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
                # @param  password [String] the plain text password to use when validating
         
     | 
| 
       17 
17 
     | 
    
         
             
                #   new clients.
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @param  logger [Logger] the logger to use.
         
     | 
| 
       18 
19 
     | 
    
         
             
                def initialize(password: nil, logger: Logger.new(STDOUT))
         
     | 
| 
       19 
20 
     | 
    
         
             
                  @session  = Session.new
         
     | 
| 
       20 
21 
     | 
    
         
             
                  @handlers = [
         
     | 
| 
         @@ -28,8 +29,17 @@ module ADAM6050 
     | 
|
| 
       28 
29 
     | 
    
         
             
                  @logger = logger
         
     | 
| 
       29 
30 
     | 
    
         
             
                end
         
     | 
| 
       30 
31 
     | 
    
         | 
| 
      
 32 
     | 
    
         
            +
                # Starts a new UDP server that listens on the given port. The state is
         
     | 
| 
      
 33 
     | 
    
         
            +
                # updated atomically and yielded to an optional block everytime a change is
         
     | 
| 
      
 34 
     | 
    
         
            +
                # made. By returning `false` the block can cancel the state update. This
         
     | 
| 
      
 35 
     | 
    
         
            +
                # call is blocking.
         
     | 
| 
      
 36 
     | 
    
         
            +
                #
         
     | 
| 
      
 37 
     | 
    
         
            +
                # @yield [Integer] the updated state.
         
     | 
| 
      
 38 
     | 
    
         
            +
                # @yield [Integer] the old state.
         
     | 
| 
      
 39 
     | 
    
         
            +
                #
         
     | 
| 
       31 
40 
     | 
    
         
             
                # @param  host [String] the host to listen on.
         
     | 
| 
       32 
41 
     | 
    
         
             
                # @param  port [Integer] the UDP port to listen on.
         
     | 
| 
      
 42 
     | 
    
         
            +
                # @return [nil]
         
     | 
| 
       33 
43 
     | 
    
         
             
                def run(host: nil, port: DEFAULT_PORT, &block)
         
     | 
| 
       34 
44 
     | 
    
         
             
                  logger.info "Listening on port #{port}"
         
     | 
| 
       35 
45 
     | 
    
         | 
| 
         @@ -40,9 +50,13 @@ module ADAM6050 
     | 
|
| 
       40 
50 
     | 
    
         
             
                      handle(handler, msg, sender, &block)
         
     | 
| 
       41 
51 
     | 
    
         
             
                    end
         
     | 
| 
       42 
52 
     | 
    
         
             
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                  nil
         
     | 
| 
       43 
54 
     | 
    
         
             
                end
         
     | 
| 
       44 
55 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
                # Updates the state atomicly.
         
     | 
| 
      
 56 
     | 
    
         
            +
                # Updates the state atomicly. The current state will be yielded to the given
         
     | 
| 
      
 57 
     | 
    
         
            +
                # block and the return value used as the next state.
         
     | 
| 
      
 58 
     | 
    
         
            +
                #
         
     | 
| 
      
 59 
     | 
    
         
            +
                # @yield [Integer] the current state.
         
     | 
| 
       46 
60 
     | 
    
         
             
                def update
         
     | 
| 
       47 
61 
     | 
    
         
             
                  @state_lock.synchronize do
         
     | 
| 
       48 
62 
     | 
    
         
             
                    @state = yield @state
         
     | 
| 
         @@ -51,6 +65,13 @@ module ADAM6050 
     | 
|
| 
       51 
65 
     | 
    
         | 
| 
       52 
66 
     | 
    
         
             
                private
         
     | 
| 
       53 
67 
     | 
    
         | 
| 
      
 68 
     | 
    
         
            +
                # @yield see #abort_state_change?
         
     | 
| 
      
 69 
     | 
    
         
            +
                #
         
     | 
| 
      
 70 
     | 
    
         
            +
                # @param  handler [Handler] the handler selected to handle the message.
         
     | 
| 
      
 71 
     | 
    
         
            +
                # @param  msg [String] the received message.
         
     | 
| 
      
 72 
     | 
    
         
            +
                # @param  sender [UDPSource] the UDP client.
         
     | 
| 
      
 73 
     | 
    
         
            +
                # @param  block [Proc]
         
     | 
| 
      
 74 
     | 
    
         
            +
                # @return [nil]
         
     | 
| 
       54 
75 
     | 
    
         
             
                def handle(handler, msg, sender, &block)
         
     | 
| 
       55 
76 
     | 
    
         
             
                  @session.validate! sender if handler.validate?
         
     | 
| 
       56 
77 
     | 
    
         | 
| 
         @@ -59,12 +80,22 @@ module ADAM6050 
     | 
|
| 
       59 
80 
     | 
    
         
             
                  return if abort_state_change?(next_state, &block)
         
     | 
| 
       60 
81 
     | 
    
         
             
                  @state = next_state
         
     | 
| 
       61 
82 
     | 
    
         | 
| 
       62 
     | 
    
         
            -
                   
     | 
| 
      
 83 
     | 
    
         
            +
                  return unless reply
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  sender.reply reply + "\r"
         
     | 
| 
      
 86 
     | 
    
         
            +
                  logger.debug reply
         
     | 
| 
       63 
87 
     | 
    
         
             
                rescue Session::InvalidSender => e
         
     | 
| 
       64 
88 
     | 
    
         
             
                  sender.reply "?\r"
         
     | 
| 
       65 
89 
     | 
    
         
             
                  logger.warn e.message
         
     | 
| 
       66 
90 
     | 
    
         
             
                end
         
     | 
| 
       67 
91 
     | 
    
         | 
| 
      
 92 
     | 
    
         
            +
                # @yield [Integer] the next state.
         
     | 
| 
      
 93 
     | 
    
         
            +
                # @yield [Integer] the current state.
         
     | 
| 
      
 94 
     | 
    
         
            +
                #
         
     | 
| 
      
 95 
     | 
    
         
            +
                # @param  next_state [Integer] the next state.
         
     | 
| 
      
 96 
     | 
    
         
            +
                # @return [true] if the next state differ from the current and the
         
     | 
| 
      
 97 
     | 
    
         
            +
                #   (optional) given block returns `false`.
         
     | 
| 
      
 98 
     | 
    
         
            +
                # @return [false] otherwise.
         
     | 
| 
       68 
99 
     | 
    
         
             
                def abort_state_change?(next_state)
         
     | 
| 
       69 
100 
     | 
    
         
             
                  return false if next_state == @state
         
     | 
| 
       70 
101 
     | 
    
         | 
    
        data/lib/adam6050/session.rb
    CHANGED
    
    | 
         @@ -32,8 +32,9 @@ module ADAM6050 
     | 
|
| 
       32 
32 
     | 
    
         
             
                # authenticated within the session. This can either be beacuse the sender
         
     | 
| 
       33 
33 
     | 
    
         
             
                # has not yet logged in, or beacuse an old login has expired.
         
     | 
| 
       34 
34 
     | 
    
         
             
                class InvalidSender < Error
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # @param  sender [Socket::UDPSource] the originating sender.
         
     | 
| 
       35 
36 
     | 
    
         
             
                  def initialize(sender)
         
     | 
| 
       36 
     | 
    
         
            -
                    super " 
     | 
| 
      
 37 
     | 
    
         
            +
                    super "#{self.class.name}: #{sender.remote_address.inspect}"
         
     | 
| 
       37 
38 
     | 
    
         
             
                  end
         
     | 
| 
       38 
39 
     | 
    
         
             
                end
         
     | 
| 
       39 
40 
     | 
    
         | 
| 
         @@ -111,27 +112,43 @@ module ADAM6050 
     | 
|
| 
       111 
112 
     | 
    
         
             
                def cleanup!(time: monotonic_timestamp)
         
     | 
| 
       112 
113 
     | 
    
         
             
                  return if time < @next_cleanup
         
     | 
| 
       113 
114 
     | 
    
         | 
| 
       114 
     | 
    
         
            -
                   
     | 
| 
      
 115 
     | 
    
         
            +
                  delete_expired! time, @timeout
         
     | 
| 
       115 
116 
     | 
    
         | 
| 
       116 
117 
     | 
    
         
             
                  @next_cleanup = time + @cleanup_interval
         
     | 
| 
       117 
118 
     | 
    
         
             
                end
         
     | 
| 
       118 
119 
     | 
    
         | 
| 
       119 
120 
     | 
    
         
             
                private
         
     | 
| 
       120 
121 
     | 
    
         | 
| 
      
 122 
     | 
    
         
            +
                # @param  sender [Socket::UDPSource] the UDP client.
         
     | 
| 
      
 123 
     | 
    
         
            +
                # @return [#hash] a unique identifier for the sender.
         
     | 
| 
       121 
124 
     | 
    
         
             
                def session_key(sender)
         
     | 
| 
       122 
125 
     | 
    
         
             
                  sender.remote_address.ip_address
         
     | 
| 
       123 
126 
     | 
    
         
             
                end
         
     | 
| 
       124 
127 
     | 
    
         | 
| 
      
 128 
     | 
    
         
            +
                # This is slightly faster than calling Time.now since no new object needs to
         
     | 
| 
      
 129 
     | 
    
         
            +
                # be allocated.
         
     | 
| 
      
 130 
     | 
    
         
            +
                #
         
     | 
| 
      
 131 
     | 
    
         
            +
                # @return [Numeric] the current monotonic process time.
         
     | 
| 
       125 
132 
     | 
    
         
             
                def monotonic_timestamp
         
     | 
| 
       126 
133 
     | 
    
         
             
                  Process.clock_gettime Process::CLOCK_MONOTONIC
         
     | 
| 
       127 
134 
     | 
    
         
             
                end
         
     | 
| 
       128 
135 
     | 
    
         | 
| 
       129 
     | 
    
         
            -
                 
     | 
| 
      
 136 
     | 
    
         
            +
                # @param  last_seen [Numeric] the time when the client was last seen.
         
     | 
| 
      
 137 
     | 
    
         
            +
                # @param  time [Numeric] the current time.
         
     | 
| 
      
 138 
     | 
    
         
            +
                # @param  timeout [Numeric] the time after which expired clients should be
         
     | 
| 
      
 139 
     | 
    
         
            +
                #   deleted.
         
     | 
| 
      
 140 
     | 
    
         
            +
                # @return [false] if the time last seen is within the timeout.
         
     | 
| 
      
 141 
     | 
    
         
            +
                # @return [true] otherwise.
         
     | 
| 
      
 142 
     | 
    
         
            +
                def expired?(last_seen, time, timeout)
         
     | 
| 
       130 
143 
     | 
    
         
             
                  threshold = time - timeout
         
     | 
| 
       131 
     | 
    
         
            -
                   
     | 
| 
      
 144 
     | 
    
         
            +
                  last_seen < threshold
         
     | 
| 
       132 
145 
     | 
    
         
             
                end
         
     | 
| 
       133 
146 
     | 
    
         | 
| 
       134 
     | 
    
         
            -
                 
     | 
| 
      
 147 
     | 
    
         
            +
                # @param  time [Numeric] the current time.
         
     | 
| 
      
 148 
     | 
    
         
            +
                # @param  timeout [Numeric] the time after which expired clients should be
         
     | 
| 
      
 149 
     | 
    
         
            +
                #   deleted.
         
     | 
| 
      
 150 
     | 
    
         
            +
                # @return [Hash] the session hash with expired clients deleted.
         
     | 
| 
      
 151 
     | 
    
         
            +
                def delete_expired!(time, timeout)
         
     | 
| 
       135 
152 
     | 
    
         
             
                  @session.delete_if { |_, t| expired? t, time, timeout }
         
     | 
| 
       136 
153 
     | 
    
         
             
                end
         
     | 
| 
       137 
154 
     | 
    
         
             
              end
         
     | 
    
        data/lib/adam6050/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: adam6050
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.1. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.1.4
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Sebastian Lindberg
         
     | 
| 
         @@ -108,8 +108,12 @@ dependencies: 
     | 
|
| 
       108 
108 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       109 
109 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       110 
110 
     | 
    
         
             
                    version: '0.9'
         
     | 
| 
       111 
     | 
    
         
            -
            description: This library implements a server that emulates the  
     | 
| 
       112 
     | 
    
         
            -
              IO module.
         
     | 
| 
      
 111 
     | 
    
         
            +
            description: This library implements a server that emulates the functionality of the
         
     | 
| 
      
 112 
     | 
    
         
            +
              network connected Advantech ADAM-6050 digital IO module. Specifically the UDP protocol
         
     | 
| 
      
 113 
     | 
    
         
            +
              that the unit speaks has been reverse engineered. Since I don't have an actual device
         
     | 
| 
      
 114 
     | 
    
         
            +
              to test with the response messages from the server may differ from what they should
         
     | 
| 
      
 115 
     | 
    
         
            +
              be. It all works well enough for interfacing with Synology Surveillance Station
         
     | 
| 
      
 116 
     | 
    
         
            +
              which is the original intent.
         
     | 
| 
       113 
117 
     | 
    
         
             
            email:
         
     | 
| 
       114 
118 
     | 
    
         
             
            - seb.lindberg@gmail.com
         
     | 
| 
       115 
119 
     | 
    
         
             
            executables: []
         
     |