Skip to content

Instantly share code, notes, and snippets.

@ktemkin
Created November 18, 2020 00:24
Show Gist options
  • Save ktemkin/bdab27cfff02fddb6de70cb1d6e285bb to your computer and use it in GitHub Desktop.
Save ktemkin/bdab27cfff02fddb6de70cb1d6e285bb to your computer and use it in GitHub Desktop.
Working mockup for controlling a JOOFOO lamp with the YS1.
#!/usr/bin/env python3
"""
Silly YS1 mockup of an interface for the JOOFOO remote-controlled lamp.
"""
import sys
import argparse
# Normally, we'd just import *, but rfcat likes to do things like export functions called str(),
# so we'll be a bit more selective.
from rflib import RfCat, MOD_ASK_OOK, SYNCM_NONE
# Store our frequency targets.
CARRIER_FREQUENCY = int(434.64e6)
DATA_RATE = int(006.25e3)
# Store the bytes that can send each of our two-bit pairs.
# Each byte we send to the YS1 encodes two symbols, which we encode as either a 0 (0b1000)
# or as a 1 (0b1110) in order to create the proper pattern on transmit.
# Note: 0 encodes as [0b1000 = 0x8], 1 encodes as [0b1110 = 0xe].
PATTERNS = {
# Full sets of two bytes...
"00": 0b0111_0111,
"01": 0b0111_0001,
"10": 0b0001_0111,
"11": 0b0001_0001,
# ... and values for any left-over "straggler" bits that can occur at the end of the communication.
# We send all zeroes after the relevant bits to generate RF silence.
"0": 0b0111_1111,
"1": 0b0001_1111
}
#
# Command Line Interface.
#
parser = argparse.ArgumentParser(description="YS1 JOOFOO lamp controller.")
parser.add_argument('-P', '--power', action="store_const", const="1", default="0")
parser.add_argument('-C', '--color', action="store_const", const="1", default="0")
parser.add_argument('-U', '--up', action="store_const", const="1", default="0")
parser.add_argument('-D', '--down', action="store_const", const="1", default="0")
parser.add_argument('-M', '--max', action="store_true")
parser.add_argument('-n', '--repeat', type=int, default=8)
args = parser.parse_args()
# Convenience: if max is set, turn the brightness up all the way by setting UP and a large repeat.
if args.max:
args.up = "1"
args.repeat = 128
# Use the CLI's set of buttons to build a button mask; which we'll eventually send to the device.
buttons = f"{args.down}{args.up}{args.color}{args.power}"
# If no buttons are pressed in the given mask, fail out early.
if buttons == "0000":
print("No buttons pressed; nothing to do!\n")
sys.exit(0)
#
# Core script.
#
# Create a new connection to our RfCat dongle.
d = RfCat()
# Create a helper function that performs PWM transmissions.
def transmit_pwm(bit_string, repeat=1, padding=2):
""" Transmits the appropriate PWM values for a string of 1's and 0's. """
bytes_to_send = bytearray()
# First, strip all underscores from our bit string, so we can use it as a place separator.
bit_string = bit_string.replace('_', '')
# As long as we have data left to send, handle sending it.
while bit_string:
# Extract up to two bits to encode...
pair_to_encode = bit_string[0:2]
bit_string = bit_string[2:]
# ... and encode them into bytes to be sent to the modem.
encoded_pair = PATTERNS[pair_to_encode]
bytes_to_send.append(encoded_pair)
# Pad our bytes to send with data before and after.
bytes_to_send = (b"\xFF" * padding) + bytes_to_send + (b"\xFF" * padding)
# Set the packet length equal to what we'll be sending.
d.makePktFLEN(len(bytes_to_send))
# Finally, transmit the bytes we've generated.
for _ in range(repeat):
d.RFxmit(bytes_to_send)
# Configure the modem to use raw on-off-keying at the lamp's frequency, and at the "data rate"
# we've decided to use. This data rate is one quarter the symbol rate; and allows us to create
# raw pulse modulated symbols directly from "on"s and "off"s.
d.setFreq(CARRIER_FREQUENCY)
d.setMdmModulation(MOD_ASK_OOK)
d.setMdmDRate(DATA_RATE)
d.setMdmSyncMode(SYNCM_NONE)
d.setMdmSyncWord(0)
# Finally, perform the lamp button presses.
transmit_pwm(f"0100_1000_0110_0101_0110_{buttons}_0", repeat=args.repeat)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment