Skip to content

Add support for reading and writing to I2C devices from lua#934

Merged
d0k3 merged 15 commits intod0k3:masterfrom
rosaage:master
Mar 25, 2026
Merged

Add support for reading and writing to I2C devices from lua#934
d0k3 merged 15 commits intod0k3:masterfrom
rosaage:master

Conversation

@rosaage
Copy link
Copy Markdown
Contributor

@rosaage rosaage commented Oct 24, 2025

This is essentially just a wrapper for the exisiting C functions.
Writing is currently set to require PERM_MEMORY

I have tested this a bit, and it seems to work ok
The syntax is i2c.read(device_id, register, length) and i2c.write(device_id, register, data_table)
Reading returns a table of values, while writing returns a bool True on success or errors on a fail.
(Should this return a false and a string instead of error?)

Test script
print("Hello World!")

print(CONSOLE_TYPE)

-- device 3 is the mcu
-- 0x09 is volume slider
ui.echo(i2c.read(3, 0x09, 1)[1])

-- 0x0D is the system voltage
local volt = i2c.read(3, 0x0D, 1)
local voltf = volt[1] * 5 / 256.0
ui.echo(voltf)

-- 0x28 is Brightness of the WiFi/Power LED 
ui.echo("Reg: "..i2c.read(3, 0x28, 1)[1])
i2c.write(3, 0x28, {200})
ui.echo("Reg: "..i2c.read(3, 0x28, 1)[1])
i2c.write(3, 0x28, {150})
ui.echo("Reg: "..i2c.read(3, 0x28, 1)[1])
i2c.write(3, 0x28, {70})
ui.echo("Reg: "..i2c.read(3, 0x28, 1)[1])

-- 0x15-17 is not in use
local data = i2c.read(3, 0x15, 3)
ui.echo(string.format("Reg: %d, %d, %d", data[1], data[2], data[3]))
i2c.write(3, 0x15, {20, 30, 40})
data = i2c.read(3, 0x15, 3)
ui.echo(string.format("Reg: %d, %d, %d", data[1], data[2], data[3]))

Volume slider reads fine, voltage reads back 3.68 (seems ok), I don't see any LED stuff, but when reading back the same register I see the changes. The final test is reading and writing multiple registers, this also seems to work ok.

@ihaveamac
Copy link
Copy Markdown
Contributor

while writing returns a bool True on success or errors on a fail.

In this case I think it should not return anything, and raise an error if it fails. Or if there is a reason to return a specific value, then return that.

@MisterSheeple
Copy link
Copy Markdown
Contributor

Since malicious or negligent i2c writes have the potential to cause bricks, I think an unlock mechanism that makes the user aware that the script is about to write to i2c would be pretty important to have.

@rosaage
Copy link
Copy Markdown
Contributor Author

rosaage commented Oct 25, 2025

Writing now returns nothing (nil) on a success or lua error on fail

@d0k3 d0k3 added the lua label Mar 18, 2026
@ihaveamac
Copy link
Copy Markdown
Contributor

The write function should use a list of registers allowed to write to (list below provided by Wolfvak). Unless GM9 has this already enforced somewhere else that I'm not aware of.

0x15-0x17, 0x1c-0x1f, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d

@rosaage
Copy link
Copy Markdown
Contributor Author

rosaage commented Mar 23, 2026

A whitelist is now implemented, as well as some register constants and bitmasks/values for some of the registers.
I tested writing outside this range, and tested using the new constants:

Test script
print("Hello World!")

print(CONSOLE_TYPE)

-- device 3 is the mcu
-- 0x09 is volume slider
ui.echo(i2c.read(3, 0x09, 1)[1])

-- Read voltage from MCU device
local voltage_raw = i2c.read(i2c.dev.MCU, i2c.mcu.reg.BATTERY_VOLTAGE, 1)
local voltage = voltage_raw[1] * 5 / 256.0
print("System voltage:", voltage, "V")

-- Read Power status
local power_state = i2c.read(i2c.dev.MCU, i2c.mcu.reg.POWER_STATUS, 1)
print("power_state: ", power_state[1])
print("Power status flags:", i2c.mcu.power_status_flags.SHELL_OPEN, " - ", i2c.mcu.power_status_flags.CHARGING)
if power_state[1] & i2c.mcu.power_status_flags.SHELL_OPEN ~= 0 then
print("Shell is open")
end
if power_state[1] & i2c.mcu.power_status_flags.CHARGING ~= 0 then
print("Charging")
end

-- 0x28 is Brightness of the WiFi/Power LED 
ui.echo("Reg: "..i2c.read(3, 0x28, 1)[1])
i2c.write(3, 0x28, {200})
ui.echo("Reg: "..i2c.read(3, 0x28, 1)[1])
i2c.write(3, 0x28, {150})
ui.echo("Reg: "..i2c.read(3, 0x28, 1)[1])
i2c.write(3, 0x28, {70})
ui.echo("Reg: "..i2c.read(3, 0x28, 1)[1])

-- 0x15-17 is not in use
local data = i2c.read(3, 0x15, 3)
ui.echo(string.format("Reg: %d, %d, %d", data[1], data[2], data[3]))
i2c.write(3, 0x15, {20, 30, 40})
data = i2c.read(3, 0x15, 3)
ui.echo(string.format("Reg: %d, %d, %d", data[1], data[2], data[3]))

-- Test write outside of the range:
ui.echo("Reg 36: "..i2c.read(3, 0x36, 1)[1])
ui.echo("Attempting to write to reg 36, which is outside of the allowed range...")
i2c.write(3, 0x36, {0})
ui.echo("Reg 36: "..i2c.read(3, 0x36, 1)[1])

This correctly reports that the shell is open, and if it is charging or not.
The register test at the end throws an error as expected. (0x36 is not on the whitelist)

@d0k3 d0k3 merged commit 584b68b into d0k3:master Mar 25, 2026
1 check passed
@d0k3
Copy link
Copy Markdown
Owner

d0k3 commented Mar 25, 2026

Merged, thanks a lot for your contribution! I also like the testing script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants