Module: Meshtastic::Admin::Firmware::NordicDFU

Defined in:
lib/meshtastic/admin/firmware/nordic_dfu.rb

Overview

Adafruit SDK11 legacy BLE DFU, not Nordic Secure DFU.

Constant Summary collapse

CONTROL_UUID =
'00001531-1212-efde-1523-785feabcd123'
PACKET_UUID =
'00001532-1212-efde-1523-785feabcd123'
MAX_PACKAGE =
8 * 1024 * 1024

Class Method Summary collapse

Class Method Details

.authorsObject



194
195
196
# File 'lib/meshtastic/admin/firmware/nordic_dfu.rb', line 194

public_class_method def self.authors
  ['Meshtastic Ruby contributors']
end

.helpObject



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/meshtastic/admin/firmware/nordic_dfu.rb', line 198

public_class_method def self.help
  puts <<~HELP
    Install application via Adafruit legacy BLE DFU.
    #{self}.install(
      protocol: 'optional - protocol selector, must be nordic_dfu',
      package: 'optional - DFU ZIP filename; required unless package_bytes is supplied',
      package_bytes: 'optional - raw DFU ZIP bytes instead of filename',
      gatt: 'optional - connected adapter implementing write/subscribe/notification/close',
      address: 'optional - bootloader BLE address; required without gatt',
      adapter: 'optional - BlueZ adapter name, default hci0',
      timeout: 'optional - positive notification timeout seconds, default 30'
    )

    List module contributing authors.
    #{self}.authors
  HELP
end

.install(opts = {}) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/meshtastic/admin/firmware/nordic_dfu.rb', line 17

public_class_method def self.install(opts = {})
  unknown = opts.keys - %i[package package_bytes gatt address adapter timeout protocol]
  raise ArgumentError, "Unsupported Nordic DFU options: #{unknown.join(', ')}" unless unknown.empty?
  raise ArgumentError, 'protocol must be :nordic_dfu' unless opts.fetch(:protocol, :nordic_dfu) == :nordic_dfu
  raise ArgumentError, 'Supply exactly one of package or package_bytes' unless opts.key?(:package) ^ opts.key?(:package_bytes)

  image, init = package(opts.merge({}))
  timeout = opts.fetch(:timeout, 30)
  raise ArgumentError, 'timeout must be finite and positive' unless timeout.is_a?(Numeric) && timeout.positive? && timeout.finite?

  gatt = opts[:gatt]
  unless gatt
    require_relative 'ble'
    gatt = BLE::BlueZ.new(address: opts.fetch(:address), adapter: opts.fetch(:adapter, 'hci0'),
                          timeout: timeout, service_uuid: '00001530-1212-efde-1523-785feabcd123').connect
  end
  session = { gatt: gatt, timeout: timeout }
  gatt.subscribe(uuid: CONTROL_UUID)
  control(session.merge(bytes: [1, 4].pack('C*')))
  packet(session.merge(bytes: [0, 0, image.bytesize].pack('V3')))
  response(session.merge(opcode: 1))
  control(session.merge(bytes: [2, 0].pack('C*')))
  init.bytes.each_slice(20) { |chunk| packet(session.merge(bytes: chunk.pack('C*'))) }
  control(session.merge(bytes: [2, 1].pack('C*')))
  response(session.merge(opcode: 2))
  # PRN=1 bounds outstanding data and catches loss before any next chunk.
  control(session.merge(bytes: [8, 1].pack('Cv')))
  control(session.merge(bytes: [3].pack('C')))
  offset = 0
  image.bytes.each_slice(20) do |chunk|
    packet(session.merge(bytes: chunk.pack('C*')))
    offset += chunk.length
    if offset == image.bytesize
      response(session.merge(opcode: 3))
    else
      receipt = notification(session)
      raise IOError, 'DFU packet receipt offset mismatch' unless receipt == [17, offset].pack('CV')
    end
  end
  control(session.merge(bytes: [4].pack('C')))
  response(session.merge(opcode: 4))
  control(session.merge(bytes: [5].pack('C')))
  { status: :verified, protocol: :nordic_dfu, bytes: image.bytesize,
    sha256: Digest::SHA256.hexdigest(image), reboot_verified: false }
ensure
  gatt&.close
end