Skip to content

Commit

Permalink
Add inital version of ka3005p python package
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicoretti committed Apr 19, 2024
1 parent 0e95ac2 commit 887bc4a
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
[package]
name = "ka3005p"
version = "0.4.0"
authors = ["Nicola Coretti <nico.coretti@gmail.com>", "Daniel Hartig <daniel-hartig@gmx.de>", "Jack Newman <Jack.Newman12@gmail.com>"]
version = "0.5.0"
authors = [
"Nicola Coretti <nico.coretti@gmail.com>",
"Daniel Hartig <daniel-hartig@gmx.de>",
"Jack Newman <Jack.Newman12@gmail.com>"
]
edition = "2021"
autobins = false
license = "MIT OR Apache-2.0"
Expand All @@ -21,7 +25,10 @@ anyhow = "1.*"
clap = { version = "4.*", features = ["derive"] }
log = "0.4.21"
env_logger = "0.11.3"
pyo3 = "0.21.2"
pyo3 = { version = "0.21.2", optional = true }

[features]
python_module = ["pyo3"]

[lib]
name = "ka3005p"
Expand Down
18 changes: 17 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,29 @@ build-backend = "maturin"
[project]
name = "ka3005p"
requires-python = ">=3.8"
description = "TBD"
readme = "python/README.md"
maintainers = [
{ name = "Nicola Coretti", email ="nico.coretti@gmail.com" }
]
classifiers = [
"Development Status :: 4 - Beta",

"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",

"License :: OSI Approved :: MIT License",
"License :: OSI Approved :: Apache Software License",

"Topic :: System :: Hardware :: Universal Serial Bus (USB) :: Miscellaneous",
]
dynamic = ["version"]

[project.optional-dependencies]
dev = ["pytest", "maturin_import_hook"]

[tool.maturin]
features = ["pyo3/extension-module"]
pythonj-source = "python"
features = ["pyo3/extension-module", "python_module"]

81 changes: 81 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# KA3005P Python Library
This Python library provides a high-level interface to control Korad, Tenma, RS, Velleman, Stamos, and other compatible power supplies via their serial interface. It is built on top of a Rust library [ka3005p](https://crates.io/crates/ka3005p).

## Installation
Make sure you have Python 3.8 or higher installed. You can install the library from PyPI by running the following command in your terminal:

```bash
pip install ka3005p
```

Note:
If you are using Linux, you may need to add users to the dialout group or adjust the permissions of the serial interfaces that are needed to communicate with the power supply.

To add a user to the dialout group, you can use the following command:
`sudo usermod -a -G dialout username`(please remember that logging out and logging back in may be required for the changes to take effect).

## Usage

```python
from ka3005p import PowerSupply

# List connected power supplies
devices = PowerSupply.list_power_supplies()

# Take control over a power supply
#
# Attention: Only one handle at the time is allowed to exist for a single PowerSupply.
#
# Note: If no parameter is specified the first power supply which was found will be used.
power_supply = PowerSupply(devices[0])


# Prepare output voltage and current
power_supply.voltage = 12.0
power_supply = 0.5

# Turn on the output
power_supply.enable()

# Read voltage and current
v = power_supply.voltage
a = power_supply.current

# Store current settings in memory slot 1
power_supply.save(1)

# Turn off the output
power_supply.disable()

# Load settings from memory slot 2
power_supply.load(2)
```

## Building from Source
If you need to build the library from the source, you'll need Python development headers and Rust installed:

1. Clone the repository:
```bash
git clone git@github.com:nicoretti/ka3005p.git
cd ka3005p
```

2. Build and install using maturin (install it if it's not installed):
```bash
pip install maturin
maturin develop
```

3. To build a wheel:
```bash
maturin build --release
```

## License
This project is licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))

- MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))

at your option.
32 changes: 32 additions & 0 deletions python/examples/basics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from ka3005p import PowerSupply

# List connected power supplies
devices = PowerSupply.list_power_supplies()

# Take control over a power supply
#
# Attention: Only one handle at the time is allowed to exist for a single PowerSupply.
#
# Note: If no parameter is specified the first power supply which was found will be used.
power_supply = PowerSupply(devices[0])


# Prepare output voltage and current
power_supply.voltage = 12.0
power_supply = 0.5

# Turn on the output
power_supply.enable()

# Read voltage and current
v = power_supply.voltage
a = power_supply.current

# Store current settings in memory slot 1
power_supply.save(1)

# Turn off the output
power_supply.disable()

# Load settings from memory slot 2
power_supply.load(2)
1 change: 1 addition & 0 deletions python/ka3005p/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ka3005p import PowerSupply
5 changes: 1 addition & 4 deletions typeshed.py → python/ka3005p/typeshed.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# power_supply.pyi
# TODO: Fix outdated API bits
from typing import List, Optional, Any, Union

class PowerSupply:
Expand Down Expand Up @@ -37,9 +37,6 @@ def off(self) -> bool: ...
@off.setter
def off(self, disable: bool) -> None: ...

#@property
#def beep(self) -> bool: ...

