1
\$\begingroup\$

I've got a working implementation of a program to talk UDP to TimeMachinesCorp POE clocks. This code is also in a github repo which notes things I know aren't implemented yet. I feel like there's enough working functionality now to get a code review and work with those suggestions before building out the rest of what the API supports.

I'm either a beginner or intermediate in terms of Go. This is the second medium-sized project I've implemented in Go. I'm mostly looking for anything that would make it more idiomatically Go.

I recognize that that there's a bit of repeated code in the get_status function for dealing with the differences between the versions of the API protocol. I'm definitely open to suggestions to get this to follow DRY principles better, but my efforts to refactor this didn't end up with anything that worked. I don't have an API2.0 clock yet so I haven't been able to test that part of it.

Usage

Invocation:

ctm $SUBCOMMAND $CLOCK_IP

Subcommands:

  • status returns the clock's status
  • time puts the clock into time display mode
  • up_ms puts the clock into uptimer mode displaying minutes and seconds
  • up_hms puts the clock into uptimer mode displaying hours, minutes and seconds
  • up_run tells the uptimer to run
  • up_pause tells the uptimer to pause
  • up_reset_ms resets the uptimer displaying minutes and seconds
  • up_reset_hms resets the uptimer displaying hours, minutes and seconds

Status output looks like:

sent status query to 192.168.42.204:7372
response hexdump:
00000000  01 c0 a8 2a cc 70 b3 d5  75 68 e2 05 00 12 2e 02  |...*.p..uh......|
00000010  2a 2c 50 4f 45 5f 43 6c  6f 63 6b 5f 55 54 43 00  |*,POE_Clock_UTC.|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
packet length 35 (API version 1.x)
Type 1
IP 192.168.42.204
MAC 70b3d57568e2
Ver 0500
Syncs 4654
Time {2 42 44}
Name POE_Clock_UTC

Using the uptimer looks like:

$ ./ctm up_ms 192.168.42.204
sent command up_mode_ms to 192.168.42.204:7372
acked by clock
$ ./ctm up_run 192.168.42.204
sent command up_mode_run to 192.168.42.204:7372
acked by clock
$ ./ctm time 192.168.42.204
sent command time_mode to 192.168.42.204:7372
acked by clock

The code

package main

import (
    "bufio"
    "bytes"
    "encoding/binary"
    "encoding/hex"
    "fmt"
    "net"
    "os"
)

type IPAddr [4]byte

// API 1.x structures
type Time10 struct {
    Hour   uint8
    Minute uint8
    Second uint8
}

type Response10 struct {
    DeviceType  uint8
    ClientIP    IPAddr
    MAC_address [6]uint8
    FirmwareVer [2]uint8
    NTPSyncCnt  uint16
    DisplayTime Time10
    DeviceName  [16]uint8
}

// API 2.x structures
type Time20 struct {
    Hour   uint8
    Minute uint8
    Second uint8
    Tenths uint8
}

type Response20 struct {
    DeviceType  uint8
    ClientIP    IPAddr
    MAC_address [6]uint8
    FirmwareVer [2]uint8
    NTPSyncCnt  uint16
    DisplayTime Time20
    DisplayMode uint8
    Downtimer   uint8
    Unused      [2]uint8
    WifiSignal  uint8
    DeviceName  [16]uint8
}

const maxBufferSize = 48 // the biggest response packet is 40 bytes

var (
    locator_commands = make(map[string]string)
)

func init() {
    locator_commands["device_query"] = "\xa1\x04\xb2"
    locator_commands["up_mode_ms"] = "\xa2\x00\x00"
    locator_commands["up_mode_hms"] = "\xa2\x01\x00"
    locator_commands["up_mode_pause"] = "\xa3\x00\x00"
    locator_commands["up_mode_run"] = "\xa3\x01\x00"
    locator_commands["up_reset_ms"] = "\xa4\x00\x00"
    locator_commands["up_reset_hms"] = "\xa4\x01\x00"
    locator_commands["down_mode_pause"] = "\xa6\x00\x00"
    locator_commands["down_mode_run"] = "\xa6\x01\x00"
    locator_commands["time_mode"] = "\xa8\x01\x00"
}

func (ip IPAddr) String() string {
    return fmt.Sprintf("%v.%v.%v.%v", int(ip[0]), int(ip[1]), int(ip[2]), int(ip[3]))
}

