diff --git a/pkg/bluetooth/bluetooth.go b/pkg/bluetooth/bluetooth.go index 10ac48f..a5fb7d4 100644 --- a/pkg/bluetooth/bluetooth.go +++ b/pkg/bluetooth/bluetooth.go @@ -333,7 +333,7 @@ func (b *Ble) expectCommand(expected Packet) { func (b *Ble) writeMessage(msg *message.Message) { var buf bytes.Buffer - var index byte = 0 + var index = 0 b.WriteCmd(CmdRTS) b.expectCommand(CmdCTS) // TODO figure out what to do if !CTS @@ -344,7 +344,7 @@ func (b *Ble) writeMessage(msg *message.Message) { log.Tracef("pkg bluetooth; Sending message: %x", bytes) sum := crc32.ChecksumIEEE(bytes) if len(bytes) <= 18 { - buf.WriteByte(index) // index + buf.WriteByte(byte(index)) buf.WriteByte(0) // fragments buf.WriteByte(byte(sum >> 24)) @@ -360,7 +360,7 @@ func (b *Ble) writeMessage(msg *message.Message) { b.writeDataBuffer(&buf) if len(bytes) > 14 { - buf.WriteByte(index) + buf.WriteByte(byte(index)) buf.WriteByte(byte(len(bytes) - 14)) buf.Write(bytes[14:]) b.writeDataBuffer(&buf) @@ -369,16 +369,16 @@ func (b *Ble) writeMessage(msg *message.Message) { } size := len(bytes) - fullFragments := (byte)((size - 18) / 19) - rest := (byte)((size - (int(fullFragments) * 19)) - 18) - buf.WriteByte(index) - buf.WriteByte(fullFragments + 1) + fullFragments := (size - 18) / 19 + rest := (size - (fullFragments * 19)) - 18 + buf.WriteByte(byte(index)) + buf.WriteByte(byte(fullFragments + 1)) buf.Write(bytes[:18]) b.writeDataBuffer(&buf) for index = 1; index <= fullFragments; index++ { - buf.WriteByte(index) + buf.WriteByte(byte(index)) if index == 1 { buf.Write(bytes[18:37]) } else { @@ -387,8 +387,8 @@ func (b *Ble) writeMessage(msg *message.Message) { b.writeDataBuffer(&buf) } - buf.WriteByte(index) - buf.WriteByte(rest) + buf.WriteByte(byte(index)) + buf.WriteByte(byte(rest)) buf.WriteByte(byte(sum >> 24)) buf.WriteByte(byte(sum >> 16)) buf.WriteByte(byte(sum >> 8)) @@ -401,8 +401,8 @@ func (b *Ble) writeMessage(msg *message.Message) { b.writeDataBuffer(&buf) if rest > 14 { index++ - buf.WriteByte(index) - buf.WriteByte(rest - 14) + buf.WriteByte(byte(index)) + buf.WriteByte(byte(rest - 14)) buf.Write(bytes[fullFragments*19+18+14:]) for buf.Len() < 20 { buf.WriteByte(0) diff --git a/pkg/command/getstatus.go b/pkg/command/getstatus.go index bc0cb18..ca69c83 100644 --- a/pkg/command/getstatus.go +++ b/pkg/command/getstatus.go @@ -26,9 +26,12 @@ func (g *GetStatus) GetSeq() uint8 { } func (g *GetStatus) IsResponseHardcoded() bool { - if g.RequestType == 0 || g.RequestType == 7 || g.RequestType == 2 { + if g.RequestType == 0 || g.RequestType == 1 || g.RequestType == 2 || + g.RequestType == 3 || g.RequestType == 5 || g.RequestType == 7 { + // These status types all return dynamic information based on changing pod values return false } else { + // 0x46, 0x50 & 0x51 and the Nack response for other request types are all hardcoded values return true } } @@ -40,9 +43,7 @@ func (g *GetStatus) DoesMutatePodState() bool { // TODO remove this once all other message types return something other than // Hardcoded for GetResponseType() func (g *GetStatus) GetResponse() (response.Response, error) { - if g.RequestType == 0x2 { - return &response.DetailedStatusResponse{}, nil - } else if g.RequestType == 0x46 { + if g.RequestType == 0x46 { return &response.Type46StatusResponse{}, nil } else if g.RequestType == 0x50 { return &response.Type50StatusResponse{}, nil diff --git a/pkg/command/programalerts.go b/pkg/command/programalerts.go index 20e3408..e13a391 100644 --- a/pkg/command/programalerts.go +++ b/pkg/command/programalerts.go @@ -6,14 +6,30 @@ import ( ) type ProgramAlerts struct { - Seq uint8 - ID []byte + Seq uint8 + ID []byte + AlertMask uint8 } func UnmarshalProgramAlerts(data []byte) (*ProgramAlerts, error) { ret := &ProgramAlerts{} - // TODO deserialize this command log.Debugf("ProgramAlerts, 0x19, received, data %x", data) + + // 19 LL NNNNNNNN IVXX YYYY 0J0K IVXX YYYY 0J0K IVXX YYYY 0J0K IVXX YYYY 0J0K 11 05 NNNNNNNN MM + // 1c 494e532e 2800 125e 060f 3800 0b56 030f 4c00 01ea 010f 79a4 10ba 050f 11 05 494e532e ff + // 0 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 1920 2122 2324 2526 2728 + const bytesPerAlert = 6 + const offsetAlert0 = 5 + ret.AlertMask = 0 + var nAlerts = int((data[0] + 1 - offsetAlert0) / bytesPerAlert) + for i := 0; i < nAlerts; i++ { + // IVXX = 0iiiabcx xxxxxxxx, extract 3 bit iii value and + // turn into mask to be used to clear any triggered alerts. + // We don't (yet) emulate alert triggers based on alert programming though. + var alertNum = (data[offsetAlert0 + (i * bytesPerAlert)] & 0x70) >> 4 + ret.AlertMask |= (1 << alertNum) + } + log.Debugf("ProgramAlerts, AlertMask 0x%x", ret.AlertMask) return ret, nil } diff --git a/pkg/pod/pod.go b/pkg/pod/pod.go index 25fd6c5..35013a1 100644 --- a/pkg/pod/pod.go +++ b/pkg/pod/pod.go @@ -106,7 +106,7 @@ func (p *Pod) StartActivation() { pair := &pair.Pair{} msg, _ := p.ble.ReadMessage() if err := pair.ParseSP1SP2(msg); err != nil { - log.Fatalf("pkg pod; pkg pod; error parsing SP1SP2 %s", err) + log.Fatalf("pkg pod; error parsing SP1SP2 %s", err) } // read PDM public key and nonce msg, _ = p.ble.ReadMessage() @@ -342,7 +342,7 @@ func (p *Pod) makeGeneralStatusResponse() response.Response { var now = time.Now() - return &response.GeneralStatusResponse{ + return &response.GeneralStatusResponse { LastProgSeqNum: p.state.LastProgSeqNum, Reservoir: p.state.Reservoir, Alerts: p.state.ActiveAlertSlots, @@ -361,7 +361,7 @@ func (p *Pod) makeDetailedStatusResponse() response.Response { var now = time.Now() - return &response.DetailedStatusResponse{ + return &response.DetailedStatusResponse { LastProgSeqNum: p.state.LastProgSeqNum, Reservoir: p.state.Reservoir, Alerts: p.state.ActiveAlertSlots, @@ -378,29 +378,120 @@ func (p *Pod) makeDetailedStatusResponse() response.Response { } } +func (p *Pod) makeType1StatusResponse() response.Response { + + return &response.Type1StatusResponse { + TriggeredAlerts: p.state.TriggerTimes, + } +} + +func (p *Pod) makeType3StatusResponse() response.Response { + + return &response.Type3StatusResponse { + FaultEvent: p.state.FaultEvent, + FaultEventTime: p.state.FaultTime, + MinutesActive: p.state.MinutesActive(), + } +} + +func (p *Pod) makeType5StatusResponse() response.Response { + + var activationTime = p.state.ActivationTime + + return &response.Type5StatusResponse { + FaultEvent: p.state.FaultEvent, + FaultEventTime: p.state.FaultTime, + Year: uint8(activationTime.Year() - 2000), + Month: uint8(activationTime.Month()), + Day: uint8(activationTime.Day()), + Hour: uint8(activationTime.Hour()), + Minute: uint8(activationTime.Minute()), + } +} + func (p *Pod) getResponse(cmd command.Command) response.Response { var rsp response.Response - // If explicit request for detail, or we have a fault, return detail status. - getStatus, ok := cmd.(*command.GetStatus) - if (ok && getStatus.RequestType == 2) || p.state.FaultEvent != 0 { - rsp = p.makeDetailedStatusResponse() + getStatus, isStatusRequest := cmd.(*command.GetStatus) + if !isStatusRequest || getStatus.RequestType == 0 { + // Not a get status command or a type 0 get status + if p.state.FaultEvent == 0 { + // Pod is not faulted, return a general status response + rsp = p.makeGeneralStatusResponse() + } else { + // Pod is faulted, return a detailed status response + rsp = p.makeDetailedStatusResponse() + } } else { - rsp = p.makeGeneralStatusResponse() + // Return the requested status type independent of the pod fault state + switch getStatus.RequestType { + case 1: + rsp = p.makeType1StatusResponse() + case 2: + rsp = p.makeDetailedStatusResponse() + case 3: + rsp = p.makeType3StatusResponse() + case 5: + rsp = p.makeType5StatusResponse() + default: + // Includes 0x46, 0x50, 0x51 and the nack responses that are all hardcoded + log.Fatal("pkg pod; getStatus: unexpected type 0x%x", getStatus.RequestType) + } } + return rsp } +// clear the alert bit mask and the trigger times array for alerts in the mask +func (p *Pod) clearAlerts(alertMask uint8) { + p.state.ActiveAlertSlots = p.state.ActiveAlertSlots &^ alertMask + + for i := 0; i < 8; i++ { + if ((1 << i) & alertMask) != 0 { + p.state.TriggerTimes[i] = 0 + } + } +} + func (p *Pod) handleCommand(cmd command.Command) { if crashBeforeProcessingCommand && cmd.DoesMutatePodState() { log.Fatalf("pkg pod; Crashing before processing command with sequence %d", cmd.GetSeq()) } + switch c := cmd.(type) { - case *command.GetVersion: + case *command.GetVersion: // 0x03 p.state.PodProgress = response.PodProgressReminderInitialized - case *command.SetUniqueID: + + case *command.SetUniqueID: // 0x07 p.state.PodProgress = response.PodProgressPairingCompleted - case *command.ProgramInsulin: + + case *command.GetStatus: // 0x0E + now := time.Now() + if p.state.PodProgress == response.PodProgressPriming { + // if enough time has passed for priming to finish, advance PodProgress + if p.state.BolusEnd.Before(now) { + log.Infof("*** Advancing progress to PodProgressPrimingCompleted as prime bolus has ended") + p.state.PodProgress = response.PodProgressPrimingCompleted + } + } + if p.state.PodProgress == response.PodProgressInsertingCannula && !p.state.BolusEnd.After(now) { + // if enough time has passed for cannula insert bolus to finish, advance PodProgress + if p.state.BolusEnd.Before(now) { + log.Infof("*** Advancing progress to PodProgressRunningAbove50U as cannula insert bolus has ended") + p.state.PodProgress = response.PodProgressRunningAbove50U + } + } + + case *command.SilenceAlerts: // 0x11 + // clears the ActiveAlertSlots bits and Trigger Times for the specified alerts + p.clearAlerts(c.AlertMask) + + case *command.ProgramAlerts: // 0x19 + // For now just clears the ActiveAlertSlots bits and Trigger Times for alerts being programmed + // Later could add code to manage timers for configured alerts to make the sim more pod-like. + p.clearAlerts(c.AlertMask) + + case *command.ProgramInsulin: // 0x1A log.Debugf("pkg pod; ProgramInsulin: PodProgress = %d", p.state.PodProgress) if p.state.PodProgress < response.PodProgressPriming { @@ -438,14 +529,7 @@ func (p *Pod) handleCommand(cmd command.Command) { } } - case *command.GetStatus: - if p.state.PodProgress == response.PodProgressPriming { - p.state.PodProgress = response.PodProgressPrimingCompleted - } - if p.state.PodProgress == response.PodProgressInsertingCannula { - p.state.PodProgress = response.PodProgressRunningAbove50U - } - case *command.StopDelivery: + case *command.StopDelivery: // 0x1F if c.StopBolus { p.state.ExtendedBolusActive = false } @@ -455,11 +539,11 @@ func (p *Pod) handleCommand(cmd command.Command) { if c.StopBasal { p.state.BasalActive = false } - case *command.SilenceAlerts: - p.state.ActiveAlertSlots = p.state.ActiveAlertSlots &^ c.AlertMask - default: + + default: // includes 0x08, 0x1C, 0x1E // No action } + if cmd.DoesMutatePodState() { seq := cmd.GetSeq() log.Debugf("pkg pod; Updating LastProgSeqNum = %d", seq) @@ -481,6 +565,15 @@ func (p *Pod) SetReservoir(newVal float32) { func (p *Pod) SetAlerts(newVal uint8) { p.mtx.Lock() p.state.ActiveAlertSlots = newVal + + // Save the current pod time in alert trigger + // time array for any alerts slots going active + var podTime = p.state.MinutesActive() + for i := 0; i < 8; i++ { + if ((1 << i) & newVal) != 0 { + p.state.TriggerTimes[i] = podTime + } + } p.state.Save() p.mtx.Unlock() } diff --git a/pkg/pod/state.go b/pkg/pod/state.go index 979668d..ce01e0f 100644 --- a/pkg/pod/state.go +++ b/pkg/pod/state.go @@ -34,6 +34,8 @@ type PODState struct { FaultTime uint16 `toml:"fault_time"` Delivered uint16 `toml:"delivered"` + TriggerTimes [8]uint16 `toml:"trigger_times"` + // At some point these could be replaced with details // of each kind of delivery (volume, start time, schedule, etc) BolusEnd time.Time `toml:"bolus_end"` diff --git a/pkg/response/type1statusresponse.go b/pkg/response/type1statusresponse.go new file mode 100644 index 0000000..ba7061b --- /dev/null +++ b/pkg/response/type1statusresponse.go @@ -0,0 +1,23 @@ +package response + +import ( + "encoding/hex" +) + +type Type1StatusResponse struct { + TriggeredAlerts [8]uint16 +} + +// CMD 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 1920 +// 02 13 01 XXXX VVVV VVVV VVVV VVVV VVVV VVVV VVVV VVVV + +func (r *Type1StatusResponse) Marshal() ([]byte, error) { + + response, _ := hex.DecodeString("021301000000000000000000000000000000000000") + + for i := 0; i < 8; i++ { + response[(2 * i) + 5] = byte(r.TriggeredAlerts[i] >> 8) + response[(2 * i) + 6] = byte(r.TriggeredAlerts[i] & 0xff) + } + return response, nil +} diff --git a/pkg/response/type3statusresponse.go b/pkg/response/type3statusresponse.go new file mode 100644 index 0000000..8570742 --- /dev/null +++ b/pkg/response/type3statusresponse.go @@ -0,0 +1,31 @@ +package response + +import ( + "encoding/hex" +) + +type Type3StatusResponse struct { + FaultEvent uint8 + FaultEventTime uint16 + MinutesActive uint16 +} + +// OFF 1 2 3 4 5 6 7 8 9 10 +// 02 LL 03 PP QQQQ SSSS 04 3c XXXXXXXX ... + +func (r *Type3StatusResponse) Marshal() ([]byte, error) { + response, _ := hex.DecodeString("02f8030000000d4c043c287331002d733b003072320035733b00387232003d733b004072320045723d00487232004d723c005072330055723e00587235805d733d806073348001713e800450338009503c800c50338011513b801475328019723a801c72328021723b002472330029733f002c7334003172400034723500397240003c733400417241004473340049733e004c73330051733d005473330059733f805c73348061713e800074338005723d80087233800d723d801073338015733c80187334801d723d802073340025724000287336002d7241003072370035734100387337003d7243004072380045724400487238004d714300") + + // Fault PP + response[3] = r.FaultEvent + + // Fault Time QQQQ + response[4] = byte(r.FaultEventTime >> 8) + response[5] = byte(r.FaultEventTime & 0xff) + + // Minutes Since Activation SSSS + response[6] = byte(r.MinutesActive >> 8) + response[7] = byte(r.MinutesActive & 0xff) + + return response, nil +} diff --git a/pkg/response/type5statusresponse.go b/pkg/response/type5statusresponse.go new file mode 100644 index 0000000..acb305e --- /dev/null +++ b/pkg/response/type5statusresponse.go @@ -0,0 +1,39 @@ +package response + +import ( + "encoding/hex" +) + +type Type5StatusResponse struct { + FaultEvent uint8 + FaultEventTime uint16 + Year uint8 + Month uint8 + Day uint8 + Hour uint8 + Minute uint8 +} + +// OFF 1 2 3 4 5 6 7 8 9 10111213 1415161718 +// 02 11 05 PP QQQQ 00000000 00000000 MMDDYYHHMM + +func (r *Type5StatusResponse) Marshal() ([]byte, error) { + + response, _ := hex.DecodeString("0211051c12c000000000000000000b1917100c") + + // Fault Code PP + response[3] = r.FaultEvent + + // Fault Event Time QQQQ + response[4] = uint8(r.FaultEventTime >> 8) + response[5] = uint8(r.FaultEventTime & 0xff) + + // Activation date and time + response[14] = r.Month // MM + response[15] = r.Day // DD + response[16] = r.Year // YY + response[17] = r.Hour // HH + response[18] = r.Minute // MM + + return response, nil +}