├── VERSION ├── .travis.yml ├── CHANGELOG.md ├── opc.go ├── device.go ├── client.go ├── MIT_LICENSE ├── server_test.go ├── message.go ├── message_test.go ├── README.md └── server.go /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | script: go test -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # go-opc changelog 2 | 3 | ## [0.1.0](https://github.com/kellydunn/go-opc/tree/v0.1.0) October 5, 2014 4 | 5 | - `opc.Message.SetLength()` is now exported and available outside the `opc` package. 6 | -------------------------------------------------------------------------------- /opc.go: -------------------------------------------------------------------------------- 1 | package opc 2 | 3 | const ( 4 | DEFAULT_OPC_PORT = "7890" 5 | ) 6 | 7 | func ListenAndServe() { 8 | s := NewServer() 9 | go s.ListenOnPort("tcp", DEFAULT_OPC_PORT) 10 | go s.Process() 11 | select {} 12 | } 13 | -------------------------------------------------------------------------------- /device.go: -------------------------------------------------------------------------------- 1 | package opc 2 | 3 | // This interface describes the behavior of an OPC device. 4 | // OPC devices should be able to write OPC messages to themselves 5 | // as well as be able to announce a Channel in which they are listening on. 6 | type Device interface { 7 | Write(*Message) error 8 | Channel() uint8 9 | } 10 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package opc 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // This struct represents an OPC client 8 | // which is used to send OPC messages to an OPC server. 9 | type Client struct { 10 | conn net.Conn 11 | } 12 | 13 | // Creates and returns a new Client 14 | func NewClient() *Client { 15 | return &Client{} 16 | } 17 | 18 | // Connects the client to a server specified by 19 | // the protocol string of either 'tcp' or 'udp', and the host location, 20 | // which is a single string in the `url:port` format. 21 | func (c *Client) Connect(protocol string, host string) error { 22 | conn, err := net.Dial(protocol, host) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | c.conn = conn 28 | return nil 29 | } 30 | 31 | // Sends an OPC message from the Client to the Server connection. 32 | func (c *Client) Send(m *Message) error { 33 | _, err := c.conn.Write(m.ByteArray()) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /MIT_LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Kelly Dunn 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package opc 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | // This struct is used to mock out network connections 11 | // So that we can test connection network operations accordingly 12 | type MockConn struct { 13 | payload []byte 14 | } 15 | 16 | // Read a single byte off of the payload into the passed in byte array. 17 | func (m *MockConn) Read(b []byte) (n int, err error) { 18 | b[0] = m.payload[0] 19 | m.payload = m.payload[len(b):] 20 | return len(b), nil 21 | } 22 | 23 | // Mocked out implementations of net.Conn. 24 | func (m *MockConn) Write(b []byte) (n int, err error) { return len(b), nil } 25 | func (m *MockConn) Close() error { return nil } 26 | func (m *MockConn) LocalAddr() net.Addr { return nil } 27 | func (m *MockConn) RemoteAddr() net.Addr { return nil } 28 | func (m *MockConn) SetDeadline(t time.Time) error { return nil } 29 | func (m *MockConn) SetReadDeadline(t time.Time) error { return nil } 30 | func (m *MockConn) SetWriteDeadline(t time.Time) error { return nil } 31 | 32 | // This struct is used to mock out a device implementation 33 | // such that we can test server operations accordingly 34 | type MockDevice struct { 35 | channel uint8 36 | } 37 | 38 | func (md *MockDevice) Write(m *Message) error { 39 | return nil 40 | } 41 | 42 | func (md *MockDevice) Channel() uint8 { 43 | return 0 44 | } 45 | 46 | func TestRegisterDevice(t *testing.T) { 47 | s := NewServer() 48 | d := &MockDevice{channel: 1} 49 | s.RegisterDevice(d) 50 | 51 | if _, ok := s.devs[d.Channel()]; !ok { 52 | t.Errorf("Expected Device to be registered") 53 | } 54 | } 55 | 56 | func TestUnregisterDevice(t *testing.T) { 57 | s := NewServer() 58 | d := &MockDevice{channel: 1} 59 | s.RegisterDevice(d) 60 | s.UnregisterDevice(d) 61 | 62 | if _, ok := s.devs[d.Channel()]; ok { 63 | t.Errorf("Expected Device to be unregistered after registering it") 64 | } 65 | } 66 | 67 | func TestReadOpc(t *testing.T) { 68 | s := NewServer() 69 | 70 | payload := []byte{255, 0, 0, 3, 1, 2, 3} 71 | m := &MockConn{payload: payload} 72 | 73 | msg, err := s.readOpc(m) 74 | if err != nil { 75 | t.Errorf("Encountered an error when reading a valid Message") 76 | } 77 | 78 | if bytes.Compare(msg.ByteArray(), payload) != 0 { 79 | t.Errorf("Recieved a mismatched message when reading from a Mocked Connection") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package opc 2 | 3 | const ( 4 | SET_PIXEL_COLORS = 0x00 5 | SYSTEM_EXCLUSIVE = 0xFF 6 | HEADER_BYTES = 4 7 | BROADCAST_CHANNEL = 0 8 | MAX_MESSAGE_SIZE = 0xFFFF 9 | ) 10 | 11 | // This struct describes a single message 12 | // that follows the OPC protocol 13 | type Message struct { 14 | channel byte 15 | command byte 16 | highLen byte 17 | lowLen byte 18 | data []byte 19 | } 20 | 21 | // Creates and returns a pointer to a new message that is to be sent 22 | // to the passed in channel 23 | func NewMessage(channel uint8) *Message { 24 | return &Message{channel: channel, data: make([]byte, MAX_MESSAGE_SIZE)} 25 | } 26 | 27 | // Sets the pixel color of the passed in pixel 28 | // to the passed in red, green, and blue colors, respectively for this message 29 | func (m *Message) SetPixelColor(pixel int, r uint8, g uint8, b uint8) { 30 | index := (3 * pixel) 31 | m.data[index] = r 32 | m.data[index+1] = g 33 | m.data[index+2] = b 34 | } 35 | 36 | // Specifies that this message is a System Exclusive Message 37 | // and populates data accordingly 38 | func (m *Message) SystemExclusive(systemId []byte, data []byte) { 39 | m.command = SYSTEM_EXCLUSIVE 40 | m.data = systemId 41 | for i := 0; i < len(data); i++ { 42 | m.data = append(m.data, data[i]) 43 | } 44 | } 45 | 46 | // Sets the length of this message by splitting 47 | // the passed in length into high and low length bytes. 48 | func (m *Message) SetLength(length uint16) { 49 | m.highLen = byte(length >> 8) 50 | m.lowLen = byte(length) 51 | } 52 | 53 | // Returns the length of the message. 54 | // The length of the message is respresented by combining 55 | // the high and low length bytes of this message. 56 | func (m *Message) Length() uint16 { 57 | return (uint16(m.highLen) << 8) | uint16(m.lowLen) 58 | } 59 | 60 | // Returns whether or not this message is valid or not. 61 | // Validity is determined as whether or not the Length of the message 62 | // corresponds with the number of data bytes in the message 63 | func (m *Message) IsValid() bool { 64 | return m.Length() == uint16(len(m.data)) 65 | } 66 | 67 | // Returns whether or not this message is a Broadcast message. 68 | func (m *Message) IsBroadcast() bool { 69 | return m.channel == BROADCAST_CHANNEL 70 | } 71 | 72 | // Returns a byte array representation of this message. 73 | func (m *Message) ByteArray() []byte { 74 | data := []byte{} 75 | data = append(data, m.channel, m.command, m.highLen, m.lowLen) 76 | for i := uint16(0); i < m.Length(); i++ { 77 | data = append(data, m.data[i]) 78 | } 79 | return data 80 | } 81 | -------------------------------------------------------------------------------- /message_test.go: -------------------------------------------------------------------------------- 1 | package opc 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestNewMessage(t *testing.T) { 9 | m := NewMessage(0) 10 | 11 | if m.channel != 0 { 12 | t.Errorf("Unexpected channel value after initialization.") 13 | } 14 | 15 | if m.command != SET_PIXEL_COLORS { 16 | t.Errorf("Unexpected command value after initialization.") 17 | } 18 | 19 | if m.highLen != 0 { 20 | t.Errorf("Unexpected high length byte value after initialization.") 21 | } 22 | 23 | if m.lowLen != 0 { 24 | t.Errorf("Unexpected low length byte value after initialization.") 25 | } 26 | } 27 | 28 | func TestSetPixelColor(t *testing.T) { 29 | m := NewMessage(0) 30 | m.SetPixelColor(0, uint8(255), uint8(254), uint8(253)) 31 | 32 | if m.data[0] != 255 { 33 | t.Errorf("Did not set pixel 0's Red value correctly") 34 | } 35 | 36 | if m.data[1] != 254 { 37 | t.Errorf("Did not set pixel 0's Green value correctly") 38 | } 39 | 40 | if m.data[2] != 253 { 41 | t.Errorf("Did not set pixel 0's Blue value correctly") 42 | } 43 | } 44 | 45 | func TestSetLength(t *testing.T) { 46 | m := NewMessage(0) 47 | m.SetLength(10) 48 | if uint64(m.lowLen) != uint64(10) { 49 | t.Errorf("Expected a call to SetLength() to set the Message length to 10.") 50 | } 51 | 52 | m.SetLength(uint16(MAX_MESSAGE_SIZE)) 53 | if m.Length() != uint16(MAX_MESSAGE_SIZE) { 54 | t.Errorf("Expected setting length to MAX_MESSAGE_SIZE to be reflected correctly.") 55 | } 56 | } 57 | 58 | func TestLength(t *testing.T) { 59 | m := NewMessage(0) 60 | m.lowLen = byte(10) 61 | v := m.Length() 62 | if v != uint16(10) { 63 | t.Errorf("Expected a call to Length() to return 10 after manually setting it to 10.") 64 | } 65 | } 66 | 67 | func TestIsValid(t *testing.T) { 68 | m := NewMessage(0) 69 | if m.IsValid() { 70 | t.Errorf("A Message should not be valid after initializing it.") 71 | } 72 | 73 | m.data = make([]byte, 9) 74 | m.SetLength(9) 75 | 76 | if !m.IsValid() { 77 | t.Errorf("A Message should not be invalid after inserting data into its byte array and explicitly setting its length") 78 | } 79 | } 80 | 81 | func TestIsBroadcast(t *testing.T) { 82 | m := NewMessage(255) 83 | if m.IsBroadcast() { 84 | t.Errorf("A Message with a channel set to 255 should not be a Broadcast.") 85 | } 86 | 87 | m.channel = byte(0) 88 | if !m.IsBroadcast() { 89 | t.Errorf("A Message with a channel set to 0 should be a Broadcast.") 90 | } 91 | } 92 | 93 | func TestByteArray(t *testing.T) { 94 | m := NewMessage(255) 95 | m.SetPixelColor(0, 1, 2, 3) 96 | m.SetLength(3) 97 | 98 | data := m.ByteArray() 99 | if bytes.Compare(data, []byte{255, 0, 0, 3, 1, 2, 3}) != 0 { 100 | t.Errorf("Unexpected message after converting to ByteArray got: %v", data) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | __ ___ ___ _____ ___ 3 | /'_ `\ / __`\ _______ / __`\/\ '__`\ /'___\ 4 | /\ \L\ \/\ \L\ \/\______\/\ \L\ \ \ \L\ \/\ \__/ 5 | \ \____ \ \____/\/______/\ \____/\ \ ,__/\ \____\ 6 | \/___L\ \/___/ \/___/ \ \ \/ \/____/ 7 | /\____/ \ \_\ 8 | \_/__/ \/_/ 9 | 10 | ``` 11 | [![Build Status](https://travis-ci.org/kellydunn/go-opc.png)](https://travis-ci.org/kellydunn/go-opc) 12 | ## what 13 | 14 | A golang implementation of the Open Pixel Control protocol. 15 | 16 | ## open pixel control 17 | 18 | Open Pixel Control is a protocol that is used to control arrays of RGB lights like Total Control Lighting (http://www.coolneon.com/) and Fadecandy devices (https://github.com/scanlime/fadecandy). 19 | 20 | ## documentation 21 | 22 | You can read the documentation here: (http://godoc.org/github.com/kellydunn/go-opc) 23 | 24 | ## usage 25 | 26 | ``` 27 | package main 28 | 29 | import("github.com/kellydunn/go-opc") 30 | 31 | func main { 32 | // Setup a new server 33 | s := opc.NewServer() 34 | 35 | // Register your devices (where r is an implementation of opc.Device) 36 | s.RegisterDevice(r) 37 | 38 | // Listen for incoming messages and serve them accordingly 39 | go s.ListenOnPort("tcp", "7890") 40 | go s.Process() 41 | 42 | // Create a client 43 | c := opc.NewClient("tcp", "localhost:7890") 44 | 45 | // Make a message! 46 | // This creates a message to send on channel 0 47 | // Or according to the OPC spec, a Broadcast Message. 48 | m := opc.NewMessage(0) 49 | 50 | // Set pixel #1 to white. 51 | m.SetPixelColors(1, 255, 255, 255) 52 | 53 | // Send the message! 54 | c.Send(m) 55 | 56 | // The first pixel of all registered devices should be white! 57 | } 58 | 59 | ``` 60 | 61 | ## design 62 | 63 | The applications of OPC are not currently tied to any single communication model, and it is currently unclear if there is any canonical method of dispatching OPC messages. So, when using this library, it is encouraged to implement the `opc.Device` interface such that you can further define the details of your devices and how they should be written to. 64 | 65 | A very simple implementation of the `opc.Device` interface could be the following: 66 | 67 | ``` 68 | type DummyDevice struct { 69 | conn net.Conn 70 | channel uint8 71 | } 72 | 73 | // Simple write behavior. Write the OPC Message over a network connection. 74 | func (d *DummyDevice) Write(m *opc.Message) error { 75 | _, err := conn.Write(m.byteArray) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | 83 | // Simple Channel getter. Return the channel in which to associate this device. 84 | func (d *DummyDevice) Channel() uint8 { 85 | return channel 86 | } 87 | ``` 88 | 89 | Here's a video of `go-opc` interacting with the default OPC client provided by openpixelcontrol.org : (https://vine.co/v/hIqiZewthIh) 90 | 91 | ## related work 92 | 93 | - Open Pixel Protocol (http://openpixelcontrol.org/) 94 | - Fadecandy OPC implementation (https://github.com/scanlime/fadecandy/tree/master/server/src) -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package opc 2 | 3 | import ( 4 | _ "fmt" 5 | "net" 6 | ) 7 | 8 | // This struct describes an OPC server, 9 | // which keeps track of all connected OPC devices 10 | // as well as a channel of incoming messages from all connected clients 11 | type Server struct { 12 | devs map[uint8]Device 13 | messages chan *Message 14 | } 15 | 16 | // Creates and returns a new opc.Server. 17 | // Accepts a list of usb product IDs in which to send opc messages to. 18 | func NewServer() *Server { 19 | return &Server{devs: make(map[uint8]Device), messages: make(chan *Message)} 20 | } 21 | 22 | // Registers the passed in device to the OPC server 23 | func (s *Server) RegisterDevice(dev Device) { 24 | s.devs[dev.Channel()] = dev 25 | } 26 | 27 | // Unregisters the passed in device from the OPC server 28 | func (s *Server) UnregisterDevice(dev Device) { 29 | delete(s.devs, dev.Channel()) 30 | } 31 | 32 | // Listens on the passed in port with the passed in protocol, 33 | // which in turn accepts incoming connections and handles them 34 | // by issuing individual goroutines. 35 | func (s *Server) ListenOnPort(protocol string, port string) { 36 | listener, listenerErr := net.Listen(protocol, port) 37 | if listenerErr != nil { 38 | panic(listenerErr) 39 | } 40 | 41 | for { 42 | conn, connErr := listener.Accept() 43 | if connErr != nil { 44 | panic(connErr) 45 | } 46 | 47 | go s.handleConn(conn) 48 | } 49 | } 50 | 51 | // Reads off OPC messages from the passed in connection 52 | // until the connection breaks. 53 | // Appends all valid messages onto the message channel 54 | func (s *Server) handleConn(conn net.Conn) { 55 | for { 56 | msg, err := s.readOpc(conn) 57 | if err != nil { 58 | // If we encounter an error reading from the connection, 59 | // "break" out of the loop and stop reading. 60 | // 61 | // TODO find some way of maybe alerting to the client 62 | // that an error occured 63 | break 64 | } 65 | 66 | s.messages <- msg 67 | } 68 | } 69 | 70 | // Reads and returns a single OPC message from the passed in connection. 71 | func (s *Server) readOpc(conn net.Conn) (*Message, error) { 72 | buf := make([]byte, 1) 73 | bytesRead := uint16(0) 74 | m := NewMessage(0) 75 | 76 | for !m.IsValid() { 77 | _, err := conn.Read(buf) 78 | 79 | // Encountered an error in reading from connection! 80 | // Bail out with error message 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | bytesRead++ 86 | 87 | // Ignore first 4 bytes to account for HEADER_BYTES 88 | switch bytesRead { 89 | case 1: 90 | m.channel = buf[0] 91 | case 2: 92 | m.command = buf[0] 93 | case 3: 94 | m.highLen = buf[0] 95 | case 4: 96 | m.lowLen = buf[0] 97 | default: 98 | m.data[bytesRead-5] = buf[0] 99 | 100 | if bytesRead-4 == m.Length() { 101 | m.data = m.data[:m.Length()] 102 | } 103 | } 104 | } 105 | 106 | return m, nil 107 | } 108 | 109 | // Dispatches the passed in message to all applicable devices. 110 | // If the message is of a Broadcast type, it sends it to all connected devices 111 | // Otherwise, it sends it to the specified device. 112 | func (s *Server) dispatch(m *Message) { 113 | if m.IsBroadcast() { 114 | // Broadcast the message to all registered devices 115 | for i := range s.devs { 116 | s.devs[i].Write(m) 117 | } 118 | 119 | } else { 120 | // Otherwise write to the device specified by the message's channel 121 | //fmt.Printf("Attempting to write to device at channel:%d\n", m.channel) 122 | s.devs[m.channel].Write(m) 123 | } 124 | } 125 | 126 | // Processes all pending messages indefinitely 127 | func (s *Server) Process() { 128 | for { 129 | msg := <-s.messages 130 | s.dispatch(msg) 131 | } 132 | } 133 | --------------------------------------------------------------------------------