@beep.setter
def beep(self, enable: bool) -> None: ...

Expand Down
2 changes: 1 addition & 1 deletion src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#![deny(warnings)]
use anyhow::Context;
use clap::Parser;
use env_logger;
use std::convert::TryInto;
use std::io::BufRead;
use std::process::exit;
use env_logger;

fn main() -> ::anyhow::Result<(), anyhow::Error> {
human_panic::setup_panic!();
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ use std::time;

#[doc(hidden)] // Users of the library shouldn't use this
pub mod cli;
pub mod py_module;
pub use serialport;
#[cfg(feature = "python_module")]
pub mod py_module;

/// On / Off
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
Expand Down
77 changes: 47 additions & 30 deletions src/py_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,20 @@ use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use pyo3::PyErr;

// TODO: Add custom exception for this module see:
// https://pyo3.rs/main/exception
///
// TODO's:
//
// * Build python extension based on feature flag
// * Add custom Exception to the module see: https://pyo3.rs/main/exception
// * Expose IDN
// * Expose Mode status
// * Expose Channel status
// * Expose Lock status
// * Export Lock, Channel, Mode and Switch Enum types to python
// * Wrap in python (src) based project
// - provide typeshed
// - provide readme
// - provide usage examples
//

struct Ka3005pError(Error);

Expand Down Expand Up @@ -57,7 +68,6 @@ impl PowerSupply {
}
}

// TODO: Remove unwraps and add proper error handling/conversion
#[pymethods]
impl PowerSupply {
#[new]
Expand Down Expand Up @@ -114,6 +124,8 @@ impl PowerSupply {
}

/// Set the output current of the power supply.
/// Args:
/// i: ampere's to be set.
#[setter]
fn set_current(&mut self, i: f32) -> PyResult<()> {
let command = Command::Current(i);
Expand All @@ -128,6 +140,9 @@ impl PowerSupply {
}

/// Set the output voltage of the power supply.
///
/// Args:
/// v: volt's to be set.
#[setter]
fn set_voltage(&mut self, v: f32) -> PyResult<()> {
let command = Command::Voltage(v);
Expand All @@ -141,39 +156,39 @@ impl PowerSupply {
Ok(format!("{}", status))
}

/// Get the power supply's on/off state.
#[getter]
fn get_on(&mut self) -> PyResult<bool> {
let status = self._status()?;
Ok(status.flags.output.into())
/// Enable the output of the the power supply.
fn enable(&mut self) -> PyResult<()> {
let command = Command::Power(Switch::On);
self._execute(command)
}

/// Set the power supply's on/off state.
#[setter]
fn set_on(&mut self, enable: bool) -> PyResult<()> {
let command = Command::Power(Switch::from(enable));
/// Disable the output of the the power supply.
fn disable(&mut self) -> PyResult<()> {
let command = Command::Power(Switch::Off);
self._execute(command)
}

/// Get the power supply's off/on state.
#[getter]
fn get_off(&mut self) -> PyResult<bool> {
fn is_off(&mut self) -> PyResult<bool> {
let status = self._status()?;
Ok(!Into::<bool>::into(status.flags.output))
}

/// Set the power supply's off/on state.
#[setter]
fn set_off(&mut self, disable: bool) -> PyResult<()> {
let command = Command::Power(Switch::from(!disable));
self._execute(command)
/// Get the power supply's on/off state.
fn is_on(&mut self) -> PyResult<bool> {
let status = self._status()?;
Ok(status.flags.output.into())
}

//#[getter]
//fn get_beep(&mut self) -> PyResult<bool> {
// let status = self.status()?;
// Ok(status.beep.into())
//}
/// Is beeping enabled.
///
/// Returns:
/// `True` if beeping is enabled, otherwise `False`.
#[getter]
fn get_beep(&mut self) -> PyResult<bool> {
let status = self._status()?;
Ok(status.flags.beep.into())
}

/// Set the beep state of the power supply.
#[setter]
Expand All @@ -194,29 +209,31 @@ impl PowerSupply {
/// Load stored settings/configuration to the power supply.
///
/// Args:
/// id: Memory slot to load from (M: 1-5).
/// id: Memory slot to load from (M: -5).
fn load(&mut self, id: u32) -> PyResult<()> {
let command = Command::Load(id);
self._execute(command)
}

/// Set the over current protection state of the power supply.
///
/// Args:
/// enable: ocp if `True`, otherwise disable ocp.
#[setter]
fn set_ocp(&mut self, enable: bool) -> PyResult<()> {
let command = Command::Ocp(Switch::from(enable));
self._execute(command)
}

/// Set the over voltage protection state of the power supply.
///
/// Args:
/// enable: ovp if `True`, otherwise disable ovp.
#[setter]
fn set_ovp(&mut self, enable: bool) -> PyResult<()> {
let command = Command::Ovp(Switch::from(enable));
self._execute(command)
}

// implement *IDN?
//fn identification(&mut self, id: u32) -> PyResult<()> {
//}
}

/// Python module for interfacing with the PowerSupply class.
Expand Down

0 comments on commit 887bc4a

Please sign in to comment.