func get_status(address string) {
    conn, err := net.Dial("udp", address)
    defer conn.Close()
    if err != nil {
        fmt.Printf("Dial error %v\n", err)
        return
    }
    fmt.Fprintf(conn, locator_commands["device_query"])
    fmt.Printf("sent status query to %s\n", address)

    udp_resp := make([]byte, maxBufferSize) // buffer for UDP responses
    packet_size, err := bufio.NewReader(conn).Read(udp_resp)
    if err == nil {
        fmt.Println("response hexdump:")
        fmt.Printf("%s", hex.Dump(udp_resp))

        if packet_size == 35 {
            // API version 1.x
            fmt.Printf("packet length %d (API version 1.x)\n", packet_size)
            struct_resp := Response10{}
            buf := bytes.NewReader(udp_resp)
            err = binary.Read(buf, binary.BigEndian, &struct_resp)
            if err != nil {
                panic(err)
            }

            fmt.Printf("Type %x\n", struct_resp.DeviceType)
            fmt.Printf("IP %v\n", struct_resp.ClientIP)
            fmt.Printf("MAC %x\n", struct_resp.MAC_address)
            fmt.Printf("Ver %x\n", struct_resp.FirmwareVer)
            fmt.Printf("Syncs %d\n", struct_resp.NTPSyncCnt)
            fmt.Printf("Time %d\n", struct_resp.DisplayTime)
            fmt.Printf("Name %s\n", struct_resp.DeviceName)
        } else if packet_size == 40 {
            // API version 2.0
            fmt.Printf("packet length %d (API version 2.0)\n", packet_size)
            struct_resp := Response20{}

            fmt.Println("need a clock to test....")
            panic("unimplemented API 2.0")

            fmt.Printf("Type %x\n", struct_resp.DeviceType)
            fmt.Printf("IP %v\n", struct_resp.ClientIP)
            fmt.Printf("MAC %x\n", struct_resp.MAC_address)
            fmt.Printf("Ver %x\n", struct_resp.FirmwareVer)
            fmt.Printf("Syncs %d\n", struct_resp.NTPSyncCnt)
            fmt.Printf("Time %d\n", struct_resp.DisplayTime)
            fmt.Printf("Name %s\n", struct_resp.DeviceName)

            fmt.Printf("Mode %x\n", struct_resp.DisplayMode)
            if (struct_resp.DisplayMode & 0x40) == 0x40 {
                fmt.Println("\trunning")
            } else {
                fmt.Println("\tstopped")
            }
            if (struct_resp.DisplayMode & 0x80) == 0x80 {
                fmt.Println("\tdisplay M:S:Tenths")
            } else {
                fmt.Println("\tdisplay H:M:S")
            }
            fmt.Printf("Down %x\n", struct_resp.Downtimer)
            fmt.Printf("Wifi %x\n", struct_resp.WifiSignal)
        } else {
            fmt.Printf("packet length %d\n", packet_size)
            panic("unexpected number of bytes returned so we don't know which protocol it is talking")
        }
    } else {
        fmt.Printf("Some error %v\n", err)
    }
}

func send_command(address string, command string) {
    conn, err := net.Dial("udp", address)
    defer conn.Close()
    if err != nil {
        fmt.Printf("Dial error %v\n", err)
        return
    }
    fmt.Fprintf(conn, locator_commands[command])
    fmt.Printf("sent command %s to %s\n", command, address)

    udp_resp := make([]byte, maxBufferSize) // buffer for UDP responses
    packet_size, err := bufio.NewReader(conn).Read(udp_resp)
    if err == nil {
        if packet_size != 2 {
            fmt.Printf("packet length %d\n", packet_size)
            panic("unexpected packet size in UDP response")
        }
        if string(udp_resp[0]) != "A" {
            fmt.Println("response hexdump:")
            fmt.Printf("%s", hex.Dump(udp_resp))
            panic("response does not look like an acknowldgement")
        }
        fmt.Println("acked by clock")
    } else {
        fmt.Printf("Some error %v\n", err)
    }
}

func main() {
    if len(os.Args) < 3 {
        fmt.Println("expected arguments of subcommand and address")
        os.Exit(1)
    }

    clock_address := os.Args[2]
    clock_addrport := clock_address + ":7372"
    // fmt.Println(clock_addrport)

    switch os.Args[1] {
    case "status":
        get_status(clock_addrport)
    case "time":
        send_command(clock_addrport, "time_mode")
    case "up_ms":
        send_command(clock_addrport, "up_mode_ms")
    case "up_hms":
        send_command(clock_addrport, "up_mode_hms")
    case "up_run":
        send_command(clock_addrport, "up_mode_run")
    case "up_pause":
        send_command(clock_addrport, "up_mode_pause")
    case "up_reset_ms":
        send_command(clock_addrport, "up_reset_ms")
    case "up_reset_hms":
        send_command(clock_addrport, "up_reset_hms")
    default:
        panic("undefined subcommand")
    }
}
```
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.