├── .ruby-version ├── spec ├── spec_helper.rb ├── pump_status_ack_spec.rb ├── device_link_spec.rb ├── alert_cleared_spec.rb ├── find_device_spec.rb ├── remote_spec.rb ├── alert_spec.rb ├── meter_spec.rb ├── packet_spec.rb └── pump_status_spec.rb ├── docs ├── Alert.png ├── Remote.packetdiag ├── WakeUp.packetdiag ├── GetModel.packetdiag ├── DeviceLink.packetdiag ├── DeviceTest.packetdiag ├── FindDevice.packetdiag ├── PumpStatusAck.packetdiag ├── Meter.packetdiag ├── PumpDump.packetdiag ├── AlertCleared.packetdiag ├── Sensor.packetdiag ├── Alert.packetdiag ├── PumpStatus.packetdiag ├── README.md ├── GetModel.svg ├── Remote.svg ├── WakeUp.svg ├── DeviceLink.svg ├── DeviceTest.svg ├── FindDevice.svg ├── PumpStatusAck.svg └── Meter.svg ├── .gitignore ├── Gemfile ├── tasks ├── history_entry_objc.h.erb ├── history_entry_objc.m.erb ├── history_entry.swift.erb ├── history.rake └── ios.rake ├── lib ├── minimed_rf │ ├── string_utils.rb │ ├── messages │ │ ├── get_model.rb │ │ ├── pump_dump.rb │ │ ├── wake_up.rb │ │ ├── device_test.rb │ │ ├── device_link.rb │ │ ├── find_device.rb │ │ ├── alert_cleared.rb │ │ ├── remote.rb │ │ ├── dump_history_page.rb │ │ ├── pump_status_ack.rb │ │ ├── meter.rb │ │ ├── message_type_map.rb │ │ ├── sensor.rb │ │ ├── alert.rb │ │ ├── message_base.rb │ │ └── pump_status.rb │ ├── log_entries │ │ ├── new_time.rb │ │ ├── self_test.rb │ │ ├── set_auto_off.rb │ │ ├── resume.rb │ │ ├── change_max_bolus.rb │ │ ├── suspend.rb │ │ ├── change_carb_units.rb │ │ ├── enable_bolus_wizard.rb │ │ ├── change_other_device_id.rb │ │ ├── save_settings.rb │ │ ├── delete_alarm_clock_time.rb │ │ ├── change_alarm_clock_enable.rb │ │ ├── change_bolus_wizard_setup.rb │ │ ├── clear_settings.rb │ │ ├── rewind.rb │ │ ├── journal_entry_low_battery.rb │ │ ├── change_watchdog_marriage_profile.rb │ │ ├── change_alarm_clock_time.rb │ │ ├── alarm_clock_reminder.rb │ │ ├── questionable_3b.rb │ │ ├── change_sensor_rate_of_change_alert_setup.rb │ │ ├── change_active_basal_profile_pattern.rb │ │ ├── bolus_reminder.rb │ │ ├── change_max_basal.rb │ │ ├── battery.rb │ │ ├── change_bg_reminder_offset.rb │ │ ├── journal_entry_meal_marker.rb │ │ ├── journal_entry_exercise_marker.rb │ │ ├── journal_entry_pump_low_reservoir.rb │ │ ├── change_alarm_notify_mode.rb │ │ ├── delete_other_device_id.rb │ │ ├── enable_disable_remote.rb │ │ ├── change_capture_event_enable.rb │ │ ├── change_bolus_reminder_time.rb │ │ ├── clear_alarm.rb │ │ ├── alarm_pump.rb │ │ ├── change_audio_bolus.rb │ │ ├── change_bg_reminder_enable.rb │ │ ├── change_temp_basal_type.rb │ │ ├── change_time_format.rb │ │ ├── change_time.rb │ │ ├── change_paradigm_link_id.rb │ │ ├── delete_bolus_reminder_time.rb │ │ ├── change_basal_profile.rb │ │ ├── prime.rb │ │ ├── change_bolus_reminder_enable.rb │ │ ├── change_watchdog_enable.rb │ │ ├── change_child_block_enable.rb │ │ ├── change_variable_bolus.rb │ │ ├── change_basal_profile_pattern.rb │ │ ├── change_bolus_scroll_step_size.rb │ │ ├── change_meter_id.rb │ │ ├── change_sensor_alarm_silence_config.rb │ │ ├── result_daily_total.rb │ │ ├── cal_bg_for_ph.rb │ │ ├── temp_basal_duration.rb │ │ ├── bg_received.rb │ │ ├── change_reservoir_warning_time.rb │ │ ├── temp_basal.rb │ │ ├── basal_profile_start.rb │ │ ├── unabsorbed_insulin.rb │ │ ├── daily_totals.rb │ │ ├── alarm_sensor.rb │ │ ├── restore_settings_mystery_events.rb │ │ ├── base.rb │ │ ├── bolus_normal.rb │ │ ├── bolus_wizard_bolus_estimate.rb │ │ ├── change_sensor_setup2.rb │ │ └── bolus_wizard_setup.rb │ ├── codes.rb │ ├── pump_models.rb │ ├── log_entries.rb │ ├── history_page.rb │ ├── crc.rb │ ├── packet.rb │ └── rfspy.rb └── minimed_rf.rb ├── Rakefile ├── bin ├── mmsend ├── mmlisten ├── mmpair ├── mmdecode ├── mmdumphistory ├── mmmockpair ├── mmhistory └── mmtune ├── minimed_rf.gemspec ├── load_ns_rfpackets.rb ├── generate_diags.rb ├── Gemfile.lock └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.1.3 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minimed_rf' 2 | -------------------------------------------------------------------------------- /docs/Alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ps2/minimed_rf/HEAD/docs/Alert.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | tmppacket.packetdiag 4 | tmppacket.svg 5 | pkg 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in simple_logic.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /docs/Remote.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | } 7 | -------------------------------------------------------------------------------- /docs/WakeUp.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | } 7 | -------------------------------------------------------------------------------- /docs/GetModel.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | } 7 | -------------------------------------------------------------------------------- /docs/DeviceLink.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 40-47: sequence [color = "none"] 7 | } 8 | -------------------------------------------------------------------------------- /docs/DeviceTest.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 40-47: sequence [color = "none"] 7 | } 8 | -------------------------------------------------------------------------------- /docs/FindDevice.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 40-47: sequence [color = "none"] 7 | } 8 | -------------------------------------------------------------------------------- /docs/PumpStatusAck.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 41-47: sequence [color = "none"] 7 | } 8 | -------------------------------------------------------------------------------- /tasks/history_entry_objc.h.erb: -------------------------------------------------------------------------------- 1 | // 2 | // <%= class_name %>.h 3 | // 4 | 5 | #import 6 | #import "PumpHistoryEventBase.h" 7 | 8 | @interface <%= class_name %> : PumpHistoryEventBase 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /docs/Meter.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 45-46: alert [color = "none"] 7 | 47-55: glucose [color = "none"] 8 | } 9 | -------------------------------------------------------------------------------- /docs/PumpDump.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 41-47: sequence [color = "none"] 7 | 55-60: x1 [color = "none"] 8 | } 9 | -------------------------------------------------------------------------------- /docs/AlertCleared.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 40-47: sequence [color = "none"] 7 | 48-55: alert_type [color = "none"] 8 | } 9 | -------------------------------------------------------------------------------- /lib/minimed_rf/string_utils.rb: -------------------------------------------------------------------------------- 1 | 2 | class String 3 | def underscore 4 | self.gsub(/::/, '/'). 5 | gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). 6 | gsub(/([a-z\d])([A-Z])/,'\1_\2'). 7 | tr("-", "_"). 8 | downcase 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/pump_status_ack_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MinimedRF::PumpStatusAck do 4 | 5 | it "should decode fields" do 6 | hex_data = "020006950004000000" 7 | message = MinimedRF::PumpStatusAck.from_hex(hex_data) 8 | expect(message.sequence).to eq 2 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /spec/device_link_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MinimedRF::DeviceLink do 4 | 5 | it "should decode fields" do 6 | message = MinimedRF::DeviceLink.from_hex("3700069500") 7 | expect(message.sequence).to eq 55 8 | expect(message.device_address).to eq "000695" 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /tasks/history_entry_objc.m.erb: -------------------------------------------------------------------------------- 1 | // 2 | // <%= class_name %>.m 3 | // 4 | 5 | #import "<%= class_name %>.h" 6 | 7 | @implementation <%= class_name %> 8 | 9 | + (int) eventTypeCode { 10 | return <%= event_type_code %>; 11 | } 12 | 13 | 14 | - (int) length { 15 | return <%= length %>; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /spec/alert_cleared_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MinimedRF::AlertCleared do 4 | 5 | it "should decode fields" do 6 | message = MinimedRF::AlertCleared.from_hex("4a72") 7 | expect(message.sequence).to eq 0x4a 8 | expect(message.alert_type).to eq :high_predicted 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /spec/find_device_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MinimedRF::FindDevice do 4 | 5 | it "should decode fields" do 6 | message = MinimedRF::FindDevice.from_hex("3499999900") 7 | expect(message.sequence).to eq 52 8 | expect(message.broadcast_address).to eq "999999" 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/get_model.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class GetModel < Message 4 | 5 | def model 6 | if @data.bytesize > 3 7 | len = @data.getbyte(1) 8 | return @data.byteslice(2,len) 9 | end 10 | end 11 | 12 | def to_s 13 | "GetModel: #{model.inspect}" 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/pump_dump.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class PumpDump < Message 4 | def self.bit_blocks 5 | { 6 | sequence: [1,7], 7 | x1: [15,6] 8 | } 9 | end 10 | 11 | def sequence 12 | b(:sequence) 13 | end 14 | 15 | def to_s 16 | "PumpDump: #{sequence}" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /docs/Sensor.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 44-47: state [color = "none"] 7 | 56-79: address [color = "none"] 8 | 80-87: version [color = "none"] 9 | 96-103: isig_adj [color = "none"] 10 | 104-107: sequence [color = "none"] 11 | 108-111: repeat [color = "none"] 12 | 112-127: isig [color = "none"] 13 | } 14 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/new_time.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class NewTime < Base 5 | def self.event_type_code 6 | 0x18 7 | end 8 | 9 | def bytesize 10 | 7 11 | end 12 | 13 | def to_s 14 | "NewTime #{timestamp_str}" 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/self_test.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class SelfTest < Base 5 | def self.event_type_code 6 | 0x20 7 | end 8 | 9 | def bytesize 10 | 7 11 | end 12 | 13 | def to_s 14 | "SelfTest #{timestamp_str}" 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/set_auto_off.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | class SetAutoOff < Base 4 | def self.event_type_code 5 | 0x1b 6 | end 7 | 8 | def bytesize 9 | 7 10 | end 11 | 12 | def to_s 13 | "SetAutoOff #{timestamp_str}" 14 | end 15 | 16 | def timestamp 17 | parse_date(2) 18 | end 19 | 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/wake_up.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | class WakeUp < Message 3 | 4 | def self.bit_blocks 5 | { 6 | } 7 | end 8 | 9 | def arg_count 10 | d(0) 11 | end 12 | 13 | def minutes 14 | d(2) 15 | end 16 | 17 | def to_s 18 | if arg_count > 0 19 | "WakeUp minutes=#{minutes}" 20 | else 21 | "WakeUp" 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/resume.rb: -------------------------------------------------------------------------------- 1 | # 1F20 763612030E 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class Resume < Base 6 | def self.event_type_code 7 | 0x1f 8 | end 9 | 10 | def bytesize 11 | 7 12 | end 13 | 14 | def to_s 15 | "Resume #{timestamp_str}" 16 | end 17 | 18 | def timestamp 19 | parse_date(2) 20 | end 21 | 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_max_bolus.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class ChangeMaxBolus < Base 5 | def self.event_type_code 6 | 0x24 7 | end 8 | 9 | def bytesize 10 | 7 11 | end 12 | 13 | def to_s 14 | "ChangeMaxBolus #{timestamp_str}" 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/suspend.rb: -------------------------------------------------------------------------------- 1 | # 1e01 603612030e 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class Suspend < Base 6 | def self.event_type_code 7 | 0x1e 8 | end 9 | 10 | def bytesize 11 | 7 12 | end 13 | 14 | def to_s 15 | "Suspend #{timestamp_str}" 16 | end 17 | 18 | def timestamp 19 | parse_date(2) 20 | end 21 | 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_carb_units.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | class ChangeCarbUnits < Base 4 | 5 | def self.event_type_code 6 | 0x6f 7 | end 8 | 9 | def bytesize 10 | 7 11 | end 12 | 13 | def timestamp 14 | parse_date(2) 15 | end 16 | 17 | def to_s 18 | "ChangeCarbUnits #{timestamp_str} " 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/enable_bolus_wizard.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | class EnableBolusWizard < Base 4 | def self.event_type_code 5 | 0x2d 6 | end 7 | 8 | def bytesize 9 | 7 10 | end 11 | 12 | def to_s 13 | "EnableBolusWizard #{timestamp_str}" 14 | end 15 | 16 | def timestamp 17 | parse_date(2) 18 | end 19 | 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_other_device_id.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class ChangeOtherDeviceID < Base 5 | def self.event_type_code 6 | 0x7d 7 | end 8 | 9 | def bytesize 10 | 37 11 | end 12 | 13 | def to_s 14 | "ChangeOtherDeviceID #{timestamp_str}" 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/save_settings.rb: -------------------------------------------------------------------------------- 1 | # 1F20 763612030E 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class SaveSettings < Base 6 | def self.event_type_code 7 | 0x5d 8 | end 9 | 10 | def bytesize 11 | 7 12 | end 13 | 14 | def to_s 15 | "SaveSettings #{timestamp_str}" 16 | end 17 | 18 | def timestamp 19 | parse_date(2) 20 | end 21 | 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/device_test.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class DeviceTest < Message 4 | 5 | # a2350535034fb4 6 | def self.bit_blocks 7 | { 8 | sequence: [0,8] 9 | } 10 | end 11 | 12 | def sequence 13 | b(:sequence) 14 | end 15 | 16 | def device_address 17 | hex_str[2,6] 18 | end 19 | 20 | def to_s 21 | "DeviceTest: ##{sequence} #{device_address}" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/remote_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MinimedRF::Meter do 4 | 5 | it "should decode fields" do 6 | hex_data = "a6411525885d46" 7 | packet = MinimedRF::Packet.from_hex(hex_data) 8 | message = packet.to_message 9 | 10 | expect(message.class).to eq MinimedRF::Remote 11 | expect(message.remote_id).to eq "411525" 12 | expect(message.button).to eq "B" 13 | expect(message.sequence).to eq 93 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/delete_alarm_clock_time.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class DeleteAlarmClockTime < Base 5 | def self.event_type_code 6 | 0x6a 7 | end 8 | 9 | def bytesize 10 | 14 11 | end 12 | 13 | def to_s 14 | "DeleteAlarmClockTime #{timestamp_str}" 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_alarm_clock_enable.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class ChangeAlarmClockEnable < Base 5 | def self.event_type_code 6 | 0x61 7 | end 8 | 9 | def bytesize 10 | 7 11 | end 12 | 13 | def to_s 14 | "ChangeAlarmClockEnable #{timestamp_str}" 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_bolus_wizard_setup.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class ChangeBolusWizardSetup < Base 5 | def self.event_type_code 6 | 0x4f 7 | end 8 | 9 | def bytesize 10 | 39 11 | end 12 | 13 | def to_s 14 | "ChangeBolusWizardSetup #{timestamp_str}" 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/clear_settings.rb: -------------------------------------------------------------------------------- 1 | 2 | # 2200b2210a1d10 3 | 4 | module MinimedRF 5 | module PumpEvents 6 | class ClearSettings < Base 7 | 8 | def self.event_type_code 9 | 0x22 10 | end 11 | 12 | def bytesize 13 | 7 14 | end 15 | 16 | def to_s 17 | "ClearSettings #{timestamp_str}" 18 | end 19 | 20 | def timestamp 21 | parse_date(2) 22 | end 23 | 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /docs/Alert.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 41-47: sequence [color = "none"] 7 | 48-55: alert_type [color = "none"] 8 | 59-63: alert_hour [color = "none"] 9 | 66-71: alert_minute [color = "none"] 10 | 74-79: alert_second [color = "none"] 11 | 80-87: alert_year [color = "none"] 12 | 92-95: alert_month [color = "none"] 13 | 99-103: alert_day [color = "none"] 14 | } 15 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/rewind.rb: -------------------------------------------------------------------------------- 1 | # 210026780D030E 2 | 3 | # [2014, 1, 3, 13, 56, 38] 4 | 5 | module MinimedRF 6 | module PumpEvents 7 | class Rewind < Base 8 | def self.event_type_code 9 | 0x21 10 | end 11 | 12 | def bytesize 13 | 7 14 | end 15 | 16 | def to_s 17 | "Rewind #{timestamp_str}" 18 | end 19 | 20 | def timestamp 21 | parse_date(2) 22 | end 23 | 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/journal_entry_low_battery.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | 4 | class JournalEntryPumpLowBattery < Base 5 | 6 | def self.event_type_code 7 | 0x19 8 | end 9 | 10 | def bytesize 11 | 7 12 | end 13 | 14 | def timestamp 15 | parse_date(2) 16 | end 17 | 18 | def to_s 19 | "JournalEntryPumpLowBattery #{timestamp_str}" 20 | end 21 | 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_watchdog_marriage_profile.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class ChangeWatchdogMarriageProfile < Base 5 | def self.event_type_code 6 | 0x81 7 | end 8 | 9 | def bytesize 10 | 12 11 | end 12 | 13 | def to_s 14 | "ChangeWatchdogMarriageProfile #{timestamp_str}" 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_alarm_clock_time.rb: -------------------------------------------------------------------------------- 1 | # 6304F217161A0D 2 | 3 | 4 | module MinimedRF 5 | module PumpEvents 6 | class ChangeAlarmClockTime < Base 7 | def self.event_type_code 8 | 0x32 9 | end 10 | 11 | def bytesize 12 | 14 13 | end 14 | 15 | def to_s 16 | "ChangeAlarmClockTime #{timestamp_str}" 17 | end 18 | 19 | def timestamp 20 | parse_date(2) 21 | end 22 | 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/alarm_clock_reminder.rb: -------------------------------------------------------------------------------- 1 | # 350040300e040e 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class AlarmClockReminder < Base 6 | 7 | def self.event_type_code 8 | 0x35 9 | end 10 | 11 | def bytesize 12 | 7 13 | end 14 | 15 | def to_s 16 | "AlarmClockReminder #{timestamp_str} type:#{d(1)}" 17 | end 18 | 19 | def timestamp 20 | parse_date(2) 21 | end 22 | 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/questionable_3b.rb: -------------------------------------------------------------------------------- 1 | # 210026780D030E 2 | 3 | # [2014, 1, 3, 13, 56, 38] 4 | 5 | module MinimedRF 6 | module PumpEvents 7 | class Questionable3b < Base 8 | def self.event_type_code 9 | 0x3b 10 | end 11 | 12 | def bytesize 13 | 7 14 | end 15 | 16 | def to_s 17 | "Questionable3b #{timestamp_str}" 18 | end 19 | 20 | def timestamp 21 | parse_date(2) 22 | end 23 | 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/device_link.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class DeviceLink < Message 4 | 5 | # a23505350a37000695008d 6 | def self.bit_blocks 7 | { 8 | flag: [0,1], 9 | sequence: [1,7] 10 | } 11 | end 12 | 13 | def sequence 14 | b(:sequence) 15 | end 16 | 17 | def device_address 18 | hex_str[2,6] 19 | end 20 | 21 | def to_s 22 | "DeviceLink: #{b(:flag)} ##{sequence} #{device_address}" 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_sensor_rate_of_change_alert_setup.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | class ChangeSensorRateOfChangeAlertSetup < Base 4 | 5 | def self.event_type_code 6 | 0x56 7 | end 8 | 9 | def bytesize 10 | 12 11 | end 12 | 13 | def timestamp 14 | parse_date(2) 15 | end 16 | 17 | def to_s 18 | "ChangeSensorRateOfChangeAlertSetup #{timestamp_str} " 19 | end 20 | 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/find_device.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class FindDevice < Message 4 | 5 | # a235053509349999990041 6 | def self.bit_blocks 7 | { 8 | flag: [0,1], 9 | sequence: [1,7] 10 | } 11 | end 12 | 13 | def sequence 14 | b(:sequence) 15 | end 16 | 17 | def broadcast_address 18 | hex_str[2,6] 19 | end 20 | 21 | def to_s 22 | "FindDevice: #{b(:flag)} ##{sequence} #{broadcast_address}" 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require 'bundler/gem_tasks' 3 | 4 | # Default directory to look in is `/specs` 5 | # Run with `rake spec` 6 | RSpec::Core::RakeTask.new(:spec) do |task| 7 | task.rspec_opts = ['--color'] 8 | end 9 | 10 | task :default => :spec 11 | 12 | lib = File.expand_path('../lib', __FILE__) 13 | #this will include the path in $LOAD_PATH unless it is already included 14 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 15 | 16 | Dir.glob('tasks/*.rake').each { |r| import r } 17 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_active_basal_profile_pattern.rb: -------------------------------------------------------------------------------- 1 | # 1F20 763612030E 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class ChangeActiveBasalProfilePattern < Base 6 | def self.event_type_code 7 | 0x14 8 | end 9 | 10 | def bytesize 11 | 7 12 | end 13 | 14 | def to_s 15 | "ChangeActiveBasalProfilePattern #{timestamp_str}" 16 | end 17 | 18 | def timestamp 19 | parse_date(2) 20 | end 21 | 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/bolus_reminder.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | 4 | class BolusReminder < Base 5 | def self.event_type_code 6 | 0x69 7 | end 8 | 9 | def bytesize 10 | if @pump_model.larger 11 | 9 12 | else 13 | 7 14 | end 15 | end 16 | 17 | def to_s 18 | "BolusReminder #{timestamp_str}" 19 | end 20 | 21 | def timestamp 22 | parse_date(2) 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_max_basal.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class ChangeMaxBasal < Base 5 | def self.event_type_code 6 | 0x2c 7 | end 8 | 9 | def bytesize 10 | 7 11 | end 12 | 13 | def max_basal 14 | d(1) / 40.0 15 | end 16 | 17 | def to_s 18 | "ChangeMaxBasal #{max_basal} #{timestamp_str}" 19 | end 20 | 21 | def timestamp 22 | parse_date(2) 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/alert_cleared.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class AlertCleared < Message 4 | 5 | # 2014-09-08T22:49:10-0500 - a2 597055 02 4a72 81 6 | def self.bit_blocks 7 | { 8 | sequence: [0,8], 9 | alert_type: [8,8] 10 | 11 | } 12 | end 13 | 14 | def sequence 15 | b(:sequence) 16 | end 17 | 18 | def alert_type 19 | AlertCodes[b(:alert_type)] 20 | end 21 | 22 | def to_s 23 | "AlertCleared: ##{sequence} #{alert_type}" 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/battery.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | 4 | # Got this from @bewest's decocare 5 | # https://github.com/bewest/decoding-carelink/blob/master/decocare/history.py 6 | 7 | class Battery < Base 8 | 9 | def self.event_type_code 10 | 0x1a 11 | end 12 | 13 | def bytesize 14 | 7 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | def to_s 22 | "Battery #{timestamp_str}" 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_bg_reminder_offset.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class ChangeBGReminderOffset < Base 5 | def self.event_type_code 6 | 0x31 7 | end 8 | 9 | def bytesize 10 | 7 11 | end 12 | 13 | def to_s 14 | "ChangeBGReminderOffset #{timestamp_str} Amount=#{amount}" 15 | end 16 | 17 | def amount 18 | d(1) # TODO: decode this 19 | end 20 | 21 | def timestamp 22 | parse_date(2) 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/journal_entry_meal_marker.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | 4 | # Got this from @bewest's decocare 5 | # https://github.com/bewest/decoding-carelink 6 | 7 | class JournalEntryMealMarker < Base 8 | 9 | def self.event_type_code 10 | 0x40 11 | end 12 | 13 | def bytesize 14 | 9 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | def to_s 22 | "JournalEntryMealMarker #{timestamp_str}" 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/journal_entry_exercise_marker.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | 4 | # Got this from @bewest's decocare 5 | # https://github.com/bewest/decoding-carelink 6 | 7 | class JournalEntryExerciseMarker < Base 8 | 9 | def self.event_type_code 10 | 0x41 11 | end 12 | 13 | def bytesize 14 | 8 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | def to_s 22 | "JournalEntryExerciseMarker #{timestamp_str}" 23 | end 24 | 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/journal_entry_pump_low_reservoir.rb: -------------------------------------------------------------------------------- 1 | # 34c8006314080e 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class JournalEntryPumpLowReservoir < Base 6 | def self.event_type_code 7 | 0x34 8 | end 9 | 10 | def bytesize 11 | 7 12 | end 13 | 14 | def to_s 15 | "JournalEntryPumpLowReservoir #{timestamp_str} Amount=#{amount}" 16 | end 17 | 18 | def amount 19 | d(1) / 10.0 20 | end 21 | 22 | def timestamp 23 | parse_date(2) 24 | end 25 | 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/alert_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MinimedRF::Alert do 4 | 5 | it "should decode fields" do 6 | message = MinimedRF::Alert.from_hex("e333153b1d0e09085200") 7 | expect(message.sequence).to eq 99 8 | expect(message.alert_type).to eq :max_hourly_bolus 9 | expect(message.timestamp.year).to eq 2014 10 | expect(message.timestamp.month).to eq 9 11 | expect(message.timestamp.day).to eq 8 12 | expect(message.timestamp.hour).to eq 21 13 | expect(message.timestamp.min).to eq 59 14 | expect(message.timestamp.sec).to eq 29 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_alarm_notify_mode.rb: -------------------------------------------------------------------------------- 1 | # 6304F217161A0D 2 | 3 | 4 | module MinimedRF 5 | module PumpEvents 6 | class ChangeAlarmNotifyMode < Base 7 | def self.event_type_code 8 | 0x63 9 | end 10 | 11 | def bytesize 12 | 7 13 | end 14 | 15 | def to_s 16 | "ChangeAlarmNotifyMode #{timestamp_str} Mode=#{mode}" 17 | end 18 | 19 | def mode 20 | { 21 | # TODO: other values? 22 | 4 => "vibration" 23 | }[d(1)] 24 | end 25 | 26 | def timestamp 27 | parse_date(2) 28 | end 29 | 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/delete_other_device_id.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class DeleteOtherDeviceID < Base 5 | def self.event_type_code 6 | 0x82 7 | end 8 | 9 | def bytesize 10 | 12 11 | end 12 | 13 | def device_id 14 | hex_str[18,6] 15 | end 16 | 17 | def to_s 18 | "DeleteOtherDeviceID #{timestamp_str} #{device_id}" 19 | end 20 | 21 | def timestamp 22 | parse_date(2) 23 | end 24 | 25 | def as_json 26 | super.merge({ 27 | device_id: device_id 28 | }) 29 | end 30 | 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/remote.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class Remote < Message 4 | 5 | # a6 41152 5885d 46 6 | def self.bit_blocks 7 | { 8 | } 9 | end 10 | 11 | def remote_id 12 | @data.byteslice(1,3).unpack("H*").first 13 | end 14 | 15 | def button 16 | case d(4) 17 | when 0x86 18 | "ACT" 19 | when 0x81 20 | "S" 21 | when 0x88 22 | "B" 23 | else 24 | "?" 25 | end 26 | end 27 | 28 | def sequence 29 | d(5) 30 | end 31 | 32 | def to_s 33 | "Remote: #{remote_id} button=#{button} sequence=#{sequence}" 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/enable_disable_remote.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | 4 | # Got this from @bewest's decocare 5 | # https://github.com/bewest/decoding-carelink 6 | 7 | class EnableDisableRemote < Base 8 | 9 | def self.event_type_code 10 | 0x26 11 | end 12 | 13 | def bytesize 14 | 21 15 | end 16 | 17 | def timestamp 18 | parse_date(2) 19 | end 20 | 21 | def enabled 22 | ((d(1) & 0b1) == 1) ? true : false 23 | end 24 | 25 | def to_s 26 | "EnableDisableRemote #{timestamp_str} enabled=#{enabled.inspect}" 27 | end 28 | 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_capture_event_enable.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | class ChangeCaptureEventEnable < Base 4 | 5 | # Got this from @bewest's decocare 6 | # https://github.com/bewest/decoding-carelink 7 | 8 | def self.event_type_code 9 | 0x83 10 | end 11 | 12 | def bytesize 13 | 7 14 | end 15 | 16 | def to_s 17 | "ChangeCaptureEventEnable #{timestamp_str} enabled:#{enabled.inspect}" 18 | end 19 | 20 | def enabled 21 | ((d(1) & 0b1) == 1) ? true : false 22 | end 23 | 24 | def timestamp 25 | parse_date(2) 26 | end 27 | 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_bolus_reminder_time.rb: -------------------------------------------------------------------------------- 1 | 2 | # 67 00 5B1902070E 001E 3 | # 514,4/7/14,02:25:27,4/7/14 02:25:27,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBolusReminderTime,"ALARM_TIME=1800000, ACTION_REQUESTOR=pump, START_TIME=0",12753936695,53119959,101,MiniMed 530G - 551 4 | 5 | module MinimedRF 6 | module PumpEvents 7 | class ChangeBolusReminderTime < Base 8 | def self.event_type_code 9 | 0x67 10 | end 11 | 12 | def bytesize 13 | 9 14 | end 15 | 16 | def to_s 17 | "ChangeBolusReminderTime #{timestamp_str} ??? #{hex_str}" 18 | end 19 | 20 | def timestamp 21 | parse_date(2) 22 | end 23 | 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/clear_alarm.rb: -------------------------------------------------------------------------------- 1 | 2 | # 0C67440012080E 3 | # 1782,4/8/14,18:00:04,4/8/14 18:00:04,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ClearAlarm,"ALARM_TYPE=103, ACTION_REQUESTOR=pump",12772625731,53126704,102,MiniMed 530G - 551 4 | 5 | module MinimedRF 6 | module PumpEvents 7 | class ClearAlarm < Base 8 | 9 | def self.event_type_code 10 | 0x0c 11 | end 12 | 13 | def bytesize 14 | 7 15 | end 16 | 17 | def to_s 18 | "ClearAlarm #{timestamp_str} alarm_type:#{raw_type}" 19 | end 20 | 21 | def raw_type 22 | d(1) 23 | end 24 | 25 | def timestamp 26 | parse_date(2) 27 | end 28 | 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/dump_history_page.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | class DumpHistoryPage < Message 3 | # a7 350535 5d 00 48 4 | def self.bit_blocks 5 | { 6 | last_frame_flag: [0,1], 7 | frame_number: [1,7] 8 | } 9 | end 10 | 11 | def frame_number 12 | b(:frame_number) 13 | end 14 | 15 | def frame_data 16 | @data[1..-1] 17 | end 18 | 19 | def last_frame_flag 20 | b(:last_frame_flag) == 1 21 | end 22 | 23 | def frame_data_hex 24 | frame_data.unpack("H*").first 25 | end 26 | 27 | def to_s 28 | "DumpHistoryPage frame:#{frame_number} data:#{frame_data_hex} lastframe:#{last_frame_flag}" 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/alarm_pump.rb: -------------------------------------------------------------------------------- 1 | # 06 67 01 6E 773BB1880E 2 | # 288,4/8/14,17:59:55,4/8/14 17:59:55,,,,,,,,,,,,,,,,,,,,,,,,,Device Alarm (103),,,,,AlarmPump,"RAW_TYPE=103, RAW_MODULE=44, LINE_NUM=366",12772625734,53126704,105,MiniMed 530G - 551 3 | 4 | module MinimedRF 5 | module PumpEvents 6 | class AlarmPump < Base 7 | 8 | def self.event_type_code 9 | 0x06 10 | end 11 | 12 | def bytesize 13 | 9 14 | end 15 | 16 | def to_s 17 | "AlarmPump #{timestamp_str} raw_type:#{raw_type}" 18 | end 19 | 20 | def raw_type 21 | d(1) 22 | end 23 | 24 | def timestamp 25 | parse_date(4) 26 | end 27 | 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_audio_bolus.rb: -------------------------------------------------------------------------------- 1 | # 5F05 473817030E - enabale 2 | # 5F04 6D3817030E - disable 3 | 4 | 5 | module MinimedRF 6 | module PumpEvents 7 | class ChangeAudioBolus < Base 8 | def self.event_type_code 9 | 0x5f 10 | end 11 | 12 | def bytesize 13 | 7 14 | end 15 | 16 | def to_s 17 | "ChangeAudioBolus #{timestamp_str} enabled:#{enabled.inspect} stepsize:#{stepsize}" 18 | end 19 | 20 | def enabled 21 | ((d(1) & 0b1) == 1) ? true : false 22 | end 23 | 24 | def stepsize 25 | (d(1) >> 1) / 20.0 26 | end 27 | 28 | def timestamp 29 | parse_date(2) 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_bg_reminder_enable.rb: -------------------------------------------------------------------------------- 1 | # 60 00 471902070E 2 | # 512,4/7/14,02:25:07,4/7/14 02:25:07,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBGReminderEnable,"ENABLE=false, ACTION_REQUESTOR=pump",12753936697,53119959,103,MiniMed 530G - 551 3 | 4 | module MinimedRF 5 | module PumpEvents 6 | class ChangeBGReminderEnable < Base 7 | def self.event_type_code 8 | 0x60 9 | end 10 | 11 | def bytesize 12 | 7 13 | end 14 | 15 | def to_s 16 | "ChangeBGReminderEnable #{timestamp_str} enable:#{enable}" 17 | end 18 | 19 | def enable 20 | d(1) == 0 ? false : true 21 | end 22 | 23 | def timestamp 24 | parse_date(2) 25 | end 26 | 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_temp_basal_type.rb: -------------------------------------------------------------------------------- 1 | 2 | # 5F05 473817030E - enabale 3 | # 5F04 6D3817030E - disable 4 | 5 | 6 | module MinimedRF 7 | module PumpEvents 8 | class ChangeTempBasalType < Base 9 | def self.event_type_code 10 | 0x62 11 | end 12 | 13 | def bytesize 14 | 7 15 | end 16 | 17 | def to_s 18 | "ChangeTempBasalType #{timestamp_str} type:#{basal_type} " 19 | end 20 | 21 | def basal_type 22 | d(1) == 1 ? "percent" : "absolute" 23 | end 24 | 25 | def timestamp 26 | parse_date(2) 27 | end 28 | 29 | def as_json 30 | super.merge({ 31 | temp: basal_type, 32 | }) 33 | end 34 | 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/meter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MinimedRF::Meter do 4 | 5 | it "should decode fields" do 6 | hex_data = "a5c527ad008e61" 7 | packet = MinimedRF::Packet.from_hex(hex_data) 8 | message = packet.to_message 9 | expect(message.glucose).to eq 142 10 | end 11 | 12 | it "should decode fields when bg above 255" do 13 | hex_data = "a5c527ad018e77" 14 | packet = MinimedRF::Packet.from_hex(hex_data) 15 | message = packet.to_message 16 | expect(message.glucose).to eq 398 17 | end 18 | 19 | it "should decode ack bits" do 20 | hex_data = "a5c527ad008e61" 21 | packet = MinimedRF::Packet.from_hex(hex_data) 22 | message = packet.to_message 23 | expect(message.is_ack?).to eq false 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /bin/mmsend: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This utility talks over a serial connection to a RileyLink that has been 4 | # loaded with the subg_rfspy firmware (https://github.com/ps2/subg_rfspy) 5 | 6 | # It asks the cc1110 to listen for packets, receives them, decodes, and prints them out. 7 | 8 | # Must install the serialport gem to use this. 9 | 10 | require 'minimed_rf' 11 | require 'minimed_rf/rfspy' 12 | 13 | if ARGV.length < 1 14 | puts "Usage: mmlisten /dev/tty.usbserial-A9048LGG [channel]" 15 | exit -1 16 | end 17 | 18 | 19 | channel = 2 20 | if ARGV.length == 2 21 | channel = ARGV[1].to_i 22 | end 23 | 24 | puts "Opening #{ARGV[0]}" 25 | rf = MinimedRF::RFSpy.new(ARGV[0]) 26 | rf.sync 27 | while 1 28 | packet = rf.send_packet("00", channel) 29 | sleep 1 30 | end 31 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/pump_status_ack.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class PumpStatusAck < Message 4 | def self.bit_blocks 5 | { 6 | flag: [0,1], 7 | sequence: [1,7], 8 | response_type: [40,8], 9 | } 10 | end 11 | 12 | def sequence 13 | b(:sequence) 14 | end 15 | 16 | def device_address 17 | hex_str[2,6] 18 | end 19 | 20 | def response_type 21 | if @data.length > 5 22 | b(:response_type) 23 | else 24 | 0 25 | end 26 | end 27 | 28 | def to_s 29 | if @data.length > 2 30 | "PumpStatusAck: #{b(:flag)} ##{sequence} #{device_address} #{response_type}" 31 | else 32 | "PumpStatusAck: #{b(:flag)} ##{sequence}" 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_time_format.rb: -------------------------------------------------------------------------------- 1 | # 64000D4E17040E 2 | # 776,1/4/14,23:14:13,1/4/14 23:14:13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeTimeDisplayFormat,"FORMAT=d12, ACTION_REQUESTOR=pump",12073459547,52854662,76,MiniMed 530G - 551 3 | 4 | module MinimedRF 5 | module PumpEvents 6 | class ChangeTimeFormat < Base 7 | 8 | def self.event_type_code 9 | 0x64 10 | end 11 | 12 | def bytesize 13 | 7 14 | end 15 | 16 | def to_s 17 | "ChangeTimeFormat #{timestamp_str} format:#{format}" 18 | end 19 | 20 | def format 21 | { 22 | # TODO: other formats 23 | 0 => "d12" 24 | }[d(1)] 25 | end 26 | 27 | def timestamp 28 | parse_date(2) 29 | end 30 | 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/meter.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | class Meter < Message 3 | 4 | # a5c527ad008e61 5 | def self.bit_blocks 6 | { 7 | flags: [37,2], 8 | glucose: [39,9] 9 | } 10 | end 11 | 12 | def glucose 13 | b(:glucose) 14 | #(b(:glucose_h) << 8) + b(:glucose_l) 15 | end 16 | 17 | def meter_id 18 | hex_str[2,6] 19 | end 20 | 21 | def flags 22 | case b(:flags) 23 | when 3 24 | "Ack" 25 | end 26 | end 27 | 28 | def is_ack? 29 | return b(:flags) == 3 30 | end 31 | 32 | def to_s 33 | if flags 34 | "Meter id:#{meter_id} #{flags} raw:#{@data.unpack("H*")}" 35 | else 36 | "Meter id:#{meter_id} BG:#{glucose}" 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/minimed_rf/codes.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | 3 | CODE_SYMBOLS = { 4 | "010101" => "0", 5 | "110001" => "1", 6 | "110010" => "2", 7 | "100011" => "3", 8 | "110100" => "4", 9 | "100101" => "5", 10 | "100110" => "6", 11 | "010110" => "7", 12 | "011010" => "8", 13 | "011001" => "9", 14 | "101010" => "a", 15 | "001011" => "b", 16 | "101100" => "c", 17 | "001101" => "d", 18 | "001110" => "e", 19 | "011100" => "f" 20 | } 21 | 22 | CODES = [ 23 | 0b010101, 24 | 0b110001, 25 | 0b110010, 26 | 0b100011, 27 | 0b110100, 28 | 0b100101, 29 | 0b100110, 30 | 0b010110, 31 | 0b011010, 32 | 0b011001, 33 | 0b101010, 34 | 0b001011, 35 | 0b101100, 36 | 0b001101, 37 | 0b001110, 38 | 0b011100] 39 | 40 | end 41 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_time.rb: -------------------------------------------------------------------------------- 1 | # 1700 234E17040E 1800004E170110 2 | 3 | # 775,1/4/14,23:14:00,1/4/14 23:14:00,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeTimeGH,NEW_TIME=1451690040000,12073459554,52854662,83,MiniMed 530G - 551 4 | # 777,1/4/14,23:14:35,1/4/14 23:14:35,1/1/16 23:14:00,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeTime,"NEW_TIME=1451690040000, ACTION_REQUESTOR=pump",12073459546,52854662,75,MiniMed 530G - 551 5 | 6 | module MinimedRF 7 | module PumpEvents 8 | class ChangeTime < Base 9 | 10 | def self.event_type_code 11 | 0x17 12 | end 13 | 14 | def bytesize 15 | 7 16 | end 17 | 18 | def to_s 19 | "ChangeTime #{timestamp_str}" # TODO: decode new time 20 | end 21 | 22 | def timestamp 23 | parse_date(2) 24 | end 25 | 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_paradigm_link_id.rb: -------------------------------------------------------------------------------- 1 | # 3c012b7016080e3dc228060000003e000000000000 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class ChangeParadigmLinkID < Base 6 | 7 | def self.event_type_code 8 | 0x3c 9 | end 10 | 11 | def bytesize 12 | 21 13 | end 14 | 15 | def timestamp 16 | parse_date(2) 17 | end 18 | 19 | def link1 20 | @data.byteslice(8,3).unpack("H*").first 21 | end 22 | 23 | def link2 24 | @data.byteslice(11,3).unpack("H*").first 25 | end 26 | 27 | def link3 28 | @data.byteslice(15,3).unpack("H*").first 29 | end 30 | 31 | def to_s 32 | "ChangeParadigmLinkID #{timestamp_str} link1:#{link1} link2:#{link2} link3:#{link3}" 33 | end 34 | 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /tasks/history_entry.swift.erb: -------------------------------------------------------------------------------- 1 | // 2 | // <%= class_name %>.swift 3 | // RileyLink 4 | // 5 | // Created by Pete Schwamb on 3/8/16. 6 | // Copyright © 2016 Pete Schwamb. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class <%= class_name %>: PumpEvent { 12 | public let length: Int 13 | let timestamp: NSDateComponents 14 | 15 | public required init?(availableData: NSData, pumpModel: PumpModel) { 16 | length = <%= length %> 17 | 18 | if length > availableData.length { 19 | timestamp = NSDateComponents() 20 | return nil 21 | } 22 | 23 | timestamp = TimeFormat.parse5ByteDate(availableData, offset: 2) 24 | } 25 | 26 | public var dictionaryRepresentation: [String: AnyObject] { 27 | return [ 28 | "_type": "<%= class_short_name %>", 29 | "timestamp": TimeFormat.timestampStr(timestamp), 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/delete_bolus_reminder_time.rb: -------------------------------------------------------------------------------- 1 | 2 | # 68005E1902070E001E 3 | # 517,4/7/14,02:25:30,4/7/14 02:25:30,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,DeleteBolusReminderTime,"ALARM_TIME=1800000, ACTION_REQUESTOR=pump, START_TIME=0",12753936693,53119959,99,MiniMed 530G - 551 4 | 5 | module MinimedRF 6 | module PumpEvents 7 | class DeleteBolusReminderTime < Base 8 | 9 | def self.event_type_code 10 | 0x68 11 | end 12 | 13 | def bytesize 14 | 9 15 | end 16 | 17 | def to_s 18 | "DeleteBolusReminderTime #{timestamp_str} alarm_time:#{alarm_time} start_time:#{start_time}" 19 | end 20 | 21 | def alarm_time 22 | d(8) * 6000 23 | end 24 | 25 | def start_time 26 | d(7) * 6000 27 | end 28 | 29 | def timestamp 30 | parse_date(2) 31 | end 32 | 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/message_type_map.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | MessageTypeMap = { 3 | 0x01 => Alert, 4 | 0x02 => AlertCleared, 5 | 0x03 => DeviceTest, 6 | 0x04 => PumpStatus, 7 | #0x05 => PumpStatus2, # 2015-02-26T21:26:48Z a2 597055 05 ee040063070a000f021a00ee1326000f021901091115000f021901121021000f02190000 2e 8 | 0x06 => PumpStatusAck, 9 | #0x08 => PumpBackfill, # 2014-09-08T23:14:55-0500 - a2 597055 08 765000ff80ff80ff80ff80ff80ff80ff80e600ff80ff80ff80ff80ff80ff80ff80 6d 10 | 0x09 => FindDevice, # From pump 11 | 0x0a => DeviceLink, # From linking device 12 | 0x0b => PumpDump, # 2014-09-08T23:14:57-0500 - a2 597055 0b 780001a7a7a7aaa39c9b9a9a9a9a9a9a9a9998979798999ea0a09c100000f2c1eb000000 41 13 | 0x80 => DumpHistoryPage, 14 | 0x8d => GetModel, 15 | 0x5d => WakeUp, 16 | } 17 | 18 | MessageTypeMap.values.each { |m| m.check_bit_block_definitions } 19 | end 20 | -------------------------------------------------------------------------------- /lib/minimed_rf.rb: -------------------------------------------------------------------------------- 1 | require 'minimed_rf/packet.rb' 2 | require 'minimed_rf/history_page.rb' 3 | require 'minimed_rf/crc.rb' 4 | require 'minimed_rf/log_entries.rb' 5 | require 'minimed_rf/codes.rb' 6 | require 'minimed_rf/pump_models.rb' 7 | require 'minimed_rf/messages/message_base' 8 | require 'minimed_rf/messages/alert' 9 | require 'minimed_rf/messages/alert_cleared' 10 | require 'minimed_rf/messages/device_link' 11 | require 'minimed_rf/messages/device_test' 12 | require 'minimed_rf/messages/dump_history_page' 13 | require 'minimed_rf/messages/find_device' 14 | require 'minimed_rf/messages/get_model' 15 | require 'minimed_rf/messages/pump_dump' 16 | require 'minimed_rf/messages/pump_status' 17 | require 'minimed_rf/messages/pump_status_ack' 18 | require 'minimed_rf/messages/wake_up' 19 | require 'minimed_rf/messages/message_type_map' 20 | require 'minimed_rf/messages/meter' 21 | require 'minimed_rf/messages/remote' 22 | require 'minimed_rf/messages/sensor' 23 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_basal_profile.rb: -------------------------------------------------------------------------------- 1 | # 0901265B11030E000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 2 | # 3568,1/3/14,17:27:38,1/3/14 17:27:38,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBasalProfile,"PATTERN_DATUM=12226881723, PROFILE_INDEX=0, RATE=0.3, START_TIME=0",12226881724,52912277,277,MiniMed 530G - 551 3 | 4 | module MinimedRF 5 | module PumpEvents 6 | class ChangeBasalProfile < Base 7 | def self.event_type_code 8 | 0x09 9 | end 10 | 11 | def bytesize 12 | 152 13 | end 14 | 15 | def to_s 16 | "ChangeBasalProfile #{timestamp_str}" 17 | end 18 | 19 | def timestamp 20 | parse_date(2) 21 | end 22 | 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/prime.rb: -------------------------------------------------------------------------------- 1 | # 0300000022 C21736170D 2 | #[2013, 12, 23, 22, 23, 2] 3 | 4 | module MinimedRF 5 | module PumpEvents 6 | class Prime < Base 7 | def self.event_type_code 8 | 0x03 9 | end 10 | 11 | def bytesize 12 | 10 13 | end 14 | 15 | def to_s 16 | "Prime #{timestamp_str} Amount:#{amount} PrimeType:#{prime_type}" 17 | end 18 | 19 | def prime_type 20 | programmed_amount == 0 ? "manual" : "fixed" 21 | end 22 | 23 | def amount 24 | (d(4) << 2) / 40.0 25 | end 26 | 27 | def programmed_amount 28 | (d(2) << 2) / 40.0 29 | end 30 | 31 | def timestamp 32 | parse_date(5) 33 | end 34 | 35 | def as_json 36 | super.merge({ 37 | amount: amount, 38 | type: prime_type, 39 | fixed: programmed_amount 40 | }) 41 | end 42 | 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /minimed_rf.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |spec| 2 | spec.name = 'minimed_rf' 3 | spec.version = '0.1.0' 4 | spec.date = '2014-07-15' 5 | spec.summary = "Minimed RF Library" 6 | spec.description = "A library for decoding minimed pump RF transmissions" 7 | spec.authors = ["Pete Schwamb"] 8 | spec.email = 'pete@schwamb.net' 9 | spec.files = Dir.glob("{bin,lib}/**/*") + %w(README.md) 10 | spec.license = 'MIT' 11 | spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 12 | spec.add_dependency "colorize" 13 | spec.add_dependency 'serialport', '~> 1.3', '>= 1.3.1' 14 | 15 | spec.add_development_dependency "bundler", "~> 1.3" 16 | spec.add_development_dependency "rake" 17 | spec.add_development_dependency "rspec" 18 | spec.add_development_dependency "rspec-nc" 19 | spec.add_development_dependency "guard" 20 | spec.add_development_dependency "guard-rspec" 21 | end 22 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_bolus_reminder_enable.rb: -------------------------------------------------------------------------------- 1 | # 6601 5B1902070E 2 | # 515,4/7/14,02:25:27,4/7/14 02:25:27,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBolusReminderEnable,"ENABLE=true, ACTION_REQUESTOR=pump",12753936694,53119959,100,MiniMed 530G - 551 3 | # 6600 5e1902070e 4 | # 516,4/7/14,02:25:30,4/7/14 02:25:30,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBolusReminderEnable,"ENABLE=false, ACTION_REQUESTOR=pump",12753936692,53119959,98,MiniMed 530G - 551 5 | 6 | module MinimedRF 7 | module PumpEvents 8 | class ChangeBolusReminderEnable < Base 9 | def self.event_type_code 10 | 0x66 11 | end 12 | 13 | def bytesize 14 | 7 15 | end 16 | 17 | def to_s 18 | "ChangeBolusReminderEnable #{timestamp_str} enable:#{enable}" 19 | end 20 | 21 | def enable 22 | d(1) == 0 ? false : true 23 | end 24 | 25 | def timestamp 26 | parse_date(2) 27 | end 28 | 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_watchdog_enable.rb: -------------------------------------------------------------------------------- 1 | # 7C00521B02070E 2 | # 524,4/7/14,02:27:18,4/7/14 02:27:18,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeWatchdogEnable,"ENABLE=false, ACTION_REQUESTOR=pump",12753936686,53119959,92,MiniMed 530G - 551 3 | 4 | # 7C01551B02070E 5 | # 525,4/7/14,02:27:21,4/7/14 02:27:21,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeWatchdogEnable,"ENABLE=true, ACTION_REQUESTOR=pump",12753936685,53119959,91,MiniMed 530G - 551 6 | 7 | module MinimedRF 8 | module PumpEvents 9 | class ChangeWatchdogEnable < Base 10 | def self.event_type_code 11 | 0x7c 12 | end 13 | 14 | def bytesize 15 | 7 16 | end 17 | 18 | def to_s 19 | "ChangeWatchdogEnable #{timestamp_str} enabled:#{enabled.inspect}" 20 | end 21 | 22 | def enabled 23 | ((d(1) & 0b1) == 1) ? true : false 24 | end 25 | 26 | def timestamp 27 | parse_date(2) 28 | end 29 | 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_child_block_enable.rb: -------------------------------------------------------------------------------- 1 | # 2301731B02070E 2 | #526,4/7/14,02:27:51,4/7/14 02:27:51,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeChildBlockEnable,"ENABLE=true, ACTION_REQUESTOR=pump",12753936684,53119959,90,MiniMed 530G - 551 3 | 4 | # 2300411C02070E 5 | #527,4/7/14,02:28:01,4/7/14 02:28:01,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeChildBlockEnable,"ENABLE=false, ACTION_REQUESTOR=pump",12753936683,53119959,89,MiniMed 530G - 551 6 | 7 | module MinimedRF 8 | module PumpEvents 9 | class ChangeChildBlockEnable < Base 10 | def self.event_type_code 11 | 0x23 12 | end 13 | 14 | def bytesize 15 | 7 16 | end 17 | 18 | def to_s 19 | "ChangeChildBlockEnable #{timestamp_str} enabled:#{enabled.inspect}" 20 | end 21 | 22 | def enabled 23 | ((d(1) & 0b1) == 1) ? true : false 24 | end 25 | 26 | def timestamp 27 | parse_date(2) 28 | end 29 | 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_variable_bolus.rb: -------------------------------------------------------------------------------- 1 | # 5E 01 540A11030E 2 | # 3672,4/3/14,17:10:20,4/3/14 17:10:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeVariableBolusEnable,"ENABLE=true, ACTION_REQUESTOR=pump",12753297208,53119725,316,MiniMed 530G - 551 3 | 4 | # 5E 00 693817030E 5 | # 3805,4/3/14,23:56:41,4/3/14 23:56:41,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeVariableBolusEnable,"ENABLE=false, ACTION_REQUESTOR=pump",12753297170,53119725,278,MiniMed 530G - 551 6 | 7 | 8 | module MinimedRF 9 | module PumpEvents 10 | class ChangeVariableBolus < Base 11 | def self.event_type_code 12 | 0x5e 13 | end 14 | 15 | def bytesize 16 | 7 17 | end 18 | 19 | def to_s 20 | "ChangeVariableBolus #{timestamp_str} enabled:#{enabled.inspect}" 21 | end 22 | 23 | def enabled 24 | ((d(1) & 0b1) == 1) ? true : false 25 | end 26 | 27 | def timestamp 28 | parse_date(2) 29 | end 30 | 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_basal_profile_pattern.rb: -------------------------------------------------------------------------------- 1 | # 0800265B11030E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 2 | # 3567,1/3/14,17:27:38,1/3/14 17:27:38,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBasalProfilePattern,"PATTERN_NAME=standard, NUM_PROFILES=1, ACTION_REQUESTOR=pump",12226881723,52912277,276,MiniMed 530G - 551 3 | 4 | 5 | module MinimedRF 6 | module PumpEvents 7 | class ChangeBasalProfilePattern < Base 8 | def self.event_type_code 9 | 0x08 10 | end 11 | 12 | def bytesize 13 | 152 14 | end 15 | 16 | def to_s 17 | "ChangeBasalProfilePattern #{timestamp_str}" 18 | end 19 | 20 | def timestamp 21 | parse_date(2) 22 | end 23 | 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_bolus_scroll_step_size.rb: -------------------------------------------------------------------------------- 1 | # 57 32 721802070E 2 | # 510,4/7/14,02:24:50,4/7/14 02:24:50,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBolusScrollStepSize,"STEP_SIZE=step_0_point_05, ACTION_REQUESTOR=pump",12753936699,53119959,105,MiniMed 530G - 551 3 | 4 | # 57 64 761802070E 5 | # 511,4/7/14,02:24:54,4/7/14 02:24:54,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBolusScrollStepSize,"STEP_SIZE=step_0_point_1, ACTION_REQUESTOR=pump",12753936698,53119959,104,MiniMed 530G - 551 6 | 7 | module MinimedRF 8 | module PumpEvents 9 | class ChangeBolusScrollStepSize < Base 10 | def self.event_type_code 11 | 0x57 12 | end 13 | 14 | def bytesize 15 | 7 16 | end 17 | 18 | def to_s 19 | "ChangeBolusScrollStepSize #{timestamp_str} stepsize=#{amount}" 20 | end 21 | 22 | def amount 23 | d(1) / 1000.0 24 | end 25 | 26 | def timestamp 27 | parse_date(2) 28 | end 29 | 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_meter_id.rb: -------------------------------------------------------------------------------- 1 | # 3601ad481208103701b20700000038000000000000 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class ChangeMeterId < Base 6 | 7 | def self.event_type_code 8 | 0x36 9 | end 10 | 11 | def bytesize 12 | 21 13 | end 14 | 15 | def timestamp 16 | parse_date(2) 17 | end 18 | 19 | def meter_id_at(pos) 20 | bytes = @data.byteslice(pos,3) 21 | (bytes.getbyte(0) << 16) + (bytes.getbyte(1) << 8) + bytes.getbyte(2) 22 | end 23 | 24 | def link1 25 | meter_id_at(8) 26 | end 27 | 28 | def link2 29 | meter_id_at(11) 30 | end 31 | 32 | def link3 33 | meter_id_at(15) 34 | end 35 | 36 | def to_s 37 | "ChangeMeterId #{timestamp_str} link1:#{link1} link2:#{link2} link3:#{link3}" 38 | #{}"ChangeMeterId #{timestamp_str} link1:#{link1} link2:#{link2} link3:#{link3}" 39 | end 40 | 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_sensor_alarm_silence_config.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class ChangeSensorAlarmSilenceConfig < Base 5 | def self.event_type_code 6 | 0x53 7 | end 8 | 9 | def bytesize 10 | 8 11 | end 12 | 13 | def alert_type_str 14 | end 15 | 16 | def alert_type 17 | # 0=off, 1=hi, 2=lo, 4=hi_lo, 8=all 18 | case d(1) 19 | when 0 20 | :off 21 | when 1 22 | :high 23 | when 2 24 | :low 25 | when 4 26 | :high_low 27 | when 8 28 | :all 29 | else 30 | :unknown 31 | end 32 | end 33 | 34 | def duration 35 | ((d(4) & 0b11100000) << 3) + d(7) 36 | end 37 | 38 | def timestamp 39 | parse_date(2) 40 | end 41 | 42 | def to_s 43 | "ChangeSensorAlarmSilenceConfig (alert_type=#{alert_type}) (duration=#{duration}m) #{timestamp_str}" 44 | end 45 | 46 | 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/result_daily_total.rb: -------------------------------------------------------------------------------- 1 | require 'date' 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class ResultDailyTotal < Base 6 | 7 | def self.event_type_code 8 | 0x07 9 | end 10 | 11 | def bytesize 12 | if @pump_model.larger 13 | 10 14 | else 15 | 7 16 | end 17 | end 18 | 19 | def to_s 20 | "ResultDailyTotal #{timestamp_str}" # TODO: figure out what this contains 21 | end 22 | 23 | def valid_date 24 | year, month, day = parse_date_2byte(5) 25 | Date.new(year, month, day) 26 | end 27 | 28 | def valid_date_str 29 | d = valid_date 30 | sprintf("%04d-%02d-%02d", d.year, d.month, d.day) 31 | end 32 | 33 | def timestamp 34 | midnight = valid_date+1 35 | [midnight.year, midnight.month, midnight.day, 0, 0, 0] 36 | end 37 | 38 | def as_json 39 | super.merge({ 40 | valid_date: valid_date_str 41 | }) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /tasks/history.rake: -------------------------------------------------------------------------------- 1 | require 'minimed_rf' 2 | require 'open-uri' 3 | 4 | namespace :history do 5 | desc "Compare history records to decocare" 6 | task :decocare_compare do 7 | 8 | type_registry = MinimedRF::HistoryPage.type_registry 9 | 10 | decocare_list = 'https://gist.githubusercontent.com/bewest/b531b24763ff172ee49b/raw/8b2ee96da1a6d491eca1c640601bffb223545ab4/history.txt' 11 | open(decocare_list).read.each_line do |line| 12 | model, details = line.chomp.split(':') 13 | name, code, head, date, body = details.split 14 | decocare_len = head.to_i + date.to_i + body.to_i 15 | h_type = type_registry[code.to_i] 16 | code = "0x%02x" % code.to_i 17 | if h_type.nil? 18 | puts "#{code} - #{model}/#{name} (#{decocare_len}) -> ***MISSING***" 19 | else 20 | mmrf_len = h_type.new("\x00\x00", MinimedRF::Model522.new).bytesize 21 | mmrf_name = h_type.to_s.split("::").last 22 | puts "#{code} - #{model}/#{name} (#{decocare_len}) -> #{mmrf_name} (#{mmrf_len})" 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/cal_bg_for_ph.rb: -------------------------------------------------------------------------------- 1 | 2 | # 229,4/4/14,15:07:00,4/4/14 15:07:00,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CalBGForGH,"AMOUNT=197, ORIGIN_TYPE=rf",12741001239,53115169,300,MiniMed 530G - 551 3 | # 0a c5 40072f640e 4 | # [10, 197, 64, 7, 47, 100, 14] 5 | # xxxxxxxx xxxxxxxx MMxxxxxx MMxxxxxx ???xxxxx ???xxxxx xxxxxxxx 6 | # cmd bgl seconds minutes hours day year 7 | # 00001010 11000101 01000000 00000111 00101111 01100100 00001110 8 | 9 | module MinimedRF 10 | module PumpEvents 11 | class CalBGForPH < Base 12 | 13 | def self.event_type_code 14 | 0x0a 15 | end 16 | 17 | def bytesize 18 | 7 19 | end 20 | 21 | def to_s 22 | "CalBGForPH #{timestamp_str} amount:#{amount}" 23 | end 24 | 25 | def amount 26 | ((d(6) & 0b10000000) << 1) + d(1) 27 | end 28 | 29 | def timestamp 30 | parse_date(2) 31 | end 32 | 33 | def as_json 34 | super.merge({ 35 | amount: amount 36 | }) 37 | end 38 | 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /load_ns_rfpackets.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'open-uri' 4 | require 'json' 5 | require 'dotenv' 6 | begin 7 | require 'minimed_rf' 8 | rescue LoadError 9 | require 'rubygems' 10 | require 'minimed_rf' 11 | end 12 | require 'openssl' 13 | 14 | Dotenv.load 15 | 16 | ns_url = ENV["NIGHTSCOUT_URL"] or abort("Please set NIGHTSCOUT_URL environment variable") 17 | 18 | count = (ARGV[0] || 100).to_i 19 | 20 | if count == 0 21 | abort("invalid count") 22 | end 23 | 24 | uri = URI.parse(ns_url + "/api/v1/entries.json?find[type]=rfpacket&count=#{count}") 25 | 26 | packets = JSON.parse(uri.read) 27 | 28 | packets.each do |p| 29 | packet = MinimedRF::Packet.from_hex(p["rfpacket"]) 30 | if !packet.valid? 31 | puts "Skipping #{p["rfpacket"]}" 32 | next 33 | end 34 | 35 | puts "#{p["dateString"]} - #{p["rfpacket"]}" 36 | m = packet.to_message 37 | puts " " + m.to_s 38 | #v = m.b(:x1) 39 | #puts "#{m.to_s} %02x %06b" % [v,v] 40 | 41 | #puts m.b(:x1) 42 | #puts p["hexBody"] 43 | #puts m.print_unused_bits 44 | 45 | end 46 | -------------------------------------------------------------------------------- /bin/mmlisten: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This utility talks over a serial connection to a RileyLink that has been 4 | # loaded with the subg_rfspy firmware (https://github.com/ps2/subg_rfspy) 5 | 6 | # It asks the cc1110 to listen for packets, receives them, decodes, and prints them out. 7 | 8 | # Must install the serialport gem to use this. 9 | 10 | require 'minimed_rf' 11 | require 'minimed_rf/rfspy' 12 | 13 | if ARGV.length < 1 14 | puts "Usage: mmlisten /dev/tty.usbserial-A9048LGG [channel]" 15 | exit -1 16 | end 17 | 18 | 19 | channel = 0 20 | if ARGV.length == 2 21 | channel = ARGV[1].to_i 22 | end 23 | 24 | puts "Opening #{ARGV[0]}" 25 | rl = MinimedRF::RFSpy.new(ARGV[0]) 26 | rl.sync 27 | rl.set_base_freq(916.630) 28 | # wide rx filter 29 | rl.update_register(MinimedRF::RFSpy::REG_MDMCFG4, 0x19) 30 | rl.update_register(MinimedRF::RFSpy::REG_FREND1, 0xB6) 31 | 32 | while 1 33 | packet = rl.get_packet(channel, 30 * 1000) 34 | if packet 35 | puts "#{Time.now.strftime('%H:%M:%S.%3N')} #{"%3d" % packet.rssi} (#{"%3d" % packet.sequence}): #{packet}" 36 | #puts "Raw: #{packet.hex_data}" 37 | else 38 | print "." 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/temp_basal_duration.rb: -------------------------------------------------------------------------------- 1 | # 33 3C 460615060E 081614 460615060E 2 | # 301,4/6/14,21:06:06,4/6/14 21:06:06,,,,60,Percent,10:00:00,,,,,,,,,,,,,,,,,,,,,,,,ChangeTempBasalPercent,"PERCENT_OF_RATE=60, DURATION=36000000, ACTION_REQUESTOR=pump",12753293982,53119725,90,MiniMed 530G - 551 3 | 4 | # 33 46 533414050e 081618 533414050e 5 | # 700,4/5/14,20:52:19,4/5/14 20:52:19,,,,70,Percent,12:00:00,,,,,,,,,,,,,,,,,,,,,,,,ChangeTempBasalPercent,"PERCENT_OF_RATE=70, DURATION=43200000, ACTION_REQUESTOR=pump",12753297028,53119725,136,MiniMed 530G - 551 6 | 7 | 8 | module MinimedRF 9 | module PumpEvents 10 | class TempBasalDuration < Base 11 | 12 | def self.event_type_code 13 | 0x16 14 | end 15 | 16 | def bytesize 17 | 7 18 | end 19 | 20 | def timestamp 21 | parse_date(2) 22 | end 23 | 24 | def duration 25 | d(1) * 30 26 | end 27 | 28 | def to_s 29 | "TempBasalDuration #{timestamp_str} duration:#{duration}" 30 | end 31 | 32 | def as_json 33 | super.merge({ 34 | duration: duration, 35 | }) 36 | end 37 | 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /docs/PumpStatus.packetdiag: -------------------------------------------------------------------------------- 1 | packetdiag { 2 | colwidth=32 3 | 0-7: packet_type [color = "none"] 4 | 8-31: addr [color = "none"] 5 | 32-39: message_type [color = "none"] 6 | 41-47: sequence [color = "none"] 7 | 52-54: trend [color = "none"] 8 | 59-63: pump_hour [color = "none"] 9 | 66-71: pump_minute [color = "none"] 10 | 74-79: pump_second [color = "none"] 11 | 80-87: pump_year [color = "none"] 12 | 92-95: pump_month [color = "none"] 13 | 99-103: pump_day [color = "none"] 14 | 112-119: bg_h [color = "none"] 15 | 120-127: prev_bg_h [color = "none"] 16 | 141-151: insulin_remaining [color = "none"] 17 | 156-159: batt [color = "none"] 18 | 160-167: reservoir_days_remaining [color = "none"] 19 | 168-183: reservoir_minutes_remaining [color = "none"] 20 | 184-191: sensor_age [color = "none"] 21 | 192-199: sensor_remaining [color = "none"] 22 | 200-207: next_cal_hour [color = "none"] 23 | 208-215: next_cal_minute [color = "none"] 24 | 221-231: active_ins [color = "none"] 25 | 238-238: prev_bg_l [color = "none"] 26 | 239-239: bg_l [color = "none"] 27 | 267-271: sensor_hour [color = "none"] 28 | 274-279: sensor_minute [color = "none"] 29 | 288-295: sensor_year [color = "none"] 30 | 300-303: sensor_month [color = "none"] 31 | 307-311: sensor_day [color = "none"] 32 | } 33 | -------------------------------------------------------------------------------- /generate_diags.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'minimed_rf' 4 | 5 | module MinimedRF 6 | class Message 7 | def self.descendants 8 | ObjectSpace.each_object(Class).select { |klass| klass < self } 9 | end 10 | end 11 | end 12 | 13 | message_types = MinimedRF::Message.descendants.map {|c| c.to_s}.sort 14 | 15 | message_types.each do |message_type| 16 | class_name = message_type.split('::').last 17 | message_class = MinimedRF.const_get(class_name) 18 | File.open("docs/#{class_name}.packetdiag", "w") do |f| 19 | f.write(message_class.packetdiag) 20 | end 21 | `packetdiag -T svg docs/#{class_name}.packetdiag` 22 | end 23 | 24 | File.open('docs/README.md', "w") do |f| 25 | f.write("# Packet Diagrams\n") 26 | message_types.each do |message_type| 27 | class_name = message_type.split('::').last 28 | message_class = MinimedRF.const_get(class_name) 29 | f.write("## #{class_name}\n") 30 | filename = File.basename(message_class.method(:bit_blocks).source_location.first) 31 | f.write("[#{filename}](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/#{filename})") 32 | f.write("![#{class_name}](https://rawgit.com/ps2/minimed_rf/master/docs/#{class_name}.svg)\n\n") 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/sensor.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class Sensor < Message 4 | 5 | # a80f25c1230d191c50008f009000343499000000000000000000000000000000dee7 6 | def self.bit_blocks 7 | { 8 | state: [4,4], 9 | address: [16,24], 10 | version: [40,8], 11 | isig_adj: [56,8], 12 | sequence: [64,4], 13 | repeat: [68,4], 14 | isig: [72,16], 15 | } 16 | end 17 | 18 | def state 19 | case b(:state) 20 | when 0xb 21 | "normal" 22 | when 0xa 23 | "warm-up" 24 | when 0x8 25 | "testing" 26 | end 27 | end 28 | 29 | def sequence 30 | b(:sequence) 31 | end 32 | 33 | def version 34 | major = b(:version) / 10 35 | minor = b(:version) % 10 36 | "#{major}.#{minor}" 37 | end 38 | 39 | def device_address 40 | b(:address) 41 | end 42 | 43 | def isig_adj 44 | b(:isig_adj) 45 | end 46 | 47 | def isig 48 | b(:isig) 49 | end 50 | 51 | def sequence 52 | b(:sequence) 53 | end 54 | 55 | def repeat 56 | b(:repeat) 57 | end 58 | 59 | def to_s 60 | "Sensor: #{state} #{device_address} v#{version} ##{sequence}.#{repeat} adjust: #{isig_adj} isig: #{isig}" 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/bg_received.rb: -------------------------------------------------------------------------------- 1 | # 3f 18 4007af640e c5 27ad 2 | # xxxxxxxx xxxxxxxx ??xxxxxx ??xxxxxx BGLxxxxx ???xxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx 3 | # cmd bgh seconds minutes hours day year meterid meterid meterid 4 | # 00111111 00011000 01000000 00000111 10101111 01100100 00001110 11000101 00100111 10101101 5 | # 231,4/4/14,15:07:00,4/4/14 15:07:00,,197,#C527AD,,,,,,,,,,,,,,,,,,,,,,,,,,,BGReceived,"AMOUNT=197, ACTION_REQUESTOR=paradigm link or b key, PARADIGM_LINK_ID=C527AD",12741001048,53115169,109,MiniMed 530G - 551 6 | 7 | 8 | module MinimedRF 9 | module PumpEvents 10 | class BGReceived < Base 11 | 12 | def self.event_type_code 13 | 0x3f 14 | end 15 | 16 | def bytesize 17 | 10 18 | end 19 | 20 | def to_s 21 | "BGReceived #{timestamp_str} BG:#{amount} METER:#{paradigm_link_id}" 22 | end 23 | 24 | def amount 25 | (d(1) << 3) + (d(4) >> 5) 26 | end 27 | 28 | def paradigm_link_id 29 | @data.byteslice(7,3).unpack("H*").first 30 | end 31 | 32 | def timestamp 33 | parse_date(2) 34 | end 35 | 36 | def as_json 37 | super.merge({ 38 | amount: amount, 39 | link: paradigm_link_id, 40 | }) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_reservoir_warning_time.rb: -------------------------------------------------------------------------------- 1 | 2 | # 6541 5E1A02070E 3 | # 522,4/7/14,02:26:30,4/7/14 02:26:30,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeReservoirWarningTime,"AMOUNT=28800000, ACTION_REQUESTOR=pump",12753936688,53119959,94,MiniMed 530G - 551 4 | 5 | # 6524 641A02070E 6 | # 523,4/7/14,02:26:36,4/7/14 02:26:36,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeReservoirWarningInsulin,"AMOUNT=9, ACTION_REQUESTOR=pump",12753936687,53119959,93,MiniMed 530G - 551 7 | 8 | module MinimedRF 9 | module PumpEvents 10 | class ChangeReservoirWarningTime < Base 11 | 12 | def self.event_type_code 13 | 0x65 14 | end 15 | 16 | def bytesize 17 | 7 18 | end 19 | 20 | def to_s 21 | "ChangeReservoirWarningTime #{change_type_str} #{timestamp_str} amount:#{amount}" 22 | end 23 | 24 | def change_type 25 | d(1) & 0x11 26 | end 27 | 28 | def change_type_str 29 | { 30 | 0 => "ChangeReservoirWarningTime", 31 | 1 => "ChangeReservoirWarningInsulin" 32 | }[change_type] 33 | end 34 | 35 | def amount 36 | { 37 | 0 => (d(1) >> 2), 38 | 1 => (d(1) >> 2) * 1800000 39 | }[change_type] 40 | end 41 | 42 | def timestamp 43 | parse_date(2) 44 | end 45 | 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/temp_basal.rb: -------------------------------------------------------------------------------- 1 | # 33 3C 460615060E 081614 460615060E 2 | # 301,4/6/14,21:06:06,4/6/14 21:06:06,,,,60,Percent,10:00:00,,,,,,,,,,,,,,,,,,,,,,,,ChangeTempBasalPercent,"PERCENT_OF_RATE=60, DURATION=36000000, ACTION_REQUESTOR=pump",12753293982,53119725,90,MiniMed 530G - 551 3 | 4 | # 33 46 533414050e 081618 533414050e 5 | # 700,4/5/14,20:52:19,4/5/14 20:52:19,,,,70,Percent,12:00:00,,,,,,,,,,,,,,,,,,,,,,,,ChangeTempBasalPercent,"PERCENT_OF_RATE=70, DURATION=43200000, ACTION_REQUESTOR=pump",12753297028,53119725,136,MiniMed 530G - 551 6 | 7 | 8 | module MinimedRF 9 | module PumpEvents 10 | class TempBasal < Base 11 | 12 | def self.event_type_code 13 | 0x33 14 | end 15 | 16 | def bytesize 17 | 8 18 | end 19 | 20 | def timestamp 21 | parse_date(2) 22 | end 23 | 24 | def rate_type 25 | (d(7) >> 3) == 0 ? 'absolute' : 'percent' 26 | end 27 | 28 | def rate 29 | if rate_type == 'absolute' 30 | (((d(7) & 0b111) << 8) + d(1)) / 40.0 31 | else 32 | d(1) 33 | end 34 | end 35 | 36 | def to_s 37 | "TempBasal #{timestamp_str} rate_type:#{rate_type} rate:#{rate}" 38 | end 39 | 40 | def as_json 41 | super.merge({ 42 | rate: rate, 43 | temp: rate_type 44 | }) 45 | end 46 | 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/basal_profile_start.rb: -------------------------------------------------------------------------------- 1 | # 7B04 400010040E 200B00 2 | # 250,4/4/14,16:00:00,4/4/14 16:00:00,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BasalProfileStart, 3 | # "PATTERN_NAME=standard, PROFILE_INDEX=4, RATE=0.275, START_TIME=57600000, ACTION_REQUESTOR=pump",12741001041,53115169,102,MiniMed 530G - 551 4 | 5 | 6 | # 7B05 400015040E 2A0C00 7 | # 420,4/4/14,21:00:00,4/4/14 21:00:00,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,BasalProfileStart, 8 | # "PATTERN_NAME=standard, PROFILE_INDEX=5, RATE=0.3, START_TIME=75600000, ACTION_REQUESTOR=pump",12741001901,53115170,86,MiniMed 530G - 551 9 | 10 | 11 | module MinimedRF 12 | module PumpEvents 13 | class BasalProfileStart < Base 14 | 15 | def self.event_type_code 16 | 0x7b 17 | end 18 | 19 | def bytesize 20 | 10 21 | end 22 | 23 | def to_s 24 | "BasalProfileStart #{timestamp_str} #{profile_index} rate:#{rate}" 25 | end 26 | 27 | def timestamp 28 | parse_date(2) 29 | end 30 | 31 | def rate 32 | d(8) / 40.0 # TODO: get high bits 33 | end 34 | 35 | def profile_index 36 | d(1) 37 | end 38 | 39 | def start_time 40 | d(7) * 30*1000*60 41 | end 42 | 43 | def as_json 44 | super.merge({ 45 | offset: start_time, 46 | rate: rate, 47 | profile_index: profile_index 48 | }) 49 | end 50 | 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/unabsorbed_insulin.rb: -------------------------------------------------------------------------------- 1 | # 5c0b 2806c0 2892c0 445ad0 2 | 3 | module MinimedRF 4 | module PumpEvents 5 | class UnabsorbedInsulin < Base 6 | 7 | class Record 8 | attr_accessor :amount, :age 9 | def to_s 10 | "Amount:#{amount} Age:#{age}" 11 | end 12 | end 13 | 14 | def self.event_type_code 15 | 0x5c 16 | end 17 | 18 | def bytesize 19 | [2, d(1)].max 20 | end 21 | 22 | def to_s 23 | "UnabsorbedInsulin #{records.length} entries, #{records.map(&:amount).inject {|sum,amount| sum + amount}}U total" 24 | end 25 | 26 | def records 27 | (0..(num_records-1)).to_a.map do |idx| 28 | record_for_idx(idx) 29 | end 30 | end 31 | 32 | def record_for_idx(idx) 33 | record = Record.new 34 | return record if @data.bytesize < bytesize 35 | record.amount = d(2 + idx * 3) / 40.0 36 | record.age = d(3 + idx * 3) + ((d(4 + idx * 3) & 0b110000) << 4) 37 | record 38 | end 39 | 40 | def num_records 41 | (d(1) - 2) / 3 42 | end 43 | 44 | def timestamp 45 | nil 46 | end 47 | 48 | def valid_for(date_range) 49 | num_records > 0 && num_records < 100 50 | end 51 | 52 | def as_json 53 | super.merge({ 54 | data: records.map{|r| {amount: r.amount, age: r.age}} 55 | }) 56 | end 57 | 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/minimed_rf/pump_models.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | class BasePumpModel 3 | def larger 4 | false 5 | end 6 | 7 | def has_low_suspend 8 | false 9 | end 10 | 11 | def strokes_per_unit 12 | 10 13 | end 14 | end 15 | 16 | class Model508 < BasePumpModel; end 17 | 18 | class Model511 < Model508; end 19 | 20 | class Model512 < Model511; end 21 | 22 | class Model515 < Model512; end 23 | 24 | class Model522 < Model515; end 25 | 26 | class Model722 < Model522; end 27 | 28 | class Model523 < Model522 29 | def larger 30 | true 31 | end 32 | 33 | def strokes_per_unit 34 | 40 35 | end 36 | end 37 | 38 | class Model723 < Model523; end 39 | 40 | class Model530 < Model523; end 41 | 42 | class Model730 < Model530; end 43 | 44 | class Model540 < Model530; end 45 | 46 | class Model740 < Model540; end 47 | 48 | class Model551 < Model540 49 | def has_low_suspend 50 | true 51 | end 52 | end 53 | 54 | class Model751 < Model551; end 55 | 56 | class Model554 < Model551; end 57 | 58 | class Model754 < Model554; end 59 | 60 | Models = { 61 | '508' => Model508, 62 | '511' => Model511, 63 | '512' => Model512, 64 | '515' => Model515, 65 | '522' => Model522, 66 | '523' => Model523, 67 | '530' => Model530, 68 | '540' => Model540, 69 | '551' => Model551, 70 | '554' => Model554, 71 | '722' => Model722, 72 | '723' => Model723, 73 | '730' => Model730, 74 | '740' => Model740, 75 | '751' => Model751, 76 | '754' => Model754 77 | } 78 | end 79 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/alert.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | 4 | AlertCodes = { 5 | 0x04 => :no_delivery, 6 | 0x33 => :max_hourly_bolus, 7 | 0x52 => :low_reservoir, 8 | 0x65 => :high_glucose, 9 | 0x66 => :low_glucose, 10 | 0x68 => :meter_bg_now, 11 | 0x69 => :meter_bg_soon, 12 | 0x6a => :calibration_error, 13 | 0x6b => :sensor_end, 14 | 0x70 => :weak_signal, 15 | 0x71 => :lost_sensor, 16 | 0x72 => :high_predicted, 17 | 0x73 => :low_predicted, 18 | } 19 | 20 | class Alert < Message 21 | 22 | # Max hourly bolus 23 | # 2014-09-08T22:01:09-0500 - a2 350535 01 e3 33 153b1d 0e 09 08 5200 d8 24 | 25 | def self.bit_blocks 26 | { 27 | sequence: [1,7], 28 | alert_type: [8,8], 29 | alert_hour: [19,5], 30 | alert_minute: [26,6], 31 | alert_second: [34,6], 32 | alert_year: [40,8], 33 | alert_month: [52,4], 34 | alert_day: [59,5], 35 | } 36 | end 37 | 38 | def sequence 39 | b(:sequence) 40 | end 41 | 42 | def alert_type 43 | AlertCodes[b(:alert_type)] 44 | end 45 | 46 | def timestamp 47 | if b(:alert_year) > 0 48 | Time.new(b(:alert_year) + 2000, b(:alert_month), b(:alert_day), b(:alert_hour), b(:alert_minute), b(:alert_second)) 49 | end 50 | end 51 | 52 | def alert_type_str 53 | case alert_type 54 | when :max_hourly_bolus 55 | "Max Hourly Bolus" 56 | when :high_predicted 57 | "High Predicted" 58 | when :meter_bg_now 59 | "Meter BG Now" 60 | else 61 | "Unknown(#{b(:alert_type)})" 62 | end 63 | end 64 | 65 | def to_s 66 | "Alert: #{timestamp} ##{sequence} \"#{alert_type_str}\"" 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | minimed_rf (0.1.0) 5 | colorize 6 | serialport (~> 1.3, >= 1.3.1) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | celluloid (0.15.2) 12 | timers (~> 1.1.0) 13 | coderay (1.1.0) 14 | colorize (0.7.7) 15 | diff-lcs (1.2.5) 16 | ffi (1.9.3) 17 | formatador (0.2.5) 18 | guard (2.6.1) 19 | formatador (>= 0.2.4) 20 | listen (~> 2.7) 21 | lumberjack (~> 1.0) 22 | pry (>= 0.9.12) 23 | thor (>= 0.18.1) 24 | guard-rspec (4.3.1) 25 | guard (~> 2.1) 26 | rspec (>= 2.14, < 4.0) 27 | listen (2.7.9) 28 | celluloid (>= 0.15.2) 29 | rb-fsevent (>= 0.9.3) 30 | rb-inotify (>= 0.9) 31 | lumberjack (1.0.9) 32 | method_source (0.8.2) 33 | pry (0.10.0) 34 | coderay (~> 1.1.0) 35 | method_source (~> 0.8.1) 36 | slop (~> 3.4) 37 | rake (10.3.2) 38 | rb-fsevent (0.9.4) 39 | rb-inotify (0.9.5) 40 | ffi (>= 0.5.0) 41 | rspec (3.0.0) 42 | rspec-core (~> 3.0.0) 43 | rspec-expectations (~> 3.0.0) 44 | rspec-mocks (~> 3.0.0) 45 | rspec-core (3.0.3) 46 | rspec-support (~> 3.0.0) 47 | rspec-expectations (3.0.3) 48 | diff-lcs (>= 1.2.0, < 2.0) 49 | rspec-support (~> 3.0.0) 50 | rspec-mocks (3.0.3) 51 | rspec-support (~> 3.0.0) 52 | rspec-nc (0.1.1) 53 | rspec (>= 2.9) 54 | terminal-notifier (>= 1.4) 55 | rspec-support (3.0.3) 56 | serialport (1.3.1) 57 | slop (3.6.0) 58 | terminal-notifier (1.6.1) 59 | thor (0.19.1) 60 | timers (1.1.0) 61 | 62 | PLATFORMS 63 | ruby 64 | 65 | DEPENDENCIES 66 | bundler (~> 1.3) 67 | guard 68 | guard-rspec 69 | minimed_rf! 70 | rake 71 | rspec 72 | rspec-nc 73 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/daily_totals.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | 4 | class DailyTotal515 < Base 5 | 6 | def self.event_type_code 7 | 0x6c 8 | end 9 | 10 | def bytesize 11 | 38 12 | end 13 | 14 | def timestamp 15 | parse_date_2byte(1) 16 | end 17 | 18 | def to_s 19 | "DailyTotal515 #{timestamp_str}" 20 | end 21 | 22 | end 23 | 24 | class DailyTotal522 < Base 25 | 26 | def self.event_type_code 27 | 0x6d 28 | end 29 | 30 | def bytesize 31 | 44 32 | end 33 | 34 | def timestamp 35 | parse_date_2byte(2) 36 | end 37 | 38 | def to_s 39 | "DailyTotal522 #{timestamp_str}" 40 | end 41 | 42 | end 43 | 44 | 45 | class DailyTotal523 < Base 46 | 47 | def self.event_type_code 48 | 0x6e 49 | end 50 | 51 | def bytesize 52 | 52 53 | end 54 | 55 | def valid_date 56 | year, month, day = parse_date_2byte(1) 57 | if year > 0 && month > 0 && day > 0 58 | Date.new(year, month, day) 59 | end 60 | end 61 | 62 | def valid_date_str 63 | if valid_date 64 | d = valid_date 65 | sprintf("%04d-%02d-%02d", d.year, d.month, d.day) 66 | end 67 | end 68 | 69 | def timestamp 70 | if valid_date 71 | midnight = valid_date+1 72 | [midnight.year, midnight.month, midnight.day, 0, 0, 0] 73 | end 74 | end 75 | 76 | def as_json 77 | super.merge({ 78 | valid_date: valid_date_str 79 | }) 80 | end 81 | 82 | def to_s 83 | "DailyTotal523 #{timestamp_str}" 84 | end 85 | 86 | end 87 | 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/alarm_sensor.rb: -------------------------------------------------------------------------------- 1 | # 0B 73 006F3532A40E - [2014, 4, 4, 18, 53, 47] 2 | 3 | # 297,4/4/14,18:53:47,4/4/14 18:53:47,,,,,,,,,,,,,,,,,,,,,,,,,Sensor Alert: Low Glucose Predicted (115),,,,,AlarmSensor,"ALARM_TYPE=115, AMOUNT=0, ACTION_REQUESTOR=sensor",12741001030,53115169,91,MiniMed 530G - 551 4 | 5 | 6 | module MinimedRF 7 | module PumpEvents 8 | class AlarmSensor < Base 9 | 10 | def self.event_type_code 11 | 0x0b 12 | end 13 | 14 | def bytesize 15 | 8 16 | end 17 | 18 | def to_s 19 | "AlarmSensor #{timestamp_str} #{alarm_type_str} amount:#{amount}" 20 | end 21 | 22 | def alarm_types 23 | { 24 | 101 => "High Glucose", 25 | 102 => "Low Glucose", 26 | 104 => "Meter BG Now", 27 | 105 => "Cal Reminder", 28 | 106 => "Calibration Error", 29 | 107 => "Sensor End", 30 | 112 => "Weak Signal", 31 | 113 => "Lost Sensor", 32 | 114 => "High Glucose Predicted", 33 | 115 => "Low Glucose Predicted" 34 | } 35 | end 36 | 37 | def alarm_type_str 38 | alarm_types[alarm_type] || alarm_type.to_s 39 | end 40 | 41 | def alarm_type 42 | d(1) 43 | end 44 | 45 | def amount 46 | ((d(7) & 0b10000000) << 1) + d(2) 47 | end 48 | 49 | def timestamp 50 | parse_date(3) 51 | end 52 | 53 | def profile_index 54 | d(1) 55 | end 56 | 57 | def as_json 58 | r = super.merge({ 59 | alarm_description: alarm_type_str, 60 | alarm_type: alarm_type 61 | }) 62 | if amount > 0 63 | r[:amount] = amount 64 | end 65 | r 66 | end 67 | 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/packet_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe MinimedRF::Packet do 4 | it "should parse data from radio" do 5 | radio_data = "ab29595959655743a5d31c7254ec4b54e55a54b555d0dd0e5555716aa563571566c9ac7258e565574555d1c55555555568bc7256c55554e55a54b55555556c55" 6 | packet = MinimedRF::Packet.decode_from_radio_hex(radio_data) 7 | expect(packet.address).to eq "597055" 8 | expect(packet.packet_type).to eq 0xa2 9 | expect(packet.message_type).to eq 0x04 10 | end 11 | 12 | it "should accept decoded hex data" do 13 | hex_data = "a259705504a24117043a0e080b003d3d00015b030105d817790a0f00000300008b1702000e080b000071" 14 | packet = MinimedRF::Packet.from_hex(hex_data) 15 | expect(packet.address).to eq "597055" 16 | expect(packet.packet_type).to eq 0xa2 17 | expect(packet.message_type).to eq 0x04 18 | end 19 | 20 | it "should convert to a message" do 21 | hex_data = "a259705504a24117043a0e080b003d3d00015b030105d817790a0f00000300008b1702000e080b000071" 22 | packet = MinimedRF::Packet.from_hex(hex_data) 23 | message = packet.to_message 24 | expect(message).to be_a MinimedRF::Message 25 | end 26 | 27 | it "should encode back to radio symbols" do 28 | hex_data = "a259705504a24117043a0e080b003d3d00015b030105d817790a0f00000300008b1702000e080b000071" 29 | packet = MinimedRF::Packet.from_hex(hex_data) 30 | radio_symbols = packet.encode 31 | expect(radio_symbols).to eq "ab2959595965574ab2d31c565748ea54e55a54b5558cd8cd55557194b56357156535ac5659956a55c55555556355555568bc5657255554e55a54b5555555b100" 32 | end 33 | 34 | it "should build raw packet from hex without crc" do 35 | hex_data = "a259705504a24117043a0e080b003d3d00015b030105d817790a0f00000300008b1702000e080b0000" 36 | packet = MinimedRF::Packet.from_hex_without_crc(hex_data) 37 | radio_symbols = packet.encode 38 | expect(radio_symbols).to eq "ab2959595965574ab2d31c565748ea54e55a54b5558cd8cd55557194b56357156535ac5659956a55c55555556355555568bc5657255554e55a54b5555555b100" 39 | end 40 | 41 | 42 | end 43 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/restore_settings_mystery_events.rb: -------------------------------------------------------------------------------- 1 | # These are events that were observed by selecting the User Settings -> Restore 2 | # menu on 551 and 522 pumps, that I do not know the function of. They do 3 | # decode with valid dates, though, so I'm fairly sure that we're parsing 4 | # at the correct byte boundaries. 5 | 6 | module MinimedRF 7 | module PumpEvents 8 | 9 | class RestoreMystery51 < Base 10 | def self.event_type_code 11 | 0x51 12 | end 13 | 14 | def bytesize 15 | 7 16 | end 17 | 18 | def to_s 19 | "RestoreMystery51 #{timestamp_str}" 20 | end 21 | 22 | def timestamp 23 | parse_date(2) 24 | end 25 | 26 | end 27 | 28 | class RestoreMystery52 < Base 29 | def self.event_type_code 30 | 0x52 31 | end 32 | 33 | def bytesize 34 | 7 35 | end 36 | 37 | def to_s 38 | "RestoreMystery52 #{timestamp_str}" 39 | end 40 | 41 | def timestamp 42 | parse_date(2) 43 | end 44 | 45 | end 46 | 47 | # 54fca71d209c10fffcff00e65000ffff00ffff00ffff00ffff00ffff00ffff00fffffcfffcff00f05000ffff00ffff00ffff00ffff00ffff00ffff00ffff 48 | class RestoreMystery54 < Base 49 | def self.event_type_code 50 | 0x54 51 | end 52 | 53 | def bytesize 54 | 64 55 | end 56 | 57 | def to_s 58 | "RestoreMystery54 #{timestamp_str}" 59 | end 60 | 61 | def timestamp 62 | parse_date(2) 63 | end 64 | 65 | end 66 | 67 | # 5511a71d809c10000f0f00ffff00ffff00ffff00ffff00ffff00ffff00ffff000f0f00ffff00ffff00ffff00ffff00ffff00ffff00ffff56 68 | class RestoreMystery55 < Base 69 | def self.event_type_code 70 | 0x55 71 | end 72 | 73 | def bytesize 74 | 55 75 | end 76 | 77 | def to_s 78 | "RestoreMystery55 #{timestamp_str}" 79 | end 80 | 81 | def timestamp 82 | parse_date(2) 83 | end 84 | 85 | end 86 | 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /bin/mmpair: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This utility talks over a serial connection to a RileyLink that has been 4 | # loaded with the subg_rfspy firmware (https://github.com/ps2/subg_rfspy) 5 | 6 | # It attempts to enable mysentry packets on a minimed pump 7 | 8 | require 'minimed_rf' 9 | require 'minimed_rf/rfspy' 10 | 11 | if ARGV.length < 2 || ARGV[1].length != 6 12 | puts "Usage: mmpair /dev/tty.usbserial-A9048LGG pumpserial" 13 | if ARGV.length > 1 && ARGV[1].length != 6 14 | puts "Error: pumpserial should be a six character id, like '55AB12'" 15 | end 16 | exit -1 17 | end 18 | 19 | pump_serial = ARGV[1] 20 | 21 | def log(msg) 22 | puts "#{Time.now.strftime('%H:%M:%S.%3N')} #{msg}" 23 | end 24 | 25 | def print_packet(p) 26 | if p.nil? 27 | puts "Nil packet!" 28 | else 29 | log "#{"%3d" % p.rssi} (#{"%3d" % p.sequence}): #{p}" 30 | #puts "raw: #{p.hex_data}" 31 | end 32 | end 33 | 34 | puts "Opening #{ARGV[0]}" 35 | rf = MinimedRF::RFSpy.new(ARGV[0]) 36 | rf.sync 37 | 38 | rx_channel = 2 39 | tx_channel = 0 40 | rx_timeout_ms = 80 41 | counter = 0 42 | 43 | mock_mysentry_id = "d5770B" 44 | 45 | while 46 | 47 | packet = rf.get_packet(rx_channel, 30 * 1000) 48 | if packet 49 | print_packet(packet) 50 | end 51 | # 04000000 a2 350535 06 02 d57708 00 09 000000 0f 52 | 53 | if packet.to_message.is_a?(MinimedRF::FindDevice) 54 | counter_hex = "%02x" % packet.to_message.sequence 55 | message_type = "%02x" % packet.message_type 56 | out = "a2" + pump_serial + "06" + counter_hex + mock_mysentry_id + "00" + message_type + "000000" 57 | log "Sending #{out}" 58 | rf.send_packet(out, tx_channel) 59 | counter += 1 60 | end 61 | 62 | if packet.to_message.is_a?(MinimedRF::DeviceLink) 63 | counter_hex = "%02x" % packet.to_message.sequence 64 | message_type = "%02x" % packet.message_type 65 | out = "a2" + pump_serial + "06" + counter_hex + mock_mysentry_id + "00" + message_type + "000000" 66 | log "Sending #{out}" 67 | rf.send_packet(out, tx_channel) 68 | counter += 1 69 | puts "Done!" 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/base.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | module PumpEvents 3 | class Base 4 | def initialize(data, pump_model=nil) 5 | @data = data 6 | @pump_model = pump_model 7 | @data = @data.byteslice(0,bytesize) 8 | end 9 | 10 | def d(i) 11 | @data.getbyte(i) 12 | end 13 | 14 | def hex_str 15 | @data.unpack("H*").first 16 | end 17 | 18 | def parse_date_2byte(offset) 19 | day = d(offset) & 0x1f 20 | month = ((d(offset) & 0xe0) >> 4) + ((d(offset+1) & 0x80) >> 7) 21 | year = 2000 + (d(offset+1) & 0b1111111) 22 | [year, month, day, 0, 0, 0] 23 | end 24 | 25 | 26 | def parse_date(offset) 27 | if @data.bytesize > offset + 4 28 | sec = d(offset) & 0x3f 29 | min = d(offset+1) & 0x3f 30 | hour = d(offset+2) & 0x1f 31 | day = d(offset+3) & 0x1f 32 | month = ((d(offset) >> 4) & 0xc) + (d(offset+1) >> 6) 33 | year = 2000 + (d(offset+4) & 0b1111111) 34 | [year, month, day, hour, min, sec] 35 | end 36 | end 37 | 38 | def valid_for(date_range) 39 | return false if @data.bytesize < length 40 | begin 41 | time = Time.new(*timestamp) 42 | return date_range.cover?(time) 43 | rescue ArgumentError 44 | return false 45 | end 46 | end 47 | 48 | def timestamp 49 | parse_date(2) 50 | end 51 | 52 | def timestamp_str 53 | t = timestamp 54 | unless t.nil? 55 | year, month, day, hour, min, sec = timestamp 56 | sprintf("%04d-%02d-%02dT%02d:%02d:%02d", year, month, day, hour, min, sec) 57 | end 58 | end 59 | 60 | def as_json 61 | json = { 62 | _type: self.class.name.gsub(/^.*::/, ''), 63 | _raw: hex_str, 64 | description: to_s 65 | } 66 | t = timestamp 67 | unless t.nil? 68 | json[:timestamp] = timestamp_str 69 | end 70 | json 71 | end 72 | 73 | 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /bin/mmdecode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Takes a hex string as an argument and attempts to decode it. 4 | 5 | require 'optparse' 6 | require 'base64' 7 | require 'minimed_rf' 8 | 9 | def handle_input(input) 10 | return if input.bytesize < 6 || input[0] == "#" 11 | 12 | # remove spaces 13 | input = input.gsub(/\W/,'') 14 | 15 | if input.bytesize == 2048 16 | puts "Use the mmhistory command to decode history pages." 17 | exit 18 | end 19 | 20 | # Look for second byte 00, which is inserted by RF Studio as length field? 21 | if input[2,2] == "00" 22 | input.slice!(2,2) 23 | end 24 | 25 | # try raw 26 | packet = MinimedRF::Packet.decode_from_radio_hex(input) 27 | 28 | # try decoded 29 | if !packet.valid? 30 | packet = MinimedRF::Packet.from_hex(input) 31 | end 32 | puts packet.hex_data 33 | puts packet.to_s 34 | 35 | message = packet.to_message 36 | 37 | if message 38 | File.open("tmppacket.packetdiag", "w") do |f| 39 | f.print packet.packetdiag 40 | end 41 | `packetdiag -T svg tmppacket.packetdiag` 42 | 43 | mime_type = "image/svg+xml" 44 | encoded_text = Base64.strict_encode64(File.open("tmppacket.svg").read) 45 | 46 | html = <<-ENDOFHTML 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ENDOFHTML 56 | 57 | File.open("#{input}.html", "w") do |f| 58 | f.write(html) 59 | end 60 | end 61 | end 62 | 63 | options = {} 64 | OptionParser.new do |opts| 65 | opts.banner = "Usage: mmdecode.rb [options]" 66 | 67 | opts.on("-f", "--file FILE", "Read binary data directly from file") do |v| 68 | options[:file] = file 69 | end 70 | end.parse! 71 | 72 | if options[:file] 73 | handle_input(File.read(options[:file])) 74 | exit 75 | end 76 | 77 | if ARGV.length > 0 78 | handle_input(ARGV.join("")) 79 | else 80 | while line = STDIN.gets 81 | handle_input(line.chomp) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/bolus_normal.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class BolusNormal < Base 5 | 6 | def self.event_type_code 7 | 0x01 8 | end 9 | 10 | attr_accessor :unabsorbed_insulin_records, :amount, :programmed_amount, :unabsorbed, :type, :duration, :unabsorbed_insulin_total 11 | 12 | def initialize(data, pump_model=nil) 13 | super(data, pump_model) 14 | 15 | return if @data.bytesize < bytesize 16 | 17 | if @pump_model.larger 18 | @amount = insulin_decode(d(3), d(4)) 19 | @programmed_amount = insulin_decode(d(1), d(2)) 20 | @unabsorbed_insulin_total = insulin_decode(d(5), d(6)) 21 | @duration = d(7) * 30 22 | else 23 | @amount = d(2)/10.0 24 | @programmed_amount = d(1)/10.0 25 | @duration = d(3) * 30 26 | end 27 | @type = @duration > 0 ? "square" : "normal" 28 | end 29 | 30 | def bytesize 31 | if @pump_model.larger 32 | 13 33 | else 34 | 9 35 | end 36 | end 37 | 38 | def insulin_decode(a, b) 39 | ((a << 8) + b) / 40.0 40 | end 41 | 42 | def to_s 43 | "BolusNormal #{timestamp_str} #{amount} #{programmed_amount} #{unabsorbed_insulin_total}" 44 | end 45 | 46 | def timestamp 47 | if @pump_model.larger 48 | parse_date(8) 49 | else 50 | parse_date(4) 51 | end 52 | end 53 | 54 | def valid_for(date_range) 55 | super && amount < 30 && programmed_amount < 30 56 | end 57 | 58 | def as_json 59 | json = super.merge({ 60 | amount: amount, 61 | programmed: programmed_amount, 62 | type: type 63 | }) 64 | if !unabsorbed_insulin_records.nil? 65 | json[:appended] = unabsorbed_insulin_records.as_json 66 | end 67 | if !unabsorbed_insulin_total.nil? 68 | json[:unabsorbed] = unabsorbed_insulin_total 69 | end 70 | if !duration.nil? 71 | json[:duration] = duration 72 | end 73 | json 74 | end 75 | 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /bin/mmdumphistory: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This utility talks over a serial connection to a RileyLink that has been 4 | # loaded with the subg_rfspy firmware (https://github.com/ps2/subg_rfspy) 5 | 6 | # It attempts to wake the pump, and get history 7 | 8 | require 'minimed_rf' 9 | require 'minimed_rf/rfspy' 10 | 11 | if ARGV.length < 2 || ARGV[1].length != 6 12 | puts "Usage: mmdumphistory /dev/tty.usbserial-A9048LGG pumpserial" 13 | if ARGV.length > 1 && ARGV[1].length != 6 14 | puts "Error: pumpserial should be a six character id, like '55AB12'" 15 | end 16 | exit -1 17 | end 18 | 19 | pump_serial = ARGV[1] 20 | 21 | def print_packet(p) 22 | if p.nil? 23 | puts "Nil packet!" 24 | else 25 | puts "#{Time.now.strftime('%H:%M:%S.%3N')} #{"%3d" % p.rssi} (#{"%3d" % p.sequence}): #{p}" 26 | #puts "raw: #{p.hex_data}" 27 | end 28 | end 29 | 30 | puts "Opening #{ARGV[0]}" 31 | rf = MinimedRF::RFSpy.new(ARGV[0]) 32 | rf.sync 33 | 34 | if true # Customize radio params 35 | # Set rx bw to 150kHz and 16kbs data rate 36 | rf.update_register(MinimedRF::RFSpy::REG_MDMCFG4, 0xd9) 37 | 38 | rf.set_base_freq(916.763) 39 | 40 | # Sometimes getting lower ber with 0x07 here (default is 0x03) 41 | rf.update_register(MinimedRF::RFSpy::REG_AGCCTRL2, 0x07) 42 | 43 | # With rx bw > 101kzHZ, this should be 0xB6, otherwise 0x56 44 | rf.update_register(MinimedRF::RFSpy::REG_FREND1, 0x56) 45 | end 46 | 47 | rx_channel = 0 48 | tx_channel = 0 49 | rx_timeout_ms = 80 50 | 51 | # Try quick model check to see if pump is awake 52 | rf.send_packet("a7" + pump_serial + "8d00", tx_channel) 53 | packet = rf.get_packet(rx_channel, 80) 54 | if packet 55 | print_packet(packet) 56 | awake = true 57 | end 58 | 59 | if !awake 60 | # Send 200 wake-up packets 61 | rf.send_packet("a7" + pump_serial + "5d00", tx_channel, 200) 62 | wake_ack = rf.get_packet(rx_channel, 10000) # wait 10 s for response 63 | if wake_ack 64 | print_packet(wake_ack) 65 | else 66 | puts "Pump not responding" 67 | exit -1 68 | end 69 | 70 | # Get model 71 | rf.send_packet("a7" + pump_serial + "8d00", tx_channel) 72 | print_packet(rf.get_packet(rx_channel)) 73 | end 74 | 75 | # Get history page 76 | rf.send_packet("a7" + pump_serial + "8000", tx_channel) 77 | print_packet(rf.get_packet(rx_channel, rx_timeout_ms)) 78 | rf.send_packet("a7" + pump_serial + "8001" + "00" * 64, tx_channel) 79 | 16.times do 80 | print_packet(rf.get_packet(rx_channel, rx_timeout_ms)) 81 | rf.send_packet("a7" + pump_serial + "0600", tx_channel) 82 | end 83 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Packet Diagrams 2 | ## Alert 3 | [alert.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/alert.rb)![Alert](https://rawgit.com/ps2/minimed_rf/master/docs/Alert.svg) 4 | 5 | ## AlertCleared 6 | [alert_cleared.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/alert_cleared.rb)![AlertCleared](https://rawgit.com/ps2/minimed_rf/master/docs/AlertCleared.svg) 7 | 8 | ## DeviceLink 9 | [device_link.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/device_link.rb)![DeviceLink](https://rawgit.com/ps2/minimed_rf/master/docs/DeviceLink.svg) 10 | 11 | ## DeviceTest 12 | [device_test.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/device_test.rb)![DeviceTest](https://rawgit.com/ps2/minimed_rf/master/docs/DeviceTest.svg) 13 | 14 | ## FindDevice 15 | [find_device.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/find_device.rb)![FindDevice](https://rawgit.com/ps2/minimed_rf/master/docs/FindDevice.svg) 16 | 17 | ## GetModel 18 | [message_base.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/message_base.rb)![GetModel](https://rawgit.com/ps2/minimed_rf/master/docs/GetModel.svg) 19 | 20 | ## Meter 21 | [meter.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/meter.rb)![Meter](https://rawgit.com/ps2/minimed_rf/master/docs/Meter.svg) 22 | 23 | ## PumpDump 24 | [pump_dump.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/pump_dump.rb)![PumpDump](https://rawgit.com/ps2/minimed_rf/master/docs/PumpDump.svg) 25 | 26 | ## PumpStatus 27 | [pump_status.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/pump_status.rb)![PumpStatus](https://rawgit.com/ps2/minimed_rf/master/docs/PumpStatus.svg) 28 | 29 | ## PumpStatusAck 30 | [pump_status_ack.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/pump_status_ack.rb)![PumpStatusAck](https://rawgit.com/ps2/minimed_rf/master/docs/PumpStatusAck.svg) 31 | 32 | ## Remote 33 | [message_base.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/message_base.rb)![Remote](https://rawgit.com/ps2/minimed_rf/master/docs/Remote.svg) 34 | 35 | ## Sensor 36 | [sensor.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/sensor.rb)![Sensor](https://rawgit.com/ps2/minimed_rf/master/docs/Sensor.svg) 37 | 38 | ## WakeUp 39 | [wake_up.rb](https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/messages/wake_up.rb)![WakeUp](https://rawgit.com/ps2/minimed_rf/master/docs/WakeUp.svg) 40 | 41 | -------------------------------------------------------------------------------- /tasks/ios.rake: -------------------------------------------------------------------------------- 1 | require 'minimed_rf' 2 | require 'minimed_rf/string_utils' 3 | require 'erb' 4 | 5 | 6 | namespace :ios do 7 | desc "Generate list of history types" 8 | task :history_types do 9 | codes = {} 10 | MinimedRF::PumpEvents.constants.each do |event_class| 11 | klazz = MinimedRF::PumpEvents.const_get(event_class) 12 | next if klazz == MinimedRF::PumpEvents::Base 13 | name = klazz.to_s.split("::").last 14 | 15 | codes[klazz.event_type_code] = name 16 | end 17 | 18 | codes.keys.sort.each do |k| 19 | code = "0x%02x" % k 20 | puts "case #{codes[k]} = #{code}" 21 | end 22 | puts "" 23 | puts "var eventType: PumpEvent.Type {" 24 | puts " switch self {" 25 | 26 | codes.keys.sort.each do |k| 27 | code = "0x%02x" % k 28 | puts " case .#{codes[k]}:" 29 | puts " return #{codes[k]}PumpEvent.self" 30 | end 31 | puts "}" 32 | 33 | end 34 | 35 | desc "Generate Objective-C classes for pump events" 36 | task :classes, [:output_dir] do |t, args| 37 | output_dir = args[:output_dir] 38 | if output_dir.nil? 39 | raise "Need to supply output directory!" 40 | end 41 | template_m = File.read(File.expand_path('../history_entry_objc.m.erb', __FILE__)) 42 | template_h = File.read(File.expand_path('../history_entry_objc.h.erb', __FILE__)) 43 | template_swift = File.read(File.expand_path('../history_entry.swift.erb', __FILE__)) 44 | MinimedRF::PumpEvents.constants.each do |event_class| 45 | klazz = MinimedRF::PumpEvents.const_get(event_class) 46 | next if klazz == MinimedRF::PumpEvents::Base 47 | class_short_name = klazz.to_s.split("::").last 48 | class_name = class_short_name + "PumpEvent" 49 | event_type_code = "0x%02x" % klazz.event_type_code 50 | length = klazz.new("0000", MinimedRF::Model522.new).bytesize 51 | h_file = output_dir + "/" + class_name + ".h" 52 | swift_file = output_dir + "/" + class_name + ".swift" 53 | if !File.exists?(h_file) 54 | File.open(h_file, "w+") do |f| 55 | f.write(ERB.new(template_h).result(binding)) 56 | end 57 | end 58 | m_file = output_dir + "/" + class_name + ".m" 59 | if !File.exists?(m_file) 60 | File.open(m_file, "w+") do |f| 61 | f.write(ERB.new(template_m).result(binding)) 62 | end 63 | end 64 | if !File.exists?(swift_file) 65 | File.open(swift_file, "w+") do |f| 66 | f.write(ERB.new(template_swift).result(binding)) 67 | end 68 | end 69 | end 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/message_base.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class Message 4 | attr_accessor :bits 5 | 6 | def initialize(data) 7 | @bits = data.unpack("B*").first 8 | @data = data 9 | end 10 | 11 | def self.bit_blocks 12 | {} 13 | end 14 | 15 | def self.check_bit_block_definitions 16 | used_bits = {} 17 | bit_blocks.each do |key, block| 18 | ((block.first)..(block.first+block.last-1)).each do |bit| 19 | raise "Bit block #{key} redefines bit #{bit} already defined by #{used_bits[bit]}" if used_bits.include?(bit) 20 | used_bits[bit] = key 21 | end 22 | end 23 | end 24 | 25 | def self.from_hex(hex_str) 26 | new([hex_str].pack('H*')) 27 | end 28 | 29 | def hex_str 30 | @data.unpack("H*").first 31 | end 32 | 33 | def bit_blocks 34 | self.class.bit_blocks 35 | end 36 | 37 | def d(offset) 38 | @data.getbyte(offset) 39 | end 40 | 41 | def b(name) 42 | range = bit_blocks[name] 43 | raise "Unknown bit block: #{name}" if range.nil? 44 | #puts "b(#{name.inspect}) = #{range}" 45 | bits = "0b" + @bits.send("[]", *range) 46 | return 0 if bits == "0b" 47 | Integer(bits) 48 | end 49 | 50 | def print_bit_differences(other) 51 | (0..(@bits.length-1)).each do |idx| 52 | if bits[idx] == other.bits[idx] 53 | print bits[idx] 54 | else 55 | print bits[idx].colorize(:red) 56 | end 57 | end 58 | print "\n" 59 | end 60 | 61 | def self.packetdiag 62 | out = "packetdiag {\ncolwidth=32\n" 63 | out << "0-7: packet_type [color = \"none\"]\n" 64 | out << "8-31: addr [color = \"none\"]\n" 65 | out << "32-39: message_type [color = \"none\"]\n" 66 | bit_blocks.keys.each do |k| 67 | first = bit_blocks[k][0] + 40 68 | last = first + bit_blocks[k][1]-1 69 | out << "#{first}-#{last}: #{k} [color = \"none\"]\n" 70 | end 71 | out << "}\n" 72 | end 73 | 74 | def print_unused_bytes 75 | end 76 | 77 | def print_unused_bits 78 | blocks = bit_blocks.values.sort_by {|b| b.first} 79 | idx = 0 80 | output = [] 81 | blocks.each do |b| 82 | raise "Invalid range: #{b.inspect}." if b.first < idx 83 | if idx < b.first 84 | output << [(idx..(b.first-1)), :unused] 85 | end 86 | output << [(b.first)..(b.first+b.last-1), :used] 87 | idx = b.first + b.last 88 | end 89 | if idx < @bits.length - 1 90 | output << [(idx..(@bits.length-1)), :unused] 91 | end 92 | output.each do |block| 93 | print @bits[block.first].colorize(block.last == :used ? :white : :light_grey) 94 | end 95 | print "\n" 96 | nil 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /bin/mmmockpair: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This utility talks over a serial connection to a RileyLink that has been 4 | # loaded with the subg_rfspy firmware (https://github.com/ps2/subg_rfspy) 5 | 6 | # Sends out FindDevice and DeviceLink packets, simulating a pump in Find Device 7 | # mode. 8 | 9 | require 'minimed_rf' 10 | require 'minimed_rf/rfspy' 11 | 12 | if ARGV.length < 2 || ARGV[1].length != 6 13 | puts "Usage: mmpair /dev/tty.usbserial-A9048LGG pumpserial" 14 | if ARGV.length > 1 && ARGV[1].length != 6 15 | puts "Error: pumpserial should be a six character id, like '55AB12'" 16 | end 17 | exit -1 18 | end 19 | 20 | pump_serial = ARGV[1] 21 | 22 | def log(msg) 23 | puts "#{Time.now.strftime('%H:%M:%S.%3N')} #{msg}" 24 | end 25 | 26 | def print_packet(p) 27 | if p.nil? 28 | puts "Nil packet!" 29 | else 30 | log "#{"%3d" % p.rssi} (#{"%3d" % p.sequence}): #{p}" 31 | #puts "raw: #{p.hex_data}" 32 | end 33 | end 34 | 35 | puts "Opening #{ARGV[0]}" 36 | rf = MinimedRF::RFSpy.new(ARGV[0]) 37 | rf.sync 38 | 39 | rx_channel = 0 40 | tx_channel = 2 41 | counter = 1; 42 | 43 | while 44 | # a2 597055 09 9e 999999 01 fa 45 | counter_hex = "%02x" % counter 46 | find_device = "a2" + pump_serial + "09" + counter_hex + "99999900" 47 | 48 | # Send copy 1 of FindDevice packet, listen 5 sec 49 | log "Sending #{find_device}" 50 | packet = rf.send_and_listen(find_device, tx_channel, 0, 0, rx_channel, 5000, 0) 51 | fd_ack = packet.to_message rescue nil 52 | 53 | if packet 54 | print_packet(packet) 55 | end 56 | 57 | if (fd_ack && fd_ack.is_a?(MinimedRF::PumpStatusAck) && 58 | fd_ack.sequence == counter && 59 | fd_ack.response_type == 0x09) 60 | log("Ok! got ack to 09") 61 | counter += 1 62 | counter_hex = "%02x" % counter 63 | device_link = "a2" + pump_serial + "0a" + counter_hex + fd_ack.device_address + "00" 64 | # Send copy 1 of DeviceLink packet, listen 5 sec 65 | log "Sending #{device_link}" 66 | packet = rf.send_and_listen(device_link, tx_channel, 0, 0, rx_channel, 5000, 0) 67 | dl_ack = packet.to_message rescue nil 68 | if dl_ack 69 | print_packet(packet) 70 | end 71 | if (dl_ack && dl_ack.is_a?(MinimedRF::PumpStatusAck) && 72 | dl_ack.sequence == counter && 73 | dl_ack.response_type == 0x0a) 74 | log("Ok! got ack to 0a") 75 | puts "Device linked!" 76 | exit 77 | else 78 | # Send copy 2 of DeviceLink packet, wait 100ms 79 | device_link = "a2" + pump_serial + "0a" + counter_hex + fd_ack.device_address + "01" 80 | log "Sending #{device_link}" 81 | packet = rf.send_and_listen(device_link, tx_channel, 0, 0, rx_channel, 5000, 0) 82 | end 83 | else 84 | # Send copy 2 of FindDevice packet, wait 100ms 85 | find_device = "a2" + pump_serial + "09" + counter_hex + "99999901" 86 | log "Sending #{find_device}" 87 | packet = rf.send_and_listen(find_device, tx_channel, 0, 0, rx_channel, 100, 0) 88 | counter += 1 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/bolus_wizard_bolus_estimate.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | module PumpEvents 4 | class BolusWizardBolusEstimate < Base 5 | 6 | attr_accessor :carbohydrates, :blood_glucose, :food_estimate, :correction_estimate, 7 | :bolus_estimate, :unabsorbed_insulin_total, :bg_target_low, :bg_target_high, 8 | :insulin_sensitivity, :carb_ratio 9 | 10 | def initialize(data, pump_model=nil) 11 | super(data, pump_model) 12 | 13 | return if @data.bytesize < bytesize 14 | 15 | if @pump_model.larger 16 | @carbohydrates = ((d(8) & 0xc) << 6) + d(7) 17 | @blood_glucose = ((d(8) & 0x3) << 8) + d(1) 18 | @food_estimate = insulin_decode(d(14), d(15)) 19 | @correction_estimate = (((d(16) & 0b111000) << 5) + d(13)) / 40.0 20 | @bolus_estimate = insulin_decode(d(19), d(20)) 21 | @unabsorbed_insulin_total = insulin_decode(d(17), d(18)) 22 | @bg_target_low = d(12) 23 | @bg_target_high = d(21) 24 | @insulin_sensitivity = d(11) 25 | @carb_ratio = ((d(9) & 0x7) << 8) + d(10) / 10.0 26 | else 27 | @carbohydrates = d(7) 28 | @blood_glucose = ((d(8) & 0x3) << 8) + d(1) 29 | @food_estimate = d(13)/10.0 30 | @correction_estimate = ((d(14) << 8) + d(12)) / 10.0 31 | @bolus_estimate = d(18)/10.0 32 | @unabsorbed_insulin_total = d(16)/10.0 33 | @bg_target_low = d(11) 34 | @bg_target_high = d(19) 35 | @insulin_sensitivity = d(10) 36 | @carb_ratio = d(9) 37 | end 38 | end 39 | 40 | def self.event_type_code 41 | 0x5b 42 | end 43 | 44 | def bytesize 45 | if @pump_model.larger 46 | 22 47 | else 48 | 20 49 | end 50 | end 51 | 52 | def to_s 53 | # "#{bolus_type} #{timestamp_str} CH:#{carbohydrates} BG:#{blood_glucose} Carb Ratio:#{carb_ratio} Insulin Sensitivity:#{insulin_sensitivity} Bolus Estimate:#{bolus_estimate} Food Estimate:#{food_estimate} Correction Estimate:#{correction_estimate} Unabsorbed Insulin Total:#{unabsorbed_insulin_total}" 54 | "#{bolus_type} #{timestamp_str} " 55 | end 56 | 57 | def insulin_decode(a, b) 58 | ((a << 8) + b) / 40.0 59 | end 60 | 61 | def bolus_type 62 | "BolusWizardBolusEstimate" 63 | end 64 | 65 | 66 | def timestamp 67 | parse_date(2) 68 | end 69 | 70 | def as_json 71 | super.merge({ 72 | bg: blood_glucose, 73 | bg_target_high: bg_target_high, 74 | correction_estimate: correction_estimate, 75 | carb_input: carbohydrates, 76 | unabsorbed_insulin_total: unabsorbed_insulin_total, 77 | bolus_estimate: bolus_estimate, 78 | carb_ratio: carb_ratio, 79 | food_estimate: food_estimate, 80 | bg_target_low: bg_target_low, 81 | sensitivity: insulin_sensitivity 82 | }) 83 | end 84 | 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/change_sensor_setup2.rb: -------------------------------------------------------------------------------- 1 | # 523 2 | # 5000bbf9150f0f21011e003c14001e3c25c123b4460021011e003c14001e3c25c123b44500 3 | # 50009fd5170f0f21011e003c14001e3c25c123b4450021011e003c14001e3c25c123b44400 4 | # 31,11/15/15,21:57:59,11/15/15 21:57:59,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeSensorSetupConfig2,"IS_SENSOR_ENABLED=true, BG_UNITS=mg dl, HIGH_AREA_UNDER_CURVE=180, HIGH_GLUCOSE_SNOOZE_TIME=3600000, LOW_AREA_UNDER_CURVE=69, LOW_GLUCOSE_SNOOZE_TIME=1200000, CAL_REMINDER_ENABLE=true, CAL_REMINDER_TIME=3600000, ALARM_SNOOZE_TIME=1800000, MISSED_DATA_TIME=1800000, IS_AUTO_CAL_ENABLE=false, TRANSMITTER_ID=2474275",15842475086,54149256,86,Paradigm Revel - 523 5 | # 32,11/15/15,21:57:59,11/15/15 21:57:59,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeSensorSetup2,"NEW_CONFIG_DATUM_ID=15842475086, OLD_CONFIG_DATUM_ID=15842475085, ACTION_REQUESTOR=pump",15842475087,54149256,87,Paradigm Revel - 523 6 | # 33,11/15/15,21:57:59,11/15/15 21:57:59,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeSensorSetupConfig2,"IS_SENSOR_ENABLED=true, BG_UNITS=mg dl, HIGH_AREA_UNDER_CURVE=180, HIGH_GLUCOSE_SNOOZE_TIME=3600000, LOW_AREA_UNDER_CURVE=70, LOW_GLUCOSE_SNOOZE_TIME=1200000, CAL_REMINDER_ENABLE=true, CAL_REMINDER_TIME=3600000, ALARM_SNOOZE_TIME=1800000, MISSED_DATA_TIME=1800000, IS_AUTO_CAL_ENABLE=false, TRANSMITTER_ID=2474275",15842475085,54149256,85,Paradigm Revel - 523 7 | 8 | 9 | # 530g - Added low suspend? 10 | # 5000431A02070E21011E003C14001E3C24EF6DB44600803C20011E003C14001E3C24EF6DB44600803C 11 | # 519,4/7/14,02:26:03,4/7/14 02:26:03,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeSensorSetup2,"NEW_CONFIG_DATUM=12753936690, OLD_CONFIG_DATUM=12753936689, ACTION_REQUESTOR=pump",12753936691,53119959,97,MiniMed 530G - 551 12 | # 520,4/7/14,02:26:03,4/7/14 02:26:03,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeSensorSetupConfig2,"IS_SENSOR_ENABLED=false, BG_UNITS=mg dl, HIGH_AREA_UNDER_CURVE=180, HIGH_GLUCOSE_SNOOZE_TIME=3600000, LOW_AREA_UNDER_CURVE=70, LOW_GLUCOSE_SNOOZE_TIME=1200000, CAL_REMINDER_ENABLE=true, CAL_REMINDER_TIME=3600000, ALARM_SNOOZE_TIME=1800000, MISSED_DATA_TIME=1800000, IS_AUTO_CAL_ENABLE=false, IS_LOW_SUSPEND_ENABLE=true, LOW_SUSPEND_LIMIT=60, TRANSMITTER_ID=2420589",12753936690,53119959,96,MiniMed 530G - 551 13 | # 521,4/7/14,02:26:03,4/7/14 02:26:03,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeSensorSetupConfig2,"IS_SENSOR_ENABLED=true, BG_UNITS=mg dl, HIGH_AREA_UNDER_CURVE=180, HIGH_GLUCOSE_SNOOZE_TIME=3600000, LOW_AREA_UNDER_CURVE=70, LOW_GLUCOSE_SNOOZE_TIME=1200000, CAL_REMINDER_ENABLE=true, CAL_REMINDER_TIME=3600000, ALARM_SNOOZE_TIME=1800000, MISSED_DATA_TIME=1800000, IS_AUTO_CAL_ENABLE=false, IS_LOW_SUSPEND_ENABLE=true, LOW_SUSPEND_LIMIT=60, 14 | 15 | 16 | 17 | module MinimedRF 18 | module PumpEvents 19 | class ChangeSensorSetup2 < Base 20 | 21 | def self.event_type_code 22 | 0x50 23 | end 24 | 25 | def bytesize 26 | if @pump_model.has_low_suspend 27 | 41 28 | else 29 | 37 30 | end 31 | end 32 | 33 | def to_s 34 | "ChangeSensorSetup2 #{timestamp_str}" 35 | end 36 | 37 | def timestamp 38 | parse_date(2) 39 | end 40 | 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/pump_status_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'time' 3 | 4 | describe MinimedRF::PumpStatus do 5 | 6 | #923,7/24/14,04:11:00,7/24/14 04:11:00,,,,,,,,,,,,,,,,,,,,,,,,,,,213,39.64,,GlucoseSensorData,"AMOUNT=213, ISIG=39.64, VCNTR=-0.488, BACKFILL_INDICATOR=false",13537455052,53400304,461,MiniMed 530G - 551 7 | it "should decode fields" do 8 | hex_data = "9041040d240e0718006a6d00022103020683068a051500000172008b040b000e07180000" 9 | message = MinimedRF::PumpStatus.from_hex(hex_data) 10 | expect(message.sequence).to eq 16 11 | expect(message.sensor_status).to eq :ok 12 | expect(message.glucose).to eq 213 13 | expect(message.previous_glucose).to eq 218 14 | expect(message.sensor_timestamp).to eq Time.parse('2014-07-24 04:11:00') 15 | expect(message.pump_timestamp).to eq Time.parse('2014-07-24 04:13:36') 16 | end 17 | 18 | it "should handle weak signal messages" do 19 | hex_data = "e0410f30090e08180002020003cc030307142523142a00000000006d0f2f000e08180000" 20 | message = MinimedRF::PumpStatus.from_hex(hex_data) 21 | expect(message.sensor_status).to eq :weak_signal 22 | expect(message.sensor_timestamp).to eq Time.parse('2014-08-24 15:47:00') 23 | end 24 | 25 | it "should handle meter bg now messages" do 26 | hex_data = "a04116211f0e081800010100037e030305f72c1cffff000c0000006e1620000e08180000" 27 | message = MinimedRF::PumpStatus.from_hex(hex_data) 28 | expect(message.sensor_status).to eq :meter_bg_now 29 | expect(message.sensor_timestamp).to eq Time.parse('2014-08-24 22:32:00') 30 | end 31 | 32 | it "should handle sensor missing messages" do 33 | hex_data = "3940080b240e090800000000050d030305b30000000000000000007a0000000000000000" 34 | message = MinimedRF::PumpStatus.from_hex(hex_data) 35 | expect(message.sensor_status).to eq :sensor_missing 36 | expect(message.sensor_timestamp).to eq nil 37 | end 38 | 39 | it "should handle sensor warm-up messages" do 40 | hex_data = "ab411519290e0c020004040001120101063b0090ffff0024000000bb1517000e0c020000" 41 | message = MinimedRF::PumpStatus.from_hex(hex_data) 42 | expect(message.sensor_status).to eq :sensor_warmup 43 | expect(message.sensor_timestamp).to eq Time.parse('2014-12-02 21:23:00') 44 | end 45 | 46 | it "should decode active insulin" do 47 | hex_data = "3b4115342c0e090800989700034f03020620781809020027030000911532000e09080000" 48 | message = MinimedRF::PumpStatus.from_hex(hex_data) 49 | expect(message.active_insulin).to be_within(0.0001).of(0.975) 50 | end 51 | 52 | it "should decode battery full" do 53 | hex_data = "f94108200c0f090e00454600022a040205f8810f0b0c000002000104081e000f090e0000" 54 | message = MinimedRF::PumpStatus.from_hex(hex_data) 55 | expect(message.battery_pct).to eq 100 56 | end 57 | 58 | it "should decode battery full2" do 59 | hex_data = "0241160e000f09110005050001d60402060aff00ffff0000000000b1160d000f09110000" 60 | message = MinimedRF::PumpStatus.from_hex(hex_data) 61 | expect(message.battery_pct).to eq 100 62 | end 63 | 64 | it "should decode battery voltage 25%" do 65 | hex_data = "d9450801110f0916004a4000013201010617246c13250000030001040800000f09160000" 66 | message = MinimedRF::PumpStatus.from_hex(hex_data) 67 | expect(message.battery_pct).to eq 25 68 | end 69 | 70 | it "should decode battery 75%" do 71 | hex_data = "fb411609310f09110038360001f00302060f4749050d000e010001041609000f09110000" 72 | message = MinimedRF::PumpStatus.from_hex(hex_data) 73 | expect(message.battery_pct).to eq 75 74 | end 75 | 76 | it "should decode clock_type" do 77 | hex_data = "d5971f1510070a013f3a0002dd020105bd08880825000502000755171e0010070a00008d" 78 | message = MinimedRF::PumpStatus.from_hex(hex_data) 79 | expect(message.clock_type).to eq :clock_type_24hr 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries.rb: -------------------------------------------------------------------------------- 1 | require 'minimed_rf/log_entries/base' 2 | require 'minimed_rf/log_entries/alarm_clock_reminder' 3 | require 'minimed_rf/log_entries/alarm_pump' 4 | require 'minimed_rf/log_entries/alarm_sensor' 5 | require 'minimed_rf/log_entries/basal_profile_start' 6 | require 'minimed_rf/log_entries/battery' 7 | require 'minimed_rf/log_entries/bolus_normal' 8 | require 'minimed_rf/log_entries/bolus_reminder' 9 | require 'minimed_rf/log_entries/bolus_wizard_bolus_estimate' 10 | require 'minimed_rf/log_entries/bg_received' 11 | require 'minimed_rf/log_entries/bolus_normal' 12 | require 'minimed_rf/log_entries/bolus_wizard_setup' 13 | require 'minimed_rf/log_entries/cal_bg_for_ph' 14 | require 'minimed_rf/log_entries/change_active_basal_profile_pattern' 15 | require 'minimed_rf/log_entries/change_alarm_clock_time' 16 | require 'minimed_rf/log_entries/change_alarm_clock_enable' 17 | require 'minimed_rf/log_entries/change_alarm_notify_mode' 18 | require 'minimed_rf/log_entries/change_audio_bolus' 19 | require 'minimed_rf/log_entries/change_basal_profile' 20 | require 'minimed_rf/log_entries/change_basal_profile_pattern' 21 | require 'minimed_rf/log_entries/change_bg_reminder_enable' 22 | require 'minimed_rf/log_entries/change_bg_reminder_offset' 23 | require 'minimed_rf/log_entries/change_bolus_reminder_time' 24 | require 'minimed_rf/log_entries/change_bolus_reminder_enable' 25 | require 'minimed_rf/log_entries/change_bolus_scroll_step_size' 26 | require 'minimed_rf/log_entries/change_bolus_wizard_setup' 27 | require 'minimed_rf/log_entries/change_capture_event_enable' 28 | require 'minimed_rf/log_entries/change_carb_units' 29 | require 'minimed_rf/log_entries/change_child_block_enable' 30 | require 'minimed_rf/log_entries/change_max_basal' 31 | require 'minimed_rf/log_entries/change_max_bolus' 32 | require 'minimed_rf/log_entries/change_meter_id' 33 | require 'minimed_rf/log_entries/change_other_device_id' 34 | require 'minimed_rf/log_entries/change_paradigm_link_id' 35 | require 'minimed_rf/log_entries/change_reservoir_warning_time' 36 | require 'minimed_rf/log_entries/change_sensor_alarm_silence_config' 37 | require 'minimed_rf/log_entries/change_sensor_rate_of_change_alert_setup' 38 | require 'minimed_rf/log_entries/change_sensor_setup2' 39 | require 'minimed_rf/log_entries/change_temp_basal_type' 40 | require 'minimed_rf/log_entries/change_time' 41 | require 'minimed_rf/log_entries/change_time_format' 42 | require 'minimed_rf/log_entries/change_variable_bolus' 43 | require 'minimed_rf/log_entries/change_watchdog_enable' 44 | require 'minimed_rf/log_entries/change_watchdog_marriage_profile' 45 | require 'minimed_rf/log_entries/clear_alarm' 46 | require 'minimed_rf/log_entries/clear_settings' 47 | require 'minimed_rf/log_entries/daily_totals' 48 | require 'minimed_rf/log_entries/delete_alarm_clock_time' 49 | require 'minimed_rf/log_entries/delete_bolus_reminder_time' 50 | require 'minimed_rf/log_entries/delete_other_device_id' 51 | require 'minimed_rf/log_entries/enable_bolus_wizard' 52 | require 'minimed_rf/log_entries/enable_disable_remote' 53 | require 'minimed_rf/log_entries/journal_entry_meal_marker' 54 | require 'minimed_rf/log_entries/journal_entry_exercise_marker' 55 | require 'minimed_rf/log_entries/journal_entry_low_battery' 56 | require 'minimed_rf/log_entries/journal_entry_pump_low_reservoir' 57 | require 'minimed_rf/log_entries/new_time' 58 | require 'minimed_rf/log_entries/prime' 59 | require 'minimed_rf/log_entries/questionable_3b' 60 | require 'minimed_rf/log_entries/resume' 61 | require 'minimed_rf/log_entries/result_daily_total' 62 | require 'minimed_rf/log_entries/rewind' 63 | require 'minimed_rf/log_entries/self_test' 64 | require 'minimed_rf/log_entries/save_settings' 65 | require 'minimed_rf/log_entries/set_auto_off' 66 | require 'minimed_rf/log_entries/suspend' 67 | require 'minimed_rf/log_entries/temp_basal' 68 | require 'minimed_rf/log_entries/temp_basal_duration' 69 | require 'minimed_rf/log_entries/unabsorbed_insulin' 70 | require 'minimed_rf/log_entries/restore_settings_mystery_events' 71 | -------------------------------------------------------------------------------- /lib/minimed_rf/log_entries/bolus_wizard_setup.rb: -------------------------------------------------------------------------------- 1 | # 5A0F145F13050E15110000960000000000000000000000000000000000000000000000003200000000000000000000000000000050B400000000000000000000000000000000000000000015110000C80000000000000000000000000000000000000000000000003200000000000000000000000000000050B400000000000000000000000000000000000000000033 2 | # 1151,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBolusWizardSetup,"NEW_CONFIG_DATUM=12079809494, OLD_CONFIG_DATUM=12079809487, ACTION_REQUESTOR=pump",12079809495,52856852,97,MiniMed 530G - 551 3 | # 1152,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBGTargetRange,"PATTERN_DATUM=12079809492, INDEX=0, AMOUNT_LOW=80, AMOUNT_HIGH=180, START_TIME=0",12079809493,52856852,96,MiniMed 530G - 551 4 | # 1153,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBolusWizardSetupConfig,"IS_SETUP_COMPLETE=true, IS_BOLUS_WIZARD_ENABLED=true, CARB_UNITS=grams, BG_UNITS=mg dl, CARB_RATIO_PATTERN_DATUM=12079809488, INSULIN_SENSITIVITY_PATTERN_DATUM=12079809490, BG_TARGET_RANGE_PATTERN_DATUM=12079809492, INSULIN_ACTION_CURVE=180",12079809494,52856852,90,MiniMed 530G - 551 5 | # 1154,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBGTargetRangePattern,"ORIGINAL_UNITS=mg dl, SIZE=1",12079809492,52856852,95,MiniMed 530G - 551 6 | # 1155,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeInsulinSensitivity,"PATTERN_DATUM=12079809490, INDEX=0, AMOUNT=50, START_TIME=0",12079809491,52856852,94,MiniMed 530G - 551 7 | # 1156,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeInsulinSensitivityPattern,"ORIGINAL_UNITS=mg dl, SIZE=1",12079809490,52856852,93,MiniMed 530G - 551 8 | # 1157,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeCarbRatio,"PATTERN_DATUM=12079809488, INDEX=0, AMOUNT=20, UNITS=grams, START_TIME=0",12079809489,52856852,92,MiniMed 530G - 551 9 | # 1158,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeCarbRatioPattern,SIZE=1,12079809488,52856852,91,MiniMed 530G - 551 10 | # 1159,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBolusWizardSetupConfig,"IS_SETUP_COMPLETE=true, IS_BOLUS_WIZARD_ENABLED=true, CARB_UNITS=grams, BG_UNITS=mg dl, CARB_RATIO_PATTERN_DATUM=12079809481, INSULIN_SENSITIVITY_PATTERN_DATUM=12079809483, BG_TARGET_RANGE_PATTERN_DATUM=12079809485, INSULIN_ACTION_CURVE=180",12079809487,52856852,83,MiniMed 530G - 551 11 | # 1160,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeCarbRatioPattern,SIZE=1,12079809481,52856852,84,MiniMed 530G - 551 12 | # 1161,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeCarbRatio,"PATTERN_DATUM=12079809481, INDEX=0, AMOUNT=15, UNITS=grams, START_TIME=0",12079809482,52856852,85,MiniMed 530G - 551 13 | # 1162,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeInsulinSensitivityPattern,"ORIGINAL_UNITS=mg dl, SIZE=1",12079809483,52856852,86,MiniMed 530G - 551 14 | # 1163,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeInsulinSensitivity,"PATTERN_DATUM=12079809483, INDEX=0, AMOUNT=50, START_TIME=0",12079809484,52856852,87,MiniMed 530G - 551 15 | # 1164,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBGTargetRangePattern,"ORIGINAL_UNITS=mg dl, SIZE=1",12079809485,52856852,88,MiniMed 530G - 551 16 | # 1165,1/5/14,19:31:20,1/5/14 19:31:20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ChangeBGTargetRange,"PATTERN_DATUM=12079809485, INDEX=0, AMOUNT_LOW=80, AMOUNT_HIGH=180, START_TIME=0",12079809486,52856852,89,MiniMed 530G - 551 17 | 18 | module MinimedRF 19 | module PumpEvents 20 | class BolusWizardSetup < Base 21 | def self.event_type_code 22 | 0x5a 23 | end 24 | 25 | def bytesize 26 | if @pump_model.larger 27 | 144 28 | else 29 | 124 30 | end 31 | end 32 | 33 | def to_s 34 | "BolusWizardSetup #{timestamp_str}" 35 | end 36 | 37 | def timestamp 38 | parse_date(2) 39 | end 40 | 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minimed_rf 2 | 3 | Many Medtronic insulin pumps are capable of sending CGM data to a remote monitor called MySentry. This project provides ruby libraries and tools to decode the message sent between the devices. To capture and send packets, you can use something like [RileyLink](https://github.com/ps2/rileylink) or an SDR. 4 | 5 | ## rf modulation 6 | 7 | The frequency used is 916.5MHz, and the modulation is ASK/OOK. Data rate is 16kBaud. I use an RX filter BW of 203kHz since the pump, sensor, and MySentry all use different antennas and the frequency varies a bit from the center. 8 | 9 | Here are the settings I use to configure a cc1110 with a 24MHz crystal: 10 | 11 | ``` 12 | /* RF settings SoC: CC1110 */ 13 | SYNC1 = 0xFF; // sync word, high byte 14 | SYNC0 = 0x00; // sync word, low byte 15 | PKTLEN = 0xFF; // packet length 16 | PKTCTRL1 = 0x00; // packet automation control 17 | PKTCTRL0 = 0x00; // packet automation control 18 | ADDR = 0x00; 19 | CHANNR = 0x02; // channel number 20 | FSCTRL1 = 0x06; // frequency synthesizer control 21 | FSCTRL0 = 0x00; 22 | FREQ2 = 0x26; // frequency control word, high byte 23 | FREQ1 = 0x2F; // frequency control word, middle byte 24 | FREQ0 = 0xE4; // frequency control word, low byte 25 | MDMCFG4 = 0xB9; // modem configuration 26 | MDMCFG3 = 0x66; // modem configuration 27 | MDMCFG2 = 0x33; // modem configuration 28 | MDMCFG1 = 0x61; // modem configuration 29 | MDMCFG0 = 0xe6; // modem configuration 30 | DEVIATN = 0x15; // modem deviation setting 31 | MCSM2 = 0x07; 32 | MCSM1 = 0x30; 33 | MCSM0 = 0x18; // main radio control state machine configuration 34 | FOCCFG = 0x17; // frequency offset compensation configuration 35 | BSCFG = 0x6C; 36 | FREND1 = 0x56; // front end tx configuration 37 | FREND0 = 0x11; // front end tx configuration 38 | FSCAL3 = 0xE9; // frequency synthesizer calibration 39 | FSCAL2 = 0x2A; // frequency synthesizer calibration 40 | FSCAL1 = 0x00; // frequency synthesizer calibration 41 | FSCAL0 = 0x1F; // frequency synthesizer calibration 42 | TEST1 = 0x31; // various test settings 43 | TEST0 = 0x09; // various test settings 44 | PA_TABLE0 = 0x00; // needs to be explicitly set! 45 | PA_TABLE1 = 0x57; // pa power setting 0 dBm 46 | ``` 47 | 48 | ## data encoding 49 | 50 | The data is encoded using an encoding called 4b6b. It looks like this: 51 | 52 | ```ruby 53 | "010101" => "0", 54 | "110001" => "1", 55 | "110010" => "2", 56 | "100011" => "3", 57 | "110100" => "4", 58 | "100101" => "5", 59 | "100110" => "6", 60 | "010110" => "7", 61 | "011010" => "8", 62 | "011001" => "9", 63 | "101010" => "a", 64 | "001011" => "b", 65 | "101100" => "c", 66 | "001101" => "d", 67 | "001110" => "e", 68 | "011100" => "f" 69 | ``` 70 | 71 | A raw (encoded) packet looks something like this: 72 | ``` 73 | ab29595959655743a5d31c7254ec4b54e55a54b555d0dd0e5555716aa563571566c9ac7258e565574555d1c55555555568bc7256c55554e55a54b55555556c55 74 | ``` 75 | 76 | Once you have decoded the data, the packets start to show some recognizable fields: 77 | ``` 78 | a259705504e541120e1b0e080b004d4e00018a03010628127e0504004f0000008b120c000e080b00000c 79 | ``` 80 | 81 | a2 identifies the type of packet, 597055 is the pump number, etc... The 0c at the end is an 8bit crc. 82 | 83 | ## usage 84 | 85 | ``` 86 | ruby -I lib bin/mmdecode 87 | ``` 88 | 89 | ## example 90 | 91 | ``` 92 | ruby -I lib bin/mmdecode ab29595959655743a5d31c7254ec4b54e55a54b555d0dd0e5555716aa563571566c9ac7258e565574555d1c55555555568bc7256c55554e55a54b55555556c55 93 | a2 597055 PumpStatus: #101 2014-08-11 18:14:00 -0500 - Glucose=154 PreviousGlucose=156 ActiveInsulin=1.975 94 | ``` 95 | 96 | # Flow control 97 | 98 | If you're using a device that doesn't have flow control (like the ERF stick), set the appropriate environment variable before use: 99 | 100 | ``` 101 | export RFSPY_RTSCTS=0 102 | ruby -I lib bin/mmtune /dev/ttyMFD1 123123 103 | ``` 104 | 105 | ## Thanks 106 | 107 | Thanks to @bewest and @loudnate and others who have provided many insights into the Minimed protocols. Much of the pump-specific knowledge has been figured out by @bewest in his [decoding-carelink](https://github.com/bewest/decoding-carelink) repository. 108 | -------------------------------------------------------------------------------- /lib/minimed_rf/history_page.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | class HistoryPage 3 | #6ebf0f050000000000000002be02be640000000000000000000000000000000000000000000000000000000000000000000000007b0180de08010f11220006040c1e80c051410f0c0488c411010f7b018ac411010f11220006040c1eb1e651410f1a008fee11010f060303688fee71010f0c040e400001070c030f4000010764001f4000010717003740000107180080f616080f07000001efa18f0000006ea18f050000000000000001ef01ef64000000000000000000000000000000000000000000000000000000000000000000000000210084f616080f0b6b0080f736a80f030000002085f736080f7b0297f716080f2c1c007b0080c000090f001600070000001ea88f0036166ea88f0500000000000000001e001e6400000000000000000000000000000000006000000000000000000000000000c0000000b07b0180de08090f1122007b0280c016090f2c1c007b0080c0000a0f00160007000002bea98f0000006ea98f050000000000000002be02be640000000000000000000000000000000000000000000000000000000000000000000000007b0180de080a0f1122007b0280c0160a0f2c1c007b0080c0000b0f00160007000002beaa8f0000006eaa8f050000000000000002be02be640000000000000000000000000000000000000000000000000000000000000000000000007b0180de080b0f1122007b0280c0160b0f2c1c007b0080c0000c0f00160007000002beab8f0000006eab8f050000000000000002be02be640000000000000000000000000000000000000000000000000000000000000000000000007b0180de080c0f1122007b0280c0160c0f2c1c007b0080c0000d0f00160007000002beac8f0000006eac8f050000000000000002be02be640000000000000000000000000000000000000000000000000000000000000000000000007b0180de080d0f1122007b0280c0160d0f2c1c007b0080c0000e0f00160007000002bead8f0000006ead8f050000000000000002be02be640000000000000000000000000000000000000000000000000000000000000000000000007b0180de080e0f112200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006bd8 4 | attr_accessor :data 5 | 6 | def self.type_registry 7 | rval = {} 8 | MinimedRF::PumpEvents.constants.each do |event_class| 9 | klazz = MinimedRF::PumpEvents.const_get(event_class) 10 | next if klazz == MinimedRF::PumpEvents::Base 11 | rval[klazz.event_type_code] = klazz 12 | end 13 | rval 14 | end 15 | 16 | def initialize(data, pump_model = Model551.new) 17 | @registry = self.class.type_registry 18 | @data = data 19 | @pump_model = pump_model 20 | end 21 | 22 | def crc_ok? 23 | CRC16::compute(data.bytes[0..-3]) == data[-2..-1].unpack('n').first 24 | end 25 | 26 | def decode(date_range = nil, print = false) 27 | 28 | entries = [] 29 | skipped = "" 30 | unabsorbed_insulin_record = nil 31 | offset = 0 32 | 33 | while (data && data.size > 0) do 34 | event = match(date_range) 35 | if event 36 | unless skipped.empty? 37 | if print 38 | #puts "************************************************************** Skipped: " + skipped 39 | end 40 | skipped = "" 41 | end 42 | if print 43 | puts event.to_s 44 | end 45 | if event.class == PumpEvents::BolusNormal && !unabsorbed_insulin_record.nil? 46 | event.unabsorbed_insulin_records = unabsorbed_insulin_record 47 | unabsorbed_insulin_record = nil 48 | end 49 | if event.class == PumpEvents::UnabsorbedInsulin 50 | unabsorbed_insulin_record = event 51 | else 52 | entries << event 53 | end 54 | #puts "Offset = #{offset}, block = #{event.bytesize}, type = 0x#{data.getbyte(0).to_s(16)}" 55 | offset += event.bytesize 56 | @data = @data.byteslice((event.bytesize)..-1) 57 | else 58 | if data.getbyte(0) != 0 59 | print "Unknown history record type: 0x#{"%02x" % data.getbyte(0)}" 60 | return entries 61 | end 62 | #puts "Skipping: Offset = #{offset}" 63 | offset += 1 64 | skipped << "%02x" % data.getbyte(0) 65 | @data = @data.byteslice(1..-1) 66 | end 67 | end 68 | 69 | unless skipped.empty? || !print 70 | #puts "Trailing bytes: " + skipped 71 | end 72 | 73 | return entries 74 | end 75 | 76 | def match(date_range) 77 | type = data.getbyte(0) 78 | klazz = @registry[type] 79 | if klazz 80 | event = klazz.new(data, @pump_model) 81 | if data.bytesize < event.bytesize 82 | return nil 83 | end 84 | return event if date_range.nil? || event.valid_for(date_range) 85 | end 86 | end 87 | 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/minimed_rf/crc.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | class CRC8 3 | TABLE = [0, 155, 173, 54, 193, 90, 108, 247, 25, 130, 180, 47, 216, 67, 117, 238, 50, 169, 159, 4, 243, 104, 94, 197, 43, 176, 134, 29, 234, 113, 71, 220, 4 | 100, 255, 201, 82, 165, 62, 8, 147, 125, 230, 208, 75, 188, 39, 17, 138, 86, 205, 251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 142, 21, 35, 184, 200, 5 | 83, 101, 254, 9, 146, 164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 250, 97, 87, 204, 59, 160, 150, 13, 227, 120, 78, 213, 34, 185, 143, 20, 172, 55, 6 | 1, 154, 109, 246, 192, 91, 181, 46, 24, 131, 116, 239, 217, 66, 158, 5, 51, 168, 95, 196, 242, 105, 135, 28, 42, 177, 70, 221, 235, 112, 11, 144, 166, 7 | 61, 202, 81, 103, 252, 18, 137, 191, 36, 211, 72, 126, 229, 57, 162, 148, 15, 248, 99, 85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 111, 244, 194, 89, 8 | 174, 53, 3, 152, 118, 237, 219, 64, 183, 44, 26, 129, 93, 198, 240, 107, 156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 40, 179, 195, 88, 110, 245, 2, 9 | 153, 175, 52, 218, 65, 119, 236, 27, 128, 182, 45, 241, 106, 92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 41, 178, 132, 31, 167, 60, 10, 145, 102, 253, 10 | 203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 33, 186, 77, 214, 224, 123] 11 | 12 | # The table is computed as follows: 13 | # @table = [0] 14 | # msbit = 0x80 15 | # polynomial = 0x9b 16 | # t = msbit 17 | # i = 1 18 | # while (i < 256) 19 | # t = ((t << 1) & 0xff) ^ ((t & msbit > 0) ? polynomial : 0) 20 | # t = t & 0xff 21 | # j = 0 22 | # while (j < i) 23 | # @table[i+j] = (@table[j] ^ t) & 0xff 24 | # j = (j + 1) & 0xff 25 | # end 26 | # i = i * 2 27 | # end 28 | 29 | def self.compute(data) 30 | if data.is_a?(Array) 31 | bytes = data 32 | elsif data.is_a?(String) 33 | bytes = data.bytes 34 | else 35 | raise "CRC16 cannot compute crc of " + data.inspect 36 | end 37 | 38 | running_crc = 0 39 | bytes.each do |b| 40 | running_crc = TABLE[(running_crc ^ b) & 0xff] 41 | end 42 | running_crc 43 | end 44 | end 45 | 46 | class CRC16 47 | TABLE = [0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 37161, 41290, 45419, 49548, 53677, 57806, 61935, 4657, 528, 12915, 8786, 21173, 48 | 17044, 29431, 25302, 37689, 33560, 45947, 41818, 54205, 50076, 62463, 58334, 9314, 13379, 1056, 5121, 25830, 29895, 17572, 21637, 42346, 46411, 49 | 34088, 38153, 58862, 62927, 50604, 54669, 13907, 9842, 5649, 1584, 30423, 26358, 22165, 18100, 46939, 42874, 38681, 34616, 63455, 59390, 55197, 50 | 51132, 18628, 22757, 26758, 30887, 2112, 6241, 10242, 14371, 51660, 55789, 59790, 63919, 35144, 39273, 43274, 47403, 23285, 19156, 31415, 27286, 51 | 6769, 2640, 14899, 10770, 56317, 52188, 64447, 60318, 39801, 35672, 47931, 43802, 27814, 31879, 19684, 23749, 11298, 15363, 3168, 7233, 60846, 52 | 64911, 52716, 56781, 44330, 48395, 36200, 40265, 32407, 28342, 24277, 20212, 15891, 11826, 7761, 3696, 65439, 61374, 57309, 53244, 48923, 44858, 53 | 40793, 36728, 37256, 33193, 45514, 41451, 53516, 49453, 61774, 57711, 4224, 161, 12482, 8419, 20484, 16421, 28742, 24679, 33721, 37784, 41979, 54 | 46042, 49981, 54044, 58239, 62302, 689, 4752, 8947, 13010, 16949, 21012, 25207, 29270, 46570, 42443, 38312, 34185, 62830, 58703, 54572, 50445, 55 | 13538, 9411, 5280, 1153, 29798, 25671, 21540, 17413, 42971, 47098, 34713, 38840, 59231, 63358, 50973, 55100, 9939, 14066, 1681, 5808, 26199, 56 | 30326, 17941, 22068, 55628, 51565, 63758, 59695, 39368, 35305, 47498, 43435, 22596, 18533, 30726, 26663, 6336, 2273, 14466, 10403, 52093, 56156, 57 | 60223, 64286, 35833, 39896, 43963, 48026, 19061, 23124, 27191, 31254, 2801, 6864, 10931, 14994, 64814, 60687, 56684, 52557, 48554, 44427, 40424, 58 | 36297, 31782, 27655, 23652, 19525, 15522, 11395, 7392, 3265, 61215, 65342, 53085, 57212, 44955, 49082, 36825, 40952, 28183, 32310, 20053, 24180, 59 | 11923, 16050, 3793, 7920] 60 | # The table is computed as follows: 61 | 62 | # @table = [0] 63 | # i = 0 64 | # while i < 256 65 | # crc = 0 66 | # c = i << 8 67 | # j = 0 68 | # while j < 8 69 | # if ((crc^c) & 0x8000) > 0 70 | # crc = ( crc << 1 ) ^ 0x1021 71 | # else 72 | # crc = crc << 1 73 | # end 74 | # crc = crc & 0xffff 75 | # c = c << 1 76 | # j+=1 77 | # end 78 | # @table[i] = crc 79 | # i+=1 80 | # end 81 | 82 | def self.compute(data) 83 | if data.is_a?(Array) 84 | bytes = data 85 | elsif data.is_a?(String) 86 | bytes = data.bytes 87 | else 88 | raise "CRC16 cannot compute crc of " + data.inspect 89 | end 90 | 91 | crc = 0xffff 92 | bytes.each do |b| 93 | crc = ((crc << 8) ^ TABLE[((crc >> 8) ^ b) & 0xff]) & 0xffff 94 | end 95 | crc 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/minimed_rf/messages/pump_status.rb: -------------------------------------------------------------------------------- 1 | 2 | module MinimedRF 3 | class PumpStatus < Message 4 | def self.bit_blocks 5 | { 6 | sequence: [1,7], 7 | trend: [12,3], 8 | clock_type: [16,1], 9 | pump_hour: [19,5], 10 | pump_minute: [26,6], 11 | pump_second: [34,6], 12 | pump_year: [40,8], 13 | pump_month: [52,4], 14 | pump_day: [59,5], 15 | bg_h: [72, 8], 16 | prev_bg_h: [80, 8], 17 | #field_x3: [94,1], 18 | insulin_remaining: [101,11], 19 | batt: [116,4], 20 | reservoir_days_remaining: [120, 8], # "Days" + 1 21 | reservoir_minutes_remaining: [128, 16], # "Minutes" only correlate with pump display during final 24 hours 22 | sensor_age: [144,8], 23 | sensor_remaining: [152,8], 24 | next_cal_hour: [160,8], # ff at sensor end, 00 at sensor off 25 | next_cal_minute: [168,8], 26 | active_ins: [181, 11], 27 | prev_bg_l: [198, 1], 28 | bg_l: [199, 1], 29 | #field_x12: [201,7], 30 | sensor_hour: [227,5], 31 | sensor_minute: [234,6], 32 | sensor_year: [248, 8], 33 | sensor_month: [260, 4], 34 | sensor_day: [267,5] 35 | } 36 | end 37 | 38 | def clock_type 39 | b(:clock_type) ? :clock_type_24hr : :clock_type_12hr 40 | end 41 | 42 | def clock_type_str 43 | { 44 | clock_type_12hr: "12 hr", 45 | clock_type_24hr: "24 hr" 46 | }[clock_type] 47 | end 48 | 49 | def pump_timestamp 50 | if b(:pump_year) > 0 51 | Time.new(b(:pump_year) + 2000, b(:pump_month), b(:pump_day), b(:pump_hour), b(:pump_minute), b(:pump_second)) 52 | end 53 | end 54 | 55 | def sensor_timestamp 56 | if b(:sensor_year) > 0 57 | Time.new(b(:sensor_year) + 2000, b(:sensor_month), b(:sensor_day), b(:sensor_hour), b(:sensor_minute)) 58 | end 59 | end 60 | 61 | def sequence 62 | b(:sequence) 63 | end 64 | 65 | def trend 66 | case b(:trend) 67 | when 0b000 68 | "" 69 | when 0b001 70 | "up" 71 | when 0b010 72 | "double up" 73 | when 0b011 74 | "down" 75 | when 0b100 76 | "double down" 77 | else 78 | "Unknown(#{b(:trend)})" 79 | end 80 | end 81 | 82 | def sensor_status 83 | case b(:bg_h) 84 | when 0 85 | :sensor_missing 86 | when 1 87 | :meter_bg_now 88 | when 2 89 | :weak_signal 90 | when 3 91 | :calibration_error 92 | when 4 93 | :sensor_warmup 94 | when 5 95 | :sensor_ended 96 | when 7 97 | :high_bg # Above 400 98 | when 10 99 | :sensor_lost 100 | else 101 | :ok 102 | end 103 | end 104 | 105 | def active_insulin 106 | b(:active_ins) * 0.025 107 | end 108 | 109 | def parse_glucose(high, low) 110 | val = (high << 1) + low 111 | #val < 20 ? nil : val 112 | end 113 | 114 | def glucose 115 | parse_glucose(b(:bg_h), b(:bg_l)) 116 | end 117 | 118 | def previous_glucose 119 | parse_glucose(b(:prev_bg_h), b(:prev_bg_l)) 120 | end 121 | 122 | def sensor_age 123 | b(:sensor_age) 124 | end 125 | 126 | def sensor_remaining 127 | b(:sensor_remaining) 128 | end 129 | 130 | def insulin_remaining 131 | b(:insulin_remaining) / 10.0 132 | end 133 | 134 | def battery_pct 135 | (b(:batt) / 4.0 * 100).to_i 136 | end 137 | 138 | def to_s 139 | val = "PumpStatus: ##{sequence} (#{clock_type_str}) #{pump_timestamp} #{sensor_timestamp} - " 140 | 141 | case sensor_status 142 | when :sensor_missing 143 | val << "Sensor missing" 144 | when :meter_bg_now 145 | val << "Meter BG Now - PreviousGlucose=#{previous_glucose} - SensorAge=#{sensor_age}hrs" 146 | when :weak_signal 147 | val << "Weak Signal - Glucose=#{glucose} PreviousGlucose=#{previous_glucose} - SensorAge=#{sensor_age}hrs" 148 | when :calibration_error 149 | val << "Calibration Error - SensorAge=#{sensor_age}hrs" 150 | when :sensor_warmup 151 | val << "Sensor Warmup" 152 | when :sensor_ended 153 | val << "Sensor Ended" 154 | when :sensor_lost 155 | val << "Sensor Lost" 156 | when :high_bg 157 | val << "BG Above 400" 158 | else 159 | val << "Glucose=#{glucose} PreviousGlucose=#{previous_glucose}" 160 | end 161 | 162 | val << " SensorAge=#{sensor_age}hrs- SensorRemaining=#{sensor_remaining} InsulinRemaining=#{insulin_remaining}U Battery=#{battery_pct}%" 163 | 164 | if active_insulin > 0 165 | val << " ActiveInsulin=#{active_insulin.round(3)}" 166 | end 167 | 168 | unless trend.empty? 169 | val << " Trend=#{trend}" 170 | end 171 | 172 | val 173 | end 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /lib/minimed_rf/packet.rb: -------------------------------------------------------------------------------- 1 | module MinimedRF 2 | class Packet 3 | attr_accessor :address, :data, :sequence, :rssi, :message_type, :channel, :capture_time, :coding_errors, :packet_type 4 | 5 | def initialize 6 | coding_errors = 0 7 | end 8 | 9 | def raw_data 10 | if @raw_data.nil? && !@data.nil? 11 | @raw_data = encode 12 | end 13 | @raw_data 14 | end 15 | 16 | def raw_hex_data 17 | if raw_data 18 | raw_data.unpack('H*').first 19 | end 20 | end 21 | 22 | def hex_data 23 | if @data 24 | @data.unpack('H*').first 25 | end 26 | end 27 | 28 | def data=(data) 29 | # Packet type (first byte of packet): 30 | # 0xa2 (162) = mysentry 31 | # 0xa5 (165) = glucose meter (bayer contour) 32 | # 0xa6 (166) = paradigm remote (MMT-503NA) 33 | # 0xa7 (167) = pump 34 | # 0xa8 (168) = sensor test 35 | # 0xaa (170) = sensor 36 | # 0xab (171) = sensor2 37 | 38 | @packet_type = data.getbyte(0) 39 | 40 | @address = data.byteslice(1,3).unpack("H*").first 41 | 42 | @message_type = data.getbyte(4) 43 | 44 | @data = data 45 | 46 | end 47 | 48 | def packetdiag 49 | out = "packetdiag {\ncolwidth=32\n" 50 | bits = "" 51 | @data.each_byte do |d| 52 | bits << "%08b" % d 53 | end 54 | (0..(bits.length-1)).each do |idx| 55 | if bits[idx] == "1" 56 | out << "#{idx}-#{idx}: [color = \"#eeeeee\", style = \"0,1\"]\n" 57 | end 58 | end 59 | out << "}\n" 60 | end 61 | 62 | def valid? 63 | !@data.nil? && 64 | @data.bytesize > 4 && 65 | (crc.nil? || crc == computed_crc) 66 | end 67 | 68 | def channel 69 | @channel || '?' 70 | end 71 | 72 | def crc 73 | case packet_type 74 | when 0xa8, 0xaa, 0xab 75 | (data.bytes[-2] << 8) + data.bytes[-1] 76 | else 77 | data.bytes[-1] 78 | end 79 | end 80 | 81 | def computed_crc 82 | case packet_type 83 | when 0xa8, 0xaa, 0xab 84 | CRC16::compute(data.bytes[0..-3]) 85 | else 86 | CRC8::compute(data.bytes[0..-2]) 87 | end 88 | end 89 | 90 | def encode 91 | codes = [] 92 | @data.bytes.each { |b| 93 | codes << CODES[(b >> 4)] 94 | codes << CODES[b & 0xf] 95 | } 96 | # aaaaaabb bbbbcccc ccdddddd 97 | bits = codes.map {|code| "%06b" % code}.join + "000000000000" 98 | output = "" 99 | bits.scan(/.{8}/).each do |byte_bits| 100 | output << "%02x" % Integer("0b"+byte_bits) 101 | end 102 | output 103 | end 104 | 105 | def local_capture_time 106 | capture_time ? capture_time.localtime : nil 107 | end 108 | 109 | def to_s 110 | msg = to_message 111 | if msg 112 | msg.to_s 113 | elsif valid? 114 | data.unpack("H*") 115 | elsif data 116 | "invalid: #{data.unpack("H*").first} expected_crc=#{"%02x" % computed_crc}" 117 | else 118 | "invalid: encoding errors" 119 | end 120 | end 121 | 122 | def to_message 123 | if valid? 124 | case packet_type 125 | when 0xa2,0xa7 126 | if MessageTypeMap.include?(message_type) 127 | MessageTypeMap[message_type].new(data[5..-2]) 128 | end 129 | when 0xa5 130 | MinimedRF::Meter.new(data[0..-2]) 131 | when 0xa6 132 | MinimedRF::Remote.new(data[0..-2]) 133 | when 0xa8, 0xaa, 0xab 134 | MinimedRF::Sensor.new(data) 135 | # Sensor 136 | end 137 | end 138 | end 139 | 140 | def self.from_hex(hex) 141 | p = Packet.new 142 | p.data = [hex].pack('H*') 143 | p 144 | end 145 | 146 | def self.from_hex_without_crc(hex) 147 | data = [hex].pack('H*') 148 | p = Packet.new 149 | p.data = (data.bytes << CRC8::compute(data.bytes)).pack("c*") 150 | p 151 | end 152 | 153 | def self.decode_from_radio_hex(hex_str) 154 | decode_from_radio([hex_str].pack('H*')) 155 | end 156 | 157 | def self.decode_from_radio(data) 158 | coding_errors = 0 159 | p = Packet.new 160 | radio_bytes = data.bytes 161 | bits = radio_bytes.map {|d| "%08b" % d}.join 162 | decoded_symbols = [] 163 | bits.scan(/.{6}/).each do |word_bits| 164 | break if word_bits == '000000' 165 | symbol = CODE_SYMBOLS[word_bits] 166 | if symbol.nil? 167 | coding_errors += 1 168 | else 169 | decoded_symbols << symbol 170 | end 171 | end 172 | if decoded_symbols.count > 12 173 | if decoded_symbols.length.odd? 174 | decoded_symbols = decoded_symbols[0..-2] 175 | end 176 | p.data = [decoded_symbols.join].pack("H*") 177 | end 178 | p.coding_errors = coding_errors 179 | p 180 | end 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /docs/GetModel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | blockdiag 10 | packetdiag { 11 | colwidth=32 12 | 0-7: packet_type [color = "none"] 13 | 8-31: addr [color = "none"] 14 | 32-39: message_type [color = "none"] 15 | } 16 | 17 | 18 | 0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 16 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 32 53 | 54 | 55 | 56 | 57 | 58 | packet_type 59 | 60 | 61 | 62 | 63 | 64 | addr 65 | 66 | 67 | 68 | 69 | 70 | message_type 71 | 72 | -------------------------------------------------------------------------------- /docs/Remote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | blockdiag 10 | packetdiag { 11 | colwidth=32 12 | 0-7: packet_type [color = "none"] 13 | 8-31: addr [color = "none"] 14 | 32-39: message_type [color = "none"] 15 | } 16 | 17 | 18 | 0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 16 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 32 53 | 54 | 55 | 56 | 57 | 58 | packet_type 59 | 60 | 61 | 62 | 63 | 64 | addr 65 | 66 | 67 | 68 | 69 | 70 | message_type 71 | 72 | -------------------------------------------------------------------------------- /docs/WakeUp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | blockdiag 10 | packetdiag { 11 | colwidth=32 12 | 0-7: packet_type [color = "none"] 13 | 8-31: addr [color = "none"] 14 | 32-39: message_type [color = "none"] 15 | } 16 | 17 | 18 | 0 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 16 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 32 53 | 54 | 55 | 56 | 57 | 58 | packet_type 59 | 60 | 61 | 62 | 63 | 64 | addr 65 | 66 | 67 | 68 | 69 | 70 | message_type 71 | 72 | -------------------------------------------------------------------------------- /bin/mmhistory: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Takes a binary file or hexdata string, and decodes it as a history page. 4 | 5 | 6 | require 'minimed_rf' 7 | require 'json' 8 | require 'optparse' 9 | require 'scanf' 10 | require 'tempfile' 11 | 12 | options = {} 13 | OptionParser.new do |opts| 14 | opts.banner = "Usage: mmhistory [options] [hexdata]" 15 | 16 | opts.on("-d", "--diff-with-decocare", "Compare output to decocare") do |v| 17 | options[:diff] = true 18 | end 19 | opts.on("-f", "--file=file", "Load data from file") do |v| 20 | options[:file] = v 21 | end 22 | opts.on("-p", "--pump=model", "Specify pump model. 522, 551, etc.") do |v| 23 | options[:model] = v 24 | end 25 | opts.on('-h', '--help', 'Displays Help') do 26 | puts opts 27 | exit 28 | end 29 | end.parse! 30 | 31 | def compare_attr(ctx, d_attr, m_attr, attr_name) 32 | if d_attr != m_attr 33 | puts "#{ctx}:#{attr_name} mismatch. decocare = #{d_attr.inspect}, minimed_rf = #{m_attr.inspect}" 34 | return false 35 | end 36 | true 37 | end 38 | 39 | def map_type_to_decocare_type(type) 40 | { 41 | "AlarmSensor" => "SensorAlert", 42 | "BolusWizardBolusEstimate" => "BolusWizard", 43 | "BolusNormal" => "Bolus", 44 | "ResultDailyTotal" => "MResultTotals", 45 | "JournalEntryPumpLowReservoir" => "LowReservoir", 46 | "Suspend" => "PumpSuspend", 47 | "Resume" => "PumpResume", 48 | "AlarmPump" => "NoDelivery", 49 | }[type] || type 50 | end 51 | 52 | def compare_to_decocare(records, file, model) 53 | #puts "Running: mm-decode-history-page.py --model=#{model} --collate #{file}" 54 | output = `/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/bin/mm-decode-history-page.py --model=#{model} --collate #{file} | egrep -v '(^#|^.end)'` 55 | ## Decocare sorts most recent first 56 | d_records = JSON.parse(output).reverse 57 | 58 | if d_records.count != records.count 59 | puts "Mismatch: #{d_records.count} decoded by decocare, #{records.count} by minimed_rf" 60 | end 61 | 62 | d_records.each_with_index do |dr, i| 63 | r = records[i] 64 | ctx = "record[#{i}]" 65 | 66 | if r.nil? 67 | puts "minimed_rf record = nil, decocare = #{dr.inspect}" 68 | next 69 | end 70 | 71 | # Compare type 72 | next unless compare_attr(ctx, dr["_type"], map_type_to_decocare_type(r["_type"]), "_type") 73 | ctx = "#{ctx}:#{dr["_type"]} - " 74 | r.delete("_type") 75 | dr.delete("_type") 76 | 77 | # Don't care about description 78 | r.delete("description") 79 | dr.delete("description") 80 | dr.delete("_description") 81 | dr.delete("unabsorbed_insulin_count") # This seems to always be '??' 82 | 83 | # Compare data 84 | d_data = dr["_head"] + dr["_date"] + dr["_body"] 85 | if d_data != r["_raw"] 86 | puts "#{ctx} - data mismatch: decocare = #{dr["_head"].inspect} #{dr["_date"].inspect} #{dr["_body"].inspect}, minimed_rf = #{r["_raw"].inspect}" 87 | end 88 | dr.delete("_head") 89 | dr.delete("_date") 90 | dr.delete("_body") 91 | r.delete("_raw") 92 | 93 | keys = (r.keys + dr.keys).sort.uniq 94 | 95 | mismatch = false 96 | keys.each do |k| 97 | next if k[0] == "_" 98 | next if k[0..11] == "unknown_byte" 99 | 100 | if k == "appended" 101 | if dr["appended"].nil? 102 | puts "#{ctx}:appended/data mismatch. decocare missing appended key" 103 | mismatch = true 104 | elsif r["appended"].nil? 105 | puts "#{ctx}:appended/data mismatch. minimed_rf missing appended key" 106 | mismatch = true 107 | elsif dr["appended"][0]["data"] != r["appended"]["data"] 108 | puts "#{ctx}:appended/data mismatch. decocare = #{dr["appended"]["data"].inspect}, minimed_rf = #{r["appended"]["data"].inspect}" 109 | mismatch = true 110 | end 111 | next 112 | end 113 | 114 | if !compare_attr(ctx, dr[k], r[k], k) 115 | mismatch = true 116 | end 117 | end 118 | 119 | if mismatch 120 | # Look for carelink csv 121 | dir = File.dirname(file) 122 | csv_files = Dir.glob(dir + "/*.csv") + Dir.glob(dir + "/../*.csv") 123 | if csv_files.length > 0 124 | year, month, day, hour, min, sec = dr["timestamp"].scanf("%4d-%2d-%2dT%2d:%2d:%2d") 125 | carelink_timestamp = sprintf("%d/%d/%d %02d:%02d:%02d", month, day, year - 2000, hour, min, sec) 126 | puts `grep '#{carelink_timestamp}' #{csv_files.join(' ')}` 127 | end 128 | puts "=====================================================" 129 | end 130 | 131 | end 132 | end 133 | 134 | if options[:file] 135 | data = File.read(options[:file]) 136 | else 137 | if ARGV.length > 0 138 | data = [ARGV[0]].pack('H*') 139 | else 140 | puts "Usage: mmhistory [options] [hexdata]" 141 | exit -1 142 | end 143 | end 144 | if options[:model] 145 | model_str = options[:model] 146 | else 147 | model_str = '551' 148 | end 149 | model = MinimedRF::Models[model_str] 150 | if model.nil? 151 | puts "Invalid pump model: #{model_str}" 152 | end 153 | history_page = MinimedRF::HistoryPage.new(data, model.new) 154 | records = history_page.decode(nil, false).map(&:as_json) 155 | if !options[:diff] 156 | puts JSON.pretty_generate(records) 157 | else 158 | file = Tempfile.new('historypage') 159 | file.write(data) 160 | file.path 161 | file.close 162 | compare_to_decocare(JSON.parse(JSON.generate(records)), file.path, model_str) 163 | file.unlink 164 | end 165 | -------------------------------------------------------------------------------- /docs/DeviceLink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | blockdiag 10 | packetdiag { 11 | colwidth=32 12 | 0-7: packet_type [color = "none"] 13 | 8-31: addr [color = "none"] 14 | 32-39: message_type [color = "none"] 15 | 40-47: sequence [color = "none"] 16 | } 17 | 18 | 19 | 0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 16 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 32 54 | 55 | 56 | 57 | 58 | 59 | packet_type 60 | 61 | 62 | 63 | 64 | 65 | addr 66 | 67 | 68 | 69 | 70 | 71 | message_type 72 | 73 | 74 | 75 | 76 | 77 | sequence 78 | 79 | -------------------------------------------------------------------------------- /docs/DeviceTest.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | blockdiag 10 | packetdiag { 11 | colwidth=32 12 | 0-7: packet_type [color = "none"] 13 | 8-31: addr [color = "none"] 14 | 32-39: message_type [color = "none"] 15 | 40-47: sequence [color = "none"] 16 | } 17 | 18 | 19 | 0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 16 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 32 54 | 55 | 56 | 57 | 58 | 59 | packet_type 60 | 61 | 62 | 63 | 64 | 65 | addr 66 | 67 | 68 | 69 | 70 | 71 | message_type 72 | 73 | 74 | 75 | 76 | 77 | sequence 78 | 79 | -------------------------------------------------------------------------------- /docs/FindDevice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | blockdiag 10 | packetdiag { 11 | colwidth=32 12 | 0-7: packet_type [color = "none"] 13 | 8-31: addr [color = "none"] 14 | 32-39: message_type [color = "none"] 15 | 40-47: sequence [color = "none"] 16 | } 17 | 18 | 19 | 0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 16 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 32 54 | 55 | 56 | 57 | 58 | 59 | packet_type 60 | 61 | 62 | 63 | 64 | 65 | addr 66 | 67 | 68 | 69 | 70 | 71 | message_type 72 | 73 | 74 | 75 | 76 | 77 | sequence 78 | 79 | -------------------------------------------------------------------------------- /docs/PumpStatusAck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | blockdiag 10 | packetdiag { 11 | colwidth=32 12 | 0-7: packet_type [color = "none"] 13 | 8-31: addr [color = "none"] 14 | 32-39: message_type [color = "none"] 15 | 41-47: sequence [color = "none"] 16 | } 17 | 18 | 19 | 0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 16 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 32 54 | 55 | 56 | 57 | 58 | 59 | packet_type 60 | 61 | 62 | 63 | 64 | 65 | addr 66 | 67 | 68 | 69 | 70 | 71 | message_type 72 | 73 | 74 | 75 | 76 | 77 | sequence 78 | 79 | -------------------------------------------------------------------------------- /lib/minimed_rf/rfspy.rb: -------------------------------------------------------------------------------- 1 | require 'serialport' 2 | 3 | module MinimedRF 4 | class RFSpy 5 | 6 | CMD_GET_STATE = 1 7 | CMD_GET_VERSION = 2 8 | CMD_GET_PACKET = 3 9 | CMD_SEND_PACKET = 4 10 | CMD_SEND_AND_LISTEN = 5 11 | CMD_UPDATE_REGISTER = 6 12 | 13 | REG_FREQ2 = 0x09 14 | REG_FREQ1 = 0x0A 15 | REG_FREQ0 = 0x0B 16 | REG_MDMCFG4 = 0x0C 17 | REG_MDMCFG3 = 0x0D 18 | REG_MDMCFG2 = 0x0E 19 | REG_MDMCFG1 = 0x0F 20 | REG_MDMCFG0 = 0x10 21 | REG_AGCCTRL2 = 0x17 22 | REG_AGCCTRL1 = 0x18 23 | REG_AGCCTRL0 = 0x19 24 | REG_FREND1 = 0x1A 25 | REG_FREND0 = 0x1B 26 | 27 | FREQ_XTAL = (ENV["RILEYLINK_XTAL_FREQ"] || 24000000).to_i 28 | 29 | def initialize(path) 30 | @ser = SerialPort.new path 31 | @ser.baud = 19200 32 | @ser.flow_control = rts_cts_flag 33 | # Non-blocking read 34 | @ser.read_timeout = -1 35 | @buf = "" 36 | end 37 | 38 | def rts_cts_flag 39 | if !ENV.has_key?('RFSPY_RTSCTS') || ENV['RFSPY_RTSCTS'].to_i == 1 40 | SerialPort::HARD 41 | else 42 | SerialPort::NONE 43 | end 44 | end 45 | 46 | def update_register(reg, value) 47 | args = [reg, value].pack("c*") 48 | do_command(CMD_UPDATE_REGISTER, args) 49 | end 50 | 51 | def set_base_freq(freq_mhz) 52 | val = ((freq_mhz * 1000000)/(FREQ_XTAL/(2**16).to_f)).to_i 53 | #puts "Updating freq: 0x#{val.to_s(16)}" 54 | update_register(REG_FREQ0, val & 0xff) 55 | update_register(REG_FREQ1, (val >> 8) & 0xff) 56 | update_register(REG_FREQ2, (val >> 16) & 0xff) 57 | end 58 | 59 | def set_channel_spacing(ch_khz, existing_mdmcfg1) 60 | chanspc_e, chanspc_m = calc_chanspc(ch_khz) 61 | update_register(REG_MDMCFG1, existing_mdmcfg1 & 0b11111100 | chanspc_e) 62 | update_register(REG_MDMCFG0, chanspc_m) 63 | end 64 | 65 | def calc_chanspc(ch_khz) 66 | freq_div = (MinimedRF::RFSpy::FREQ_XTAL/(2**18).to_f) 67 | vals = [] 68 | d = [] 69 | [0,1,2,3].each do |chanspc_e| 70 | chanspc_m = ((ch_khz*1000)/(freq_div * 2**chanspc_e) - 256).round; 71 | if chanspc_m >= 0 && chanspc_m < 256 72 | return chanspc_e, chanspc_m 73 | end 74 | end 75 | raise "#{ch_khz}kHz out of range for CHANSPC" 76 | end 77 | 78 | 79 | 80 | def do_command(command, param="") 81 | send_command(command, param) 82 | get_response 83 | end 84 | 85 | def send_command(command, param="") 86 | #puts "Sending command: #{command.inspect}" 87 | @ser.write(command.chr) 88 | if param.bytesize > 0 89 | @ser.write(param) 90 | end 91 | end 92 | 93 | def get_response 94 | @ser.read_timeout = 0 95 | while 1 96 | @buf += @ser.readpartial(4096) 97 | #puts "Read: #{@buf.unpack("H*")}" 98 | eop = @buf.bytes.index(0) 99 | if eop 100 | r = @buf.byteslice(0,eop) 101 | @buf = @buf.byteslice(eop+1..-1) 102 | return r 103 | end 104 | end 105 | end 106 | 107 | def response_to_packet(data) 108 | if data.bytesize > 2 109 | packet = MinimedRF::Packet.decode_from_radio(data.byteslice(2..-1)) 110 | rssi_dec = data.getbyte(0) 111 | rssi_offset = 73 112 | if rssi_dec >= 128 113 | packet.rssi = (( rssi_dec - 256) / 2) - rssi_offset 114 | else 115 | packet.rssi = (rssi_dec / 2) - rssi_offset 116 | end 117 | packet.sequence = data.getbyte(1) 118 | packet 119 | else 120 | #puts "#{Time.now.strftime('%H:%M:%S.%3N')} Timeout" 121 | end 122 | end 123 | 124 | def get_packet(channel, timeout = 0) 125 | args = [channel, timeout >> 24, (timeout >> 16) & 0xff, (timeout >> 8) & 0xff, timeout & 0xff].pack("c*") 126 | data = do_command(CMD_GET_PACKET, args) 127 | #puts "#{Time.now.strftime('%H:%M:%S.%3N')} Raw data: #{data.unpack("H*")}" 128 | response_to_packet(data) 129 | end 130 | 131 | def send_packet(data, channel, repeat=0, msec_repeat_delay=0) 132 | p = MinimedRF::Packet.from_hex_without_crc(data) 133 | encoded = [p.encode].pack('H*') 134 | prefix = [channel, repeat, msec_repeat_delay].pack("c*") 135 | data = do_command(CMD_SEND_PACKET, prefix + encoded) 136 | #puts "#{Time.now.strftime('%H:%M:%S.%3N')} Sent #{p.encode}" 137 | data 138 | end 139 | 140 | def send_and_listen(data, send_channel, repeat, delay_ms, listen_channel, timeout, retry_count) 141 | p = MinimedRF::Packet.from_hex_without_crc(data) 142 | encoded = [p.encode].pack('H*') 143 | prefix = [send_channel, repeat, delay_ms, listen_channel, timeout >> 24, (timeout >> 16) & 0xff, (timeout >> 8) & 0xff, timeout & 0xff, retry_count].pack("c*") 144 | data = do_command(CMD_SEND_AND_LISTEN, prefix + encoded) 145 | response_to_packet(data) 146 | end 147 | 148 | def print_and_flush(str) 149 | print str 150 | $stdout.flush 151 | end 152 | 153 | def sync 154 | while 1 155 | send_command(CMD_GET_STATE) 156 | data = get_response 157 | if data == "OK" 158 | puts "RileyLink " + data 159 | break 160 | end 161 | #puts "retry" 162 | print_and_flush "." 163 | end 164 | 165 | while 1 166 | send_command(CMD_GET_VERSION) 167 | data = get_response 168 | if data.bytesize >= 3 169 | puts "Version: " + data 170 | break 171 | end 172 | #puts "retry" 173 | print_and_flush "." 174 | end 175 | end 176 | end 177 | end 178 | -------------------------------------------------------------------------------- /bin/mmtune: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This utility talks over a serial connection to a RileyLink that has been 4 | # loaded with the subg_rfspy firmware (https://github.com/ps2/subg_rfspy) 5 | 6 | # It attempts to query pump model many times to get an estimate of connection quality 7 | 8 | require 'minimed_rf' 9 | require 'minimed_rf/rfspy' 10 | 11 | if ARGV.length < 2 || ARGV[1].length != 6 12 | puts "Usage: mmtune /dev/tty.usbserial-A9048LGG pumpserial" 13 | if ARGV.length > 1 && ARGV[1].length != 6 14 | puts "Error: pumpserial should be a six character id, like '55AB12'" 15 | end 16 | exit -1 17 | end 18 | 19 | pump_serial = ARGV[1] 20 | 21 | class MMTune 22 | 23 | def initialize(path, pump_serial) 24 | @rx_channel = 0 25 | @tx_channel = 0 26 | @rx_timeout_ms = 80 27 | @pump_serial = pump_serial 28 | 29 | puts "Opening #{path}" 30 | @rf = MinimedRF::RFSpy.new(path) 31 | @rf.sync 32 | end 33 | 34 | def print_packet(p) 35 | if p.nil? 36 | puts "Nil packet!" 37 | else 38 | puts "#{Time.now.strftime('%H:%M:%S.%3N')} #{"%3d" % p.rssi} (#{"%3d" % p.sequence}): #{p}" 39 | #puts "raw: #{p.hex_data}" 40 | end 41 | end 42 | 43 | def wakeup 44 | # Try quick model check to see if pump is awake 45 | awake = false 46 | 3.times do 47 | @rf.send_packet("a7" + @pump_serial + "8d00", @tx_channel) 48 | packet = @rf.get_packet(@rx_channel, 80) 49 | if packet 50 | print_packet(packet) 51 | awake = true 52 | break 53 | end 54 | end 55 | 56 | if !awake 57 | # Send 200 wake-up packets 58 | @rf.send_packet("a7" + @pump_serial + "5d00", @tx_channel, 200) 59 | wake_ack = @rf.get_packet(@rx_channel, 9000) # wait 9 s for response 60 | if wake_ack 61 | print_packet(wake_ack) 62 | else 63 | puts "Pump not responding" 64 | end 65 | end 66 | end 67 | 68 | def run_trial(var) 69 | sample_size = 5 70 | success_count = 0 71 | crc_error_count = 0 72 | timeout_count = 0 73 | valid_rssi_readings = [] 74 | sample_size.times do 75 | @rf.send_packet("a7" + @pump_serial + "8d00", @tx_channel) # Get Model 76 | packet = @rf.get_packet(@rx_channel, 80) 77 | if packet.nil? 78 | timeout_count += 1 79 | elsif !packet.valid? 80 | crc_error_count += 1 81 | else 82 | success_count += 1 83 | valid_rssi_readings << packet.rssi 84 | end 85 | end 86 | avg_rssi = -99 87 | if valid_rssi_readings.length > 0 88 | avg_rssi = valid_rssi_readings.inject{ |sum, el| sum + el }.to_f / valid_rssi_readings.size 89 | end 90 | puts "#{var}, #{timeout_count}, #{crc_error_count+timeout_count}, rssi:%0.1f" % avg_rssi 91 | return [var, success_count, avg_rssi] 92 | end 93 | 94 | def continuous_trial 95 | loop { run_trial(".") } 96 | end 97 | 98 | def scan_over_freq(start_freq, end_freq, steps) 99 | step_size = (end_freq - start_freq) / steps 100 | cur_freq = start_freq 101 | results = [] 102 | while cur_freq < end_freq 103 | @rf.set_base_freq(cur_freq) 104 | results << run_trial("%0.3f" % cur_freq) 105 | cur_freq += step_size 106 | end 107 | results.sort_by { |r| r[1..2]}.reverse 108 | end 109 | 110 | def scan_over_rx_bw 111 | bw_bits = 0xf 112 | while bw_bits > 0 113 | new_val = 0x09 | (bw_bits << 4) # 0x09 = 16kbs data rate 114 | @rf.update_register(MinimedRF::RFSpy::REG_MDMCFG4, new_val) 115 | run_trial(bw_bits) 116 | bw_bits -= 1 117 | end 118 | end 119 | 120 | def scan_over_agc 121 | agc = 0x0 122 | while agc < 8 123 | @rf.update_register(MinimedRF::RFSpy::REG_AGCCTRL2, agc) 124 | run_trial(agc) 125 | agc += 1 126 | end 127 | end 128 | 129 | def scan_over_frend 130 | @rf.update_register(MinimedRF::RFSpy::REG_FREND1, 0xB6) 131 | run_trial("0xb6") 132 | @rf.update_register(MinimedRF::RFSpy::REG_FREND1, 0x56) 133 | run_trial("0x56") 134 | end 135 | 136 | def scan_over_rx_freq(range, base_freq, chanspc) 137 | range.each do |ch| 138 | @rx_channel = ch 139 | rx_freq = base_freq + ch * chanspc 140 | run_trial("rx%02d %0.2f" % [ch, rx_freq]) 141 | end 142 | end 143 | 144 | def scan_over_tx_freq(range, base_freq, chanspc) 145 | range.each do |ch| 146 | @tx_channel = ch 147 | tx_freq = base_freq + ch * chanspc 148 | run_trial("tx%02d %0.2f" % [ch, tx_freq]) 149 | end 150 | end 151 | 152 | def do_rxtx_spacing_test 153 | base_freq = 916.3 154 | chanspc = 30 155 | @rf.set_base_freq(base_freq) 156 | @rf.set_channel_spacing(chanspc,0x61) # 30 kHz 157 | @tx_channel = 11 # 916.63 158 | scan_over_rx_freq(0..20,base_freq,chanspc/1000.0) 159 | puts "="*30 160 | @rx_channel = 11 # 916.63 161 | scan_over_tx_freq(0..20,base_freq,chanspc/1000.0) 162 | end 163 | 164 | 165 | def run 166 | 167 | # Set rx bw to 75kHz and 16kbs data rate 168 | @rf.update_register(MinimedRF::RFSpy::REG_MDMCFG4, 0xd9) 169 | 170 | # Pump in free space 171 | @rf.set_base_freq(916.630) 172 | 173 | # Sometimes getting lower ber with 0x07 here (default is 0x03) 174 | @rf.update_register(MinimedRF::RFSpy::REG_AGCCTRL2, 0x07) 175 | 176 | @rf.update_register(MinimedRF::RFSpy::REG_AGCCTRL1, 0x40) 177 | 178 | # With rx bw > 101kzHZ, this should be 0xB6, otherwise 0x56 179 | @rf.update_register(MinimedRF::RFSpy::REG_FREND1, 0x56) 180 | 181 | # default (0x91) seems to work best 182 | #@rf.update_register(MinimedRF::RFSpy::REG_AGCCTRL0, 0x91) 183 | 184 | wakeup 185 | 186 | results = scan_over_freq(916.5, 916.9, 20) 187 | if results[0][1] > 0 188 | puts "Setting to best freq of #{results[0][0]}" 189 | best = results[0][0].to_f 190 | @rf.set_base_freq(best) 191 | #continuous_trial 192 | else 193 | @rf.set_base_freq(916.630) 194 | end 195 | 196 | 197 | #do_rxtx_spacing_test 198 | #scan_over_rx_bw 199 | #scan_over_agc 200 | #scan_over_frend 201 | 202 | end 203 | 204 | end 205 | 206 | tune = MMTune.new(ARGV[0], ARGV[1]) 207 | tune.run 208 | -------------------------------------------------------------------------------- /docs/Meter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | blockdiag 10 | packetdiag { 11 | colwidth=32 12 | 0-7: packet_type [color = "none"] 13 | 8-31: addr [color = "none"] 14 | 32-39: message_type [color = "none"] 15 | 45-46: alert [color = "none"] 16 | 47-55: glucose [color = "none"] 17 | } 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 16 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 32 55 | 56 | 57 | 58 | 59 | 60 | packet_type 61 | 62 | 63 | 64 | 65 | 66 | addr 67 | 68 | 69 | 70 | 71 | 72 | message_type 73 | 74 | 75 | 76 | 77 | 78 | alert 79 | 80 | 81 | 82 | 83 | 84 | glucose 85 | 86 | --------------------------------------------------------------------------------