movingsign_api 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|