From dc699bf001a953067961f3f8ef7fab1243727afb Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Tue, 25 Feb 2025 15:35:57 +0100 Subject: [PATCH] Review. For now mostly syntactic and conventions. I only changed some of the camlCase to kebab-case. There are a lot of explicit `yield` calls. Not clear if all of them are needed. Logging with `tags` is usually the preferred way. String-interpolation is your friend. (Also calls `.stringify` automatically). Missing toitdocs. They should be there at least for public elements. A few too many abbreviatons. --- .github/dependabot.yml | 8 + src/devices/base.toit | 10 +- src/devices/devices.toit | 4 +- src/devices/fake.toit | 8 +- src/devices/i2c.toit | 102 +++--- src/devices/rh2.toit | 19 +- src/devices/uart.toit | 15 +- src/messages/Ack.toit | 10 - src/messages/GPSControl.toit | 18 - src/messages/LinkControl.toit | 20 -- src/messages/ack.toit | 13 + src/messages/{Config.toit => config.toit} | 12 +- src/messages/gps-control.toit | 18 + ...pticsControl.toit => haptics-control.toit} | 10 +- .../{Heartbeat.toit => heartbeat.toit} | 2 +- .../{LastPosition.toit => last-position.toit} | 54 +-- src/messages/link-control.toit | 20 ++ src/messages/messages.toit | 18 +- src/messages/{Open.toit => open.toit} | 2 +- src/messages/types.toit | 58 ++-- src/protocol/DataField.toit | 16 - src/protocol/Header.toit | 83 ----- src/protocol/Message.toit | 112 ------ src/protocol/data-field.toit | 16 + src/protocol/{Data.toit => data.toit} | 327 +++++++++--------- src/protocol/header.toit | 83 +++++ src/protocol/message.toit | 113 ++++++ src/protocol/protocol.toit | 14 +- src/services/comms.toit | 112 +++--- src/util/bytes.toit | 2 +- tests/messages/V3Data-classes.test.toit | 2 +- tests/protocol/Data.test.toit | 22 +- tests/protocol/DataField.test.toit | 8 +- tests/protocol/Message.test.toit | 4 +- 34 files changed, 680 insertions(+), 655 deletions(-) create mode 100644 .github/dependabot.yml delete mode 100644 src/messages/Ack.toit delete mode 100644 src/messages/GPSControl.toit delete mode 100644 src/messages/LinkControl.toit create mode 100644 src/messages/ack.toit rename src/messages/{Config.toit => config.toit} (52%) create mode 100644 src/messages/gps-control.toit rename src/messages/{HapticsControl.toit => haptics-control.toit} (63%) rename src/messages/{Heartbeat.toit => heartbeat.toit} (75%) rename src/messages/{LastPosition.toit => last-position.toit} (57%) create mode 100644 src/messages/link-control.toit rename src/messages/{Open.toit => open.toit} (74%) delete mode 100644 src/protocol/DataField.toit delete mode 100644 src/protocol/Header.toit delete mode 100644 src/protocol/Message.toit create mode 100644 src/protocol/data-field.toit rename src/protocol/{Data.toit => data.toit} (50%) create mode 100644 src/protocol/header.toit create mode 100644 src/protocol/message.toit diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..03b0284 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" # Necessary to update action hashs. + directory: "/" + schedule: + interval: "weekly" + # Allow up to 3 opened pull requests for github-actions versions. + open-pull-requests-limit: 3 diff --git a/src/devices/base.toit b/src/devices/base.toit index 86a7f69..20f6cd3 100644 --- a/src/devices/base.toit +++ b/src/devices/base.toit @@ -4,15 +4,15 @@ import uart import io /* -An interface representing a Lightbug device +An interface representing a Lightbug device. */ interface Device extends Comms: /* -An interface for communicationg to and from a Lightbug device +An interface for communicating to and from a Lightbug device. */ interface Comms: - // Reader reading from the device + // Reader reading from the device. in -> io.Reader - // Writer writing to the device - out -> io.Writer \ No newline at end of file + // Writer writing to the device. + out -> io.Writer diff --git a/src/devices/devices.toit b/src/devices/devices.toit index 080b208..b7b07a8 100644 --- a/src/devices/devices.toit +++ b/src/devices/devices.toit @@ -2,6 +2,4 @@ import .base show Device Comms import .fake show Fake import .rh2 show RH2 -export Device Comms -export Fake -export RH2 +export * diff --git a/src/devices/fake.toit b/src/devices/fake.toit index 4420312..b9264dc 100644 --- a/src/devices/fake.toit +++ b/src/devices/fake.toit @@ -3,21 +3,23 @@ import io import .base import .i2c -// A fake device, that might be useful sometimes while testing +// A fake device, that might be useful sometimes while testing. class Fake implements Device: in -> io.Reader: return FakeReader out -> io.Writer: return FakeWriter -class FakeReader extends io.Reader with io.InMixin: +// The FakeReader doesn't need an additional `.in` method. +class FakeReader extends io.Reader: constructor: read_ -> ByteArray?: // log.debug "FakeReader: Simulating read operation" return #[] -class FakeWriter extends io.Writer with io.OutMixin: +// The FakeWriter doesn't need an additional `.out` method. +class FakeWriter extends io.Writer: try-write_ data/io.Data from/int to/int -> int: bytes/ByteArray := ? if data is ByteArray: diff --git a/src/devices/i2c.toit b/src/devices/i2c.toit index b12b81f..e6da873 100644 --- a/src/devices/i2c.toit +++ b/src/devices/i2c.toit @@ -4,25 +4,30 @@ import io import log import io.byte-order show LITTLE-ENDIAN -I2C_ADDRESS_LIGHTBUG := 0x1b - -I2C_COMMAND_LIGHTBUG_READABLE_BYTES := 0x01 // Get the number of bytes available to read -I2C_COMMAND_LIGHTBUG_READ := 0x02 // Read data -I2C_COMMAND_LIGHTBUG_WRITE := 0x03 // Write data - -LBI2CDevice --sda/int --scl/int -> i2c.Device: +I2C-ADDRESS-LIGHTBUG := 0x1b + +I2C-COMMAND-LIGHTBUG-READABLE-BYTES := 0x01 // Get the number of bytes available to read. +I2C-COMMAND-LIGHTBUG-READ := 0x02 // Read data. +I2C-COMMAND-LIGHTBUG-WRITE := 0x03 // Write data. + +// this is not really good style. +// The function was upper-case which made it look like a type. +// Furthermore, the pins to the i2c bus are allocated but never closed. +// Typically this isn't an issue, as most users keep the i2c device forever, but +// in theory you should close the pins when you are done with the i2c device. +lb-i2c-device --sda/int --scl/int -> i2c.Device: bus := i2c.Bus --sda=gpio.Pin sda --scl=gpio.Pin scl --frequency=400_000 - return bus.device I2C_ADDRESS_LIGHTBUG + return bus.device I2C-ADDRESS-LIGHTBUG -class Reader extends io.Reader with io.InMixin: +// No need to add the in-mixin. A reader doesn't need any `.in` method. +class Reader extends io.Reader: device /i2c.Device finishWhenEmpty_ /bool - constructor d/i2c.Device --finishWhenEmpty=false: - device = d + constructor .device --finishWhenEmpty=false: // XXX: --finishWhenEmpty is not used since factoring out into the lightbug package // TODO: Decide if we want to keep it, refactor it, or remove it... finishWhenEmpty_ = finishWhenEmpty @@ -30,59 +35,59 @@ class Reader extends io.Reader with io.InMixin: /** Reads the next bytes. There are no yields in this function, so it will block until there are bytes to read, - as we want to empty the buffer as soon as possible into our own memory. + as we want to empty the buffer as soon as possible into our own memory. */ read_ -> ByteArray?: // log.debug "calling read_ in LB Reader for i2c" all := #[] - allExpected := 0 + all-expected := 0 loops := 0 - // Read from the buffer as fast as possible (as our buffer is bigger) - // At most 5*(tx buffer), so 5*1000 = 5KB + // Read from the buffer as fast as possible (as our buffer is bigger). + // At most 5*(tx buffer), so 5*1000 = 5KB. while loops <= 5: loops++ // log.debug "Getting number of bytes available to read, loop $loops" - lenBytes := device.read-address #[I2C_COMMAND_LIGHTBUG_READABLE_BYTES] 2 - lenInt := LITTLE-ENDIAN.uint16 lenBytes 0 - allExpected = allExpected + lenInt - + len-bytes := device.read-address #[I2C-COMMAND-LIGHTBUG-READABLE-BYTES] 2 + len-int := LITTLE-ENDIAN.uint16 len-bytes 0 + all-expected = all-expected + len-int + + // --- This comment looks stale. // Taking uart as an example, if there are no bytes, it loops until there are some. // uart does this with a read state, for now we will just sleep a bit... - if lenInt == 0: + if len-int == 0: if finishWhenEmpty_: log.debug "No bytes to read, finishing" return null // log.debug "No bytes to read, yielding" break // Leave the while loop - log.debug "Got $lenInt bytes to read" + log.debug "Got $len-int bytes to read" - while lenInt > 0: - chunkSize := min lenInt 254 - log.debug "Reading chunk of $chunkSize bytes stage 1" - device.write #[I2C_COMMAND_LIGHTBUG_READ, chunkSize] - log.debug "Reading chunk of $chunkSize bytes stage 2" - b := device.read chunkSize - if b.size != chunkSize: - log.error "Failed to read chunk $chunkSize bytes, got $b.size bytes" + while len-int > 0: + chunk-size := min len-int 254 + log.debug "Reading chunk of $chunk-size bytes stage 1" + device.write #[I2C-COMMAND-LIGHTBUG-READ, chunk-size] + log.debug "Reading chunk of $chunk-size bytes stage 2" + b := device.read chunk-size + if b.size != chunk-size: + log.error "Failed to read chunk $chunk-size bytes, got $b.size bytes" return null all += b - lenInt -= chunkSize + len-int -= chunk-size - if all.size != allExpected: - log.error "Failed to read $allExpected bytes, got $all.size bytes" + if all.size != all-expected: + log.error "Failed to read $all-expected bytes, got $all.size bytes" return null log.debug "Read $all.size bytes after $loops loops" - yield // They are in our buffer now, so yield briefly before returning + yield // They are in our buffer now, so yield briefly before returning. return all -class Writer extends io.Writer with io.OutMixin: +class Writer extends io.Writer: device /i2c.Device - constructor d/i2c.Device: - device = d + constructor .device: /** Writes the given $data to this writer. @@ -97,21 +102,14 @@ class Writer extends io.Writer with io.OutMixin: bytes = ByteArray.from data log.debug "Writing $bytes.size bytes" log.debug "Bytes: $bytes" - - currentIndex := 0 - readToIndex := 0 - while currentIndex < bytes.size: - // Send in batches of 255 - readToIndex = currentIndex + 255 - if readToIndex > bytes.size: - readToIndex = bytes.size - - log.debug "Writing bytes $currentIndex to $readToIndex" - log.debug "Bytes: $bytes[currentIndex..readToIndex]" + + List.chunk-up 0 bytes.size 255: | from/int to/int | + log.debug "Writing bytes $from to $to" + log.debug "Bytes: $bytes[from..to]" sendLen := #[0] - LITTLE-ENDIAN.put-uint8 sendLen 0 (readToIndex - currentIndex) - device.write-address #[I2C_COMMAND_LIGHTBUG_WRITE] sendLen + bytes[currentIndex..readToIndex] + LITTLE-ENDIAN.put-uint8 sendLen 0 (to - from) + device.write-address #[I2C-COMMAND-LIGHTBUG-WRITE] sendLen + bytes[from..to] + + from = to - currentIndex = readToIndex - - return bytes.size \ No newline at end of file + return bytes.size diff --git a/src/devices/rh2.toit b/src/devices/rh2.toit index 6907ef5..e8ed1f5 100644 --- a/src/devices/rh2.toit +++ b/src/devices/rh2.toit @@ -3,14 +3,17 @@ import io import .base import .i2c -// The RH2 device, currently at revision 3 +// Needs more information. Can't say if abbreviating is OK or not. +// Most likely, it should be `Rh2`. +// The RH2 device, currently at revision 3. class RH2 implements Device: - static I2C_SDA := 6 - static I2C_SCL := 7 - static I2C_DEVICE := LBI2CDevice --sda=RH2.I2C_SDA --scl=RH2.I2C_SCL - static I2C_READER := Reader I2C_DEVICE - static I2C_WRITER := Writer I2C_DEVICE + static I2C-SDA := 6 + static I2C-SCL := 7 + // Feels weird to have a static and instance way of accessing the in/out. + static I2C-DEVICE := lb-i2c-device --sda=RH2.I2C-SDA --scl=RH2.I2C-SCL + static I2C-READER := Reader I2C-DEVICE + static I2C-WRITER := Writer I2C-DEVICE in -> io.Reader: - return I2C_READER + return I2C-READER out -> io.Writer: - return I2C_WRITER \ No newline at end of file + return I2C-WRITER diff --git a/src/devices/uart.toit b/src/devices/uart.toit index 614fd67..2767cab 100644 --- a/src/devices/uart.toit +++ b/src/devices/uart.toit @@ -3,11 +3,14 @@ import gpio // ESP32-C6 https://docs.espressif.com/projects/esp-at/en/latest/esp32c6/Get_Started/Hardware_connection.html#esp32c6-4mb-series // UART0 GPIO17 (RX) GPIO16 (TX) Defaults -ESP32C6_UART_RX_PIN := 17 -ESP32C6_UART_TX_PIN := 16 +// Toit typically doesn't "force" users to the defaults. The esp gpio +// matrix is pretty good. Usually you don't care for the default pins. +ESP32C6-UART-RX-PIN := 17 +ESP32C6-UART-TX-PIN := 16 -ESP32C6UartPort -> uart.Port: +// As for the i2c device, the pins are never closed. +esp32c6-uart-port -> uart.Port: return uart.Port - --rx=gpio.Pin ESP32C6_UART_RX_PIN - --tx=gpio.Pin ESP32C6_UART_TX_PIN - --baud_rate=115200 \ No newline at end of file + --rx=gpio.Pin ESP32C6-UART-RX-PIN + --tx=gpio.Pin ESP32C6-UART-TX-PIN + --baud-rate=115200 diff --git a/src/messages/Ack.toit b/src/messages/Ack.toit deleted file mode 100644 index e26ebc2..0000000 --- a/src/messages/Ack.toit +++ /dev/null @@ -1,10 +0,0 @@ -import ..protocol as protocol - -class Ack extends protocol.Data: - static MT := 5 - - constructor: - super - - msg -> protocol.Message: - return protocol.Message.withData MT this diff --git a/src/messages/GPSControl.toit b/src/messages/GPSControl.toit deleted file mode 100644 index bb5b8ae..0000000 --- a/src/messages/GPSControl.toit +++ /dev/null @@ -1,18 +0,0 @@ -import ..protocol as protocol - -class GPSControl extends protocol.Data: - static MT := 39 - static GPS_ENABLE := 1 - static RTK_ENABLE_CORRECTION := 2 - - constructor --gps/bool --rtk/bool: - this.addDataUint8 GPS_ENABLE (if gps: 1 else: 0) - this.addDataUint8 RTK_ENABLE_CORRECTION (if rtk: 1 else: 0) - - constructor.fromData data/protocol.Data: - super.fromData data - - msg -> protocol.Message: - msg := protocol.Message.withData MT this - msg.header.data.addDataUint8 protocol.Header.TYPE_MESSAGE_METHOD protocol.Header.METHOD-SET - return msg diff --git a/src/messages/LinkControl.toit b/src/messages/LinkControl.toit deleted file mode 100644 index 28de8a1..0000000 --- a/src/messages/LinkControl.toit +++ /dev/null @@ -1,20 +0,0 @@ -import ..protocol as protocol - -class LinkControl extends protocol.Data: - static MT := 50 - static ADDRESS := 1 - static PORT := 2 - static ENABLE := 3 - - constructor --ip/string --port/int --enable/bool: - this.addDataS ADDRESS ip - this.addDataUint16 PORT port - this.addDataUint8 ENABLE (if enable: 1 else: 0) - - constructor.fromData data/protocol.Data: - super.fromData data - - msg -> protocol.Message: - msg := protocol.Message.withData MT this - msg.header.data.addDataUint8 protocol.Header.TYPE_MESSAGE_METHOD protocol.Header.METHOD-SET - return msg \ No newline at end of file diff --git a/src/messages/ack.toit b/src/messages/ack.toit new file mode 100644 index 0000000..cc04351 --- /dev/null +++ b/src/messages/ack.toit @@ -0,0 +1,13 @@ +import ..protocol as protocol + +class Ack extends protocol.Data: + // Do users need this constant? + // Should it be private instead? `MT_` + // Same for the other messages. + static MT := 5 + + constructor: + super + + msg -> protocol.Message: + return protocol.Message.with-data MT this diff --git a/src/messages/Config.toit b/src/messages/config.toit similarity index 52% rename from src/messages/Config.toit rename to src/messages/config.toit index 3070b02..cf83f41 100644 --- a/src/messages/Config.toit +++ b/src/messages/config.toit @@ -5,14 +5,14 @@ class Config extends protocol.Data: static KEY := 7 static PAYLOAD := 9 - constructor.fromData data/protocol.Data: - super.fromData data - + constructor.from-data data/protocol.Data: + super.from-data data + msg -> protocol.Message: - return protocol.Message.withData MT this + return protocol.Message.with-data MT this key -> int: - return getDataUintn KEY + return get-data-uintn KEY payload -> ByteArray: - return getData PAYLOAD + return get-data PAYLOAD diff --git a/src/messages/gps-control.toit b/src/messages/gps-control.toit new file mode 100644 index 0000000..ffd79f4 --- /dev/null +++ b/src/messages/gps-control.toit @@ -0,0 +1,18 @@ +import ..protocol as protocol + +class GPSControl extends protocol.Data: + static MT := 39 + static GPS-ENABLE := 1 + static RTK-ENABLE-CORRECTION := 2 + + constructor --gps/bool --rtk/bool: + this.add-data-uint8 GPS-ENABLE (gps ? 1 : 0) + this.add-data-uint8 RTK-ENABLE-CORRECTION (rtk ? 1 : 0) + + constructor.from-data data/protocol.Data: + super.from-data data + + msg -> protocol.Message: + msg := protocol.Message.with-data MT this + msg.header.data.add-data-uint8 protocol.Header.TYPE-MESSAGE-METHOD protocol.Header.METHOD-SET + return msg diff --git a/src/messages/HapticsControl.toit b/src/messages/haptics-control.toit similarity index 63% rename from src/messages/HapticsControl.toit rename to src/messages/haptics-control.toit index b45f5c2..64c01df 100644 --- a/src/messages/HapticsControl.toit +++ b/src/messages/haptics-control.toit @@ -5,17 +5,17 @@ class HapticsControl extends protocol.Data: static PATTERN := 1 static INTENSITY := 2 - constructor.fromData data/protocol.Data: - super.fromData data + constructor.from-data data/protocol.Data: + super.from-data data msg -> protocol.Message: - return protocol.Message.withData MT this + return protocol.Message.with-data MT this pattern -> int: - return getDataUint8 PATTERN + return get-data-uint8 PATTERN intensity -> int: - return getDataUint8 INTENSITY + return get-data-uint8 INTENSITY stringify -> string: return { diff --git a/src/messages/Heartbeat.toit b/src/messages/heartbeat.toit similarity index 75% rename from src/messages/Heartbeat.toit rename to src/messages/heartbeat.toit index a51c60d..4c1b1c4 100644 --- a/src/messages/Heartbeat.toit +++ b/src/messages/heartbeat.toit @@ -7,4 +7,4 @@ class Heartbeat extends protocol.Data: super msg -> protocol.Message: - return protocol.Message.withData MT this + return protocol.Message.with-data MT this diff --git a/src/messages/LastPosition.toit b/src/messages/last-position.toit similarity index 57% rename from src/messages/LastPosition.toit rename to src/messages/last-position.toit index da9dc55..6c9e7bb 100644 --- a/src/messages/LastPosition.toit +++ b/src/messages/last-position.toit @@ -9,30 +9,30 @@ class LastPosition extends protocol.Data: static LONGITUDE := 3 static ALTITUDE := 4 static ACCURACY := 5 - static COURSE_OVER_GROUND := 6 + static COURSE-OVER-GROUND := 6 static SPEED := 7 - static NUMBER_OF_SATELLITES := 8 - static AVERAGE_CN0 := 9 - static POSITION_TYPE := 10 - static POSITION_SOURCE := 11 + static NUMBER-OF-SATELLITES := 8 + static AVERAGE-CN0 := 9 + static POSITION-TYPE := 10 + static POSITION-SOURCE := 11 - static LAT_LON_RAW_ADJUSTMENT := 1e7 + static LAT-LON-RAW-ADJUSTMENT := 1e7 + + constructor.from-data data/protocol.Data: + super.from-data data - constructor.fromData data/protocol.Data: - super.fromData data - msg -> protocol.Message: - return protocol.Message.withData MT this - - static subscribeMsg --intervalms/int -> protocol.Message: + return protocol.Message.with-data MT this + + static subscribe-msg --interval-ms/int -> protocol.Message: msg := protocol.Message MT - msg.header.data.addDataUint8 protocol.Header.TYPE_MESSAGE_METHOD protocol.Header.METHOD-SUBSCRIBE - msg.header.data.addDataUint32 protocol.Header.TYPE_SUBSCRIPTION_INTERVAL intervalms // must be uint32 + msg.header.data.add-data-uint8 protocol.Header.TYPE-MESSAGE-METHOD protocol.Header.METHOD-SUBSCRIBE + msg.header.data.add-data-uint32 protocol.Header.TYPE-SUBSCRIPTION-INTERVAL interval-ms // must be uint32 return msg timestamp -> int: // TODO return a typed value - return getDataUintn TIMESTAMP + return get-data-uintn TIMESTAMP coordinate -> Coordinate: return Coordinate latitude longitude latitude -> float: @@ -44,29 +44,29 @@ class LastPosition extends protocol.Data: longitudeFixed -> FixedPoint: return ( FixedPoint --decimals=7 longitude-float ) latitude-float -> float: - return ( getDataIntn LATITUDE) / LAT_LON_RAW_ADJUSTMENT + return ( get-data-intn LATITUDE) / LAT-LON-RAW-ADJUSTMENT longitude-float -> float: - return ( getDataIntn LONGITUDE) / LAT_LON_RAW_ADJUSTMENT + return ( get-data-intn LONGITUDE) / LAT-LON-RAW-ADJUSTMENT latitude-raw -> int: - return getDataIntn LATITUDE + return get-data-intn LATITUDE longitude-raw -> int: - return getDataIntn LONGITUDE + return get-data-intn LONGITUDE altitude -> int: - return getDataUintn ALTITUDE + return get-data-uintn ALTITUDE accuracy -> int: - return getDataUintn ACCURACY + return get-data-uintn ACCURACY course-over-ground -> int: - return getDataUintn COURSE_OVER_GROUND + return get-data-uintn COURSE-OVER-GROUND speed -> int: - return getDataUintn SPEED + return get-data-uintn SPEED number-of-satellites -> int: - return getDataUintn NUMBER_OF_SATELLITES + return get-data-uintn NUMBER-OF-SATELLITES average-cn0 -> int: - return getDataUintn AVERAGE_CN0 + return get-data-uintn AVERAGE-CN0 position-type -> int: - return getDataUintn POSITION_TYPE + return get-data-uintn POSITION-TYPE position-source -> int: - return getDataUintn POSITION_SOURCE + return get-data-uintn POSITION-SOURCE stringify -> string: return { diff --git a/src/messages/link-control.toit b/src/messages/link-control.toit new file mode 100644 index 0000000..79132cb --- /dev/null +++ b/src/messages/link-control.toit @@ -0,0 +1,20 @@ +import ..protocol as protocol + +class LinkControl extends protocol.Data: + static MT := 50 + static ADDRESS := 1 + static PORT := 2 + static ENABLE := 3 + + constructor --ip/string --port/int --enable/bool: + this.add-data-s ADDRESS ip + this.add-data-uint16 PORT port + this.add-data-uint8 ENABLE (if enable: 1 else: 0) + + constructor.from-data data/protocol.Data: + super.from-data data + + msg -> protocol.Message: + msg := protocol.Message.with-data MT this + msg.header.data.add-data-uint8 protocol.Header.TYPE-MESSAGE-METHOD protocol.Header.METHOD-SET + return msg diff --git a/src/messages/messages.toit b/src/messages/messages.toit index ee3b67b..e09af43 100644 --- a/src/messages/messages.toit +++ b/src/messages/messages.toit @@ -1,11 +1,11 @@ -import .LastPosition -import .Config -import .HapticsControl -import .LinkControl -import .GPSControl -import .Open -import .Heartbeat -import .Ack +import .last-position +import .config +import .haptics-control +import .link-control +import .gps-control +import .open +import .heartbeat +import .ack import .types show * -export * \ No newline at end of file +export * diff --git a/src/messages/Open.toit b/src/messages/open.toit similarity index 74% rename from src/messages/Open.toit rename to src/messages/open.toit index 588ba22..ed5d736 100644 --- a/src/messages/Open.toit +++ b/src/messages/open.toit @@ -7,4 +7,4 @@ class Open extends protocol.Data: super msg -> protocol.Message: - return protocol.Message.withData MT this + return protocol.Message.with-data MT this diff --git a/src/messages/types.toit b/src/messages/types.toit index 7c8bea4..1f7890e 100644 --- a/src/messages/types.toit +++ b/src/messages/types.toit @@ -1,37 +1,37 @@ // TODO move all of these to their own message classes? -MSGTYPE_GENERAL_ACK /int ::= 5 -MSGTYPE_GENERAL_KEEPALIVE /int ::= 6 // No data needed -MSGTYPE_LIVELINK_START /int ::= 10 -MSGTYPE_LIVELINK_OPEN /int ::= 11 -MSGTYPE_LIVELINK_CLOSE /int ::= 12 -MSGTYPE_LIVELINK_HEARTBEAT /int ::= 13 -MSGTYPE_LIVELINK_CONFIGUPDATE /int ::= 14 -MSGTYPE_LIVELINK_POSITIONDATA /int ::= 15 -MSGTYPE_LIVELINK_DEVICESTATUS /int ::= 16 -MSGTYPE_DEVICESERVICE_TXNOW /int ::= 30 -MSGTYPE_DEVICESERVICE_GSM_CFUN /int ::= 31 -MSGTYPE_DEVICESERVICE_GSM_IMEI /int ::= 32 -MSGTYPE_DEVICESERVICE_GSM_ICCID /int ::= 33 -MSGTYPE_DEVICESERVICE_DEVICEINFO_STATUS /int ::= 34 -MSGTYPE_DEVICESERVICE_DEVICEINFO_ID /int ::= 35 -MSGTYPE_DEVICESERVICE_DEVICEINFO_TIME /int ::= 36 -MSGTYPE_DEVICESERVICE_DEVICEINFO_LASTPOST /int ::= 37 +MSGTYPE-GENERAL-ACK /int ::= 5 +MSGTYPE-GENERAL-KEEPALIVE /int ::= 6 // No data needed +MSGTYPE-LIVELINK-START /int ::= 10 +MSGTYPE-LIVELINK-OPEN /int ::= 11 +MSGTYPE-LIVELINK-CLOSE /int ::= 12 +MSGTYPE-LIVELINK-HEARTBEAT /int ::= 13 +MSGTYPE-LIVELINK-CONFIGUPDATE /int ::= 14 +MSGTYPE-LIVELINK-POSITIONDATA /int ::= 15 +MSGTYPE-LIVELINK-DEVICESTATUS /int ::= 16 +MSGTYPE-DEVICESERVICE-TXNOW /int ::= 30 +MSGTYPE-DEVICESERVICE-GSM-CFUN /int ::= 31 +MSGTYPE-DEVICESERVICE-GSM-IMEI /int ::= 32 +MSGTYPE-DEVICESERVICE-GSM-ICCID /int ::= 33 +MSGTYPE-DEVICESERVICE-DEVICEINFO-STATUS /int ::= 34 +MSGTYPE-DEVICESERVICE-DEVICEINFO-ID /int ::= 35 +MSGTYPE-DEVICESERVICE-DEVICEINFO-TIME /int ::= 36 +MSGTYPE-DEVICESERVICE-DEVICEINFO-LASTPOST /int ::= 37 // TODO 38 -MSGTYPE_DEVICESERVICE_GPS_CONTROL /int ::= 39 -MSGTYPE_DEVICESERVICE_HAPTICS_CONTROL /int ::= 40 -MSGTYPE_DEVICESERVICE_DEVICEINFO_TEMP /int ::= 41 -MSGTYPE_DEVICESERVICE_BUZZER_CONTROL /int ::= 42 +MSGTYPE-DEVICESERVICE-GPS-CONTROL /int ::= 39 +MSGTYPE-DEVICESERVICE-HAPTICS-CONTROL /int ::= 40 +MSGTYPE-DEVICESERVICE-DEVICEINFO-TEMP /int ::= 41 +MSGTYPE-DEVICESERVICE-BUZZER-CONTROL /int ::= 42 // todo 43 -MSGTYPE_DEVICESERVICE_DEVICEINFO_PRESSURE /int ::= 44 +MSGTYPE-DEVICESERVICE-DEVICEINFO-PRESSURE /int ::= 44 // Screenupdate messages -MSGTYPE_UIUPDATE_START /int ::= 10000 -MSGTYPE_UIUPDATE_END /int ::= 11000 -MSGTYPE_UIUPDATE_TEXT_PAGE /int ::= 10009 -MSGTYPE_UIUPDATE_MENU /int ::= 10010 -MSGTYPE_UIUPDATE_BITMAP /int ::= 10011 -MSGTYPE_UIUPDATE_CLEAR /int ::= 10014 -MSGTYPE_UIEVENT_BUTTON /int ::= 10013 +MSGTYPE-UIUPDATE-START /int ::= 10000 +MSGTYPE-UIUPDATE-END /int ::= 11000 +MSGTYPE-UIUPDATE-TEXT-PAGE /int ::= 10009 +MSGTYPE-UIUPDATE-MENU /int ::= 10010 +MSGTYPE-UIUPDATE-BITMAP /int ::= 10011 +MSGTYPE-UIUPDATE-CLEAR /int ::= 10014 +MSGTYPE-UIEVENT-BUTTON /int ::= 10013 // Not used yet? //MSGTYPE_UIEVENT_HOME /int ::= 10011 \ No newline at end of file diff --git a/src/protocol/DataField.toit b/src/protocol/DataField.toit deleted file mode 100644 index c58b14f..0000000 --- a/src/protocol/DataField.toit +++ /dev/null @@ -1,16 +0,0 @@ -class DataField: - dataBytes_ /ByteArray := #[] - - constructor data/ByteArray=#[]: - if data.size > 255: - throw "V3 protocol can't have data field of over 255 bytes" - dataBytes_ = data - - stringify -> string: - return dataBytes_.stringify - - bytesForProtocol -> ByteArray: - b := ByteArray dataBytes_.size + 1 - b[0] = dataBytes_.size - b.replace 1 dataBytes_ 0 dataBytes_.size - return b \ No newline at end of file diff --git a/src/protocol/Header.toit b/src/protocol/Header.toit deleted file mode 100644 index a8e8289..0000000 --- a/src/protocol/Header.toit +++ /dev/null @@ -1,83 +0,0 @@ -import .Data show * - -class Header: - // Header data types - static TYPE_MESSAGE_ID := 1 // ID that can be used by receiver to ACK, and client to track for various responses, or re-sends - static TYPE_CLIENT_ID := 2 // ID of the client sending the message - static TYPE_RESPONSE_TO_MESSAGE_ID := 3 // ID of the message that is being responded to - static TYPE_MESSAGE_STATUS := 4 // Status of the message. If omitted, assume OK? - static TYPE_MESSAGE_METHOD := 5 // Request a service to be perform an action - static TYPE_SUBSCRIPTION_INTERVAL := 6 // Interval in ms for a subscription to be sent - static TYPE_FORWARDED_FOR_TYPE := 9 - static TYPE_FORWARDED_FOR := 10 // ID of the client sending the original message that is being forwarded - static TYPE_FORWARDED_RSSI := 11 // RSSI of forwarded message - static TYPE_FORWARDED_SNR := 12 // SNR of forwarded message - static TYPE_FORWARD_TO_TYPE := 13 // Type of forwarded message - static TYPE_FORWARD_TO := 14 - - // For use with TYPE_MESSAGE_STATUS - static STATUS_GENERIC_ERROR /int ::= 1 - static STATUS_MISSING_PARAMETER /int ::= 2 - static STATUS_METHOD_NOT_SUPPORTED /int ::= 3 - static STATUS_INVALID_PARAMETER /int ::= 4 - static STATUS_INVALID_STATE /int ::= 5 - static STATUS_NO_DATA /int ::= 6 - static STATUS_NOT_SUPPORTED /int ::= 7 - static STATUS_FAILED_WILL_RETRY /int ::= 8 - static STATUS_FAILED_PERMANENTLY /int ::= 9 - static STATUS_UNKNOWN_MESSAGE /int ::= 10 - - // For use with TYPE_MESSAGE_METHOD - static METHOD_SET /int ::= 1 - static METHOD_GET /int ::= 2 - static METHOD_SUBSCRIBE /int ::= 3 - static METHOD_DO /int ::= 4 - - messageLength_ /int := 0 - messageType_ /int := 0 - data_ /Data := Data - - constructor messageLength/int=0 messageType/int=0 data/Data=Data: - messageLength_ = messageLength - messageType_ = messageType - data_ = data - - constructor.fromHeader header/Header: - messageLength_ = header.messageLength_ - messageType_ = header.messageType_ - data_ = header.data_ - - constructor.fromList bytes/List: - // First is uint16 LE message length - messageLength_ = bytes[0] + (bytes[1] << 8) - // Second is uint16 LE message type - messageType_ = bytes[2] + (bytes[3] << 8) - // Third is data - data_ = Data.fromList bytes[4..] - - stringify -> string: - return "messageLength: " + messageLength_.stringify + ", messageType: " + messageType_.stringify + ", header data: " + data_.stringify - - messageType -> int: - return messageType_ - - size -> int: - // length is uint16, type is uint16 - return 2 + 2 + data_.size - - data -> Data: - return data_ - - bytesForProtocol -> ByteArray: - bData := data_.bytesForProtocol - - b := ByteArray 4 + bData.size - // length is uint16 LE - b[0] = messageLength_ & 0xFF - b[1] = messageLength_ >> 8 - // type is uint 16 LE - b[2] = messageType_ & 0xFF - b[3] = messageType_ >> 8 - // data - b.replace 4 bData 0 bData.size - return b \ No newline at end of file diff --git a/src/protocol/Message.toit b/src/protocol/Message.toit deleted file mode 100644 index 4cf4ed0..0000000 --- a/src/protocol/Message.toit +++ /dev/null @@ -1,112 +0,0 @@ -import crypto.crc -import io -import .Header show * -import .Data show * - -class Message: - protocolVersion_ /int := 3 - header_ /Header := Header - data_ /Data := Data - checksum_ /int := 0 - - constructor messageType/int: - header_.messageType_ = messageType - - constructor.withData messageType/int data/Data: - header_.messageType_ = messageType - data_ = data - - constructor.fromList bytes/List: - header_ = Header.fromList bytes[1..] // skip protocol version - data_ = Data.fromList bytes[1 + header_.size..] - checksum_ = (bytes[bytes.size - 1] << 8) + bytes[bytes.size - 2] - - constructor.fromMessage msg/Message: - header_ = Header.fromHeader msg.header - data_ = Data.fromData msg.data - checksum_ = msg.checksum_ - - withRandomMsgId -> Message: - randomUint32 := ( random 4_294_967_295) +1 - header.data.addDataUint32 Header.TYPE_MESSAGE_ID randomUint32 - return this - - msgId -> int?: - if header.data.hasData Header.TYPE_MESSAGE_ID: return header.data.getDataUintn Header.TYPE_MESSAGE_ID - return null - - msgType -> int: - return header_.messageType_ - - msgStatus -> int?: - if header.data.hasData Header.TYPE_MESSAGE_STATUS: return header.data.getDataIntn Header.TYPE_MESSAGE_STATUS - return null - - msgStatusIs status/int -> bool: - return header.data.hasData Header.TYPE_MESSAGE_STATUS and (header.data.getDataIntn Header.TYPE_MESSAGE_STATUS) == status - - wasForwarded -> bool: - return header_.data.hasData Header.TYPE_FORWARDED_FOR or header_.data.hasData Header.TYPE_FORWARDED_FOR_TYPE - - header -> Header: - return header_ - - type -> int: - return header_.messageType_ - - data -> Data: - return data_ - - stringify -> string: - // TODO make a much nicer stringify, including docs link, and stringified data? - return stringifyAllBytes - - stringifyAllBytes -> string: - buffer := io.Buffer - is-first := true - bytes.do: - if is-first: is-first = false - else: buffer.write ", " - buffer.write "0x$(%02x it)" - return buffer.to-string - - size -> int: - // protocol, header, data, checksum - return 1 + header_.size + data_.size +2 - - // TODO remove this duplicate method... - bytes -> ByteArray: - return bytesForProtocol - - bytesForProtocol -> ByteArray: - // first prep the message to be rendered as bytes - // set the message length in the header - header_.messageLength_ = size - - // set the checksum in the message - checksum_ = checksumCalc - - // now render the message with the checksum - return bytesEarly_ - - checksumCalc -> int: - preChcksumByteArray := bytesEarly_ - // Calculate CRC16 XMODEM over the bytes (without the last 2 which will be checksum) - checksum := crc.crc16-xmodem (preChcksumByteArray.byte-slice 0 (preChcksumByteArray.byte-size - 2)) - return checksum - - // byteListEarly_ is a byteList, without length of checksum calculated - bytesEarly_ -> ByteArray: - bHeader := header_.bytesForProtocol - bData := data_.bytesForProtocol - - b := ByteArray 1 + bHeader.size + bData.size + 2 - // first byte is protocol version - b[0] = protocolVersion_ - // then header - b.replace 1 bHeader 0 bHeader.size - // then main data - b.replace 1 + bHeader.size bData 0 bData.size - // add checksum which is uint16 LE - b.replace b.size - 2 #[checksum_ & 0xFF, checksum_ >> 8] 0 2 - return b \ No newline at end of file diff --git a/src/protocol/data-field.toit b/src/protocol/data-field.toit new file mode 100644 index 0000000..bbe6f26 --- /dev/null +++ b/src/protocol/data-field.toit @@ -0,0 +1,16 @@ +class DataField: + data-bytes_ /ByteArray := #[] + + constructor data/ByteArray=#[]: + if data.size > 255: + throw "V3 protocol can't have data field of over 255 bytes" + data-bytes_ = data + + stringify -> string: + return data-bytes_.stringify + + bytes-for-protocol -> ByteArray: + b := ByteArray data-bytes_.size + 1 + b[0] = data-bytes_.size + b.replace 1 data-bytes_ 0 data-bytes_.size + return b diff --git a/src/protocol/Data.toit b/src/protocol/data.toit similarity index 50% rename from src/protocol/Data.toit rename to src/protocol/data.toit index 3175ce0..efe2388 100644 --- a/src/protocol/Data.toit +++ b/src/protocol/data.toit @@ -1,70 +1,77 @@ -import io.byte-order show LITTLE-ENDIAN +import io show LITTLE-ENDIAN // Works as well. Both are fine. import log import coordinate show Coordinate -import .DataField show * +import .data-field show * class Data: - dataTypes_ /List := [] + data-types_ /List := [] data_ /List := [] constructor: - dataTypes_ = [] + data-types_ = [] data_ = [] - - constructor.fromData data/Data: - dataTypes_ = data.dataTypes_ + + constructor.from-data data/Data: + data-types_ = data.data-types_ data_ = data.data_ - constructor.fromList bytes/List: + constructor.from-list bytes/List: if bytes.size < 2: - throw "V3 OUT_OF_BOUNDS: Not enough bytes to read fields, expected 2 bytes but only have " + bytes.size.stringify + throw "V3 OUT_OF_BOUNDS: Not enough bytes to read fields, expected 2 bytes but only have $bytes.size" fields := (bytes[1] << 8) + bytes[0] // log.debug "Data fields: " + fields.stringify if bytes.size < 2 + fields: - throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data types, expected " + (2 + fields).stringify + " bytes but only have " + bytes.size.stringify + throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data types, expected $(2 + fields) bytes but only have $bytes.size" // read data types - for i := 0; i < fields; i++: - dataType := bytes[2 + i] - dataTypes_.add dataType - // log.debug "Data data type: " + dataType.stringify + fields.repeat: | i | + data-type := bytes[2 + i] + data-types_.add data-type + // log.debug "Data data type: " + data-type.stringify // read data (each is a uint8 length, then that number of bytes) index := 2 + fields - for i := 0; i < fields; i++: + fields.repeat: | i | if index >= bytes.size: - throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data length, expected " + index.stringify + " bytes but only have " + bytes.size.stringify + throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data length, expected $index bytes but only have $bytes.size" length := bytes[index] // log.debug "Data data length: " + length.stringify index += 1 if index + length > bytes.size: - throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data, expected " + (index + length).stringify + " bytes but only have " + bytes.size.stringify + throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data, expected $(index + length) bytes but only have $bytes.size" index += length // log.debug "Data data bytes: " + bytes[index - length..index].stringify - dataField := DataField (listToByteArray bytes[index - length..index]) + dataField := DataField (list-to-byte-array bytes[index - length..index]) data_.add dataField // log.debug "Data data: " + dataField.stringify - - constructor.fromBytes bytes/ByteArray: + + constructor.from-bytes bytes/ByteArray: + // You could also store the bytes in an io.Reader and read it from there. + // Something like + // reader := io.Reader bytes + // fields := reader.little-endian.read-uint16 + // fields.repeat: | i | + // data-type := reader.read-byte + // ... if bytes.size < 2: - throw "V3 OUT_OF_BOUNDS: Not enough bytes to read fields, expected 2 bytes but only have " + bytes.size.stringify + throw "V3 OUT_OF_BOUNDS: Not enough bytes to read fields, expected 2 bytes but only have $bytes.size" fields := LITTLE-ENDIAN.uint16 bytes 0 // log.debug "Data fields: " + fields.stringify if bytes.size < 2 + fields: - throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data types, expected " + (2 + fields).stringify + " bytes but only have " + bytes.size.stringify + throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data types, expected $(2 + fields) bytes but only have $bytes.size" // read data types - for i := 0; i < fields; i++: - dataType := bytes[2 + i] - dataTypes_.add dataType - // log.debug "Data data type: " + dataType.stringify + fields.repeat: | i | + data-type := bytes[2 + i] + data-types_.add data-type + // log.debug "Data data type: " + data-type.stringify // read data (each is a uint8 length, then that number of bytes) index := 2 + fields - for i := 0; i < fields; i++: + fields.repeat: | i | if index >= bytes.size: - throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data length, expected " + index.stringify + " bytes but only have " + bytes.size.stringify + throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data length, expected $index bytes but only have $bytes.size" length := bytes[index] // log.debug "Data data length: " + length.stringify index += 1 if index + length > bytes.size: - throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data, expected " + (index + length).stringify + " bytes but only have " + bytes.size.stringify + throw "V3 OUT_OF_BOUNDS: Not enough bytes to read data, expected $(index + length) bytes but only have $bytes.size" index += length // log.debug "Data data bytes: " + bytes[index - length..index].stringify dataField := DataField (bytes[index - length..index]) @@ -72,213 +79,216 @@ class Data: // log.debug "Data data: " + dataField.stringify stringify -> string: - s := "" - for i := 0; i < dataTypes_.size; i++: - s += dataTypes_[i].stringify + ": " + data_[i].stringify + ", " - return s + result := "" + data-types_.size.repeat: | i | + if i != 0: result += ", " + result += "$data-types_[i]: $data_[i]" + return result - addDataS dataType/int data/string -> none: - addData dataType data.to-byte-array + add-data-s data-type/int data/string -> none: + add-data data-type data.to-byte-array - addDataUint8 dataType/int data/int -> none: - addData dataType #[data] + add-data-uint8 data-type/int data/int -> none: + add-data data-type #[data] - addDataUint16 dataType/int data/int -> none: - addData dataType #[data & 0xFF, data >> 8] + add-data-uint16 data-type/int data/int -> none: + add-data data-type #[data & 0xFF, data >> 8] - addDataUint32 dataType/int data/int -> none: + add-data-uint32 data-type/int data/int -> none: b := #[0,0,0,0] LITTLE-ENDIAN.put-uint32 b 0 data - addData dataType b + add-data data-type b - addDataInt32 dataType/int data/int -> none: + add-data-int32 data-type/int data/int -> none: b := #[0,0,0,0] LITTLE-ENDIAN.put-int32 b 0 data - addData dataType b + add-data data-type b - addDataUint64 dataType/int data/int -> none: + add-data-uint64 data-type/int data/int -> none: b := #[0,0,0,0,0,0,0,0] LITTLE-ENDIAN.put-uint b 8 0 data - addData dataType b - - addDataUintn dataType/int data/int -> none: + add-data data-type b + + add-data-uintn data-type/int data/int -> none: if data < 256: - addDataUint8 dataType data + add-data-uint8 data-type data else if data < 65536: - addDataUint16 dataType data + add-data-uint16 data-type data else if data < 16777216: - addDataUint32 dataType data + add-data-uint32 data-type data else if data < 4294967296: - addDataUint32 dataType data + add-data-uint32 data-type data else if data < 1099511627776: - addDataUint64 dataType data + add-data-uint64 data-type data else: log.error "Data too large for uintn: " + data.stringify - addDataFloat dataType/int data/float -> none: + add-data-float data-type/int data/float -> none: b := #[0,0,0,0] LITTLE-ENDIAN.put-float32 b 0 data - addData dataType b + add-data data-type b - addData dataType/int data/ByteArray -> none: + add-data data-type/int data/ByteArray -> none: data_.add (DataField data) - dataTypes_.add dataType + data-types_.add data-type // TODO kill this method - addDataList dataType/int data/List -> none: - addData dataType (listToByteArray data) + add-data-list data-type/int data/List -> none: + add-data data-type (list-to-byte-array data) - hasData dataType/int -> bool: + has-data data-type/int -> bool: e := catch: - for i := 0; i < dataTypes_.size; i++: - if dataTypes_[i] == dataType: + for i := 0; i < data-types_.size; i++: + if data-types_[i] == data-type: return true return false if e: - log.warn "Failed to check for data: " + e.stringify + // Typically we have the logging message as a constant and then add + // variable data as tags. + log.warn "failed to check for data" --tags={"exception": e} return false - removeData dataType/int -> none: + remove-data data-type/int -> none: e := catch: - for i := 0; i < dataTypes_.size; i++: - if dataTypes_[i] == dataType: - dataTypes_.remove --at=i + for i := 0; i < data-types_.size; i++: + if data-types_[i] == data-type: + data-types_.remove --at=i data_.remove --at=i return if e: - log.warn "Failed to remove data: " + e.stringify - - // returns the data for the type, or an empty list if not found - // will never throw an error - getData dataType/int -> ByteArray: - e := catch: - for i := 0; i < dataTypes_.size; i++: - if dataTypes_[i] == dataType: - return data_[i].dataBytes_ + log.warn "failed to remove data" --tags={"exception": e} + + // Returns the data for the type, or an empty byte-array if not found. + // Never throws an error. + get-data data-type/int -> ByteArray: + e := catch: // Why do you need the catch? + data-types_.size.repeat: | i | + if data-types_[i] == data-type: + return data_[i].data-bytes_ return #[] if e: log.warn "Failed to get data: " + e.stringify return #[] - getDataS dataType/int -> string: - data := getData dataType + get-data-s data-type/int -> string: + data := get-data data-type return data.to-string - getAsciiData dataType/int -> string: - data := getData dataType + get-ascii-data data-type/int -> string: + data := get-data data-type e := catch: - return (_trimBadAsciiRightAndLog data).to-string + return (_trim-bad-ascii-right-and-log data).to-string if e: log.error "Failed to convert data to ascii: " + e.stringify + ": " + data.stringify return "" - _trimBadAsciiRightAndLog data/ByteArray -> ByteArray: - lastGoodIndex := 0 + _trim-bad-ascii-right-and-log data/ByteArray -> ByteArray: + last-good-index := 0 i := 0 data.do: | element | i++ if element < 32 or element > 126: log.warn "Invalid ascii data, skipping index " + i.stringify + ": " + element.stringify - lastGoodIndex = i - 1 - return data[0..lastGoodIndex] + last-good-index = i - 1 + return data[..last-good-index] - getDataUint8 dataType/int -> int: + get-data-uint8 data-type/int -> int: // TODO use LITTLE-ENDIAN? (When we have a byte array not a list?) - data := getData dataType + data := get-data data-type if data.size == 0: - log.warn "No data for datatype " + dataType.stringify + log.warn "No data for datatype " + data-type.stringify return 0 return data[0] - getDataUint16 dataType/int -> int: - return LITTLE-ENDIAN.uint16 (getData dataType) 0 + get-data-uint16 data-type/int -> int: + return LITTLE-ENDIAN.uint16 (get-data data-type) 0 - getDataUint32 dataType/int -> int: - return LITTLE-ENDIAN.uint32 (getData dataType) 0 + get-data-uint32 data-type/int -> int: + return LITTLE-ENDIAN.uint32 (get-data data-type) 0 - getDataInt32 dataType/int -> int: - return LITTLE-ENDIAN.int32 (getData dataType) 0 + get-data-int32 data-type/int -> int: + return LITTLE-ENDIAN.int32 (get-data data-type) 0 - getDataUint64 dataType/int -> int: - data := getData dataType + get-data-uint64 data-type/int -> int: + data := get-data data-type if data.size < 8: - log.warn "No data for datatype " + dataType.stringify + log.warn "No data for datatype " + data-type.stringify return 0 - return (data[7] << 56) + (data[6] << 48) + (data[5] << 40) + (data[4] << 32) + (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0] + return LITTLE-ENDIAN.int64 data 0 - addDataListUint16 dataType/int data/List -> none: + add-data-list-uint16 data-type/int data/List -> none: b := ByteArray data.size * 2 - for i := 0; i < data.size; i++: + data.size.repeat: | i | LITTLE-ENDIAN.put-uint16 b (i * 2) data[i] - addData dataType b + add-data data-type b - getDataListUint16 dataType/int -> List: - data := getData dataType + get-data-list-uint16 data-type/int -> List: + data := get-data data-type if data.size % 2 != 0: - log.error "Data size not a multiple of 2 for datatype " + dataType.stringify + log.error "Data size not a multiple of 2 for datatype " + data-type.stringify return [] l := [] for i := 0; i < data.size; i += 2: l.add (LITTLE-ENDIAN.uint16 data i) return l - - addDataListUint32 dataType/int data/List -> none: + + add-data-list-uint32 data-type/int data/List -> none: b := ByteArray data.size * 4 for i := 0; i < data.size; i++: LITTLE-ENDIAN.put-uint32 b (i * 4) data[i] - addData dataType b - - getDataListUint32 dataType/int -> List: - data := getData dataType + add-data data-type b + + get-data-list-uint32 data-type/int -> List: + data := get-data data-type if data.size % 4 != 0: - log.error "Data size not a multiple of 4 for datatype " + dataType.stringify + log.error "Data size not a multiple of 4 for datatype " + data-type.stringify return [] l := [] for i := 0; i < data.size; i += 4: l.add (LITTLE-ENDIAN.uint32 data i) return l - - addDataListInt32Pairs dataType/int data/List -> none: + + add-data-list-int32-pairs data-type/int data/List -> none: b := ByteArray data.size * 8 for i := 0; i < data.size; i++: LITTLE-ENDIAN.put-int32 b (i * 8) data[i][0] LITTLE-ENDIAN.put-int32 b ((i * 8) + 4) data[i][1] - addData dataType b - - getDataListInt32Pairs dataType/int -> List: - data := getData dataType + add-data data-type b + + get-data-list-int32-pairs data-type/int -> List: + data := get-data data-type if data.size % 8 != 0: - log.error "Data size not a multiple of 8 for datatype " + dataType.stringify + log.error "Data size not a multiple of 8 for datatype " + data-type.stringify return [] l := [] for i := 0; i < data.size; i += 8: l.add [LITTLE-ENDIAN.int32 data i, LITTLE-ENDIAN.int32 data (i + 4)] return l - getDataListCoordinates dataType/int -> List: - data := getData dataType + get-data-list-coordinates data-type/int -> List: + data := get-data data-type if data.size % 8 != 0: - log.error "Data size not a multiple of 8 for datatype " + dataType.stringify + log.error "Data size not a multiple of 8 for datatype " + data-type.stringify return [] l := [] for i := 0; i < data.size; i += 8: l.add (Coordinate ((LITTLE-ENDIAN.int32 data i) / 1e7) ((LITTLE-ENDIAN.int32 data (i + 4)) / 1e7)) return l - getDataFloat dataType/int -> float: - d := getData dataType + get-data-float data-type/int -> float: + d := get-data data-type return LITTLE-ENDIAN.float32 d 0 - getDataUintn dataType/int -> int: + get-data-uintn data-type/int -> int: // TODO use LITTLE-ENDIAN more consistently // read the data for the type, and decide which size it fits in // ie 1 bytes is unint8 // 2 bytes is uint16 // 3 bytes should be 32, as should 4 bytes // etc - data := getData dataType + data := get-data data-type if data.size == 0: - log.warn "No data for datatype " + dataType.stringify + log.warn "No data for datatype " + data-type.stringify return 0 if data.size == 1: return LITTLE-ENDIAN.uint8 data 0 @@ -300,67 +310,64 @@ class Data: log.error "Data size too large for uintn: " + data.size.stringify return 0 - getDataIntn dataType/int -> int: + get-data-intn data-type/int -> int: // read the data for the type, and decide which size it fits in // ie 1 byte is int8 // 2 bytes is int16 // 3 bytes should be 32, as should 4 bytes // etc - data := getData dataType + data := get-data data-type if data.size == 0: - log.warn "No data for datatype " + dataType.stringify - return 0 + log.warn "No data for datatype " + data-type.stringify + return 0 if data.size == 1: - return LITTLE-ENDIAN.int8 data 0 + return LITTLE-ENDIAN.int8 data 0 if data.size == 2: - return LITTLE-ENDIAN.int16 data 0 + return LITTLE-ENDIAN.int16 data 0 if data.size == 3: - return (data[2] << 16) + (data[1] << 8) + data[0] + return (data[2] << 16) + (data[1] << 8) + data[0] if data.size == 4: return LITTLE-ENDIAN.int32 data 0 if data.size == 5: - return (data[4] << 32) + (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0] + return (data[4] << 32) + (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0] if data.size == 6: - return (data[5] << 40) + (data[4] << 32) + (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0] + return (data[5] << 40) + (data[4] << 32) + (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0] if data.size == 7: - return (data[6] << 48) + (data[5] << 40) + (data[4] << 32) + (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0] + return (data[6] << 48) + (data[5] << 40) + (data[4] << 32) + (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0] if data.size == 8: - // toit doesnt actually support int64, so this will always be represented as int64 - return (data[7] << 56) + (data[6] << 48) + (data[5] << 40) + (data[4] << 32) + (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0] + // toit doesnt actually support int64, so this will always be represented as int64 + return LITTLE-ENDIAN.int64 data 0 log.error "Data size too large for intn: " + data.size.stringify return 0 size -> int: - return bytesForProtocol.size - - dataFieldCount -> int: - return dataTypes_.size - - bytesForProtocol -> ByteArray: - dataLength := 0 - for i := 0; i < dataFieldCount; i++: - dataLength += 1 + data_[i].dataBytes_.size - bLen := 2 + dataFieldCount + dataLength - - b := ByteArray bLen + return bytes-for-protocol.size + + data-field-count -> int: + return data-types_.size + + bytes-for-protocol -> ByteArray: + data-length := 0 + for i := 0; i < data-field-count; i++: + data-length += 1 + data_[i].data-bytes_.size + bytes-size := 2 + data-field-count + data-length + + b := ByteArray bytes-size // first, datafield count uint16 LE - dfc := dataFieldCount + dfc := data-field-count b[0] = dfc & 0xFF b[1] = dfc >> 8 // then data types bi := 2 - for i := 0; i < dataFieldCount; i++: - b[bi] = dataTypes_[i] + for i := 0; i < data-field-count; i++: + b[bi] = data-types_[i] bi += 1 // then data - for i := 0; i < dataFieldCount; i++: - b.replace bi data_[i].bytesForProtocol 0 (1 + data_[i].dataBytes_.size) - bi += (1 + data_[i].dataBytes_.size) + for i := 0; i < data-field-count; i++: + b.replace bi data_[i].bytes-for-protocol 0 (1 + data_[i].data-bytes_.size) + bi += (1 + data_[i].data-bytes_.size) return b -listToByteArray l/List -> ByteArray: - b := ByteArray l.size - for i := 0; i < l.size; i++: - b[i] = l[i] - return b \ No newline at end of file +list-to-byte-array l/List -> ByteArray: + return ByteArray l.size: l[it] diff --git a/src/protocol/header.toit b/src/protocol/header.toit new file mode 100644 index 0000000..cec63b8 --- /dev/null +++ b/src/protocol/header.toit @@ -0,0 +1,83 @@ +import .data show * + +class Header: + // Header data types. + static TYPE-MESSAGE-ID := 1 // ID that can be used by receiver to ACK, and client to track for various responses, or re-sends. + static TYPE-CLIENT-ID := 2 // ID of the client sending the message. + static TYPE-RESPONSE-TO-MESSAGE-ID := 3 // ID of the message that is being responded to. + static TYPE-MESSAGE-STATUS := 4 // Status of the message. If omitted, assume OK? + static TYPE-MESSAGE-METHOD := 5 // Request a service to be perform an action. + static TYPE-SUBSCRIPTION-INTERVAL := 6 // Interval in ms for a subscription to be sent. + static TYPE-FORWARDED-FOR-TYPE := 9 + static TYPE-FORWARDED-FOR := 10 // ID of the client sending the original message that is being forwarded. + static TYPE-FORWARDED-RSSI := 11 // RSSI of forwarded message. + static TYPE-FORWARDED-SNR := 12 // SNR of forwarded message. + static TYPE-FORWARD-TO-TYPE := 13 // Type of forwarded message. + static TYPE-FORWARD-TO := 14 + + // For use with TYPE_MESSAGE_STATUS. + static STATUS-GENERIC-ERROR /int ::= 1 + static STATUS-MISSING-PARAMETER /int ::= 2 + static STATUS-METHOD-NOT-SUPPORTED /int ::= 3 + static STATUS-INVALID-PARAMETER /int ::= 4 + static STATUS-INVALID-STATE /int ::= 5 + static STATUS-NO-DATA /int ::= 6 + static STATUS-NOT-SUPPORTED /int ::= 7 + static STATUS-FAILED-WILL-RETRY /int ::= 8 + static STATUS-FAILED-PERMANENTLY /int ::= 9 + static STATUS-UNKNOWN-MESSAGE /int ::= 10 + + // For use with TYPE_MESSAGE_METHOD. + static METHOD-SET /int ::= 1 + static METHOD-GET /int ::= 2 + static METHOD-SUBSCRIBE /int ::= 3 + static METHOD-DO /int ::= 4 + + message-length_ /int := 0 + message-type_ /int := 0 + data_ /Data := Data + + constructor message-length/int=0 message-type/int=0 data/Data=Data: + message-length_ = message-length + message-type_ = message-type + data_ = data + + constructor.from-header header/Header: + message-length_ = header.message-length_ + message-type_ = header.message-type_ + data_ = header.data_ + + constructor.from-list bytes/List: + // First is uint16 LE message length + message-length_ = bytes[0] + (bytes[1] << 8) + // Second is uint16 LE message type + message-type_ = bytes[2] + (bytes[3] << 8) + // Third is data + data_ = Data.from-list bytes[4..] + + stringify -> string: + return "message-length: $message-length_, message-type: $message-type_, header data: $data" + + message-type -> int: + return message-type_ + + size -> int: + // length is uint16, type is uint16 + return 2 + 2 + data_.size + + data -> Data: + return data_ + + bytes-for-protocol -> ByteArray: + bytes-data := data_.bytes-for-protocol + + b := ByteArray 4 + bytes-data.size + // length is uint16 LE + b[0] = message-length_ & 0xFF + b[1] = message-length_ >> 8 + // type is uint 16 LE + b[2] = message-type_ & 0xFF + b[3] = message-type_ >> 8 + // data + b.replace 4 bytes-data 0 bytes-data.size + return b diff --git a/src/protocol/message.toit b/src/protocol/message.toit new file mode 100644 index 0000000..06e1889 --- /dev/null +++ b/src/protocol/message.toit @@ -0,0 +1,113 @@ +import crypto.crc +import io +import .header show * +import .data show * + +class Message: + protocolVersion_ /int := 3 + header_ /Header := Header + data_ /Data := Data + checksum_ /int := 0 + + constructor message-type/int: + header_.message-type_ = message-type + + constructor.with-data message-type/int data/Data: + header_.message-type_ = message-type + data_ = data + + constructor.from-list bytes/List: + header_ = Header.from-list bytes[1..] // Skip protocol version. + data_ = Data.from-list bytes[1 + header_.size..] + checksum_ = (bytes[bytes.size - 1] << 8) + bytes[bytes.size - 2] + + constructor.from-message msg/Message: + header_ = Header.from-header msg.header + data_ = Data.from-data msg.data + checksum_ = msg.checksum_ + + with-random-msg-id -> Message: + randomUint32 := ( random 4_294_967_295) +1 + header.data.add-data-uint32 Header.TYPE-MESSAGE-ID randomUint32 + return this + + msg-id -> int?: + if header.data.has-data Header.TYPE-MESSAGE-ID: return header.data.get-data-uintn Header.TYPE-MESSAGE-ID + return null + + msg-type -> int: + return header_.message-type_ + + msg-status -> int?: + if header.data.has-data Header.TYPE-MESSAGE-STATUS: return header.data.get-data-intn Header.TYPE-MESSAGE-STATUS + return null + + msg-status-is status/int -> bool: + return header.data.has-data Header.TYPE-MESSAGE-STATUS and (header.data.get-data-intn Header.TYPE-MESSAGE-STATUS) == status + + was-forwarded -> bool: + return header_.data.has-data Header.TYPE-FORWARDED-FOR or header_.data.has-data Header.TYPE-FORWARDED-FOR-TYPE + + header -> Header: + return header_ + + type -> int: + return header_.message-type_ + + data -> Data: + return data_ + + stringify -> string: + // TODO make a much nicer stringify, including docs link, and stringified data? + return stringify-all-bytes + + stringify-all-bytes -> string: + buffer := io.Buffer + is-first := true + bytes.do: + if is-first: is-first = false + else: buffer.write ", " + buffer.write "0x$(%02x it)" + return buffer.to-string + + size -> int: + // Protocol, header, data, checksum. + return 1 + header_.size + data_.size +2 + + // TODO remove this duplicate method... + bytes -> ByteArray: + return bytes-for-protocol + + bytes-for-protocol -> ByteArray: + // First prep the message to be rendered as bytes. + // Set the message length in the header. + header_.message-length_ = size + + // Set the checksum in the message. + checksum_ = checksum-calc + + // Now render the message with the checksum. + return bytes-early_ + + checksum-calc -> int: + pre-checksum-byte-array := bytes-early_ + // Calculate CRC16 XMODEM over the bytes (without the last 2 which will be checksum). + // No need to make a copy. A view is more efficient. + checksum := crc.crc16-xmodem pre-checksum-byte-array[..pre-checksum-byte-array.size - 2] + return checksum + + // byteListEarly_ is a byteList, without length of checksum calculated + bytes-early_ -> ByteArray: + bytes-header := header_.bytes-for-protocol + bytes-data := data_.bytes-for-protocol + + b := ByteArray 1 + bytes-header.size + bytes-data.size + 2 + // First byte is protocol version. + b[0] = protocolVersion_ + // Then header. + b.replace 1 bytes-header + // Then main data. + b.replace 1 + bytes-header.size bytes-data + // Add checksum which is uint16 LE. + b.replace b.size - 2 #[checksum_ & 0xFF, checksum_ >> 8] + return b diff --git a/src/protocol/protocol.toit b/src/protocol/protocol.toit index c55d3bc..271925a 100644 --- a/src/protocol/protocol.toit +++ b/src/protocol/protocol.toit @@ -1,10 +1,6 @@ -import .Data show Data -import .DataField show DataField -import .Header show Header -import .Message show Message +import .data show Data +import .data-field show DataField +import .header show Header +import .message show Message -export Data -export DataField -export Header -export Message -export * \ No newline at end of file +export * diff --git a/src/services/comms.toit b/src/services/comms.toit index 17f5943..bde8585 100644 --- a/src/services/comms.toit +++ b/src/services/comms.toit @@ -6,7 +6,7 @@ import ..messages as messages import ..util.docs show messageBytesToDocsURL import ..util.resilience show catchAndRestart import ..util.idgen show IdGenerator RandomIdGenerator SequentialIdGenerator -import ..util.bytes show stringifyAllBytes byteArrayToList +import ..util.bytes show stringifyAllBytes byte-array-to-list import io.reader show Reader import io.writer show Writer import encoding.url @@ -17,7 +17,7 @@ import monitor show Channel class Comms: device_ /devices.Device - msgIdGenerator /IdGenerator + msg-idGenerator /IdGenerator outbox_ /Channel @@ -40,9 +40,9 @@ class Comms: device_ = device if idGenerator == null: - msgIdGenerator = RandomIdGenerator --lowerBound=1 --upperBound=4_294_967_295 // uint32 max + msg-idGenerator = RandomIdGenerator --lowerBound=1 --upperBound=int.MAX-U32 else: - msgIdGenerator = idGenerator + msg-idGenerator = idGenerator // TODO add into the toit LB package if devices need sync bytes or not? // LBSyncBytes_ = #[0x4c, 0x42] @@ -51,7 +51,7 @@ class Comms: // Start with randomish numbers for msg and page id, incase we restarted but the STM didn't // TODO allow optional injection of an outbox?! outbox_ = Channel 15 - + start_ start_: @@ -61,8 +61,10 @@ class Comms: task:: catchAndRestart "processAwaitTimeouts_" (:: processAwaitTimeouts) log.info "Comms started" - // Creates or gets an inbox by name - // A single inbox will only deliver messages once + /** + Creates or gets an inbox by name. + A single inbox will only deliver messages once. + */ inbox name/string --size/int? = 15 -> Channel: if not inboxesByName.contains name: log.info "Created new inbox " + name @@ -77,38 +79,42 @@ class Comms: yield while not device_.in.try-ensure-buffered 1: + // Why these loops? + // Can't you just `device_.in_.peek-byte` ? + // If that doesn't work, add a comment why not. It should be more efficient, + // since it doesn't do busy waiting. log.debug "Inbound reader waiting for 1 byte" yield - - // Look for the next byte that is 3, which could indicate our protocol version + + // Look for the next byte that is 3, which could indicate our protocol version. if device_.in.peek-byte != 3: - // if we don't find a 3, we can skip this byte + // If we don't find a 3, we can skip this byte. device_.in.read-byte continue - // Wait for a total of 3 bytes, which would also give us the length + // Wait for a total of 3 bytes, which would also give us the length. while not device_.in.try-ensure-buffered 3: log.debug "Inbound reader waiting for 3 bytes" yield - // Peek all 3 bytes, which is protocol + message length + // Peek all 3 bytes, which is protocol + message length. b3 := device_.in.peek-bytes 3 - // last to bytes of b3 are the uint16 LE message length - messageLength := (b3[2] << 8) + b3[1] - // If the msgLength looks too long (over 1000, just advance, as its probably garbage) - if messageLength > 1000: - log.error "Message length probably too long, skipping: " + messageLength.stringify + // Last two bytes of b3 are the uint16 LE message length. + message-length := (b3[2] << 8) + b3[1] + // If the msgLength looks too long (over 1000, just advance, as its probably garbage). + if message-length > 1000: + log.error "Message length probably too long, skipping: " + message-length.stringify device_.in.read-byte continue - // Try and make sure that we have enough bytes buffered to read the full potential message - while not device_.in.try-ensure-buffered messageLength: - log.debug "Inbound reader waiting for message length: " + messageLength.stringify + // Try and make sure that we have enough bytes buffered to read the full potential message. + while not device_.in.try-ensure-buffered message-length: + log.debug "Inbound reader waiting for message length: " + message-length.stringify yield - messageBytes := device_.in.peek-bytes messageLength + messageBytes := device_.in.peek-bytes message-length // TODO remove once we are sure its good? - if messageBytes.size != messageLength: // Fail safe, but shouldn't happen due to the try-enure-buffered above + if messageBytes.size != message-length: // Fail safe, but shouldn't happen due to the try-enure-buffered above. log.error "Message length mismatch, no more bytes availible? skipping message" device_.in.read-byte throw "Message length mismatch, no more bytes availible? skipping message" @@ -116,38 +122,38 @@ class Comms: e := catch --trace: // Extract the expected checksum from the last 2 bytes of the message (LE) - expectedChecksumBytes := [messageBytes[messageLength - 2], messageBytes[messageLength - 1]] + expectedChecksumBytes := [messageBytes[message-length - 2], messageBytes[message-length - 1]] // And parse it as a protocol.Message - v3 := protocol.Message.fromList ( byteArrayToList messageBytes ) + v3 := protocol.Message.from-list ( byte-array-to-list messageBytes ) // Calculate the checksum of the message data - calculatedChecksum := v3.checksumCalc + calculatedChecksum := v3.checksum-calc // calculatedChecksumBytes is LE uint16 of calculatedChecksum calculatedChecksumBytes := [calculatedChecksum & 0xFF, calculatedChecksum >> 8] // if they match, we have a message, return it if expectedChecksumBytes == calculatedChecksumBytes: // read the bytes we peeked - device_.in.read-bytes messageLength + device_.in.read-bytes message-length processReceivedMessage_ v3 else: log.error "Checksum mismatch, skipping message" - // Read a byte, and continue looking for a message + // Read a byte, and continue looking for a message. device_.in.read-byte if e: // output a row of red cross emojis log.error " ❌ " * 20 log.error "Error parsing message (probably garbeled): " + e.stringify + " " + ( stringifyAllBytes messageBytes) log.error " ❌ " * 20 - // Read a byte, and continue looking for a message + // Read a byte, and continue looking for a message. device_.in.read-byte processReceivedMessage_ msg/protocol.Message: log.debug "RCV msg type " + msg.type.stringify + " : " + msg.stringify + " " + ( messageBytesToDocsURL msg.bytes ) - // Add to any registered inboxes + // Add to any registered inboxes. inboxesByName.do --values=true: | inbox | if inbox.size >= inbox.capacity: dropped := inbox.receive @@ -155,13 +161,13 @@ class Comms: inbox.send msg yield // on each inbox population - // Process awaiting lambdas + // Process awaiting lambdas. // TODO possibly have a timeout for the age of waiting for a response? - // That could be its own lambda to act on, but also remove the lambdas from the list - if msg.header.data.hasData protocol.Header.TYPE-RESPONSE-TO-MESSAGE-ID: - respondingTo := msg.header.data.getDataUintn protocol.Header.TYPE-RESPONSE-TO-MESSAGE-ID - isAck := msg.header.messageType == messages.MSGTYPE_GENERAL_ACK // Otherwise it is a response - isBad := msg.header.data.hasData protocol.Header.TYPE-MESSAGE-STATUS and msg.msgStatus > 0 + // That could be its own lambda to act on, but also remove the lambdas from the list. + if msg.header.data.has-data protocol.Header.TYPE-RESPONSE-TO-MESSAGE-ID: + respondingTo := msg.header.data.get-data-uintn protocol.Header.TYPE-RESPONSE-TO-MESSAGE-ID + isAck := msg.header.message-type == messages.MSGTYPE-GENERAL-ACK // Otherwise it is a response + isBad := msg.header.data.has-data protocol.Header.TYPE-MESSAGE-STATUS and msg.msg-status > 0 if isAck: if isBad: if lambdasForBadAck.contains respondingTo: @@ -189,8 +195,8 @@ class Comms: lambdasForBadResponse.remove respondingTo lambdasForGoodResponse.remove respondingTo // If there are no waiting lambdas for the msg id, then latchForMessage can be released - if (lambdasForBadAck.contains respondingTo) == false and (lambdasForGoodAck.contains respondingTo) == false and (lambdasForBadResponse.contains respondingTo) == false and (lambdasForGoodResponse.contains respondingTo) == false: - // Remove remaining timeouts, and release the latch + if not lambdasForBadAck.contains respondingTo and not lambdasForGoodAck.contains respondingTo and not lambdasForBadResponse.contains respondingTo and not lambdasForGoodResponse.contains respondingTo: + // Remove remaining timeouts, and release the latch. if waitTimeouts.contains respondingTo: waitTimeouts.remove respondingTo if latchForMessage.contains respondingTo: @@ -212,34 +218,34 @@ class Comms: --onResponse/Lambda? = null --onError/Lambda? = null --timeout/Duration = (Duration --s=60) -> monitor.Latch: - log.debug "Sending (and) message: " + msg.stringify + " " + ( messageBytesToDocsURL msg.bytes ) - + log.debug "Sending (and) message: " + msg.stringify + " " + (messageBytesToDocsURL msg.bytes) + // Ensure the message has a known message id - if not (msg.header.data.hasData protocol.Header.TYPE-MESSAGE-ID): - msg.header.data.addDataUint32 protocol.Header.TYPE-MESSAGE-ID msgIdGenerator.next + if not (msg.header.data.has-data protocol.Header.TYPE-MESSAGE-ID): + msg.header.data.add-data-uint32 protocol.Header.TYPE-MESSAGE-ID msg-idGenerator.next latch := monitor.Latch - - // add the lambdas (if set) + + // Add the lambdas (if set). // TODO add a timeout for ack and or response too! shouldTrack := false if onAck != null: - lambdasForGoodAck[msg.msgId] = onAck + lambdasForGoodAck[msg.msg-id] = onAck shouldTrack = true if onNack != null: - lambdasForBadAck[msg.msgId] = onNack + lambdasForBadAck[msg.msg-id] = onNack shouldTrack = true if onResponse != null: - lambdasForGoodResponse[msg.msgId] = onResponse + lambdasForGoodResponse[msg.msg-id] = onResponse shouldTrack = true if onError != null: - lambdasForBadResponse[msg.msgId] = onError + lambdasForBadResponse[msg.msg-id] = onError shouldTrack = true if shouldTrack: - latchForMessage[msg.msgId] = latch - waitTimeouts[msg.msgId] = Time.now + timeout - + latchForMessage[msg.msg-id] = latch + waitTimeouts[msg.msg-id] = Time.now + timeout + if preSend != null: preSend.call msg @@ -255,8 +261,8 @@ class Comms: // TODO don't call this on both the outbox and regular paths, as it is messy if now: // Ensure the message has a known message id - if not (msg.header.data.hasData protocol.Header.TYPE-MESSAGE-ID): - msg.header.data.addDataUint32 protocol.Header.TYPE-MESSAGE-ID msgIdGenerator.next + if not (msg.header.data.has-data protocol.Header.TYPE-MESSAGE-ID): + msg.header.data.add-data-uint32 protocol.Header.TYPE-MESSAGE-ID msg-idGenerator.next // TODO only send sync bytes on UART? m := LBSyncBytes_ + msg.bytes @@ -288,7 +294,7 @@ class Comms: yield sleep TimeoutCheckEvery_ waitTimeouts.do --keys=true: | key | - durationSinceTimeout := (waitTimeouts[key].to Time.now) + durationSinceTimeout := Duration.since waitTimeouts[key].to if (durationSinceTimeout > (Duration --s=0)): log.debug "Timeout for message: " + key.stringify + " expired " + durationSinceTimeout.stringify + " ago" // Remove the timeout key, complete the latch, and remove all callbacks?! diff --git a/src/util/bytes.toit b/src/util/bytes.toit index 71dd48c..5100d3e 100644 --- a/src/util/bytes.toit +++ b/src/util/bytes.toit @@ -9,7 +9,7 @@ stringifyAllBytes bytes/ByteArray -> string: buffer.write "0x$(%02x it)" return buffer.to-string -byteArrayToList ba/ByteArray -> List: +byte-array-to-list ba/ByteArray -> List: l := [] ba.do: | byte | l.add byte diff --git a/tests/messages/V3Data-classes.test.toit b/tests/messages/V3Data-classes.test.toit index cd1e591..0753caa 100644 --- a/tests/messages/V3Data-classes.test.toit +++ b/tests/messages/V3Data-classes.test.toit @@ -9,5 +9,5 @@ testConstructor: // Just make a location // http://docs-next.lightbug.io/devices/api/parse?bytes=03%2C%200x4f%2C%200x00%2C%200x0f%2C%200x00%2C%200x02%2C%200x00%2C%200x01%2C%200x02%2C%200x04%2C%200x1b%2C%200x01%2C%200x00%2C%200x00%2C%200x08%2C%200x56%2C%200x99%2C%200x93%2C%200x00%2C%200x00%2C%200x00%2C%200x00%2C%200x00%2C%200x0b%2C%200x00%2C%200x01%2C%200x02%2C%200x03%2C%200x04%2C%200x05%2C%200x06%2C%200x07%2C%200x08%2C%200x09%2C%200x0a%2C%200x0b%2C%200x08%2C%200x78%2C%200x87%2C%200xad%2C%200x0c%2C%200x00%2C%200x00%2C%200x00%2C%200x00%2C%200x04%2C%200xd8%2C%200xa7%2C%200xad%2C%200x1e%2C%200x04%2C%200x5f%2C%200x33%2C%200x7c%2C%200xfe%2C%200x04%2C%200xf2%2C%200xe8%2C%200x01%2C%200x00%2C%200x02%2C%200x69%2C%200x03%2C%200x02%2C%200x17%2C%200x70%2C%200x02%2C%200x02%2C%200x00%2C%200x01%2C%200x1b%2C%200x01%2C%200x00%2C%200x01%2C%200x03%2C%200x01%2C%200x01%2C%200xf6%2C%200x75 b := #[0X0B, 0X00, 0X01, 0X02, 0X03, 0X04, 0X05, 0X06, 0X07, 0X08, 0X09, 0X0A, 0X0B, 0X08, 0X78, 0X87, 0XAD, 0X0C, 0X00, 0X00, 0X00, 0X00, 0X04, 0XD8, 0XA7, 0XAD, 0X1E, 0X04, 0X5F, 0X33, 0X7C, 0XFE, 0X04, 0XF2, 0XE8, 0X01, 0X00, 0X02, 0X69, 0X03, 0X02, 0X17, 0X70, 0X02, 0X02, 0X00, 0X01, 0X1B, 0X01, 0X00, 0X01, 0X03, 0X01, 0X01] - LastPositionData.fromData (protocol.Data.fromBytes b) + LastPositionData.from-data (protocol.Data.fromBytes b) log.info "✅ Passed creating LastPositionData from Data" \ No newline at end of file diff --git a/tests/protocol/Data.test.toit b/tests/protocol/Data.test.toit index 66fb081..7584600 100644 --- a/tests/protocol/Data.test.toit +++ b/tests/protocol/Data.test.toit @@ -4,7 +4,7 @@ import log main: addAndGetData fromBytes - fromList + from-list toBytes fromBytes: @@ -29,23 +29,23 @@ fromBytes: else: log.info "✅ Passed getting dataFieldCount of 2 data fields" -fromList: +from-list: // no data fileds - d := Data.fromList [0x00, 0x00] + d := Data.from-list [0x00, 0x00] if d.dataFieldCount != 0: log.error "❌ LIST Failed getting dataFieldCount of empty data, got: " + d.dataFieldCount.stringify else: log.info "✅ LIST Passed getting dataFieldCount of empty data" // a single uint8 of type 1 value 2 - d = Data.fromList [0x01, 0x00, 0x01, 0x01, 0x02] + d = Data.from-list [0x01, 0x00, 0x01, 0x01, 0x02] if d.dataFieldCount != 1: log.error "❌ LIST Failed getting dataFieldCount of single data field, got: " + d.dataFieldCount.stringify else: log.info "✅ LIST Passed getting dataFieldCount of single data field" // uint8 of type 1 value 2, and 2 bytes of type 5, value 8 8 - d = Data.fromList [0x02, 0x00, 0x01, 0x05, 0x01, 0x02, 0x02, 0x08, 0x08] + d = Data.from-list [0x02, 0x00, 0x01, 0x05, 0x01, 0x02, 0x02, 0x08, 0x08] if d.dataFieldCount != 2: log.error "❌ LIST Failed getting dataFieldCount of 2 data fields, got: " + d.dataFieldCount.stringify else: @@ -53,15 +53,15 @@ fromList: toBytes: d := Data - d.addDataUint8 1 6 - b := d.bytesForProtocol + d.add-data-uint8 1 6 + b := d.bytes-for-protocol if b != #[0x01, 0x00, 0x01, 0x01, 0x06]: log.error "❌ Failed toBytes single uint8, got: $b" else: log.info "✅ Passed toBytes single uint8" d.addData 5 #[8, 8] - b = d.bytesForProtocol + b = d.bytes-for-protocol if b != #[0x02, 0x00, 0x01, 0x05, 0x01, 0x06, 0x02, 0x08, 0x08]: log.error "❌ Failed toBytes 2 data fields, got: $b" else: @@ -74,7 +74,7 @@ addAndGetData: //////////////// // Test uint32 - d.addDataUint32 1 290 + d.add-data-uint32 1 290 if (d.getData 1) != #[0x22, 0x01, 0x00, 0x00]: log.error "❌ Failed uint32 byte representation" else: @@ -101,8 +101,8 @@ addAndGetData: //////////////// // Test uint32 roundtrip - d.addDataUint32 4 290 - if (d.getDataUint32 4) != 290: + d.add-data-uint32 4 290 + if (d.get-data-uint32 4) != 290: log.error "❌ Failed uint32 roundtrip" else: log.info "✅ Passed uint32 roundtrip" diff --git a/tests/protocol/DataField.test.toit b/tests/protocol/DataField.test.toit index 80233e9..18b9f2e 100644 --- a/tests/protocol/DataField.test.toit +++ b/tests/protocol/DataField.test.toit @@ -2,18 +2,18 @@ import lightbug.protocol show * import log main: - name := "bytesForProtocol: no bytes still has length" + name := "bytes-for-protocol: no bytes still has length" d := DataField - got := d.bytesForProtocol + got := d.bytes-for-protocol want := #[0x00] if got != want: log.error "❌ " + name + ": Wanted " + want.stringify + " got " + got.stringify else: log.info "✅ " + name + ": Got " + got.stringify - name = "bytesForProtocol: one byte" + name = "bytes-for-protocol: one byte" d = DataField #[0x50] - got = d.bytesForProtocol + got = d.bytes-for-protocol want = #[0x01, 0x50] if got != want: log.error "❌ " + name + ": Wanted " + want.stringify + " got " + got.stringify diff --git a/tests/protocol/Message.test.toit b/tests/protocol/Message.test.toit index 37b1532..e27d971 100644 --- a/tests/protocol/Message.test.toit +++ b/tests/protocol/Message.test.toit @@ -3,6 +3,6 @@ import log main: bl := [0x03, 0x28, 0x00, 0x05, 0x00, 0x04, 0x00, 0x03, 0x04, 0x01, 0x02, 0x04, 0xe0, 0x7e, 0x7e, 0x87, 0x01, 0x00, 0x04, 0x58, 0x00, 0x00, 0x00, 0x08, 0x56, 0x99, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x0b, 0x00, 0xc9, 0x35] - m := Message.fromList bl + m := Message.from-list bl // If we didnt error, count it as passed :) - log.info "✅ Passed fromList (as no error)" \ No newline at end of file + log.info "✅ Passed from-list (as no error)" \ No newline at end of file