movingsign_api 0.0.1
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 +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +60 -0
- data/Rakefile +1 -0
- data/lib/movingsign_api/commands/command.rb +76 -0
- data/lib/movingsign_api/commands/hard_reset_command.rb +16 -0
- data/lib/movingsign_api/commands/internal/align_mode.rb +50 -0
- data/lib/movingsign_api/commands/internal/display_mode.rb +61 -0
- data/lib/movingsign_api/commands/internal/display_pause.rb +33 -0
- data/lib/movingsign_api/commands/internal/display_speed.rb +45 -0
- data/lib/movingsign_api/commands/internal/file_handle.rb +54 -0
- data/lib/movingsign_api/commands/internal/pretty_keyable.rb +50 -0
- data/lib/movingsign_api/commands/internal/sender_receiver_address.rb +82 -0
- data/lib/movingsign_api/commands/internal/utilities.rb +8 -0
- data/lib/movingsign_api/commands/set_clock_command.rb +32 -0
- data/lib/movingsign_api/commands/set_sound_command.rb +21 -0
- data/lib/movingsign_api/commands/software_reset_command.rb +16 -0
- data/lib/movingsign_api/commands/write_control_command.rb +35 -0
- data/lib/movingsign_api/commands/write_text_command.rb +116 -0
- data/lib/movingsign_api/errors.rb +22 -0
- data/lib/movingsign_api/sign.rb +144 -0
- data/lib/movingsign_api/version.rb +3 -0
- data/lib/movingsign_api.rb +13 -0
- data/movingsign_api.gemspec +30 -0
- data/spec/align_mode_spec.rb +28 -0
- data/spec/commands/set_sound_command_spec.rb +19 -0
- data/spec/display_mode_spec.rb +57 -0
- data/spec/display_speed_spec.rb +23 -0
- data/spec/examples/example_A_spec.rb +24 -0
- data/spec/examples/example_C_spec.rb +16 -0
- data/spec/examples/example_D_spec.rb +14 -0
- data/spec/examples/example_K_spec.rb +14 -0
- data/spec/examples/expected.rb +130 -0
- data/spec/file_handle_spec.rb +17 -0
- data/spec/sender_receiver_address_spec.rb +60 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/tutorials/readme_1.rb +5 -0
- data/spec/tutorials/readme_2.rb +5 -0
- data/spec/tutorials/tutorials_spec.rb +14 -0
- metadata +188 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e0a1ad2325eb6653f274649048c8cc87521342cf
|
4
|
+
data.tar.gz: a6c42e582bd97653f85baf448a85db94bed5eaa9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: abef5a49210a9f01bc090de1eb78d5979b0ca2e2969bf45b947309b096a3c047158754c4aee7c6f19e3876ffb752239fb6c93f4138a073f6cf92d9d1b33f93c5
|
7
|
+
data.tar.gz: c1e6bddf3f7a8c86e48dc5f5b1936a7f3713aa7a2099b69a69db5fa4b9720f2d8f174432106c57c863f83ae6bc5e8ac46716587b6fe06508504c6211954f0d86
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*~
|
2
|
+
/temp.rb
|
3
|
+
|
4
|
+
# Gem/Bundler Defaults
|
5
|
+
|
6
|
+
*.gem
|
7
|
+
*.rbc
|
8
|
+
.bundle
|
9
|
+
.config
|
10
|
+
.yardoc
|
11
|
+
Gemfile.lock
|
12
|
+
InstalledFiles
|
13
|
+
_yardoc
|
14
|
+
coverage
|
15
|
+
doc/
|
16
|
+
lib/bundler/man
|
17
|
+
pkg
|
18
|
+
rdoc
|
19
|
+
spec/reports
|
20
|
+
test/tmp
|
21
|
+
test/version_tmp
|
22
|
+
tmp
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- README.md CHANGELOG.md
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Eric Webb
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# MovingsignApi
|
2
|
+
|
3
|
+
MovingSign Communication Protocol V2.1 Implementation in Ruby to control
|
4
|
+
[compatible LED signs](http://www.signsdirect.com/Home/LED-Signs-Programmable/7x80-LED-Indoor-Brightness-Sign-Red.html).
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'movingsign_api'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install movingsign_api
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Using the MovingsignApi is straight forward.
|
23
|
+
|
24
|
+
<!-- include spec/tutorials/readme_1.rb -->
|
25
|
+
``` ruby
|
26
|
+
require 'movingsign_api'
|
27
|
+
|
28
|
+
sign = MovingsignApi::Sign.new '/dev/ttyUSB0'
|
29
|
+
|
30
|
+
sign.show_text "Hello World"
|
31
|
+
|
32
|
+
```
|
33
|
+
|
34
|
+
There are other commands and quite a few options. See [MovingsignApi::Sign](lib/movingsign_api/sign.rb) or [MovingsignApi::Command](lib/movingsign_api/commands/command.rb) and it's subclasses.
|
35
|
+
|
36
|
+
## Versions
|
37
|
+
|
38
|
+
A complete version history is in [CHANGELOG.md](CHANGELOG.md).
|
39
|
+
|
40
|
+
## Todo
|
41
|
+
|
42
|
+
Not all of the Movingsign protocol is implemented. Some of missing functionality include:
|
43
|
+
|
44
|
+
* Text formatting isn't supported
|
45
|
+
* Graphics commands aren't supported
|
46
|
+
* Some write control commands are not implemented:
|
47
|
+
* Set/Change password
|
48
|
+
* Set/Change device address
|
49
|
+
* Changing the text file display mode
|
50
|
+
* Read control commands
|
51
|
+
* Read clock
|
52
|
+
* Read equipment attributes
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
Make a pull request and be sure to include test cases!
|
57
|
+
|
58
|
+
## Other Projects
|
59
|
+
|
60
|
+
* [multi_movingsign](https://github.com/webmonarch/multi_movingsign) - to drive multiple Movingsigns at the same time for an information display board
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'movingsign_api/commands/internal/sender_receiver_address'
|
2
|
+
require 'movingsign_api/commands/internal/utilities'
|
3
|
+
|
4
|
+
module MovingsignApi
|
5
|
+
# Command class, subclassed by each MovingsignApi class
|
6
|
+
#
|
7
|
+
# When subclassing, be sure to implement {#command_code} and {#command_payload_bytes}
|
8
|
+
#
|
9
|
+
# Also see some useful subclasses: {WriteTextCommand}, {WriteControlCommand}
|
10
|
+
class Command
|
11
|
+
include Utilities
|
12
|
+
|
13
|
+
# The sender's address. See {SenderReceiverAddress}
|
14
|
+
attr_accessor :sender
|
15
|
+
# The receiving sign's address. See {SenderReceiverAddress}
|
16
|
+
attr_accessor :receiver
|
17
|
+
|
18
|
+
def sender=(val)
|
19
|
+
@sender = MovingsignApi::SenderReceiverAddress.parse(val)
|
20
|
+
end
|
21
|
+
|
22
|
+
def receiver=(val)
|
23
|
+
@receiver = MovingsignApi::SenderReceiverAddress.parse(val)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the command identifier string. See specification for list of valid command codes.
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
def command_code
|
30
|
+
raise MovingsignApi::NotImplementedError, "Needs to be implemented in subclass."
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a byte array representing this command, appropriate for sending to the sign's serial port
|
34
|
+
#
|
35
|
+
# @return [Array<Byte>]
|
36
|
+
def to_bytes
|
37
|
+
# set defaults
|
38
|
+
self.sender ||= :pc
|
39
|
+
self.receiver ||= 1
|
40
|
+
|
41
|
+
bytes = []
|
42
|
+
|
43
|
+
bytes.concat [0x00] * 5 # start of command
|
44
|
+
bytes.concat [0x01] # <SOH>
|
45
|
+
bytes.concat self.sender.to_bytes # Sender Address
|
46
|
+
bytes.concat self.receiver.to_bytes # Reciver Address
|
47
|
+
bytes.concat [0x02] # <STX>
|
48
|
+
bytes.concat string_to_ascii_bytes(command_code) # Command Code
|
49
|
+
bytes.concat command_payload_bytes # command specific payload
|
50
|
+
bytes.concat [0x03] # <ETX>
|
51
|
+
bytes.concat generate_checksum_bytes(bytes[10..-1]) # Checksum bytes (4)
|
52
|
+
bytes.concat [0x04] # <EOT>
|
53
|
+
|
54
|
+
bytes
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Returns command specific byte array payload
|
60
|
+
#
|
61
|
+
# @return [Array<Byte>]
|
62
|
+
def command_payload_bytes
|
63
|
+
raise MovingsignApi::NotImplementedError, "Needs to be implemented in subclass."
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns a checksum string (4 characters) appropriate for sending to the serial port
|
67
|
+
#
|
68
|
+
# ie: '12AF' (note capitalization)
|
69
|
+
def generate_checksum_bytes(payload)
|
70
|
+
sum = payload.reduce :+
|
71
|
+
sum_hex = ('%04x' % sum).upcase
|
72
|
+
|
73
|
+
string_to_ascii_bytes sum_hex
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'movingsign_api/commands/write_control_command'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
# Performs a hard reset, <b>DELETING ALL DATA ON THE SIGN</b>
|
5
|
+
#
|
6
|
+
# @note The sign will restart after receiving this command
|
7
|
+
class HardResetCommand < WriteControlCommand
|
8
|
+
def subcommand_code
|
9
|
+
'L'
|
10
|
+
end
|
11
|
+
|
12
|
+
def subcommand_payload_bytes
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'movingsign_api/commands/internal/pretty_keyable'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
# Align mode for a sign.
|
5
|
+
#
|
6
|
+
# Valid values are:
|
7
|
+
# * +:left+ or +'1'+
|
8
|
+
# * +:center+ or +'2'+
|
9
|
+
# * +:right+ or +'3+
|
10
|
+
class AlignMode
|
11
|
+
include PrettyKeyable
|
12
|
+
|
13
|
+
# @return [Symbol] align mode (one of {#left}, {#right}, {#center})
|
14
|
+
attr_accessor :key
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
# Registers a specific align mode key and value combo
|
19
|
+
def self.align_mode(key, code)
|
20
|
+
register key, code
|
21
|
+
end
|
22
|
+
|
23
|
+
public
|
24
|
+
|
25
|
+
# @!group Align Mode Constants
|
26
|
+
# @!macro [attach] dm.align_mode
|
27
|
+
# @!attribute [r] $1
|
28
|
+
# Align mode +:$1+ (protocol align mode +'$2'+)
|
29
|
+
# @return [Symbol] +:$1+
|
30
|
+
align_mode :left, '1'
|
31
|
+
align_mode :right, '3'
|
32
|
+
align_mode :center, '2'
|
33
|
+
# @!endgroup
|
34
|
+
|
35
|
+
# @param key [Symbol] one of the valid alignment keys
|
36
|
+
def initialize(key)
|
37
|
+
@key = key
|
38
|
+
end
|
39
|
+
|
40
|
+
# Parses an symbol or string into a valid {AlignMode} instance
|
41
|
+
# @return [AlignMode]
|
42
|
+
def self.parse(input)
|
43
|
+
if key = parse_to_key(input)
|
44
|
+
self.new key
|
45
|
+
else
|
46
|
+
raise InvalidInputError, "Align mode '#{input}' is invalid."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'movingsign_api/commands/internal/pretty_keyable'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
# Text display mode setting.
|
5
|
+
class DisplayMode
|
6
|
+
include PrettyKeyable
|
7
|
+
|
8
|
+
# @!visibility private
|
9
|
+
def self.display_mode(key, code)
|
10
|
+
register key, code
|
11
|
+
end
|
12
|
+
|
13
|
+
# @!group Display Mode Constants
|
14
|
+
# @!macro [attach] dm.display_mode
|
15
|
+
# @!attribute [r] $1
|
16
|
+
# Display mode $1 (protocol display mode +'$2'+)
|
17
|
+
# @return [Symbol] +:$1+
|
18
|
+
display_mode :auto, 'A'
|
19
|
+
display_mode :flash, 'B'
|
20
|
+
display_mode :hold, 'C'
|
21
|
+
display_mode :interlock, 'D'
|
22
|
+
display_mode :rolldown, 'E'
|
23
|
+
display_mode :rollup, 'F'
|
24
|
+
display_mode :rollin, 'G'
|
25
|
+
display_mode :rollout, 'H'
|
26
|
+
display_mode :rollleft, 'I'
|
27
|
+
display_mode :rollright, 'J'
|
28
|
+
display_mode :rotate, 'K'
|
29
|
+
display_mode :slide, 'L'
|
30
|
+
display_mode :snow, 'M'
|
31
|
+
display_mode :sparkle, 'N'
|
32
|
+
display_mode :spray, 'O'
|
33
|
+
display_mode :starburst, 'P'
|
34
|
+
display_mode :switch, 'Q'
|
35
|
+
display_mode :twinkle, 'R'
|
36
|
+
display_mode :wipedown, 'S'
|
37
|
+
display_mode :wipeup, 'T'
|
38
|
+
display_mode :wipein, 'U'
|
39
|
+
display_mode :wipeout, 'V'
|
40
|
+
display_mode :wipeleft, 'W'
|
41
|
+
display_mode :wiperight, 'X'
|
42
|
+
display_mode :cyclecolor, 'Y'
|
43
|
+
display_mode :clock, 'Z'
|
44
|
+
# @!endgroup
|
45
|
+
|
46
|
+
# @return [Symbol] display mode constant (see attributes {#auto}, {#flash}, {#hold}, etc.)
|
47
|
+
attr_accessor :key
|
48
|
+
|
49
|
+
def initialize(mode)
|
50
|
+
@key = mode
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.parse(input)
|
54
|
+
if key = parse_to_key(input)
|
55
|
+
self.new key
|
56
|
+
else
|
57
|
+
raise InvalidInputError, "Display mode '#{input}' is not valid."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'movingsign_api/commands/internal/pretty_keyable'
|
2
|
+
require 'movingsign_api/commands/internal/utilities'
|
3
|
+
|
4
|
+
module MovingsignApi
|
5
|
+
# Text display pause setting (in seconds, 0 - 9)
|
6
|
+
class DisplayPause
|
7
|
+
include Utilities
|
8
|
+
|
9
|
+
# @return [Integer] seconds
|
10
|
+
attr_accessor :seconds
|
11
|
+
|
12
|
+
# @param seconds [Integer] Time to pause (in seconds) (0 - 9)
|
13
|
+
def initialize(seconds)
|
14
|
+
@seconds = parse_seconds(seconds)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_bytes
|
18
|
+
string_to_ascii_bytes self.seconds
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parse_seconds(input)
|
24
|
+
if input.kind_of?(String) && input.match(/\A[0-9]\z/)
|
25
|
+
input.to_i
|
26
|
+
elsif input.kind_of?(Fixnum) && input.between?(0, 9)
|
27
|
+
input
|
28
|
+
else
|
29
|
+
raise "Pause time '#{input}' is invalid."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'movingsign_api/commands/internal/pretty_keyable'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
# Text display mode speed sending
|
5
|
+
class DisplaySpeed
|
6
|
+
include PrettyKeyable
|
7
|
+
|
8
|
+
# @!visibility private
|
9
|
+
def self.display_speed(key, code)
|
10
|
+
register(key, code)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @!group Display speed constants
|
14
|
+
|
15
|
+
# @!macro [attach] dm.display_speed
|
16
|
+
# @!attribute [r] $1
|
17
|
+
# Display speed +:$1+ (protocol display speed +'$2'+)
|
18
|
+
# @return [Symbol] +:$1+
|
19
|
+
display_speed :fastest, '1'
|
20
|
+
display_speed :faster, '2'
|
21
|
+
display_speed :normal, '3'
|
22
|
+
display_speed :slow, '4'
|
23
|
+
display_speed :slower, '5'
|
24
|
+
|
25
|
+
# @!endgroup
|
26
|
+
|
27
|
+
# @return [Symbol] Display speed constant, one of {#faster}, {#normal}, {#slow}
|
28
|
+
attr_accessor :key
|
29
|
+
|
30
|
+
def initialize(speed)
|
31
|
+
@key = speed
|
32
|
+
end
|
33
|
+
|
34
|
+
# Parses the supplied input into a {DisplaySpeed} instance if possible
|
35
|
+
# @raise InvalidInputError on invalid input
|
36
|
+
# @return [DisplaySpeed]
|
37
|
+
def self.parse(input)
|
38
|
+
if key = parse_to_key(input)
|
39
|
+
self.new key
|
40
|
+
else
|
41
|
+
raise InvalidInputError, "Display speed '#{input}' is invalid."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'movingsign_api/commands/internal/utilities'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
# Text file handle
|
5
|
+
#
|
6
|
+
# Valid values are:
|
7
|
+
# * Integer - (0 - 35)
|
8
|
+
# * String '0' - '9', 'A' - 'Z'
|
9
|
+
class FileHandle
|
10
|
+
include Utilities
|
11
|
+
|
12
|
+
# @return [Integer] the file hander integer
|
13
|
+
attr_accessor :handle
|
14
|
+
|
15
|
+
def initialize(input)
|
16
|
+
self.handle = self.class.parse_file_handle(input)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the file handle as an integer when given a file handle string
|
20
|
+
def self.code_to_handle(code)
|
21
|
+
if code.match /[0-9]/
|
22
|
+
code.to_i
|
23
|
+
else
|
24
|
+
(code.unpack('C')[0] - 'A'.unpack('C')[0]) + 10
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a file handle string when given a file handle integer
|
29
|
+
def self.handle_to_code(handle)
|
30
|
+
if handle.between?(0,9)
|
31
|
+
handle.to_s
|
32
|
+
else
|
33
|
+
(0x41 + handle - 10).chr
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_bytes
|
38
|
+
string_to_ascii_bytes self.class.handle_to_code(self.handle)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def self.parse_file_handle(input)
|
44
|
+
if input.kind_of?(Fixnum) && input.between?(0, 35)
|
45
|
+
input
|
46
|
+
elsif input.kind_of?(String) && input.match(/\A[0-9A-Z]\z/)
|
47
|
+
code_to_handle(input)
|
48
|
+
else
|
49
|
+
raise InvalidInputError, "File handle '#{input}' is invalid."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'movingsign_api/commands/internal/utilities'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
module PrettyKeyable
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
# @!visibility private
|
8
|
+
def self.included(other)
|
9
|
+
other.class_variable_set(:@@KEYS, [])
|
10
|
+
other.class_variable_set(:@@CODES, [])
|
11
|
+
|
12
|
+
other.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!visibility private
|
16
|
+
def to_bytes
|
17
|
+
string_to_ascii_bytes self.class.codes[self.class.keys.index(self.key)]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# registers the specified key and code pair as synonymous
|
24
|
+
def register(key, code)
|
25
|
+
keys << key
|
26
|
+
codes << code
|
27
|
+
|
28
|
+
define_singleton_method(key) do
|
29
|
+
key
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_to_key(input)
|
34
|
+
if index = (keys.index(input) || codes.index(input))
|
35
|
+
keys[index]
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def keys
|
42
|
+
class_variable_get(:@@KEYS)
|
43
|
+
end
|
44
|
+
|
45
|
+
def codes
|
46
|
+
class_variable_get(:@@CODES)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'movingsign_api/commands/internal/utilities'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
# Represents a sender or receiver identifier for a {Command}
|
5
|
+
#
|
6
|
+
# A address can be represented as an integer between 0 and 255, or per the spec as a two character hex string: '00' - 'FF'.
|
7
|
+
#
|
8
|
+
# Valid inputs include:
|
9
|
+
# - Integer - +0+ - +255+
|
10
|
+
# - Symbol - +:broadcast+ or +:pc+
|
11
|
+
# - String - +'00'+ - +'FF'+
|
12
|
+
#
|
13
|
+
# Some notes:
|
14
|
+
#
|
15
|
+
# - Broadcast Address: (+:broadcast+, +0+, or +'00'+) is a broadcast identifier and shouldn't be used as a sender
|
16
|
+
# - PC Address: (+:pc+, +255+, or +'FF''+) is reserved to represent the "PC address" and shouldn't be used as a recipient
|
17
|
+
#
|
18
|
+
class SenderReceiverAddress
|
19
|
+
include Utilities
|
20
|
+
|
21
|
+
# @return [Integer] the address repsented as an integer
|
22
|
+
attr_accessor :identifier
|
23
|
+
|
24
|
+
# @param identifier [String] Identifier hex string, or hex string with wildcard
|
25
|
+
def initialize(identifier)
|
26
|
+
@identifier = identifier
|
27
|
+
end
|
28
|
+
|
29
|
+
# Parses the specified input value returning an appropriate {SenderReceiver} instance or raises.
|
30
|
+
#
|
31
|
+
# @raise [InvalidInputError] on invalid input
|
32
|
+
# @return [SenderReceiverAddress]
|
33
|
+
def self.parse(input)
|
34
|
+
raise MovingsignApi::InvalidInputError, "nil not allowed" if input.nil?
|
35
|
+
|
36
|
+
value = nil
|
37
|
+
if input.kind_of? Fixnum
|
38
|
+
if input.between?(0, 255)
|
39
|
+
value = ('%02x' % input).upcase
|
40
|
+
else
|
41
|
+
raise MovingsignApi::InvalidInputError, "Integer #{input} is out of the valid range."
|
42
|
+
end
|
43
|
+
elsif input.kind_of? Symbol
|
44
|
+
if input == :broadcast
|
45
|
+
value = '00'
|
46
|
+
elsif input == :pc
|
47
|
+
value = 'FF'
|
48
|
+
else
|
49
|
+
raise MovingsignApi::InvalidInputError, "Symbol :#{input} isn't supported"
|
50
|
+
end
|
51
|
+
elsif input.kind_of? String
|
52
|
+
if (value = input.upcase.match /\A[0-9,A-F\?]{2}\z/)
|
53
|
+
value = value[0]
|
54
|
+
else
|
55
|
+
raise MovingsignApi::InvalidInputError, "Parsing string '#{input}' isn't supported"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise MovingsignApi::InvalidInputError, "Parsing '#{input.class}' isn't supported"
|
59
|
+
end
|
60
|
+
|
61
|
+
self.new(value)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns true if this represents the special 'broadcast' address
|
65
|
+
def broadcast?
|
66
|
+
self.identifier == '00'
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns true if this represents the special 'pc' address
|
70
|
+
def pc?
|
71
|
+
self.identifier = 'FF'
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
self.identifier
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_bytes
|
79
|
+
string_to_ascii_bytes self.identifier
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'movingsign_api/commands/write_control_command'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
# Sets the signs date + time
|
5
|
+
class SetClockCommand < WriteControlCommand
|
6
|
+
# @return [Time]
|
7
|
+
attr_accessor :datetime
|
8
|
+
|
9
|
+
def subcommand_code
|
10
|
+
'A'
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def subcommand_payload_bytes
|
16
|
+
bytes = []
|
17
|
+
|
18
|
+
# date
|
19
|
+
bytes.concat string_to_ascii_bytes(self.datetime.year)
|
20
|
+
bytes.concat string_to_ascii_bytes('%02d' % self.datetime.month)
|
21
|
+
bytes.concat string_to_ascii_bytes('%02d' % self.datetime.day)
|
22
|
+
# time
|
23
|
+
bytes.concat string_to_ascii_bytes('%02d' % self.datetime.hour)
|
24
|
+
bytes.concat string_to_ascii_bytes('%02d' % self.datetime.min)
|
25
|
+
bytes.concat string_to_ascii_bytes('%02d' % self.datetime.sec)
|
26
|
+
# day of week
|
27
|
+
bytes.concat string_to_ascii_bytes(self.datetime.wday)
|
28
|
+
|
29
|
+
bytes
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'movingsign_api/commands/write_control_command'
|
2
|
+
|
3
|
+
module MovingsignApi
|
4
|
+
# Sets the sound on/off property of the sign
|
5
|
+
class SetSoundCommand < WriteControlCommand
|
6
|
+
attr_accessor :on
|
7
|
+
|
8
|
+
# @param sound_on [Boolean] +true+ to turn sound on, +false+ otherwise
|
9
|
+
def initialize(sound_on)
|
10
|
+
@on = sound_on
|
11
|
+
end
|
12
|
+
|
13
|
+
def subcommand_code
|
14
|
+
'J'
|
15
|
+
end
|
16
|
+
|
17
|
+
def subcommand_payload_bytes
|
18
|
+
string_to_ascii_bytes(@on ? '1' : '0')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|